From 451ec31a049cac1a2c05527d29726295e10f53ba Mon Sep 17 00:00:00 2001 From: Andrea Bocci Date: Mon, 14 Jan 2019 12:50:32 +0100 Subject: [PATCH] Squash Patatrack developments on top of CMSSW_10_4_0 --- .../Common/interface/device_unique_ptr.h | 16 + .../Common/interface/host_unique_ptr.h | 16 + CUDADataFormats/SiPixelCluster/BuildFile.xml | 8 + .../interface/SiPixelClustersCUDA.h | 73 + .../SiPixelCluster/src/SiPixelClustersCUDA.cc | 24 + CUDADataFormats/SiPixelDigi/BuildFile.xml | 7 + .../SiPixelDigi/interface/SiPixelDigisCUDA.h | 65 + .../SiPixelDigi/src/SiPixelDigisCUDA.cc | 25 + .../SiPixelGainCalibrationForHLTGPURcd.h | 14 + .../src/SiPixelGainCalibrationForHLTGPURcd.cc | 5 + CalibTracker/SiPixelESProducers/BuildFile.xml | 2 + .../SiPixelGainCalibrationForHLTGPU.h | 32 + .../SiPixelESProducers/plugins/BuildFile.xml | 2 + ...PixelGainCalibrationForHLTGPUESProducer.cc | 47 + .../src/ES_SiPixelGainCalibrationForHLTGPU.cc | 4 + .../src/SiPixelGainCalibrationForHLTGPU.cc | 98 + .../interface/SiPixelGainForHLTonGPU.h | 73 + .../Applications/python/ConfigBuilder.py | 2 + .../ProcessModifiers/python/gpu_cff.py | 5 + .../python/riemannFitGPU_cff.py | 5 + .../ProcessModifiers/python/riemannFit_cff.py | 5 + .../PyReleaseValidation/python/relval_2017.py | 2 +- .../python/relval_standard.py | 3 + .../python/relval_steps.py | 27 + .../python/relval_upgrade.py | 13 +- .../python/upgradeWorkflowComponents.py | 35 +- .../StandardSequences/python/RawToDigi_cff.py | 3 +- .../python/Reconstruction_cff.py | 2 +- .../GeometrySurface/interface/SOARotation.h | 42 + .../GeometrySurface/test/BuildFile.xml | 13 + .../test/gpuFrameTransformKernel.cu | 45 + .../test/gpuFrameTransformTest.cpp | 140 + .../python/SiPixelRawToDigi_cfi.py | 7 + .../Tracking/python/SeedingMigration.py | 5 +- .../interface/phase1PixelTopology.h | 70 + .../test/phase1PixelTopology_t.cpp | 8 + HeterogeneousCore/CUDACore/BuildFile.xml | 11 + .../CUDACore/interface/CUDAESProduct.h | 93 + .../CUDACore/interface/GPUCuda.h | 42 + HeterogeneousCore/CUDACore/src/GPUCuda.cc | 105 + HeterogeneousCore/CUDAServices/BuildFile.xml | 12 + .../CUDAServices/bin/BuildFile.xml | 3 + .../bin/cudaComputeCapabilities.cpp | 23 + .../CUDAServices/interface/CUDAService.h | 148 + .../interface/numberOfCUDADevices.h | 9 + .../CUDAServices/plugins/BuildFile.xml | 1 + .../plugins/CUDAMonitoringService.cc | 100 + .../CUDAServices/plugins/NVProfilerService.cc | 593 ++-- .../CUDAServices/plugins/plugins.cc | 4 + .../CUDAServices/scripts/cmsCudaRebuild.sh | 10 + .../CUDAServices/scripts/cmsCudaSetup.sh | 19 + .../CUDAServices/scripts/cudaPreallocate.py | 38 + .../CUDAServices/scripts/nvprof-remote | 21 + .../CUDAServices/src/CUDAService.cc | 492 +++ .../CUDAServices/src/CachingDeviceAllocator.h | 710 ++++ .../CUDAServices/src/CachingHostAllocator.h | 643 ++++ .../CUDAServices/src/numberOfCUDADevices.cc | 8 + .../CUDAServices/test/BuildFile.xml | 5 + .../CUDAServices/test/testCUDAService.cpp | 242 ++ .../CUDAServices/test/testCUDAService.py | 13 + .../CUDAServices/test/test_main.cc | 2 + .../CUDAServices/test/test_main.cpp | 2 + HeterogeneousCore/CUDAUtilities/BuildFile.xml | 2 + .../interface/AtomicPairCounter.h | 54 + .../interface/CUDAHostAllocator.h | 57 + .../CUDAUtilities/interface/GPUSimpleVector.h | 119 + .../CUDAUtilities/interface/GPUVecArray.h | 104 + .../CUDAUtilities/interface/HistoContainer.h | 335 ++ .../CUDAUtilities/interface/cudaCheck.h | 40 + .../CUDAUtilities/interface/cuda_assert.h | 18 + .../CUDAUtilities/interface/cuda_cxx17.h | 72 + .../interface/cudastdAlgorithm.h | 83 + .../interface/getCudaDrvErrorString.h | 17 + .../CUDAUtilities/interface/prefixScan.h | 55 + .../CUDAUtilities/interface/radixSort.h | 283 ++ .../CUDAUtilities/test/AtomicPairCounter_t.cu | 66 + .../CUDAUtilities/test/BuildFile.xml | 72 + .../CUDAUtilities/test/HistoContainer_t.cpp | 110 + .../CUDAUtilities/test/HistoContainer_t.cu | 136 + .../CUDAUtilities/test/OneHistoContainer_t.cu | 145 + .../CUDAUtilities/test/OneToManyAssoc_t.cu | 177 + .../CUDAUtilities/test/assert_t.cu | 14 + .../CUDAUtilities/test/cudastdAlgorithm_t.cpp | 41 + .../CUDAUtilities/test/cudastdAlgorithm_t.cu | 49 + .../CUDAUtilities/test/prefixScan_t.cu | 126 + .../CUDAUtilities/test/radixSort_t.cu | 203 ++ .../test/test_GPUSimpleVector.cu | 87 + HeterogeneousCore/MPICore/BuildFile.xml | 5 + .../MPICore/interface/WrapperHandle.h | 290 ++ .../MPICore/interface/serialization.h | 99 + .../MPICore/src/serialization.cc | 101 + .../MPIServices/plugins/BuildFile.xml | 4 + .../MPIServices/plugins/MPIService.cc | 39 + HeterogeneousCore/Producer/BuildFile.xml | 9 + HeterogeneousCore/Producer/README.md | 468 +++ .../Producer/interface/DeviceWrapper.h | 20 + .../interface/HeterogeneousEDProducer.h | 232 ++ .../Producer/interface/HeterogeneousEvent.h | 134 + .../Producer/src/HeterogeneousEDProducer.cc | 70 + HeterogeneousCore/Producer/test/BuildFile.xml | 32 + .../Producer/test/TestGPUConcurrency.cc | 46 + .../Producer/test/TestGPUConcurrency.h | 44 + .../Producer/test/TestGPUConcurrencyAlgo.cu | 17 + .../Producer/test/TestGPUConcurrencyAlgo.h | 23 + .../TestHeterogeneousEDProducerAnalyzer.cc | 56 + .../test/TestHeterogeneousEDProducerGPU.cc | 156 + .../TestHeterogeneousEDProducerGPUHelpers.cu | 203 ++ .../TestHeterogeneousEDProducerGPUHelpers.h | 44 + .../TestHeterogeneousEDProducerGPUMock.cc | 164 + .../Producer/test/testGPUConcurrency.py | 29 + .../Producer/test/testGPUMock_cfg.py | 36 + .../Producer/test/testGPU_cfg.py | 35 + HeterogeneousCore/Product/BuildFile.xml | 7 + .../Product/interface/HeterogeneousDeviceId.h | 45 + .../Product/interface/HeterogeneousProduct.h | 338 ++ .../interface/HeterogeneousProductBase.h | 41 + .../Product/src/HeterogeneousProduct.cc | 4 + HeterogeneousCore/Product/src/classes.h | 9 + HeterogeneousCore/Product/src/classes_def.xml | 4 + HeterogeneousCore/Product/test/BuildFile.xml | 4 + .../Product/test/testHeterogeneousProduct.cpp | 308 ++ .../python/RecoLocalTracker_cff.py | 4 + .../SiPixelClusterizer/BuildFile.xml | 9 + .../interface/PixelTrackingGPUConstants.h | 11 + .../interface/SiPixelFedCablingMapGPU.h | 27 + .../SiPixelFedCablingMapGPUWrapper.h | 63 + .../SiPixelClusterizer/plugins/BuildFile.xml | 13 +- .../SiPixelDigiHeterogeneousConverter.cc | 95 + ...iPixelFedCablingMapGPUWrapperESProducer.cc | 68 + .../plugins/SiPixelRawToClusterGPUKernel.cu | 695 ++++ .../plugins/SiPixelRawToClusterGPUKernel.h | 281 ++ .../SiPixelRawToClusterHeterogeneous.cc | 739 ++++ .../plugins/gpuCalibPixel.h | 125 + .../plugins/gpuClusterChargeCut.h | 97 + .../plugins/gpuClustering.h | 271 ++ .../plugins/gpuClusteringConstants.h | 14 + .../siPixelRawToClusterHeterogeneousProduct.h | 47 + .../SiPixelClusterizerPreSplitting_cfi.py | 8 +- .../python/SiPixelClusterizer_cfi.py | 4 + .../siPixelClustersHeterogeneous_cfi.py | 37 + .../src/ES_SiPixelFedCablingMapGPUWrapper.cc | 4 + .../src/SiPixelFedCablingMapGPUWrapper.cc | 188 + .../SiPixelClusterizer/test/BuildFile.xml | 15 + .../SiPixelClusterizer/test/gpuClustering.cu | 337 ++ RecoLocalTracker/SiPixelRecHits/BuildFile.xml | 5 + .../SiPixelRecHits/interface/PixelCPEBase.h | 3 +- .../SiPixelRecHits/interface/PixelCPEFast.h | 98 + .../SiPixelRecHits/interface/pixelCPEforGPU.h | 289 ++ .../SiPixelRecHits/plugins/BuildFile.xml | 17 +- .../plugins/PixelCPEFastESProducer.cc | 103 + .../SiPixelRecHits/plugins/PixelRecHits.cu | 206 ++ .../SiPixelRecHits/plugins/PixelRecHits.h | 73 + .../plugins/SiPixelRecHitHeterogeneous.cc | 324 ++ .../SiPixelRecHits/plugins/gpuPixelRecHits.h | 157 + .../siPixelRecHitsHeterogeneousProduct.h | 77 + .../python/PixelCPEESProducers_cff.py | 1 + .../SiPixelRecHits/python/PixelCPEFast_cfi.py | 31 + .../SiPixelRecHits/src/PixelCPEFast.cc | 468 +++ .../python/RecoPixelVertexing_cff.py | 4 +- .../customizePixelTracksForProfiling.py | 26 + .../PixelTrackFitting/BuildFile.xml | 1 + .../PixelTrackFitting/interface/FitResult.h | 112 + .../PixelFitterByRiemannParaboloid.h | 28 + .../PixelTrackFitting/interface/RiemannFit.h | 1181 +++++++ .../pixelTrackHeterogeneousProduct.h | 35 + .../PixelTrackFitting/plugins/BuildFile.xml | 10 +- .../PixelFitterByRiemannParaboloidProducer.cc | 53 + .../plugins/PixelTrackProducer.cc | 71 +- .../plugins/PixelTrackProducer.h | 13 +- .../plugins/PixelTrackProducerFromCUDA.cc | 215 ++ .../plugins/PixelTrackReconstructionGPU.cc | 195 ++ .../plugins/PixelTrackReconstructionGPU.cu | 211 ++ .../plugins/PixelTrackReconstructionGPU.h | 43 + .../PixelTrackFitting/plugins/storeTracks.h | 77 + .../python/PixelTracks_cff.py | 19 + .../pixelFitterByRiemannParaboloid_cfi.py | 6 + .../src/PixelFitterByRiemannParaboloid.cc | 115 + .../PixelTrackFitting/test/BuildFile.xml | 45 +- .../test/PixelTrackRiemannFit.cc | 435 +++ .../PixelTrackFitting/test/testEigenGPU.cu | 224 ++ .../test/testEigenGPUNoFit.cu | 213 ++ .../test/testEigenJacobian.cpp | 94 + .../PixelTrackFitting/test/testRiemannFit.cpp | 88 + .../PixelTrackFitting/test/test_common.h | 51 + .../interface/CAHitQuadrupletGenerator.h | 2 +- .../PixelTriplets/interface/CircleEq.h | 128 + .../PixelTriplets/plugins/BuildFile.xml | 25 +- .../PixelTriplets/plugins/CAConstants.h | 34 + .../CAHitNtupletHeterogeneousEDProducer.cc | 182 + .../plugins/CAHitQuadrupletGeneratorGPU.cc | 239 ++ .../plugins/CAHitQuadrupletGeneratorGPU.h | 121 + .../CAHitQuadrupletGeneratorKernels.cc | 44 + .../CAHitQuadrupletGeneratorKernels.cu | 333 ++ .../plugins/CAHitQuadrupletGeneratorKernels.h | 51 + .../PixelTriplets/plugins/GPUCACell.h | 196 ++ .../PixelTriplets/plugins/RecHitsMap.h | 76 + .../PixelTriplets/plugins/RiemannFitOnGPU.cc | 35 + .../PixelTriplets/plugins/RiemannFitOnGPU.cu | 195 ++ .../PixelTriplets/plugins/RiemannFitOnGPU.h | 60 + .../PixelTriplets/plugins/gpuFishbone.h | 93 + .../PixelTriplets/plugins/gpuPixelDoublets.h | 199 ++ .../plugins/pixelTuplesHeterogeneousProduct.h | 65 + .../python/caHitQuadrupletEDProducer_cfi.py | 4 + .../PixelTriplets/test/BuildFile.xml | 5 +- .../PixelTriplets/test/CircleEq_t.cpp | 99 + .../PixelTriplets/test/fastDPHI_t.cpp | 197 ++ .../PixelTriplets/test/pixHits.ipynb | 3120 +++++++++++++++++ .../PixelVertexFinding/BuildFile.xml | 4 + .../pixelVertexHeterogeneousProduct.h | 43 + .../python/PixelVertexes_cfi.py | 3 + .../src/PixelVertexHeterogeneousConverter.cc | 55 + .../src/PixelVertexHeterogeneousProducer.cc | 283 ++ .../PixelVertexFinding/src/gpuClusterTracks.h | 202 ++ .../PixelVertexFinding/src/gpuFitVertices.h | 101 + .../PixelVertexFinding/src/gpuSortByPt2.h | 59 + .../PixelVertexFinding/src/gpuSplitVertices.h | 129 + .../PixelVertexFinding/src/gpuVertexFinder.cu | 151 + .../PixelVertexFinding/src/gpuVertexFinder.h | 101 + .../PixelVertexFinding/test/BuildFile.xml | 7 + .../test/gpuVertexFinder_t.cu | 298 ++ .../plugins/BuildFile.xml | 13 +- .../plugins/ClusterSLOnGPU.cu | 196 ++ .../plugins/ClusterSLOnGPU.h | 40 + .../ClusterTPAssociationHeterogeneous.cc | 409 +++ ...sterTPAssociationHeterogeneousConverter.cc | 59 + ...rackerHitAssociationHeterogeneousProduct.h | 47 + .../python/tpClusterProducer_cfi.py | 8 + .../python/PostProcessorTracker_cfi.py | 4 +- .../RecoTrack/python/TrackValidation_cff.py | 59 +- Validation/RecoTrack/python/plotting/html.py | 10 + .../python/plotting/trackingPlots.py | 18 +- 231 files changed, 25906 insertions(+), 338 deletions(-) create mode 100644 CUDADataFormats/Common/interface/device_unique_ptr.h create mode 100644 CUDADataFormats/Common/interface/host_unique_ptr.h create mode 100644 CUDADataFormats/SiPixelCluster/BuildFile.xml create mode 100644 CUDADataFormats/SiPixelCluster/interface/SiPixelClustersCUDA.h create mode 100644 CUDADataFormats/SiPixelCluster/src/SiPixelClustersCUDA.cc create mode 100644 CUDADataFormats/SiPixelDigi/BuildFile.xml create mode 100644 CUDADataFormats/SiPixelDigi/interface/SiPixelDigisCUDA.h create mode 100644 CUDADataFormats/SiPixelDigi/src/SiPixelDigisCUDA.cc create mode 100644 CalibTracker/Records/interface/SiPixelGainCalibrationForHLTGPURcd.h create mode 100644 CalibTracker/Records/src/SiPixelGainCalibrationForHLTGPURcd.cc create mode 100644 CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h create mode 100644 CalibTracker/SiPixelESProducers/plugins/SiPixelGainCalibrationForHLTGPUESProducer.cc create mode 100644 CalibTracker/SiPixelESProducers/src/ES_SiPixelGainCalibrationForHLTGPU.cc create mode 100644 CalibTracker/SiPixelESProducers/src/SiPixelGainCalibrationForHLTGPU.cc create mode 100644 CondFormats/SiPixelObjects/interface/SiPixelGainForHLTonGPU.h create mode 100644 Configuration/ProcessModifiers/python/gpu_cff.py create mode 100644 Configuration/ProcessModifiers/python/riemannFitGPU_cff.py create mode 100644 Configuration/ProcessModifiers/python/riemannFit_cff.py create mode 100644 DataFormats/GeometrySurface/test/gpuFrameTransformKernel.cu create mode 100644 DataFormats/GeometrySurface/test/gpuFrameTransformTest.cpp create mode 100644 HeterogeneousCore/CUDACore/BuildFile.xml create mode 100644 HeterogeneousCore/CUDACore/interface/CUDAESProduct.h create mode 100644 HeterogeneousCore/CUDACore/interface/GPUCuda.h create mode 100644 HeterogeneousCore/CUDACore/src/GPUCuda.cc create mode 100644 HeterogeneousCore/CUDAServices/BuildFile.xml create mode 100644 HeterogeneousCore/CUDAServices/bin/BuildFile.xml create mode 100644 HeterogeneousCore/CUDAServices/bin/cudaComputeCapabilities.cpp create mode 100644 HeterogeneousCore/CUDAServices/interface/CUDAService.h create mode 100644 HeterogeneousCore/CUDAServices/interface/numberOfCUDADevices.h create mode 100644 HeterogeneousCore/CUDAServices/plugins/CUDAMonitoringService.cc create mode 100644 HeterogeneousCore/CUDAServices/plugins/plugins.cc create mode 100755 HeterogeneousCore/CUDAServices/scripts/cmsCudaRebuild.sh create mode 100755 HeterogeneousCore/CUDAServices/scripts/cmsCudaSetup.sh create mode 100755 HeterogeneousCore/CUDAServices/scripts/cudaPreallocate.py create mode 100755 HeterogeneousCore/CUDAServices/scripts/nvprof-remote create mode 100644 HeterogeneousCore/CUDAServices/src/CUDAService.cc create mode 100644 HeterogeneousCore/CUDAServices/src/CachingDeviceAllocator.h create mode 100644 HeterogeneousCore/CUDAServices/src/CachingHostAllocator.h create mode 100644 HeterogeneousCore/CUDAServices/src/numberOfCUDADevices.cc create mode 100644 HeterogeneousCore/CUDAServices/test/BuildFile.xml create mode 100644 HeterogeneousCore/CUDAServices/test/testCUDAService.cpp create mode 100644 HeterogeneousCore/CUDAServices/test/testCUDAService.py create mode 100644 HeterogeneousCore/CUDAServices/test/test_main.cc create mode 100644 HeterogeneousCore/CUDAServices/test/test_main.cpp create mode 100644 HeterogeneousCore/CUDAUtilities/BuildFile.xml create mode 100644 HeterogeneousCore/CUDAUtilities/interface/AtomicPairCounter.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/cuda_cxx17.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/getCudaDrvErrorString.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/prefixScan.h create mode 100644 HeterogeneousCore/CUDAUtilities/interface/radixSort.h create mode 100644 HeterogeneousCore/CUDAUtilities/test/AtomicPairCounter_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/BuildFile.xml create mode 100644 HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp create mode 100644 HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/OneToManyAssoc_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/assert_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cpp create mode 100644 HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/prefixScan_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/radixSort_t.cu create mode 100644 HeterogeneousCore/CUDAUtilities/test/test_GPUSimpleVector.cu create mode 100644 HeterogeneousCore/MPICore/BuildFile.xml create mode 100644 HeterogeneousCore/MPICore/interface/WrapperHandle.h create mode 100644 HeterogeneousCore/MPICore/interface/serialization.h create mode 100644 HeterogeneousCore/MPICore/src/serialization.cc create mode 100644 HeterogeneousCore/MPIServices/plugins/BuildFile.xml create mode 100644 HeterogeneousCore/MPIServices/plugins/MPIService.cc create mode 100644 HeterogeneousCore/Producer/BuildFile.xml create mode 100644 HeterogeneousCore/Producer/README.md create mode 100644 HeterogeneousCore/Producer/interface/DeviceWrapper.h create mode 100644 HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h create mode 100644 HeterogeneousCore/Producer/interface/HeterogeneousEvent.h create mode 100644 HeterogeneousCore/Producer/src/HeterogeneousEDProducer.cc create mode 100644 HeterogeneousCore/Producer/test/BuildFile.xml create mode 100644 HeterogeneousCore/Producer/test/TestGPUConcurrency.cc create mode 100644 HeterogeneousCore/Producer/test/TestGPUConcurrency.h create mode 100644 HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.cu create mode 100644 HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.h create mode 100644 HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerAnalyzer.cc create mode 100644 HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPU.cc create mode 100644 HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.cu create mode 100644 HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.h create mode 100644 HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUMock.cc create mode 100644 HeterogeneousCore/Producer/test/testGPUConcurrency.py create mode 100644 HeterogeneousCore/Producer/test/testGPUMock_cfg.py create mode 100644 HeterogeneousCore/Producer/test/testGPU_cfg.py create mode 100644 HeterogeneousCore/Product/BuildFile.xml create mode 100644 HeterogeneousCore/Product/interface/HeterogeneousDeviceId.h create mode 100644 HeterogeneousCore/Product/interface/HeterogeneousProduct.h create mode 100644 HeterogeneousCore/Product/interface/HeterogeneousProductBase.h create mode 100644 HeterogeneousCore/Product/src/HeterogeneousProduct.cc create mode 100644 HeterogeneousCore/Product/src/classes.h create mode 100644 HeterogeneousCore/Product/src/classes_def.xml create mode 100644 HeterogeneousCore/Product/test/BuildFile.xml create mode 100644 HeterogeneousCore/Product/test/testHeterogeneousProduct.cpp create mode 100644 RecoLocalTracker/SiPixelClusterizer/BuildFile.xml create mode 100644 RecoLocalTracker/SiPixelClusterizer/interface/PixelTrackingGPUConstants.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPU.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelDigiHeterogeneousConverter.cc create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelFedCablingMapGPUWrapperESProducer.cc create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.cu create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterHeterogeneous.cc create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/gpuCalibPixel.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusterChargeCut.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/gpuClustering.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusteringConstants.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h create mode 100644 RecoLocalTracker/SiPixelClusterizer/python/siPixelClustersHeterogeneous_cfi.py create mode 100644 RecoLocalTracker/SiPixelClusterizer/src/ES_SiPixelFedCablingMapGPUWrapper.cc create mode 100644 RecoLocalTracker/SiPixelClusterizer/src/SiPixelFedCablingMapGPUWrapper.cc create mode 100644 RecoLocalTracker/SiPixelClusterizer/test/gpuClustering.cu create mode 100644 RecoLocalTracker/SiPixelRecHits/interface/PixelCPEFast.h create mode 100644 RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h create mode 100644 RecoLocalTracker/SiPixelRecHits/plugins/PixelCPEFastESProducer.cc create mode 100644 RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.cu create mode 100644 RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h create mode 100644 RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc create mode 100644 RecoLocalTracker/SiPixelRecHits/plugins/gpuPixelRecHits.h create mode 100644 RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h create mode 100644 RecoLocalTracker/SiPixelRecHits/python/PixelCPEFast_cfi.py create mode 100644 RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc create mode 100644 RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h create mode 100644 RecoPixelVertexing/PixelTrackFitting/interface/PixelFitterByRiemannParaboloid.h create mode 100644 RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h create mode 100644 RecoPixelVertexing/PixelTrackFitting/interface/pixelTrackHeterogeneousProduct.h create mode 100644 RecoPixelVertexing/PixelTrackFitting/plugins/PixelFitterByRiemannParaboloidProducer.cc create mode 100644 RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc create mode 100644 RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cc create mode 100644 RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu create mode 100644 RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.h create mode 100644 RecoPixelVertexing/PixelTrackFitting/plugins/storeTracks.h create mode 100644 RecoPixelVertexing/PixelTrackFitting/python/pixelFitterByRiemannParaboloid_cfi.py create mode 100644 RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc create mode 100644 RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc create mode 100644 RecoPixelVertexing/PixelTrackFitting/test/testEigenGPU.cu create mode 100644 RecoPixelVertexing/PixelTrackFitting/test/testEigenGPUNoFit.cu create mode 100644 RecoPixelVertexing/PixelTrackFitting/test/testEigenJacobian.cpp create mode 100644 RecoPixelVertexing/PixelTrackFitting/test/testRiemannFit.cpp create mode 100644 RecoPixelVertexing/PixelTrackFitting/test/test_common.h create mode 100644 RecoPixelVertexing/PixelTriplets/interface/CircleEq.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/CAConstants.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/CAHitNtupletHeterogeneousEDProducer.cc create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cc create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cu create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/GPUCACell.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/RecHitsMap.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cc create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cu create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/gpuFishbone.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/gpuPixelDoublets.h create mode 100644 RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h create mode 100644 RecoPixelVertexing/PixelTriplets/python/caHitQuadrupletEDProducer_cfi.py create mode 100644 RecoPixelVertexing/PixelTriplets/test/CircleEq_t.cpp create mode 100644 RecoPixelVertexing/PixelTriplets/test/fastDPHI_t.cpp create mode 100644 RecoPixelVertexing/PixelTriplets/test/pixHits.ipynb create mode 100644 RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousConverter.cc create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousProducer.cc create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/gpuFitVertices.h create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/gpuSortByPt2.h create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/gpuSplitVertices.h create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.cu create mode 100644 RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h create mode 100644 RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu create mode 100644 SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.cu create mode 100644 SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.h create mode 100644 SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneous.cc create mode 100644 SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneousConverter.cc create mode 100644 SimTracker/TrackerHitAssociation/plugins/trackerHitAssociationHeterogeneousProduct.h diff --git a/CUDADataFormats/Common/interface/device_unique_ptr.h b/CUDADataFormats/Common/interface/device_unique_ptr.h new file mode 100644 index 0000000000000..1282c52125fa6 --- /dev/null +++ b/CUDADataFormats/Common/interface/device_unique_ptr.h @@ -0,0 +1,16 @@ +#ifndef CUDADataFormats_Common_interface_device_unique_ptr_h +#define CUDADataFormats_Common_interface_device_unique_ptr_h + +#include +#include + +namespace edm { + namespace cuda { + namespace device { + template + using unique_ptr = std::unique_ptr>; + } + } +} + +#endif diff --git a/CUDADataFormats/Common/interface/host_unique_ptr.h b/CUDADataFormats/Common/interface/host_unique_ptr.h new file mode 100644 index 0000000000000..c945d9b0aa027 --- /dev/null +++ b/CUDADataFormats/Common/interface/host_unique_ptr.h @@ -0,0 +1,16 @@ +#ifndef CUDADataFormats_Common_interface_host_unique_ptr_h +#define CUDADataFormats_Common_interface_host_unique_ptr_h + +#include +#include + +namespace edm { + namespace cuda { + namespace host { + template + using unique_ptr = std::unique_ptr>; + } + } +} + +#endif diff --git a/CUDADataFormats/SiPixelCluster/BuildFile.xml b/CUDADataFormats/SiPixelCluster/BuildFile.xml new file mode 100644 index 0000000000000..21c527e7b2f0d --- /dev/null +++ b/CUDADataFormats/SiPixelCluster/BuildFile.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/CUDADataFormats/SiPixelCluster/interface/SiPixelClustersCUDA.h b/CUDADataFormats/SiPixelCluster/interface/SiPixelClustersCUDA.h new file mode 100644 index 0000000000000..ca8a75d178b6c --- /dev/null +++ b/CUDADataFormats/SiPixelCluster/interface/SiPixelClustersCUDA.h @@ -0,0 +1,73 @@ +#ifndef CUDADataFormats_SiPixelCluster_interface_SiPixelClustersCUDA_h +#define CUDADataFormats_SiPixelCluster_interface_SiPixelClustersCUDA_h + +#include "CUDADataFormats/Common/interface/device_unique_ptr.h" + +#include + +class SiPixelClustersCUDA { +public: + SiPixelClustersCUDA() = default; + explicit SiPixelClustersCUDA(size_t feds, size_t nelements, cuda::stream_t<>& stream); + ~SiPixelClustersCUDA() = default; + + SiPixelClustersCUDA(const SiPixelClustersCUDA&) = delete; + SiPixelClustersCUDA& operator=(const SiPixelClustersCUDA&) = delete; + SiPixelClustersCUDA(SiPixelClustersCUDA&&) = default; + SiPixelClustersCUDA& operator=(SiPixelClustersCUDA&&) = default; + + uint32_t *moduleStart() { return moduleStart_d.get(); } + int32_t *clus() { return clus_d.get(); } + uint32_t *clusInModule() { return clusInModule_d.get(); } + uint32_t *moduleId() { return moduleId_d.get(); } + uint32_t *clusModuleStart() { return clusModuleStart_d.get(); } + + uint32_t const *moduleStart() const { return moduleStart_d.get(); } + int32_t const *clus() const { return clus_d.get(); } + uint32_t const *clusInModule() const { return clusInModule_d.get(); } + uint32_t const *moduleId() const { return moduleId_d.get(); } + uint32_t const *clusModuleStart() const { return clusModuleStart_d.get(); } + + uint32_t const *c_moduleStart() const { return moduleStart_d.get(); } + int32_t const *c_clus() const { return clus_d.get(); } + uint32_t const *c_clusInModule() const { return clusInModule_d.get(); } + uint32_t const *c_moduleId() const { return moduleId_d.get(); } + uint32_t const *c_clusModuleStart() const { return clusModuleStart_d.get(); } + + class DeviceConstView { + public: + DeviceConstView() = default; + +#ifdef __CUDACC__ + __device__ __forceinline__ uint32_t moduleStart(int i) const { return __ldg(moduleStart_+i); } + __device__ __forceinline__ int32_t clus(int i) const { return __ldg(clus_+i); } + __device__ __forceinline__ uint32_t clusInModule(int i) const { return __ldg(clusInModule_+i); } + __device__ __forceinline__ uint32_t moduleId(int i) const { return __ldg(moduleId_+i); } + __device__ __forceinline__ uint32_t clusModuleStart(int i) const { return __ldg(clusModuleStart_+i); } +#endif + + friend SiPixelClustersCUDA; + + private: + uint32_t const *moduleStart_; + int32_t const *clus_; + uint32_t const *clusInModule_; + uint32_t const *moduleId_; + uint32_t const *clusModuleStart_; + }; + + DeviceConstView *view() const { return view_d.get(); } + +private: + edm::cuda::device::unique_ptr moduleStart_d; // index of the first pixel of each module + edm::cuda::device::unique_ptr clus_d; // cluster id of each pixel + edm::cuda::device::unique_ptr clusInModule_d; // number of clusters found in each module + edm::cuda::device::unique_ptr moduleId_d; // module id of each module + + // originally from rechits + edm::cuda::device::unique_ptr clusModuleStart_d; + + edm::cuda::device::unique_ptr view_d; // "me" pointer +}; + +#endif diff --git a/CUDADataFormats/SiPixelCluster/src/SiPixelClustersCUDA.cc b/CUDADataFormats/SiPixelCluster/src/SiPixelClustersCUDA.cc new file mode 100644 index 0000000000000..7363c2fd364af --- /dev/null +++ b/CUDADataFormats/SiPixelCluster/src/SiPixelClustersCUDA.cc @@ -0,0 +1,24 @@ +#include "CUDADataFormats/SiPixelCluster/interface/SiPixelClustersCUDA.h" + +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" + +SiPixelClustersCUDA::SiPixelClustersCUDA(size_t feds, size_t nelements, cuda::stream_t<>& stream) { + edm::Service cs; + + moduleStart_d = cs->make_device_unique(nelements+1, stream); + clus_d = cs->make_device_unique< int32_t[]>(feds, stream); + clusInModule_d = cs->make_device_unique(nelements, stream); + moduleId_d = cs->make_device_unique(nelements, stream); + clusModuleStart_d = cs->make_device_unique(nelements+1, stream); + + auto view = cs->make_host_unique(stream); + view->moduleStart_ = moduleStart_d.get(); + view->clus_ = clus_d.get(); + view->clusInModule_ = clusInModule_d.get(); + view->moduleId_ = moduleId_d.get(); + view->clusModuleStart_ = clusModuleStart_d.get(); + + view_d = cs->make_device_unique(stream); + cudaMemcpyAsync(view_d.get(), view.get(), sizeof(DeviceConstView), cudaMemcpyDefault, stream.id()); +} diff --git a/CUDADataFormats/SiPixelDigi/BuildFile.xml b/CUDADataFormats/SiPixelDigi/BuildFile.xml new file mode 100644 index 0000000000000..259aa9f08d054 --- /dev/null +++ b/CUDADataFormats/SiPixelDigi/BuildFile.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/CUDADataFormats/SiPixelDigi/interface/SiPixelDigisCUDA.h b/CUDADataFormats/SiPixelDigi/interface/SiPixelDigisCUDA.h new file mode 100644 index 0000000000000..66ca680effd19 --- /dev/null +++ b/CUDADataFormats/SiPixelDigi/interface/SiPixelDigisCUDA.h @@ -0,0 +1,65 @@ +#ifndef CUDADataFormats_SiPixelDigi_interface_SiPixelDigisCUDA_h +#define CUDADataFormats_SiPixelDigi_interface_SiPixelDigisCUDA_h + +#include "CUDADataFormats/Common/interface/device_unique_ptr.h" +#include "FWCore/Utilities/interface/propagate_const.h" + +#include + +class SiPixelDigisCUDA { +public: + SiPixelDigisCUDA() = default; + explicit SiPixelDigisCUDA(size_t nelements, cuda::stream_t<>& stream); + ~SiPixelDigisCUDA() = default; + + SiPixelDigisCUDA(const SiPixelDigisCUDA&) = delete; + SiPixelDigisCUDA& operator=(const SiPixelDigisCUDA&) = delete; + SiPixelDigisCUDA(SiPixelDigisCUDA&&) = default; + SiPixelDigisCUDA& operator=(SiPixelDigisCUDA&&) = default; + + uint16_t * xx() { return xx_d.get(); } + uint16_t * yy() { return yy_d.get(); } + uint16_t * adc() { return adc_d.get(); } + uint16_t * moduleInd() { return moduleInd_d.get(); } + + uint16_t const *xx() const { return xx_d.get(); } + uint16_t const *yy() const { return yy_d.get(); } + uint16_t const *adc() const { return adc_d.get(); } + uint16_t const *moduleInd() const { return moduleInd_d.get(); } + + uint16_t const *c_xx() const { return xx_d.get(); } + uint16_t const *c_yy() const { return yy_d.get(); } + uint16_t const *c_adc() const { return adc_d.get(); } + uint16_t const *c_moduleInd() const { return moduleInd_d.get(); } + + class DeviceConstView { + public: + DeviceConstView() = default; + +#ifdef __CUDACC__ + __device__ __forceinline__ uint16_t xx(int i) const { return __ldg(xx_+i); } + __device__ __forceinline__ uint16_t yy(int i) const { return __ldg(yy_+i); } + __device__ __forceinline__ uint16_t adc(int i) const { return __ldg(adc_+i); } + __device__ __forceinline__ uint16_t moduleInd(int i) const { return __ldg(moduleInd_+i); } +#endif + + friend class SiPixelDigisCUDA; + + private: + uint16_t const *xx_; + uint16_t const *yy_; + uint16_t const *adc_; + uint16_t const *moduleInd_; + }; + + const DeviceConstView *view() const { return view_d.get(); } + +private: + edm::cuda::device::unique_ptr xx_d; // local coordinates of each pixel + edm::cuda::device::unique_ptr yy_d; // + edm::cuda::device::unique_ptr adc_d; // ADC of each pixel + edm::cuda::device::unique_ptr moduleInd_d; // module id of each pixel + edm::cuda::device::unique_ptr view_d; // "me" pointer +}; + +#endif diff --git a/CUDADataFormats/SiPixelDigi/src/SiPixelDigisCUDA.cc b/CUDADataFormats/SiPixelDigi/src/SiPixelDigisCUDA.cc new file mode 100644 index 0000000000000..7e3d876ac8bdc --- /dev/null +++ b/CUDADataFormats/SiPixelDigi/src/SiPixelDigisCUDA.cc @@ -0,0 +1,25 @@ +#include "CUDADataFormats/SiPixelDigi/interface/SiPixelDigisCUDA.h" + +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +#include + +SiPixelDigisCUDA::SiPixelDigisCUDA(size_t nelements, cuda::stream_t<>& stream) { + edm::Service cs; + + xx_d = cs->make_device_unique(nelements, stream); + yy_d = cs->make_device_unique(nelements, stream); + adc_d = cs->make_device_unique(nelements, stream); + moduleInd_d = cs->make_device_unique(nelements, stream); + + auto view = cs->make_host_unique(stream); + view->xx_ = xx_d.get(); + view->yy_ = yy_d.get(); + view->adc_ = adc_d.get(); + view->moduleInd_ = moduleInd_d.get(); + + view_d = cs->make_device_unique(stream); + cudaCheck(cudaMemcpyAsync(view_d.get(), view.get(), sizeof(DeviceConstView), cudaMemcpyDefault, stream.id())); +} diff --git a/CalibTracker/Records/interface/SiPixelGainCalibrationForHLTGPURcd.h b/CalibTracker/Records/interface/SiPixelGainCalibrationForHLTGPURcd.h new file mode 100644 index 0000000000000..afb682e5d451f --- /dev/null +++ b/CalibTracker/Records/interface/SiPixelGainCalibrationForHLTGPURcd.h @@ -0,0 +1,14 @@ +#ifndef CalibTracker_Records_SiPixelGainCalibrationForHLTGPURcd_h +#define CalibTracker_Records_SiPixelGainCalibrationForHLTGPURcd_h + +#include "FWCore/Framework/interface/EventSetupRecordImplementation.h" +#include "FWCore/Framework/interface/DependentRecordImplementation.h" + +#include "CondFormats/DataRecord/interface/SiPixelGainCalibrationForHLTRcd.h" +#include "Geometry/Records/interface/TrackerDigiGeometryRecord.h" + +#include "boost/mpl/vector.hpp" + +class SiPixelGainCalibrationForHLTGPURcd : public edm::eventsetup::DependentRecordImplementation > {}; + +#endif diff --git a/CalibTracker/Records/src/SiPixelGainCalibrationForHLTGPURcd.cc b/CalibTracker/Records/src/SiPixelGainCalibrationForHLTGPURcd.cc new file mode 100644 index 0000000000000..e6020eca80b1f --- /dev/null +++ b/CalibTracker/Records/src/SiPixelGainCalibrationForHLTGPURcd.cc @@ -0,0 +1,5 @@ +#include "CalibTracker/Records/interface/SiPixelGainCalibrationForHLTGPURcd.h" +#include "FWCore/Framework/interface/eventsetuprecord_registration_macro.h" +#include "FWCore/Utilities/interface/typelookup.h" + +EVENTSETUP_RECORD_REG(SiPixelGainCalibrationForHLTGPURcd); diff --git a/CalibTracker/SiPixelESProducers/BuildFile.xml b/CalibTracker/SiPixelESProducers/BuildFile.xml index e9d22b32f0afb..69d258da21ed1 100644 --- a/CalibTracker/SiPixelESProducers/BuildFile.xml +++ b/CalibTracker/SiPixelESProducers/BuildFile.xml @@ -7,7 +7,9 @@ + + diff --git a/CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h b/CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h new file mode 100644 index 0000000000000..96989c8a2c3b2 --- /dev/null +++ b/CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h @@ -0,0 +1,32 @@ +#ifndef CalibTracker_SiPixelESProducers_SiPixelGainCalibrationForHLTGPU_H +#define CalibTracker_SiPixelESProducers_SiPixelGainCalibrationForHLTGPU_H + +#include "HeterogeneousCore/CUDACore/interface/CUDAESProduct.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelGainCalibrationForHLT.h" + +#include + +class SiPixelGainCalibrationForHLT; +class SiPixelGainForHLTonGPU; +struct SiPixelGainForHLTonGPU_DecodingStructure; +class TrackerGeometry; + +class SiPixelGainCalibrationForHLTGPU { +public: + explicit SiPixelGainCalibrationForHLTGPU(const SiPixelGainCalibrationForHLT& gains, const TrackerGeometry& geom); + ~SiPixelGainCalibrationForHLTGPU(); + + const SiPixelGainForHLTonGPU *getGPUProductAsync(cuda::stream_t<>& cudaStream) const; + +private: + const SiPixelGainCalibrationForHLT *gains_ = nullptr; + SiPixelGainForHLTonGPU *gainForHLTonHost_ = nullptr; + struct GPUData { + ~GPUData(); + SiPixelGainForHLTonGPU *gainForHLTonGPU = nullptr; + SiPixelGainForHLTonGPU_DecodingStructure *gainDataOnGPU = nullptr; + }; + CUDAESProduct gpuData_; +}; + +#endif diff --git a/CalibTracker/SiPixelESProducers/plugins/BuildFile.xml b/CalibTracker/SiPixelESProducers/plugins/BuildFile.xml index 44db9d9ba0582..b33657e273036 100644 --- a/CalibTracker/SiPixelESProducers/plugins/BuildFile.xml +++ b/CalibTracker/SiPixelESProducers/plugins/BuildFile.xml @@ -6,6 +6,8 @@ + + diff --git a/CalibTracker/SiPixelESProducers/plugins/SiPixelGainCalibrationForHLTGPUESProducer.cc b/CalibTracker/SiPixelESProducers/plugins/SiPixelGainCalibrationForHLTGPUESProducer.cc new file mode 100644 index 0000000000000..186bb2d72c3f3 --- /dev/null +++ b/CalibTracker/SiPixelESProducers/plugins/SiPixelGainCalibrationForHLTGPUESProducer.cc @@ -0,0 +1,47 @@ +#include "CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h" +#include "CalibTracker/Records/interface/SiPixelGainCalibrationForHLTGPURcd.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelGainCalibrationForHLT.h" +#include "CondFormats/DataRecord/interface/SiPixelGainCalibrationForHLTRcd.h" +#include "FWCore/Framework/interface/ESProducer.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/ModuleFactory.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "Geometry/Records/interface/TrackerDigiGeometryRecord.h" + +#include + +class SiPixelGainCalibrationForHLTGPUESProducer: public edm::ESProducer { +public: + explicit SiPixelGainCalibrationForHLTGPUESProducer(const edm::ParameterSet& iConfig); + std::unique_ptr produce(const SiPixelGainCalibrationForHLTGPURcd& iRecord); + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); +private: +}; + +SiPixelGainCalibrationForHLTGPUESProducer::SiPixelGainCalibrationForHLTGPUESProducer(const edm::ParameterSet& iConfig) { + setWhatProduced(this); +} + +void SiPixelGainCalibrationForHLTGPUESProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + descriptions.add("siPixelGainCalibrationForHLTGPU", desc); +} + +std::unique_ptr SiPixelGainCalibrationForHLTGPUESProducer::produce(const SiPixelGainCalibrationForHLTGPURcd& iRecord) { + edm::ESHandle gains; + iRecord.getRecord().get(gains); + + edm::ESHandle geom; + iRecord.getRecord().get(geom); + + return std::make_unique(*gains, *geom); +} + +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Utilities/interface/typelookup.h" +#include "FWCore/Framework/interface/eventsetuprecord_registration_macro.h" + +DEFINE_FWK_EVENTSETUP_MODULE(SiPixelGainCalibrationForHLTGPUESProducer); diff --git a/CalibTracker/SiPixelESProducers/src/ES_SiPixelGainCalibrationForHLTGPU.cc b/CalibTracker/SiPixelESProducers/src/ES_SiPixelGainCalibrationForHLTGPU.cc new file mode 100644 index 0000000000000..80932fb468f71 --- /dev/null +++ b/CalibTracker/SiPixelESProducers/src/ES_SiPixelGainCalibrationForHLTGPU.cc @@ -0,0 +1,4 @@ +#include "CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h" +#include "FWCore/Utilities/interface/typelookup.h" + +TYPELOOKUP_DATA_REG(SiPixelGainCalibrationForHLTGPU); diff --git a/CalibTracker/SiPixelESProducers/src/SiPixelGainCalibrationForHLTGPU.cc b/CalibTracker/SiPixelESProducers/src/SiPixelGainCalibrationForHLTGPU.cc new file mode 100644 index 0000000000000..3aef3f44c8f67 --- /dev/null +++ b/CalibTracker/SiPixelESProducers/src/SiPixelGainCalibrationForHLTGPU.cc @@ -0,0 +1,98 @@ +#include "CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelGainCalibrationForHLT.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelGainForHLTonGPU.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "Geometry/CommonDetUnit/interface/GeomDetType.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +#include + +SiPixelGainCalibrationForHLTGPU::SiPixelGainCalibrationForHLTGPU(const SiPixelGainCalibrationForHLT& gains, const TrackerGeometry& geom): + gains_(&gains) +{ + // bizzarre logic (looking for fist strip-det) don't ask + auto const & dus = geom.detUnits(); + unsigned m_detectors = dus.size(); + for(unsigned int i=1;i<7;++i) { + if(geom.offsetDU(GeomDetEnumerators::tkDetEnum[i]) != dus.size() && + dus[geom.offsetDU(GeomDetEnumerators::tkDetEnum[i])]->type().isTrackerStrip()) { + if(geom.offsetDU(GeomDetEnumerators::tkDetEnum[i]) < m_detectors) m_detectors = geom.offsetDU(GeomDetEnumerators::tkDetEnum[i]); + } + } + + /* + std::cout << "caching calibs for " << m_detectors << " pixel detectors of size " << gains.data().size() << std::endl; + std::cout << "sizes " << sizeof(char) << ' ' << sizeof(uint8_t) << ' ' << sizeof(SiPixelGainForHLTonGPU::DecodingStructure) << std::endl; + */ + + cudaCheck(cudaMallocHost((void**) & gainForHLTonHost_, sizeof(SiPixelGainForHLTonGPU))); + //gainForHLTonHost_->v_pedestals = gainDataOnGPU_; // how to do this? + + // do not read back from the (possibly write-combined) memory buffer + auto minPed = gains.getPedLow(); + auto maxPed = gains.getPedHigh(); + auto minGain = gains.getGainLow(); + auto maxGain = gains.getGainHigh(); + auto nBinsToUseForEncoding = 253; + + // we will simplify later (not everything is needed....) + gainForHLTonHost_->minPed_ = minPed; + gainForHLTonHost_->maxPed_ = maxPed; + gainForHLTonHost_->minGain_= minGain; + gainForHLTonHost_->maxGain_= maxGain; + + gainForHLTonHost_->numberOfRowsAveragedOver_ = 80; + gainForHLTonHost_->nBinsToUseForEncoding_ = nBinsToUseForEncoding; + gainForHLTonHost_->deadFlag_ = 255; + gainForHLTonHost_->noisyFlag_ = 254; + + gainForHLTonHost_->pedPrecision = static_cast(maxPed - minPed) / nBinsToUseForEncoding; + gainForHLTonHost_->gainPrecision = static_cast(maxGain - minGain) / nBinsToUseForEncoding; + + /* + std::cout << "precisions g " << gainForHLTonHost_->pedPrecision << ' ' << gainForHLTonHost_->gainPrecision << std::endl; + */ + + // fill the index map + auto const & ind = gains.getIndexes(); + /* + std::cout << ind.size() << " " << m_detectors << std::endl; + */ + + for (auto i=0U; igeographicalId().rawId(),SiPixelGainCalibrationForHLT::StrictWeakOrdering()); + assert (p!=ind.end() && p->detid==dus[i]->geographicalId()); + assert(p->iend<=gains.data().size()); + assert(p->iend>=p->ibegin); + assert(0==p->ibegin%2); + assert(0==p->iend%2); + assert(p->ibegin!=p->iend); + assert(p->ncols>0); + gainForHLTonHost_->rangeAndCols[i] = std::make_pair(SiPixelGainForHLTonGPU::Range(p->ibegin,p->iend), p->ncols); + // if (ind[i].detid!=dus[i]->geographicalId()) std::cout << ind[i].detid<<"!="<geographicalId() << std::endl; + // gainForHLTonHost_->rangeAndCols[i] = std::make_pair(SiPixelGainForHLTonGPU::Range(ind[i].ibegin,ind[i].iend), ind[i].ncols); + } + +} + +SiPixelGainCalibrationForHLTGPU::~SiPixelGainCalibrationForHLTGPU() { + cudaCheck(cudaFreeHost(gainForHLTonHost_)); +} + +SiPixelGainCalibrationForHLTGPU::GPUData::~GPUData() { + cudaCheck(cudaFree(gainForHLTonGPU)); + cudaCheck(cudaFree(gainDataOnGPU)); +} + +const SiPixelGainForHLTonGPU *SiPixelGainCalibrationForHLTGPU::getGPUProductAsync(cuda::stream_t<>& cudaStream) const { + const auto& data = gpuData_.dataForCurrentDeviceAsync(cudaStream, [this](GPUData& data, cuda::stream_t<>& stream) { + cudaCheck(cudaMalloc((void**) & data.gainForHLTonGPU, sizeof(SiPixelGainForHLTonGPU))); + cudaCheck(cudaMalloc((void**) & data.gainDataOnGPU, this->gains_->data().size())); // TODO: this could be changed to cuda::memory::device::unique_ptr<> + // gains.data().data() is used also for non-GPU code, we cannot allocate it on aligned and write-combined memory + cudaCheck(cudaMemcpyAsync(data.gainDataOnGPU, this->gains_->data().data(), this->gains_->data().size(), cudaMemcpyDefault, stream.id())); + + cudaCheck(cudaMemcpyAsync(data.gainForHLTonGPU, this->gainForHLTonHost_, sizeof(SiPixelGainForHLTonGPU), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(&(data.gainForHLTonGPU->v_pedestals), &(data.gainDataOnGPU), sizeof(SiPixelGainForHLTonGPU_DecodingStructure*), cudaMemcpyDefault, stream.id())); + }); + return data.gainForHLTonGPU; +} diff --git a/CondFormats/SiPixelObjects/interface/SiPixelGainForHLTonGPU.h b/CondFormats/SiPixelObjects/interface/SiPixelGainForHLTonGPU.h new file mode 100644 index 0000000000000..931ee7e65f295 --- /dev/null +++ b/CondFormats/SiPixelObjects/interface/SiPixelGainForHLTonGPU.h @@ -0,0 +1,73 @@ +#ifndef CondFormats_SiPixelObjects_SiPixelGainForHLTonGPU_h +#define CondFormats_SiPixelObjects_SiPixelGainForHLTonGPU_h + +#include +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +struct SiPixelGainForHLTonGPU_DecodingStructure{ + uint8_t gain; + uint8_t ped; +}; + + +// copy of SiPixelGainCalibrationForHLT +class SiPixelGainForHLTonGPU { + + public: + + using DecodingStructure = SiPixelGainForHLTonGPU_DecodingStructure; + + using Range = std::pair; + + + inline __host__ __device__ + std::pair getPedAndGain(uint32_t moduleInd, int col, int row, bool& isDeadColumn, bool& isNoisyColumn ) const { + + + auto range = rangeAndCols[moduleInd].first; + auto nCols = rangeAndCols[moduleInd].second; + + // determine what averaged data block we are in (there should be 1 or 2 of these depending on if plaquette is 1 by X or 2 by X + unsigned int lengthOfColumnData = (range.second-range.first)/nCols; + unsigned int lengthOfAveragedDataInEachColumn = 2; // we always only have two values per column averaged block + unsigned int numberOfDataBlocksToSkip = row / numberOfRowsAveragedOver_; + + + auto offset = range.first + col*lengthOfColumnData + lengthOfAveragedDataInEachColumn*numberOfDataBlocksToSkip; + + assert(offset rangeAndCols[2000]; + + float minPed_, maxPed_, minGain_, maxGain_; + + float pedPrecision, gainPrecision; + + unsigned int numberOfRowsAveragedOver_; // this is 80!!!! + unsigned int nBinsToUseForEncoding_; + unsigned int deadFlag_; + unsigned int noisyFlag_; +}; + +#endif // CondFormats_SiPixelObjects_SiPixelGainForHLTonGPU_h diff --git a/Configuration/Applications/python/ConfigBuilder.py b/Configuration/Applications/python/ConfigBuilder.py index 0e878141bf8bf..2e331f4fdc5d4 100644 --- a/Configuration/Applications/python/ConfigBuilder.py +++ b/Configuration/Applications/python/ConfigBuilder.py @@ -918,6 +918,8 @@ def define_Configs(self): self.loadAndRemember('SimGeneral.HepPDTESSource.'+self._options.particleTable+'_cfi') self.loadAndRemember('FWCore/MessageService/MessageLogger_cfi') + # Eventually replace with some more generic file to load + self.loadAndRemember('HeterogeneousCore/CUDAServices/CUDAService_cfi') self.ALCADefaultCFF="Configuration/StandardSequences/AlCaRecoStreams_cff" self.GENDefaultCFF="Configuration/StandardSequences/Generator_cff" diff --git a/Configuration/ProcessModifiers/python/gpu_cff.py b/Configuration/ProcessModifiers/python/gpu_cff.py new file mode 100644 index 0000000000000..993f71804fbc1 --- /dev/null +++ b/Configuration/ProcessModifiers/python/gpu_cff.py @@ -0,0 +1,5 @@ +import FWCore.ParameterSet.Config as cms + +# This modifier is for replacing CPU modules with GPU counterparts + +gpu = cms.Modifier() diff --git a/Configuration/ProcessModifiers/python/riemannFitGPU_cff.py b/Configuration/ProcessModifiers/python/riemannFitGPU_cff.py new file mode 100644 index 0000000000000..ef622f26b2da0 --- /dev/null +++ b/Configuration/ProcessModifiers/python/riemannFitGPU_cff.py @@ -0,0 +1,5 @@ +import FWCore.ParameterSet.Config as cms + +# This modifier is for replacing the default pixel track "fitting" with Riemann fit on GPU + +riemannFitGPU = cms.Modifier() diff --git a/Configuration/ProcessModifiers/python/riemannFit_cff.py b/Configuration/ProcessModifiers/python/riemannFit_cff.py new file mode 100644 index 0000000000000..f97f50df63fb6 --- /dev/null +++ b/Configuration/ProcessModifiers/python/riemannFit_cff.py @@ -0,0 +1,5 @@ +import FWCore.ParameterSet.Config as cms + +# This modifier is for replacing the default pixel track "fitting" with Riemann fit + +riemannFit = cms.Modifier() diff --git a/Configuration/PyReleaseValidation/python/relval_2017.py b/Configuration/PyReleaseValidation/python/relval_2017.py index 92a39b4fd5620..749b7c19d85dd 100644 --- a/Configuration/PyReleaseValidation/python/relval_2017.py +++ b/Configuration/PyReleaseValidation/python/relval_2017.py @@ -27,7 +27,7 @@ 10024.1,10024.2,10024.3,10024.4,10024.5, 10801.0,10802.0,10803.0,10804.0,10805.0,10806.0,10807.0,10808.0,10809.0,10859.0,10871.0, 10842.0,10824.0,10825.0,10826.0,10823.0,11024.0,11025.0,11224.0, - 10824.1,10824.5, + 10824.1,10824.5,10824.7,10824.8,10824.9, 10824.6,11024.6,11224.6, 11642.0,11624.0,11625.0,11626.0,11623.0,11824.0,11825.0,12024.0] for numWF in numWFIB: diff --git a/Configuration/PyReleaseValidation/python/relval_standard.py b/Configuration/PyReleaseValidation/python/relval_standard.py index 071ee684f8313..b9cf72f5b3aa0 100644 --- a/Configuration/PyReleaseValidation/python/relval_standard.py +++ b/Configuration/PyReleaseValidation/python/relval_standard.py @@ -348,6 +348,9 @@ workflows[136.862] = ['',['RunEGamma2018B','HLTDR2_2018','RECODR2_2018reHLT_skimEGamma_Prompt_L1TEgDQM','HARVEST2018_L1TEgDQM']] workflows[136.863] = ['',['RunDoubleMuon2018B','HLTDR2_2018','RECODR2_2018reHLT_Prompt','HARVEST2018']] workflows[136.864] = ['',['RunJetHT2018B','HLTDR2_2018','RECODR2_2018reHLT_skimJetHT_Prompt','HARVEST2018']] +workflows[136.8645] = ['',['RunJetHT2018B','HLTDR2_2018','RECODR2_2018reHLT_Prompt_pixelTrackingOnly','HARVEST2018_pixelTrackingOnly']] +workflows[136.8648] = ['',['RunJetHT2018B','HLTDR2_2018','RECODR2_2018reHLT_Prompt_pixelTrackingOnlyGPU','HARVEST2018_pixelTrackingOnly']] + workflows[136.865] = ['',['RunMET2018B','HLTDR2_2018','RECODR2_2018reHLT_skimMET_Prompt','HARVEST2018']] workflows[136.866] = ['',['RunMuonEG2018B','HLTDR2_2018','RECODR2_2018reHLT_skimMuonEG_Prompt','HARVEST2018']] workflows[136.867] = ['',['RunSingleMu2018B','HLTDR2_2018','RECODR2_2018reHLT_skimSingleMu_Prompt_Lumi','HARVEST2018_L1TMuDQM']] diff --git a/Configuration/PyReleaseValidation/python/relval_steps.py b/Configuration/PyReleaseValidation/python/relval_steps.py index 5e3dee5c928a5..a43407479f28a 100644 --- a/Configuration/PyReleaseValidation/python/relval_steps.py +++ b/Configuration/PyReleaseValidation/python/relval_steps.py @@ -1932,6 +1932,15 @@ def gen2018HiMix(fragment,howMuch): '--datatier': 'GEN-SIM-RECO,DQMIO', '--eventcontent': 'RECOSIM,DQM', } +step3_riemannFit = { + '--procModifiers': 'riemannFit', +} +step3_riemannFitGPU = { + '--procModifiers': 'riemannFitGPU', +} +step3_gpu = { + '--procModifiers': 'gpu', +} step3_trackingLowPU = { '--era': 'Run2_2016_trackingLowPU' } @@ -2052,6 +2061,8 @@ def gen2018HiMix(fragment,howMuch): steps['RECODR2_2017reHLTSiPixelCalZeroBias_Prompt']=merge([{'--conditions':'auto:run2_data_promptlike'},steps['RECODR2_2017reHLTSiPixelCalZeroBias']]) steps['RECODR2_2018reHLT_Prompt']=merge([{'--conditions':'auto:run2_data_promptlike'},steps['RECODR2_2018reHLT']]) +steps['RECODR2_2018reHLT_Prompt_pixelTrackingOnly']=merge([{'-s': 'RAW2DIGI:RawToDigi_pixelOnly,RECO:reconstruction_pixelTrackingOnly,DQM:@pixelTrackingOnlyDQM'},steps['RECODR2_2018reHLT_Prompt']]) +steps['RECODR2_2018reHLT_Prompt_pixelTrackingOnlyGPU']=merge([step3_gpu, steps['RECODR2_2018reHLT_Prompt_pixelTrackingOnly']]) steps['RECODR2_2018reHLT_skimEGamma_Prompt_L1TEgDQM']=merge([{'--conditions':'auto:run2_data_promptlike'},steps['RECODR2_2018reHLT_skimEGamma_L1TEgDQM']]) steps['RECODR2_2018reHLT_skimJetHT_Prompt']=merge([{'--conditions':'auto:run2_data_promptlike'},steps['RECODR2_2018reHLT_skimJetHT']]) steps['RECODR2_2018reHLT_skimDisplacedJet_Prompt']=merge([{'--conditions':'auto:run2_data_promptlike'},steps['RECODR2_2018reHLT_skimDisplacedJet']]) @@ -2351,6 +2362,7 @@ def gen2018HiMix(fragment,howMuch): steps['HARVEST2018'] = merge([ {'--conditions':'auto:run2_data_relval','--era':'Run2_2018','--conditions':'auto:run2_data_promptlike',}, steps['HARVESTD'] ]) steps['HARVEST2018_L1TEgDQM'] = merge([ {'-s':'HARVESTING:@standardDQM+@ExtraHLT+@miniAODDQM+@L1TEgamma'}, steps['HARVEST2018'] ]) steps['HARVEST2018_L1TMuDQM'] = merge([ {'-s':'HARVESTING:@standardDQM+@ExtraHLT+@miniAODDQM+@L1TMuon'}, steps['HARVEST2018'] ]) +steps['HARVEST2018_pixelTrackingOnly'] = merge([ {'-s':'HARVESTING:@pixelTrackingOnlyDQM'}, steps['HARVEST2018'] ]) steps['HARVEST2018_hBStar'] = merge([ {'--era' : 'Run2_2018_highBetaStar'}, steps['HARVEST2018'] ]) steps['HARVEST2018_HEfail'] = merge([ {'--conditions':'auto:run2_data_promptlike_HEfail'}, steps['HARVEST2018'] ]) steps['HARVEST2018_BadHcalMitig'] = merge([ {'--era' : 'Run2_2018,pf_badHcalMitigation','--conditions':'auto:run2_data_promptlike_HEfail'}, steps['HARVEST2018'] ]) @@ -2990,6 +3002,21 @@ def gen2018HiMix(fragment,howMuch): if 'Reco' in step: upgradeStepDict[stepName][k] = merge([step3_pixelTrackingOnly, upgradeStepDict[step][k]]) elif 'HARVEST' in step: upgradeStepDict[stepName][k] = merge([{'-s': 'HARVESTING:@trackingOnlyValidation+@pixelTrackingOnlyDQM'}, upgradeStepDict[step][k]]) + for step in upgradeSteps['pixelTrackingOnlyRiemannFit']['steps']: + stepName = step + upgradeSteps['pixelTrackingOnlyRiemannFit']['suffix'] + if 'Reco' in step: upgradeStepDict[stepName][k] = merge([step3_riemannFit, step3_pixelTrackingOnly, upgradeStepDict[step][k]]) + elif 'HARVEST' in step: upgradeStepDict[stepName][k] = merge([{'-s': 'HARVESTING:@trackingOnlyValidation+@pixelTrackingOnlyDQM'}, upgradeStepDict[step][k]]) + + for step in upgradeSteps['pixelTrackingOnlyRiemannFitGPU']['steps']: + stepName = step + upgradeSteps['pixelTrackingOnlyRiemannFitGPU']['suffix'] + if 'Reco' in step: upgradeStepDict[stepName][k] = merge([step3_riemannFitGPU, step3_pixelTrackingOnly, upgradeStepDict[step][k]]) + elif 'HARVEST' in step: upgradeStepDict[stepName][k] = merge([{'-s': 'HARVESTING:@trackingOnlyValidation+@pixelTrackingOnlyDQM'}, upgradeStepDict[step][k]]) + + for step in upgradeSteps['pixelTrackingOnlyGPU']['steps']: + stepName = step + upgradeSteps['pixelTrackingOnlyGPU']['suffix'] + if 'Reco' in step: upgradeStepDict[stepName][k] = merge([step3_gpu, step3_pixelTrackingOnly, upgradeStepDict[step][k]]) + elif 'HARVEST' in step: upgradeStepDict[stepName][k] = merge([{'-s': 'HARVESTING:@trackingOnlyValidation+@pixelTrackingOnlyDQM'}, upgradeStepDict[step][k]]) + for step in upgradeSteps['trackingRun2']['steps']: stepName = step + upgradeSteps['trackingRun2']['suffix'] if 'Reco' in step and upgradeStepDict[step][k]['--era']=='Run2_2017': diff --git a/Configuration/PyReleaseValidation/python/relval_upgrade.py b/Configuration/PyReleaseValidation/python/relval_upgrade.py index 88d4ab88302ac..4a3127904809f 100644 --- a/Configuration/PyReleaseValidation/python/relval_upgrade.py +++ b/Configuration/PyReleaseValidation/python/relval_upgrade.py @@ -5,7 +5,7 @@ # here only define the workflows as a combination of the steps defined above: workflows = Matrix() -# each workflow defines a name and a list of steps to be done. +# each workflow defines a name and a list of steps to be done. # if no explicit name/label given for the workflow (first arg), # the name of step1 will be used @@ -28,13 +28,13 @@ def makeStepName(key,frag,step,suffix): for stepType in upgradeSteps.keys(): stepList[stepType] = [] hasHarvest = False - for step in upgradeProperties[year][key]['ScenToRun']: + for step in upgradeProperties[year][key]['ScenToRun']: stepMaker = makeStepName if 'Sim' in step: if 'HLBeamSpotFull' in step and '14TeV' in frag: step = 'GenSimHLBeamSpotFull14' stepMaker = makeStepNameSim - + if 'HARVEST' in step: hasHarvest = True for stepType in upgradeSteps.keys(): @@ -74,7 +74,7 @@ def makeStepName(key,frag,step,suffix): # special workflows for tracker if (upgradeDatasetFromFragment[frag]=="TTbar_13" or upgradeDatasetFromFragment[frag]=="TTbar_14TeV") and not 'PU' in key and hasHarvest: # skip ALCA and Nano - trackingVariations = ['trackingOnly','trackingRun2','trackingOnlyRun2','trackingLowPU','pixelTrackingOnly'] + trackingVariations = ['trackingOnly','trackingRun2','trackingOnlyRun2','trackingLowPU','pixelTrackingOnly','pixelTrackingOnlyRiemannFit','pixelTrackingOnlyRiemannFitGPU','pixelTrackingOnlyGPU'] for tv in trackingVariations: stepList[tv] = [s for s in stepList[tv] if (("ALCA" not in s) and ("Nano" not in s))] workflows[numWF+upgradeSteps['trackingOnly']['offset']] = [ upgradeDatasetFromFragment[frag], stepList['trackingOnly']] @@ -82,7 +82,10 @@ def makeStepName(key,frag,step,suffix): for tv in trackingVariations[1:]: workflows[numWF+upgradeSteps[tv]['offset']] = [ upgradeDatasetFromFragment[frag], stepList[tv]] elif '2018' in key: - workflows[numWF+upgradeSteps['pixelTrackingOnly']['offset']] = [ upgradeDatasetFromFragment[frag], stepList['pixelTrackingOnly']] + for tv in trackingVariations: + if not "pixelTrackingOnly" in tv: + continue + workflows[numWF+upgradeSteps[tv]['offset']] = [ upgradeDatasetFromFragment[frag], stepList[tv]] # special workflows for HE if upgradeDatasetFromFragment[frag]=="TTbar_13" and '2018' in key: diff --git a/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py b/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py index 6bdad15a3fc7e..81fba89d26ed0 100644 --- a/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py +++ b/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py @@ -154,6 +154,39 @@ 'suffix' : '_pixelTrackingOnly', 'offset' : 0.5, } +upgradeSteps['pixelTrackingOnlyRiemannFit'] = { + 'steps' : [ + 'RecoFull', + 'HARVESTFull', + 'RecoFullGlobal', + 'HARVESTFullGlobal', + ], + 'PU' : [], + 'suffix' : '_pixelTrackingOnlyRiemannFit', + 'offset' : 0.7, +} +upgradeSteps['pixelTrackingOnlyGPU'] = { + 'steps' : [ + 'RecoFull', + 'HARVESTFull', + 'RecoFullGlobal', + 'HARVESTFullGlobal', + ], + 'PU' : [], + 'suffix' : '_pixelTrackingOnlyGPU', + 'offset' : 0.8, +} +upgradeSteps['pixelTrackingOnlyRiemannFitGPU'] = { + 'steps' : [ + 'RecoFull', + 'HARVESTFull', + 'RecoFullGlobal', + 'HARVESTFullGlobal', + ], + 'PU' : [], + 'suffix' : '_pixelTrackingOnlyRiemannFitGPU', + 'offset' : 0.9, +} upgradeSteps['Timing'] = { 'steps' : upgradeSteps['baseline']['steps'], 'PU' : upgradeSteps['baseline']['PU'], @@ -443,7 +476,7 @@ 'DoubleMuPt1000Extended_pythia8_cfi', 'TenMuE_0_200_pythia8_cfi', 'SinglePiE50HCAL_pythia8_cfi', - 'MinBias_13TeV_pythia8_TuneCUETP8M1_cfi', + 'MinBias_13TeV_pythia8_TuneCUETP8M1_cfi', 'TTbar_13TeV_TuneCUETP8M1_cfi', 'ZEE_13TeV_TuneCUETP8M1_cfi', 'QCD_Pt_600_800_13TeV_TuneCUETP8M1_cfi', diff --git a/Configuration/StandardSequences/python/RawToDigi_cff.py b/Configuration/StandardSequences/python/RawToDigi_cff.py index 35e57a29778b2..96aaeebbfaacd 100644 --- a/Configuration/StandardSequences/python/RawToDigi_cff.py +++ b/Configuration/StandardSequences/python/RawToDigi_cff.py @@ -71,7 +71,8 @@ RawToDigi_pixelOnly = cms.Sequence(RawToDigiTask_pixelOnly) scalersRawToDigi.scalersInputTag = 'rawDataCollector' -siPixelDigis.InputLabel = 'rawDataCollector' +from Configuration.ProcessModifiers.gpu_cff import gpu +(~gpu).toModify(siPixelDigis, InputLabel = 'rawDataCollector') #false by default anyways ecalDigis.DoRegional = False ecalDigis.InputLabel = 'rawDataCollector' ecalPreshowerDigis.sourceTag = 'rawDataCollector' diff --git a/Configuration/StandardSequences/python/Reconstruction_cff.py b/Configuration/StandardSequences/python/Reconstruction_cff.py index 822da0e74a98c..b65504a8575b9 100644 --- a/Configuration/StandardSequences/python/Reconstruction_cff.py +++ b/Configuration/StandardSequences/python/Reconstruction_cff.py @@ -15,7 +15,7 @@ siPixelClusterShapeCachePreSplitting = siPixelClusterShapeCache.clone( src = 'siPixelClustersPreSplitting' - ) +) # Global reco from RecoEcal.Configuration.RecoEcal_cff import * diff --git a/DataFormats/GeometrySurface/interface/SOARotation.h b/DataFormats/GeometrySurface/interface/SOARotation.h index 1373a4091c5e5..14f3df34b993c 100644 --- a/DataFormats/GeometrySurface/interface/SOARotation.h +++ b/DataFormats/GeometrySurface/interface/SOARotation.h @@ -139,6 +139,48 @@ class SOAFrame { ux+=px; uy+=py; uz+=pz; } + constexpr inline + void toGlobal( + T cxx, + T cxy, + T cyy, + T * gl) const { + + auto const & r = rot; + gl[0] = r.xx()*(r.xx()*cxx+r.yx()*cxy) + r.yx()*(r.xx()*cxy+r.yx()*cyy); + gl[1] = r.xx()*(r.xy()*cxx+r.yy()*cxy) + r.yx()*(r.xy()*cxy+r.yy()*cyy); + gl[2] = r.xy()*(r.xy()*cxx+r.yy()*cxy) + r.yy()*(r.xy()*cxy+r.yy()*cyy); + gl[3] = r.xx()*(r.xz()*cxx+r.yz()*cxy) + r.yx()*(r.xz()*cxy+r.yz()*cyy); + gl[4] = r.xy()*(r.xz()*cxx+r.yz()*cxy) + r.yy()*(r.xz()*cxy+r.yz()*cyy); + gl[5] = r.xz()*(r.xz()*cxx+r.yz()*cxy) + r.yz()*(r.xz()*cxy+r.yz()*cyy); + } + + constexpr inline + void toLocal( + T const * ge, + T & lxx, + T & lxy, + T & lyy ) const { + + auto const & r = rot; + + T cxx = ge[0]; T cyx = ge[1]; T cyy = ge[2]; + T czx = ge[3]; T czy = ge[4]; T czz = ge[5]; + + lxx + = r.xx()*(r.xx()*cxx + r.xy()*cyx + r.xz()*czx) + + r.xy()*(r.xx()*cyx + r.xy()*cyy + r.xz()*czy) + + r.xz()*(r.xx()*czx + r.xy()*czy + r.xz()*czz); + lxy + = r.yx()*(r.xx()*cxx + r.xy()*cyx + r.xz()*czx) + + r.yy()*(r.xx()*cyx + r.xy()*cyy + r.xz()*czy) + + r.yz()*(r.xx()*czx + r.xy()*czy + r.xz()*czz); + lyy + = r.yx()*(r.yx()*cxx + r.yy()*cyx + r.yz()*czx) + + r.yy()*(r.yx()*cyx + r.yy()*cyy + r.yz()*czy) + + r.yz()*(r.yx()*czx + r.yy()*czy + r.yz()*czz); + } + constexpr inline T x() const { return px; } diff --git a/DataFormats/GeometrySurface/test/BuildFile.xml b/DataFormats/GeometrySurface/test/BuildFile.xml index ed732f292ebf1..172c8440fe806 100644 --- a/DataFormats/GeometrySurface/test/BuildFile.xml +++ b/DataFormats/GeometrySurface/test/BuildFile.xml @@ -14,3 +14,16 @@ + + + + + + + + + + + + + diff --git a/DataFormats/GeometrySurface/test/gpuFrameTransformKernel.cu b/DataFormats/GeometrySurface/test/gpuFrameTransformKernel.cu new file mode 100644 index 0000000000000..fa45a6c0ddd0e --- /dev/null +++ b/DataFormats/GeometrySurface/test/gpuFrameTransformKernel.cu @@ -0,0 +1,45 @@ +#include "DataFormats/GeometrySurface/interface/SOARotation.h" +#include + + +__global__ +void toGlobal(SOAFrame const * frame, + float const * xl, float const * yl, + float * x, float * y, float * z, + float const * le, float * ge, + uint32_t n) +{ + int i = blockDim.x * blockIdx.x + threadIdx.x; + if (i >= n) return; + + frame[0].toGlobal(xl[i],yl[i],x[i],y[i],z[i]); + frame[0].toGlobal(le[3*i],le[3*i+1],le[3*i+2],ge+6*i); + +} + +#include +#include +#include "cuda/api_wrappers.h" + + +void toGlobalWrapper(SOAFrame const * frame, + float const * xl, float const * yl, + float * x, float * y, float * z, + float const * le, float * ge, + uint32_t n) { + + int threadsPerBlock = 256; + int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock; + std::cout + << "CUDA toGlobal kernel launch with " << blocksPerGrid + << " blocks of " << threadsPerBlock << " threads" << std::endl; + + cuda::launch( + toGlobal, + { blocksPerGrid, threadsPerBlock }, + frame, xl, yl, x, y, z, le, ge, + n + ); + +} + diff --git a/DataFormats/GeometrySurface/test/gpuFrameTransformTest.cpp b/DataFormats/GeometrySurface/test/gpuFrameTransformTest.cpp new file mode 100644 index 0000000000000..22b09c52a970a --- /dev/null +++ b/DataFormats/GeometrySurface/test/gpuFrameTransformTest.cpp @@ -0,0 +1,140 @@ +#include "DataFormats/GeometrySurface/interface/SOARotation.h" +#include + + + +void toGlobalWrapper(SOAFrame const * frame, + float const * xl, float const * yl, + float * x, float * y, float * z, + float const * le, float * ge, + uint32_t n); + + + +#include "DataFormats/GeometrySurface/interface/TkRotation.h" +#include "DataFormats/GeometrySurface/interface/GloballyPositioned.h" + + +#include "cuda/api_wrappers.h" + + +#include +#include +#include +#include +#include +#include + + + +#include +#include + +int main(void) +{ + + typedef float T; + typedef TkRotation Rotation; + typedef SOARotation SRotation; + typedef GloballyPositioned Frame; + typedef SOAFrame SFrame; + typedef typename Frame::PositionType Position; + typedef typename Frame::GlobalVector GlobalVector; + typedef typename Frame::GlobalPoint GlobalPoint; + typedef typename Frame::LocalVector LocalVector; + typedef typename Frame::LocalPoint LocalPoint; + + + + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + + + + constexpr uint32_t size = 10000; + constexpr uint32_t size32 = size*sizeof(float); + + + float xl[size],yl[size]; + float x[size],y[size],z[size]; + + // errors + float le[3*size]; + float ge[6*size]; + + + auto current_device = cuda::device::current::get(); + auto d_xl = cuda::memory::device::make_unique(current_device, size); + auto d_yl = cuda::memory::device::make_unique(current_device, size); + + auto d_x = cuda::memory::device::make_unique(current_device, size); + auto d_y = cuda::memory::device::make_unique(current_device, size); + auto d_z = cuda::memory::device::make_unique(current_device, size); + + auto d_le = cuda::memory::device::make_unique(current_device, 3*size); + auto d_ge = cuda::memory::device::make_unique(current_device, 6*size); + + + double a = 0.01; + double ca = std::cos(a); + double sa = std::sin(a); + + Rotation r1(ca, sa, 0, + -sa, ca, 0, + 0, 0, 1); + Frame f1(Position(2,3,4), r1); + std::cout << "f1.position() " << f1.position() << std::endl; + std::cout << "f1.rotation() " << '\n' << f1.rotation() << std::endl; + + SFrame sf1(f1.position().x(), + f1.position().y(), + f1.position().z(), + f1.rotation() + ); + + + // auto d_sf = cuda::memory::device::make_unique(current_device, 1); + auto d_sf = cuda::memory::device::make_unique(current_device, sizeof(SFrame)); + cuda::memory::copy(d_sf.get(), &sf1, sizeof(SFrame)); + + + + for (auto i=0U; isize/2) ? 1.f : 0.04f; + le[2*i+1]=0.; + } + std::random_shuffle(xl,xl+size); + std::random_shuffle(yl,yl+size); + + cuda::memory::copy(d_xl.get(), xl, size32); + cuda::memory::copy(d_yl.get(), yl, size32); + cuda::memory::copy(d_le.get(), le, 3*size32); + + + toGlobalWrapper((SFrame const *)(d_sf.get()), d_xl.get(), d_yl.get(), d_x.get(), d_y.get(), d_z.get(), + d_le.get(), d_ge.get(), size + ); + + cuda::memory::copy(x,d_x.get(), size32); + cuda::memory::copy(y,d_y.get(), size32); + cuda::memory::copy(z,d_z.get(), size32); + cuda::memory::copy(ge,d_ge.get(), 6*size32); + + + float eps=0.; + for (auto i=0U; i +#include namespace phase1PixelTopology { @@ -20,6 +21,75 @@ namespace phase1PixelTopology { constexpr uint32_t numPixsInModule = uint32_t(numRowsInModule)* uint32_t(numColsInModule); + constexpr uint32_t numberOfModules = 1856; + + constexpr uint32_t layerStart[11] = {0,96,320,672,1184,1296,1408,1520,1632,1744,1856}; + constexpr char const * layerName[10] = {"BL1","BL2","BL3","BL4", + "E+1", "E+2", "E+3", + "E-1", "E-2", "E-3" + }; + + + template + constexpr auto map_to_array_helper(Function f, std::index_sequence) + -> std::array::type, sizeof...(Indices)> + { + return {{ f(Indices)... }}; + } + + template + constexpr auto map_to_array(Function f) + -> std::array::type, N> + { + return map_to_array_helper(f, std::make_index_sequence{}); + } + + + constexpr uint32_t findMaxModuleStride() { + bool go = true; + int n=2; + while (go) { + for (uint8_t i=1; i<11; ++i) { + if (layerStart[i]%n !=0) {go=false; break;} + } + if(!go) break; + n*=2; + } + return n/2; + } + + constexpr uint32_t maxModuleStride = findMaxModuleStride(); + + + constexpr uint8_t findLayer(uint32_t detId) { + for (uint8_t i=0; i<11; ++i) if (detId layer = map_to_array(findLayerFromCompact); + + constexpr bool validateLayerIndex() { + bool res=true; + for (auto i=0U; i=layerStart[layer[j]]); + res &=(i(ori)==bp); } + using namespace phase1PixelTopology; + for (auto i=0U; i=layerStart[layer[i]]); + assert(i + + + + + + + + + + diff --git a/HeterogeneousCore/CUDACore/interface/CUDAESProduct.h b/HeterogeneousCore/CUDACore/interface/CUDAESProduct.h new file mode 100644 index 0000000000000..36c17721249aa --- /dev/null +++ b/HeterogeneousCore/CUDACore/interface/CUDAESProduct.h @@ -0,0 +1,93 @@ +#ifndef HeterogeneousCore_CUDACore_CUDAESProduct_h +#define HeterogeneousCore_CUDACore_CUDAESProduct_h + +#include +#include + +#include + +#include "FWCore/Concurrency/interface/hardware_pause.h" +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "FWCore/Utilities/interface/thread_safety_macros.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDAServices/interface/numberOfCUDADevices.h" + +template +class CUDAESProduct { +public: + CUDAESProduct(): gpuDataPerDevice_(numberOfCUDADevices()) {} + ~CUDAESProduct() = default; + + // transferAsync should be a function of (T&, cuda::stream_t<>&) + // which enqueues asynchronous transfers (possibly kernels as well) + // to the CUDA stream + template + const T& dataForCurrentDeviceAsync(cuda::stream_t<>& cudaStream, F transferAsync) const { + edm::Service cs; + auto device = cs->getCurrentDevice(); + + auto& data = gpuDataPerDevice_[device]; + if(data.m_filled.load()) { + // GPU data has already been filled, so can return it immediately + return data.m_data; + } + + + bool expected = false; + if(data.m_filling.compare_exchange_strong(expected, true)) { + // so nobody else was filling + // then check if it got filled in the mean time + if(data.m_filled.load()) { + // someone else finished the filling in the meantime + data.m_filling.store(false); + return data.m_data; + } + + // now we can be sure that the data is not yet on the GPU, and + // this thread is the first one to try that + try { + transferAsync(data.m_data, cudaStream); + + cudaStream.enqueue.callback([&filling = data.m_filling, + &filled = data.m_filled] + (cuda::stream::id_t streamId, cuda::status_t status) mutable { + // TODO: check status and throw if fails + auto should_be_false = filled.exchange(true); + assert(!should_be_false); + auto should_be_true = filling.exchange(false); + assert(should_be_true); + }); + } catch(...) { + // release the filling state and propagate exception + auto should_be_true = data.m_filling.exchange(false); + assert(should_be_true); + throw std::current_exception(); + } + + // Now the filling has been enqueued to the cudaStream, so we + // can return the GPU data immediately, since all subsequent + // work must be either enqueued to the cudaStream, or the cudaStream + // must be synchronized by the caller + return data.m_data; + } + + // can we do better than just spin on the atomic while waiting another thread to finish the filling? + while(data.m_filling.load()) { + hardware_pause(); + } + assert(data.m_filled.load()); + + return data.m_data; + } + +private: + struct Item { + mutable std::atomic m_filling = false; // true if some thread is already filling + mutable std::atomic m_filled = false; // easy check if data has been filled already or not + CMS_THREAD_GUARD(m_filling) mutable T m_data; + }; + + std::vector gpuDataPerDevice_; +}; + +#endif diff --git a/HeterogeneousCore/CUDACore/interface/GPUCuda.h b/HeterogeneousCore/CUDACore/interface/GPUCuda.h new file mode 100644 index 0000000000000..6e02ef83b312c --- /dev/null +++ b/HeterogeneousCore/CUDACore/interface/GPUCuda.h @@ -0,0 +1,42 @@ +#ifndef HeterogeneousCore_CUDAServices_GPUCuda_h +#define HeterogeneousCore_CUDAServices_GPUCuda_h + +#include "FWCore/Concurrency/interface/WaitingTaskWithArenaHolder.h" +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "HeterogeneousCore/Producer/interface/DeviceWrapper.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEvent.h" + +#include + +#include + +namespace heterogeneous { + class GPUCuda { + public: + using CallbackType = std::function; + + explicit GPUCuda(const edm::ParameterSet& iConfig); + virtual ~GPUCuda() noexcept(false); + + void call_beginStreamGPUCuda(edm::StreamID id); + bool call_acquireGPUCuda(DeviceBitSet inputLocation, edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, edm::WaitingTaskWithArenaHolder waitingTaskHolder); + void call_produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup); + + static void fillPSetDescription(edm::ParameterSetDescription& desc); + + private: + virtual void beginStreamGPUCuda(edm::StreamID id, cuda::stream_t<>& cudaStream) {}; + virtual void acquireGPUCuda(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) = 0; + virtual void produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) = 0; + + std::unique_ptr> cudaStream_; + int deviceId_ = -1; // device assigned to this edm::Stream + bool enabled_; + const bool forced_; + }; + DEFINE_DEVICE_WRAPPER(GPUCuda, HeterogeneousDevice::kGPUCuda); +} + +#endif diff --git a/HeterogeneousCore/CUDACore/src/GPUCuda.cc b/HeterogeneousCore/CUDACore/src/GPUCuda.cc new file mode 100644 index 0000000000000..154c3edf80411 --- /dev/null +++ b/HeterogeneousCore/CUDACore/src/GPUCuda.cc @@ -0,0 +1,105 @@ +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" + +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" + +#include + +#include + +namespace heterogeneous { + GPUCuda::GPUCuda(const edm::ParameterSet& iConfig): + enabled_(iConfig.getUntrackedParameter("GPUCuda")), + forced_(iConfig.getUntrackedParameter("force") == "GPUCuda") + { + if(forced_ && !enabled_) { + throw cms::Exception("Configuration") << "It makes no sense to force the module on GPUCuda, and then disable GPUCuda."; + } + } + + GPUCuda::~GPUCuda() noexcept(false) {} + + void GPUCuda::fillPSetDescription(edm::ParameterSetDescription& desc) { + desc.addUntracked("GPUCuda", true); + } + + void GPUCuda::call_beginStreamGPUCuda(edm::StreamID id) { + edm::Service cudaService; + enabled_ = (enabled_ && cudaService->enabled(id)); + if(!enabled_) { + if(forced_) { + throw cms::Exception("LogicError") << "This module was forced to run on GPUCuda, but the device is not available."; + } + return; + } + + // For startes we "statically" assign the device based on + // edm::Stream number. This is suboptimal if the number of + // edm::Streams is not a multiple of the number of CUDA devices + // (and even then there is no load balancing). + // + // TODO: improve. Possible ideas include + // - allocate M (< N(edm::Streams)) buffers per device per module, choose dynamically which (buffer, device) to use + // * the first module of a chain dictates the device for the rest of the chain + // - our own CUDA memory allocator + // * being able to cheaply allocate+deallocate scratch memory allows to make the execution fully dynamic e.g. based on current load + // * would probably still need some buffer space/device to hold e.g. conditions data + // - for conditions, how to handle multiple lumis per job? + deviceId_ = id % cudaService->numberOfDevices(); + + cuda::device::current::scoped_override_t<> setDeviceForThisScope(deviceId_); + + // Create the CUDA stream for this module-edm::Stream pair + auto current_device = cuda::device::current::get(); + cudaStream_ = std::make_unique>(current_device.create_stream(cuda::stream::implicitly_synchronizes_with_default_stream)); + + beginStreamGPUCuda(id, *cudaStream_); + } + + bool GPUCuda::call_acquireGPUCuda(DeviceBitSet inputLocation, edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, edm::WaitingTaskWithArenaHolder waitingTaskHolder) { + if(!enabled_) { + return false; + } + + // TODO: currently 'forced_ == true' is already assumed. When the + // scheduling logic evolves, add explicit treatment of forced_. + + cuda::device::current::scoped_override_t<> setDeviceForThisScope(deviceId_); + + try { + iEvent.setInputLocation(HeterogeneousDeviceId(HeterogeneousDevice::kGPUCuda, 0)); + acquireGPUCuda(iEvent, iSetup, *cudaStream_); + cudaStream_->enqueue.callback([deviceId=deviceId_, + waitingTaskHolder, // copy needed for the catch block + locationSetter = iEvent.locationSetter() + ](cuda::stream::id_t streamId, cuda::status_t status) mutable { + if (status == cudaSuccess) { + locationSetter(HeterogeneousDeviceId(HeterogeneousDevice::kGPUCuda, deviceId)); + LogTrace("GPUCuda") << " GPU kernel finished (in callback) device " << deviceId << " CUDA stream " << streamId; + waitingTaskHolder.doneWaiting(nullptr); + } else { + // wrap the exception in a try-catch block to let GDB "catch throw" break on it + try { + auto error = cudaGetErrorName(status); + auto message = cudaGetErrorString(status); + throw cms::Exception("CUDAError") << "Callback of CUDA stream " << streamId << " in device " << deviceId << " error " << error << ": " << message; + } catch(...) { + waitingTaskHolder.doneWaiting(std::current_exception()); + } + } + }); + } catch(...) { + waitingTaskHolder.doneWaiting(std::current_exception()); + } + return true; + } + + void GPUCuda::call_produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + // I guess we have to assume that produce() may be called from a different thread than acquire() was run + // The current CUDA device is a thread-local property, so have to set it here + cuda::device::current::scoped_override_t<> setDeviceForThisScope(deviceId_); + + produceGPUCuda(iEvent, iSetup, *cudaStream_); + } +} diff --git a/HeterogeneousCore/CUDAServices/BuildFile.xml b/HeterogeneousCore/CUDAServices/BuildFile.xml new file mode 100644 index 0000000000000..61572b96fb26e --- /dev/null +++ b/HeterogeneousCore/CUDAServices/BuildFile.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/HeterogeneousCore/CUDAServices/bin/BuildFile.xml b/HeterogeneousCore/CUDAServices/bin/BuildFile.xml new file mode 100644 index 0000000000000..a116ca8d78d33 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/bin/BuildFile.xml @@ -0,0 +1,3 @@ + + + diff --git a/HeterogeneousCore/CUDAServices/bin/cudaComputeCapabilities.cpp b/HeterogeneousCore/CUDAServices/bin/cudaComputeCapabilities.cpp new file mode 100644 index 0000000000000..3f0b537e1029e --- /dev/null +++ b/HeterogeneousCore/CUDAServices/bin/cudaComputeCapabilities.cpp @@ -0,0 +1,23 @@ +// C++ standard headers +#include +#include + +// CUDA headers +#include + +// CMSSW headers +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +int main() { + + int devices = 0; + cudaCheck(cudaGetDeviceCount(&devices)); + + for (int i = 0; i < devices; ++i) { + cudaDeviceProp properties; + cudaGetDeviceProperties(&properties, i); + std::cout << std::setw(4) << i << " " << std::setw(2) << properties.major << "." << properties.minor << " " << properties.name << std::endl; + } + + return 0; +} diff --git a/HeterogeneousCore/CUDAServices/interface/CUDAService.h b/HeterogeneousCore/CUDAServices/interface/CUDAService.h new file mode 100644 index 0000000000000..9555321f5153a --- /dev/null +++ b/HeterogeneousCore/CUDAServices/interface/CUDAService.h @@ -0,0 +1,148 @@ +#ifndef HeterogeneousCore_CUDAServices_CUDAService_h +#define HeterogeneousCore_CUDAServices_CUDAService_h + +#include +#include + +#include + +#include "FWCore/Utilities/interface/StreamID.h" + +#include "CUDADataFormats/Common/interface/device_unique_ptr.h" +#include "CUDADataFormats/Common/interface/host_unique_ptr.h" + +namespace edm { + class ParameterSet; + class ActivityRegistry; + class ConfigurationDescriptions; +} + +namespace cudaserviceimpl { + template + struct make_device_unique_selector { using non_array = edm::cuda::device::unique_ptr; }; + template + struct make_device_unique_selector { using unbounded_array = edm::cuda::device::unique_ptr; }; + template + struct make_device_unique_selector { struct bounded_array {}; }; + + template + struct make_host_unique_selector { using non_array = edm::cuda::host::unique_ptr; }; + template + struct make_host_unique_selector { using unbounded_array = edm::cuda::host::unique_ptr; }; + template + struct make_host_unique_selector { struct bounded_array {}; }; +} + +/** + * TODO: + * - CUDA stream management? + * * Not really needed until we want to pass CUDA stream objects from one module to another + * * Which is not really needed until we want to go for "streaming mode" + * * Until that framework's inter-module synchronization is safe (but not necessarily optimal) + * - Management of (preallocated) memory? + */ +class CUDAService { +public: + CUDAService(edm::ParameterSet const& iConfig, edm::ActivityRegistry& iRegistry); + ~CUDAService(); + + static void fillDescriptions(edm::ConfigurationDescriptions & descriptions); + + // To be used in global context when an edm::Stream is not available + bool enabled() const { return enabled_; } + // To be used in stream context when an edm::Stream is available + bool enabled(edm::StreamID streamId) const { return enabled(static_cast(streamId)); } + bool enabled(unsigned int streamId) const { return enabled_ && (numberOfStreamsTotal_ == 0 || streamId < numberOfStreamsTotal_); } // to make testing easier + + int numberOfDevices() const { return numberOfDevices_; } + + // major, minor + std::pair computeCapability(int device) { return computeCapabilities_.at(device); } + + // Returns the id of device with most free memory. If none is found, returns -1. + int deviceWithMostFreeMemory() const; + + // Set the current device + void setCurrentDevice(int device) const; + + // Get the current device + int getCurrentDevice() const; + + // Allocate device memory + template + typename cudaserviceimpl::make_device_unique_selector::non_array + make_device_unique(cuda::stream_t<>& stream) { + static_assert(std::is_trivially_constructible::value, "Allocating with non-trivial constructor on the device memory is not supported"); + int dev = getCurrentDevice(); + void *mem = allocate_device(dev, sizeof(T), stream); + return typename cudaserviceimpl::make_device_unique_selector::non_array(reinterpret_cast(mem), + [this, dev](void *ptr) { + this->free_device(dev, ptr); + }); + } + + template + typename cudaserviceimpl::make_device_unique_selector::unbounded_array + make_device_unique(size_t n, cuda::stream_t<>& stream) { + using element_type = typename std::remove_extent::type; + static_assert(std::is_trivially_constructible::value, "Allocating with non-trivial constructor on the device memory is not supported"); + int dev = getCurrentDevice(); + void *mem = allocate_device(dev, n*sizeof(element_type), stream); + return typename cudaserviceimpl::make_device_unique_selector::unbounded_array(reinterpret_cast(mem), + [this, dev](void *ptr) { + this->free_device(dev, ptr); + }); + } + + template + typename cudaserviceimpl::make_device_unique_selector::bounded_array + make_device_unique(Args&&...) = delete; + + // Allocate pinned host memory + template + typename cudaserviceimpl::make_host_unique_selector::non_array + make_host_unique(cuda::stream_t<>& stream) { + static_assert(std::is_trivially_constructible::value, "Allocating with non-trivial constructor on the pinned host memory is not supported"); + void *mem = allocate_host(sizeof(T), stream); + return typename cudaserviceimpl::make_host_unique_selector::non_array(reinterpret_cast(mem), + [this](void *ptr) { + this->free_host(ptr); + }); + } + + template + typename cudaserviceimpl::make_host_unique_selector::unbounded_array + make_host_unique(size_t n, cuda::stream_t<>& stream) { + using element_type = typename std::remove_extent::type; + static_assert(std::is_trivially_constructible::value, "Allocating with non-trivial constructor on the pinned host memory is not supported"); + void *mem = allocate_host(n*sizeof(element_type), stream); + return typename cudaserviceimpl::make_host_unique_selector::unbounded_array(reinterpret_cast(mem), + [this](void *ptr) { + this->free_host(ptr); + }); + } + + template + typename cudaserviceimpl::make_host_unique_selector::bounded_array + make_host_unique(Args&&...) = delete; + + // Free device memory (to be called from unique_ptr) + void free_device(int device, void *ptr); + + // Free pinned host memory (to be called from unique_ptr) + void free_host(void *ptr); + +private: + // PIMPL to hide details of allocator + struct Allocator; + std::unique_ptr allocator_; + void *allocate_device(int dev, size_t nbytes, cuda::stream_t<>& stream); + void *allocate_host(size_t nbytes, cuda::stream_t<>& stream); + + int numberOfDevices_ = 0; + unsigned int numberOfStreamsTotal_ = 0; + std::vector> computeCapabilities_; + bool enabled_ = false; +}; + +#endif diff --git a/HeterogeneousCore/CUDAServices/interface/numberOfCUDADevices.h b/HeterogeneousCore/CUDAServices/interface/numberOfCUDADevices.h new file mode 100644 index 0000000000000..b563b98b516cf --- /dev/null +++ b/HeterogeneousCore/CUDAServices/interface/numberOfCUDADevices.h @@ -0,0 +1,9 @@ +#ifndef HeterogeneousCore_CUDAServices_numberOfCUDADevices_h +#define HeterogeneousCore_CUDAServices_numberOfCUDADevices_h + +// Returns the number of CUDA devices +// The difference wrt. the standard CUDA function is that if +// CUDAService is disabled, this function returns 0. +int numberOfCUDADevices(); + +#endif diff --git a/HeterogeneousCore/CUDAServices/plugins/BuildFile.xml b/HeterogeneousCore/CUDAServices/plugins/BuildFile.xml index afcf86afdef75..7f213ec8fd987 100644 --- a/HeterogeneousCore/CUDAServices/plugins/BuildFile.xml +++ b/HeterogeneousCore/CUDAServices/plugins/BuildFile.xml @@ -12,6 +12,7 @@ + diff --git a/HeterogeneousCore/CUDAServices/plugins/CUDAMonitoringService.cc b/HeterogeneousCore/CUDAServices/plugins/CUDAMonitoringService.cc new file mode 100644 index 0000000000000..7b7711c63c502 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/plugins/CUDAMonitoringService.cc @@ -0,0 +1,100 @@ +#include + +#include + +#include "DataFormats/Provenance/interface/ModuleDescription.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ServiceRegistry/interface/ActivityRegistry.h" +#include "FWCore/ServiceRegistry/interface/ModuleCallingContext.h" +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "FWCore/ServiceRegistry/interface/ServiceMaker.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +namespace edm { + class StreamContext; +} + +class CUDAMonitoringService { +public: + CUDAMonitoringService(edm::ParameterSet const& iConfig, edm::ActivityRegistry& iRegistry); + ~CUDAMonitoringService() = default; + + static void fillDescriptions(edm::ConfigurationDescriptions & descriptions); + + void postModuleConstruction(edm::ModuleDescription const& desc); + void postModuleBeginStream(edm::StreamContext const&, edm::ModuleCallingContext const& mcc); + void postEvent(edm::StreamContext const& sc); + +private: + int numberOfDevices_ = 0; +}; + +CUDAMonitoringService::CUDAMonitoringService(edm::ParameterSet const& config, edm::ActivityRegistry& registry) { + // make sure that CUDA is initialised, and that the CUDAService destructor is called after this service's destructor + edm::Service cudaService; + if(!cudaService->enabled()) + return; + numberOfDevices_ = cudaService->numberOfDevices(); + + if(config.getUntrackedParameter("memoryConstruction")) { + registry.watchPostModuleConstruction(this, &CUDAMonitoringService::postModuleConstruction); + } + if(config.getUntrackedParameter("memoryBeginStream")) { + registry.watchPostModuleBeginStream(this, &CUDAMonitoringService::postModuleBeginStream); + } + if(config.getUntrackedParameter("memoryPerEvent")) { + registry.watchPostEvent(this, &CUDAMonitoringService::postEvent); + } +} + +void CUDAMonitoringService::fillDescriptions(edm::ConfigurationDescriptions & descriptions) { + edm::ParameterSetDescription desc; + + desc.addUntracked("memoryConstruction", false)->setComment("Print memory information for each device after the construction of each module"); + desc.addUntracked("memoryBeginStream", true)->setComment("Print memory information for each device after the beginStream() of each module"); + desc.addUntracked("memoryPerEvent", true)->setComment("Print memory information for each device after each event"); + + descriptions.add("CUDAMonitoringService", desc); + descriptions.setComment("The memory information is the global state of the device. This gets confusing if there are multiple processes running on the same device. Probably the information retrieval should be re-thought?"); +} + + +// activity handlers +namespace { + template + void dumpUsedMemory(T& log, int num) { + int old = 0; + cudaCheck(cudaGetDevice(&old)); + for(int i = 0; i < num; ++i) { + size_t freeMemory, totalMemory; + cudaCheck(cudaSetDevice(i)); + cudaCheck(cudaMemGetInfo(&freeMemory, &totalMemory)); + log << "\n" << i << ": " << (totalMemory-freeMemory) / (1<<20) << " MB used / " << totalMemory / (1<<20) << " MB total"; + } + cudaCheck(cudaSetDevice(old)); + } +} + +void CUDAMonitoringService::postModuleConstruction(edm::ModuleDescription const& desc) { + auto log = edm::LogPrint("CUDAMonitoringService"); + log << "CUDA device memory after construction of " << desc.moduleLabel() << " (" << desc.moduleName() << ")"; + dumpUsedMemory(log, numberOfDevices_); +} + +void CUDAMonitoringService::postModuleBeginStream(edm::StreamContext const&, edm::ModuleCallingContext const& mcc) { + auto log = edm::LogPrint("CUDAMonitoringService"); + log<< "CUDA device memory after beginStream() of " << mcc.moduleDescription()->moduleLabel() << " (" << mcc.moduleDescription()->moduleName() << ")"; + dumpUsedMemory(log, numberOfDevices_); +} + +void CUDAMonitoringService::postEvent(edm::StreamContext const& sc) { + auto log = edm::LogPrint("CUDAMonitoringService"); + log << "CUDA device memory after event"; + dumpUsedMemory(log, numberOfDevices_); +} + +DEFINE_FWK_SERVICE(CUDAMonitoringService); diff --git a/HeterogeneousCore/CUDAServices/plugins/NVProfilerService.cc b/HeterogeneousCore/CUDAServices/plugins/NVProfilerService.cc index 2d0b68e352ec7..3d1684b0dcac0 100644 --- a/HeterogeneousCore/CUDAServices/plugins/NVProfilerService.cc +++ b/HeterogeneousCore/CUDAServices/plugins/NVProfilerService.cc @@ -1,6 +1,6 @@ // -*- C++ -*- // -// Package: Services +// Package: HeterogeneousCore/CUDAServices // Class : NVProfilerService #include @@ -9,8 +9,8 @@ #include #include -#include #include +#include #include @@ -34,12 +34,14 @@ #include "FWCore/ServiceRegistry/interface/PathContext.h" #include "FWCore/ServiceRegistry/interface/PathsAndConsumesOfModulesBase.h" #include "FWCore/ServiceRegistry/interface/ProcessContext.h" +#include "FWCore/ServiceRegistry/interface/Service.h" #include "FWCore/ServiceRegistry/interface/StreamContext.h" #include "FWCore/ServiceRegistry/interface/SystemBounds.h" #include "FWCore/Utilities/interface/BranchType.h" #include "FWCore/Utilities/interface/Exception.h" #include "FWCore/Utilities/interface/ProductKindOfType.h" #include "FWCore/Utilities/interface/TimeOfDay.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" using namespace std::string_literals; @@ -118,6 +120,8 @@ namespace { nvtxLightAmber = 0x00fff2cc, nvtxWhite = 0x00ffffff }; + + constexpr nvtxRangeId_t nvtxInvalidRangeId = 0xfffffffffffffffful; } class NVProfilerService { @@ -273,16 +277,29 @@ class NVProfilerService { void postEventReadFromSource(edm::StreamContext const&, edm::ModuleCallingContext const&); private: - bool highlight(std::string const&); + bool highlight(std::string const& label) const { + return (std::binary_search(highlightModules_.begin(), highlightModules_.end(), label)); + } - std::vector highlightModules_; - bool showModulePrefetching_; + uint32_t labelColor(std::string const& label) const { + return highlight(label) ? nvtxAmber : nvtxGreen; + } - unsigned int concurrentStreams_; - std::vector event_; // per-stream event ranges - std::vector> stream_modules_; // per-stream, per-module ranges + uint32_t labelColorLight(std::string const& label) const { + return highlight(label) ? nvtxLightAmber : nvtxLightGreen; + } + + std::vector highlightModules_; + const bool showModulePrefetching_; + bool skipFirstEvent_; + + unsigned int concurrentStreams_; + bool globalFirstEventDone_ = false; + std::vector streamFirstEventDone_; + std::vector event_; // per-stream event ranges + std::vector> stream_modules_; // per-stream, per-module ranges // use a tbb::concurrent_vector rather than an std::vector because its final size is not known - tbb::concurrent_vector global_modules_; // global per-module events + tbb::concurrent_vector global_modules_; // global per-module events private: struct Domains { @@ -327,13 +344,19 @@ class NVProfilerService { NVProfilerService::NVProfilerService(edm::ParameterSet const & config, edm::ActivityRegistry & registry) : highlightModules_(config.getUntrackedParameter>("highlightModules")), showModulePrefetching_(config.getUntrackedParameter("showModulePrefetching")), + skipFirstEvent_(config.getUntrackedParameter("skipFirstEvent")), concurrentStreams_(0), domains_(this) { + // make sure that CUDA is initialised, and that the CUDAService destructor is called after this service's destructor + edm::Service cudaService; + std::sort(highlightModules_.begin(), highlightModules_.end()); - // enables profile collection; if profiling is already enabled, has no effect - cudaProfilerStart(); + // enables profile collection; if profiling is already enabled it has no effect + if (not skipFirstEvent_) { + cudaProfilerStart(); + } registry.watchPreallocate(this, &NVProfilerService::preallocate); @@ -384,9 +407,11 @@ NVProfilerService::NVProfilerService(edm::ParameterSet const & config, edm::Acti registry.watchPrePathEvent(this, &NVProfilerService::prePathEvent); registry.watchPostPathEvent(this, &NVProfilerService::postPathEvent); - // these signal pair are NOT guaranteed to be called by the same thread - registry.watchPreModuleEventPrefetching(this, &NVProfilerService::preModuleEventPrefetching); - registry.watchPostModuleEventPrefetching(this, &NVProfilerService::postModuleEventPrefetching); + if (showModulePrefetching_) { + // these signal pair are NOT guaranteed to be called by the same thread + registry.watchPreModuleEventPrefetching(this, &NVProfilerService::preModuleEventPrefetching); + registry.watchPostModuleEventPrefetching(this, &NVProfilerService::postModuleEventPrefetching); + } // these signal pair are guaranteed to be called by the same thread registry.watchPreOpenFile(this, &NVProfilerService::preOpenFile); @@ -485,20 +510,17 @@ NVProfilerService::~NVProfilerService() { cudaProfilerStop(); } -bool -NVProfilerService::highlight(std::string const& label) { - return (std::binary_search(highlightModules_.begin(), highlightModules_.end(), label)); -} - void NVProfilerService::fillDescriptions(edm::ConfigurationDescriptions & descriptions) { edm::ParameterSetDescription desc; desc.addUntracked>("highlightModules", {})->setComment(""); desc.addUntracked("showModulePrefetching", false)->setComment("Show the stack of dependencies that requested to run a module."); + desc.addUntracked("skipFirstEvent", false)->setComment("Start profiling after the first event has completed.\nWith multiple streams, ignore transitions belonging to events started in parallel to the first event.\nRequires running nvprof with the '--profile-from-start off' option."); descriptions.add("NVProfilerService", desc); descriptions.setComment(R"(This Service provides CMSSW-aware annotations to nvprof/nvvm. -Notes: +Notes on nvprof options: + - the option '--profile-from-start off' should be used if skipFirstEvent is True. - the option '--cpu-profiling on' currently results in cmsRun being stuck at the beginning of the job. - the option '--cpu-thread-tracing on' is not compatible with jemalloc, and should only be used with cmsRunGlibC.)"); } @@ -518,6 +540,10 @@ NVProfilerService::preallocate(edm::service::SystemBounds const& bounds) { } event_.resize(concurrentStreams_); stream_modules_.resize(concurrentStreams_); + if (skipFirstEvent_) { + globalFirstEventDone_ = false; + streamFirstEventDone_.resize(concurrentStreams_, false); + } } void @@ -525,185 +551,244 @@ NVProfilerService::preBeginJob(edm::PathsAndConsumesOfModulesBase const& pathsAn nvtxDomainMark(global_domain(), "preBeginJob"); // FIXME this probably works only in the absence of subprocesses - unsigned int modules = pathsAndConsumes.allModules().size(); - global_modules_.resize(modules); + // size() + 1 because pathsAndConsumes.allModules() does not include the source + unsigned int modules = pathsAndConsumes.allModules().size() + 1; + global_modules_.resize(modules, nvtxInvalidRangeId); for (unsigned int sid = 0; sid < concurrentStreams_; ++sid) { - stream_modules_[sid].resize(modules); + stream_modules_[sid].resize(modules, nvtxInvalidRangeId); } } void NVProfilerService::postBeginJob() { - nvtxDomainMark(global_domain(), "postBeginJob"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainMark(global_domain(), "postBeginJob"); + } } void NVProfilerService::postEndJob() { - nvtxDomainMark(global_domain(), "postEndJob"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainMark(global_domain(), "postEndJob"); + } } void NVProfilerService::preSourceEvent(edm::StreamID sid) { - nvtxDomainRangePush(stream_domain(sid), "source"); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePush(stream_domain(sid), "source"); + } } void NVProfilerService::postSourceEvent(edm::StreamID sid) { - nvtxDomainRangePop(stream_domain(sid)); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePop(stream_domain(sid)); + } } void NVProfilerService::preSourceLumi(edm::LuminosityBlockIndex index) { - nvtxDomainRangePush(global_domain(), "source lumi"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), "source lumi"); + } } void NVProfilerService::postSourceLumi(edm::LuminosityBlockIndex index) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preSourceRun(edm::RunIndex index) { - nvtxDomainRangePush(global_domain(), "source run"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), "source run"); + } } void NVProfilerService::postSourceRun(edm::RunIndex index) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preOpenFile(std::string const& lfn, bool) { - nvtxDomainRangePush(global_domain(), ("open file "s + lfn).c_str()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), ("open file "s + lfn).c_str()); + } } void NVProfilerService::postOpenFile(std::string const& lfn, bool) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preCloseFile(std::string const & lfn, bool) { - nvtxDomainRangePush(global_domain(), ("close file "s + lfn).c_str()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), ("close file "s + lfn).c_str()); + } } void NVProfilerService::postCloseFile(std::string const& lfn, bool) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preModuleBeginStream(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " begin stream"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " begin stream"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleBeginStream(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleEndStream(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " end stream"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " end stream"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleEndStream(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preGlobalBeginRun(edm::GlobalContext const& gc) { - nvtxDomainRangePush(global_domain(), "global begin run"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), "global begin run"); + } } void NVProfilerService::postGlobalBeginRun(edm::GlobalContext const& gc) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preGlobalEndRun(edm::GlobalContext const& gc) { - nvtxDomainRangePush(global_domain(), "global end run"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), "global end run"); + } } void NVProfilerService::postGlobalEndRun(edm::GlobalContext const& gc) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preStreamBeginRun(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangePush(stream_domain(sid), "stream begin run"); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePush(stream_domain(sid), "stream begin run"); + } } void NVProfilerService::postStreamBeginRun(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangePop(stream_domain(sid)); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePop(stream_domain(sid)); + } } void NVProfilerService::preStreamEndRun(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangePush(stream_domain(sid), "stream end run"); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePush(stream_domain(sid), "stream end run"); + } } void NVProfilerService::postStreamEndRun(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangePop(stream_domain(sid)); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePop(stream_domain(sid)); + } } void NVProfilerService::preGlobalBeginLumi(edm::GlobalContext const& gc) { - nvtxDomainRangePush(global_domain(), "global begin lumi"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), "global begin lumi"); + } } void NVProfilerService::postGlobalBeginLumi(edm::GlobalContext const& gc) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preGlobalEndLumi(edm::GlobalContext const& gc) { - nvtxDomainRangePush(global_domain(), "global end lumi"); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePush(global_domain(), "global end lumi"); + } } void NVProfilerService::postGlobalEndLumi(edm::GlobalContext const& gc) { - nvtxDomainRangePop(global_domain()); + if (not skipFirstEvent_ or globalFirstEventDone_) { + nvtxDomainRangePop(global_domain()); + } } void NVProfilerService::preStreamBeginLumi(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangePush(stream_domain(sid), "stream begin lumi"); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePush(stream_domain(sid), "stream begin lumi"); + } } void NVProfilerService::postStreamBeginLumi(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangePop(stream_domain(sid)); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePop(stream_domain(sid)); + } } void @@ -715,156 +800,186 @@ NVProfilerService::preStreamEndLumi(edm::StreamContext const& sc) { void NVProfilerService::postStreamEndLumi(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangePop(stream_domain(sid)); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangePop(stream_domain(sid)); + } } void NVProfilerService::preEvent(edm::StreamContext const& sc) { auto sid = sc.streamID(); - event_[sid] = nvtxDomainRangeStartColor(stream_domain(sid), "event", nvtxDarkGreen); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + event_[sid] = nvtxDomainRangeStartColor(stream_domain(sid), "event", nvtxDarkGreen); + } } void NVProfilerService::postEvent(edm::StreamContext const& sc) { auto sid = sc.streamID(); - nvtxDomainRangeEnd(stream_domain(sid), event_[sid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainRangeEnd(stream_domain(sid), event_[sid]); + event_[sid] = nvtxInvalidRangeId; + } else { + streamFirstEventDone_[sid] = true; + // there is a possible race condition among different threads processing different events; + // however, cudaProfilerStart() is supposed to be thread-safe and ignore multiple calls, so this should not be an issue. + if (std::all_of(streamFirstEventDone_.begin(), streamFirstEventDone_.end(), [](bool x){ return x; })) { + globalFirstEventDone_ = true; + cudaProfilerStart(); + } + } } void NVProfilerService::prePathEvent(edm::StreamContext const& sc, edm::PathContext const& pc) { auto sid = sc.streamID(); - nvtxDomainMark(global_domain(), ("before path "s + pc.pathName()).c_str()); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainMark(global_domain(), ("before path "s + pc.pathName()).c_str()); + } } void NVProfilerService::postPathEvent(edm::StreamContext const& sc, edm::PathContext const& pc, edm::HLTPathStatus const& hlts) { auto sid = sc.streamID(); - nvtxDomainMark(global_domain(), ("after path "s + pc.pathName()).c_str()); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + nvtxDomainMark(global_domain(), ("after path "s + pc.pathName()).c_str()); + } } void NVProfilerService::preModuleEventPrefetching(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { - if (showModulePrefetching_) { - auto sid = sc.streamID(); + auto sid = sc.streamID(); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { auto mid = mcc.moduleDescription()->id(); auto const & label = mcc.moduleDescription()->moduleLabel(); auto const & msg = label + " prefetching"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxLightAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxLightGreen); + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColorLight(label)); } } void NVProfilerService::postModuleEventPrefetching(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { - if (showModulePrefetching_) { - auto sid = sc.streamID(); + auto sid = sc.streamID(); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { auto mid = mcc.moduleDescription()->id(); nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; } } void NVProfilerService::preModuleConstruction(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - global_modules_.grow_to_at_least(mid+1); - auto const & label = desc.moduleLabel(); - auto const & msg = label + " construction"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_) { + auto mid = desc.id(); + global_modules_.grow_to_at_least(mid+1); + auto const & label = desc.moduleLabel(); + auto const & msg = label + " construction"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleConstruction(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_) { + auto mid = desc.id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleBeginJob(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - auto const & label = desc.moduleLabel(); - auto const & msg = label + " begin job"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_) { + auto mid = desc.id(); + auto const & label = desc.moduleLabel(); + auto const & msg = label + " begin job"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleBeginJob(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_) { + auto mid = desc.id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleEndJob(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - auto const & label = desc.moduleLabel(); - auto const & msg = label + " end job"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = desc.id(); + auto const & label = desc.moduleLabel(); + auto const & msg = label + " end job"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleEndJob(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = desc.id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleEventAcquire(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " acquire"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " acquire"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleEventAcquire(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleEvent(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), label.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), label.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), label.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleEvent(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleEventDelayedGet(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { /* FIXME auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " delayed get"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), label.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), label.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " delayed get"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), label.c_str(), labelColorLight(label)); + } */ } @@ -872,8 +987,11 @@ void NVProfilerService::postModuleEventDelayedGet(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { /* FIXME auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } */ } @@ -881,13 +999,13 @@ void NVProfilerService::preEventReadFromSource(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { /* FIXME auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " read from source"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " read from source"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColorLight(label)); + } */ } @@ -895,171 +1013,196 @@ void NVProfilerService::postEventReadFromSource(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { /* FIXME auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } */ } void NVProfilerService::preModuleStreamBeginRun(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " stream begin run"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " stream begin run"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleStreamBeginRun(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleStreamEndRun(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " stream end run"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " stream end run"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleStreamEndRun(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleStreamBeginLumi(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " stream begin lumi"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " stream begin lumi"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleStreamBeginLumi(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleStreamEndLumi(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " stream end lumi"; - if (highlight(label)) - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxAmber); - else - stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " stream end lumi"; + assert(stream_modules_[sid][mid] == nvtxInvalidRangeId); + stream_modules_[sid][mid] = nvtxDomainRangeStartColor(stream_domain(sid), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleStreamEndLumi(edm::StreamContext const& sc, edm::ModuleCallingContext const& mcc) { auto sid = sc.streamID(); - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + if (not skipFirstEvent_ or streamFirstEventDone_[sid]) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(stream_domain(sid), stream_modules_[sid][mid]); + stream_modules_[sid][mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleGlobalBeginRun(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " global begin run"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " global begin run"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleGlobalBeginRun(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleGlobalEndRun(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " global end run"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " global end run"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleGlobalEndRun(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleGlobalBeginLumi(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " global begin lumi"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " global begin lumi"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleGlobalBeginLumi(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preModuleGlobalEndLumi(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - auto const & label = mcc.moduleDescription()->moduleLabel(); - auto const & msg = label + " global end lumi"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + auto const & label = mcc.moduleDescription()->moduleLabel(); + auto const & msg = label + " global end lumi"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postModuleGlobalEndLumi(edm::GlobalContext const& gc, edm::ModuleCallingContext const& mcc) { - auto mid = mcc.moduleDescription()->id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_ or globalFirstEventDone_) { + auto mid = mcc.moduleDescription()->id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } void NVProfilerService::preSourceConstruction(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - global_modules_.grow_to_at_least(mid+1); - auto const & label = desc.moduleLabel(); - auto const & msg = label + " construction"; - if (highlight(label)) - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxAmber); - else - global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), nvtxGreen);; + if (not skipFirstEvent_) { + auto mid = desc.id(); + global_modules_.grow_to_at_least(mid+1); + auto const & label = desc.moduleLabel(); + auto const & msg = label + " construction"; + global_modules_[mid] = nvtxDomainRangeStartColor(global_domain(), msg.c_str(), labelColor(label)); + } } void NVProfilerService::postSourceConstruction(edm::ModuleDescription const& desc) { - auto mid = desc.id(); - nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + if (not skipFirstEvent_) { + auto mid = desc.id(); + nvtxDomainRangeEnd(global_domain(), global_modules_[mid]); + global_modules_[mid] = nvtxInvalidRangeId; + } } #include "FWCore/ServiceRegistry/interface/ServiceMaker.h" diff --git a/HeterogeneousCore/CUDAServices/plugins/plugins.cc b/HeterogeneousCore/CUDAServices/plugins/plugins.cc new file mode 100644 index 0000000000000..d8aefa42e9c99 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/plugins/plugins.cc @@ -0,0 +1,4 @@ +#include "FWCore/ServiceRegistry/interface/ServiceMaker.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" + +DEFINE_FWK_SERVICE(CUDAService); diff --git a/HeterogeneousCore/CUDAServices/scripts/cmsCudaRebuild.sh b/HeterogeneousCore/CUDAServices/scripts/cmsCudaRebuild.sh new file mode 100755 index 0000000000000..bde3e26382976 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/scripts/cmsCudaRebuild.sh @@ -0,0 +1,10 @@ +#! /bin/bash -e + +# move to the .../src directory +cd $CMSSW_BASE/src/ + +# check out all packages containing .cu files +git ls-files --full-name | grep '.*\.cu$' | cut -d/ -f-2 | sort -u | xargs git cms-addpkg + +# rebuild all checked out packages +scram b -j diff --git a/HeterogeneousCore/CUDAServices/scripts/cmsCudaSetup.sh b/HeterogeneousCore/CUDAServices/scripts/cmsCudaSetup.sh new file mode 100755 index 0000000000000..f3335f4cd409f --- /dev/null +++ b/HeterogeneousCore/CUDAServices/scripts/cmsCudaSetup.sh @@ -0,0 +1,19 @@ +#! /bin/bash +TOOL=$CMSSW_BASE/config/toolbox/$SCRAM_ARCH/tools/selected/cuda.xml + +# enumerate the supported streaming multiprocessor (sm) compute capabilites +DOTS=$(cudaComputeCapabilities | awk '{ print $2 }' | sort -u) +CAPS=$(echo $DOTS | sed -e's#\.*##g') + +# remove existing capabilities +sed -i $TOOL -e'\##d' + +# add support for the capabilities found on this machine +for CAP in $CAPS; do + sed -i $TOOL -e"\##a\ " +done + +# reconfigure the cuda.xml tool +scram setup cuda + +echo "SCRAM configured to support CUDA streaming multiprocessor architectures $DOTS" diff --git a/HeterogeneousCore/CUDAServices/scripts/cudaPreallocate.py b/HeterogeneousCore/CUDAServices/scripts/cudaPreallocate.py new file mode 100755 index 0000000000000..331ddd30f73bd --- /dev/null +++ b/HeterogeneousCore/CUDAServices/scripts/cudaPreallocate.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +from __future__ import print_function +import re +import sys +import argparse + +def main(opts): + device = [] + host = [] + + device_re = re.compile("Device.*allocated new device block.*\((?P\d+) bytes") + host_re = re.compile("Host.*allocated new host block.*\((?P\d+) bytes") + + f = open(opts.file) + for line in f: + m = device_re.search(line) + if m: + device.append(m.group("bytes")) + continue + m = host_re.search(line) + if m: + host.append(m.group("bytes")) + f.close() + + print("process.CUDAService.allocator.devicePreallocate = cms.untracked.vuint32(%s)" % ",".join(device)) + print("process.CUDAService.allocator.hostPreallocate = cms.untracked.vuint32(%s)" % ",".join(host)) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="""Extract CUDAService preallocation parameters from a log file. + +To use, run the job once with "process.CUDAService.allocator.debug = +True" and direct the output to a file. Then run this script by passing +the file as an argument, and copy the output of this script back to +the configuration file.""") + parser.add_argument("file", type=str, help="Log file to parse") + opts = parser.parse_args() + main(opts) diff --git a/HeterogeneousCore/CUDAServices/scripts/nvprof-remote b/HeterogeneousCore/CUDAServices/scripts/nvprof-remote new file mode 100755 index 0000000000000..de455e04c9b69 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/scripts/nvprof-remote @@ -0,0 +1,21 @@ +#! /bin/bash + +# load the CMSSW environment +if ! [ "$CMSSW_BASE" ]; then + EXEC=`readlink -f $0` + CMSSW_BASE=`dirname "$EXEC"` + unset EXEC +fi +which scram >& /dev/null || source "$CMS_PATH"/cmsset_default.sh +eval `cd "$CMSSW_BASE"; scram runtime -sh 2> /dev/null` + +# log the commands being run +{ + date + echo "cwd: $PWD" + echo "cmd: $0 $@" + echo +} > $CMSSW_BASE/tmp/nvprof.log + +# run the CUDA profiler +nvprof "$@" diff --git a/HeterogeneousCore/CUDAServices/src/CUDAService.cc b/HeterogeneousCore/CUDAServices/src/CUDAService.cc new file mode 100644 index 0000000000000..babe062f9bab2 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/src/CUDAService.cc @@ -0,0 +1,492 @@ +#include +#include +#include + +#include +#include + +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +#include "CachingDeviceAllocator.h" +#include "CachingHostAllocator.h" + +void setCudaLimit(cudaLimit limit, const char* name, size_t request) { + // read the current device + int device; + cudaCheck(cudaGetDevice(&device)); + // try to set the requested limit + auto result = cudaDeviceSetLimit(limit, request); + if (cudaErrorUnsupportedLimit == result) { + edm::LogWarning("CUDAService") << "CUDA device " << device << ": unsupported limit \"" << name << "\""; + return; + } + // read back the limit value + size_t value; + cudaCheck(cudaDeviceGetLimit(&value, limit)); + if (cudaSuccess != result) { + edm::LogWarning("CUDAService") << "CUDA device " << device << ": failed to set limit \"" << name << "\" to " << request << ", current value is " << value ; + } else if (value != request) { + edm::LogWarning("CUDAService") << "CUDA device " << device << ": limit \"" << name << "\" set to " << value << " instead of requested " << request; + } +} + +constexpr +unsigned int getCudaCoresPerSM(unsigned int major, unsigned int minor) { + switch (major * 10 + minor) { + // Fermi architecture + case 20: // SM 2.0: GF100 class + return 32; + case 21: // SM 2.1: GF10x class + return 48; + + // Kepler architecture + case 30: // SM 3.0: GK10x class + case 32: // SM 3.2: GK10x class + case 35: // SM 3.5: GK11x class + case 37: // SM 3.7: GK21x class + return 192; + + // Maxwell architecture + case 50: // SM 5.0: GM10x class + case 52: // SM 5.2: GM20x class + case 53: // SM 5.3: GM20x class + return 128; + + // Pascal architecture + case 60: // SM 6.0: GP100 class + return 64; + case 61: // SM 6.1: GP10x class + case 62: // SM 6.2: GP10x class + return 128; + + // Volta architecture + case 70: // SM 7.0: GV100 class + case 72: // SM 7.2: GV11b class + return 64; + + // Turing architecture + case 75: // SM 7.5: TU10x class + return 64; + + // unknown architecture, return a default value + default: + return 64; + } +} + +namespace { + template typename UniquePtr, + typename Allocate> + void preallocate(Allocate allocate, const std::vector& bufferSizes) { + auto current_device = cuda::device::current::get(); + auto stream = current_device.create_stream(cuda::stream::implicitly_synchronizes_with_default_stream); + + std::vector > buffers; + buffers.reserve(bufferSizes.size()); + for(auto size: bufferSizes) { + buffers.push_back(allocate(size, stream)); + } + } + + void devicePreallocate(CUDAService& cs, int numberOfDevices, const std::vector& bufferSizes) { + int device; + cudaCheck(cudaGetDevice(&device)); + for(int i=0; i([&](size_t size, cuda::stream_t<>& stream) { + return cs.make_device_unique(size, stream); + }, bufferSizes); + } + cudaCheck(cudaSetDevice(device)); + } + + void hostPreallocate(CUDAService& cs, const std::vector& bufferSizes) { + preallocate([&](size_t size, cuda::stream_t<>& stream) { + return cs.make_host_unique(size, stream); + }, bufferSizes); + } +} + +/// Constructor +CUDAService::CUDAService(edm::ParameterSet const& config, edm::ActivityRegistry& iRegistry) { + bool configEnabled = config.getUntrackedParameter("enabled"); + if (not configEnabled) { + edm::LogInfo("CUDAService") << "CUDAService disabled by configuration"; + return; + } + + auto status = cudaGetDeviceCount(&numberOfDevices_); + if (cudaSuccess != status) { + edm::LogWarning("CUDAService") << "Failed to initialize the CUDA runtime.\n" << "Disabling the CUDAService."; + return; + } + edm::LogInfo log("CUDAService"); + computeCapabilities_.reserve(numberOfDevices_); + log << "CUDA runtime successfully initialised, found " << numberOfDevices_ << " compute devices.\n\n"; + + auto numberOfStreamsPerDevice = config.getUntrackedParameter("numberOfStreamsPerDevice"); + if (numberOfStreamsPerDevice > 0) { + numberOfStreamsTotal_ = numberOfStreamsPerDevice * numberOfDevices_; + log << "Number of edm::Streams per CUDA device has been set to " << numberOfStreamsPerDevice << ", for a total of " << numberOfStreamsTotal_ << " edm::Streams across all CUDA device(s).\n\n"; + } + + auto const& limits = config.getUntrackedParameter("limits"); + auto printfFifoSize = limits.getUntrackedParameter("cudaLimitPrintfFifoSize"); + auto stackSize = limits.getUntrackedParameter("cudaLimitStackSize"); + auto mallocHeapSize = limits.getUntrackedParameter("cudaLimitMallocHeapSize"); + auto devRuntimeSyncDepth = limits.getUntrackedParameter("cudaLimitDevRuntimeSyncDepth"); + auto devRuntimePendingLaunchCount = limits.getUntrackedParameter("cudaLimitDevRuntimePendingLaunchCount"); + + for (int i = 0; i < numberOfDevices_; ++i) { + // read information about the compute device. + // see the documentation of cudaGetDeviceProperties() for more information. + cudaDeviceProp properties; + cudaCheck(cudaGetDeviceProperties(&properties, i)); + log << "CUDA device " << i << ": " << properties.name << '\n'; + + // compute capabilities + log << " compute capability: " << properties.major << "." << properties.minor << " (sm_" << properties.major << properties.minor << ")\n"; + computeCapabilities_.emplace_back(properties.major, properties.minor); + log << " streaming multiprocessors: " << std::setw(13) << properties.multiProcessorCount << '\n'; + log << " CUDA cores: " << std::setw(28) << properties.multiProcessorCount * getCudaCoresPerSM(properties.major, properties.minor ) << '\n'; + log << " single to double performance: " << std::setw(8) << properties.singleToDoublePrecisionPerfRatio << ":1\n"; + + // compute mode + static constexpr const char* computeModeDescription[] = { + "default (shared)", // cudaComputeModeDefault + "exclusive (single thread)", // cudaComputeModeExclusive + "prohibited", // cudaComputeModeProhibited + "exclusive (single process)", // cudaComputeModeExclusiveProcess + "unknown" + }; + log << " compute mode:" << std::right << std::setw(27) << computeModeDescription[std::min(properties.computeMode, (int) std::size(computeModeDescription) - 1)] << '\n'; + + // TODO if a device is in exclusive use, skip it and remove it from the list, instead of failing with abort() + cudaCheck(cudaSetDevice(i)); + cudaCheck(cudaSetDeviceFlags(cudaDeviceScheduleAuto | cudaDeviceMapHost)); + + // read the free and total amount of memory available for allocation by the device, in bytes. + // see the documentation of cudaMemGetInfo() for more information. + size_t freeMemory, totalMemory; + cudaCheck(cudaMemGetInfo(&freeMemory, &totalMemory)); + log << " memory: " << std::setw(6) << freeMemory / (1 << 20) << " MB free / " << std::setw(6) << totalMemory / (1 << 20) << " MB total\n"; + log << " constant memory: " << std::setw(6) << properties.totalConstMem / (1 << 10) << " kB\n"; + log << " L2 cache size: " << std::setw(6) << properties.l2CacheSize / (1 << 10) << " kB\n"; + + // L1 cache behaviour + static constexpr const char* l1CacheModeDescription[] = { + "unknown", + "local memory", + "global memory", + "local and global memory" + }; + int l1CacheMode = properties.localL1CacheSupported + 2 * properties.globalL1CacheSupported; + log << " L1 cache mode:" << std::setw(26) << std::right << l1CacheModeDescription[l1CacheMode] << '\n'; + log << '\n'; + + log << "Other capabilities\n"; + log << " " << (properties.canMapHostMemory ? "can" : "cannot") << " map host memory into the CUDA address space for use with cudaHostAlloc()/cudaHostGetDevicePointer()\n"; + log << " " << (properties.pageableMemoryAccess ? "supports" : "does not support") << " coherently accessing pageable memory without calling cudaHostRegister() on it\n"; + log << " " << (properties.pageableMemoryAccessUsesHostPageTables ? "can" : "cannot") << " access pageable memory via the host's page tables\n"; + log << " " << (properties.canUseHostPointerForRegisteredMem ? "can" : "cannot") << " access host registered memory at the same virtual address as the host\n"; + log << " " << (properties.unifiedAddressing ? "shares" : "does not share") << " a unified address space with the host\n"; + log << " " << (properties.managedMemory ? "supports" : "does not support") << " allocating managed memory on this system\n"; + log << " " << (properties.concurrentManagedAccess ? "can" : "cannot") << " coherently access managed memory concurrently with the host\n"; + log << " " << "the host " << (properties.directManagedMemAccessFromHost ? "can" : "cannot") << " directly access managed memory on the device without migration\n"; + log << " " << (properties.cooperativeLaunch ? "supports" : "does not support") << " launching cooperative kernels via cudaLaunchCooperativeKernel()\n"; + log << " " << (properties.cooperativeMultiDeviceLaunch ? "supports" : "does not support") << " launching cooperative kernels via cudaLaunchCooperativeKernelMultiDevice()\n"; + log << '\n'; + + // set and read the CUDA device flags. + // see the documentation of cudaSetDeviceFlags and cudaGetDeviceFlags for more information. + log << "CUDA flags\n"; + unsigned int flags; + cudaCheck(cudaGetDeviceFlags(&flags)); + switch (flags & cudaDeviceScheduleMask) { + case cudaDeviceScheduleAuto: + log << " thread policy: default\n"; + break; + case cudaDeviceScheduleSpin: + log << " thread policy: spin\n"; + break; + case cudaDeviceScheduleYield: + log << " thread policy: yield\n"; + break; + case cudaDeviceScheduleBlockingSync: + log << " thread policy: blocking sync\n"; + break; + default: + log << " thread policy: undefined\n"; + } + if (flags & cudaDeviceMapHost) { + log << " pinned host memory allocations: enabled\n"; + } else { + log << " pinned host memory allocations: disabled\n"; + } + if (flags & cudaDeviceLmemResizeToMax) { + log << " kernel host memory reuse: enabled\n"; + } else { + log << " kernel host memory reuse: disabled\n"; + } + log << '\n'; + + // set and read the CUDA resource limits. + // see the documentation of cudaDeviceSetLimit() for more information. + + // cudaLimitPrintfFifoSize controls the size in bytes of the shared FIFO used by the + // printf() device system call. + if (printfFifoSize >= 0) { + setCudaLimit(cudaLimitPrintfFifoSize, "cudaLimitPrintfFifoSize", printfFifoSize); + } + // cudaLimitStackSize controls the stack size in bytes of each GPU thread. + if (stackSize >= 0) { + setCudaLimit(cudaLimitStackSize, "cudaLimitStackSize", stackSize); + } + // cudaLimitMallocHeapSize controls the size in bytes of the heap used by the malloc() + // and free() device system calls. + if (mallocHeapSize >= 0) { + setCudaLimit(cudaLimitMallocHeapSize, "cudaLimitMallocHeapSize", mallocHeapSize); + } + if ((properties.major > 3) or (properties.major == 3 and properties.minor >= 5)) { + // cudaLimitDevRuntimeSyncDepth controls the maximum nesting depth of a grid at which + // a thread can safely call cudaDeviceSynchronize(). + if (devRuntimeSyncDepth >= 0) { + setCudaLimit(cudaLimitDevRuntimeSyncDepth, "cudaLimitDevRuntimeSyncDepth", devRuntimeSyncDepth); + } + // cudaLimitDevRuntimePendingLaunchCount controls the maximum number of outstanding + // device runtime launches that can be made from the current device. + if (devRuntimePendingLaunchCount >= 0) { + setCudaLimit(cudaLimitDevRuntimePendingLaunchCount, "cudaLimitDevRuntimePendingLaunchCount", devRuntimePendingLaunchCount); + } + } + + size_t value; + log << "CUDA limits\n"; + cudaCheck(cudaDeviceGetLimit(&value, cudaLimitPrintfFifoSize)); + log << " printf buffer size: " << std::setw(10) << value / (1 << 20) << " MB\n"; + cudaCheck(cudaDeviceGetLimit(&value, cudaLimitStackSize)); + log << " stack size: " << std::setw(10) << value / (1 << 10) << " kB\n"; + cudaCheck(cudaDeviceGetLimit(&value, cudaLimitMallocHeapSize)); + log << " malloc heap size: " << std::setw(10) << value / (1 << 20) << " MB\n"; + if ((properties.major > 3) or (properties.major == 3 and properties.minor >= 5)) { + cudaCheck(cudaDeviceGetLimit(&value, cudaLimitDevRuntimeSyncDepth)); + log << " runtime sync depth: " << std::setw(10) << value << '\n'; + cudaCheck(cudaDeviceGetLimit(&value, cudaLimitDevRuntimePendingLaunchCount)); + log << " runtime pending launch count: " << std::setw(10) << value << '\n'; + } + log << '\n'; + } + + // create allocator + auto const& allocator = config.getUntrackedParameter("allocator"); + auto allocatorEnabled = allocator.getUntrackedParameter("enabled"); + if(allocatorEnabled) { + auto binGrowth = allocator.getUntrackedParameter("binGrowth"); + auto minBin = allocator.getUntrackedParameter("minBin"); + auto maxBin = allocator.getUntrackedParameter("maxBin"); + size_t maxCachedBytes = allocator.getUntrackedParameter("maxCachedBytes"); + auto maxCachedFraction = allocator.getUntrackedParameter("maxCachedFraction"); + auto debug = allocator.getUntrackedParameter("debug"); + + size_t minCachedBytes = std::numeric_limits::max(); + int currentDevice; + cudaCheck(cudaGetDevice(¤tDevice)); + for (int i = 0; i < numberOfDevices_; ++i) { + size_t freeMemory, totalMemory; + cudaCheck(cudaSetDevice(i)); + cudaCheck(cudaMemGetInfo(&freeMemory, &totalMemory)); + minCachedBytes = std::min(minCachedBytes, static_cast(maxCachedFraction * freeMemory)); + } + cudaCheck(cudaSetDevice(currentDevice)); + if (maxCachedBytes > 0) { + minCachedBytes = std::min(minCachedBytes, maxCachedBytes); + } + log << "cub::CachingDeviceAllocator settings\n" + << " bin growth " << binGrowth << "\n" + << " min bin " << minBin << "\n" + << " max bin " << maxBin << "\n" + << " resulting bins:\n"; + for (auto bin = minBin; bin <= maxBin; ++bin) { + auto binSize = notcub::CachingDeviceAllocator::IntPow(binGrowth, bin); + if (binSize >= (1<<30) and binSize % (1<<30) == 0) { + log << " " << std::setw(8) << (binSize >> 30) << " GB\n"; + } else if (binSize >= (1<<20) and binSize % (1<<20) == 0) { + log << " " << std::setw(8) << (binSize >> 20) << " MB\n"; + } else if (binSize >= (1<<10) and binSize % (1<<10) == 0) { + log << " " << std::setw(8) << (binSize >> 10) << " kB\n"; + } else { + log << " " << std::setw(9) << binSize << " B\n"; + } + } + log << " maximum amount of cached memory: " << (minCachedBytes >> 20) << " MB\n"; + + allocator_ = std::make_unique(notcub::CachingDeviceAllocator::IntPow(binGrowth, maxBin), + binGrowth, minBin, maxBin, minCachedBytes, + false, // do not skip cleanup + debug + ); + log << "\n"; + } + else { + log << "cub::CachingDeviceAllocator disabled\n"; + } + + log << "CUDAService fully initialized"; + enabled_ = true; + + // Preallocate buffers if asked to + devicePreallocate(*this, numberOfDevices_, allocator.getUntrackedParameter >("devicePreallocate")); + hostPreallocate(*this, allocator.getUntrackedParameter >("hostPreallocate")); +} + +CUDAService::~CUDAService() { + if (enabled_) { + // Explicitly destruct the allocator before the device resets below + if(allocator_) { + allocator_.reset(); + } + + for (int i = 0; i < numberOfDevices_; ++i) { + cudaCheck(cudaSetDevice(i)); + cudaCheck(cudaDeviceSynchronize()); + // Explicitly destroys and cleans up all resources associated with the current device in the + // current process. Any subsequent API call to this device will reinitialize the device. + // Useful to check for memory leaks with `cuda-memcheck --tool memcheck --leak-check full`. + cudaDeviceReset(); + } + } +} + +void CUDAService::fillDescriptions(edm::ConfigurationDescriptions & descriptions) { + edm::ParameterSetDescription desc; + desc.addUntracked("enabled", true); + desc.addUntracked("numberOfStreamsPerDevice", 0)->setComment("Upper limit of the number of edm::Streams that will run on a single CUDA GPU device. The remaining edm::Streams will be run only on other devices (for time being this means CPU in practice).\nThe value '0' means 'unlimited', a value >= 1 imposes the limit."); + + edm::ParameterSetDescription limits; + limits.addUntracked("cudaLimitPrintfFifoSize", -1)->setComment("Size in bytes of the shared FIFO used by the printf() device system call."); + limits.addUntracked("cudaLimitStackSize", -1)->setComment("Stack size in bytes of each GPU thread."); + limits.addUntracked("cudaLimitMallocHeapSize", -1)->setComment("Size in bytes of the heap used by the malloc() and free() device system calls."); + limits.addUntracked("cudaLimitDevRuntimeSyncDepth", -1)->setComment("Maximum nesting depth of a grid at which a thread can safely call cudaDeviceSynchronize()."); + limits.addUntracked("cudaLimitDevRuntimePendingLaunchCount", -1)->setComment("Maximum number of outstanding device runtime launches that can be made from the current device."); + desc.addUntracked("limits", limits)->setComment("See the documentation of cudaDeviceSetLimit for more information.\nSetting any of these options to -1 keeps the default value."); + + edm::ParameterSetDescription allocator; + allocator.addUntracked("enabled", true)->setComment("Enable caching. Setting this to false means to call cudaMalloc for each allocation etc."); + allocator.addUntracked("binGrowth", 8)->setComment("Growth factor (bin_growth in cub::CachingDeviceAllocator"); + allocator.addUntracked("minBin", 1)->setComment("Smallest bin, corresponds to binGrowth^minBin bytes (min_bin in cub::CacingDeviceAllocator"); + allocator.addUntracked("maxBin", 9)->setComment("Largest bin, corresponds to binGrowth^maxBin bytes (max_bin in cub::CachingDeviceAllocator). Note that unlike in cub, allocations larger than binGrowth^maxBin are set to fail."); + allocator.addUntracked("maxCachedBytes", 0)->setComment("Total storage for the allocator. 0 means no limit."); + allocator.addUntracked("maxCachedFraction", 0.8)->setComment("Fraction of total device memory taken for the allocator. In case there are multiple devices with different amounts of memory, the smallest of them is taken. If maxCachedBytes is non-zero, the smallest of them is taken."); + allocator.addUntracked("debug", false)->setComment("Enable debug prints"); + allocator.addUntracked >("devicePreallocate", std::vector{})->setComment("Preallocates buffers of given bytes on all devices"); + allocator.addUntracked >("hostPreallocate", std::vector{})->setComment("Preallocates buffers of given bytes on the host"); + desc.addUntracked("allocator", allocator)->setComment("See the documentation of cub::CachingDeviceAllocator for more details."); + + descriptions.add("CUDAService", desc); +} + +int CUDAService::deviceWithMostFreeMemory() const { + // save the current device + int currentDevice; + cudaCheck(cudaGetDevice(¤tDevice)); + + size_t maxFreeMemory = 0; + int device = -1; + for(int i = 0; i < numberOfDevices_; ++i) { + /* + // TODO: understand why the api-wrappers version gives same value for all devices + auto device = cuda::device::get(i); + auto freeMemory = device.memory.amount_free(); + */ + size_t freeMemory, totalMemory; + cudaSetDevice(i); + cudaMemGetInfo(&freeMemory, &totalMemory); + edm::LogPrint("CUDAService") << "CUDA device " << i << ": " << freeMemory / (1 << 20) << " MB free / " << totalMemory / (1 << 20) << " MB total memory"; + if (freeMemory > maxFreeMemory) { + maxFreeMemory = freeMemory; + device = i; + } + } + // restore the current device + cudaCheck(cudaSetDevice(currentDevice)); + return device; +} + +void CUDAService::setCurrentDevice(int device) const { + cuda::device::current::set(device); +} + +int CUDAService::getCurrentDevice() const { + return cuda::device::current::get().id(); +} + + +// allocator +struct CUDAService::Allocator { + template + Allocator(size_t max, Args&&... args): maxAllocation(max), deviceAllocator(args...), hostAllocator(std::forward(args)...) {} + + void devicePreallocate(int numberOfDevices, const std::vector& bytes); + void hostPreallocate(int numberOfDevices, const std::vector& bytes); + + size_t maxAllocation; + notcub::CachingDeviceAllocator deviceAllocator; + notcub::CachingHostAllocator hostAllocator; +}; + +void *CUDAService::allocate_device(int dev, size_t nbytes, cuda::stream_t<>& stream) { + void *ptr = nullptr; + if(allocator_) { + if(nbytes > allocator_->maxAllocation) { + throw std::runtime_error("Tried to allocate "+std::to_string(nbytes)+" bytes, but the allocator maximum is "+std::to_string(allocator_->maxAllocation)); + } + + cuda::throw_if_error(allocator_->deviceAllocator.DeviceAllocate(dev, &ptr, nbytes, stream.id())); + } + else { + cuda::device::current::scoped_override_t<> setDeviceForThisScope(dev); + cuda::throw_if_error(cudaMalloc(&ptr, nbytes)); + } + return ptr; +} + +void CUDAService::free_device(int device, void *ptr) { + if(allocator_) { + cuda::throw_if_error(allocator_->deviceAllocator.DeviceFree(device, ptr)); + } + else { + cuda::device::current::scoped_override_t<> setDeviceForThisScope(device); + cuda::throw_if_error(cudaFree(ptr)); + } +} + +void *CUDAService::allocate_host(size_t nbytes, cuda::stream_t<>& stream) { + void *ptr = nullptr; + + if(allocator_) { + if(nbytes > allocator_->maxAllocation) { + throw std::runtime_error("Tried to allocate "+std::to_string(nbytes)+" bytes, but the allocator maximum is "+std::to_string(allocator_->maxAllocation)); + } + + cuda::throw_if_error(allocator_->hostAllocator.HostAllocate(&ptr, nbytes, stream.id())); + } + else { + cuda::throw_if_error(cudaMallocHost(&ptr, nbytes)); + } + + return ptr; +} + +void CUDAService::free_host(void *ptr) { + if(allocator_) { + cuda::throw_if_error(allocator_->hostAllocator.HostFree(ptr)); + } + else { + cuda::throw_if_error(cudaFreeHost(ptr)); + } +} diff --git a/HeterogeneousCore/CUDAServices/src/CachingDeviceAllocator.h b/HeterogeneousCore/CUDAServices/src/CachingDeviceAllocator.h new file mode 100644 index 0000000000000..eb0b6686ef8d5 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/src/CachingDeviceAllocator.h @@ -0,0 +1,710 @@ +#ifndef HeterogenousCore_CUDAServices_src_CachingDeviceAllocator_h +#define HeterogenousCore_CUDAServices_src_CachingDeviceAllocator_h + +/****************************************************************************** + * Copyright (c) 2011, Duane Merrill. All rights reserved. + * Copyright (c) 2011-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the NVIDIA CORPORATION nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +/** + * Forked to CMSSW by Matti Kortelainen + */ + +/****************************************************************************** + * Simple caching allocator for device memory allocations. The allocator is + * thread-safe and capable of managing device allocations on multiple devices. + ******************************************************************************/ + +#include +#include +#include + +#include +#include + +/// CUB namespace +namespace notcub { + + +/** + * \addtogroup UtilMgmt + * @{ + */ + + +/****************************************************************************** + * CachingDeviceAllocator (host use) + ******************************************************************************/ + +/** + * \brief A simple caching allocator for device memory allocations. + * + * \par Overview + * The allocator is thread-safe and stream-safe and is capable of managing cached + * device allocations on multiple devices. It behaves as follows: + * + * \par + * - Allocations from the allocator are associated with an \p active_stream. Once freed, + * the allocation becomes available immediately for reuse within the \p active_stream + * with which it was associated with during allocation, and it becomes available for + * reuse within other streams when all prior work submitted to \p active_stream has completed. + * - Allocations are categorized and cached by bin size. A new allocation request of + * a given size will only consider cached allocations within the corresponding bin. + * - Bin limits progress geometrically in accordance with the growth factor + * \p bin_growth provided during construction. Unused device allocations within + * a larger bin cache are not reused for allocation requests that categorize to + * smaller bin sizes. + * - Allocation requests below (\p bin_growth ^ \p min_bin) are rounded up to + * (\p bin_growth ^ \p min_bin). + * - Allocations above (\p bin_growth ^ \p max_bin) are not rounded up to the nearest + * bin and are simply freed when they are deallocated instead of being returned + * to a bin-cache. + * - %If the total storage of cached allocations on a given device will exceed + * \p max_cached_bytes, allocations for that device are simply freed when they are + * deallocated instead of being returned to their bin-cache. + * + * \par + * For example, the default-constructed CachingDeviceAllocator is configured with: + * - \p bin_growth = 8 + * - \p min_bin = 3 + * - \p max_bin = 7 + * - \p max_cached_bytes = 6MB - 1B + * + * \par + * which delineates five bin-sizes: 512B, 4KB, 32KB, 256KB, and 2MB + * and sets a maximum of 6,291,455 cached bytes per device + * + */ +struct CachingDeviceAllocator +{ + + //--------------------------------------------------------------------- + // Constants + //--------------------------------------------------------------------- + + /// Out-of-bounds bin + static const unsigned int INVALID_BIN = (unsigned int) -1; + + /// Invalid size + static const size_t INVALID_SIZE = (size_t) -1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS // Do not document + + /// Invalid device ordinal + static const int INVALID_DEVICE_ORDINAL = -1; + + //--------------------------------------------------------------------- + // Type definitions and helper types + //--------------------------------------------------------------------- + + /** + * Descriptor for device memory allocations + */ + struct BlockDescriptor + { + void* d_ptr; // Device pointer + size_t bytes; // Size of allocation in bytes + unsigned int bin; // Bin enumeration + int device; // device ordinal + cudaStream_t associated_stream; // Associated associated_stream + cudaEvent_t ready_event; // Signal when associated stream has run to the point at which this block was freed + + // Constructor (suitable for searching maps for a specific block, given its pointer and device) + BlockDescriptor(void *d_ptr, int device) : + d_ptr(d_ptr), + bytes(0), + bin(INVALID_BIN), + device(device), + associated_stream(nullptr), + ready_event(nullptr) + {} + + // Constructor (suitable for searching maps for a range of suitable blocks, given a device) + BlockDescriptor(int device) : + d_ptr(nullptr), + bytes(0), + bin(INVALID_BIN), + device(device), + associated_stream(nullptr), + ready_event(nullptr) + {} + + // Comparison functor for comparing device pointers + static bool PtrCompare(const BlockDescriptor &a, const BlockDescriptor &b) + { + if (a.device == b.device) + return (a.d_ptr < b.d_ptr); + else + return (a.device < b.device); + } + + // Comparison functor for comparing allocation sizes + static bool SizeCompare(const BlockDescriptor &a, const BlockDescriptor &b) + { + if (a.device == b.device) + return (a.bytes < b.bytes); + else + return (a.device < b.device); + } + }; + + /// BlockDescriptor comparator function interface + typedef bool (*Compare)(const BlockDescriptor &, const BlockDescriptor &); + + class TotalBytes { + public: + size_t free; + size_t live; + TotalBytes() { free = live = 0; } + }; + + /// Set type for cached blocks (ordered by size) + typedef std::multiset CachedBlocks; + + /// Set type for live blocks (ordered by ptr) + typedef std::multiset BusyBlocks; + + /// Map type of device ordinals to the number of cached bytes cached by each device + typedef std::map GpuCachedBytes; + + + //--------------------------------------------------------------------- + // Utility functions + //--------------------------------------------------------------------- + + /** + * Integer pow function for unsigned base and exponent + */ + static unsigned int IntPow( + unsigned int base, + unsigned int exp) + { + unsigned int retval = 1; + while (exp > 0) + { + if (exp & 1) { + retval = retval * base; // multiply the result by the current base + } + base = base * base; // square the base + exp = exp >> 1; // divide the exponent in half + } + return retval; + } + + + /** + * Round up to the nearest power-of + */ + void NearestPowerOf( + unsigned int &power, + size_t &rounded_bytes, + unsigned int base, + size_t value) + { + power = 0; + rounded_bytes = 1; + + if (value * base < value) + { + // Overflow + power = sizeof(size_t) * 8; + rounded_bytes = size_t(0) - 1; + return; + } + + while (rounded_bytes < value) + { + rounded_bytes *= base; + power++; + } + } + + + //--------------------------------------------------------------------- + // Fields + //--------------------------------------------------------------------- + + cub::Mutex mutex; /// Mutex for thread-safety + + unsigned int bin_growth; /// Geometric growth factor for bin-sizes + unsigned int min_bin; /// Minimum bin enumeration + unsigned int max_bin; /// Maximum bin enumeration + + size_t min_bin_bytes; /// Minimum bin size + size_t max_bin_bytes; /// Maximum bin size + size_t max_cached_bytes; /// Maximum aggregate cached bytes per device + + const bool skip_cleanup; /// Whether or not to skip a call to FreeAllCached() when destructor is called. (The CUDA runtime may have already shut down for statically declared allocators) + bool debug; /// Whether or not to print (de)allocation events to stdout + + GpuCachedBytes cached_bytes; /// Map of device ordinal to aggregate cached bytes on that device + CachedBlocks cached_blocks; /// Set of cached device allocations available for reuse + BusyBlocks live_blocks; /// Set of live device allocations currently in use + +#endif // DOXYGEN_SHOULD_SKIP_THIS + + //--------------------------------------------------------------------- + // Methods + //--------------------------------------------------------------------- + + /** + * \brief Constructor. + */ + CachingDeviceAllocator( + unsigned int bin_growth, ///< Geometric growth factor for bin-sizes + unsigned int min_bin = 1, ///< Minimum bin (default is bin_growth ^ 1) + unsigned int max_bin = INVALID_BIN, ///< Maximum bin (default is no max bin) + size_t max_cached_bytes = INVALID_SIZE, ///< Maximum aggregate cached bytes per device (default is no limit) + bool skip_cleanup = false, ///< Whether or not to skip a call to \p FreeAllCached() when the destructor is called (default is to deallocate) + bool debug = false) ///< Whether or not to print (de)allocation events to stdout (default is no stderr output) + : + bin_growth(bin_growth), + min_bin(min_bin), + max_bin(max_bin), + min_bin_bytes(IntPow(bin_growth, min_bin)), + max_bin_bytes(IntPow(bin_growth, max_bin)), + max_cached_bytes(max_cached_bytes), + skip_cleanup(skip_cleanup), + debug(debug), + cached_blocks(BlockDescriptor::SizeCompare), + live_blocks(BlockDescriptor::PtrCompare) + {} + + + /** + * \brief Default constructor. + * + * Configured with: + * \par + * - \p bin_growth = 8 + * - \p min_bin = 3 + * - \p max_bin = 7 + * - \p max_cached_bytes = (\p bin_growth ^ \p max_bin) * 3) - 1 = 6,291,455 bytes + * + * which delineates five bin-sizes: 512B, 4KB, 32KB, 256KB, and 2MB and + * sets a maximum of 6,291,455 cached bytes per device + */ + CachingDeviceAllocator( + bool skip_cleanup = false, + bool debug = false) + : + bin_growth(8), + min_bin(3), + max_bin(7), + min_bin_bytes(IntPow(bin_growth, min_bin)), + max_bin_bytes(IntPow(bin_growth, max_bin)), + max_cached_bytes((max_bin_bytes * 3) - 1), + skip_cleanup(skip_cleanup), + debug(debug), + cached_blocks(BlockDescriptor::SizeCompare), + live_blocks(BlockDescriptor::PtrCompare) + {} + + + /** + * \brief Sets the limit on the number bytes this allocator is allowed to cache per device. + * + * Changing the ceiling of cached bytes does not cause any allocations (in-use or + * cached-in-reserve) to be freed. See \p FreeAllCached(). + */ + cudaError_t SetMaxCachedBytes( + size_t max_cached_bytes) + { + // Lock + mutex.Lock(); + + if (debug) _CubLog("Changing max_cached_bytes (%lld -> %lld)\n", (long long) this->max_cached_bytes, (long long) max_cached_bytes); + + this->max_cached_bytes = max_cached_bytes; + + // Unlock + mutex.Unlock(); + + return cudaSuccess; + } + + + /** + * \brief Provides a suitable allocation of device memory for the given size on the specified device. + * + * Once freed, the allocation becomes available immediately for reuse within the \p active_stream + * with which it was associated with during allocation, and it becomes available for reuse within other + * streams when all prior work submitted to \p active_stream has completed. + */ + cudaError_t DeviceAllocate( + int device, ///< [in] Device on which to place the allocation + void **d_ptr, ///< [out] Reference to pointer to the allocation + size_t bytes, ///< [in] Minimum number of bytes for the allocation + cudaStream_t active_stream = nullptr) ///< [in] The stream to be associated with this allocation + { + *d_ptr = nullptr; + int entrypoint_device = INVALID_DEVICE_ORDINAL; + cudaError_t error = cudaSuccess; + + if (device == INVALID_DEVICE_ORDINAL) + { + if (CubDebug(error = cudaGetDevice(&entrypoint_device))) return error; + device = entrypoint_device; + } + + // Create a block descriptor for the requested allocation + bool found = false; + BlockDescriptor search_key(device); + search_key.associated_stream = active_stream; + NearestPowerOf(search_key.bin, search_key.bytes, bin_growth, bytes); + + if (search_key.bin > max_bin) + { + // Bin is greater than our maximum bin: allocate the request + // exactly and give out-of-bounds bin. It will not be cached + // for reuse when returned. + search_key.bin = INVALID_BIN; + search_key.bytes = bytes; + } + else + { + // Search for a suitable cached allocation: lock + mutex.Lock(); + + if (search_key.bin < min_bin) + { + // Bin is less than minimum bin: round up + search_key.bin = min_bin; + search_key.bytes = min_bin_bytes; + } + + // Iterate through the range of cached blocks on the same device in the same bin + CachedBlocks::iterator block_itr = cached_blocks.lower_bound(search_key); + while ((block_itr != cached_blocks.end()) + && (block_itr->device == device) + && (block_itr->bin == search_key.bin)) + { + // To prevent races with reusing blocks returned by the host but still + // in use by the device, only consider cached blocks that are + // either (from the active stream) or (from an idle stream) + if ((active_stream == block_itr->associated_stream) || + (cudaEventQuery(block_itr->ready_event) != cudaErrorNotReady)) + { + // Reuse existing cache block. Insert into live blocks. + found = true; + search_key = *block_itr; + search_key.associated_stream = active_stream; + live_blocks.insert(search_key); + + // Remove from free blocks + cached_bytes[device].free -= search_key.bytes; + cached_bytes[device].live += search_key.bytes; + + if (debug) _CubLog("\tDevice %d reused cached block at %p (%lld bytes) for stream %lld (previously associated with stream %lld).\n", + device, search_key.d_ptr, (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) block_itr->associated_stream); + + cached_blocks.erase(block_itr); + + break; + } + block_itr++; + } + + // Done searching: unlock + mutex.Unlock(); + } + + // Allocate the block if necessary + if (!found) + { + // Set runtime's current device to specified device (entrypoint may not be set) + if (device != entrypoint_device) + { + if (CubDebug(error = cudaGetDevice(&entrypoint_device))) return error; + if (CubDebug(error = cudaSetDevice(device))) return error; + } + + // Attempt to allocate + if (CubDebug(error = cudaMalloc(&search_key.d_ptr, search_key.bytes)) == cudaErrorMemoryAllocation) + { + // The allocation attempt failed: free all cached blocks on device and retry + if (debug) _CubLog("\tDevice %d failed to allocate %lld bytes for stream %lld, retrying after freeing cached allocations", + device, (long long) search_key.bytes, (long long) search_key.associated_stream); + + error = cudaSuccess; // Reset the error we will return + cudaGetLastError(); // Reset CUDART's error + + // Lock + mutex.Lock(); + + // Iterate the range of free blocks on the same device + BlockDescriptor free_key(device); + CachedBlocks::iterator block_itr = cached_blocks.lower_bound(free_key); + + while ((block_itr != cached_blocks.end()) && (block_itr->device == device)) + { + // No need to worry about synchronization with the device: cudaFree is + // blocking and will synchronize across all kernels executing + // on the current device + + // Free device memory and destroy stream event. + if (CubDebug(error = cudaFree(block_itr->d_ptr))) break; + if (CubDebug(error = cudaEventDestroy(block_itr->ready_event))) break; + + // Reduce balance and erase entry + cached_bytes[device].free -= block_itr->bytes; + + if (debug) _CubLog("\tDevice %d freed %lld bytes.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks (%lld bytes) outstanding.\n", + device, (long long) block_itr->bytes, (long long) cached_blocks.size(), (long long) cached_bytes[device].free, (long long) live_blocks.size(), (long long) cached_bytes[device].live); + + cached_blocks.erase(block_itr); + + block_itr++; + } + + // Unlock + mutex.Unlock(); + + // Return under error + if (error) return error; + + // Try to allocate again + if (CubDebug(error = cudaMalloc(&search_key.d_ptr, search_key.bytes))) return error; + } + + // Create ready event + if (CubDebug(error = cudaEventCreateWithFlags(&search_key.ready_event, cudaEventDisableTiming))) + return error; + + // Insert into live blocks + mutex.Lock(); + live_blocks.insert(search_key); + cached_bytes[device].live += search_key.bytes; + mutex.Unlock(); + + if (debug) _CubLog("\tDevice %d allocated new device block at %p (%lld bytes associated with stream %lld).\n", + device, search_key.d_ptr, (long long) search_key.bytes, (long long) search_key.associated_stream); + + // Attempt to revert back to previous device if necessary + if ((entrypoint_device != INVALID_DEVICE_ORDINAL) && (entrypoint_device != device)) + { + if (CubDebug(error = cudaSetDevice(entrypoint_device))) return error; + } + } + + // Copy device pointer to output parameter + *d_ptr = search_key.d_ptr; + + if (debug) _CubLog("\t\t%lld available blocks cached (%lld bytes), %lld live blocks outstanding(%lld bytes).\n", + (long long) cached_blocks.size(), (long long) cached_bytes[device].free, (long long) live_blocks.size(), (long long) cached_bytes[device].live); + + return error; + } + + + /** + * \brief Provides a suitable allocation of device memory for the given size on the current device. + * + * Once freed, the allocation becomes available immediately for reuse within the \p active_stream + * with which it was associated with during allocation, and it becomes available for reuse within other + * streams when all prior work submitted to \p active_stream has completed. + */ + cudaError_t DeviceAllocate( + void **d_ptr, ///< [out] Reference to pointer to the allocation + size_t bytes, ///< [in] Minimum number of bytes for the allocation + cudaStream_t active_stream = nullptr) ///< [in] The stream to be associated with this allocation + { + return DeviceAllocate(INVALID_DEVICE_ORDINAL, d_ptr, bytes, active_stream); + } + + + /** + * \brief Frees a live allocation of device memory on the specified device, returning it to the allocator. + * + * Once freed, the allocation becomes available immediately for reuse within the \p active_stream + * with which it was associated with during allocation, and it becomes available for reuse within other + * streams when all prior work submitted to \p active_stream has completed. + */ + cudaError_t DeviceFree( + int device, + void* d_ptr) + { + int entrypoint_device = INVALID_DEVICE_ORDINAL; + cudaError_t error = cudaSuccess; + + if (device == INVALID_DEVICE_ORDINAL) + { + if (CubDebug(error = cudaGetDevice(&entrypoint_device))) + return error; + device = entrypoint_device; + } + + // Lock + mutex.Lock(); + + // Find corresponding block descriptor + bool recached = false; + BlockDescriptor search_key(d_ptr, device); + BusyBlocks::iterator block_itr = live_blocks.find(search_key); + if (block_itr != live_blocks.end()) + { + // Remove from live blocks + search_key = *block_itr; + live_blocks.erase(block_itr); + cached_bytes[device].live -= search_key.bytes; + + // Keep the returned allocation if bin is valid and we won't exceed the max cached threshold + if ((search_key.bin != INVALID_BIN) && (cached_bytes[device].free + search_key.bytes <= max_cached_bytes)) + { + // Insert returned allocation into free blocks + recached = true; + cached_blocks.insert(search_key); + cached_bytes[device].free += search_key.bytes; + + if (debug) _CubLog("\tDevice %d returned %lld bytes from associated stream %lld.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks outstanding. (%lld bytes)\n", + device, (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) cached_blocks.size(), + (long long) cached_bytes[device].free, (long long) live_blocks.size(), (long long) cached_bytes[device].live); + } + } + + // First set to specified device (entrypoint may not be set) + if (device != entrypoint_device) + { + if (CubDebug(error = cudaGetDevice(&entrypoint_device))) return error; + if (CubDebug(error = cudaSetDevice(device))) return error; + } + + if (recached) + { + // Insert the ready event in the associated stream (must have current device set properly) + if (CubDebug(error = cudaEventRecord(search_key.ready_event, search_key.associated_stream))) return error; + } + + // Unlock + mutex.Unlock(); + + if (!recached) + { + // Free the allocation from the runtime and cleanup the event. + if (CubDebug(error = cudaFree(d_ptr))) return error; + if (CubDebug(error = cudaEventDestroy(search_key.ready_event))) return error; + + if (debug) _CubLog("\tDevice %d freed %lld bytes from associated stream %lld.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks (%lld bytes) outstanding.\n", + device, (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) cached_blocks.size(), (long long) cached_bytes[device].free, (long long) live_blocks.size(), (long long) cached_bytes[device].live); + } + + // Reset device + if ((entrypoint_device != INVALID_DEVICE_ORDINAL) && (entrypoint_device != device)) + { + if (CubDebug(error = cudaSetDevice(entrypoint_device))) return error; + } + + return error; + } + + + /** + * \brief Frees a live allocation of device memory on the current device, returning it to the allocator. + * + * Once freed, the allocation becomes available immediately for reuse within the \p active_stream + * with which it was associated with during allocation, and it becomes available for reuse within other + * streams when all prior work submitted to \p active_stream has completed. + */ + cudaError_t DeviceFree( + void* d_ptr) + { + return DeviceFree(INVALID_DEVICE_ORDINAL, d_ptr); + } + + + /** + * \brief Frees all cached device allocations on all devices + */ + cudaError_t FreeAllCached() + { + cudaError_t error = cudaSuccess; + int entrypoint_device = INVALID_DEVICE_ORDINAL; + int current_device = INVALID_DEVICE_ORDINAL; + + mutex.Lock(); + + while (!cached_blocks.empty()) + { + // Get first block + CachedBlocks::iterator begin = cached_blocks.begin(); + + // Get entry-point device ordinal if necessary + if (entrypoint_device == INVALID_DEVICE_ORDINAL) + { + if (CubDebug(error = cudaGetDevice(&entrypoint_device))) break; + } + + // Set current device ordinal if necessary + if (begin->device != current_device) + { + if (CubDebug(error = cudaSetDevice(begin->device))) break; + current_device = begin->device; + } + + // Free device memory + if (CubDebug(error = cudaFree(begin->d_ptr))) break; + if (CubDebug(error = cudaEventDestroy(begin->ready_event))) break; + + // Reduce balance and erase entry + cached_bytes[current_device].free -= begin->bytes; + + if (debug) _CubLog("\tDevice %d freed %lld bytes.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks (%lld bytes) outstanding.\n", + current_device, (long long) begin->bytes, (long long) cached_blocks.size(), (long long) cached_bytes[current_device].free, (long long) live_blocks.size(), (long long) cached_bytes[current_device].live); + + cached_blocks.erase(begin); + } + + mutex.Unlock(); + + // Attempt to revert back to entry-point device if necessary + if (entrypoint_device != INVALID_DEVICE_ORDINAL) + { + if (CubDebug(error = cudaSetDevice(entrypoint_device))) return error; + } + + return error; + } + + + /** + * \brief Destructor + */ + virtual ~CachingDeviceAllocator() + { + if (!skip_cleanup) + FreeAllCached(); + } + +}; + + + + +/** @} */ // end group UtilMgmt + +} // CUB namespace + +#endif diff --git a/HeterogeneousCore/CUDAServices/src/CachingHostAllocator.h b/HeterogeneousCore/CUDAServices/src/CachingHostAllocator.h new file mode 100644 index 0000000000000..215e7be96a4d6 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/src/CachingHostAllocator.h @@ -0,0 +1,643 @@ +#ifndef HeterogenousCore_CUDAServices_src_CachingHostAllocator_h +#define HeterogenousCore_CUDAServices_src_CachingHostAllocator_h + +/****************************************************************************** + * Copyright (c) 2011, Duane Merrill. All rights reserved. + * Copyright (c) 2011-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the NVIDIA CORPORATION nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +/** + * Modified to cache pinned host allocations by Matti Kortelainen + */ + +/****************************************************************************** + * Simple caching allocator for pinned host memory allocations. The allocator is + * thread-safe. + ******************************************************************************/ + +#include +#include +#include + +#include +#include + +/// CUB namespace +namespace notcub { + + +/** + * \addtogroup UtilMgmt + * @{ + */ + + +/****************************************************************************** + * CachingHostAllocator (host use) + ******************************************************************************/ + +/** + * \brief A simple caching allocator pinned host memory allocations. + * + * \par Overview + * The allocator is thread-safe. It behaves as follows: + * + * I presume the CUDA stream-safeness is not useful as to read/write + * from/to the pinned host memory one needs to synchronize anyway. The + * difference wrt. device memory is that in the CPU all operations to + * the device memory are scheduled via the CUDA stream, while for the + * host memory one can perform operations directly. + * + * \par + * - Allocations are categorized and cached by bin size. A new allocation request of + * a given size will only consider cached allocations within the corresponding bin. + * - Bin limits progress geometrically in accordance with the growth factor + * \p bin_growth provided during construction. Unused host allocations within + * a larger bin cache are not reused for allocation requests that categorize to + * smaller bin sizes. + * - Allocation requests below (\p bin_growth ^ \p min_bin) are rounded up to + * (\p bin_growth ^ \p min_bin). + * - Allocations above (\p bin_growth ^ \p max_bin) are not rounded up to the nearest + * bin and are simply freed when they are deallocated instead of being returned + * to a bin-cache. + * - %If the total storage of cached allocations will exceed + * \p max_cached_bytes, allocations are simply freed when they are + * deallocated instead of being returned to their bin-cache. + * + * \par + * For example, the default-constructed CachingHostAllocator is configured with: + * - \p bin_growth = 8 + * - \p min_bin = 3 + * - \p max_bin = 7 + * - \p max_cached_bytes = 6MB - 1B + * + * \par + * which delineates five bin-sizes: 512B, 4KB, 32KB, 256KB, and 2MB + * and sets a maximum of 6,291,455 cached bytes + * + */ +struct CachingHostAllocator +{ + + //--------------------------------------------------------------------- + // Constants + //--------------------------------------------------------------------- + + /// Out-of-bounds bin + static const unsigned int INVALID_BIN = (unsigned int) -1; + + /// Invalid size + static const size_t INVALID_SIZE = (size_t) -1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS // Do not document + + /// Invalid device ordinal + static const int INVALID_DEVICE_ORDINAL = -1; + + //--------------------------------------------------------------------- + // Type definitions and helper types + //--------------------------------------------------------------------- + + /** + * Descriptor for pinned host memory allocations + */ + struct BlockDescriptor + { + void* d_ptr; // Host pointer + size_t bytes; // Size of allocation in bytes + unsigned int bin; // Bin enumeration + int device; // device ordinal + cudaStream_t associated_stream; // Associated associated_stream + cudaEvent_t ready_event; // Signal when associated stream has run to the point at which this block was freed + + // Constructor (suitable for searching maps for a specific block, given its pointer) + BlockDescriptor(void *d_ptr) : + d_ptr(d_ptr), + bytes(0), + bin(INVALID_BIN), + device(INVALID_DEVICE_ORDINAL), + associated_stream(nullptr), + ready_event(nullptr) + {} + + // Constructor (suitable for searching maps for a range of suitable blocks) + BlockDescriptor() : + d_ptr(nullptr), + bytes(0), + bin(INVALID_BIN), + device(INVALID_DEVICE_ORDINAL), + associated_stream(nullptr), + ready_event(nullptr) + {} + + // Comparison functor for comparing host pointers + static bool PtrCompare(const BlockDescriptor &a, const BlockDescriptor &b) + { + return (a.d_ptr < b.d_ptr); + } + + // Comparison functor for comparing allocation sizes + static bool SizeCompare(const BlockDescriptor &a, const BlockDescriptor &b) + { + return (a.bytes < b.bytes); + } + }; + + /// BlockDescriptor comparator function interface + typedef bool (*Compare)(const BlockDescriptor &, const BlockDescriptor &); + + class TotalBytes { + public: + size_t free; + size_t live; + TotalBytes() { free = live = 0; } + }; + + /// Set type for cached blocks (ordered by size) + typedef std::multiset CachedBlocks; + + /// Set type for live blocks (ordered by ptr) + typedef std::multiset BusyBlocks; + + + //--------------------------------------------------------------------- + // Utility functions + //--------------------------------------------------------------------- + + /** + * Integer pow function for unsigned base and exponent + */ + static unsigned int IntPow( + unsigned int base, + unsigned int exp) + { + unsigned int retval = 1; + while (exp > 0) + { + if (exp & 1) { + retval = retval * base; // multiply the result by the current base + } + base = base * base; // square the base + exp = exp >> 1; // divide the exponent in half + } + return retval; + } + + + /** + * Round up to the nearest power-of + */ + void NearestPowerOf( + unsigned int &power, + size_t &rounded_bytes, + unsigned int base, + size_t value) + { + power = 0; + rounded_bytes = 1; + + if (value * base < value) + { + // Overflow + power = sizeof(size_t) * 8; + rounded_bytes = size_t(0) - 1; + return; + } + + while (rounded_bytes < value) + { + rounded_bytes *= base; + power++; + } + } + + + //--------------------------------------------------------------------- + // Fields + //--------------------------------------------------------------------- + + cub::Mutex mutex; /// Mutex for thread-safety + + unsigned int bin_growth; /// Geometric growth factor for bin-sizes + unsigned int min_bin; /// Minimum bin enumeration + unsigned int max_bin; /// Maximum bin enumeration + + size_t min_bin_bytes; /// Minimum bin size + size_t max_bin_bytes; /// Maximum bin size + size_t max_cached_bytes; /// Maximum aggregate cached bytes + + const bool skip_cleanup; /// Whether or not to skip a call to FreeAllCached() when destructor is called. (The CUDA runtime may have already shut down for statically declared allocators) + bool debug; /// Whether or not to print (de)allocation events to stdout + + TotalBytes cached_bytes; /// Aggregate cached bytes + CachedBlocks cached_blocks; /// Set of cached pinned host allocations available for reuse + BusyBlocks live_blocks; /// Set of live pinned host allocations currently in use + +#endif // DOXYGEN_SHOULD_SKIP_THIS + + //--------------------------------------------------------------------- + // Methods + //--------------------------------------------------------------------- + + /** + * \brief Constructor. + */ + CachingHostAllocator( + unsigned int bin_growth, ///< Geometric growth factor for bin-sizes + unsigned int min_bin = 1, ///< Minimum bin (default is bin_growth ^ 1) + unsigned int max_bin = INVALID_BIN, ///< Maximum bin (default is no max bin) + size_t max_cached_bytes = INVALID_SIZE, ///< Maximum aggregate cached bytes (default is no limit) + bool skip_cleanup = false, ///< Whether or not to skip a call to \p FreeAllCached() when the destructor is called (default is to deallocate) + bool debug = false) ///< Whether or not to print (de)allocation events to stdout (default is no stderr output) + : + bin_growth(bin_growth), + min_bin(min_bin), + max_bin(max_bin), + min_bin_bytes(IntPow(bin_growth, min_bin)), + max_bin_bytes(IntPow(bin_growth, max_bin)), + max_cached_bytes(max_cached_bytes), + skip_cleanup(skip_cleanup), + debug(debug), + cached_blocks(BlockDescriptor::SizeCompare), + live_blocks(BlockDescriptor::PtrCompare) + {} + + + /** + * \brief Default constructor. + * + * Configured with: + * \par + * - \p bin_growth = 8 + * - \p min_bin = 3 + * - \p max_bin = 7 + * - \p max_cached_bytes = (\p bin_growth ^ \p max_bin) * 3) - 1 = 6,291,455 bytes + * + * which delineates five bin-sizes: 512B, 4KB, 32KB, 256KB, and 2MB and + * sets a maximum of 6,291,455 cached bytes + */ + CachingHostAllocator( + bool skip_cleanup = false, + bool debug = false) + : + bin_growth(8), + min_bin(3), + max_bin(7), + min_bin_bytes(IntPow(bin_growth, min_bin)), + max_bin_bytes(IntPow(bin_growth, max_bin)), + max_cached_bytes((max_bin_bytes * 3) - 1), + skip_cleanup(skip_cleanup), + debug(debug), + cached_blocks(BlockDescriptor::SizeCompare), + live_blocks(BlockDescriptor::PtrCompare) + {} + + + /** + * \brief Sets the limit on the number bytes this allocator is allowed to cache + * + * Changing the ceiling of cached bytes does not cause any allocations (in-use or + * cached-in-reserve) to be freed. See \p FreeAllCached(). + */ + void SetMaxCachedBytes( + size_t max_cached_bytes) + { + // Lock + mutex.Lock(); + + if (debug) _CubLog("Changing max_cached_bytes (%lld -> %lld)\n", (long long) this->max_cached_bytes, (long long) max_cached_bytes); + + this->max_cached_bytes = max_cached_bytes; + + // Unlock + mutex.Unlock(); + } + + + /** + * \brief Provides a suitable allocation of pinned host memory for the given size. + * + * Once freed, the allocation becomes available immediately for reuse. + */ + cudaError_t HostAllocate( + void **d_ptr, ///< [out] Reference to pointer to the allocation + size_t bytes, ///< [in] Minimum number of bytes for the allocation + cudaStream_t active_stream = nullptr) ///< [in] The stream to be associated with this allocation + { + *d_ptr = nullptr; + int device = INVALID_DEVICE_ORDINAL; + cudaError_t error = cudaSuccess; + + if (CubDebug(error = cudaGetDevice(&device))) return error; + + // Create a block descriptor for the requested allocation + bool found = false; + BlockDescriptor search_key; + search_key.device = device; + search_key.associated_stream = active_stream; + NearestPowerOf(search_key.bin, search_key.bytes, bin_growth, bytes); + + if (search_key.bin > max_bin) + { + // Bin is greater than our maximum bin: allocate the request + // exactly and give out-of-bounds bin. It will not be cached + // for reuse when returned. + search_key.bin = INVALID_BIN; + search_key.bytes = bytes; + } + else + { + // Search for a suitable cached allocation: lock + mutex.Lock(); + + if (search_key.bin < min_bin) + { + // Bin is less than minimum bin: round up + search_key.bin = min_bin; + search_key.bytes = min_bin_bytes; + } + + // Iterate through the range of cached blocks in the same bin + CachedBlocks::iterator block_itr = cached_blocks.lower_bound(search_key); + while ((block_itr != cached_blocks.end()) + && (block_itr->bin == search_key.bin)) + { + // To prevent races with reusing blocks returned by the host but still + // in use for transfers, only consider cached blocks that are from an idle stream + if(cudaEventQuery(block_itr->ready_event) != cudaErrorNotReady) { + // Reuse existing cache block. Insert into live blocks. + found = true; + search_key = *block_itr; + search_key.associated_stream = active_stream; + if(search_key.device != device) { + // If "associated" device changes, need to re-create the event on the right device + if (CubDebug(error = cudaSetDevice(search_key.device))) return error; + if (CubDebug(error = cudaEventDestroy(search_key.ready_event))) return error; + if (CubDebug(error = cudaSetDevice(device))) return error; + if (CubDebug(error = cudaEventCreateWithFlags(&search_key.ready_event, cudaEventDisableTiming))) return error; + search_key.device = device; + } + + live_blocks.insert(search_key); + + // Remove from free blocks + cached_bytes.free -= search_key.bytes; + cached_bytes.live += search_key.bytes; + + if (debug) _CubLog("\tHost reused cached block at %p (%lld bytes) for stream %lld on device %lld (previously associated with stream %lld).\n", + search_key.d_ptr, (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) search_key.device, (long long) block_itr->associated_stream); + + cached_blocks.erase(block_itr); + + break; + } + block_itr++; + } + + // Done searching: unlock + mutex.Unlock(); + } + + // Allocate the block if necessary + if (!found) + { + // Attempt to allocate + // TODO: eventually support allocation flags + if (CubDebug(error = cudaHostAlloc(&search_key.d_ptr, search_key.bytes, cudaHostAllocDefault)) == cudaErrorMemoryAllocation) + { + // The allocation attempt failed: free all cached blocks on device and retry + if (debug) _CubLog("\tHost failed to allocate %lld bytes for stream %lld on device %lld, retrying after freeing cached allocations", + (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) search_key.device); + + error = cudaSuccess; // Reset the error we will return + cudaGetLastError(); // Reset CUDART's error + + // Lock + mutex.Lock(); + + // Iterate the range of free blocks + CachedBlocks::iterator block_itr = cached_blocks.begin(); + + while ((block_itr != cached_blocks.end())) + { + // No need to worry about synchronization with the device: cudaFree is + // blocking and will synchronize across all kernels executing + // on the current device + + // Free pinned host memory. + if (CubDebug(error = cudaFreeHost(block_itr->d_ptr))) break; + if (CubDebug(error = cudaEventDestroy(block_itr->ready_event))) break; + + // Reduce balance and erase entry + cached_bytes.free -= block_itr->bytes; + + if (debug) _CubLog("\tHost freed %lld bytes.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks (%lld bytes) outstanding.\n", + (long long) block_itr->bytes, (long long) cached_blocks.size(), (long long) cached_bytes.free, (long long) live_blocks.size(), (long long) cached_bytes.live); + + cached_blocks.erase(block_itr); + + block_itr++; + } + + // Unlock + mutex.Unlock(); + + // Return under error + if (error) return error; + + // Try to allocate again + if (CubDebug(error = cudaHostAlloc(&search_key.d_ptr, search_key.bytes, cudaHostAllocDefault))) return error; + } + + // Create ready event + if (CubDebug(error = cudaEventCreateWithFlags(&search_key.ready_event, cudaEventDisableTiming))) + return error; + + // Insert into live blocks + mutex.Lock(); + live_blocks.insert(search_key); + cached_bytes.live += search_key.bytes; + mutex.Unlock(); + + if (debug) _CubLog("\tHost allocated new host block at %p (%lld bytes associated with stream %lld on device %lld).\n", + search_key.d_ptr, (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) search_key.device); + } + + // Copy host pointer to output parameter + *d_ptr = search_key.d_ptr; + + if (debug) _CubLog("\t\t%lld available blocks cached (%lld bytes), %lld live blocks outstanding(%lld bytes).\n", + (long long) cached_blocks.size(), (long long) cached_bytes.free, (long long) live_blocks.size(), (long long) cached_bytes.live); + + return error; + } + + + /** + * \brief Frees a live allocation of pinned host memory, returning it to the allocator. + * + * Once freed, the allocation becomes available immediately for reuse. + */ + cudaError_t HostFree( + void* d_ptr) + { + int entrypoint_device = INVALID_DEVICE_ORDINAL; + cudaError_t error = cudaSuccess; + + // Lock + mutex.Lock(); + + // Find corresponding block descriptor + bool recached = false; + BlockDescriptor search_key(d_ptr); + BusyBlocks::iterator block_itr = live_blocks.find(search_key); + if (block_itr != live_blocks.end()) + { + // Remove from live blocks + search_key = *block_itr; + live_blocks.erase(block_itr); + cached_bytes.live -= search_key.bytes; + + // Keep the returned allocation if bin is valid and we won't exceed the max cached threshold + if ((search_key.bin != INVALID_BIN) && (cached_bytes.free + search_key.bytes <= max_cached_bytes)) + { + // Insert returned allocation into free blocks + recached = true; + cached_blocks.insert(search_key); + cached_bytes.free += search_key.bytes; + + if (debug) _CubLog("\tHost returned %lld bytes from associated stream %lld on device %lld.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks outstanding. (%lld bytes)\n", + (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) search_key.device, (long long) cached_blocks.size(), + (long long) cached_bytes.free, (long long) live_blocks.size(), (long long) cached_bytes.live); + } + } + + if (CubDebug(error = cudaGetDevice(&entrypoint_device))) return error; + if (entrypoint_device != search_key.device) { + if (CubDebug(error = cudaSetDevice(search_key.device))) return error; + } + + if (recached) { + // Insert the ready event in the associated stream (must have current device set properly) + if (CubDebug(error = cudaEventRecord(search_key.ready_event, search_key.associated_stream))) return error; + } + + // Unlock + mutex.Unlock(); + + if (!recached) + { + // Free the allocation from the runtime and cleanup the event. + if (CubDebug(error = cudaFreeHost(d_ptr))) return error; + if (CubDebug(error = cudaEventDestroy(search_key.ready_event))) return error; + + if (debug) _CubLog("\tHost freed %lld bytes from associated stream %lld on device %lld.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks (%lld bytes) outstanding.\n", + (long long) search_key.bytes, (long long) search_key.associated_stream, (long long) search_key.device, (long long) cached_blocks.size(), (long long) cached_bytes.free, (long long) live_blocks.size(), (long long) cached_bytes.live); + } + + // Reset device + if ((entrypoint_device != INVALID_DEVICE_ORDINAL) && (entrypoint_device != search_key.device)) + { + if (CubDebug(error = cudaSetDevice(entrypoint_device))) return error; + } + + return error; + } + + + /** + * \brief Frees all cached pinned host allocations + */ + cudaError_t FreeAllCached() + { + cudaError_t error = cudaSuccess; + int entrypoint_device = INVALID_DEVICE_ORDINAL; + int current_device = INVALID_DEVICE_ORDINAL; + + mutex.Lock(); + + while (!cached_blocks.empty()) + { + // Get first block + CachedBlocks::iterator begin = cached_blocks.begin(); + + // Get entry-point device ordinal if necessary + if (entrypoint_device == INVALID_DEVICE_ORDINAL) + { + if (CubDebug(error = cudaGetDevice(&entrypoint_device))) break; + } + + // Set current device ordinal if necessary + if (begin->device != current_device) + { + if (CubDebug(error = cudaSetDevice(begin->device))) break; + current_device = begin->device; + } + + // Free host memory + if (CubDebug(error = cudaFreeHost(begin->d_ptr))) break; + if (CubDebug(error = cudaEventDestroy(begin->ready_event))) break; + + // Reduce balance and erase entry + cached_bytes.free -= begin->bytes; + + if (debug) _CubLog("\tHost freed %lld bytes.\n\t\t %lld available blocks cached (%lld bytes), %lld live blocks (%lld bytes) outstanding.\n", + (long long) begin->bytes, (long long) cached_blocks.size(), (long long) cached_bytes.free, (long long) live_blocks.size(), (long long) cached_bytes.live); + + cached_blocks.erase(begin); + } + + mutex.Unlock(); + + // Attempt to revert back to entry-point device if necessary + if (entrypoint_device != INVALID_DEVICE_ORDINAL) + { + if (CubDebug(error = cudaSetDevice(entrypoint_device))) return error; + } + + return error; + } + + + /** + * \brief Destructor + */ + ~CachingHostAllocator() + { + if (!skip_cleanup) + FreeAllCached(); + } + +}; + + + + +/** @} */ // end group UtilMgmt + +} // CUB namespace + +#endif diff --git a/HeterogeneousCore/CUDAServices/src/numberOfCUDADevices.cc b/HeterogeneousCore/CUDAServices/src/numberOfCUDADevices.cc new file mode 100644 index 0000000000000..a0bbc503c3451 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/src/numberOfCUDADevices.cc @@ -0,0 +1,8 @@ +#include "HeterogeneousCore/CUDAServices/interface/numberOfCUDADevices.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "FWCore/ServiceRegistry/interface/Service.h" + +int numberOfCUDADevices() { + edm::Service cs; + return cs->enabled() ? cs->numberOfDevices() : 0; +} diff --git a/HeterogeneousCore/CUDAServices/test/BuildFile.xml b/HeterogeneousCore/CUDAServices/test/BuildFile.xml new file mode 100644 index 0000000000000..8697cb61fb40a --- /dev/null +++ b/HeterogeneousCore/CUDAServices/test/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/HeterogeneousCore/CUDAServices/test/testCUDAService.cpp b/HeterogeneousCore/CUDAServices/test/testCUDAService.cpp new file mode 100644 index 0000000000000..d0a1afcc8203f --- /dev/null +++ b/HeterogeneousCore/CUDAServices/test/testCUDAService.cpp @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "catch.hpp" + +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ServiceRegistry/interface/ActivityRegistry.h" +#include "FWCore/Utilities/interface/Exception.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" + +namespace { + CUDAService makeCUDAService(edm::ParameterSet ps, edm::ActivityRegistry& ar) { + auto desc = edm::ConfigurationDescriptions("Service", "CUDAService"); + CUDAService::fillDescriptions(desc); + desc.validate(ps, "CUDAService"); + return CUDAService(ps, ar); + } +} + +TEST_CASE("Tests of CUDAService", "[CUDAService]") { + edm::ActivityRegistry ar; + + // Test setup: check if a simple CUDA runtime API call fails: + // if so, skip the test with the CUDAService enabled + int deviceCount = 0; + auto ret = cudaGetDeviceCount( &deviceCount ); + + if( ret != cudaSuccess ) { + WARN("Unable to query the CUDA capable devices from the CUDA runtime API: (" + << ret << ") " << cudaGetErrorString( ret ) + << ". Running only tests not requiring devices."); + } + + SECTION("CUDAService enabled") { + edm::ParameterSet ps; + ps.addUntrackedParameter("enabled", true); + ps.addUntrackedParameter("numberOfStreamsPerDevice", 0U); + SECTION("Enabled only if there are CUDA capable GPUs") { + auto cs = makeCUDAService(ps, ar); + if(deviceCount <= 0) { + REQUIRE(cs.enabled() == false); + WARN("CUDAService is disabled as there are no CUDA GPU devices"); + } + else { + REQUIRE(cs.enabled() == true); + INFO("CUDAService is enabled"); + } + } + + if(deviceCount <= 0) { + return; + } + + auto cs = makeCUDAService(ps, ar); + + SECTION("CUDA Queries") { + int driverVersion = 0, runtimeVersion = 0; + ret = cudaDriverGetVersion( &driverVersion ); + if( ret != cudaSuccess ) { + FAIL("Unable to query the CUDA driver version from the CUDA runtime API: (" + << ret << ") " << cudaGetErrorString( ret )); + } + ret = cudaRuntimeGetVersion( &runtimeVersion ); + if( ret != cudaSuccess ) { + FAIL("Unable to query the CUDA runtime API version: (" + << ret << ") " << cudaGetErrorString( ret )); + } + + WARN("CUDA Driver Version / Runtime Version: " << driverVersion/1000 << "." << (driverVersion%100)/10 + << " / " << runtimeVersion/1000 << "." << (runtimeVersion%100)/10); + + // Test that the number of devices found by the service + // is the same as detected by the CUDA runtime API + REQUIRE( cs.numberOfDevices() == deviceCount ); + WARN("Detected " << cs.numberOfDevices() << " CUDA Capable device(s)"); + + // Test that the compute capabilities of each device + // are the same as detected by the CUDA runtime API + for( int i=0; i mem) { + mem = free; + dev = i; + } + } + WARN("Device with most free memory " << dev << "\n" + << " as given by CUDAService " << cs.deviceWithMostFreeMemory()); + } + + SECTION("CUDAService set/get the current device") { + for(int i=0; i::max()) == true); + } + + SECTION("Limit to 1") { + ps.addUntrackedParameter("numberOfStreamsPerDevice", 1U); + auto cs = makeCUDAService(ps, ar); + REQUIRE(cs.enabled() == true); + REQUIRE(cs.enabled(0) == true); + REQUIRE(cs.enabled(1*deviceCount-1) == true); + REQUIRE(cs.enabled(1*deviceCount) == false); + REQUIRE(cs.enabled(1*deviceCount+1) == false); + } + + SECTION("Limit to 2") { + ps.addUntrackedParameter("numberOfStreamsPerDevice", 2U); + auto cs = makeCUDAService(ps, ar); + REQUIRE(cs.enabled() == true); + REQUIRE(cs.enabled(0) == true); + REQUIRE(cs.enabled(1*deviceCount) == true); + REQUIRE(cs.enabled(2*deviceCount-1) == true); + REQUIRE(cs.enabled(2*deviceCount) == false); + } + + } + + SECTION("Device allocator") { + edm::ParameterSet ps; + ps.addUntrackedParameter("enabled", true); + edm::ParameterSet alloc; + alloc.addUntrackedParameter("minBin", 1U); + alloc.addUntrackedParameter("maxBin", 3U); + ps.addUntrackedParameter("allocator", alloc); + auto cs = makeCUDAService(ps, ar); + cs.setCurrentDevice(0); + auto current_device = cuda::device::current::get(); + auto cudaStream = current_device.create_stream(cuda::stream::implicitly_synchronizes_with_default_stream); + + SECTION("Destructor") { + auto ptr = cs.make_device_unique(cudaStream); + REQUIRE(ptr.get() != nullptr); + cudaStream.synchronize(); + } + + SECTION("Reset") { + auto ptr = cs.make_device_unique(5, cudaStream); + REQUIRE(ptr.get() != nullptr); + cudaStream.synchronize(); + + ptr.reset(); + REQUIRE(ptr.get() == nullptr); + } + + SECTION("Allocating too much") { + auto ptr = cs.make_device_unique(512, cudaStream); + ptr.reset(); + REQUIRE_THROWS(ptr = cs.make_device_unique(513, cudaStream)); + } + } + + + SECTION("Host allocator") { + edm::ParameterSet ps; + ps.addUntrackedParameter("enabled", true); + edm::ParameterSet alloc; + alloc.addUntrackedParameter("minBin", 1U); + alloc.addUntrackedParameter("maxBin", 3U); + ps.addUntrackedParameter("allocator", alloc); + auto cs = makeCUDAService(ps, ar); + cs.setCurrentDevice(0); + auto current_device = cuda::device::current::get(); + auto cudaStream = current_device.create_stream(cuda::stream::implicitly_synchronizes_with_default_stream); + + SECTION("Destructor") { + auto ptr = cs.make_host_unique(cudaStream); + REQUIRE(ptr.get() != nullptr); + } + + SECTION("Reset") { + auto ptr = cs.make_host_unique(5, cudaStream); + REQUIRE(ptr.get() != nullptr); + + ptr.reset(); + REQUIRE(ptr.get() == nullptr); + } + + SECTION("Allocating too much") { + auto ptr = cs.make_host_unique(512, cudaStream); + ptr.reset(); + REQUIRE_THROWS(ptr = cs.make_host_unique(513, cudaStream)); + } + } + + //Fake the end-of-job signal. + ar.postEndJobSignal_(); +} diff --git a/HeterogeneousCore/CUDAServices/test/testCUDAService.py b/HeterogeneousCore/CUDAServices/test/testCUDAService.py new file mode 100644 index 0000000000000..06edefcac2b66 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/test/testCUDAService.py @@ -0,0 +1,13 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process( "TEST" ) + +process.load('FWCore.MessageService.MessageLogger_cfi') +process.load('HeterogeneousCore.CUDAServices.CUDAService_cfi') +process.MessageLogger.categories.append("CUDAService") + +process.source = cms.Source("EmptySource") + +process.maxEvents = cms.untracked.PSet( + input = cms.untracked.int32( 0 ) +) diff --git a/HeterogeneousCore/CUDAServices/test/test_main.cc b/HeterogeneousCore/CUDAServices/test/test_main.cc new file mode 100644 index 0000000000000..0c7c351f437f5 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/test/test_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/HeterogeneousCore/CUDAServices/test/test_main.cpp b/HeterogeneousCore/CUDAServices/test/test_main.cpp new file mode 100644 index 0000000000000..0c7c351f437f5 --- /dev/null +++ b/HeterogeneousCore/CUDAServices/test/test_main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/HeterogeneousCore/CUDAUtilities/BuildFile.xml b/HeterogeneousCore/CUDAUtilities/BuildFile.xml new file mode 100644 index 0000000000000..fc6a61ffd433e --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/BuildFile.xml @@ -0,0 +1,2 @@ + + diff --git a/HeterogeneousCore/CUDAUtilities/interface/AtomicPairCounter.h b/HeterogeneousCore/CUDAUtilities/interface/AtomicPairCounter.h new file mode 100644 index 0000000000000..425bc1ef8a701 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/AtomicPairCounter.h @@ -0,0 +1,54 @@ +#ifndef HeterogeneousCoreCUDAUtilitiesAtomicPairCounter_H +#define HeterogeneousCoreCUDAUtilitiesAtomicPairCounter_H + +#include +#include + +class AtomicPairCounter { +public: + + using c_type = unsigned long long int; + + AtomicPairCounter(){} + AtomicPairCounter(c_type i) { counter.ac=i;} + + __device__ __host__ + AtomicPairCounter & operator=(c_type i) { counter.ac=i; return *this;} + + struct Counters { + uint32_t n; // in a "One to Many" association is the number of "One" + uint32_t m; // in a "One to Many" association is the total number of associations + }; + + union Atomic2 { + Counters counters; + c_type ac; + }; + +#ifdef __CUDACC__ + + static constexpr c_type incr = 1UL<<32; + + __device__ __host__ + Counters get() const { return counter.counters;} + + // increment n by 1 and m by i. return previous value + __device__ + Counters add(uint32_t i) { + c_type c = i; + c+=incr; + Atomic2 ret; + ret.ac = atomicAdd(&counter.ac,c); + return ret.counters; + } + +#endif + +private: + + Atomic2 counter; + +}; + + +#endif diff --git a/HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h b/HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h new file mode 100644 index 0000000000000..68cf807499143 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h @@ -0,0 +1,57 @@ +#ifndef HeterogeneousCore_CUDAUtilities_CUDAHostAllocator_h +#define HeterogeneousCore_CUDAUtilities_CUDAHostAllocator_h + +#include +#include +#include + + +class cuda_bad_alloc : public std::bad_alloc { +public: + cuda_bad_alloc(cudaError_t error) noexcept : + error_(error) + { } + + const char* what() const noexcept override + { + return cudaGetErrorString(error_); + } + +private: + cudaError_t error_; +}; + + +template class CUDAHostAllocator { +public: + using value_type = T; + + template + struct rebind { + using other = CUDAHostAllocator; + }; + + T* allocate(std::size_t n) const __attribute__((warn_unused_result)) __attribute__((malloc)) __attribute__((returns_nonnull)) + { + void* ptr = nullptr; + cudaError_t status = cudaMallocHost(&ptr, n * sizeof(T), FLAGS); + if (status != cudaSuccess) { + throw cuda_bad_alloc(status); + } + if (ptr == nullptr) { + throw std::bad_alloc(); + } + return static_cast(ptr); + } + + void deallocate(T* p, std::size_t n) const + { + cudaError_t status = cudaFreeHost(p); + if (status != cudaSuccess) { + throw cuda_bad_alloc(status); + } + } + +}; + +#endif // HeterogeneousCore_CUDAUtilities_CUDAHostAllocator_h diff --git a/HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h b/HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h new file mode 100644 index 0000000000000..66525d13db8dc --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h @@ -0,0 +1,119 @@ +#ifndef HeterogeneousCore_CUDAUtilities_interface_GPUSimpleVector_h +#define HeterogeneousCore_CUDAUtilities_interface_GPUSimpleVector_h + +// author: Felice Pantaleo, CERN, 2018 + +#include +#include + +#include + +namespace GPU { +template struct SimpleVector { + constexpr SimpleVector() = default; + + // ownership of m_data stays within the caller + constexpr void construct(int capacity, T *data) { + m_size = 0; + m_capacity = capacity; + m_data = data; + } + + inline constexpr int push_back_unsafe(const T &element) { + auto previousSize = m_size; + m_size++; + if (previousSize < m_capacity) { + m_data[previousSize] = element; + return previousSize; + } else { + --m_size; + return -1; + } + } + + template constexpr int emplace_back_unsafe(Ts &&... args) { + auto previousSize = m_size; + m_size++; + if (previousSize < m_capacity) { + (new (&m_data[previousSize]) T(std::forward(args)...)); + return previousSize; + } else { + --m_size; + return -1; + } + } + + inline constexpr T & back() const { + + if (m_size > 0) { + return m_data[m_size - 1]; + } else + return T(); //undefined behaviour + } + +#ifdef __CUDACC__ + + // thread-safe version of the vector, when used in a CUDA kernel + __device__ + int push_back(const T &element) { + auto previousSize = atomicAdd(&m_size, 1); + if (previousSize < m_capacity) { + m_data[previousSize] = element; + return previousSize; + } else { + atomicSub(&m_size, 1); + return -1; + } + } + + template + __device__ + int emplace_back(Ts &&... args) { + auto previousSize = atomicAdd(&m_size, 1); + if (previousSize < m_capacity) { + (new (&m_data[previousSize]) T(std::forward(args)...)); + return previousSize; + } else { + atomicSub(&m_size, 1); + return -1; + } + } + +#endif // __CUDACC__ + inline constexpr bool empty() const { return m_size==0;} + inline constexpr bool full() const { return m_size==m_capacity;} + inline constexpr T& operator[](int i) { return m_data[i]; } + inline constexpr const T& operator[](int i) const { return m_data[i]; } + inline constexpr void reset() { m_size = 0; } + inline constexpr int size() const { return m_size; } + inline constexpr int capacity() const { return m_capacity; } + inline constexpr T const * data() const { return m_data; } + inline constexpr void resize(int size) { m_size = size; } + inline constexpr void set_data(T * data) { m_data = data; } + +private: + int m_size; + int m_capacity; + + T *m_data; +}; + + // ownership of m_data stays within the caller + template + SimpleVector make_SimpleVector(int capacity, T *data) { + SimpleVector ret; + ret.construct(capacity, data); + return ret; + } + + // ownership of m_data stays within the caller + template + SimpleVector *make_SimpleVector(SimpleVector *mem, int capacity, T *data) { + auto ret = new(mem) SimpleVector(); + ret->construct(capacity, data); + return ret; + } + +} // namespace GPU + +#endif // HeterogeneousCore_CUDAUtilities_interface_GPUSimpleVector_h diff --git a/HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h b/HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h new file mode 100644 index 0000000000000..a060fe6799b6c --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h @@ -0,0 +1,104 @@ +#ifndef HeterogeneousCore_CUDAUtilities_interface_GPUVecArray_h +#define HeterogeneousCore_CUDAUtilities_interface_GPUVecArray_h + +// +// Author: Felice Pantaleo, CERN +// + +#include + +namespace GPU { + +template struct VecArray { + inline constexpr int push_back_unsafe(const T &element) { + auto previousSize = m_size; + m_size++; + if (previousSize < maxSize) { + m_data[previousSize] = element; + return previousSize; + } else { + --m_size; + return -1; + } + } + + template constexpr int emplace_back_unsafe(Ts &&... args) { + auto previousSize = m_size; + m_size++; + if (previousSize < maxSize) { + (new (&m_data[previousSize]) T(std::forward(args)...)); + return previousSize; + } else { + --m_size; + return -1; + } + } + + inline constexpr T & back() const { + if (m_size > 0) { + return m_data[m_size - 1]; + } else + return T(); //undefined behaviour + } + +#ifdef __CUDACC__ + + // thread-safe version of the vector, when used in a CUDA kernel + __device__ + int push_back(const T &element) { + auto previousSize = atomicAdd(&m_size, 1); + if (previousSize < maxSize) { + m_data[previousSize] = element; + return previousSize; + } else { + atomicSub(&m_size, 1); + return -1; + } + } + + template + __device__ + int emplace_back(Ts &&... args) { + auto previousSize = atomicAdd(&m_size, 1); + if (previousSize < maxSize) { + (new (&m_data[previousSize]) T(std::forward(args)...)); + return previousSize; + } else { + atomicSub(&m_size, 1); + return -1; + } + } + +#endif // __CUDACC__ + + __host__ __device__ + inline T pop_back() { + if (m_size > 0) { + auto previousSize = m_size--; + return m_data[previousSize - 1]; + } else + return T(); + } + + inline constexpr T const * begin() const { return m_data;} + inline constexpr T const * end() const { return m_data+m_size;} + inline constexpr T * begin() { return m_data;} + inline constexpr T * end() { return m_data+m_size;} + inline constexpr int size() const { return m_size; } + inline constexpr T& operator[](int i) { return m_data[i]; } + inline constexpr const T& operator[](int i) const { return m_data[i]; } + inline constexpr void reset() { m_size = 0; } + inline constexpr int capacity() const { return maxSize; } + inline constexpr T const * data() const { return m_data; } + inline constexpr void resize(int size) { m_size = size; } + inline constexpr bool empty() const { return 0 == m_size; } + inline constexpr bool full() const { return maxSize == m_size; } + + int m_size = 0; + + T m_data[maxSize]; +}; + +} + +#endif // HeterogeneousCore_CUDAUtilities_interface_GPUVecArray_h diff --git a/HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h b/HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h new file mode 100644 index 0000000000000..61ccc7484d68b --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h @@ -0,0 +1,335 @@ +#ifndef HeterogeneousCore_CUDAUtilities_HistoContainer_h +#define HeterogeneousCore_CUDAUtilities_HistoContainer_h + +#include +#ifndef __CUDA_ARCH__ +#include +#endif // __CUDA_ARCH__ +#include +#include +#include + +#ifdef __CUDACC__ +#include +#endif + +#include "HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#ifdef __CUDACC__ +#include "HeterogeneousCore/CUDAUtilities/interface/prefixScan.h" +#endif +#include "HeterogeneousCore/CUDAUtilities/interface/AtomicPairCounter.h" + + +#ifdef __CUDACC__ +namespace cudautils { + + template + __global__ + void countFromVector(Histo * __restrict__ h, uint32_t nh, T const * __restrict__ v, uint32_t const * __restrict__ offsets) { + auto i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= offsets[nh]) return; + auto off = cuda_std::upper_bound(offsets, offsets + nh + 1, i); + assert((*off) > 0); + int32_t ih = off - offsets - 1; + assert(ih >= 0); + assert(ih < nh); + (*h).count(v[i], ih); + } + + template + __global__ + void fillFromVector(Histo * __restrict__ h, uint32_t nh, T const * __restrict__ v, uint32_t const * __restrict__ offsets) { + auto i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= offsets[nh]) return; + auto off = cuda_std::upper_bound(offsets, offsets + nh + 1, i); + assert((*off) > 0); + int32_t ih = off - offsets - 1; + assert(ih >= 0); + assert(ih < nh); + (*h).fill(v[i], i, ih); + } + + template + void launchZero(Histo * __restrict__ h, cudaStream_t stream) { + uint32_t * off = (uint32_t *)( (char*)(h) +offsetof(Histo,off)); + cudaMemsetAsync(off,0, 4*Histo::totbins(),stream); + } + + template + void launchFinalize(Histo * __restrict__ h, uint8_t * __restrict__ ws, cudaStream_t stream) { + uint32_t * off = (uint32_t *)( (char*)(h) +offsetof(Histo,off)); + size_t wss = Histo::wsSize(); + CubDebugExit(cub::DeviceScan::InclusiveSum(ws, wss, off, off, Histo::totbins(), stream)); + } + + + template + void fillManyFromVector(Histo * __restrict__ h, uint8_t * __restrict__ ws, + uint32_t nh, T const * __restrict__ v, uint32_t const * __restrict__ offsets, uint32_t totSize, + int nthreads, cudaStream_t stream) { + launchZero(h,stream); + auto nblocks = (totSize + nthreads - 1) / nthreads; + countFromVector<<>>(h, nh, v, offsets); + cudaCheck(cudaGetLastError()); + launchFinalize(h,ws,stream); + fillFromVector<<>>(h, nh, v, offsets); + cudaCheck(cudaGetLastError()); + } + + + template + __global__ + void finalizeBulk(AtomicPairCounter const * apc, Assoc * __restrict__ assoc) { + assoc->bulkFinalizeFill(*apc); + } + +} // namespace cudautils +#endif + + +// iteratate over N bins left and right of the one containing "v" +template +__host__ __device__ +__forceinline__ +void forEachInBins(Hist const & hist, V value, int n, Func func) { + int bs = Hist::bin(value); + int be = std::min(int(Hist::nbins()-1),bs+n); + bs = std::max(0,bs-n); + assert(be>=bs); + for (auto pj=hist.begin(bs);pj +__host__ __device__ +__forceinline__ +void forEachInWindow(Hist const & hist, V wmin, V wmax, Func const & func) { + auto bs = Hist::bin(wmin); + auto be = Hist::bin(wmax); + assert(be>=bs); + for (auto pj=hist.begin(bs);pj +class HistoContainer { +public: +#ifdef __CUDACC__ + using Counter = uint32_t; +#else + using Counter = std::atomic; +#endif + + using index_type = I; + using UT = typename std::make_unsigned::type; + + static constexpr uint32_t ilog2(uint32_t v) { + + constexpr uint32_t b[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000}; + constexpr uint32_t s[] = {1, 2, 4, 8, 16}; + + uint32_t r = 0; // result of log2(v) will go here + for (auto i = 4; i >= 0; i--) if (v & b[i]) { + v >>= s[i]; + r |= s[i]; + } + return r; + } + + + static constexpr uint32_t sizeT() { return S; } + static constexpr uint32_t nbins() { return NBINS;} + static constexpr uint32_t nhists() { return NHISTS;} + static constexpr uint32_t totbins() { return NHISTS*NBINS+1;} + static constexpr uint32_t nbits() { return ilog2(NBINS-1)+1;} + static constexpr uint32_t capacity() { return SIZE; } + + static constexpr auto histOff(uint32_t nh) { return NBINS*nh; } + +#ifdef __CUDACC__ + __host__ + static size_t wsSize() { + uint32_t * v =nullptr; + void * d_temp_storage = nullptr; + size_t temp_storage_bytes = 0; + cub::DeviceScan::InclusiveSum(d_temp_storage, temp_storage_bytes, v, v, totbins()); + return temp_storage_bytes; + } +#endif + + + static constexpr UT bin(T t) { + constexpr uint32_t shift = sizeT() - nbits(); + constexpr uint32_t mask = (1<> shift) & mask; + } + + void zero() { + for (auto & i : off) + i = 0; + } + + static __host__ __device__ + __forceinline__ + uint32_t atomicIncrement(Counter & x) { + #ifdef __CUDA_ARCH__ + return atomicAdd(&x, 1); + #else + return x++; + #endif + } + + static __host__ __device__ + __forceinline__ + uint32_t atomicDecrement(Counter & x) { + #ifdef __CUDA_ARCH__ + return atomicSub(&x, 1); + #else + return x--; + #endif + } + + __host__ __device__ + __forceinline__ + void countDirect(T b) { + assert(b0); + bins[w-1] = j; + } + + +#ifdef __CUDACC__ + __device__ + __forceinline__ + uint32_t bulkFill(AtomicPairCounter & apc, index_type const * v, uint32_t n) { + auto c = apc.add(n); + off[c.m] = c.n; + for(int j=0; j=totbins()) return; + off[i]=n; + } + + +#endif + + + __host__ __device__ + __forceinline__ + void count(T t) { + uint32_t b = bin(t); + assert(b0); + bins[w-1] = j; + } + + + __host__ __device__ + __forceinline__ + void count(T t, uint32_t nh) { + uint32_t b = bin(t); + assert(b0); + bins[w-1] = j; + } + +#ifdef __CUDACC__ + __device__ + __forceinline__ + void finalize(Counter * ws) { + assert(off[totbins()-1]==0); + blockPrefixScan(off,totbins(),ws); + assert(off[totbins()-1]==off[totbins()-2]); + } + __host__ +#endif + void finalize() { + assert(off[totbins()-1]==0); + for(uint32_t i=1; i +using OneToManyAssoc = HistoContainer; + +#endif // HeterogeneousCore_CUDAUtilities_HistoContainer_h diff --git a/HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h b/HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h new file mode 100644 index 0000000000000..4930307a89567 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h @@ -0,0 +1,40 @@ +#ifndef HeterogeneousCore_CUDAUtilities_cudaCheck_h +#define HeterogeneousCore_CUDAUtilities_cudaCheck_h + +#include +#include +#include + +inline +bool cudaCheck_(const char* file, int line, const char* cmd, CUresult result) +{ + //std::cerr << file << ", line " << line << ": " << cmd << std::endl; + if (result == CUDA_SUCCESS) + return true; + + const char* error; + const char* message; + cuGetErrorName(result, &error); + cuGetErrorString(result, &message); + std::cerr << file << ", line " << line << ": " << error << ": " << message << std::endl; + abort(); + return false; +} + +inline +bool cudaCheck_(const char* file, int line, const char* cmd, cudaError_t result) +{ + //std::cerr << file << ", line " << line << ": " << cmd << std::endl; + if (result == cudaSuccess) + return true; + + const char* error = cudaGetErrorName(result); + const char* message = cudaGetErrorString(result); + std::cerr << file << ", line " << line << ": " << error << ": " << message << std::endl; + abort(); + return false; +} + +#define cudaCheck(ARG) (cudaCheck_(__FILE__, __LINE__, #ARG, (ARG))) + +#endif // HeterogeneousCore_CUDAUtilities_cudaCheck_h diff --git a/HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h b/HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h new file mode 100644 index 0000000000000..37dcacec3cf6a --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h @@ -0,0 +1,18 @@ +// The omission of #include guards is on purpose: it does make sense to #include +// this file multiple times, setting a different value of GPU_DEBUG beforehand. + +#ifdef __CUDA_ARCH__ +# ifndef GPU_DEBUG +// disable asserts +# ifndef NDEBUG +# define NDEBUG +# endif +# else +// enable asserts +# ifdef NDEBUG +# undef NDEBUG +# endif +# endif +#endif // __CUDA_ARCH__ + +#include diff --git a/HeterogeneousCore/CUDAUtilities/interface/cuda_cxx17.h b/HeterogeneousCore/CUDAUtilities/interface/cuda_cxx17.h new file mode 100644 index 0000000000000..172bb31806dbb --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/cuda_cxx17.h @@ -0,0 +1,72 @@ +#ifndef HeterogeneousCore_CUDAUtilities_cuda_cxx17_h +#define HeterogeneousCore_CUDAUtilities_cuda_cxx17_h + +#include + +// CUDA does not support C++17 yet, so we define here some of the missing library functions +#if __cplusplus <= 201402L + +namespace std { + + // from https://en.cppreference.com/w/cpp/iterator/size + template + constexpr auto size(const C& c) -> decltype(c.size()) + { + return c.size(); + } + + template + constexpr std::size_t size(const T (&array)[N]) noexcept + { + return N; + } + + // from https://en.cppreference.com/w/cpp/iterator/empty + template + constexpr auto empty(const C& c) -> decltype(c.empty()) + { + return c.empty(); + } + + template + constexpr bool empty(const T (&array)[N]) noexcept + { + return false; + } + + template + constexpr bool empty(std::initializer_list il) noexcept + { + return il.size() == 0; + } + + // from https://en.cppreference.com/w/cpp/iterator/data + template + constexpr auto data(C& c) -> decltype(c.data()) + { + return c.data(); + } + + template + constexpr auto data(const C& c) -> decltype(c.data()) + { + return c.data(); + } + + template + constexpr T* data(T (&array)[N]) noexcept + { + return array; + } + + template + constexpr const E* data(std::initializer_list il) noexcept + { + return il.begin(); + } + +} + +#endif + +#endif // HeterogeneousCore_CUDAUtilities_cuda_cxx17_h diff --git a/HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h b/HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h new file mode 100644 index 0000000000000..94067095e51d7 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h @@ -0,0 +1,83 @@ +#ifndef HeterogeneousCore_CUDAUtilities_cudastdAlgorithm_h +#define HeterogeneousCore_CUDAUtilities_cudastdAlgorithm_h + +#include + +#include + +// reimplementation of std algorithms able to compile with CUDA and run on GPUs, +// mostly by declaringthem constexpr + +namespace cuda_std { + + template + struct less { + __host__ __device__ + constexpr bool operator()(const T &lhs, const T &rhs) const { + return lhs < rhs; + } + }; + + template<> + struct less { + template + __host__ __device__ + constexpr bool operator()(const T &lhs, const U &rhs ) const { return lhs < rhs;} + }; + + template> + __host__ __device__ + constexpr + RandomIt lower_bound(RandomIt first, RandomIt last, const T& value, Compare comp={}) + { + auto count = last - first; + + while (count > 0) { + auto it = first; + auto step = count / 2; + it += step; + if (comp(*it, value)) { + first = ++it; + count -= step + 1; + } + else { + count = step; + } + } + return first; + } + + template> + __host__ __device__ + constexpr + RandomIt upper_bound(RandomIt first, RandomIt last, const T& value, Compare comp={}) + { + auto count = last - first; + + while (count > 0) { + auto it = first; + auto step = count / 2; + it+=step; + if (!comp(value,*it)) { + first = ++it; + count -= step + 1; + } + else { + count = step; + } + } + return first; + } + + template> + __host__ __device__ + constexpr + RandomIt binary_find(RandomIt first, RandomIt last, const T& value, Compare comp={}) + { + first = cuda_std::lower_bound(first, last, value, comp); + return first != last && !comp(value, *first) ? first : last; + } + +} + +#endif // HeterogeneousCore_CUDAUtilities_cudastdAlgorithm_h diff --git a/HeterogeneousCore/CUDAUtilities/interface/getCudaDrvErrorString.h b/HeterogeneousCore/CUDAUtilities/interface/getCudaDrvErrorString.h new file mode 100644 index 0000000000000..dea870db50878 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/getCudaDrvErrorString.h @@ -0,0 +1,17 @@ +#ifndef HeterogeneousCore_CUDAUtilities_getCudaDrvErrorString_h +#define HeterogeneousCore_CUDAUtilities_getCudaDrvErrorString_h + +#include + +inline const char *getCudaDrvErrorString(CUresult error_id) { + const char *message; + auto ret = cuGetErrorName(error_id, &message); + if(ret == CUDA_ERROR_INVALID_VALUE) { + return static_cast("CUDA_ERROR not found!"); + } + else { + return message; + } +} + +#endif diff --git a/HeterogeneousCore/CUDAUtilities/interface/prefixScan.h b/HeterogeneousCore/CUDAUtilities/interface/prefixScan.h new file mode 100644 index 0000000000000..acad1e2cabdc1 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/interface/prefixScan.h @@ -0,0 +1,55 @@ +#ifndef HeterogeneousCore_CUDAUtilities_prefixScan_h +#define HeterogeneousCore_CUDAUtilities_prefixScan_h + +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +template +__device__ +void +__forceinline__ +warpPrefixScan(T * c, uint32_t i, uint32_t mask) { + auto x = c[i]; + auto laneId = threadIdx.x & 0x1f; + #pragma unroll + for( int offset = 1 ; offset < 32 ; offset <<= 1 ) { + auto y = __shfl_up_sync(mask,x, offset); + if(laneId >= offset) x += y; + } + c[i] = x; +} + +// limited to 32*32 elements.... +template +__device__ +void +__forceinline__ +blockPrefixScan(T * c, uint32_t size, T* ws) { + assert(size<=1024); + assert(0==blockDim.x%32); + + auto first = threadIdx.x; + auto mask = __ballot_sync(0xffffffff,first + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +template +__device__ +inline void dummyReorder(T const * a, uint16_t * ind, uint16_t * ind2, uint32_t size) { +} + +template +__device__ +inline void reorderSigned(T const * a, uint16_t * ind, uint16_t * ind2, uint32_t size) { + + //move negative first... + + int32_t first = threadIdx.x; + __shared__ uint32_t firstNeg; + firstNeg = a[ind[0]]<0 ? 0 : size; + __syncthreads(); + + // find first negative + for (auto i=first; i=0); + for (auto i=first;i +__device__ +inline void reorderFloat(T const * a, uint16_t * ind, uint16_t * ind2, uint32_t size) { + + //move negative first... + + int32_t first = threadIdx.x; + __shared__ uint32_t firstNeg; + firstNeg = a[ind[0]]<0 ? 0 : size; + __syncthreads(); + + // find first negative + for (auto i=first; i=0); + for (auto i=first;i +__device__ +void +__forceinline__ +radixSortImpl(T const * __restrict__ a, uint16_t * ind, uint16_t * ind2, uint32_t size, RF reorder) { + + constexpr int d = 8, w = 8*sizeof(T); + constexpr int sb = 1<0); + assert(blockDim.x>=sb); + + // bool debug = false; // threadIdx.x==0 && blockIdx.x==5; + + p = ps; + + auto j = ind; + auto k = ind2; + + int32_t first = threadIdx.x; + for (auto i=first; i> d*p)&(sb-1); + atomicAdd(&c[bin],1); + } + __syncthreads(); + + // prefix scan "optimized"???... + if (threadIdx.x= offset) x += y; + } + ct[threadIdx.x] = x; + } + __syncthreads(); + if (threadIdx.x0; i-=32) c[threadIdx.x] +=ct[i]; + } + /* + //prefix scan for the nulls (for documentation) + if (threadIdx.x==0) + for (int i = 1; i < sb; ++i) c[i] += c[i-1]; + */ + + + // broadcast + ibs =size-1; + __syncthreads(); + while (__syncthreads_and(ibs>0)) { + int i = ibs - threadIdx.x; + if (threadIdx.x=0) { + bin = (a[j[i]] >> d*p)&(sb-1); + ct[threadIdx.x]=bin; + atomicMax(&cu[bin],int(i)); + } + } + __syncthreads(); + if (threadIdx.x=0 && i==cu[bin]) // ensure to keep them in order + for (int ii=threadIdx.x; ii=oi);if(i>=oi) + k[--c[bin]] = j[i-oi]; + } + } + __syncthreads(); + if (bin>=0) assert(c[bin]>=0); + if (threadIdx.x==0) ibs-=sb; + __syncthreads(); + } + + /* + // broadcast for the nulls (for documentation) + if (threadIdx.x==0) + for (int i=size-first-1; i>=0; i--) { // =blockDim.x) { + auto bin = (a[j[i]] >> d*p)&(sb-1); + auto ik = atomicSub(&c[bin],1); + k[ik-1] = j[i]; + } + */ + + __syncthreads(); + assert(c[0]==0); + + + // swap (local, ok) + auto t=j;j=k;k=t; + + if (threadIdx.x==0) ++p; + __syncthreads(); + + } + + if( (w!=8) && (0==(NS&1)) ) assert(j==ind); // w/d is even so ind is correct + + if (j!=ind) // odd... + for (auto i=first; i::value,T>::type* = nullptr +> +__device__ +void +__forceinline__ +radixSort(T const * a, uint16_t * ind, uint16_t * ind2, uint32_t size) { + radixSortImpl(a,ind,ind2,size,dummyReorder); +} + +template< + typename T, + int NS=sizeof(T), // number of significant bytes to use in sorting + typename std::enable_if::value&&std::is_signed::value,T>::type* = nullptr +> +__device__ +void +__forceinline__ +radixSort(T const * a, uint16_t * ind, uint16_t * ind2, uint32_t size) { + radixSortImpl(a,ind,ind2,size,reorderSigned); +} + +template< + typename T, + int NS=sizeof(T), // number of significant bytes to use in sorting + typename std::enable_if::value,T>::type* = nullptr +> +__device__ +void +__forceinline__ + radixSort(T const * a, uint16_t * ind, uint16_t * ind2, uint32_t size) { + using I = int; + radixSortImpl((I const *)(a),ind,ind2, size,reorderFloat); +} + + + +template +__device__ +void +__forceinline__ +radixSortMulti(T const * v, uint16_t * index, uint32_t const * offsets, uint16_t * workspace) { + + extern __shared__ uint16_t ws[]; + + auto a = v+offsets[blockIdx.x]; + auto ind = index+offsets[blockIdx.x]; + auto ind2 = nullptr==workspace ? ws : workspace+offsets[blockIdx.x]; + auto size = offsets[blockIdx.x+1]-offsets[blockIdx.x]; + assert(offsets[blockIdx.x+1]>=offsets[blockIdx.x]); + if (size>0) radixSort(a,ind,ind2,size); + +} + +template +__global__ +void +__launch_bounds__(256, 4) +radixSortMultiWrapper(T const * v, uint16_t * index, uint32_t const * offsets, uint16_t * workspace) { + radixSortMulti(v,index,offsets, workspace); +} + +template +__global__ +void +// __launch_bounds__(256, 4) +radixSortMultiWrapper2(T const * v, uint16_t * index, uint32_t const * offsets, uint16_t * workspace) { + radixSortMulti(v,index,offsets, workspace); +} + + +#endif // HeterogeneousCoreCUDAUtilities_radixSort_H diff --git a/HeterogeneousCore/CUDAUtilities/test/AtomicPairCounter_t.cu b/HeterogeneousCore/CUDAUtilities/test/AtomicPairCounter_t.cu new file mode 100644 index 0000000000000..c52988b2dd0d9 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/AtomicPairCounter_t.cu @@ -0,0 +1,66 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/AtomicPairCounter.h" + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +__global__ +void update(AtomicPairCounter * dc, uint32_t * ind, uint32_t * cont, uint32_t n) { + auto i = blockIdx.x*blockDim.x + threadIdx.x; + if (i>=n) return; + + auto m = i%11; + m = m%6 +1; // max 6, no 0 + auto c = dc->add(m); + assert(c.mget().m==n); + ind[n]= dc->get().n; +} + +__global__ +void verify(AtomicPairCounter const * dc, uint32_t const * ind, uint32_t const * cont, uint32_t n) { + auto i = blockIdx.x*blockDim.x + threadIdx.x; + if (i>=n) return; + assert(0==ind[0]); + assert(dc->get().m==n); + assert(ind[n] == dc->get().n); + auto ib = ind[i]; + auto ie = ind[i+1]; + auto k = cont[ib++]; + assert(k +int main() { + + AtomicPairCounter * dc_d; + cudaMalloc(&dc_d, sizeof(AtomicPairCounter)); + cudaMemset(dc_d, 0, sizeof(AtomicPairCounter)); + + std::cout << "size " << sizeof(AtomicPairCounter) << std::endl; + + constexpr uint32_t N=20000; + constexpr uint32_t M=N*6; + uint32_t *n_d, *m_d; + cudaMalloc(&n_d, N*sizeof(int)); + // cudaMemset(n_d, 0, N*sizeof(int)); + cudaMalloc(&m_d, M*sizeof(int)); + + + update<<<2000, 512 >>>(dc_d,n_d,m_d,10000); + finalize<<<1,1 >>>(dc_d,n_d,m_d,10000); + verify<<<2000, 512 >>>(dc_d,n_d,m_d,10000); + + AtomicPairCounter dc; + cudaMemcpy(&dc, dc_d, sizeof(AtomicPairCounter), cudaMemcpyDeviceToHost); + + std::cout << dc.get().n << ' ' << dc.get().m << std::endl; + + return 0; +} diff --git a/HeterogeneousCore/CUDAUtilities/test/BuildFile.xml b/HeterogeneousCore/CUDAUtilities/test/BuildFile.xml new file mode 100644 index 0000000000000..57df6834bba83 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/BuildFile.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp new file mode 100644 index 0000000000000..c61a5004f8bd9 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp @@ -0,0 +1,110 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" + +#include +#include +#include +#include +#include + +template +void go() { + std::mt19937 eng; + + int rmin=std::numeric_limits::min(); + int rmax=std::numeric_limits::max(); + if (NBINS!=128) { + rmin=0; + rmax=NBINS*2-1; + } + + + + std::uniform_int_distribution rgen(rmin,rmax); + + + constexpr int N=12000; + T v[N]; + + using Hist = HistoContainer; + using Hist4 = HistoContainer; + std::cout << "HistoContainer " << Hist::nbits() << ' ' << Hist::nbins() << ' ' << Hist::totbins() << ' ' << Hist::capacity() << ' ' << (rmax-rmin)/Hist::nbins() << std::endl; + std::cout << "bins " << int(Hist::bin(0)) << ' ' << int(Hist::bin(rmin)) << ' ' << int(Hist::bin(rmax)) << std::endl; + std::cout << "HistoContainer4 " << Hist4::nbits() << ' ' << Hist4::nbins() << ' ' << Hist4::totbins() << ' ' << Hist4::capacity() << ' ' << (rmax-rmin)/Hist::nbins() << std::endl; + for (auto nh=0; nh<4; ++nh) std::cout << "bins " << int(Hist4::bin(0))+Hist4::histOff(nh) << ' ' << int(Hist::bin(rmin))+Hist4::histOff(nh) << ' ' << int(Hist::bin(rmax))+Hist4::histOff(nh) << std::endl; + + + Hist h; + Hist4 h4; + for (int it=0; it<5; ++it) { + for (long long j = 0; j < N; j++) v[j]=rgen(eng); + if (it==2) for (long long j = N/2; j < N/2+N/4; j++) v[j]=4; + h.zero();h4.zero(); + assert(h.size()==0);assert(h4.size()==0); + for (long long j = 0; j < N; j++) { h.count(v[j]); if(j<2000) h4.count(v[j],2); else h4.count(v[j],j%4); } + assert(h.size()==0); + assert(h4.size()==0); + h.finalize(); h4.finalize(); + assert(h.size()==N); + assert(h4.size()==N); + for (long long j = 0; j < N; j++) { h.fill(v[j],j); if(j<2000) h4.fill(v[j],2); else h4.fill(v[j],j,j%4); } + assert(h.off[0]==0); + assert(h4.off[0]==0); + assert(h.size()==N); + assert(h4.size()==N); + + auto verify = [&](uint32_t i, uint32_t j, uint32_t k, uint32_t t1, uint32_t t2) { + assert(t1=i); } + // std::cout << kl << ' ' << kh << std::endl; + for (auto j=h.begin(kl); j=0 && k=0) rtot += h.end(bm)-h.begin(bm); + assert(tot==rtot); + w=2; tot=0; + forEachInBins(h,v[j],w,ftest); + bp++; + bm--; + if (bp=0) rtot += h.end(bm)-h.begin(bm); + assert(tot==rtot); + } + +} + +int main() { + go(); + go(); + go(); + + + return 0; +} diff --git a/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu new file mode 100644 index 0000000000000..67cbd30b7e786 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu @@ -0,0 +1,136 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" + +#include +#include +#include +#include +#include + +#include + +template +void go() { + + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + + auto current_device = cuda::device::current::get(); + + + + std::mt19937 eng; + std::uniform_int_distribution rgen(std::numeric_limits::min(),std::numeric_limits::max()); + + + constexpr int N=12000; + T v[N]; + auto v_d = cuda::memory::device::make_unique(current_device, N); + + cuda::memory::copy(v_d.get(), v, N*sizeof(T)); + + constexpr uint32_t nParts = 10; + constexpr uint32_t partSize = N/nParts; + uint32_t offsets[nParts+1]; + + using Hist = HistoContainer; + std::cout << "HistoContainer " << (int)(offsetof(Hist,off)) << ' ' + << Hist::nbins() << ' ' << Hist::totbins() << ' ' << Hist::capacity() << ' ' << Hist::wsSize() << ' ' + << (std::numeric_limits::max()-std::numeric_limits::min())/Hist::nbins() << std::endl; + + Hist h; + + auto h_d = cuda::memory::device::make_unique(current_device, 1); + auto ws_d = cuda::memory::device::make_unique(current_device, Hist::wsSize()); + + auto off_d = cuda::memory::device::make_unique(current_device, nParts+1); + + + for (int it=0; it<5; ++it) { + + offsets[0]=0; + for (uint32_t j=1; j window ) {} else {++tot;} + } + if (kk==i) { l=false; continue; } + if (l) for (auto p=h.begin(kk+off); p=nm)) { + std::cout << "too bad " << j << ' ' << i <<' ' << int(me) << '/'<< (int)T(me-window)<< '/'<< (int)T(me+window) << ": " << kl << '/' << kh << ' '<< khh << ' '<< tot<<'/'<(); + go(); + + return 0; +} diff --git a/HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu b/HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu new file mode 100644 index 0000000000000..0d529fd7e7b03 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu @@ -0,0 +1,145 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" + +#include +#include +#include +#include +#include + +#include + +template +__global__ +void mykernel(T const * __restrict__ v, uint32_t N) { + + assert(v); + assert(N==12000); + + if (threadIdx.x==0) printf("start kernel for %d data\n",N); + + using Hist = HistoContainer; + + __shared__ Hist hist; + __shared__ typename Hist::Counter ws[32]; + + for (auto j=threadIdx.x; j=k1); + } + + for (auto i=threadIdx.x; i=0 && k::max(); + vm = std::max(vm, 0); + vm = std::min(vm,vmax); + vp = std::min(vp,vmax); + vp = std::max(vp, 0); + assert(vp>=vm); + forEachInWindow(hist, vm,vp, ftest); + int bp = Hist::bin(vp); + int bm = Hist::bin(vm); + rtot = hist.end(bp)-hist.begin(bm); + assert(tot==rtot); + } + + +} + +template +void go() { + + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + + auto current_device = cuda::device::current::get(); + + + std::mt19937 eng; + + int rmin=std::numeric_limits::min(); + int rmax=std::numeric_limits::max(); + if (NBINS!=128) { + rmin=0; + rmax=NBINS*2-1; + } + + + + std::uniform_int_distribution rgen(rmin,rmax); + + + constexpr int N=12000; + T v[N]; + + auto v_d = cuda::memory::device::make_unique(current_device, N); + assert(v_d.get()); + + using Hist = HistoContainer; + std::cout << "HistoContainer " << Hist::nbits() << ' ' << Hist::nbins() << ' ' << Hist::capacity() << ' ' << (rmax-rmin)/Hist::nbins() << std::endl; + std::cout << "bins " << int(Hist::bin(0)) << ' ' << int(Hist::bin(rmin)) << ' ' << int(Hist::bin(rmax)) << std::endl; + + for (int it=0; it<5; ++it) { + for (long long j = 0; j < N; j++) v[j]=rgen(eng); + if (it==2) for (long long j = N/2; j < N/2+N/4; j++) v[j]=4; + + assert(v_d.get()); + assert(v); + cuda::memory::copy(v_d.get(), v, N*sizeof(T)); + assert(v_d.get()); + cuda::launch(mykernel,{1,256},v_d.get(),N); + } + +} + +int main() { + go(); + go(); + go(); + + + return 0; +} diff --git a/HeterogeneousCore/CUDAUtilities/test/OneToManyAssoc_t.cu b/HeterogeneousCore/CUDAUtilities/test/OneToManyAssoc_t.cu new file mode 100644 index 0000000000000..700c7dfa6bcd4 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/OneToManyAssoc_t.cu @@ -0,0 +1,177 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" + +#include +#include +#include +#include +#include +#include + +#include + + +constexpr uint32_t MaxElem=64000; +constexpr uint32_t MaxTk=8000; +constexpr uint32_t MaxAssocs = 4*MaxTk; +using Assoc = OneToManyAssoc; + +using TK = std::array; + +__global__ +void count(TK const * __restrict__ tk, Assoc * __restrict__ assoc, uint32_t n) { + auto i = blockIdx.x * blockDim.x + threadIdx.x; + auto k = i/4; + auto j = i - 4*k; + assert(j<4); + if (k>=n) return; + if (tk[k][j]countDirect(tk[k][j]); + +} + +__global__ +void fill(TK const * __restrict__ tk, Assoc * __restrict__ assoc, uint32_t n) { + auto i = blockIdx.x * blockDim.x + threadIdx.x; + auto k = i/4; + auto j = i - 4*k; + assert(j<4); + if (k>=n) return; + if (tk[k][j]fillDirect(tk[k][j],k); + +} + +__global__ +void verify(Assoc * __restrict__ assoc) { + assert(assoc->size()=n) return; + auto m = tk[k][3]bulkFill(*apc,&tk[k][0],m); +} + + + + +int main() { + + + std::cout << "OneToManyAssoc " << Assoc::nbins() << ' ' << Assoc::capacity() << ' '<< Assoc::wsSize() << std::endl; + + + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + + auto current_device = cuda::device::current::get(); + + + std::mt19937 eng; + + std::geometric_distribution rdm(0.8); + + constexpr uint32_t N = 4000; + + std::vector> tr(N); + + // fill with "index" to element + long long ave=0; + int imax=0; + auto n=0U; + auto z=0U; + auto nz=0U; + for (auto i=0U; i<4U; ++i) { + auto j=0U; + while(j[]>(current_device, N); + assert(v_d.get()); + auto a_d = cuda::memory::device::make_unique(current_device,1); + auto ws_d = cuda::memory::device::make_unique(current_device, Assoc::wsSize()); + + cuda::memory::copy(v_d.get(), tr.data(), N*sizeof(std::array)); + + cudautils::launchZero(a_d.get(),0); + + auto nThreads = 256; + auto nBlocks = (4*N + nThreads - 1) / nThreads; + + count<<>>(v_d.get(),a_d.get(),N); + + cudautils::launchFinalize(a_d.get(),ws_d.get(),0); + verify<<<1,1>>>(a_d.get()); + fill<<>>(v_d.get(),a_d.get(),N); + + Assoc la; + cuda::memory::copy(&la,a_d.get(),sizeof(Assoc)); + std::cout << la.size() << std::endl; + imax = 0; + ave=0; + z=0; + for (auto i=0U; i>>(dc_d,v_d.get(),a_d.get(),N); + cudautils::finalizeBulk<<>>(dc_d,a_d.get()); + + AtomicPairCounter dc; + cudaMemcpy(&dc, dc_d, sizeof(AtomicPairCounter), cudaMemcpyDeviceToHost); + + std::cout << "final counter value " << dc.get().n << ' ' << dc.get().m << std::endl; + + + cuda::memory::copy(&la,a_d.get(),sizeof(Assoc)); + std::cout << la.size() << std::endl; + imax = 0; + ave=0; + for (auto i=0U; i>>(c); + cudaDeviceSynchronize(); + return c==1; + +} diff --git a/HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cpp b/HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cpp new file mode 100644 index 0000000000000..ea45a8bf77f83 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cpp @@ -0,0 +1,41 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h" +#include +#include +#include +#include + +void testBinaryFind() +{ + std::vector data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 }; + + auto lower = cuda_std::lower_bound(data.begin(), data.end(), 4); + auto upper = cuda_std::upper_bound(data.begin(), data.end(), 4); + + std::copy(lower, upper, std::ostream_iterator(std::cout, " ")); + + std::cout << '\n'; + + // classic binary search, returning a value only if it is present + + data = { 1, 2, 4, 6, 9, 10 }; + + auto test = [&](auto v) { + auto it = cuda_std::binary_find(data.cbegin(), data.cend(), v); + + if(it != data.cend()) + std::cout << *it << " found at index "<< std::distance(data.cbegin(), it) << std::endl; + else + std::cout << v << " non found" << std::endl; + }; + + test(4); + test(5); + +} + +int main() { + + testBinaryFind(); + + +} diff --git a/HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cu b/HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cu new file mode 100644 index 0000000000000..df5f84453af99 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/cudastdAlgorithm_t.cu @@ -0,0 +1,49 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h" + + +#include "cuda/api_wrappers.h" +#include + +__global__ +void testBinaryFind() +{ + int data[] = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 }; + + auto lower = cuda_std::lower_bound(data, data+13, 4); + auto upper = cuda_std::upper_bound(data, data+12, 4); + + assert(3 == upper-lower); + + // classic binary search, returning a value only if it is present + + constexpr int data2[] = { 1, 2, 4, 6, 9, 10 }; + + assert(data2+2 == cuda_std::binary_find(data2, data2+6, 4)); + assert(data2+6 == cuda_std::binary_find(data2, data2+6, 5)); +} + +#include +void wrapper() +{ + + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + + auto current_device = cuda::device::current::get(); + + cuda::launch( + testBinaryFind, + { 32, 64 } + ); + + +} + +int main() { + + wrapper(); + + +} diff --git a/HeterogeneousCore/CUDAUtilities/test/prefixScan_t.cu b/HeterogeneousCore/CUDAUtilities/test/prefixScan_t.cu new file mode 100644 index 0000000000000..53cb9b81eb332 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/prefixScan_t.cu @@ -0,0 +1,126 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/prefixScan.h" + + +template +__global__ +void testPrefixScan(uint32_t size) { + + __shared__ T ws[32]; + __shared__ T c[1024]; + auto first = threadIdx.x; + for (auto i=first; i +__global__ +void testWarpPrefixScan(uint32_t size) { + assert(size<=32); + __shared__ T c[1024]; + auto i = threadIdx.x; + c[i]=1; + __syncthreads(); + + warpPrefixScan(c,i,0xffffffff); + __syncthreads(); + + assert(1==c[0]); + if(i!=0) { + if (c[i]!=c[i-1]+1) printf("failed %d %d %d: %d %d\n",size, i, blockDim.x, c[i],c[i-1]); + assert(c[i]==c[i-1]+1); assert(c[i]==i+1); + } +} + +#include + + +__global__ +void init(uint32_t * v, uint32_t val, uint32_t n) { + auto i = blockIdx.x * blockDim.x + threadIdx.x; + if(i +int main() { + + std::cout << "warp level" << std::endl; + // std::cout << "warp 32" << std::endl; + testWarpPrefixScan<<<1,32>>>(32); + cudaDeviceSynchronize(); + // std::cout << "warp 16" << std::endl; + testWarpPrefixScan<<<1,32>>>(16); + cudaDeviceSynchronize(); + // std::cout << "warp 5" << std::endl; + testWarpPrefixScan<<<1,32>>>(5); + cudaDeviceSynchronize(); + + std::cout << "block level" << std::endl; + for(int bs=32; bs<=1024; bs+=32) { +// std::cout << "bs " << bs << std::endl; + for (int j=1;j<=1024; ++j) { +// std::cout << j << std::endl; + testPrefixScan<<<1,bs>>>(j); + cudaDeviceSynchronize(); + testPrefixScan<<<1,bs>>>(j); + cudaDeviceSynchronize(); + }} + cudaDeviceSynchronize(); + + + // test cub + std::cout << "cub" << std::endl; +// Declare, allocate, and initialize device-accessible pointers for input and output + int num_items = 10000; + uint32_t *d_in; + uint32_t *d_out; + + + cudaMalloc(&d_in,num_items*sizeof(uint32_t)); + // cudaMalloc(&d_out,num_items*sizeof(uint32_t)); + + d_out = d_in; + + auto nthreads = 256; + auto nblocks = (num_items + nthreads - 1) / nthreads; + + init<<>>(d_in, 1, num_items); + + // Determine temporary device storage requirements for inclusive prefix sum + void *d_temp_storage = nullptr; + size_t temp_storage_bytes = 0; + cub::DeviceScan::InclusiveSum(d_temp_storage, temp_storage_bytes, d_in, d_out, num_items); + + std::cout << "temp storage " << temp_storage_bytes << std::endl; + + // Allocate temporary storage for inclusive prefix sum + // fake larger ws already available + temp_storage_bytes *=8; + cudaMalloc(&d_temp_storage, temp_storage_bytes); + std::cout << "temp storage " << temp_storage_bytes << std::endl; + // Run inclusive prefix sum + CubDebugExit(cub::DeviceScan::InclusiveSum(d_temp_storage, temp_storage_bytes, d_in, d_out, num_items)); + std::cout << "temp storage " << temp_storage_bytes << std::endl; + + verify<<>>(d_out, num_items); + cudaDeviceSynchronize(); + + return 0; +} diff --git a/HeterogeneousCore/CUDAUtilities/test/radixSort_t.cu b/HeterogeneousCore/CUDAUtilities/test/radixSort_t.cu new file mode 100644 index 0000000000000..08441c8cc9555 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/radixSort_t.cu @@ -0,0 +1,203 @@ +#include "HeterogeneousCore/CUDAUtilities/interface/radixSort.h" + +#include "cuda/api_wrappers.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +template +struct RS { + using type = std::uniform_int_distribution; + static auto ud() { return type(std::numeric_limits::min(),std::numeric_limits::max());} + static constexpr T imax = std::numeric_limits::max(); +}; + +template<> +struct RS { + using T = float; + using type = std::uniform_real_distribution; + static auto ud() { return type(-std::numeric_limits::max()/2,std::numeric_limits::max()/2);} +// static auto ud() { return type(0,std::numeric_limits::max()/2);} + static constexpr int imax = std::numeric_limits::max(); +}; + +template +void go(bool useShared) { + + std::mt19937 eng; +// std::mt19937 eng2; + auto rgen = RS::ud(); + + + auto start = std::chrono::high_resolution_clock::now(); + auto delta = start - start; + + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + + auto current_device = cuda::device::current::get(); + + constexpr int blocks=10; + constexpr int blockSize = 256*32; + constexpr int N=blockSize*blocks; + T v[N]; + uint16_t ind[N]; + + + + constexpr bool sgn = T(-1) < T(0); + std::cout << "Will sort " << N << (sgn ? " signed" : " unsigned") + << (std::numeric_limits::is_integer ? " 'ints'" : " 'float'") << " of size " << sizeof(T) + << " using " << NS << " significant bytes" << std::endl; + + for (int i=0; i<50; ++i) { + + if (i==49) { + for (long long j = 0; j < N; j++) v[j]=0; + } else if (i>30) { + for (long long j = 0; j < N; j++) v[j]=rgen(eng); + } else { + uint64_t imax = (i<15) ? uint64_t(RS::imax) +1LL : 255; + for (uint64_t j = 0; j < N; j++) { + v[j]=(j%imax); if(j%2 && i%2) v[j]=-v[j]; + } + } + + uint32_t offsets[blocks+1]; + offsets[0]=0; + for (int j=1; j(current_device, N); + auto ind_d = cuda::memory::device::make_unique(current_device, N); + auto ws_d = cuda::memory::device::make_unique(current_device, N); + auto off_d = cuda::memory::device::make_unique(current_device, blocks+1); + + cuda::memory::copy(v_d.get(), v, N*sizeof(T)); + cuda::memory::copy(off_d.get(), offsets, 4*(blocks+1)); + + if (i<2) std::cout << "lauch for " << offsets[blocks] << std::endl; + + auto ntXBl = 1==i%4 ? 256 : 256; + + delta -= (std::chrono::high_resolution_clock::now()-start); + constexpr int MaxSize = 256*32; + if (useShared) + cuda::launch( + radixSortMultiWrapper, + { blocks, ntXBl, MaxSize*2 }, + v_d.get(),ind_d.get(),off_d.get(),nullptr + ); + else + cuda::launch( + radixSortMultiWrapper2, + { blocks, ntXBl }, + v_d.get(),ind_d.get(),off_d.get(),ws_d.get() + ); + + + if (i==0) std::cout << "done for " << offsets[blocks] << std::endl; + +// cuda::memory::copy(v, v_d.get(), 2*N); + cuda::memory::copy(ind, ind_d.get(), 2*N); + + delta += (std::chrono::high_resolution_clock::now()-start); + + if (i==0) std::cout << "done for " << offsets[blocks] << std::endl; + + if (32==i) { + std::cout << LL(v[ind[0]]) << ' ' << LL(v[ind[1]]) << ' ' << LL(v[ind[2]]) << std::endl; + std::cout << LL(v[ind[3]]) << ' ' << LL(v[ind[10]]) << ' ' << LL(v[ind[blockSize-1000]]) << std::endl; + std::cout << LL(v[ind[blockSize/2-1]]) << ' ' << LL(v[ind[blockSize/2]]) << ' ' << LL(v[ind[blockSize/2+1]]) << std::endl; + } + for (int ib=0; ib inds; + if (offsets[ib+1]> offsets[ib]) inds.insert(ind[offsets[ib]]); + for (auto j = offsets[ib]+1; j < offsets[ib+1]; j++) { + inds.insert(ind[j]); + auto a = v+offsets[ib]; + auto k1=a[ind[j]]; auto k2=a[ind[j-1]]; + auto sh = sizeof(uint64_t)-NS; sh*=8; + auto shorten = [sh](T& t) { + auto k = (uint64_t *)(&t); + *k = (*k >> sh)<(delta).count()/50. + << " ms" << std::endl; +} + + +int main() { + + bool useShared=false; + + std::cout << "using Global memory" << std::endl; + + + go(useShared); + go(useShared); + go(useShared); + go(useShared); + go(useShared); + go(useShared); + go(useShared); + + go(useShared); + go(useShared); + go(useShared); + // go(v); + + useShared=true; + + std::cout << "using Shared memory" << std::endl; + + go(useShared); + go(useShared); + go(useShared); + go(useShared); + go(useShared); + go(useShared); + go(useShared); + + go(useShared); + go(useShared); + go(useShared); + // go(v); + + + + return 0; +} diff --git a/HeterogeneousCore/CUDAUtilities/test/test_GPUSimpleVector.cu b/HeterogeneousCore/CUDAUtilities/test/test_GPUSimpleVector.cu new file mode 100644 index 0000000000000..d98710c00a324 --- /dev/null +++ b/HeterogeneousCore/CUDAUtilities/test/test_GPUSimpleVector.cu @@ -0,0 +1,87 @@ +// author: Felice Pantaleo, CERN, 2018 +#include +#include +#include + +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +__global__ void vector_pushback(GPU::SimpleVector *foo) { + auto index = threadIdx.x + blockIdx.x * blockDim.x; + foo->push_back(index); +} + +__global__ void vector_reset(GPU::SimpleVector *foo) { + + foo->reset(); +} + +__global__ void vector_emplace_back(GPU::SimpleVector *foo) { + auto index = threadIdx.x + blockIdx.x * blockDim.x; + foo->emplace_back(index); +} + +int main() { + auto maxN = 10000; + GPU::SimpleVector *obj_ptr = nullptr; + GPU::SimpleVector *d_obj_ptr = nullptr; + GPU::SimpleVector *tmp_obj_ptr = nullptr; + int *data_ptr = nullptr; + int *d_data_ptr = nullptr; + + cudaCheck(cudaMallocHost(&obj_ptr, sizeof(GPU::SimpleVector))); + cudaCheck(cudaMallocHost(&data_ptr, maxN * sizeof(int))); + cudaCheck(cudaMalloc(&d_data_ptr, maxN * sizeof(int))); + + auto v = GPU::make_SimpleVector(obj_ptr, maxN, data_ptr); + + cudaCheck(cudaMallocHost(&tmp_obj_ptr, sizeof(GPU::SimpleVector))); + GPU::make_SimpleVector(tmp_obj_ptr, maxN, d_data_ptr); + assert(tmp_obj_ptr->size() == 0); + assert(tmp_obj_ptr->capacity() == static_cast(maxN)); + + cudaCheck(cudaMalloc(&d_obj_ptr, sizeof(GPU::SimpleVector))); + // ... and copy the object to the device. + cudaCheck(cudaMemcpy(d_obj_ptr, tmp_obj_ptr, sizeof(GPU::SimpleVector), cudaMemcpyDefault)); + + int numBlocks = 5; + int numThreadsPerBlock = 256; + vector_pushback<<>>(d_obj_ptr); + cudaCheck(cudaGetLastError()); + cudaCheck(cudaDeviceSynchronize()); + + cudaCheck(cudaMemcpy(obj_ptr, d_obj_ptr, sizeof(GPU::SimpleVector), cudaMemcpyDefault)); + + assert(obj_ptr->size() == (numBlocks * numThreadsPerBlock < maxN + ? numBlocks * numThreadsPerBlock + : maxN)); + vector_reset<<>>(d_obj_ptr); + cudaCheck(cudaGetLastError()); + cudaCheck(cudaDeviceSynchronize()); + + cudaCheck(cudaMemcpy(obj_ptr, d_obj_ptr, sizeof(GPU::SimpleVector), cudaMemcpyDefault)); + + assert(obj_ptr->size() == 0); + + vector_emplace_back<<>>(d_obj_ptr); + cudaCheck(cudaGetLastError()); + cudaCheck(cudaDeviceSynchronize()); + + cudaCheck(cudaMemcpy(obj_ptr, d_obj_ptr, sizeof(GPU::SimpleVector), cudaMemcpyDefault)); + + assert(obj_ptr->size() == (numBlocks * numThreadsPerBlock < maxN + ? numBlocks * numThreadsPerBlock + : maxN)); + + cudaCheck(cudaMemcpy(data_ptr, d_data_ptr, obj_ptr->size() * sizeof(int), cudaMemcpyDefault)); + cudaCheck(cudaFreeHost(obj_ptr)); + cudaCheck(cudaFreeHost(data_ptr)); + cudaCheck(cudaFreeHost(tmp_obj_ptr)); + cudaCheck(cudaFree(d_data_ptr)); + cudaCheck(cudaFree(d_obj_ptr)); + std::cout << "TEST PASSED" << std::endl; + return 0; +} diff --git a/HeterogeneousCore/MPICore/BuildFile.xml b/HeterogeneousCore/MPICore/BuildFile.xml new file mode 100644 index 0000000000000..91b083944c269 --- /dev/null +++ b/HeterogeneousCore/MPICore/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/HeterogeneousCore/MPICore/interface/WrapperHandle.h b/HeterogeneousCore/MPICore/interface/WrapperHandle.h new file mode 100644 index 0000000000000..1b46173213fc4 --- /dev/null +++ b/HeterogeneousCore/MPICore/interface/WrapperHandle.h @@ -0,0 +1,290 @@ +#ifndef WrapperHandle_h +#define WrapperHandle_h + +#include +#include +#include +#include + +#include "DataFormats/Common/interface/Handle.h" +#include "DataFormats/Common/interface/OrphanHandle.h" +#include "DataFormats/Common/interface/WrapperBase.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Utilities/interface/TypeID.h" + + +namespace edm { + + // specialise Handle for a WrapperBase + + template <> + class Handle : public HandleBase { + public: + // default constructor + Handle() : + HandleBase(), + type_(nullptr) + { } + + // throws exception if `type` is not a known C++ class type + Handle(std::string const& type) : + HandleBase(), + type_(& TypeWithDict::byName(type).typeInfo()) + { } + + Handle(WrapperBase const* wrapper, Provenance const* prov, std::string const& type) : + HandleBase(wrapper, prov), + type_(& TypeWithDict::byName(type).typeInfo()) + { } + + // throws exception if `type` is invalid + Handle(std::type_info const& type) : + HandleBase(), + type_(& type) + { } + + Handle(WrapperBase const* wrapper, Provenance const* prov, std::type_info const& type) : + HandleBase(wrapper, prov), + type_(& type) + { } + + // used when the attempt to get the data failed + Handle(std::shared_ptr&& whyFailed) : + HandleBase(std::move(whyFailed)), + type_(nullptr) + { } + + // copiable and moveable + Handle(Handle const& h) = default; + Handle(Handle && h) = default; + + Handle & operator=(Handle const& h) = default; + Handle & operator=(Handle && h) = default; + + // (non-trivial) default destructor + ~Handle() = default; + + // reimplement swap over HandleBase + // DO NOT swap with a HandleBase or with a different Handle + void swap(Handle & other) { + HandleBase::swap(static_cast(other)); + std::swap(type_, other.type_); + } + + WrapperBase const* product() const { + return static_cast(productStorage()); + } + + WrapperBase const* operator->() const { + return product(); + } + + WrapperBase const& operator*() const { + return *product(); + } + + std::type_info const& typeInfo() const { + return *type_; + } + + private: + std::type_info const* type_ = nullptr; + }; + + + // swap free function + + inline void swap(Handle & a, Handle & b) + { + a.swap(b); + } + + + // specialise the conversion from a BasicHandle into a Handle + + template <> + inline + void convert_handle(BasicHandle && bh, Handle& result) + { + if (bh.failedToGet()) { + Handle h(std::move(bh.whyFailedFactory())); + result = std::move(h); + return; + } + WrapperBase const* wrapper = bh.wrapper(); + if (wrapper == nullptr) { + handleimpl::throwInvalidReference(); + } + if (not (wrapper->dynamicTypeInfo() == result.typeInfo())) { + handleimpl::throwConvertTypeError(result.typeInfo(), bh.wrapper()->dynamicTypeInfo()); + } + Handle h(wrapper, bh.provenance(), result.typeInfo()); + result = std::move(h); + } + + + // specialise OrphanHandle for a WrapperBase + + template <> + class OrphanHandle : public OrphanHandleBase { + public: + // default constructed handles are invalid + OrphanHandle() : + OrphanHandleBase(), + type_(nullptr) + { } + + // does not take ownership of the WrapperBase + // throws an exception if `type` is an invalid or unknown C++ class type + OrphanHandle(WrapperBase const* wrapper, std::string const& type, ProductID const& id) : + OrphanHandleBase(wrapper, id), + type_(& TypeWithDict::byName(type).typeInfo()) + { } + + // does not take ownership of the WrapperBase + // assumes `type` to be a valid and known C++ type + OrphanHandle(WrapperBase const* wrapper, std::type_info const& type, ProductID const& id) : + OrphanHandleBase(wrapper, id), + type_(& type) + { } + + // copiable and moveable + OrphanHandle(OrphanHandle const& h) = default; + OrphanHandle(OrphanHandle && h) = default; + + OrphanHandle & operator=(OrphanHandle const& h) = default; + OrphanHandle & operator=(OrphanHandle && h) = default; + + // default destructor + ~OrphanHandle() = default; + + // reimplement swap over OrphanHandleBase + // DO NOT swap with a OrphanHandleBase or with a different OrphanHandle + void swap(OrphanHandle & other) { + OrphanHandleBase::swap(static_cast(other)); + std::swap(type_, other.type_); + } + + WrapperBase const* product() const { + return static_cast(productStorage()); + } + + WrapperBase const* operator->() const { + return product(); + } + + WrapperBase const& operator*() const { + return *product(); + } + + std::type_info const& typeInfo() const { + return *type_; + } + + private: + std::type_info const* type_ = nullptr; + }; + + + // swap free function + + inline void swap(OrphanHandle & a, OrphanHandle & b) { + a.swap(b); + } + + // specialise the Event methods for getting a WrapperBase + + template <> + inline + bool Event::getByLabel(InputTag const& tag, Handle& result) const + { + result.clear(); + BasicHandle bh = provRecorder_.getByLabel_(TypeID(result.typeInfo()), tag, moduleCallingContext_); + convert_handle(std::move(bh), result); // throws on conversion error + if (result.failedToGet()) { + return false; + } + addToGotBranchIDs(*result.provenance()); + return true; + } + + template <> + inline + bool Event::getByLabel(std::string const& label, std::string const& productInstanceName, Handle& result) const + { + result.clear(); + BasicHandle bh = provRecorder_.getByLabel_(TypeID(result.typeInfo()), label, productInstanceName, emptyString_, moduleCallingContext_); + convert_handle(std::move(bh), result); // throws on conversion error + if (result.failedToGet()) { + return false; + } + addToGotBranchIDs(*result.provenance()); + return true; + } + + template <> + inline + bool Event::getByToken(EDGetToken token, Handle& result) const + { + result.clear(); + BasicHandle bh = provRecorder_.getByToken_(TypeID(result.typeInfo()), PRODUCT_TYPE, token, moduleCallingContext_); + convert_handle(std::move(bh), result); // throws on conversion error + if (result.failedToGet()) { + return false; + } + addToGotBranchIDs(*result.provenance()); + return true; + } + + template <> + inline + bool Event::getByToken(EDGetTokenT token, Handle& result) const + { + result.clear(); + BasicHandle bh = provRecorder_.getByToken_(TypeID(result.typeInfo()), PRODUCT_TYPE, token, moduleCallingContext_); + convert_handle(std::move(bh), result); // throws on conversion error + if (result.failedToGet()) { + return false; + } + addToGotBranchIDs(*result.provenance()); + return true; + } + + // specialise the Event methods for putting a WrapperBase + + template <> + inline + OrphanHandle Event::putImpl(EDPutToken::value_type index, std::unique_ptr product) + { + // the underlying collection `post_insert` is not called, as it is assumed + // it was done before creating the Wrapper + assert(index < putProducts().size()); + //putProducts()[index] = std::move(product->wrapper()); + putProducts()[index] = std::move(product); + WrapperBase const* prod = putProducts()[index].get(); + auto const& prodType = prod->dynamicTypeInfo(); + auto const& prodID = provRecorder_.getProductID(index); + return OrphanHandle(prod, prodType, prodID); + } + + template <> + inline + OrphanHandle Event::put(EDPutToken token, std::unique_ptr product) + { + auto const& typeInfo = product->dynamicTypeInfo(); + if (UNLIKELY(product.get() == nullptr)) { + // null pointer is illegal + principal_get_adapter_detail::throwOnPutOfNullProduct("Event", TypeID(typeInfo), provRecorder_.productInstanceLabel(token)); + } + if (UNLIKELY(token.isUninitialized())) { + principal_get_adapter_detail::throwOnPutOfUninitializedToken("Event", typeInfo); + } + if (UNLIKELY(provRecorder_.getTypeIDForPutTokenIndex(token.index()) != TypeID(typeInfo))) { + principal_get_adapter_detail::throwOnPutOfWrongType(typeInfo, provRecorder_.getTypeIDForPutTokenIndex(token.index())); + } + return putImpl(token.index(), std::move(product)); + } + +} + +#endif // WrapperHandle_h diff --git a/HeterogeneousCore/MPICore/interface/serialization.h b/HeterogeneousCore/MPICore/interface/serialization.h new file mode 100644 index 0000000000000..765ca7a79d0e8 --- /dev/null +++ b/HeterogeneousCore/MPICore/interface/serialization.h @@ -0,0 +1,99 @@ +#ifndef HeterogeneousCore_MPICore_interface_serialization_h +#define HeterogeneousCore_MPICore_interface_serialization_h + +#include +#include +#include +#include +#include + +#include "DataFormats/Common/interface/WrapperBase.h" + +namespace io { + + // io::unique_buffer manages a memory buffer of run-time fixed size; + // the underlying memory is release when the io::unique_buffer is destroyed. + class unique_buffer { + public: + // default constructor + unique_buffer(); + + // allocate a new buffer of the specified size + unique_buffer(size_t size); + + // adopt an existing memory buffer of the specified size + unique_buffer(std::byte* data, size_t size); + unique_buffer(std::unique_ptr && data, size_t size); + + // default copy and move constructors and assignment operators + unique_buffer(unique_buffer const&) = default; + unique_buffer(unique_buffer &&) = default; + unique_buffer& operator=(unique_buffer const&) = default; + unique_buffer& operator=(unique_buffer &&) = default; + + // default destructor, releasing the owned memory + ~unique_buffer() = default; + + // access to the underlying memory + std::byte const * data() const; + std::byte * data(); + + // release ownership of the underlying memory + std::byte * release(); + + // return the size of of the buffer + size_t size() const; + + private: + std::unique_ptr data_; + size_t size_; + }; + + // dump the content of a buffer, using the same format as `hd` + template + T& operator<<(T& out, unique_buffer const& buffer) + { + auto data = reinterpret_cast(buffer.data()); + auto size = buffer.size(); + unsigned int l = 0; + for (; l < size / 16; ++l) { + out << std::setw(8) << std::setfill('0') << std::hex << l*16 << std::dec; + for (unsigned int i = l*16; i < (l+1)*16; ++i) { + out << ((i % 8 == 0) ? " " : " "); + out << std::setw(2) << std::setfill('0') << std::hex << (((int) data[i]) & 0xff) << std::dec; + } + out << " |"; + for (unsigned int i = l*16; i < (l+1)*16; ++i) { + out << (char) (std::isprint(data[i]) ? data[i] : '.'); + } + out << "|\n"; + } + if (size % 16 != 0) { + out << std::setw(8) << std::setfill('0') << std::hex << l*16 << std::dec; + for (unsigned int i = l*16; i < size; ++i) { + out << ((i % 8 == 0) ? " " : " "); + out << std::setw(2) << std::setfill('0') << std::hex << (((int) data[i]) & 0xff) << std::dec; + } + for (unsigned int i = size; i < (l+1)*16; ++i) { + out << ((i % 8 == 0) ? " " : " "); + } + out << " |"; + for (unsigned int i = l*16; i < size; ++i) { + out << (char) (std::isprint(data[i]) ? data[i] : '.'); + } + out << "|\n"; + } + out << std::setw(8) << std::setfill('0') << std::hex << size << std::dec << '\n'; + return out; + } + + + // serialise a Wrapper into an io::unique_buffer + io::unique_buffer serialize(edm::WrapperBase const& wrapper); + + // deserialise a Wrapper from an io::unique_buffer and store it in a unique_ptr + std::unique_ptr deserialize(io::unique_buffer const& buffer); + +} + +#endif // HeterogeneousCore_MPICore_interface_serialization_h diff --git a/HeterogeneousCore/MPICore/src/serialization.cc b/HeterogeneousCore/MPICore/src/serialization.cc new file mode 100644 index 0000000000000..17d4358c4fdf1 --- /dev/null +++ b/HeterogeneousCore/MPICore/src/serialization.cc @@ -0,0 +1,101 @@ +#include +#include + +#include +#include + +#include "HeterogeneousCore/MPICore/interface/serialization.h" + +namespace io { + + // default constructor + unique_buffer::unique_buffer() : + data_(nullptr), + size_(0) + { + } + + // allocate a new buffer of the specified size + unique_buffer::unique_buffer(size_t size) : + data_(new std::byte[size]), + size_(size) + { + } + + // adopt an existing memory buffer of the specified size + unique_buffer::unique_buffer(std::byte* data, size_t size) : + data_(data), + size_(size) + { + } + + // adopt an existing memory buffer of the specified size + unique_buffer::unique_buffer(std::unique_ptr && data, size_t size) : + data_(std::move(data)), + size_(size) + { + } + + // access to the underlying memory + std::byte const * unique_buffer::data() const + { + return data_.get(); + } + + // access to the underlying memory + std::byte * unique_buffer::data() + { + return data_.get(); + } + + // release ownership of the underlying memory + std::byte * unique_buffer::release() + { + return data_.release(); + } + + // return the size of of the buffer + size_t unique_buffer::size() const + { + return size_; + } + + + // serialise a Wrapper into a char[] via a TBufferFile + unique_buffer serialize(edm::WrapperBase const& wrapper) + { + /* TODO + * construct the buffer with an initial size based on the wrapped class size ? + * take into account any offset to/from the edm::WrapperBase base class ? + */ + TBufferFile serializer(TBuffer::kWrite); + serializer.WriteObjectAny(& wrapper, TClass::GetClass(wrapper.wrappedTypeInfo())); + + size_t size = serializer.Length(); + std::byte * data = reinterpret_cast(serializer.Buffer()); + serializer.DetachBuffer(); + + return unique_buffer(data, size); + } + + std::unique_ptr deserialize(unique_buffer const& buffer) + { + TBufferFile deserializer(TBuffer::kRead, buffer.size(), (void *) buffer.data(), false); + + /* TODO try different versions: + * does not work ? + return std::unique_ptr(reinterpret_cast(deserializer.ReadObjectAny(edmWrapperBaseClass))); + * works but maybe not always ? + return std::unique_ptr(reinterpret_cast(deserializer.ReadObjectAny(nullptr))); + * not useful ? + return std::unique_ptr(reinterpret_cast(deserializer.ReadObjectAny(rootType))); + * not useful ? + static TClass const* edmWrapperBaseClass = TClass::GetClass(typeid(edm::WrapperBase)); + TClass * rootType = wrappedType.getClass(); + int offset = rootType->GetBaseClassOffset(edmWrapperBaseClass); + return std::unique_ptr(reinterpret_cast(reinterpret_cast(deserializer.ReadObjectAny(rootType)) + offset)); + */ + return std::unique_ptr(reinterpret_cast(deserializer.ReadObjectAny(nullptr))); + } + +} diff --git a/HeterogeneousCore/MPIServices/plugins/BuildFile.xml b/HeterogeneousCore/MPIServices/plugins/BuildFile.xml new file mode 100644 index 0000000000000..e857a457b005f --- /dev/null +++ b/HeterogeneousCore/MPIServices/plugins/BuildFile.xml @@ -0,0 +1,4 @@ + + + + diff --git a/HeterogeneousCore/MPIServices/plugins/MPIService.cc b/HeterogeneousCore/MPIServices/plugins/MPIService.cc new file mode 100644 index 0000000000000..1835ea00c5dc0 --- /dev/null +++ b/HeterogeneousCore/MPIServices/plugins/MPIService.cc @@ -0,0 +1,39 @@ +// -*- C++ -*- + +#include + +#include + +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ServiceRegistry/interface/ActivityRegistry.h" + +class MPIService { +public: + MPIService(edm::ParameterSet const& config, edm::ActivityRegistry & registry); + ~MPIService(); + + static void fillDescriptions(edm::ConfigurationDescriptions & descriptions); +}; + +MPIService::MPIService(edm::ParameterSet const & config, edm::ActivityRegistry & registry) +{ + int provided; + MPI_Init_thread(nullptr, nullptr, MPI_THREAD_MULTIPLE, &provided); + assert(provided == MPI_THREAD_MULTIPLE); +} + +MPIService::~MPIService() { + MPI_Finalize(); +} + +void +MPIService::fillDescriptions(edm::ConfigurationDescriptions & descriptions) { + edm::ParameterSetDescription desc; + descriptions.add("MPIService", desc); + descriptions.setComment(R"(This Service provides a common interface to MPI configuration for the CMSSW job.)"); +} + +#include "FWCore/ServiceRegistry/interface/ServiceMaker.h" +DEFINE_FWK_SERVICE(MPIService); diff --git a/HeterogeneousCore/Producer/BuildFile.xml b/HeterogeneousCore/Producer/BuildFile.xml new file mode 100644 index 0000000000000..b75ff104fc2d9 --- /dev/null +++ b/HeterogeneousCore/Producer/BuildFile.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/HeterogeneousCore/Producer/README.md b/HeterogeneousCore/Producer/README.md new file mode 100644 index 0000000000000..595dec70e1006 --- /dev/null +++ b/HeterogeneousCore/Producer/README.md @@ -0,0 +1,468 @@ +# Prototype for CMSSW interface to heterogenous algorithms + +## Introduction + +This package contains a prtotype for the CMSSW interface to +heterogeneous algorithms. The current implementation is, in a sense, a +mini-framework between the CMSSW core framework and the heterogeneous +algorithms. + +More details can be found from the sub-package specific README files (when they get added). + +## Sub-packages + +* [`CUDACore`](../CUDACore) CUDA-specific core components + - *TODO:* Do we actually need this separate from `CUDAServices`? Which one to keep? +* [`CUDAServices`](../CUDAServices) Various edm::Services related to CUDA +* [`CUDAUtilities`](../CUDAUtilities) Various utilities for CUDA kernel code +* [`Producer`](#heterogeneousedproducer) Core of the mini-framework for code organization: a base EDProducer class with algorithm scheduling to devices +* [`Product`](../Product) Core of the mini-framework for data products + +## Design goals + +1. Same module configuration should work on all machines (whether they have heterogeneous devices or not) +2. Keep track of where data is located +3. Run algorithms on the device where their input is located if possible +4. Transfer temporary/transient data to CPU only if needed +5. Abstract away repeated boilerplate code + +## Design considerations + +All below is assuming we do not touch the core CMSSW framework (that +is left for a later exercise when we know better what exactly we want +to do). + +1. The device-specific algorithms must be implemented and scheduled to the device(s) within a single EDProducer +2. Need a special product keeping track of the data location +3. Information of all the input heterogeneous devices must be propagated to the point deciding the device to run the algorithm +4. The special product e.g. holds functions to do the transfer that are called if/when needed + +## General TODO items + +There are also many, more specific TODO items mentioned in comments +within the code. The items below are more general topics (in no +particular order). + +* Improve algorithm-to-device scheduling + - Currently if an algoritm has a GPU implementation and system has a + GPU, the algorithm is always scheduled to the GPU + * This may lead to under-utilization of the CPU if most of the + computations are offloaded to the GPU + - An essential question for making this scheduling more dynamic is + what exactly (we want) it (to) means that a "GPU is too busy" so + it is better to run the algorithm on a CPU + - Possible ideas to explore + * Check the CUDA device utilization (see also monitoring point below) + - Past/current state does not guarantee much about the near future + * Use "tokens" as a resource allocation mechanism + - How many tokens per device? + - What if there no free tokens now but one becomes available after 1 ms? + * In acquire, if GPU is "busy", put the EDProducer to a queue of + heterogeneous tasks. When GPU "becomes available", pick an + EDProducer from the queue and run it in GPU. If CPU runs out of + job, pick an EDProducer from the queue and run it in CPU. + - How to define "busy" and "becomes available"? + - How to become aware that CPU runs out of job? + * Can we create a TBB task that is executed only if there is nothing else to do? + - How does this interact with possible other devices? E.g. if an algorithm has implementations for CPU, GPU, and FPGA? +* Improve edm::Stream-to-CUDA-device scheduling + - Currently each edm::Stream is assigned "statically" to each CUDA device in a round-robin fastion + * There is no load balancing so throughput and utilization will not be optimal + - The reasons for bothering with this is that the `cudaMalloc` is a + heavy operation (see next point) not to be called for each event. + Instead we preallocate the buffers the algorithms need at the + initialization time. In the presence of multiple devices this + pre-allocation leads to a need to somehow match the edm::Streams + and devices. + - Possible ideas to explore + * Naively we could allocate a buffer per edm::Stream in each CUDA device + - Amount of allocated memory is rather excessive + * For N edm::Streams and M GPUs, allocate int((N+1)/M) buffers on each device, eventually pick the least utilized GPU + - No unnecessary buffers + - Need a global list/queue of these buffers per module + * Can the list be abstracted? If not, this solution scales poorly with modules + * Our own CUDA memory allocator that provides a fast way to allocate scratch buffers + - Allows allocating the buffers on-demand on the "best-suited" device +* Our own CUDA memory allocator + - A `cudaMalloc` is a global synchronization point and takes time, + so we want to minimize their calls. This is the main reason to + assign edm::Streams to CUDA devices (see previous point). + - Well-performing allocators are typically highly non-trivial to construct +* Conditions data on GPU + - Currently each module takes care of formatting, transferring, and updating the conditions data to GPU + - This is probably good-enough for the current prototyping phase, but what about longer term? + * How to deal with multiple devices, multiple edm::Streams, and multiple lumi sections in flight? + * Do we need to make EventSetup aware of the devices? How much do the details depend on device type? +* Add possibility to initiate the GPU->CPU transfer before the CPU product is needed + - This would enable overlapping the GPU->CPU transfer while CPU is busy + with other work, so the CPU product requestor would not have to wait +* Improve configurability + - E.g. for preferred device order? +* Add fault tolerance + - E.g. in a case of a GPU running out of memory continue with CPU + - Should be configurable +* Add support for multiple heterogeneous inputs for a module + - Currently the device scheduling is based only on the "first input" + - Clearly this is inadequate in general and needs to be improved + - Any better suggestions than taking `and` of all locations? +* Improve resource monitoring + - E.g. for CUDA device utilization + * https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html#group__nvmlDeviceQueries_1g540824faa6cef45500e0d1dc2f50b321 + * https://docs.nvidia.com/deploy/nvml-api/structnvmlUtilization__t.html#structnvmlUtilization__t + - Include in `CUDAService` or add a separete monitoring service? +* Add support for a mode similar to `tbb::streaming_node` + - Current way of `HeterogeneousEDProducer` with the `ExternalWork` resembles `tbb::async_node` + - In principle the `streaming_node`-way would, for a chain of + GPU-enabled modules, allow the GPU to immediately continue to the + next module without waiting the code path to go through the CMSSW + framework +* Add support for more devices + - E.g. OpenCL, FPGA, remote offload +* Explore the implementation of these features into the core CMSSW framework + - E.g. HeterogeneousProduct would likely go to edm::Wrapper +* Explore how to make core framework/TBB scheduling aware of heterogenous devices + +# HeterogeneousEDProducer + +`HeterogeneousEDProducer` is implemented as a `stream::EDProducer` using the +[`ExternalWork` extension](https://twiki.cern.ch/twiki/bin/view/CMSPublic/FWMultithreadedFrameworkStreamModuleInterface#edm_ExternalWork). + +## Configuration + +`HeterogeneousEDProducer` requires `heterogeneousEnabled_` `cms.PSet` +to be available in the module configuration. The `PSet` can be used to +override module-by-module which devices can be used by that module. +The structure of the `PSet` are shown in the following example + +```python +process.foo = cms.EDModule("FooProducer", + a = cms.int32(1), + b = cms.VPSet(...), + ... + heterogeneousEnabled_ = cms.untracked.PSet( + GPUCuda = cms.untracked.bool(True), + FPGA = cms.untracked.bool(False), # forbid the module from running on a device type (note that we don't support FPGA devices at the moment though) + force = cms.untracked.string("GPUCuda") # force the module to run on a specific device type + ) +) +``` + +The difference between the boolean flags and the `force` parameter is the following +* The boolean flags control whether the algorithm can be scheduled on the individual device type or not +* The `force` parameter implies that the algorithm is always scheduled on that device type no matter what. If the device type is not available on the machine, an exception is thrown. + +Currently, with only CUDA GPU and CPU support, this level of configurability is a bit overkill though. + +## Class declaration + +In order to use the `HeterogeneousEDProducer` the `EDProducer` class +must inherit from `HeterogeneousEDProducer<...>`. The devices, which +the `EDProducer` is supposed to support, are given as a template +argument via `heterogeneous::HeterogeneousDevices<...>`. The usual +[stream producer extensions](https://twiki.cern.ch/twiki/bin/view/CMSPublic/FWMultithreadedFrameworkStreamModuleInterface#Template_Arguments) +can be also passed via additional template arguments. + +```cpp +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" // needed for heterogeneous::GPUCuda + +class FooProducer: public HeterogeneousEDProducer< + heterogeneous::HeterogeneousDevices< + heterogeneous::GPUCuda, + heterogeneous::CPU + > + // here you can pass any stream producer extensions +> { + ... + +``` + +In this example the `FooProducer` declares that prodives +implementations for CUDA GPU and CPU. Note that currently CPU is +mandatory, and it has to be the last argument. The order of the +devices dictates the order that the algorithm is scheduled to the +devices. E.g. in this example, the system runs the algorithm in GPU if +it can, and only if it can not, in CPU. For the list of supported +device types, see the [list below](#devices). + +## Constructor + +`HeterogeneousEDProducer` needs access to the configuration, so it has +to be passed to its constructor as in the following example + +```cpp +FooProducer::FooProducer(edm::ParameterSet const& iConfig): + HeterogeneousEDProducer(iConfig), + ... +``` +### Consumes + +If the `EDProducer` reads any `HeterogeneousProduct`'s +([see more details](#heterogeneousproduct)), the `consumes()` call +should be made along the following + +```cpp +class FooProducer ... { + ... + EDGetTokenT token_; +}; +... +FooProducer::FooProducer(edm::ParameterSet const& iConfig): + ... + token_(consumesHeterogeneous(iConfig.getParameter("..."))), + ... +``` + +so that `HeterogeneousEDProducer` can inspect the location of input +heterogeneous products to decide on which device to run the algorithm +([see more details](#device-scheduling)). + +### Produces + +If the `EDProducer` produces any `HeterogeneousProduct`'s +([see more details](#heterogeneousproduct)), the `produces()` call +should be made along the following (i.e. as usual) + +```cpp +FooProducer::FooProducer(edm::ParameterSet const& iConfig) ... { + ... + produces(); +} +``` + +## fillDescriptions() + +`HeterogeneousEDProducer` provides a `fillPSetDescription()` function +that can be called from the concrete `EDProducer`'s +`fillDescriptions()` as in the following example + +```cpp +void FooProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + + // fill desc for other parameters + + HeterogeneousEDProducer::fillPSetDescription(desc); + + descriptions.add("fooProducer", desc); +} +``` + +## Device scheduling + +The per-event scheduling of algorithms is currently as follows +1. If there are no `HeterogeneousProduct` inputs ([see more details](#heterogeneousproduct)) + * Loop over the device types in the order specified in `heterogeneous::HeterogeneousDevices<...>` template arguments + * Run the algorithm on the first device that is enabled for that module (instance) +2. If there are `HeterogeneousProduct` inputs + * Run the algorithm on **the device where the data of the first input resides** + +## HeterogeneousProduct + +The `HeterogeneousProduct` is a transient edm product with the following properties +* placeholder for products (of arbitrary types) in all device types +* tracks the location of the data +* automatic, on-demand transfers from device to CPU + - developer has to provide a function to do the transfer and possible data reorganiazation + +Some of the complexity exists to avoid ROOT dictionary generation of the concrete product types. + +## HeterogeneousEvent + +The `HeterogeneousEvent` is a wrapper on top of `edm::Event` to hide +most of the complexity of `HeterogeneousProduct` to make its use look +almost like standard products with `edm::Event`. Some part of the +`edm::Event` interface is implemented (and delegated back to +`edm::Event`) in order to get/put standard products as well. + +Here is a short example how to deal use `HeterogeneousProduct` with +`HeterogeneousEvent` (using the same `FooProducer` example as before) + +```cpp +class FooProducer ... { + ... + // in principle these definitions should be treated like DataFormats + struct CPUProduct { + std::vector foo; + }; + struct GPUProduct { + float *foo_d; // pointer to GPU memory + } + + using InputType = HeterogeneousProductImpl, + heterogeneous::GPUProduct>; + using OutputType = InputType; // using same input and output only because of laziness + + void transferGPUtoCPU(GPUProduct const& gpu, CPUProduct& cpu) const; +}; + +void FooProducer::produceCPU(edm::HeterogeneousEvent const& iEvent, ...) { + edm::Handle hinput; + iEvent.getByToken(token_, hinput); // note the InputType template argument + + // do whatever you want with hinput->foo; + + auto output = std::make_unique(...); + iEvent.put(std::move(output)); // note the OutputType template argument +} + +void FooProducer::acquireGPUCuda(edm::HeterogeneousEvent const& iEvent, ...) { + edm::Handle hinput; + iEvent.getByToken(token_, hinput); // note the InputType template argument + + // do whatever you want with hinput->foo_d; + + auto output = std::make_unique(...); + // For non-CPU products, a GPU->CPU transfer function must be provided + // In this example it is prodided as a lambda calling a member function, but this is not required + // The function can be anything assignable to std::function + iEvent.put(std::move(output), [this](GPUProduct const& gpu, CPUProduct& cpu) { // note the OutputType template argument + this->transferGPUtoCPU(gpu, cpu); + }); + // It is also possible to disable the GPU->CPU transfer + // If the data resides on a GPU, and the corresponding CPU product is requested, an exception is thrown + //iEvent.put(std::move(output), heterogeneous::DisableTransfer); // note the OutputType template argument +} + +``` + + +## Devices + +This section documents which functions the `EDProducer` can/has to +implement for various devices. + +### CPU + +A CPU implementation is declared by giving `heterogeneous::CPU` as a +template argument to `heterogeneous::HeterogeneousDevices`. Currently +it is a mandatory argument, and has to be the last one (i.e. there +must always be a CPU implementation, which is used as the last resort +if there are no other devices). + +#### Optional functions + +There is one optional function + +```cpp +void beginStreamCPU(edm::StreamID id); +``` + +which is called at the beginning of an `edm::Stream`. Usually there is +no need to implement it, but the possibility is provided in case it is +needed for something (as the `stream::EDProducer::beginStream()` is +overridden by `HeterogeneousEDProducer`). + +#### Mandatory functions + +There is one mandatory function + +```cpp +void produceCPU(edm::HeterogeneousEvent& iEvent, edm::EventSetup const& iSetup); +``` + +which is almost equal to the usual `stream::EDProducer::produce()` +function. It is called from `HeterogeneousEDProducer::produce()` if +the device scheduler decides that the algorithm should be run on CPU +([see more details](#device-scheduling)). The first argument is +`edm::HeterogeneousEvent` instead of the usual `edm::Event` +([see more details](#heterogeneousevent)). + +The function should read its input, run the algorithm, and put the +output to the event. + +### CUDA GPU + +A CUDA GPU implementation is declared by giving +`heterogeneous::GPUCuda` as a template argument to +`heterogeneous::HeterogeneousDevices`. The following `#include` is +also needed +```cpp +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +``` +#### Optional functions + +There is one optional function + +```cpp +void beginStreamGPUCuda(edm::StreamID id, cuda::stream_t<>& cudaStream); +``` + +which is called at the beginning of an `edm::Stream`. **If the +algorithm has to allocate memory buffers for the duration of the whole +job, the recommended place is here.** The current CUDA device is set +by the framework before the call, and all asynchronous tasks should be +enqueued to the CUDA stream given as an argument. + +Currently the very same CUDA stream object will be given to the +`acquireGPUCuda()` and `produceGPUCuda()` for this `edm::Stream`. This +may change in the future though, in which case it will still be +guaranteed that the CUDA stream here will be synchronized before the +first call to `acquireGPUCuda()`. + +#### Mandatory functions + +There are two mandatory functions: + +```cpp +void acquireGPUCuda(edm::HeterogeneousEvent const& iEvent, edm::EventSetup const& iSetup, cuda::stream_t<>& cudaStream); +``` + +The `acquireGPUCuda()` is called from +`HeterogeneousEDProducer::acquire()` if the device scheduler devices +that the algorithm should be run on a CUDA GPU +([see more details](#device-scheduling)). The function should read +the necessary input (which may possibly be already on a GPU, +([see more details](#heterogeneousproduct)), and enqueue the +*asynchronous* work on the CUDA stream given as an argument. The +current CUDA deviceis set by the framework before the call. After the +`acquireGPUCuda()` returns, framework will itself enqueue a callback +function to the CUDA stream that will call +`edm::WaitingTaskWithArenaHolder::doneWaiting()` to signal to the +framework that this `EDProducer` is ready to transition to +`produce()`. + +Currently the very same CUDA stream will be given to the +`produceGPUCuda()`. + + +```cpp +void produceGPUCuda(edm::HeterogeneousEvent& iEvent, edm::EventSetup const& iSetup, cuda::stream_t<>& cudaStream); +``` + +The `produceGPUCuda()` is called from +`HeterogeneousEDProducer::produce()` if the algorithm was run on a +CUDA GPU. The function should do any necessary GPU->CPU transfers, +post-processing, and put the products to the event (for passing "GPU +products" [see here](#heterogeneousproduct)). + +#### Memory allocations + +The `cudaMalloc()` is somewhat heavy function (synchronizing the whole +device, among others). The current strategy (although not enforced by +the framework) is to allocate memory buffers at the beginning of a +job. It is recommended to do these allocations in the +`beginStreamGPUCuda()`, as it is called exactly once per job per +`stream::EDProducer` instance, and it is the earliest point in the +framework where we have the concept of `edm::Stream` so that the +framework can assign the `edm::Stream`s to CUDA devices +([see more details](#multipledevices)). + +Freeing the GPU memory can be done in the destructor as it does not +require any special support from the framework. + +#### Multiple devices + +Currently `edm::Stream`'s are statically assigned to CUDA devices in a +round-robin fashion. The assignment is done at the `beginStream()` +time before calling the `EDProducer` `beginStreamDevice()` functions. + +Technically "assigning `edm::Stream`" means that each relevant +`EDProducer` instance of that `edm::Stream` will hold a device id of +`streamId % numberOfDevices`. + +### Mock GPU + +The `GPUMock` is intended only for testing of the framework as a +something requiring a GPU-like interface but still ran on the CPU. The +documentation is left to be the code itself. diff --git a/HeterogeneousCore/Producer/interface/DeviceWrapper.h b/HeterogeneousCore/Producer/interface/DeviceWrapper.h new file mode 100644 index 0000000000000..ed711b8cf35a4 --- /dev/null +++ b/HeterogeneousCore/Producer/interface/DeviceWrapper.h @@ -0,0 +1,20 @@ +#ifndef HeterogeneousCore_Producer_DeviceWrapper_h +#define HeterogeneousCore_Producer_DeviceWrapper_h + +namespace heterogeneous { + template struct Mapping; +} + +#define DEFINE_DEVICE_WRAPPER(DEVICE, ENUM) \ + template <> \ + struct Mapping { \ + template \ + static void beginStream(DEVICE& algo, Args&&... args) { algo.call_beginStream##DEVICE(std::forward(args)...); } \ + template \ + static bool acquire(DEVICE& algo, Args&&... args) { return algo.call_acquire##DEVICE(std::forward(args)...); } \ + template \ + static void produce(DEVICE& algo, Args&&... args) { algo.call_produce##DEVICE(std::forward(args)...); } \ + static constexpr HeterogeneousDevice deviceEnum = ENUM; \ + } + +#endif diff --git a/HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h b/HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h new file mode 100644 index 0000000000000..4da113b4f15cd --- /dev/null +++ b/HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h @@ -0,0 +1,232 @@ +#ifndef HeterogeneousCore_Producer_HeterogeneousEDProducer_h +#define HeterogeneousCore_Producer_HeterogeneousEDProducer_h + +#include "FWCore/Concurrency/interface/WaitingTaskWithArenaHolder.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Utilities/interface/Exception.h" + +#include "DataFormats/Common/interface/Handle.h" + +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEvent.h" +#include "HeterogeneousCore/Producer/interface/DeviceWrapper.h" + +namespace heterogeneous { + class CPU { + public: + explicit CPU(const edm::ParameterSet& iConfig) {} + virtual ~CPU() noexcept(false); + + static void fillPSetDescription(edm::ParameterSetDescription desc) {} + + void call_beginStreamCPU(edm::StreamID id) { + beginStreamCPU(id); + } + bool call_acquireCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, edm::WaitingTaskWithArenaHolder waitingTaskHolder); + void call_produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup); + + private: + virtual void beginStreamCPU(edm::StreamID id) {}; + virtual void produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) = 0; + }; + DEFINE_DEVICE_WRAPPER(CPU, HeterogeneousDevice::kCPU); + + class GPUMock { + public: + explicit GPUMock(const edm::ParameterSet& iConfig); + virtual ~GPUMock() noexcept(false); + + static void fillPSetDescription(edm::ParameterSetDescription& desc); + + void call_beginStreamGPUMock(edm::StreamID id) { + beginStreamGPUMock(id); + } + bool call_acquireGPUMock(DeviceBitSet inputLocation, edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, edm::WaitingTaskWithArenaHolder waitingTaskHolder); + void call_produceGPUMock(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + produceGPUMock(iEvent, iSetup); + } + + private: + virtual void beginStreamGPUMock(edm::StreamID id) {}; + virtual void acquireGPUMock(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, std::function callback) = 0; + virtual void produceGPUMock(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) = 0; + + const bool enabled_; + const bool forced_; + }; + DEFINE_DEVICE_WRAPPER(GPUMock, HeterogeneousDevice::kGPUMock); +} + +namespace heterogeneous { + //////////////////// + template + struct CallBeginStream; + template + struct CallBeginStream { + template + static void call(T& ref, Args&&... args) { + // may not perfect-forward here in order to be able to forward arguments to next CallBeginStream. + Mapping::beginStream(ref, args...); + CallBeginStream::call(ref, std::forward(args)...); + } + }; + // break recursion and require CPU to be the last + template + struct CallBeginStream { + template + static void call(T& ref, Args&&... args) { + Mapping::beginStream(ref, std::forward(args)...); + } + }; + + //////////////////// + template + struct CallAcquire; + template + struct CallAcquire { + template + static void call(T& ref, const HeterogeneousProductBase *input, Args&&... args) { + bool succeeded = true; + DeviceBitSet inputLocation; + if(input) { + succeeded = input->isProductOn(Mapping::deviceEnum); + if(succeeded) { + inputLocation = input->onDevices(Mapping::deviceEnum); + } + } + if(succeeded) { + // may not perfect-forward here in order to be able to forward arguments to next CallAcquire. + succeeded = Mapping::acquire(ref, inputLocation, args...); + } + if(!succeeded) { + CallAcquire::call(ref, input, std::forward(args)...); + } + } + }; + // break recursion and require CPU to be the last + template + struct CallAcquire { + template + static void call(T& ref, const HeterogeneousProductBase *input, Args&&... args) { + Mapping::acquire(ref, std::forward(args)...); + } + }; + + //////////////////// + template + struct CallProduce; + template + struct CallProduce { + template + static void call(T& ref, edm::HeterogeneousEvent& iEvent, Args&&... args) { + if(iEvent.location().deviceType() == Mapping::deviceEnum) { + Mapping::produce(ref, iEvent, std::forward(args)...); + } + else { + CallProduce::call(ref, iEvent, std::forward(args)...); + } + } + }; + template + struct CallProduce { + template + static void call(T& ref, Args&&... args) {} + }; + + + template + class HeterogeneousDevices: public Devices... { + public: + explicit HeterogeneousDevices(const edm::ParameterSet& iConfig): Devices(iConfig)... {} + + static void fillPSetDescription(edm::ParameterSetDescription& desc) { + // The usual trick to expand the parameter pack for function call + using expander = int[]; + (void)expander {0, ((void)Devices::fillPSetDescription(desc), 1)... }; + desc.addUntracked("force", ""); + } + + void call_beginStream(edm::StreamID id) { + CallBeginStream::call(*this, id); + } + + void call_acquire(const HeterogeneousProductBase *input, + edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, + edm::WaitingTaskWithArenaHolder waitingTaskHolder) { + CallAcquire::call(*this, input, iEvent, iSetup, std::move(waitingTaskHolder)); + } + + void call_produce(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + CallProduce::call(*this, iEvent, iSetup); + } + }; +} // end namespace heterogeneous + + +template +class HeterogeneousEDProducer: public Devices, public edm::stream::EDProducer { +public: + explicit HeterogeneousEDProducer(const edm::ParameterSet& iConfig): + Devices(iConfig.getUntrackedParameter("heterogeneousEnabled_")) + {} + ~HeterogeneousEDProducer() override = default; + +protected: + edm::EDGetTokenT consumesHeterogeneous(const edm::InputTag& tag) { + tokens_.push_back(this->template consumes(tag)); + return tokens_.back(); + } + + static void fillPSetDescription(edm::ParameterSetDescription& desc) { + edm::ParameterSetDescription nested; + Devices::fillPSetDescription(nested); + desc.addUntracked("heterogeneousEnabled_", nested); + } + +private: + void beginStream(edm::StreamID id) override { + Devices::call_beginStream(id); + } + + void acquire(const edm::Event& iEvent, const edm::EventSetup& iSetup, edm::WaitingTaskWithArenaHolder waitingTaskHolder) final { + const HeterogeneousProductBase *input = nullptr; + + std::vector products; + for(const auto& token: tokens_) { + edm::Handle handle; + iEvent.getByToken(token, handle); + if(handle.isValid()) { + // let the user acquire() code to deal with missing products + // (and hope they don't mess up the scheduling!) + products.push_back(handle.product()); + } + } + if(!products.empty()) { + // TODO: check all inputs, not just the first one + input = products[0]->getBase(); + } + + auto eventWrapper = edm::HeterogeneousEvent(&iEvent, &algoExecutionLocation_); + Devices::call_acquire(input, eventWrapper, iSetup, std::move(waitingTaskHolder)); + } + + void produce(edm::Event& iEvent, const edm::EventSetup& iSetup) final { + if(algoExecutionLocation_.deviceType() == HeterogeneousDeviceId::kInvalidDevice) { + // TODO: eventually fall back to CPU + throw cms::Exception("LogicError") << "Trying to produce(), but algorithm was not executed successfully anywhere?"; + } + auto eventWrapper = edm::HeterogeneousEvent(&iEvent, &algoExecutionLocation_); + Devices::call_produce(eventWrapper, iSetup); + } + + std::vector > tokens_; + HeterogeneousDeviceId algoExecutionLocation_; +}; + +#endif + + + diff --git a/HeterogeneousCore/Producer/interface/HeterogeneousEvent.h b/HeterogeneousCore/Producer/interface/HeterogeneousEvent.h new file mode 100644 index 0000000000000..f461450c7631e --- /dev/null +++ b/HeterogeneousCore/Producer/interface/HeterogeneousEvent.h @@ -0,0 +1,134 @@ +#ifndef HeterogeneousCore_Producer_HeterogeneousEvent_h +#define HeterogeneousCore_Producer_HeterogeneousEvent_h + +#include "FWCore/Framework/interface/Event.h" +#include "DataFormats/Common/interface/Handle.h" + +#include "HeterogeneousCore/Product/interface/HeterogeneousDeviceId.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +namespace edm { + class HeterogeneousEvent { + public: + HeterogeneousEvent(const edm::Event *event, HeterogeneousDeviceId *location): constEvent_(event), location_(location) {} + HeterogeneousEvent(edm::Event *event, HeterogeneousDeviceId *location): event_(event), constEvent_(event), location_(location) {} + + // Accessors to members + edm::Event& event() { return *event_; } + const edm::Event& event() const { return *constEvent_; } + + // For the "acquire" phase, the "input location" is used for + // scheduling, while "location" is used to set the location where + // the algorithm was run + void setInputLocation(HeterogeneousDeviceId location) { inputLocation_ = location; } + + std::function locationSetter() { + return [loc=location_](HeterogeneousDeviceId location) { *loc = location; }; + } + const HeterogeneousDeviceId& location() const { return *location_; } + + // Delegate to edm::Event + auto id() const { return constEvent_->id(); } + auto streamID() const { return constEvent_->streamID(); } + + + template + bool getByToken(const Token& token, edm::Handle& handle) const { + edm::Handle tmp; + constEvent_->getByToken(token, tmp); + if(tmp.failedToGet()) { + auto copy = tmp.whyFailedFactory(); + handle = edm::Handle(std::move(copy)); + return false; + } + if(tmp.isValid()) { +#define CASE(ENUM) case ENUM: this->template get(handle, tmp, 0); break + switch(inputLocation_.deviceType()) { + CASE(HeterogeneousDevice::kCPU); + CASE(HeterogeneousDevice::kGPUMock); + CASE(HeterogeneousDevice::kGPUCuda); + default: + throw cms::Exception("LogicError") << "edm::HeterogeneousEvent::getByToken(): no case statement for device " << static_cast(location().deviceType()) << ". If you are calling getByToken() from produceX() where X != CPU, please move the call to acquireX()."; + } +#undef CASE + return true; + } + return false; + } + + // Delegate standard getByToken to edm::Event + template + bool getByToken(const Token& token, edm::Handle& handle) const { + return constEvent_->getByToken(token, handle); + } + + template + auto put(std::unique_ptr product) { + return event_->put(std::move(product)); + } + + template + auto put(std::unique_ptr product, std::string const& productInstanceName) { + return event_->put(std::move(product), productInstanceName); + } + + template + void put(std::unique_ptr product) { + assert(location().deviceType() == HeterogeneousDevice::kCPU); + event_->put(std::make_unique(Product(heterogeneous::HeterogeneousDeviceTag(), std::move(*product)))); + } + + template + auto put(std::unique_ptr product, F transferToCPU) { + std::unique_ptr prod; +#define CASE(ENUM) case ENUM: this->template make(prod, std::move(product), std::move(transferToCPU), 0); break + switch(location().deviceType()) { + CASE(HeterogeneousDevice::kGPUMock); + CASE(HeterogeneousDevice::kGPUCuda); + default: + throw cms::Exception("LogicError") << "edm::HeterogeneousEvent::put(): no case statement for device " << static_cast(location().deviceType()); + } +#undef CASE + return event_->put(std::move(prod)); + } + + private: + template + typename std::enable_if_t::value, void> + get(edm::Handle& dst, const edm::Handle& src, int) const { + const auto& concrete = src->get(); + const auto& provenance = src.provenance(); + dst = edm::Handle(&(concrete.template getProduct()), provenance); + } + template + void get(edm::Handle& dst, const edm::Handle& src, long) const { + throw cms::Exception("Assert") << "Invalid call to get, Device " << static_cast(Device) + << " Product " << typeid(Product).name() + << " Type " << typeid(Type).name() + << " CanGet::FromType " << typeid(typename Product::template CanGet::FromType).name() + << " CanGet::value " << Product::template CanGet::value; + } + + template + typename std::enable_if_t::value, void> + make(std::unique_ptr& ret, std::unique_ptr product, F transferToCPU, int) { + ret = std::make_unique(Product(heterogeneous::HeterogeneousDeviceTag(), + std::move(*product), location(), std::move(transferToCPU))); + } + template + void make(std::unique_ptr& ret, std::unique_ptr product, F transferToCPU, long) { + throw cms::Exception("Assert") << "Invalid call to make, Device " << static_cast(Device) + << " Product " << typeid(Product).name() + << " Type " << typeid(Type).name() + << " CanPut::ToType " << typeid(typename Product::template CanPut::ToType).name() + << " CanPut::value " << Product::template CanPut::value; + } + + edm::Event *event_ = nullptr; + const edm::Event *constEvent_ = nullptr; + HeterogeneousDeviceId inputLocation_; + HeterogeneousDeviceId *location_ = nullptr; + }; +} // end namespace edm + +#endif diff --git a/HeterogeneousCore/Producer/src/HeterogeneousEDProducer.cc b/HeterogeneousCore/Producer/src/HeterogeneousEDProducer.cc new file mode 100644 index 0000000000000..74bce0c8e89a9 --- /dev/null +++ b/HeterogeneousCore/Producer/src/HeterogeneousEDProducer.cc @@ -0,0 +1,70 @@ +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" + +#include "FWCore/MessageLogger/interface/MessageLogger.h" + +#include +#include +#include +#include + +namespace heterogeneous { + CPU::~CPU() noexcept(false) {} + + bool CPU::call_acquireCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, edm::WaitingTaskWithArenaHolder waitingTaskHolder) { + // There is no need for acquire in CPU, everything can be done in produceCPU(). + iEvent.locationSetter()(HeterogeneousDeviceId(HeterogeneousDevice::kCPU)); + waitingTaskHolder.doneWaiting(nullptr); + return true; + } + + void CPU::call_produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + // For CPU we set the heterogeneous input location for produce, because there is no acquire + // For other devices this probably doesn't make sense, because the device code is supposed to be launched from acquire. + iEvent.setInputLocation(HeterogeneousDeviceId(HeterogeneousDevice::kCPU, 0)); + produceCPU(iEvent, iSetup); + } + + GPUMock::GPUMock(const edm::ParameterSet& iConfig): + enabled_(iConfig.getUntrackedParameter("GPUMock")), + forced_(iConfig.getUntrackedParameter("force") == "GPUMock") + {} + + GPUMock::~GPUMock() noexcept(false) {} + + void GPUMock::fillPSetDescription(edm::ParameterSetDescription& desc) { + desc.addUntracked("GPUMock", true); + } + + bool GPUMock::call_acquireGPUMock(DeviceBitSet inputLocation, edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, edm::WaitingTaskWithArenaHolder waitingTaskHolder) { + if(!enabled_) { + edm::LogPrint("HeterogeneousEDProducer") << "Mock GPU is not available for this module (disabled in configuration)"; + return false; + } + + if(!forced_) { + // Decide randomly whether to run on GPU or CPU to simulate scheduler decisions + std::random_device r; + std::mt19937 gen(r()); + auto dist1 = std::uniform_int_distribution<>(0, 3); // simulate GPU (in)availability + if(dist1(gen) == 0) { + edm::LogPrint("HeterogeneousEDProducer") << "Mock GPU is not available (by chance)"; + return false; + } + } + + try { + iEvent.setInputLocation(HeterogeneousDeviceId(HeterogeneousDevice::kGPUMock, 0)); + acquireGPUMock(iEvent, iSetup, + [waitingTaskHolder, // copy needed for the catch block + locationSetter=iEvent.locationSetter(), + location=&(iEvent.location()) + ]() mutable { + locationSetter(HeterogeneousDeviceId(HeterogeneousDevice::kGPUMock, 0)); + waitingTaskHolder.doneWaiting(nullptr); + }); + } catch(...) { + waitingTaskHolder.doneWaiting(std::current_exception()); + } + return true; + } +} diff --git a/HeterogeneousCore/Producer/test/BuildFile.xml b/HeterogeneousCore/Producer/test/BuildFile.xml new file mode 100644 index 0000000000000..4d56079ce3f87 --- /dev/null +++ b/HeterogeneousCore/Producer/test/BuildFile.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HeterogeneousCore/Producer/test/TestGPUConcurrency.cc b/HeterogeneousCore/Producer/test/TestGPUConcurrency.cc new file mode 100644 index 0000000000000..3c959f48f9d68 --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestGPUConcurrency.cc @@ -0,0 +1,46 @@ +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "TestGPUConcurrency.h" +#include "TestGPUConcurrencyAlgo.h" + +TestGPUConcurrency::TestGPUConcurrency(edm::ParameterSet const& config): + HeterogeneousEDProducer(config), + blocks_(config.getParameter("blocks")), + threads_(config.getParameter("threads")), + sleep_(config.getParameter("sleep")) +{ +} + +void TestGPUConcurrency::fillDescriptions(edm::ConfigurationDescriptions& descriptions) +{ + edm::ParameterSetDescription desc; + HeterogeneousEDProducer::fillPSetDescription(desc); + desc.add("blocks", 100); + desc.add("threads", 256); + desc.add("sleep", 1000000); + descriptions.add("testGPUConcurrency", desc); +} + +void TestGPUConcurrency::beginStreamGPUCuda(edm::StreamID streamId, cuda::stream_t<>& cudaStream) +{ + algo_ = new TestGPUConcurrencyAlgo(blocks_, threads_, sleep_); +} + +void TestGPUConcurrency::acquireGPUCuda(const edm::HeterogeneousEvent& event, const edm::EventSetup& setup, cuda::stream_t<>& cudaStream) +{ + algo_->kernelWrapper(cudaStream.id()); +} + +void TestGPUConcurrency::produceCPU(edm::HeterogeneousEvent& event, const edm::EventSetup& setup) +{ +} + +void TestGPUConcurrency::produceGPUCuda(edm::HeterogeneousEvent& event, const edm::EventSetup& setup, cuda::stream_t<>& cudaStream) +{ +} + +#include "FWCore/Framework/interface/MakerMacros.h" +DEFINE_FWK_MODULE(TestGPUConcurrency); diff --git a/HeterogeneousCore/Producer/test/TestGPUConcurrency.h b/HeterogeneousCore/Producer/test/TestGPUConcurrency.h new file mode 100644 index 0000000000000..9ab9daf566c4d --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestGPUConcurrency.h @@ -0,0 +1,44 @@ +#ifndef HeterogeneousCore_Producer_test_TestGPUConcurrency_h +#define HeterogeneousCore_Producer_test_TestGPUConcurrency_h + +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" +//#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +class TestGPUConcurrencyAlgo; + +/** + * The purpose of this test is to demonstrate running multiple kernels concurrently on a GPU, + * associated to different framework streams on he CPU. + */ +class TestGPUConcurrency: public HeterogeneousEDProducer > { +public: + explicit TestGPUConcurrency(edm::ParameterSet const& config); + ~TestGPUConcurrency() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + using OutputType = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct>; + + void beginStreamGPUCuda(edm::StreamID streamId, cuda::stream_t<>& cudaStream) override; + void acquireGPUCuda(const edm::HeterogeneousEvent& event, const edm::EventSetup& setup, cuda::stream_t<>& cudaStream) override; + void produceGPUCuda(edm::HeterogeneousEvent& event, const edm::EventSetup& setup, cuda::stream_t<>& cudaStream) override; + void produceCPU(edm::HeterogeneousEvent& event, const edm::EventSetup& setup) override; + +// GPU code +private: + TestGPUConcurrencyAlgo * algo_; + +// data members +private: + unsigned int blocks_; + unsigned int threads_; + unsigned int sleep_; +}; + +#endif // HeterogeneousCore_Producer_test_TestGPUConcurrency_h diff --git a/HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.cu b/HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.cu new file mode 100644 index 0000000000000..a167e229a76de --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.cu @@ -0,0 +1,17 @@ +#include +#include + +#include "TestGPUConcurrencyAlgo.h" + +__global__ +void kernel(uint32_t sleep) { + volatile int sum = 0; + auto index = threadIdx.x + blockDim.x*blockIdx.x; + if(index < 32) + for (uint32_t i = 0; i < sleep; ++i) + sum += i; +} + +void TestGPUConcurrencyAlgo::kernelWrapper(cudaStream_t stream) const { + kernel<<>>(sleep_); +} diff --git a/HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.h b/HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.h new file mode 100644 index 0000000000000..d171332fa5c87 --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestGPUConcurrencyAlgo.h @@ -0,0 +1,23 @@ +#ifndef HeterogeneousCore_Producer_test_TestGPUConcurrencyAlgo_h +#define HeterogeneousCore_Producer_test_TestGPUConcurrencyAlgo_h + +#include + +class TestGPUConcurrencyAlgo { +public: + TestGPUConcurrencyAlgo(unsigned int blocks, unsigned int threads, unsigned int sleep) : + blocks_(blocks), + threads_(threads), + sleep_(sleep) + { } + + void kernelWrapper(cudaStream_t stream) const; + +// data members +private: + unsigned int blocks_; + unsigned int threads_; + unsigned int sleep_; +}; + +#endif // HeterogeneousCore_Producer_test_TestGPUConcurrencyAlgo_h diff --git a/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerAnalyzer.cc b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerAnalyzer.cc new file mode 100644 index 0000000000000..18f3e4437eb80 --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerAnalyzer.cc @@ -0,0 +1,56 @@ +#include "FWCore/Framework/interface/global/EDAnalyzer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" + +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/Utilities/interface/transform.h" + +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +#include + +class TestHeterogeneousEDProducerAnalyzer: public edm::global::EDAnalyzer<> { +public: + explicit TestHeterogeneousEDProducerAnalyzer(edm::ParameterSet const& iConfig); + ~TestHeterogeneousEDProducerAnalyzer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void analyze(edm::StreamID streamID, const edm::Event& iEvent, const edm::EventSetup& iSetup) const override; + + using InputType = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct>>; + std::string label_; + std::vector> srcTokens_; +}; + +TestHeterogeneousEDProducerAnalyzer::TestHeterogeneousEDProducerAnalyzer(const edm::ParameterSet& iConfig): + label_(iConfig.getParameter("@module_label")), + srcTokens_(edm::vector_transform(iConfig.getParameter >("src"), + [this](const edm::InputTag& tag) { + return consumes(tag); + })) +{} + +void TestHeterogeneousEDProducerAnalyzer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add>("src", std::vector{}); + descriptions.add("testHeterogeneousEDProducerAnalyzer", desc); +} + +void TestHeterogeneousEDProducerAnalyzer::analyze(edm::StreamID streamID, const edm::Event& iEvent, const edm::EventSetup& iSetup) const { + edm::Handle hinput; + int inp=0; + for(const auto& token: srcTokens_) { + iEvent.getByToken(token, hinput); + edm::LogPrint("TestHeterogeneousEDProducerAnalyzer") << "Analyzer event " << iEvent.id().event() + << " stream " << streamID + << " label " << label_ + << " coll " << inp + << " result " << hinput->get().getProduct(); + ++inp; + } +} + +DEFINE_FWK_MODULE(TestHeterogeneousEDProducerAnalyzer); diff --git a/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPU.cc b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPU.cc new file mode 100644 index 0000000000000..7ee2e07b397ca --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPU.cc @@ -0,0 +1,156 @@ +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" + +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" + +#include "FWCore/ParameterSet/interface/ParameterSet.h" + +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +#include "TestHeterogeneousEDProducerGPUHelpers.h" + +#include +#include +#include + +#include +#include + +/** + * The purpose of this test is to demonstrate the following + * - EDProducer implementing an algorithm for CPU and a CUDA GPU + * - How to initialize the GPU algorithm and make once-per-job-per-stream allocations on the device + * - How to read heterogeneous product from event + * - How to write heterogeneous product to event + * * Especially pointers to device memory + */ +class TestHeterogeneousEDProducerGPU: public HeterogeneousEDProducer > { +public: + explicit TestHeterogeneousEDProducerGPU(edm::ParameterSet const& iConfig); + ~TestHeterogeneousEDProducerGPU() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + using OutputType = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct>; + + void beginStreamGPUCuda(edm::StreamID streamId, cuda::stream_t<>& cudaStream) override; + + void acquireGPUCuda(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) override; + + void produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) override; + void produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) override; + + std::string label_; + edm::EDGetTokenT srcToken_; + + // GPU stuff + std::unique_ptr gpuAlgo_; + TestHeterogeneousEDProducerGPUTask::ResultType gpuOutput_; +}; + +TestHeterogeneousEDProducerGPU::TestHeterogeneousEDProducerGPU(edm::ParameterSet const& iConfig): + HeterogeneousEDProducer(iConfig), + label_(iConfig.getParameter("@module_label")) +{ + auto srcTag = iConfig.getParameter("src"); + if(!srcTag.label().empty()) { + srcToken_ = consumesHeterogeneous(srcTag); + } + + produces(); +} + +void TestHeterogeneousEDProducerGPU::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag()); + HeterogeneousEDProducer::fillPSetDescription(desc); + descriptions.add("testHeterogeneousEDProducerGPU", desc); +} + +void TestHeterogeneousEDProducerGPU::beginStreamGPUCuda(edm::StreamID streamId, cuda::stream_t<>& cudaStream) { + edm::Service cs; + + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label_ << " TestHeterogeneousEDProducerGPU::beginStreamGPUCuda begin stream " << streamId << " device " << cs->getCurrentDevice(); + + gpuAlgo_ = std::make_unique(); + + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label_ << " TestHeterogeneousEDProducerGPU::beginStreamGPUCuda end stream " << streamId << " device " << cs->getCurrentDevice(); +} + +void TestHeterogeneousEDProducerGPU::acquireGPUCuda(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) { + edm::Service cs; + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label_ << " TestHeterogeneousEDProducerGPU::acquireGPUCuda begin event " << iEvent.id().event() << " stream " << iEvent.streamID() << " device " << cs->getCurrentDevice(); + + gpuOutput_.first.reset(); + gpuOutput_.second.reset(); + + TestHeterogeneousEDProducerGPUTask::ResultTypeRaw input = std::make_pair(nullptr, nullptr); + if(!srcToken_.isUninitialized()) { + edm::Handle hin; + iEvent.getByToken(srcToken_, hin); + input = *hin; + } + + gpuOutput_ = gpuAlgo_->runAlgo(label_, 0, input, cudaStream); + + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label_ << " TestHeterogeneousEDProducerGPU::acquireGPUCuda end event " << iEvent.id().event() << " stream " << iEvent.streamID() << " device " << cs->getCurrentDevice(); +} + +void TestHeterogeneousEDProducerGPU::produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + edm::LogPrint("TestHeterogeneousEDProducerGPU") << label_ << " TestHeterogeneousEDProducerGPU::produceCPU begin event " << iEvent.id().event() << " stream " << iEvent.streamID(); + + unsigned int input = 0; + if(!srcToken_.isUninitialized()) { + edm::Handle hin; + iEvent.getByToken(srcToken_, hin); + input = *hin; + } + + std::random_device r; + std::mt19937 gen(r()); + auto dist = std::uniform_real_distribution<>(1.0, 3.0); + auto dur = dist(gen); + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " Task (CPU) for event " << iEvent.id().event() << " in stream " << iEvent.streamID() << " will take " << dur << " seconds"; + std::this_thread::sleep_for(std::chrono::seconds(1)*dur); + + const unsigned int output = input + iEvent.streamID()*100 + iEvent.id().event(); + + iEvent.put(std::make_unique(output)); + + edm::LogPrint("TestHeterogeneousEDProducerGPU") << label_ << " TestHeterogeneousEDProducerGPU::produceCPU end event " << iEvent.id().event() << " stream " << iEvent.streamID() << " result " << output; +} + +void TestHeterogeneousEDProducerGPU::produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) { + edm::Service cs; + edm::LogPrint("TestHeterogeneousEDProducerGPU") << label_ << " TestHeterogeneousEDProducerGPU::produceGPUCuda begin event " << iEvent.id().event() << " stream " << iEvent.streamID() << " device " << cs->getCurrentDevice(); + + gpuAlgo_->release(label_, cudaStream); + iEvent.put(std::make_unique(gpuOutput_.first.get(), gpuOutput_.second.get()), + [this, eventId=iEvent.event().id().event(), streamId=iEvent.event().streamID(), + dev=cs->getCurrentDevice(), &cudaStream + ](const TestHeterogeneousEDProducerGPUTask::ResultTypeRaw& src, unsigned int& dst) { + // TODO: try to abstract both the current device setting and the delivery of cuda::stream to this function + // It needs some further thought so I leave it now as it is + // Maybe "per-thread default stream" would help as they are regular CUDA streams (wrt. to the default stream)? + // Or not, because the current device has to be set correctly. + // Maybe we should initiate the transfer in all cases? + cuda::device::current::scoped_override_t<> setDeviceForThisScope(dev); + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label_ << " Copying from GPU to CPU for event " << eventId << " in stream " << streamId; + dst = TestHeterogeneousEDProducerGPUTask::getResult(src, cudaStream); + }); + + // If, for any reason, you want to disable the automatic GPU->CPU transfer, pass heterogeneous::DisableTransfer{} insteads of the function, i.e. + //iEvent.put(std::make_unique(gpuOutput_.first.get(), gpuOutput_.second.get()), heterogeneous::DisableTransfer{}); + + edm::LogPrint("TestHeterogeneousEDProducerGPU") << label_ << " TestHeterogeneousEDProducerGPU::produceGPUCuda end event " << iEvent.id().event() << " stream " << iEvent.streamID() << " device " << cs->getCurrentDevice(); +} + +DEFINE_FWK_MODULE(TestHeterogeneousEDProducerGPU); diff --git a/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.cu b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.cu new file mode 100644 index 0000000000000..01107a34cb8d4 --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.cu @@ -0,0 +1,203 @@ +#include +#include +#include + +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "TestHeterogeneousEDProducerGPUHelpers.h" + +// +// Vector Addition Kernel +// +namespace { + template + __global__ + void vectorAdd(const T *a, const T *b, T *c, int numElements) { + int i = blockIdx.x * blockDim.x + threadIdx.x; + if(i < numElements) { c[i] = a[i] + b[i]; } + } + + template + __global__ + void vectorProd(const T *a, const T *b, T *c, int numElements) { + int row = blockIdx.y*blockDim.y + threadIdx.y; + int col = blockIdx.x*blockDim.x + threadIdx.x; + + if(row < numElements && col < numElements) { + c[row*numElements + col] = a[row]*b[col]; + } + } + + template + __global__ + void matrixMul(const T *a, const T *b, T *c, int numElements) { + int row = blockIdx.y*blockDim.y + threadIdx.y; + int col = blockIdx.x*blockDim.x + threadIdx.x; + + if(row < numElements && col < numElements) { + T tmp = 0; + for(int i=0; i + __global__ + void matrixMulVector(const T *a, const T *b, T *c, int numElements) { + int row = blockIdx.y*blockDim.y + threadIdx.y; + + if(row < numElements) { + T tmp = 0; + for(int i=0; i(NUM_VALUES); + auto h_b = cuda::memory::host::make_unique(NUM_VALUES); + auto h_c = cuda::memory::host::make_unique(NUM_VALUES); + + for (auto i=0; i(current_device, NUM_VALUES); + auto d_b = cuda::memory::device::make_unique(current_device, NUM_VALUES); + auto d_c = cuda::memory::device::make_unique(current_device, NUM_VALUES); + + cuda::memory::async::copy(d_a.get(), h_a.get(), NUM_VALUES*sizeof(int), stream.id()); + cuda::memory::async::copy(d_b.get(), h_b.get(), NUM_VALUES*sizeof(int), stream.id()); + + int threadsPerBlock {256}; + int blocksPerGrid = (NUM_VALUES + threadsPerBlock - 1) / threadsPerBlock; + + vectorAdd<<>>(d_a.get(), d_b.get(), d_c.get(), NUM_VALUES); + cudaCheck(cudaGetLastError()); + /* + // doesn't work with header-only? + cuda::launch(vectorAdd, {blocksPerGrid, threadsPerBlock}, + d_a.get(), d_b.get(), d_c.get(), NUM_VALUES); + */ + + cuda::memory::async::copy(h_c.get(), d_c.get(), NUM_VALUES*sizeof(int), stream.id()); + + stream.synchronize(); + + int ret = 0; + for (auto i=0; i<10; i++) { + ret += h_c[i]; + } + + return ret; +} + +namespace { + constexpr int NUM_VALUES = 10000; +} + +TestHeterogeneousEDProducerGPUTask::TestHeterogeneousEDProducerGPUTask() { + h_a = cuda::memory::host::make_unique(NUM_VALUES); + h_b = cuda::memory::host::make_unique(NUM_VALUES); + + auto current_device = cuda::device::current::get(); + d_b = cuda::memory::device::make_unique(current_device, NUM_VALUES); + + d_ma = cuda::memory::device::make_unique(current_device, NUM_VALUES*NUM_VALUES); + d_mb = cuda::memory::device::make_unique(current_device, NUM_VALUES*NUM_VALUES); + d_mc = cuda::memory::device::make_unique(current_device, NUM_VALUES*NUM_VALUES); +} + +TestHeterogeneousEDProducerGPUTask::ResultType +TestHeterogeneousEDProducerGPUTask::runAlgo(const std::string& label, int input, const ResultTypeRaw inputArrays, cuda::stream_t<>& stream) { + // First make the sanity check + if(inputArrays.first != nullptr) { + auto h_check = std::make_unique(NUM_VALUES); + cuda::memory::copy(h_check.get(), inputArrays.first, NUM_VALUES*sizeof(float)); + for(int i=0; i(current_device, NUM_VALUES); + auto d_c = cuda::memory::device::make_unique(current_device, NUM_VALUES); + if(inputArrays.second != nullptr) { + d_d = cuda::memory::device::make_unique(current_device, NUM_VALUES); + } + + // Create stream + cuda::memory::async::copy(d_a.get(), h_a.get(), NUM_VALUES*sizeof(float), stream.id()); + cuda::memory::async::copy(d_b.get(), h_b.get(), NUM_VALUES*sizeof(float), stream.id()); + + int threadsPerBlock {32}; + int blocksPerGrid = (NUM_VALUES + threadsPerBlock - 1) / threadsPerBlock; + + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label << " GPU launching kernels device " << current_device.id() << " CUDA stream " << stream.id(); + vectorAdd<<>>(d_a.get(), d_b.get(), d_c.get(), NUM_VALUES); + cudaCheck(cudaGetLastError()); + if(inputArrays.second != nullptr) { + vectorAdd<<>>(inputArrays.second, d_c.get(), d_d.get(), NUM_VALUES); + cudaCheck(cudaGetLastError()); + std::swap(d_c, d_d); + } + + dim3 threadsPerBlock3{NUM_VALUES, NUM_VALUES}; + dim3 blocksPerGrid3{1,1}; + if(NUM_VALUES*NUM_VALUES > 32) { + threadsPerBlock3.x = 32; + threadsPerBlock3.y = 32; + blocksPerGrid3.x = ceil(double(NUM_VALUES)/double(threadsPerBlock3.x)); + blocksPerGrid3.y = ceil(double(NUM_VALUES)/double(threadsPerBlock3.y)); + } + vectorProd<<>>(d_a.get(), d_b.get(), d_ma.get(), NUM_VALUES); + cudaCheck(cudaGetLastError()); + vectorProd<<>>(d_a.get(), d_c.get(), d_mb.get(), NUM_VALUES); + cudaCheck(cudaGetLastError()); + matrixMul<<>>(d_ma.get(), d_mb.get(), d_mc.get(), NUM_VALUES); + cudaCheck(cudaGetLastError()); + matrixMulVector<<>>(d_mc.get(), d_b.get(), d_c.get(), NUM_VALUES); + cudaCheck(cudaGetLastError()); + + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label << " GPU kernels launched, returning return pointer device " << current_device.id() << " CUDA stream " << stream.id(); + return std::make_pair(std::move(d_a), std::move(d_c)); +} + +void TestHeterogeneousEDProducerGPUTask::release(const std::string& label, cuda::stream_t<>& stream) { + // any way to automate the release? + edm::LogPrint("TestHeterogeneousEDProducerGPU") << " " << label << " GPU releasing temporary memory device " << cuda::stream::associated_device(stream.id()) << " CUDA stream " << stream.id(); + d_d.reset(); +} + +int TestHeterogeneousEDProducerGPUTask::getResult(const ResultTypeRaw& d_ac, cuda::stream_t<>& stream) { + auto h_c = cuda::memory::host::make_unique(NUM_VALUES); + cuda::memory::async::copy(h_c.get(), d_ac.second, NUM_VALUES*sizeof(int), stream.id()); + stream.synchronize(); + + float ret = 0; + for (auto i=0; i(ret); +} + diff --git a/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.h b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.h new file mode 100644 index 0000000000000..5c12b0918c5b0 --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUHelpers.h @@ -0,0 +1,44 @@ +#ifndef HeterogeneousCore_Producer_TestHeterogneousEDProducerGPUHelpers +#define HeterogeneousCore_Producer_TestHeterogneousEDProducerGPUHelpers + +#include + +#include +#include +#include +#include + +int TestHeterogeneousEDProducerGPUHelpers_simple_kernel(int input); + +class TestHeterogeneousEDProducerGPUTask { +public: + TestHeterogeneousEDProducerGPUTask(); + ~TestHeterogeneousEDProducerGPUTask() = default; + + using Ptr = cuda::memory::device::unique_ptr; + using PtrRaw = Ptr::pointer; + + using ResultType = std::pair; + using ResultTypeRaw = std::pair; + using ConstResultTypeRaw = std::pair; + + ResultType runAlgo(const std::string& label, int input, const ResultTypeRaw inputArrays, cuda::stream_t<>& stream); + void release(const std::string& label, cuda::stream_t<>& stream); + static int getResult(const ResultTypeRaw& d_ac, cuda::stream_t<>& stream); + +private: + std::unique_ptr> streamPtr; + + // stored for the job duration + cuda::memory::host::unique_ptr h_a; + cuda::memory::host::unique_ptr h_b; + cuda::memory::device::unique_ptr d_b; + cuda::memory::device::unique_ptr d_ma; + cuda::memory::device::unique_ptr d_mb; + cuda::memory::device::unique_ptr d_mc; + + // temporary storage, need to be somewhere to allow async execution + cuda::memory::device::unique_ptr d_d; +}; + +#endif diff --git a/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUMock.cc b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUMock.cc new file mode 100644 index 0000000000000..8be516ccbba8e --- /dev/null +++ b/HeterogeneousCore/Producer/test/TestHeterogeneousEDProducerGPUMock.cc @@ -0,0 +1,164 @@ +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" + +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" + +#include "FWCore/ParameterSet/interface/ParameterSet.h" + +#include "tbb/concurrent_vector.h" + +#include +#include +#include +#include + +/** + * The purpose of this test is to demonstrate the following + * - EDProducer implementing an algorithm for CPU and a (mock GPU) device + * * The mock device exercises all the structures without a need for actual device + * - How to read heterogeneous product from event + * - How to read normal product from event + * - How to write heterogeneous product to event + */ +class TestHeterogeneousEDProducerGPUMock: public HeterogeneousEDProducer > { +public: + explicit TestHeterogeneousEDProducerGPUMock(edm::ParameterSet const& iConfig); + ~TestHeterogeneousEDProducerGPUMock() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + using OutputType = HeterogeneousProductImpl, + heterogeneous::GPUMockProduct >; + + std::string label_; + edm::EDGetTokenT srcToken_; + edm::EDGetTokenT srcIntToken_; + + // hack for GPU mock + tbb::concurrent_vector > pendingFutures; + + // simulating GPU memory + unsigned int gpuOutput_; + + void acquireGPUMock(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, std::function callback) override; + + void produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) override; + void produceGPUMock(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) override; +}; + +TestHeterogeneousEDProducerGPUMock::TestHeterogeneousEDProducerGPUMock(edm::ParameterSet const& iConfig): + HeterogeneousEDProducer(iConfig), + label_(iConfig.getParameter("@module_label")) +{ + auto srcTag = iConfig.getParameter("src"); + if(!srcTag.label().empty()) { + srcToken_ = consumesHeterogeneous(srcTag); + } + auto srcIntTag = iConfig.getParameter("srcInt"); + if(!srcIntTag.label().empty()) { + srcIntToken_ = consumes(srcIntTag); + } + + produces(); + produces(); + produces("foo"); +} + +void TestHeterogeneousEDProducerGPUMock::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag()); + desc.add("srcInt", edm::InputTag()); + HeterogeneousEDProducer::fillPSetDescription(desc); + descriptions.add("testHeterogeneousEDProducerGPUMock", desc); +} + +void TestHeterogeneousEDProducerGPUMock::acquireGPUMock(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, std::function callback) { + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << " " << label_ << " TestHeterogeneousEDProducerGPUMock::acquireGPUMock begin event " << iEvent.id().event() << " stream " << iEvent.streamID(); + + unsigned int input = 0; + if(!srcToken_.isUninitialized()) { + edm::Handle hin; + iEvent.getByToken(srcToken_, hin); + input = *hin; + } + if(!srcIntToken_.isUninitialized()) { + edm::Handle hin; + iEvent.getByToken(srcIntToken_, hin); + input += *hin; + } + + /// GPU work + std::random_device r; + std::mt19937 gen(r()); + auto dist = std::uniform_real_distribution<>(0.1, 1.0); + auto dur = dist(gen); + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << " " << label_ << " Task (GPU) for event " << iEvent.id().event() << " in stream " << iEvent.streamID() << " will take " << dur << " seconds"; + + auto ret = std::async(std::launch::async, + [this, dur, input, + callback = std::move(callback), + eventId = iEvent.id().event(), + streamId = iEvent.streamID() + ](){ + std::this_thread::sleep_for(std::chrono::seconds(1)*dur); + gpuOutput_ = input + streamId*100 + eventId; + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << " " << label_ << " TestHeterogeneousEDProducerGPUMock::acquireGPUMock finished async for event " << eventId << " stream " << streamId; + callback(); + }); + pendingFutures.push_back(std::move(ret)); + + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << " " << label_ << " TestHeterogeneousEDProducerGPUMock::acquireGPUMock end event " << iEvent.id().event() << " stream " << iEvent.streamID(); +} + +void TestHeterogeneousEDProducerGPUMock::produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << label_ << " TestHeterogeneousEDProducerGPUMock::produceCPU begin event " << iEvent.id().event() << " stream " << iEvent.streamID(); + + unsigned int input = 0; + if(!srcToken_.isUninitialized()) { + edm::Handle hin; + iEvent.getByToken(srcToken_, hin); + input = *hin; + } + if(!srcIntToken_.isUninitialized()) { + edm::Handle hin; + iEvent.getByToken(srcIntToken_, hin); + input += *hin; + } + + std::random_device r; + std::mt19937 gen(r()); + auto dist = std::uniform_real_distribution<>(1.0, 3.0); + auto dur = dist(gen); + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << " Task (CPU) for event " << iEvent.id().event() << " in stream " << iEvent.streamID() << " will take " << dur << " seconds"; + std::this_thread::sleep_for(std::chrono::seconds(1)*dur); + + const unsigned int output = input+ iEvent.streamID()*100 + iEvent.id().event(); + + iEvent.put(std::make_unique(output)); + iEvent.put(std::make_unique(1)); + iEvent.put(std::make_unique(2), "foo"); + + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << label_ << " TestHeterogeneousEDProducerGPUMock::produceCPU end event " << iEvent.id().event() << " stream " << iEvent.streamID() << " result " << output; +} + +void TestHeterogeneousEDProducerGPUMock::produceGPUMock(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << label_ << " TestHeterogeneousEDProducerGPUMock::produceGPUMock begin event " << iEvent.id().event() << " stream " << iEvent.streamID(); + + iEvent.put(std::make_unique(gpuOutput_), + [this, + eventId = iEvent.id().event(), + streamId = iEvent.streamID() + ](const unsigned int& src, unsigned int& dst) { + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << " " << label_ << " Task (GPU) for event " << eventId << " in stream " << streamId << " copying to CPU"; + dst = src; + }); + iEvent.put(std::make_unique(2)); + + edm::LogPrint("TestHeterogeneousEDProducerGPUMock") << label_ << " TestHeterogeneousEDProducerGPUMock::produceGPUMock end event " << iEvent.id().event() << " stream " << iEvent.streamID() << " result " << gpuOutput_; +} + +DEFINE_FWK_MODULE(TestHeterogeneousEDProducerGPUMock); diff --git a/HeterogeneousCore/Producer/test/testGPUConcurrency.py b/HeterogeneousCore/Producer/test/testGPUConcurrency.py new file mode 100644 index 0000000000000..af2f83374a41a --- /dev/null +++ b/HeterogeneousCore/Producer/test/testGPUConcurrency.py @@ -0,0 +1,29 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process('RECO') + +process.load('FWCore.MessageService.MessageLogger_cfi') +process.load('HeterogeneousCore.CUDAServices.CUDAService_cfi') + +# Empty source +process.source = cms.Source("EmptySource") + +process.maxEvents = cms.untracked.PSet( + input = cms.untracked.int32(10) +) + +process.options = cms.untracked.PSet( + numberOfThreads = cms.untracked.uint32( 4 ), + numberOfStreams = cms.untracked.uint32( 4 ), +) + +# Path and EndPath definitions +from HeterogeneousCore.Producer.testGPUConcurrency_cfi import testGPUConcurrency +process.testGPU = testGPUConcurrency.clone() +process.testGPU.sleep = 1000000 +process.testGPU.blocks = 100000 +process.testGPU.threads = 256 + +process.path = cms.Path(process.testGPU) + +process.schedule = cms.Schedule(process.path) diff --git a/HeterogeneousCore/Producer/test/testGPUMock_cfg.py b/HeterogeneousCore/Producer/test/testGPUMock_cfg.py new file mode 100644 index 0000000000000..8bfcf031a53c4 --- /dev/null +++ b/HeterogeneousCore/Producer/test/testGPUMock_cfg.py @@ -0,0 +1,36 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process("Test") +process.load("FWCore.MessageService.MessageLogger_cfi") + +process.source = cms.Source("EmptySource") + +process.maxEvents = cms.untracked.PSet( input = cms.untracked.int32(10) ) + +process.options = cms.untracked.PSet( +# numberOfThreads = cms.untracked.uint32(4), + numberOfStreams = cms.untracked.uint32(0) +) + + +#process.Tracer = cms.Service("Tracer") +process.prod1 = cms.EDProducer('TestHeterogeneousEDProducerGPUMock') +process.prod2 = cms.EDProducer('TestHeterogeneousEDProducerGPUMock', + src = cms.InputTag("prod1") +) +process.prod3 = cms.EDProducer('TestHeterogeneousEDProducerGPUMock', + srcInt = cms.InputTag("prod1") +) + +#process.t = cms.Task(process.prod1, process.prod2) + +process.eca = cms.EDAnalyzer("EventContentAnalyzer", + getData = cms.untracked.bool(True), + getDataForModuleLabels = cms.untracked.vstring("producer"), + listContent = cms.untracked.bool(True), +) +process.p = cms.Path(process.prod1+process.prod2+process.prod3)#+process.eca) +#process.p.associate(process.t) + +# Example of forcing module to run a specific device for one module via configuration +#process.prod1.heterogeneousEnabled_ = cms.untracked.PSet(force = cms.untracked.string("GPUMock")) diff --git a/HeterogeneousCore/Producer/test/testGPU_cfg.py b/HeterogeneousCore/Producer/test/testGPU_cfg.py new file mode 100644 index 0000000000000..b8ce382339fbe --- /dev/null +++ b/HeterogeneousCore/Producer/test/testGPU_cfg.py @@ -0,0 +1,35 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process("Test") +process.load("FWCore.MessageService.MessageLogger_cfi") + +process.source = cms.Source("EmptySource") + +process.maxEvents = cms.untracked.PSet( input = cms.untracked.int32(10) ) + +process.options = cms.untracked.PSet( +# numberOfThreads = cms.untracked.uint32(4), + numberOfStreams = cms.untracked.uint32(0) +) + +from HeterogeneousCore.Producer.testHeterogeneousEDProducerGPU_cfi import testHeterogeneousEDProducerGPU as prod + +#process.Tracer = cms.Service("Tracer") +process.load("HeterogeneousCore.CUDAServices.CUDAService_cfi") +process.prod1 = prod.clone() +process.prod2 = prod.clone(src = "prod1") +process.prod3 = prod.clone(src = "prod1") +process.prod4 = prod.clone() +process.ana = cms.EDAnalyzer("TestHeterogeneousEDProducerAnalyzer", + src = cms.VInputTag("prod2", "prod3", "prod4") +) + +process.t = cms.Task(process.prod1, process.prod2, process.prod3, process.prod4) +process.p = cms.Path(process.ana) +process.p.associate(process.t) + +# Example of disabling CUDA device type for one module via configuration +#process.prod4.heterogeneousEnabled_.GPUCuda = False + +# Example of limiting the number of EDM streams per device +#process.CUDAService.numberOfStreamsPerDevice = 1 diff --git a/HeterogeneousCore/Product/BuildFile.xml b/HeterogeneousCore/Product/BuildFile.xml new file mode 100644 index 0000000000000..0e6323ac9009b --- /dev/null +++ b/HeterogeneousCore/Product/BuildFile.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/HeterogeneousCore/Product/interface/HeterogeneousDeviceId.h b/HeterogeneousCore/Product/interface/HeterogeneousDeviceId.h new file mode 100644 index 0000000000000..969280516a094 --- /dev/null +++ b/HeterogeneousCore/Product/interface/HeterogeneousDeviceId.h @@ -0,0 +1,45 @@ +#ifndef HeterogeneousCore_Product_HeterogeneousDeviceId_h +#define HeterogeneousCore_Product_HeterogeneousDeviceId_h + +/** + * Enumerator for heterogeneous device types + */ +enum class HeterogeneousDevice { + kCPU = 0, + kGPUMock, + kGPUCuda, + kSize +}; + +namespace heterogeneous { + template + struct HeterogeneousDeviceTag { + constexpr static HeterogeneousDevice value = Device; + }; +} + +/** + * Class to represent an identifier for a heterogeneous device. + * Contains device type and an integer identifier. + */ +class HeterogeneousDeviceId { +public: + constexpr static auto kInvalidDevice = HeterogeneousDevice::kSize; + + HeterogeneousDeviceId(): + deviceType_(kInvalidDevice), + deviceId_(0) + {} + explicit HeterogeneousDeviceId(HeterogeneousDevice device, unsigned int id=0): + deviceType_(device), deviceId_(id) + {} + + HeterogeneousDevice deviceType() const { return deviceType_; } + + unsigned int deviceId() const { return deviceId_; } +private: + HeterogeneousDevice deviceType_; + unsigned int deviceId_; +}; + +#endif diff --git a/HeterogeneousCore/Product/interface/HeterogeneousProduct.h b/HeterogeneousCore/Product/interface/HeterogeneousProduct.h new file mode 100644 index 0000000000000..c43f75a86b46f --- /dev/null +++ b/HeterogeneousCore/Product/interface/HeterogeneousProduct.h @@ -0,0 +1,338 @@ +#ifndef HeterogeneousCore_Product_HeterogeneousProduct_h +#define HeterogeneousCore_Product_HeterogeneousProduct_h + +#include "FWCore/Utilities/interface/Exception.h" + +#include "HeterogeneousCore/Product/interface/HeterogeneousDeviceId.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProductBase.h" + +#include +#include +#include +#include + +namespace heterogeneous { + template + std::string bitsetArrayToString(const T& bitsetArray) { + std::string ret; + for(const auto& bitset: bitsetArray) { + ret += bitset.to_string() + " "; + } + return ret; + } + + /** + * The *Product templates are to specify in a generic way which + * data locations and device-specific types the + * HeterogeneousProduct<> supports. + * + * Helper functions are provided to infer the type from input + * arguments to ease the construction of HeterogeneousProduct<> + * + * TODO: try to simplify... + */ + + // Mapping from *Product to HeterogeneousDevice enumerator + template struct ProductToEnum {}; + +#define DEFINE_DEVICE_PRODUCT(ENUM) \ + template \ + class ENUM##Product { \ + public: \ + using DataType = T; \ + static constexpr const HeterogeneousDevice tag = HeterogeneousDevice::k##ENUM; \ + ENUM##Product() = default; \ + ENUM##Product(T&& data): data_(std::move(data)) {} \ + const T& product() const { return data_; } \ + T& product() { return data_; } \ + private: \ + T data_; \ + }; \ + template struct ProductToEnum> { static constexpr const HeterogeneousDevice value = HeterogeneousDevice::k##ENUM; } + + DEFINE_DEVICE_PRODUCT(CPU); + DEFINE_DEVICE_PRODUCT(GPUMock); + DEFINE_DEVICE_PRODUCT(GPUCuda); +#undef DEFINE_DEVICE_PRODUCT + + // Tag class to allow disabling automatic device->CPU transfers + struct DisableTransfer {}; + + /** + * Below are various helpers + * + * TODO: move to an inner namespace (e.g. detail, impl)?, possibly to a separate file + */ + + // Empty struct for tuple defitionons + struct Empty {}; + + // Metaprogram to return the *Product type for a given enumerator if it exists in Types... pack + template + struct IfInPack; + + template + struct IfInPack { + using type = std::conditional_t::type >; + }; + template + struct IfInPack { + using type = Empty; + }; + + template + using IfInPack_t = typename IfInPack::type; + + // Metaprogram to construct the callback function type for device->CPU transfers + template + struct CallBackType { + using type = std::function; + }; + template + struct CallBackType { + using type = Empty; + }; + template + using CallBackType_t = typename CallBackType::type; + + // Metaprogram to get an element from a tuple, or Empty if out of bounds + template + struct TupleElement { + using type = Empty; + }; + template + struct TupleElement::value)>::type> { + using type = std::tuple_element_t; + }; + template + using TupleElement_t = typename TupleElement::type; + + + // Metaprogram to loop over two tuples and an array of bitsets (of + // equal length), and if any element of bitset is set to true call a + // function from one of the tuples with arguments from the second + // tuple + template + struct CallFunctionIf { + static bool call(const FunctionTuple& functionTuple, ProductTuple& productTuple, const BitSetArray& bitsetArray) { + constexpr const auto index = std::tuple_size::value - sizeMinusIndex; + if(bitsetArray[index].any()) { + const auto& func = std::get(functionTuple); + if(!func) { + throw cms::Exception("Assert") << "Attempted to call transfer-to-CPU function for device " << index << " but the std::function object is not valid!"; + } + func(std::get(productTuple).product(), std::get<0>(productTuple).product()); + return true; + } + return CallFunctionIf, sizeMinusIndex-1>::call(functionTuple, productTuple, bitsetArray); + } + }; + template + struct CallFunctionIf { + static bool call(const FunctionTuple& functionTuple, ProductTuple& productTuple, const BitSetArray& bitsetArray) { + constexpr const auto index = std::tuple_size::value - sizeMinusIndex; + return CallFunctionIf, sizeMinusIndex-1>::call(functionTuple, productTuple, bitsetArray); + } + }; + template + struct CallFunctionIf { + static bool call(const FunctionTuple& functionTuple, ProductTuple& productTuple, const BitSetArray& bitsetArray) { + return false; + } + }; + + // Metaprogram to specialize getProduct() for CPU + template + struct GetOrTransferProduct { + template + static const auto& getProduct(const FunctionTuple& functionTuple, ProductTuple& productTuple, const BitSetArray& location) { + constexpr const auto index = static_cast(device); + if(!location[index].any()) { + throw cms::Exception("LogicError") << "Called getProduct() for device " << index << " but the data is not there! Location bitfield is " << bitsetArrayToString(location); + } + return std::get(productTuple).product(); + } + }; + + template <> + struct GetOrTransferProduct { + template + static const auto& getProduct(const FunctionTuple& functionTuple, ProductTuple& productTuple, BitSetArray& location) { + constexpr const auto index = static_cast(HeterogeneousDevice::kCPU); + if(!location[index].any()) { + auto found = CallFunctionIf, std::tuple_size::value-1>::call(functionTuple, productTuple, location); + if(!found) { + throw cms::Exception("LogicError") << "Attempted to transfer data to CPU, but the data is not available anywhere! Location bitfield is " << bitsetArrayToString(location); + } + } + location[index].set(0); + return std::get(productTuple).product(); + } + }; + + // Metaprogram to return DataType or Empty + template + struct DataTypeOrEmpty { + using type = typename T::DataType; + }; + template<> + struct DataTypeOrEmpty { + using type = Empty; + }; +} + +/** + * Generic data product for holding data on CPU or a heterogeneous + * device which keeps track where the data is. Data can be + * automatically transferred from the device to CPU when data is + * requested on CPU but does not exist there (yet). + * + * TODO: + * * extend transfers to device->device (within a single device type) + */ +template +class HeterogeneousProductImpl: public HeterogeneousProductBase { + using ProductTuple = std::tuple, + heterogeneous::IfInPack_t + >; + using TransferToCPUTuple = std::tuple(HeterogeneousDevice::kGPUMock), ProductTuple>>, + heterogeneous::CallBackType_t(HeterogeneousDevice::kGPUCuda), ProductTuple>> + >; + // Some sanity checks + static_assert(std::tuple_size::value == std::tuple_size::value, "Size mismatch"); + static_assert(std::tuple_size::value == static_cast(HeterogeneousDevice::kSize), "Size mismatch"); +public: + template + struct CanGet { + using FromType = typename heterogeneous::DataTypeOrEmpty(Device), ProductTuple> >::type; + static const bool value = std::is_same::value; + }; + + template + struct CanPut { + using ToType = typename heterogeneous::DataTypeOrEmpty(Device), ProductTuple> >::type; + static const bool value = std::is_same::value; + }; + + HeterogeneousProductImpl() = default; + ~HeterogeneousProductImpl() override = default; + HeterogeneousProductImpl(HeterogeneousProductImpl&& other) { + std::lock(mutex_, other.mutex_); + std::lock_guard lk1(mutex_, std::adopt_lock); + std::lock_guard lk2(other.mutex_, std::adopt_lock); + + products_ = std::move(other.products_); + transfersToCPU_ = std::move(other.transfersToCPU_); + location_ = std::move(other.location_); + } + HeterogeneousProductImpl& operator=(HeterogeneousProductImpl&& other) { + std::lock(mutex_, other.mutex_); + std::lock_guard lk1(mutex_, std::adopt_lock); + std::lock_guard lk2(other.mutex_, std::adopt_lock); + + products_ = std::move(other.products_); + transfersToCPU_ = std::move(other.transfersToCPU_); + location_ = std::move(other.location_); + return *this; + } + + // Constructor for CPU data + template + HeterogeneousProductImpl(heterogeneous::HeterogeneousDeviceTag, D&& data) { + static_assert(Device == HeterogeneousDevice::kCPU, "This overload allows only CPU device"); + constexpr const auto index = static_cast(HeterogeneousDevice::kCPU); + std::get(products_) = std::move(data); + location_[index].set(0); + } + + /** + * Generic constructor for device data. A function to transfer the + * data to CPU has to be provided as well. + */ + template + HeterogeneousProductImpl(heterogeneous::HeterogeneousDeviceTag, D&& data, HeterogeneousDeviceId location, F transferToCPU) { + constexpr const auto index = static_cast(Device); + assert(location.deviceType() == Device); + std::get(products_) = std::move(data); + std::get(transfersToCPU_) = std::move(transferToCPU); + location_[index].set(location.deviceId()); + } + + /** + * Generic constructor for device data, but without the transfer function(!). + */ + template + HeterogeneousProductImpl(heterogeneous::HeterogeneousDeviceTag, D&& data, HeterogeneousDeviceId location, heterogeneous::DisableTransfer) { + // TODO: try to avoid code duplication between the other device data + constexpr const auto index = static_cast(Device); + assert(location.deviceType() == Device); + std::get(products_) = std::move(data); + location_[index].set(location.deviceId()); + } + + template + const auto& getProduct() const { + constexpr const auto index = static_cast(device); + static_assert(!std::is_same, + heterogeneous::Empty>::value, + "This HeterogeneousProduct does not support this type"); + + // Locking the mutex here is quite "conservative" + // Writes happen only if the "device" is CPU and the data is elsewhere + std::lock_guard lk(mutex_); + return heterogeneous::GetOrTransferProduct::getProduct(transfersToCPU_, products_, location_); + } + +private: + mutable ProductTuple products_; + TransferToCPUTuple transfersToCPU_; +}; + +/** + * The main purpose of the HeterogeneousProduct, + * HeterogeneousProductBase, HeterogeneousProductImpl<...> class + * hierarchy is to avoid the dictionary generation for the concrete + * HeterogeneousProductImpl<...>'s. + */ +class HeterogeneousProduct { +public: + HeterogeneousProduct() = default; + + template + HeterogeneousProduct(HeterogeneousProductImpl&& impl) { + impl_.reset(static_cast(new HeterogeneousProductImpl(std::move(impl)))); + } + + HeterogeneousProduct(HeterogeneousProduct&&) = default; + HeterogeneousProduct& operator=(HeterogeneousProduct&&) = default; + + ~HeterogeneousProduct() = default; + + bool isNonnull() const { return static_cast(impl_); } + bool isNull() const { return !isNonnull(); } + + const HeterogeneousProductBase *getBase() const { return impl_.get(); } + + template + const T& get() const { + if(isNull()) + throw cms::Exception("LogicError") << "HerogeneousProduct is null"; + + const auto& ref = *impl_; + if(typeid(T) != typeid(ref)) { + throw cms::Exception("LogicError") << "Trying to get HeterogeneousProductImpl " << typeid(T).name() << " but the product contains " << typeid(ref).name(); + } + return static_cast(*impl_); + } +private: + std::unique_ptr impl_; +}; + +#endif diff --git a/HeterogeneousCore/Product/interface/HeterogeneousProductBase.h b/HeterogeneousCore/Product/interface/HeterogeneousProductBase.h new file mode 100644 index 0000000000000..fcba60150c824 --- /dev/null +++ b/HeterogeneousCore/Product/interface/HeterogeneousProductBase.h @@ -0,0 +1,41 @@ +#ifndef HeterogeneousCore_Product_HeterogeneousProductBase_h +#define HeterogeneousCore_Product_HeterogeneousProductBase_h + +#include "HeterogeneousCore/Product/interface/HeterogeneousDeviceId.h" + +#include +#include +#include + +namespace heterogeneous { + constexpr const unsigned int kMaxDevices = 16; + using DeviceBitSet = std::bitset; +} + +// For type erasure to ease dictionary generation +class HeterogeneousProductBase { +public: + // TODO: Given we'll likely have the data on one or at most a couple + // of devices, storing the information in a "dense" bit pattern may + // be overkill. Maybe a "sparse" presentation would be sufficient + // and easier to deal with? + using BitSet = heterogeneous::DeviceBitSet; + using BitSetArray = std::array(HeterogeneousDevice::kSize)>; + + virtual ~HeterogeneousProductBase() = 0; + + bool isProductOn(HeterogeneousDevice loc) const { + // should this be protected with the mutex? + return location_[static_cast(loc)].any(); + } + BitSet onDevices(HeterogeneousDevice loc) const { + // should this be protected with the mutex? + return location_[static_cast(loc)]; + } + +protected: + mutable std::mutex mutex_; + mutable BitSetArray location_; +}; + +#endif diff --git a/HeterogeneousCore/Product/src/HeterogeneousProduct.cc b/HeterogeneousCore/Product/src/HeterogeneousProduct.cc new file mode 100644 index 0000000000000..98e18d8596dc0 --- /dev/null +++ b/HeterogeneousCore/Product/src/HeterogeneousProduct.cc @@ -0,0 +1,4 @@ +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +HeterogeneousProductBase::~HeterogeneousProductBase() {} + diff --git a/HeterogeneousCore/Product/src/classes.h b/HeterogeneousCore/Product/src/classes.h new file mode 100644 index 0000000000000..011e2c8f2d241 --- /dev/null +++ b/HeterogeneousCore/Product/src/classes.h @@ -0,0 +1,9 @@ +#include "DataFormats/Common/interface/Wrapper.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" +#include + +namespace { + struct dictionary { + HeterogeneousProduct hp; + }; +} diff --git a/HeterogeneousCore/Product/src/classes_def.xml b/HeterogeneousCore/Product/src/classes_def.xml new file mode 100644 index 0000000000000..3e4263b7905f6 --- /dev/null +++ b/HeterogeneousCore/Product/src/classes_def.xml @@ -0,0 +1,4 @@ + + + + diff --git a/HeterogeneousCore/Product/test/BuildFile.xml b/HeterogeneousCore/Product/test/BuildFile.xml new file mode 100644 index 0000000000000..dbcab64bc0023 --- /dev/null +++ b/HeterogeneousCore/Product/test/BuildFile.xml @@ -0,0 +1,4 @@ + + + + diff --git a/HeterogeneousCore/Product/test/testHeterogeneousProduct.cpp b/HeterogeneousCore/Product/test/testHeterogeneousProduct.cpp new file mode 100644 index 0000000000000..adf8753316fd9 --- /dev/null +++ b/HeterogeneousCore/Product/test/testHeterogeneousProduct.cpp @@ -0,0 +1,308 @@ +#include + +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +class testHeterogeneousProduct: public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(testHeterogeneousProduct); + CPPUNIT_TEST(testDefault); + CPPUNIT_TEST(testCPU); + CPPUNIT_TEST(testGPUMock); + CPPUNIT_TEST(testGPUCuda); + CPPUNIT_TEST(testGPUAll); + CPPUNIT_TEST(testMoveGPUMock); + CPPUNIT_TEST(testMoveGPUCuda); + CPPUNIT_TEST(testProduct); + CPPUNIT_TEST_SUITE_END(); +public: + void setUp() override {} + void tearDown() override {} + + void testDefault(); + void testCPU(); + void testGPUMock(); + void testGPUCuda(); + void testGPUAll(); + void testMoveGPUMock(); + void testMoveGPUCuda(); + void testProduct(); +}; + +///registration of the test so that the runner can find it +CPPUNIT_TEST_SUITE_REGISTRATION(testHeterogeneousProduct); + +void testHeterogeneousProduct::testDefault() { + HeterogeneousProductImpl, + heterogeneous::GPUMockProduct + > prod; + + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + + CPPUNIT_ASSERT_THROW(prod.getProduct(), cms::Exception); + CPPUNIT_ASSERT_THROW(prod.getProduct(), cms::Exception); +} + +void testHeterogeneousProduct::testCPU() { + HeterogeneousProductImpl, + heterogeneous::GPUMockProduct + > prod{heterogeneous::HeterogeneousDeviceTag(), 5}; + + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + + CPPUNIT_ASSERT(prod.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod.getProduct(), cms::Exception); +} + +void testHeterogeneousProduct::testGPUMock() { + HeterogeneousProductImpl, + heterogeneous::GPUMockProduct + > prod{heterogeneous::HeterogeneousDeviceTag(), 5, + HeterogeneousDeviceId(HeterogeneousDevice::kGPUMock, 0), + [](const int& src, int& dst) { dst = src; }}; + + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUMock) == true); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + + CPPUNIT_ASSERT(prod.onDevices(HeterogeneousDevice::kGPUMock)[0] == true); + CPPUNIT_ASSERT(prod.onDevices(HeterogeneousDevice::kGPUMock)[1] == false); + + CPPUNIT_ASSERT(prod.getProduct() == 5); + + // Automatic transfer + CPPUNIT_ASSERT(prod.getProduct() == 5); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUMock) == true); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + CPPUNIT_ASSERT(prod.getProduct() == 5); +} + +void testHeterogeneousProduct::testGPUCuda() { + HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct + > prod{heterogeneous::HeterogeneousDeviceTag(), 5, + HeterogeneousDeviceId(HeterogeneousDevice::kGPUCuda, 1), + [](const int& src, int& dst) { dst = src; }}; + + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUCuda) == true); + + CPPUNIT_ASSERT(prod.onDevices(HeterogeneousDevice::kGPUCuda)[0] == false); + CPPUNIT_ASSERT(prod.onDevices(HeterogeneousDevice::kGPUCuda)[1] == true); + CPPUNIT_ASSERT(prod.onDevices(HeterogeneousDevice::kGPUCuda)[2] == false); + + CPPUNIT_ASSERT(prod.getProduct() == 5); + + // Automatic transfer + CPPUNIT_ASSERT(prod.getProduct() == 5); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod.isProductOn(HeterogeneousDevice::kGPUCuda) == true); + CPPUNIT_ASSERT(prod.getProduct() == 5); +} + +void testHeterogeneousProduct::testGPUAll() { + // Data initially on CPU + HeterogeneousProductImpl, + heterogeneous::GPUMockProduct, + heterogeneous::GPUCudaProduct + > prod1{heterogeneous::HeterogeneousDeviceTag(), 5}; + + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + + CPPUNIT_ASSERT(prod1.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod1.getProduct(), cms::Exception); + CPPUNIT_ASSERT_THROW(prod1.getProduct(), cms::Exception); + + // Data initially on GPUMock + HeterogeneousProductImpl, + heterogeneous::GPUMockProduct, + heterogeneous::GPUCudaProduct + > prod2{heterogeneous::HeterogeneousDeviceTag(), 5, + HeterogeneousDeviceId(HeterogeneousDevice::kGPUMock, 0), + [](const int& src, int& dst) { dst = src; }}; + + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + + CPPUNIT_ASSERT(prod2.onDevices(HeterogeneousDevice::kGPUMock)[0] == true); + CPPUNIT_ASSERT(prod2.onDevices(HeterogeneousDevice::kGPUMock)[1] == false); + + CPPUNIT_ASSERT(prod2.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + + // Automatic transfer + CPPUNIT_ASSERT(prod2.getProduct() == 5); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + CPPUNIT_ASSERT(prod2.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + + // Data initially on GPUCuda + HeterogeneousProductImpl, + heterogeneous::GPUMockProduct, + heterogeneous::GPUCudaProduct + > prod3{heterogeneous::HeterogeneousDeviceTag(), 5, + HeterogeneousDeviceId(HeterogeneousDevice::kGPUCuda, 2), + [](const int& src, int& dst) { dst = src; }}; + + CPPUNIT_ASSERT(prod3.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod3.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod3.isProductOn(HeterogeneousDevice::kGPUCuda) == true); + + CPPUNIT_ASSERT(prod3.onDevices(HeterogeneousDevice::kGPUCuda)[0] == false); + CPPUNIT_ASSERT(prod3.onDevices(HeterogeneousDevice::kGPUCuda)[1] == false); + CPPUNIT_ASSERT(prod3.onDevices(HeterogeneousDevice::kGPUCuda)[2] == true); + CPPUNIT_ASSERT(prod3.onDevices(HeterogeneousDevice::kGPUCuda)[3] == false); + + CPPUNIT_ASSERT_THROW(prod3.getProduct(), cms::Exception); + CPPUNIT_ASSERT(prod3.getProduct() == 5); + + // Automatic transfer + CPPUNIT_ASSERT(prod3.getProduct() == 5); + CPPUNIT_ASSERT(prod3.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod3.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod3.isProductOn(HeterogeneousDevice::kGPUCuda) == true); + CPPUNIT_ASSERT_THROW(prod3.getProduct(), cms::Exception); + CPPUNIT_ASSERT(prod3.getProduct() == 5); +} + + +void testHeterogeneousProduct::testMoveGPUMock() { + // Data initially on GPUMock + using Type = HeterogeneousProductImpl, + heterogeneous::GPUMockProduct, + heterogeneous::GPUCudaProduct + >; + Type prod1{heterogeneous::HeterogeneousDeviceTag(), 5, + HeterogeneousDeviceId(HeterogeneousDevice::kGPUMock, 0), + [](const int& src, int& dst) { dst = src; }}; + Type prod2; + + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kGPUMock) == true); + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + CPPUNIT_ASSERT(prod1.onDevices(HeterogeneousDevice::kGPUMock)[0] == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + CPPUNIT_ASSERT(prod2.onDevices(HeterogeneousDevice::kGPUMock).none() == true); + + CPPUNIT_ASSERT(prod1.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod1.getProduct(), cms::Exception); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + + // move + prod2 = std::move(prod1); + + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + CPPUNIT_ASSERT(prod2.onDevices(HeterogeneousDevice::kGPUMock)[0] == true); + + CPPUNIT_ASSERT(prod2.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + + // automatic transfer + CPPUNIT_ASSERT(prod2.getProduct() == 5); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + CPPUNIT_ASSERT(prod2.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); +} + +void testHeterogeneousProduct::testMoveGPUCuda() { + // Data initially on GPUCuda + using Type = HeterogeneousProductImpl, + heterogeneous::GPUMockProduct, + heterogeneous::GPUCudaProduct + >; + Type prod1{heterogeneous::HeterogeneousDeviceTag(), 5, + HeterogeneousDeviceId(HeterogeneousDevice::kGPUCuda, 3), + [](const int& src, int& dst) { dst = src; }}; + Type prod2; + + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod1.isProductOn(HeterogeneousDevice::kGPUCuda) == true); + CPPUNIT_ASSERT(prod1.onDevices(HeterogeneousDevice::kGPUCuda)[3] == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == false); + CPPUNIT_ASSERT(prod2.onDevices(HeterogeneousDevice::kGPUCuda).none() == true); + + CPPUNIT_ASSERT_THROW(prod1.getProduct(), cms::Exception); + CPPUNIT_ASSERT(prod1.getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + + // move + prod2 = std::move(prod1); + + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == true); + CPPUNIT_ASSERT(prod2.onDevices(HeterogeneousDevice::kGPUCuda)[3] == true); + + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + CPPUNIT_ASSERT(prod2.getProduct() == 5); + + // automatic transfer + CPPUNIT_ASSERT(prod2.getProduct() == 5); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kCPU) == true); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUMock) == false); + CPPUNIT_ASSERT(prod2.isProductOn(HeterogeneousDevice::kGPUCuda) == true); + CPPUNIT_ASSERT_THROW(prod2.getProduct(), cms::Exception); + CPPUNIT_ASSERT(prod2.getProduct() == 5); +} + +void testHeterogeneousProduct::testProduct() { + using Type1 = HeterogeneousProductImpl, + heterogeneous::GPUMockProduct + >; + using Type2 = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct + >; + + Type1 data1{heterogeneous::HeterogeneousDeviceTag(), 5}; + Type2 data2{heterogeneous::HeterogeneousDeviceTag(), 10}; + + HeterogeneousProduct prod{}; + CPPUNIT_ASSERT(prod.isNull() == true); + CPPUNIT_ASSERT(prod.isNonnull() == false); + CPPUNIT_ASSERT_THROW(prod.get(), cms::Exception); + + HeterogeneousProduct prod1{std::move(data1)}; + CPPUNIT_ASSERT(prod1.isNull() == false); + CPPUNIT_ASSERT(prod1.isNonnull() == true); + CPPUNIT_ASSERT(prod1.get().getProduct() == 5); + CPPUNIT_ASSERT_THROW(prod1.get(), cms::Exception); + + HeterogeneousProduct prod2{std::move(data2)}; + CPPUNIT_ASSERT(prod2.isNull() == false); + CPPUNIT_ASSERT(prod2.isNonnull() == true); + CPPUNIT_ASSERT_THROW(prod2.get(), cms::Exception); + CPPUNIT_ASSERT(prod2.get().getProduct() == 10); + + prod1 = std::move(prod2); + CPPUNIT_ASSERT_THROW(prod1.get(), cms::Exception); + CPPUNIT_ASSERT(prod1.get().getProduct() == 10); + + prod = std::move(prod1); + CPPUNIT_ASSERT(prod.isNull() == false); + CPPUNIT_ASSERT(prod.isNonnull() == true); + CPPUNIT_ASSERT_THROW(prod.get(), cms::Exception); + CPPUNIT_ASSERT(prod.get().getProduct() == 10); +} + +#include diff --git a/RecoLocalTracker/Configuration/python/RecoLocalTracker_cff.py b/RecoLocalTracker/Configuration/python/RecoLocalTracker_cff.py index 4d25967bd803f..b75e75e000d48 100644 --- a/RecoLocalTracker/Configuration/python/RecoLocalTracker_cff.py +++ b/RecoLocalTracker/Configuration/python/RecoLocalTracker_cff.py @@ -21,6 +21,10 @@ striptrackerlocalreco = cms.Sequence(striptrackerlocalrecoTask) trackerlocalreco = cms.Sequence(trackerlocalrecoTask) +from Configuration.ProcessModifiers.gpu_cff import gpu +from RecoLocalTracker.SiPixelRecHits.siPixelRecHitHeterogeneous_cfi import siPixelRecHitHeterogeneous as _siPixelRecHitHeterogeneous +gpu.toReplaceWith(siPixelRecHitsPreSplitting, _siPixelRecHitHeterogeneous) + from RecoLocalTracker.SiPhase2Clusterizer.phase2TrackerClusterizer_cfi import * from RecoLocalTracker.Phase2TrackerRecHits.Phase2StripCPEGeometricESProducer_cfi import * diff --git a/RecoLocalTracker/SiPixelClusterizer/BuildFile.xml b/RecoLocalTracker/SiPixelClusterizer/BuildFile.xml new file mode 100644 index 0000000000000..74e76ab6ff3e2 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/BuildFile.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/RecoLocalTracker/SiPixelClusterizer/interface/PixelTrackingGPUConstants.h b/RecoLocalTracker/SiPixelClusterizer/interface/PixelTrackingGPUConstants.h new file mode 100644 index 0000000000000..ceb831d7865b6 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/interface/PixelTrackingGPUConstants.h @@ -0,0 +1,11 @@ +#ifndef RecoLocalTracker_SiPixelClusterizer_interface_PixelTrackingGPUConstants_h +#define RecoLocalTracker_SiPixelClusterizer_interface_PixelTrackingGPUConstants_h + +#include + +namespace PixelGPUConstants { + constexpr uint16_t maxNumberOfHits = 40000; // data at pileup 50 has 18300 +/- 3500 hits; 40000 is around 6 sigma away + +} + +#endif // RecoLocalTracker_SiPixelClusterizer_interface_PixelTrackingGPUConstants_h diff --git a/RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPU.h b/RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPU.h new file mode 100644 index 0000000000000..de465268f4154 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPU.h @@ -0,0 +1,27 @@ +#ifndef RecoLocalTracker_SiPixelClusterizer_SiPixelFedCablingMapGPU_h +#define RecoLocalTracker_SiPixelClusterizer_SiPixelFedCablingMapGPU_h + +namespace pixelgpudetails { + // Maximum fed for phase1 is 150 but not all of them are filled + // Update the number FED based on maximum fed found in the cabling map + constexpr unsigned int MAX_FED = 150; + constexpr unsigned int MAX_LINK = 48; // maximum links/channels for Phase 1 + constexpr unsigned int MAX_ROC = 8; + constexpr unsigned int MAX_SIZE = MAX_FED * MAX_LINK * MAX_ROC; + constexpr unsigned int MAX_SIZE_BYTE_INT = MAX_SIZE * sizeof(unsigned int); + constexpr unsigned int MAX_SIZE_BYTE_BOOL = MAX_SIZE * sizeof(unsigned char); +} + +// TODO: since this has more information than just cabling map, maybe we should invent a better name? +struct SiPixelFedCablingMapGPU { + unsigned int size = 0; + unsigned int * fed = nullptr; + unsigned int * link = nullptr; + unsigned int * roc = nullptr; + unsigned int * RawId = nullptr; + unsigned int * rocInDet = nullptr; + unsigned int * moduleId = nullptr; + unsigned char * badRocs = nullptr; +}; + +#endif diff --git a/RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h b/RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h new file mode 100644 index 0000000000000..e5e5f41053e3d --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h @@ -0,0 +1,63 @@ +#ifndef RecoLocalTracker_SiPixelClusterizer_SiPixelFedCablingMapGPUWrapper_h +#define RecoLocalTracker_SiPixelClusterizer_SiPixelFedCablingMapGPUWrapper_h + +#include "CUDADataFormats/Common/interface/device_unique_ptr.h" +#include "CUDADataFormats/Common/interface/host_unique_ptr.h" +#include "HeterogeneousCore/CUDACore/interface/CUDAESProduct.h" +#include "HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h" +#include "RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPU.h" + +#include + +#include + +class SiPixelFedCablingMap; +class TrackerGeometry; +class SiPixelQuality; + +// TODO: since this has more information than just cabling map, maybe we should invent a better name? +class SiPixelFedCablingMapGPUWrapper { +public: + SiPixelFedCablingMapGPUWrapper(SiPixelFedCablingMap const& cablingMap, + TrackerGeometry const& trackerGeom, + SiPixelQuality const *badPixelInfo); + ~SiPixelFedCablingMapGPUWrapper(); + + bool hasQuality() const { return hasQuality_; } + + // returns pointer to GPU memory + const SiPixelFedCablingMapGPU *getGPUProductAsync(cuda::stream_t<>& cudaStream) const; + + // returns pointer to GPU memory + const unsigned char *getModToUnpAllAsync(cuda::stream_t<>& cudaStream) const; + edm::cuda::device::unique_ptr getModToUnpRegionalAsync(std::set const& modules, cuda::stream_t<>& cudaStream) const; + +private: + const SiPixelFedCablingMap *cablingMap_; + std::vector> fedMap; + std::vector> linkMap; + std::vector> rocMap; + std::vector> RawId; + std::vector> rocInDet; + std::vector> moduleId; + std::vector> badRocs; + std::vector> modToUnpDefault; + unsigned int size; + bool hasQuality_; + + struct GPUData { + ~GPUData(); + SiPixelFedCablingMapGPU *cablingMapHost = nullptr; // internal pointers are to GPU, struct itself is on CPU + SiPixelFedCablingMapGPU *cablingMapDevice = nullptr; // same internal pointers as above, struct itself is on GPU + }; + CUDAESProduct gpuData_; + + struct ModulesToUnpack { + ~ModulesToUnpack(); + unsigned char *modToUnpDefault = nullptr; // pointer to GPU + }; + CUDAESProduct modToUnp_; +}; + + +#endif diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/BuildFile.xml b/RecoLocalTracker/SiPixelClusterizer/plugins/BuildFile.xml index f6958151bdb91..40a489f763397 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/BuildFile.xml +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/BuildFile.xml @@ -3,7 +3,18 @@ + + - + + + + + + + + + + diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelDigiHeterogeneousConverter.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelDigiHeterogeneousConverter.cc new file mode 100644 index 0000000000000..149aab2bad1f8 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelDigiHeterogeneousConverter.cc @@ -0,0 +1,95 @@ +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" + +#include "DataFormats/Common/interface/DetSetVector.h" +#include "DataFormats/DetId/interface/DetIdCollection.h" +#include "DataFormats/SiPixelDetId/interface/PixelFEDChannel.h" +#include "DataFormats/SiPixelDigi/interface/PixelDigi.h" +#include "DataFormats/SiPixelRawData/interface/SiPixelRawDataError.h" + +/** + * This is very stupid but currently the easiest way to go forward as + * one can't replace and EDProducer with an EDAlias in the + * configuration... + */ + +class SiPixelDigiHeterogeneousConverter: public edm::global::EDProducer<> { +public: + explicit SiPixelDigiHeterogeneousConverter(edm::ParameterSet const& iConfig); + ~SiPixelDigiHeterogeneousConverter() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const override; + + edm::EDGetTokenT > token_collection_; + edm::EDGetTokenT> token_errorcollection_; + edm::EDGetTokenT token_tkerror_detidcollection_; + edm::EDGetTokenT token_usererror_detidcollection_; + edm::EDGetTokenT> token_disabled_channelcollection_; + bool includeErrors_; +}; + +SiPixelDigiHeterogeneousConverter::SiPixelDigiHeterogeneousConverter(edm::ParameterSet const& iConfig): + includeErrors_(iConfig.getParameter("includeErrors")) +{ + auto src = iConfig.getParameter("src"); + + token_collection_ = consumes >(src); + produces >(); + if(includeErrors_) { + token_errorcollection_ = consumes>(src); + produces< edm::DetSetVector >(); + + token_tkerror_detidcollection_ = consumes(src); + produces(); + + token_usererror_detidcollection_ = consumes(edm::InputTag(src.label(), "UserErrorModules")); + produces("UserErrorModules"); + + token_disabled_channelcollection_ = consumes>(src); + produces >(); + } +} + +void SiPixelDigiHeterogeneousConverter::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("siPixelClustersPreSplitting")); + desc.add("includeErrors",true); + + descriptions.addWithDefaultLabel(desc); +} + +namespace { + template + void copy(edm::Event& iEvent, const edm::EDGetTokenT& token) { + edm::Handle h; + iEvent.getByToken(token, h); + iEvent.put(std::make_unique(*h)); + } + + template + void copy(edm::Event& iEvent, const edm::EDGetTokenT& token, const std::string& instance) { + edm::Handle h; + iEvent.getByToken(token, h); + iEvent.put(std::make_unique(*h), instance); + } +} + +void SiPixelDigiHeterogeneousConverter::produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const { + copy(iEvent, token_collection_); + if(includeErrors_) { + copy(iEvent, token_errorcollection_); + copy(iEvent, token_tkerror_detidcollection_); + copy(iEvent, token_usererror_detidcollection_, "UserErrorModules"); + copy(iEvent, token_disabled_channelcollection_); + } +} + + +DEFINE_FWK_MODULE(SiPixelDigiHeterogeneousConverter); diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelFedCablingMapGPUWrapperESProducer.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelFedCablingMapGPUWrapperESProducer.cc new file mode 100644 index 0000000000000..5ef6018280bef --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelFedCablingMapGPUWrapperESProducer.cc @@ -0,0 +1,68 @@ +#include "RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h" +#include "CondFormats/DataRecord/interface/SiPixelFedCablingMapRcd.h" +#include "CondFormats/DataRecord/interface/SiPixelQualityRcd.h" +#include "FWCore/Framework/interface/ESProducer.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/ESTransientHandle.h" +#include "FWCore/Framework/interface/ModuleFactory.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "Geometry/Records/interface/TrackerDigiGeometryRecord.h" + +#include "RecoTracker/Record/interface/CkfComponentsRecord.h" // TODO: eventually use something more limited + +#include + +class SiPixelFedCablingMapGPUWrapperESProducer: public edm::ESProducer { +public: + explicit SiPixelFedCablingMapGPUWrapperESProducer(const edm::ParameterSet& iConfig); + std::unique_ptr produce(const CkfComponentsRecord& iRecord); + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + std::string cablingMapLabel_; + bool useQuality_; +}; + +SiPixelFedCablingMapGPUWrapperESProducer::SiPixelFedCablingMapGPUWrapperESProducer(const edm::ParameterSet& iConfig): + cablingMapLabel_(iConfig.getParameter("CablingMapLabel")), + useQuality_(iConfig.getParameter("UseQualityInfo")) +{ + std::string myname = iConfig.getParameter("ComponentName"); + setWhatProduced(this, myname); +} + +void SiPixelFedCablingMapGPUWrapperESProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + + std::string label = "siPixelFedCablingMapGPUWrapper"; + desc.add("ComponentName", ""); + desc.add("CablingMapLabel","")->setComment("CablingMap label"); + desc.add("UseQualityInfo",false); + + descriptions.add(label, desc); +} + +std::unique_ptr SiPixelFedCablingMapGPUWrapperESProducer::produce(const CkfComponentsRecord& iRecord) { + edm::ESTransientHandle cablingMap; + iRecord.getRecord().get( cablingMapLabel_, cablingMap ); + + const SiPixelQuality *quality = nullptr; + if(useQuality_) { + edm::ESTransientHandle qualityInfo; + iRecord.getRecord().get(qualityInfo); + quality = qualityInfo.product(); + } + + edm::ESTransientHandle geom; + iRecord.getRecord().get(geom); + + return std::make_unique(*cablingMap, *geom, quality); +} + +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Utilities/interface/typelookup.h" +#include "FWCore/Framework/interface/eventsetuprecord_registration_macro.h" + +DEFINE_FWK_EVENTSETUP_MODULE(SiPixelFedCablingMapGPUWrapperESProducer); diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.cu b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.cu new file mode 100644 index 0000000000000..c3f4292d4c4c4 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.cu @@ -0,0 +1,695 @@ +/* Sushil Dubey, Shashi Dugad, TIFR, July 2017 + * + * File Name: RawToClusterGPU.cu + * Description: It converts Raw data into Digi Format on GPU + * then it converts adc -> electron and + * applies the adc threshold to needed for clustering + * Finaly the Output of RawToDigi data is given to pixelClusterizer + * +**/ + +// C++ includes +#include +#include +#include +#include +#include +#include +#include +#include + +// CUDA includes +#include +#include +#include +#include +#include +#include +#include + +// cub includes +#include + +// CMSSW includes +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/gpuCalibPixel.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/gpuClustering.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusterChargeCut.h" +#include "RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPU.h" + +// local includes +#include "SiPixelRawToClusterGPUKernel.h" + +namespace pixelgpudetails { + + // data structures size + constexpr uint32_t vsize = sizeof(GPU::SimpleVector); + constexpr uint32_t esize = sizeof(pixelgpudetails::error_obj); + + // number of words for all the FEDs + constexpr uint32_t MAX_FED_WORDS = pixelgpudetails::MAX_FED * pixelgpudetails::MAX_WORD; + constexpr uint32_t MAX_ERROR_SIZE = MAX_FED_WORDS * esize; + + SiPixelRawToClusterGPUKernel::WordFedAppender::WordFedAppender(cuda::stream_t<>& cudaStream) { + edm::Service cs; + word_ = cs->make_host_unique(MAX_FED_WORDS, cudaStream); + fedId_ = cs->make_host_unique(MAX_FED_WORDS, cudaStream); + } + + void SiPixelRawToClusterGPUKernel::WordFedAppender::initializeWordFed(int fedId, unsigned int wordCounterGPU, const cms_uint32_t *src, unsigned int length) { + std::memcpy(word_.get()+wordCounterGPU, src, sizeof(cms_uint32_t)*length); + std::memset(fedId_.get()+wordCounterGPU/2, fedId - 1200, length/2); + } + + //////////////////// + + __device__ uint32_t getLink(uint32_t ww) { + return ((ww >> pixelgpudetails::LINK_shift) & pixelgpudetails::LINK_mask); + } + + + __device__ uint32_t getRoc(uint32_t ww) { + return ((ww >> pixelgpudetails::ROC_shift ) & pixelgpudetails::ROC_mask); + } + + + __device__ uint32_t getADC(uint32_t ww) { + return ((ww >> pixelgpudetails::ADC_shift) & pixelgpudetails::ADC_mask); + } + + + __device__ bool isBarrel(uint32_t rawId) { + return (1==((rawId>>25)&0x7)); + } + + __device__ pixelgpudetails::DetIdGPU getRawId(const SiPixelFedCablingMapGPU * cablingMap, uint32_t fed, uint32_t link, uint32_t roc) { + uint32_t index = fed * MAX_LINK * MAX_ROC + (link-1) * MAX_ROC + roc; + pixelgpudetails::DetIdGPU detId = { cablingMap->RawId[index], cablingMap->rocInDet[index], cablingMap->moduleId[index] }; + return detId; + } + + //reference http://cmsdoxygen.web.cern.ch/cmsdoxygen/CMSSW_9_2_0/doc/html/dd/d31/FrameConversion_8cc_source.html + //http://cmslxr.fnal.gov/source/CondFormats/SiPixelObjects/src/PixelROC.cc?v=CMSSW_9_2_0#0071 + // Convert local pixel to pixelgpudetails::global pixel + __device__ pixelgpudetails::Pixel frameConversion(bool bpix, int side, uint32_t layer, uint32_t rocIdInDetUnit, pixelgpudetails::Pixel local) { + + int slopeRow = 0, slopeCol = 0; + int rowOffset = 0, colOffset = 0; + + if (bpix) { + + if (side == -1 && layer != 1) { // -Z side: 4 non-flipped modules oriented like 'dddd', except Layer 1 + if (rocIdInDetUnit < 8) { + slopeRow = 1; + slopeCol = -1; + rowOffset = 0; + colOffset = (8-rocIdInDetUnit)*pixelgpudetails::numColsInRoc-1; + } + else { + slopeRow = -1; + slopeCol = 1; + rowOffset = 2*pixelgpudetails::numRowsInRoc-1; + colOffset = (rocIdInDetUnit-8)*pixelgpudetails::numColsInRoc; + } // if roc + } + else { // +Z side: 4 non-flipped modules oriented like 'pppp', but all 8 in layer1 + if (rocIdInDetUnit < 8) { + slopeRow = -1; + slopeCol = 1; + rowOffset = 2*pixelgpudetails::numRowsInRoc-1; + colOffset = rocIdInDetUnit * pixelgpudetails::numColsInRoc; + } + else { + slopeRow = 1; + slopeCol = -1; + rowOffset = 0; + colOffset = (16-rocIdInDetUnit)*pixelgpudetails::numColsInRoc-1; + } + } + + } + else { // fpix + if (side==-1) { // pannel 1 + if (rocIdInDetUnit < 8) { + slopeRow = 1; + slopeCol = -1; + rowOffset = 0; + colOffset = (8-rocIdInDetUnit)*pixelgpudetails::numColsInRoc-1; + } + else { + slopeRow = -1; + slopeCol = 1; + rowOffset = 2*pixelgpudetails::numRowsInRoc-1; + colOffset = (rocIdInDetUnit-8)*pixelgpudetails::numColsInRoc; + } + } + else { // pannel 2 + if (rocIdInDetUnit < 8) { + slopeRow = 1; + slopeCol = -1; + rowOffset = 0; + colOffset = (8-rocIdInDetUnit)*pixelgpudetails::numColsInRoc-1; + } + else { + slopeRow = -1; + slopeCol = 1; + rowOffset = 2*pixelgpudetails::numRowsInRoc-1; + colOffset = (rocIdInDetUnit-8)*pixelgpudetails::numColsInRoc; + } + + } // side + + } + + uint32_t gRow = rowOffset+slopeRow*local.row; + uint32_t gCol = colOffset+slopeCol*local.col; + //printf("Inside frameConversion row: %u, column: %u\n", gRow, gCol); + pixelgpudetails::Pixel global = {gRow, gCol}; + return global; + } + + + __device__ uint32_t conversionError(uint32_t fedId, uint32_t status, bool debug = false) + { + uint32_t errorType = 0; + + // debug = true; + + switch (status) { + case(1) : { + if (debug) printf("Error in Fed: %i, invalid channel Id (errorType = 35\n)", fedId ); + errorType = 35; + break; + } + case(2) : { + if (debug) printf("Error in Fed: %i, invalid ROC Id (errorType = 36)\n", fedId); + errorType = 36; + break; + } + case(3) : { + if (debug) printf("Error in Fed: %i, invalid dcol/pixel value (errorType = 37)\n", fedId); + errorType = 37; + break; + } + case(4) : { + if (debug) printf("Error in Fed: %i, dcol/pixel read out of order (errorType = 38)\n", fedId); + errorType = 38; + break; + } + default: + if (debug) printf("Cabling check returned unexpected result, status = %i\n", status); + }; + + return errorType; + } + + __device__ bool rocRowColIsValid(uint32_t rocRow, uint32_t rocCol) + { + uint32_t numRowsInRoc = 80; + uint32_t numColsInRoc = 52; + + /// row and collumn in ROC representation + return ((rocRow < numRowsInRoc) & (rocCol < numColsInRoc)); + } + + __device__ bool dcolIsValid(uint32_t dcol, uint32_t pxid) + { + return ((dcol < 26) & (2 <= pxid) & (pxid < 162)); + } + + __device__ uint32_t checkROC(uint32_t errorWord, uint32_t fedId, uint32_t link, const SiPixelFedCablingMapGPU *cablingMap, bool debug = false) + { + int errorType = (errorWord >> pixelgpudetails::ROC_shift) & pixelgpudetails::ERROR_mask; + if (errorType < 25) return false; + bool errorFound = false; + + switch (errorType) { + case(25) : { + errorFound = true; + uint32_t index = fedId * MAX_LINK * MAX_ROC + (link-1) * MAX_ROC + 1; + if (index > 1 && index <= cablingMap->size) { + if (!(link == cablingMap->link[index] && 1 == cablingMap->roc[index])) errorFound = false; + } + if (debug&errorFound) printf("Invalid ROC = 25 found (errorType = 25)\n"); + break; + } + case(26) : { + if (debug) printf("Gap word found (errorType = 26)\n"); + errorFound = true; + break; + } + case(27) : { + if (debug) printf("Dummy word found (errorType = 27)\n"); + errorFound = true; + break; + } + case(28) : { + if (debug) printf("Error fifo nearly full (errorType = 28)\n"); + errorFound = true; + break; + } + case(29) : { + if (debug) printf("Timeout on a channel (errorType = 29)\n"); + if ((errorWord >> pixelgpudetails::OMIT_ERR_shift) & pixelgpudetails::OMIT_ERR_mask) { + if (debug) printf("...first errorType=29 error, this gets masked out\n"); + } + errorFound = true; + break; + } + case(30) : { + if (debug) printf("TBM error trailer (errorType = 30)\n"); + int StateMatch_bits = 4; + int StateMatch_shift = 8; + uint32_t StateMatch_mask = ~(~uint32_t(0) << StateMatch_bits); + int StateMatch = (errorWord >> StateMatch_shift) & StateMatch_mask; + if ( StateMatch != 1 && StateMatch != 8 ) { + if (debug) printf("FED error 30 with unexpected State Bits (errorType = 30)\n"); + } + if ( StateMatch == 1 ) errorType = 40; // 1=Overflow -> 40, 8=number of ROCs -> 30 + errorFound = true; + break; + } + case(31) : { + if (debug) printf("Event number error (errorType = 31)\n"); + errorFound = true; + break; + } + default: + errorFound = false; + }; + + return errorFound? errorType : 0; + } + + __device__ uint32_t getErrRawID(uint32_t fedId, uint32_t errWord, uint32_t errorType, const SiPixelFedCablingMapGPU *cablingMap, bool debug = false) + { + uint32_t rID = 0xffffffff; + + switch (errorType) { + case 25 : case 30 : case 31 : case 36 : case 40 : { + //set dummy values for cabling just to get detId from link + //cabling.dcol = 0; + //cabling.pxid = 2; + uint32_t roc = 1; + uint32_t link = (errWord >> pixelgpudetails::LINK_shift) & pixelgpudetails::LINK_mask; + uint32_t rID_temp = getRawId(cablingMap, fedId, link, roc).RawId; + if (rID_temp != 9999) rID = rID_temp; + break; + } + case 29 : { + int chanNmbr = 0; + const int DB0_shift = 0; + const int DB1_shift = DB0_shift + 1; + const int DB2_shift = DB1_shift + 1; + const int DB3_shift = DB2_shift + 1; + const int DB4_shift = DB3_shift + 1; + const uint32_t DataBit_mask = ~(~uint32_t(0) << 1); + + int CH1 = (errWord >> DB0_shift) & DataBit_mask; + int CH2 = (errWord >> DB1_shift) & DataBit_mask; + int CH3 = (errWord >> DB2_shift) & DataBit_mask; + int CH4 = (errWord >> DB3_shift) & DataBit_mask; + int CH5 = (errWord >> DB4_shift) & DataBit_mask; + int BLOCK_bits = 3; + int BLOCK_shift = 8; + uint32_t BLOCK_mask = ~(~uint32_t(0) << BLOCK_bits); + int BLOCK = (errWord >> BLOCK_shift) & BLOCK_mask; + int localCH = 1*CH1+2*CH2+3*CH3+4*CH4+5*CH5; + if (BLOCK%2==0) chanNmbr=(BLOCK/2)*9+localCH; + else chanNmbr = ((BLOCK-1)/2)*9+4+localCH; + if ((chanNmbr < 1)||(chanNmbr > 36)) break; // signifies unexpected result + + // set dummy values for cabling just to get detId from link if in Barrel + //cabling.dcol = 0; + //cabling.pxid = 2; + uint32_t roc = 1; + uint32_t link = chanNmbr; + uint32_t rID_temp = getRawId(cablingMap, fedId, link, roc).RawId; + if(rID_temp != 9999) rID = rID_temp; + break; + } + case 37 : case 38: { + //cabling.dcol = 0; + //cabling.pxid = 2; + uint32_t roc = (errWord >> pixelgpudetails::ROC_shift) & pixelgpudetails::ROC_mask; + uint32_t link = (errWord >> pixelgpudetails::LINK_shift) & pixelgpudetails::LINK_mask; + uint32_t rID_temp = getRawId(cablingMap, fedId, link, roc).RawId; + if(rID_temp != 9999) rID = rID_temp; + break; + } + default: + break; + }; + + return rID; + } + + /*---------- + * Name: applyADCthreshold_kernel() + * Desc: converts adc count to electrons and then applies the + * threshold on each channel. + * make pixel to 0 if it is below the threshold + * Input: xx_d[], yy_d[], layer_d[], wordCounter, adc[], ADCThreshold + *----------- + * Output: xx_adc[], yy_adc[] with pixel threshold applied + */ + // kernel to apply adc threshold on the channels + + + // Felice: gains and pedestals are not the same for each pixel. This code should be rewritten to take + // in account local gains/pedestals + // __global__ void applyADCthreshold_kernel(const uint32_t *xx_d, const uint32_t *yy_d, const uint32_t *layer_d, uint32_t *adc, const uint32_t wordCounter, + // const ADCThreshold adcThreshold, uint32_t *xx_adc, uint32_t *yy_adc ) { + // int tid = threadIdx.x; + // int gIndex = blockDim.x*blockIdx.x+tid; + // if (gIndex=adcThreshold.theFirstStack_) { + // if (adcThreshold.theStackADC_==1 && adcOld==1) { + // adcNew = int(255*135); // Arbitrarily use overflow value. + // } + // if (adcThreshold.theStackADC_ >1 && adcThreshold.theStackADC_!=255 && adcOld>=1){ + // adcNew = int((adcOld-1) * gain * 255/float(adcThreshold.theStackADC_-1)); + // } + // } + // + // if (adcNew >adcThreshold.thePixelThreshold ) { + // xx_adc[gIndex]=xx_d[gIndex]; + // yy_adc[gIndex]=yy_d[gIndex]; + // } + // else { + // xx_adc[gIndex]=0; // 0: dead pixel + // yy_adc[gIndex]=0; + // } + // adc[gIndex] = adcNew; + // } + // } + + + // Kernel to perform Raw to Digi conversion + __global__ void RawToDigi_kernel(const SiPixelFedCablingMapGPU *cablingMap, const unsigned char *modToUnp, + const uint32_t wordCounter, const uint32_t *word, const uint8_t *fedIds, + uint16_t *xx, uint16_t *yy, uint16_t *adc, + uint32_t *pdigi, uint32_t *rawIdArr, uint16_t *moduleId, + GPU::SimpleVector *err, + bool useQualityInfo, bool includeErrors, bool debug) + { + //if (threadIdx.x==0) printf("Event: %u blockIdx.x: %u start: %u end: %u\n", eventno, blockIdx.x, begin, end); + + auto gIndex = threadIdx.x + blockIdx.x * blockDim.x; + xx[gIndex] = 0; + yy[gIndex] = 0; + adc[gIndex] = 0; + bool skipROC = false; + + do { // too many coninue below.... (to be fixed) + if (gIndex < wordCounter) { + uint32_t fedId = fedIds[gIndex/2]; // +1200; + + // initialize (too many coninue below) + pdigi[gIndex] = 0; + rawIdArr[gIndex] = 0; + moduleId[gIndex] = 9999; + + uint32_t ww = word[gIndex]; // Array containing 32 bit raw data + if (ww == 0) { + // 0 is an indicator of a noise/dead channel, skip these pixels during clusterization + continue; + } + + uint32_t link = getLink(ww); // Extract link + uint32_t roc = getRoc(ww); // Extract Roc in link + pixelgpudetails::DetIdGPU detId = getRawId(cablingMap, fedId, link, roc); + + uint32_t errorType = checkROC(ww, fedId, link, cablingMap, debug); + skipROC = (roc < pixelgpudetails::maxROCIndex) ? false : (errorType != 0); + if (includeErrors and skipROC) + { + uint32_t rID = getErrRawID(fedId, ww, errorType, cablingMap, debug); + err->push_back(pixelgpudetails::error_obj{rID, ww, errorType, fedId}); + continue; + } + + uint32_t rawId = detId.RawId; + uint32_t rocIdInDetUnit = detId.rocInDet; + bool barrel = isBarrel(rawId); + + uint32_t index = fedId * MAX_LINK * MAX_ROC + (link-1) * MAX_ROC + roc; + if (useQualityInfo) { + skipROC = cablingMap->badRocs[index]; + if (skipROC) continue; + } + skipROC = modToUnp[index]; + if (skipROC) continue; + + uint32_t layer = 0;//, ladder =0; + int side = 0, panel = 0, module = 0;//disk = 0, blade = 0 + + if (barrel) + { + layer = (rawId >> pixelgpudetails::layerStartBit) & pixelgpudetails::layerMask; + module = (rawId >> pixelgpudetails::moduleStartBit) & pixelgpudetails::moduleMask; + side = (module < 5)? -1 : 1; + } + else { + // endcap ids + layer = 0; + panel = (rawId >> pixelgpudetails::panelStartBit) & pixelgpudetails::panelMask; + //disk = (rawId >> diskStartBit_) & diskMask_; + side = (panel == 1)? -1 : 1; + //blade = (rawId >> bladeStartBit_) & bladeMask_; + } + + // ***special case of layer to 1 be handled here + pixelgpudetails::Pixel localPix; + if (layer == 1) { + uint32_t col = (ww >> pixelgpudetails::COL_shift) & pixelgpudetails::COL_mask; + uint32_t row = (ww >> pixelgpudetails::ROW_shift) & pixelgpudetails::ROW_mask; + localPix.row = row; + localPix.col = col; + if (includeErrors) { + if (not rocRowColIsValid(row, col)) { + uint32_t error = conversionError(fedId, 3, debug); //use the device function and fill the arrays + err->push_back(pixelgpudetails::error_obj{rawId, ww, error, fedId}); + if(debug) printf("BPIX1 Error status: %i\n", error); + continue; + } + } + } else { + // ***conversion rules for dcol and pxid + uint32_t dcol = (ww >> pixelgpudetails::DCOL_shift) & pixelgpudetails::DCOL_mask; + uint32_t pxid = (ww >> pixelgpudetails::PXID_shift) & pixelgpudetails::PXID_mask; + uint32_t row = pixelgpudetails::numRowsInRoc - pxid/2; + uint32_t col = dcol*2 + pxid%2; + localPix.row = row; + localPix.col = col; + if (includeErrors and not dcolIsValid(dcol, pxid)) { + uint32_t error = conversionError(fedId, 3, debug); + err->push_back(pixelgpudetails::error_obj{rawId, ww, error, fedId}); + if(debug) printf("Error status: %i %d %d %d %d\n", error, dcol, pxid, fedId, roc); + continue; + } + } + + pixelgpudetails::Pixel globalPix = frameConversion(barrel, side, layer, rocIdInDetUnit, localPix); + xx[gIndex] = globalPix.row; // origin shifting by 1 0-159 + yy[gIndex] = globalPix.col; // origin shifting by 1 0-415 + adc[gIndex] = getADC(ww); + pdigi[gIndex] = pixelgpudetails::pack(globalPix.row, globalPix.col, adc[gIndex]); + moduleId[gIndex] = detId.moduleId; + rawIdArr[gIndex] = rawId; + } // end of if (gIndex < end) + } while (false); // end fake loop + } // end of Raw to Digi kernel + + // Interface to outside + void SiPixelRawToClusterGPUKernel::makeClustersAsync( + const SiPixelFedCablingMapGPU *cablingMap, + const unsigned char *modToUnp, + const SiPixelGainForHLTonGPU *gains, + const WordFedAppender& wordFed, + const uint32_t wordCounter, const uint32_t fedCounter, + bool convertADCtoElectrons, + bool useQualityInfo, bool includeErrors, bool transferToCPU, bool debug, + cuda::stream_t<>& stream) + { + nDigis = wordCounter; + + constexpr uint32_t MAX_FED_WORDS = pixelgpudetails::MAX_FED * pixelgpudetails::MAX_WORD; + digis_d = SiPixelDigisCUDA(MAX_FED_WORDS, stream); + clusters_d = SiPixelClustersCUDA(MAX_FED_WORDS, gpuClustering::MaxNumModules, stream); + + edm::Service cs; + digis_clusters_h.nModules_Clusters = cs->make_host_unique(2, stream); + + { + const int threadsPerBlock = 512; + const int blocks = (wordCounter + threadsPerBlock-1) /threadsPerBlock; // fill it all + + assert(0 == wordCounter%2); + // wordCounter is the total no of words in each event to be trasfered on device + auto word_d = cs->make_device_unique(wordCounter, stream); + auto fedId_d = cs->make_device_unique(wordCounter, stream); + + auto error_d = cs->make_device_unique>(stream); + auto data_d = cs->make_device_unique(MAX_FED_WORDS, stream); + cudaCheck(cudaMemsetAsync(data_d.get(), 0x00, MAX_ERROR_SIZE, stream.id())); + auto error_h_tmp = cs->make_host_unique>(stream); + GPU::make_SimpleVector(error_h_tmp.get(), MAX_FED_WORDS, data_d.get()); + assert(error_h_tmp->size() == 0); + assert(error_h_tmp->capacity() == static_cast(MAX_FED_WORDS)); + + cudaCheck(cudaMemcpyAsync(word_d.get(), wordFed.word(), wordCounter*sizeof(uint32_t), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(fedId_d.get(), wordFed.fedId(), wordCounter*sizeof(uint8_t) / 2, cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(error_d.get(), error_h_tmp.get(), vsize, cudaMemcpyDefault, stream.id())); + + auto pdigi_d = cs->make_device_unique(wordCounter, stream); + auto rawIdArr_d = cs->make_device_unique(wordCounter, stream); + + // Launch rawToDigi kernel + RawToDigi_kernel<<>>( + cablingMap, + modToUnp, + wordCounter, + word_d.get(), + fedId_d.get(), + digis_d.xx(), digis_d.yy(), digis_d.adc(), + pdigi_d.get(), + rawIdArr_d.get(), + digis_d.moduleInd(), + error_d.get(), + useQualityInfo, + includeErrors, + debug); + cudaCheck(cudaGetLastError()); + + // copy data to host variable + if(transferToCPU) { + digis_clusters_h.pdigi = cs->make_host_unique(MAX_FED_WORDS, stream); + digis_clusters_h.rawIdArr = cs->make_host_unique(MAX_FED_WORDS, stream); + cudaCheck(cudaMemcpyAsync(digis_clusters_h.pdigi.get(), pdigi_d.get(), wordCounter*sizeof(uint32_t), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(digis_clusters_h.rawIdArr.get(), rawIdArr_d.get(), wordCounter*sizeof(uint32_t), cudaMemcpyDefault, stream.id())); + + if (includeErrors) { + digis_clusters_h.data = cs->make_host_unique(MAX_FED_WORDS, stream); + digis_clusters_h.error = cs->make_host_unique>(stream); + GPU::make_SimpleVector(digis_clusters_h.error.get(), MAX_FED_WORDS, digis_clusters_h.data.get()); + assert(digis_clusters_h.error->size() == 0); + assert(digis_clusters_h.error->capacity() == static_cast(MAX_FED_WORDS)); + + cudaCheck(cudaMemcpyAsync(digis_clusters_h.error.get(), error_d.get(), vsize, cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(digis_clusters_h.data.get(), data_d.get(), MAX_ERROR_SIZE, cudaMemcpyDefault, stream.id())); + // If we want to transfer only the minimal amount of data, we + // need a synchronization point. A single ExternalWork (of + // SiPixelRawToClusterHeterogeneous) does not help because it is + // already used to synchronize the data movement. So we'd need + // two ExternalWorks (or explicit use of TBB tasks). The + // prototype of #100 would allow this easily (as there would be + // two ExternalWorks). + // + //cudaCheck(cudaStreamSynchronize(stream.id())); + //int size = digis_clusters_h.error->size(); + //cudaCheck(cudaMemcpyAsync(digis_clusters_h.data.get(), data_d.get(), size*esize, cudaMemcpyDefault, stream.id())); + } + } + } + // End of Raw2Digi and passing data for cluserisation + + { + // clusterizer ... + using namespace gpuClustering; + int threadsPerBlock = 256; + int blocks = (wordCounter + threadsPerBlock - 1) / threadsPerBlock; + + gpuCalibPixel::calibDigis<<>>( + digis_d.moduleInd(), + digis_d.c_xx(), digis_d.c_yy(), digis_d.adc(), + gains, + wordCounter); + cudaCheck(cudaGetLastError()); + + // calibrated adc + if(transferToCPU) { + digis_clusters_h.adc = cs->make_host_unique(MAX_FED_WORDS, stream); + cudaCheck(cudaMemcpyAsync(digis_clusters_h.adc.get(), digis_d.adc(), wordCounter*sizeof(uint16_t), cudaMemcpyDefault, stream.id())); + } + +#ifdef GPU_DEBUG + std::cout + << "CUDA countModules kernel launch with " << blocks + << " blocks of " << threadsPerBlock << " threads\n"; +#endif + + cudaCheck(cudaMemsetAsync(clusters_d.moduleStart(), 0x00, sizeof(uint32_t), stream.id())); + + countModules<<>>(digis_d.c_moduleInd(), clusters_d.moduleStart(), clusters_d.clus(), wordCounter); + cudaCheck(cudaGetLastError()); + + // read the number of modules into a data member, used by getProduct()) + cudaCheck(cudaMemcpyAsync(&(digis_clusters_h.nModules_Clusters[0]), clusters_d.moduleStart(), sizeof(uint32_t), cudaMemcpyDefault, stream.id())); + + threadsPerBlock = 256; + blocks = MaxNumModules; +#ifdef GPU_DEBUG + std::cout << "CUDA findClus kernel launch with " << blocks + << " blocks of " << threadsPerBlock << " threads\n"; +#endif + cudaCheck(cudaMemsetAsync(clusters_d.clusInModule(), 0, (MaxNumModules)*sizeof(uint32_t), stream.id())); + findClus<<>>( + digis_d.c_moduleInd(), + digis_d.c_xx(), digis_d.c_yy(), + clusters_d.c_moduleStart(), + clusters_d.clusInModule(), clusters_d.moduleId(), + clusters_d.clus(), + wordCounter); + cudaCheck(cudaGetLastError()); + + // apply charge cut + clusterChargeCut<<>>( + digis_d.moduleInd(), + digis_d.c_adc(), + clusters_d.c_moduleStart(), + clusters_d.clusInModule(), clusters_d.c_moduleId(), + clusters_d.clus(), + wordCounter); + cudaCheck(cudaGetLastError()); + + + + // count the module start indices already here (instead of + // rechits) so that the number of clusters/hits can be made + // available in the rechit producer without additional points of + // synchronization/ExternalWork + // + // Temporary storage + size_t tempScanStorageSize = 0; + { + uint32_t *tmp = nullptr; + cudaCheck(cub::DeviceScan::InclusiveSum(nullptr, tempScanStorageSize, tmp, tmp, MaxNumModules)); + } + auto tempScanStorage_d = cs->make_device_unique(tempScanStorageSize, stream); + // Set first the first element to 0 + cudaCheck(cudaMemsetAsync(clusters_d.clusModuleStart(), 0, sizeof(uint32_t), stream.id())); + // Then use inclusive_scan to get the partial sum to the rest + cudaCheck(cub::DeviceScan::InclusiveSum(tempScanStorage_d.get(), tempScanStorageSize, + clusters_d.c_clusInModule(), &clusters_d.clusModuleStart()[1], gpuClustering::MaxNumModules, + stream.id())); + // last element holds the number of all clusters + cudaCheck(cudaMemcpyAsync(&(digis_clusters_h.nModules_Clusters[1]), clusters_d.clusModuleStart()+gpuClustering::MaxNumModules, sizeof(uint32_t), cudaMemcpyDefault, stream.id())); + + + // clusters + if(transferToCPU) { + digis_clusters_h.clus = cs->make_host_unique(MAX_FED_WORDS, stream); + cudaCheck(cudaMemcpyAsync(digis_clusters_h.clus.get(), clusters_d.clus(), wordCounter*sizeof(uint32_t), cudaMemcpyDefault, stream.id())); + } + } // end clusterizer scope + } + +} diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.h b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.h new file mode 100644 index 0000000000000..b0151055ed7f1 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.h @@ -0,0 +1,281 @@ +#ifndef RecoLocalTracker_SiPixelClusterizer_plugins_SiPixelRawToClusterGPUKernel_h +#define RecoLocalTracker_SiPixelClusterizer_plugins_SiPixelRawToClusterGPUKernel_h + +#include +#include +#include "cuda/api_wrappers.h" + +#include "CUDADataFormats/Common/interface/host_unique_ptr.h" +#include "FWCore/Utilities/interface/typedefs.h" +#include "HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h" +#include "siPixelRawToClusterHeterogeneousProduct.h" + +class SiPixelFedCablingMapGPU; +class SiPixelGainForHLTonGPU; + +namespace pixelgpudetails { + + // Phase 1 geometry constants + const uint32_t layerStartBit = 20; + const uint32_t ladderStartBit = 12; + const uint32_t moduleStartBit = 2; + + const uint32_t panelStartBit = 10; + const uint32_t diskStartBit = 18; + const uint32_t bladeStartBit = 12; + + const uint32_t layerMask = 0xF; + const uint32_t ladderMask = 0xFF; + const uint32_t moduleMask = 0x3FF; + const uint32_t panelMask = 0x3; + const uint32_t diskMask = 0xF; + const uint32_t bladeMask = 0x3F; + + const uint32_t LINK_bits = 6; + const uint32_t ROC_bits = 5; + const uint32_t DCOL_bits = 5; + const uint32_t PXID_bits = 8; + const uint32_t ADC_bits = 8; + + // special for layer 1 + const uint32_t LINK_bits_l1 = 6; + const uint32_t ROC_bits_l1 = 5; + const uint32_t COL_bits_l1 = 6; + const uint32_t ROW_bits_l1 = 7; + const uint32_t OMIT_ERR_bits = 1; + + const uint32_t maxROCIndex = 8; + const uint32_t numRowsInRoc = 80; + const uint32_t numColsInRoc = 52; + + const uint32_t MAX_WORD = 2000; + + const uint32_t ADC_shift = 0; + const uint32_t PXID_shift = ADC_shift + ADC_bits; + const uint32_t DCOL_shift = PXID_shift + PXID_bits; + const uint32_t ROC_shift = DCOL_shift + DCOL_bits; + const uint32_t LINK_shift = ROC_shift + ROC_bits_l1; + // special for layer 1 ROC + const uint32_t ROW_shift = ADC_shift + ADC_bits; + const uint32_t COL_shift = ROW_shift + ROW_bits_l1; + const uint32_t OMIT_ERR_shift = 20; + + const uint32_t LINK_mask = ~(~uint32_t(0) << LINK_bits_l1); + const uint32_t ROC_mask = ~(~uint32_t(0) << ROC_bits_l1); + const uint32_t COL_mask = ~(~uint32_t(0) << COL_bits_l1); + const uint32_t ROW_mask = ~(~uint32_t(0) << ROW_bits_l1); + const uint32_t DCOL_mask = ~(~uint32_t(0) << DCOL_bits); + const uint32_t PXID_mask = ~(~uint32_t(0) << PXID_bits); + const uint32_t ADC_mask = ~(~uint32_t(0) << ADC_bits); + const uint32_t ERROR_mask = ~(~uint32_t(0) << ROC_bits_l1); + const uint32_t OMIT_ERR_mask = ~(~uint32_t(0) << OMIT_ERR_bits); + + struct DetIdGPU { + uint32_t RawId; + uint32_t rocInDet; + uint32_t moduleId; + }; + + struct Pixel { + uint32_t row; + uint32_t col; + }; + + class Packing { + public: + using PackedDigiType = uint32_t; + + // Constructor: pre-computes masks and shifts from field widths + __host__ __device__ + inline + constexpr Packing(unsigned int row_w, unsigned int column_w, + unsigned int time_w, unsigned int adc_w) : + row_width(row_w), + column_width(column_w), + adc_width(adc_w), + row_shift(0), + column_shift(row_shift + row_w), + time_shift(column_shift + column_w), + adc_shift(time_shift + time_w), + row_mask(~(~0U << row_w)), + column_mask( ~(~0U << column_w)), + time_mask(~(~0U << time_w)), + adc_mask(~(~0U << adc_w)), + rowcol_mask(~(~0U << (column_w+row_w))), + max_row(row_mask), + max_column(column_mask), + max_adc(adc_mask) + { } + + uint32_t row_width; + uint32_t column_width; + uint32_t adc_width; + + uint32_t row_shift; + uint32_t column_shift; + uint32_t time_shift; + uint32_t adc_shift; + + PackedDigiType row_mask; + PackedDigiType column_mask; + PackedDigiType time_mask; + PackedDigiType adc_mask; + PackedDigiType rowcol_mask; + + uint32_t max_row; + uint32_t max_column; + uint32_t max_adc; + }; + + __host__ __device__ + inline + constexpr Packing packing() { + return Packing(11, 11, 0, 10); + } + + + __host__ __device__ + inline + uint32_t pack(uint32_t row, uint32_t col, uint32_t adc) { + constexpr Packing thePacking = packing(); + adc = std::min(adc, thePacking.max_adc); + + return (row << thePacking.row_shift) | + (col << thePacking.column_shift) | + (adc << thePacking.adc_shift); + } + + constexpr + uint32_t pixelToChannel( int row, int col) { + constexpr Packing thePacking = packing(); + return (row << thePacking.column_width) | col; + } + + + using error_obj = siPixelRawToClusterHeterogeneousProduct::error_obj; + + + class SiPixelRawToClusterGPUKernel { + public: + + using GPUProduct = siPixelRawToClusterHeterogeneousProduct::GPUProduct; + + struct CPUData { + CPUData() = default; + ~CPUData() = default; + + CPUData(const CPUData&) = delete; + CPUData& operator=(const CPUData&) = delete; + CPUData(CPUData&&) = default; + CPUData& operator=(CPUData&&) = default; + + edm::cuda::host::unique_ptr nModules_Clusters; // These should really be part of the GPU product + + edm::cuda::host::unique_ptr data; + edm::cuda::host::unique_ptr> error; + + edm::cuda::host::unique_ptr pdigi; + edm::cuda::host::unique_ptr rawIdArr; + edm::cuda::host::unique_ptr adc; + edm::cuda::host::unique_ptr clus; + }; + + class WordFedAppender { + public: + WordFedAppender(cuda::stream_t<>& cudaStream); + ~WordFedAppender() = default; + + void initializeWordFed(int fedId, unsigned int wordCounterGPU, const cms_uint32_t *src, unsigned int length); + + const unsigned int *word() const { return word_.get(); } + const unsigned char *fedId() const { return fedId_.get(); } + + private: + edm::cuda::host::unique_ptr word_; + edm::cuda::host::unique_ptr fedId_; + }; + + SiPixelRawToClusterGPUKernel() = default; + ~SiPixelRawToClusterGPUKernel() = default; + + + SiPixelRawToClusterGPUKernel(const SiPixelRawToClusterGPUKernel&) = delete; + SiPixelRawToClusterGPUKernel(SiPixelRawToClusterGPUKernel&&) = delete; + SiPixelRawToClusterGPUKernel& operator=(const SiPixelRawToClusterGPUKernel&) = delete; + SiPixelRawToClusterGPUKernel& operator=(SiPixelRawToClusterGPUKernel&&) = delete; + + void makeClustersAsync(const SiPixelFedCablingMapGPU *cablingMap, const unsigned char *modToUnp, + const SiPixelGainForHLTonGPU *gains, + const WordFedAppender& wordFed, + const uint32_t wordCounter, const uint32_t fedCounter, bool convertADCtoElectrons, + bool useQualityInfo, bool includeErrors, bool transferToCPU_, bool debug, + cuda::stream_t<>& stream); + + siPixelRawToClusterHeterogeneousProduct::GPUProduct getProduct() { + return siPixelRawToClusterHeterogeneousProduct::GPUProduct( + std::move(digis_d), std::move(clusters_d), + nDigis, + digis_clusters_h.nModules_Clusters[0], + digis_clusters_h.nModules_Clusters[1] + ); + } + + CPUData&& getCPUData() { + // Set the vector data pointer to point to CPU + digis_clusters_h.error->set_data(digis_clusters_h.data.get()); + return std::move(digis_clusters_h); + } + + private: + uint32_t nDigis = 0; + + // CPU data + CPUData digis_clusters_h; + + // Data to be put in the event + SiPixelDigisCUDA digis_d; + SiPixelClustersCUDA clusters_d; + }; + + // configuration and memory buffers alocated on the GPU + struct context { + uint32_t * word_d; + uint8_t * fedId_d; + uint32_t * pdigi_d; + uint16_t * xx_d; + uint16_t * yy_d; + uint16_t * adc_d; + uint16_t * moduleInd_d; + uint32_t * rawIdArr_d; + + GPU::SimpleVector * error_d; + error_obj * data_d; + + // these are for the clusterizer (to be moved) + uint32_t * moduleStart_d; + int32_t * clus_d; + uint32_t * clusInModule_d; + uint32_t * moduleId_d; + uint32_t * debug_d; + }; + + // void initCablingMap(); + context initDeviceMemory(); + void freeMemory(context &); + + // see RecoLocalTracker/SiPixelClusterizer + // all are runtime const, should be specified in python _cfg.py + struct ADCThreshold { + const int thePixelThreshold = 1000; // default Pixel threshold in electrons + const int theSeedThreshold = 1000; // seed thershold in electrons not used in our algo + const float theClusterThreshold = 4000; // cluster threshold in electron + const int ConversionFactor = 65; // adc to electron conversion factor + + const int theStackADC_ = 255; // the maximum adc count for stack layer + const int theFirstStack_ = 5; // the index of the fits stack layer + const double theElectronPerADCGain_ = 600; // ADC to electron conversion + }; + +} + +#endif // RecoLocalTracker_SiPixelClusterizer_plugins_SiPixelRawToClusterGPUKernel_h diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterHeterogeneous.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterHeterogeneous.cc new file mode 100644 index 0000000000000..035cf93bbd111 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterHeterogeneous.cc @@ -0,0 +1,739 @@ +// C++ includes +#include +#include +#include +#include + +// CUDA kincludes +#include +#include + +// CMSSW includes +#include "CalibTracker/Records/interface/SiPixelGainCalibrationForHLTGPURcd.h" +#include "CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTGPU.h" +#include "CalibTracker/SiPixelESProducers/interface/SiPixelGainCalibrationForHLTService.h" +#include "CondFormats/DataRecord/interface/SiPixelFedCablingMapRcd.h" +#include "CondFormats/DataRecord/interface/SiPixelQualityRcd.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelFedCablingMap.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelFedCablingTree.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelQuality.h" +#include "DataFormats/Common/interface/DetSetVector.h" +#include "DataFormats/Common/interface/Handle.h" +#include "DataFormats/DetId/interface/DetIdCollection.h" +#include "DataFormats/FEDRawData/interface/FEDNumbering.h" +#include "DataFormats/FEDRawData/interface/FEDRawData.h" +#include "DataFormats/FEDRawData/interface/FEDRawDataCollection.h" +#include "DataFormats/SiPixelCluster/interface/SiPixelCluster.h" +#include "DataFormats/SiPixelDetId/interface/PixelFEDChannel.h" +#include "DataFormats/SiPixelDigi/interface/PixelDigi.h" +#include "DataFormats/SiPixelRawData/interface/SiPixelRawDataError.h" +#include "DataFormats/TrackerCommon/interface/TrackerTopology.h" +#include "EventFilter/SiPixelRawToDigi/interface/PixelDataFormatter.h" +#include "EventFilter/SiPixelRawToDigi/interface/PixelUnpackingRegions.h" +#include "FWCore/Framework/interface/ConsumesCollector.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/ESTransientHandle.h" +#include "FWCore/Framework/interface/ESWatcher.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/PluginManager/interface/ModuleDef.h" +#include "Geometry/Records/interface/TrackerDigiGeometryRecord.h" +#include "Geometry/TrackerGeometryBuilder/interface/PixelGeomDetUnit.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +#include "RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h" +#include "RecoTracker/Record/interface/CkfComponentsRecord.h" + +#include "SiPixelRawToClusterGPUKernel.h" +#include "siPixelRawToClusterHeterogeneousProduct.h" +#include "PixelThresholdClusterizer.h" + +namespace { + struct AccretionCluster { + typedef unsigned short UShort; + static constexpr UShort MAXSIZE = 256; + UShort adc[MAXSIZE]; + UShort x[MAXSIZE]; + UShort y[MAXSIZE]; + UShort xmin=16000; + UShort ymin=16000; + unsigned int isize=0; + int charge=0; + + void clear() { + isize=0; + charge=0; + xmin=16000; + ymin=16000; + } + + bool add(SiPixelCluster::PixelPos const & p, UShort const iadc) { + if (isize==MAXSIZE) return false; + xmin=std::min(xmin,(unsigned short)(p.row())); + ymin=std::min(ymin,(unsigned short)(p.col())); + adc[isize]=iadc; + x[isize]=p.row(); + y[isize++]=p.col(); + charge+=iadc; + return true; + } + }; + + constexpr uint32_t dummydetid = 0xffffffff; +} + +class SiPixelRawToClusterHeterogeneous: public HeterogeneousEDProducer > { +public: + using CPUProduct = siPixelRawToClusterHeterogeneousProduct::CPUProduct; + using GPUProduct = siPixelRawToClusterHeterogeneousProduct::GPUProduct; + using Output = siPixelRawToClusterHeterogeneousProduct::HeterogeneousDigiCluster; + + explicit SiPixelRawToClusterHeterogeneous(const edm::ParameterSet& iConfig); + ~SiPixelRawToClusterHeterogeneous() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + // CPU implementation + void produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) override; + + // GPU implementation + void acquireGPUCuda(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) override; + void produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) override; + void convertGPUtoCPU(edm::Event& ev, unsigned int nDigis, pixelgpudetails::SiPixelRawToClusterGPUKernel::CPUData) const; + + // Commonalities + const FEDRawDataCollection *initialize(const edm::Event& ev, const edm::EventSetup& es); + + std::unique_ptr cabling_; + const SiPixelQuality *badPixelInfo_ = nullptr; + const SiPixelFedCablingMap *cablingMap_ = nullptr; +std::unique_ptr regions_; + edm::EDGetTokenT tFEDRawDataCollection; + + bool includeErrors; + bool useQuality; + bool debug; + std::vector tkerrorlist; + std::vector usererrorlist; + std::vector fedIds; + + edm::ESWatcher recordWatcher; + edm::ESWatcher qualityWatcher; + + bool usePilotBlade; + bool usePhase1; + bool convertADCtoElectrons; + std::string cablingMapLabel; + + // clusterizer + PixelThresholdClusterizer clusterizer_; + const TrackerGeometry *geom_ = nullptr; + const TrackerTopology *ttopo_ = nullptr; + + // gain calib + SiPixelGainCalibrationForHLTService theSiPixelGainCalibration_; + + // GPU algo + pixelgpudetails::SiPixelRawToClusterGPUKernel gpuAlgo_; + PixelDataFormatter::Errors errors_; + + bool enableTransfer_; + bool enableConversion_; +}; + +SiPixelRawToClusterHeterogeneous::SiPixelRawToClusterHeterogeneous(const edm::ParameterSet& iConfig): + HeterogeneousEDProducer(iConfig), + clusterizer_(iConfig), + theSiPixelGainCalibration_(iConfig) { + includeErrors = iConfig.getParameter("IncludeErrors"); + useQuality = iConfig.getParameter("UseQualityInfo"); + tkerrorlist = iConfig.getParameter > ("ErrorList"); + usererrorlist = iConfig.getParameter > ("UserErrorList"); + tFEDRawDataCollection = consumes (iConfig.getParameter("InputLabel")); + + enableConversion_ = iConfig.getParameter("gpuEnableConversion"); + enableTransfer_ = enableConversion_ || iConfig.getParameter("gpuEnableTransfer"); + + clusterizer_.setSiPixelGainCalibrationService(&theSiPixelGainCalibration_); + + // Products in GPU + produces(); + // Products in CPU + if(enableConversion_) { + produces>(); + if(includeErrors) { + produces>(); + produces(); + produces("UserErrorModules"); + produces(); + produces>(); + } + } + + // regions + if(!iConfig.getParameter("Regions").getParameterNames().empty()) { + regions_ = std::make_unique(iConfig, consumesCollector()); + } + + // Control the usage of pilot-blade data, FED=40 + usePilotBlade = iConfig.getParameter ("UsePilotBlade"); + if(usePilotBlade) edm::LogInfo("SiPixelRawToCluster") << " Use pilot blade data (FED 40)"; + + // Control the usage of phase1 + usePhase1 = iConfig.getParameter ("UsePhase1"); + if(usePhase1) edm::LogInfo("SiPixelRawToCluster") << " Using phase1"; + + //CablingMap could have a label //Tav + cablingMapLabel = iConfig.getParameter ("CablingMapLabel"); + + convertADCtoElectrons = iConfig.getParameter("ConvertADCtoElectrons"); +} + +void SiPixelRawToClusterHeterogeneous::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("IncludeErrors",true); + desc.add("UseQualityInfo",false); + { + std::vector temp1; + temp1.reserve(1); + temp1.push_back(29); + desc.add >("ErrorList",temp1)->setComment("## ErrorList: list of error codes used by tracking to invalidate modules"); + } + { + std::vector temp1; + temp1.reserve(1); + temp1.push_back(40); + desc.add >("UserErrorList",temp1)->setComment("## UserErrorList: list of error codes used by Pixel experts for investigation"); + } + desc.add("InputLabel",edm::InputTag("rawDataCollector")); + { + edm::ParameterSetDescription psd0; + psd0.addOptional>("inputs"); + psd0.addOptional>("deltaPhi"); + psd0.addOptional>("maxZ"); + psd0.addOptional("beamSpot"); + desc.add("Regions",psd0)->setComment("## Empty Regions PSet means complete unpacking"); + } + desc.add("UsePilotBlade",false)->setComment("## Use pilot blades"); + desc.add("UsePhase1",false)->setComment("## Use phase1"); + desc.add("CablingMapLabel","")->setComment("CablingMap label"); //Tav + desc.addOptional("CheckPixelOrder"); // never used, kept for back-compatibility + + desc.add("ConvertADCtoElectrons", false)->setComment("## do the calibration ADC-> Electron and apply the threshold, requried for clustering"); + + // clusterizer + desc.add("ChannelThreshold", 1000); + desc.add("SeedThreshold", 1000); + desc.add("ClusterThreshold", 4000); + desc.add("ClusterThreshold_L1", 4000); + desc.add("VCaltoElectronGain", 65); + desc.add("VCaltoElectronGain_L1", 65); + desc.add("VCaltoElectronOffset", -414); + desc.add("VCaltoElectronOffset_L1", -414); + desc.addUntracked("MissCalibrate", true); + desc.add("SplitClusters", false); + desc.add("ElectronPerADCGain", 135.); + // Phase 2 clusterizer + desc.add("Phase2Calibration", false); + desc.add("Phase2ReadoutMode", -1); + desc.add("Phase2DigiBaseline", 1200.); + desc.add("Phase2KinkADC", 8); + + desc.add("gpuEnableTransfer", true); + desc.add("gpuEnableConversion", true); + + HeterogeneousEDProducer::fillPSetDescription(desc); + + descriptions.add("siPixelClustersHeterogeneousDefault",desc); +} + +const FEDRawDataCollection *SiPixelRawToClusterHeterogeneous::initialize(const edm::Event& ev, const edm::EventSetup& es) { + debug = edm::MessageDrop::instance()->debugEnabled; + + // setup gain calibration service + theSiPixelGainCalibration_.setESObjects( es ); + + // initialize cabling map or update if necessary + if (recordWatcher.check( es )) { + // cabling map, which maps online address (fed->link->ROC->local pixel) to offline (DetId->global pixel) + edm::ESTransientHandle cablingMap; + es.get().get( cablingMapLabel, cablingMap ); //Tav + cablingMap_ = cablingMap.product(); + fedIds = cablingMap->fedIds(); + cabling_ = cablingMap->cablingTree(); + LogDebug("map version:")<< cabling_->version(); + } + // initialize quality record or update if necessary + if (qualityWatcher.check( es )&&useQuality) { + // quality info for dead pixel modules or ROCs + edm::ESHandle qualityInfo; + es.get().get( qualityInfo ); + badPixelInfo_ = qualityInfo.product(); + if (!badPixelInfo_) { + edm::LogError("SiPixelQualityNotPresent")<<" Configured to use SiPixelQuality, but SiPixelQuality not present"; + } + } + + // tracker geometry: to make sure numbering of DetId is consistent... + edm::ESHandle geom; + es.get().get(geom); + geom_ = geom.product(); + + edm::ESHandle trackerTopologyHandle; + es.get().get(trackerTopologyHandle); + ttopo_ = trackerTopologyHandle.product(); + + if (regions_) { + regions_->run(ev, es); + LogDebug("SiPixelRawToCluster") << "region2unpack #feds: "<nFEDs(); + LogDebug("SiPixelRawToCluster") << "region2unpack #modules (BPIX,EPIX,total): "<nBarrelModules()<<" "<nForwardModules()<<" "<nModules(); + } + + edm::Handle buffers; + ev.getByToken(tFEDRawDataCollection, buffers); + return buffers.product(); +} + + +// ----------------------------------------------------------------------------- +void SiPixelRawToClusterHeterogeneous::produceCPU(edm::HeterogeneousEvent& ev, const edm::EventSetup& es) +{ + const auto buffers = initialize(ev.event(), es); + + // create product (digis & errors) + auto collection = std::make_unique>(); + auto errorcollection = std::make_unique>(); + auto tkerror_detidcollection = std::make_unique(); + auto usererror_detidcollection = std::make_unique(); + auto disabled_channelcollection = std::make_unique< edmNew::DetSetVector>(); + auto outputClusters = std::make_unique(); + // output->collection.reserve(8*1024); + + + PixelDataFormatter formatter(cabling_.get(), usePhase1); // for phase 1 & 0 + formatter.setErrorStatus(includeErrors); + if (useQuality) formatter.setQualityStatus(useQuality, badPixelInfo_); + + bool errorsInEvent = false; + PixelDataFormatter::DetErrors nodeterrors; + + if (regions_) { + formatter.setModulesToUnpack(regions_->modulesToUnpack()); + } + + for (auto aFed = fedIds.begin(); aFed != fedIds.end(); ++aFed) { + int fedId = *aFed; + + if(!usePilotBlade && (fedId==40) ) continue; // skip pilot blade data + + if (regions_ && !regions_->mayUnpackFED(fedId)) continue; + + if(debug) LogDebug("SiPixelRawToCluster")<< " PRODUCE DIGI FOR FED: " << fedId; + + PixelDataFormatter::Errors errors; + + //get event data for this fed + const FEDRawData& fedRawData = buffers->FEDData( fedId ); + + //convert data to digi and strip off errors + formatter.interpretRawData( errorsInEvent, fedId, fedRawData, *collection, errors); + + //pack errors into collection + if(includeErrors) { + typedef PixelDataFormatter::Errors::iterator IE; + for (IE is = errors.begin(); is != errors.end(); is++) { + uint32_t errordetid = is->first; + if (errordetid==dummydetid) { // errors given dummy detId must be sorted by Fed + nodeterrors.insert( nodeterrors.end(), errors[errordetid].begin(), errors[errordetid].end() ); + } else { + edm::DetSet& errorDetSet = errorcollection->find_or_insert(errordetid); + errorDetSet.data.insert(errorDetSet.data.end(), is->second.begin(), is->second.end()); + // Fill detid of the detectors where there is error AND the error number is listed + // in the configurable error list in the job option cfi. + // Code needs to be here, because there can be a set of errors for each + // entry in the for loop over PixelDataFormatter::Errors + + std::vector disabledChannelsDetSet; + + for (auto const& aPixelError : errorDetSet) { + // For the time being, we extend the error handling functionality with ErrorType 25 + // In the future, we should sort out how the usage of tkerrorlist can be generalized + if (aPixelError.getType()==25) { + assert(aPixelError.getFedId()==fedId); + const sipixelobjects::PixelFEDCabling* fed = cabling_->fed(fedId); + if (fed) { + cms_uint32_t linkId = formatter.linkId(aPixelError.getWord32()); + const sipixelobjects::PixelFEDLink* link = fed->link(linkId); + if (link) { + // The "offline" 0..15 numbering is fixed by definition, also, the FrameConversion depends on it + // in contrast, the ROC-in-channel numbering is determined by hardware --> better to use the "offline" scheme + PixelFEDChannel ch = {fed->id(), linkId, 25, 0}; + for (unsigned int iRoc=1; iRoc<=link->numberOfROCs(); iRoc++) { + const sipixelobjects::PixelROC * roc = link->roc(iRoc); + if (roc->idInDetUnit()idInDetUnit(); + if (roc->idInDetUnit()>ch.roc_last) ch.roc_last=roc->idInDetUnit(); + } + disabledChannelsDetSet.push_back(ch); + } + } + } else { + // fill list of detIds to be turned off by tracking + if(!tkerrorlist.empty()) { + auto it_find = std::find(tkerrorlist.begin(), tkerrorlist.end(), aPixelError.getType()); + if(it_find != tkerrorlist.end()){ + tkerror_detidcollection->push_back(errordetid); + } + } + } + + // fill list of detIds with errors to be studied + if(!usererrorlist.empty()) { + auto it_find = std::find(usererrorlist.begin(), usererrorlist.end(), aPixelError.getType()); + if(it_find != usererrorlist.end()){ + usererror_detidcollection->push_back(errordetid); + } + } + + } // loop on DetSet of errors + + if (!disabledChannelsDetSet.empty()) { + disabled_channelcollection->insert(errordetid, disabledChannelsDetSet.data(), disabledChannelsDetSet.size()); + } + + } // if error assigned to a real DetId + } // loop on errors in event for this FED + } // if errors to be included in the event + } // loop on FED data to be unpacked + + if(includeErrors) { + edm::DetSet& errorDetSet = errorcollection->find_or_insert(dummydetid); + errorDetSet.data = nodeterrors; + } + if (errorsInEvent) LogDebug("SiPixelRawToCluster") << "Error words were stored in this event"; + + // clusterize, originally from SiPixelClusterProducer + for(const auto detset: *collection) { + const auto detId = DetId(detset.detId()); + + std::vector badChannels; // why do we need this? + + // Comment: At the moment the clusterizer depends on geometry + // to access information as the pixel topology (number of columns + // and rows in a detector module). + // In the future the geometry service will be replaced with + // a ES service. + const GeomDetUnit * geoUnit = geom_->idToDetUnit( detId ); + const PixelGeomDetUnit * pixDet = dynamic_cast(geoUnit); + edmNew::DetSetVector::FastFiller spc(*outputClusters, detset.detId()); + clusterizer_.clusterizeDetUnit(detset, pixDet, ttopo_, badChannels, spc); + if ( spc.empty() ) { + spc.abort(); + } + } + outputClusters->shrink_to_fit(); + + //send digis and errors back to framework + ev.put(std::move(collection)); + if(includeErrors){ + ev.put(std::move(errorcollection)); + ev.put(std::move(tkerror_detidcollection)); + ev.put(std::move(usererror_detidcollection), "UserErrorModules"); + ev.put(std::move(disabled_channelcollection)); + } + ev.put(std::move(outputClusters)); +} + +// ----------------------------------------------------------------------------- +void SiPixelRawToClusterHeterogeneous::acquireGPUCuda(const edm::HeterogeneousEvent& ev, const edm::EventSetup& es, cuda::stream_t<>& cudaStream) { + const auto buffers = initialize(ev.event(), es); + + edm::ESHandle hgpuMap; + es.get().get(hgpuMap); + if(hgpuMap->hasQuality() != useQuality) { + throw cms::Exception("LogicError") << "UseQuality of the module (" << useQuality<< ") differs the one from SiPixelFedCablingMapGPUWrapper. Please fix your configuration."; + } + // get the GPU product already here so that the async transfer can begin + const auto *gpuMap = hgpuMap->getGPUProductAsync(cudaStream); + + edm::cuda::device::unique_ptr modulesToUnpackRegional; + const unsigned char *gpuModulesToUnpack; + if (regions_) { + modulesToUnpackRegional = hgpuMap->getModToUnpRegionalAsync(*(regions_->modulesToUnpack()), cudaStream); + gpuModulesToUnpack = modulesToUnpackRegional.get(); + } + else { + gpuModulesToUnpack = hgpuMap->getModToUnpAllAsync(cudaStream); + } + + + edm::ESHandle hgains; + es.get().get(hgains); + + errors_.clear(); + + // GPU specific: Data extraction for RawToDigi GPU + unsigned int wordCounterGPU = 0; + unsigned int fedCounter = 0; + bool errorsInEvent = false; + + // In CPU algorithm this loop is part of PixelDataFormatter::interpretRawData() + ErrorChecker errorcheck; + auto wordFedAppender = pixelgpudetails::SiPixelRawToClusterGPUKernel::WordFedAppender(cudaStream); + for (auto aFed = fedIds.begin(); aFed != fedIds.end(); ++aFed) { + int fedId = *aFed; + + if (!usePilotBlade && (fedId==40) ) continue; // skip pilot blade data + if (regions_ && !regions_->mayUnpackFED(fedId)) continue; + + // for GPU + // first 150 index stores the fedId and next 150 will store the + // start index of word in that fed + assert(fedId>=1200); + fedCounter++; + + // get event data for this fed + const FEDRawData& rawData = buffers->FEDData( fedId ); + + // GPU specific + int nWords = rawData.size()/sizeof(cms_uint64_t); + if (nWords == 0) { + continue; + } + + // check CRC bit + const cms_uint64_t* trailer = reinterpret_cast(rawData.data())+(nWords-1); + if (not errorcheck.checkCRC(errorsInEvent, fedId, trailer, errors_)) { + continue; + } + + // check headers + const cms_uint64_t* header = reinterpret_cast(rawData.data()); header--; + bool moreHeaders = true; + while (moreHeaders) { + header++; + bool headerStatus = errorcheck.checkHeader(errorsInEvent, fedId, header, errors_); + moreHeaders = headerStatus; + } + + // check trailers + bool moreTrailers = true; + trailer++; + while (moreTrailers) { + trailer--; + bool trailerStatus = errorcheck.checkTrailer(errorsInEvent, fedId, nWords, trailer, errors_); + moreTrailers = trailerStatus; + } + + const cms_uint32_t * bw = (const cms_uint32_t *)(header+1); + const cms_uint32_t * ew = (const cms_uint32_t *)(trailer); + + assert(0 == (ew-bw)%2); + wordFedAppender.initializeWordFed(fedId, wordCounterGPU, bw, (ew-bw)); + wordCounterGPU+=(ew-bw); + + } // end of for loop + + gpuAlgo_.makeClustersAsync(gpuMap, gpuModulesToUnpack, hgains->getGPUProductAsync(cudaStream), + wordFedAppender, + wordCounterGPU, fedCounter, convertADCtoElectrons, + useQuality, includeErrors, enableTransfer_, debug, cudaStream); +} + +void SiPixelRawToClusterHeterogeneous::produceGPUCuda(edm::HeterogeneousEvent& ev, const edm::EventSetup& es, cuda::stream_t<>& cudaStream) { + auto output = std::make_unique(gpuAlgo_.getProduct()); + + if(enableConversion_) { + convertGPUtoCPU(ev.event(), output->nDigis, gpuAlgo_.getCPUData()); + } + + ev.put(std::move(output), heterogeneous::DisableTransfer{}); +} + +void SiPixelRawToClusterHeterogeneous::convertGPUtoCPU(edm::Event& ev, + unsigned int nDigis, + pixelgpudetails::SiPixelRawToClusterGPUKernel::CPUData digis_clusters_h) const { + // TODO: add the transfers here as well? + + auto collection = std::make_unique>(); + auto errorcollection = std::make_unique>(); + auto tkerror_detidcollection = std::make_unique(); + auto usererror_detidcollection = std::make_unique(); + auto disabled_channelcollection = std::make_unique< edmNew::DetSetVector>(); + auto outputClusters = std::make_unique(); + + edm::DetSet * detDigis=nullptr; + for (uint32_t i = 0; i < nDigis; i++) { + if (digis_clusters_h.pdigi[i]==0) continue; + detDigis = &collection->find_or_insert(digis_clusters_h.rawIdArr[i]); + if ( (*detDigis).empty() ) (*detDigis).data.reserve(32); // avoid the first relocations + break; + } + + int32_t nclus=-1; + std::vector aclusters(1024); + auto totCluseFilled=0; + + auto fillClusters = [&](uint32_t detId){ + if (nclus<0) return; // this in reality should never happen + edmNew::DetSetVector::FastFiller spc(*outputClusters, detId); + auto layer = (DetId(detId).subdetId()==1) ? ttopo_->pxbLayer(detId) : 0; + auto clusterThreshold = (layer==1) ? 2000 : 4000; + for (int32_t ic=0; ic9000) continue; // not in cluster + assert(digis_clusters_h.rawIdArr[i] > 109999); + if ( (*detDigis).detId() != digis_clusters_h.rawIdArr[i]) + { + fillClusters((*detDigis).detId()); + assert(nclus==-1); + detDigis = &collection->find_or_insert(digis_clusters_h.rawIdArr[i]); + if ( (*detDigis).empty() ) + (*detDigis).data.reserve(32); // avoid the first relocations + else { std::cout << "Problem det present twice in input! " << (*detDigis).detId() << std::endl; } + } + (*detDigis).data.emplace_back(digis_clusters_h.pdigi[i]); + auto const & dig = (*detDigis).data.back(); + // fill clusters + assert(digis_clusters_h.clus[i]>=0); + assert(digis_clusters_h.clus[i]<1024); + nclus = std::max(digis_clusters_h.clus[i],nclus); + auto row = dig.row(); + auto col = dig.column(); + SiPixelCluster::PixelPos pix(row,col); + aclusters[digis_clusters_h.clus[i]].add(pix, digis_clusters_h.adc[i]); + } + + // fill final clusters + fillClusters((*detDigis).detId()); + //std::cout << "filled " << totCluseFilled << " clusters" << std::endl; + + PixelDataFormatter formatter(cabling_.get(), usePhase1); // for phase 1 & 0 + auto errors = errors_; // make a copy + PixelDataFormatter::DetErrors nodeterrors; + + auto size = digis_clusters_h.error->size(); + for (auto i = 0; i < size; i++) { + pixelgpudetails::error_obj err = (*digis_clusters_h.error)[i]; + if (err.errorType != 0) { + SiPixelRawDataError error(err.word, err.errorType, err.fedId + 1200); + errors[err.rawId].push_back(error); + } + } + + // pack errors into collection + if (includeErrors) { + + typedef PixelDataFormatter::Errors::iterator IE; + for (IE is = errors.begin(); is != errors.end(); is++) { + + uint32_t errordetid = is->first; + if (errordetid == dummydetid) {// errors given dummy detId must be sorted by Fed + nodeterrors.insert( nodeterrors.end(), errors[errordetid].begin(), errors[errordetid].end() ); + } + else { + edm::DetSet& errorDetSet = errorcollection->find_or_insert(errordetid); + errorDetSet.data.insert(errorDetSet.data.end(), is->second.begin(), is->second.end()); + // Fill detid of the detectors where there is error AND the error number is listed + // in the configurable error list in the job option cfi. + // Code needs to be here, because there can be a set of errors for each + // entry in the for loop over PixelDataFormatter::Errors + + std::vector disabledChannelsDetSet; + + for (auto const& aPixelError : errorDetSet) { + // For the time being, we extend the error handling functionality with ErrorType 25 + // In the future, we should sort out how the usage of tkerrorlist can be generalized + if (aPixelError.getType() == 25) { + int fedId = aPixelError.getFedId(); + const sipixelobjects::PixelFEDCabling* fed = cabling_->fed(fedId); + if (fed) { + cms_uint32_t linkId = formatter.linkId(aPixelError.getWord32()); + const sipixelobjects::PixelFEDLink* link = fed->link(linkId); + if (link) { + // The "offline" 0..15 numbering is fixed by definition, also, the FrameConversion depends on it + // in contrast, the ROC-in-channel numbering is determined by hardware --> better to use the "offline" scheme + PixelFEDChannel ch = {fed->id(), linkId, 25, 0}; + for (unsigned int iRoc = 1; iRoc <= link->numberOfROCs(); iRoc++) { + const sipixelobjects::PixelROC * roc = link->roc(iRoc); + if (roc->idInDetUnit() < ch.roc_first) ch.roc_first = roc->idInDetUnit(); + if (roc->idInDetUnit() > ch.roc_last) ch.roc_last = roc->idInDetUnit(); + } + if (ch.roc_firstpush_back(errordetid); + } + } + } + + // fill list of detIds with errors to be studied + if (!usererrorlist.empty()) { + auto it_find = std::find(usererrorlist.begin(), usererrorlist.end(), aPixelError.getType()); + if (it_find != usererrorlist.end()) { + usererror_detidcollection->push_back(errordetid); + } + } + + } // loop on DetSet of errors + + if (!disabledChannelsDetSet.empty()) { + disabled_channelcollection->insert(errordetid, disabledChannelsDetSet.data(), disabledChannelsDetSet.size()); + } + + } // if error assigned to a real DetId + } // loop on errors in event for this FED + } // if errors to be included in the event + + if (includeErrors) { + edm::DetSet& errorDetSet = errorcollection->find_or_insert(dummydetid); + errorDetSet.data = nodeterrors; + } + + ev.put(std::move(collection)); + if(includeErrors){ + ev.put(std::move(errorcollection)); + ev.put(std::move(tkerror_detidcollection)); + ev.put(std::move(usererror_detidcollection), "UserErrorModules"); + ev.put(std::move(disabled_channelcollection)); + } + ev.put(std::move(outputClusters)); +} + +// define as framework plugin +DEFINE_FWK_MODULE(SiPixelRawToClusterHeterogeneous); diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/gpuCalibPixel.h b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuCalibPixel.h new file mode 100644 index 0000000000000..5a681e791f94f --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuCalibPixel.h @@ -0,0 +1,125 @@ +#ifndef RecoLocalTracker_SiPixelClusterizer_plugins_gpuCalibPixel_h +#define RecoLocalTracker_SiPixelClusterizer_plugins_gpuCalibPixel_h + +#include +#include + +#include "CondFormats/SiPixelObjects/interface/SiPixelGainForHLTonGPU.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +namespace gpuCalibPixel { + + constexpr uint16_t InvId=9999; // must be > MaxNumModules + + constexpr float VCaltoElectronGain = 47; // L2-4: 47 +- 4.7 + constexpr float VCaltoElectronGain_L1 = 50; // L1: 49.6 +- 2.6 + constexpr float VCaltoElectronOffset = -60; // L2-4: -60 +- 130 + constexpr float VCaltoElectronOffset_L1 = -670; // L1: -670 +- 220 + + + __global__ void calibDigis(uint16_t * id, + uint16_t const * __restrict__ x, + uint16_t const * __restrict__ y, + uint16_t * adc, + SiPixelGainForHLTonGPU const * __restrict__ ped, + int numElements + ) +{ + + int i = blockDim.x * blockIdx.x + threadIdx.x; + if (i >= numElements) return; + if (InvId==id[i]) return; + + float conversionFactor = id[i]<96 ? VCaltoElectronGain_L1 : VCaltoElectronGain; + float offset = id[i]<96 ? VCaltoElectronOffset_L1 : VCaltoElectronOffset; + + bool isDeadColumn=false, isNoisyColumn=false; + + int row = x[i]; + int col = y[i]; + auto ret = ped->getPedAndGain(id[i], col, row, isDeadColumn, isNoisyColumn); + float pedestal = ret.first; float gain = ret.second; + // float pedestal = 0; float gain = 1.; + if ( isDeadColumn | isNoisyColumn ) + { + id[i]=InvId; adc[i] =0; + printf("bad pixel at %d in %d\n",i,id[i]); + } + else { + float vcal = adc[i] * gain - pedestal*gain; + adc[i] = std::max(100, int( vcal * conversionFactor + offset)); + } + + // if (threadIdx.x==0) + // printf ("calibrated %d\n",id[i]); +} + + __global__ void calibADCByModule(uint16_t * id, + uint16_t const * __restrict__ x, + uint16_t const * __restrict__ y, + uint16_t * adc, + uint32_t * moduleStart, + SiPixelGainForHLTonGPU const * __restrict__ ped, + int numElements + ) +{ + + + auto first = moduleStart[1 + blockIdx.x]; + + auto me = id[first]; + + assert(me<2000); + + /// depends on "me" + + float conversionFactor = me<96 ? VCaltoElectronGain_L1 : VCaltoElectronGain; + float offset = me<96 ? VCaltoElectronOffset_L1 : VCaltoElectronOffset; + + +#ifdef GPU_DEBUG + if (me%100==1) + if (threadIdx.x==0) printf("start pixel calibration for module %d in block %d\n",me,blockIdx.x); +#endif + + first+=threadIdx.x; + + // __syncthreads(); + + float pedestal=0,gain=0; + bool isDeadColumn=false, isNoisyColumn=false; + int oldCol=-1, oldAveragedBlock=-1; + + for (int i=first; inumberOfRowsAveragedOver_; // 80.... ( row<80 will be faster...) + if ( (col!=oldCol) | ( averagedBlock != oldAveragedBlock) ) { + oldCol=col; oldAveragedBlock= averagedBlock; + auto ret = ped->getPedAndGain(me,col, row, isDeadColumn, isNoisyColumn); + pedestal = ret.first; gain = ret.second; + } + if ( isDeadColumn | isNoisyColumn ) + { id[i]=InvId; adc[i] =0; } + else { + float vcal = adc[i] * gain - pedestal*gain; + adc[i] = std::max(100, int( vcal * conversionFactor + offset)); + } + } + + __syncthreads(); + //reset start + if(0==threadIdx.x) { + auto & k = moduleStart[1 + blockIdx.x]; + while (id[k]==InvId) ++k; + } + + + } + + +} + +#endif // RecoLocalTracker_SiPixelClusterizer_plugins_gpuCalibPixel_h diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusterChargeCut.h b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusterChargeCut.h new file mode 100644 index 0000000000000..855216960d659 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusterChargeCut.h @@ -0,0 +1,97 @@ +#ifndef RecoLocalTracker_SiPixelClusterizer_plugins_gpuClusterChargeCut_h +#define RecoLocalTracker_SiPixelClusterizer_plugins_gpuClusterChargeCut_h + +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "HeterogeneousCore/CUDAUtilities/interface/prefixScan.h" + +#include "gpuClusteringConstants.h" + +namespace gpuClustering { + + __global__ void clusterChargeCut( + uint16_t * __restrict__ id, // module id of each pixel (modified if bad cluster) + uint16_t const * __restrict__ adc, // charge of each pixel + uint32_t const * __restrict__ moduleStart, // index of the first pixel of each module + uint32_t * __restrict__ nClustersInModule, // modified: number of clusters found in each module + uint32_t const * __restrict__ moduleId, // module id of each module + int32_t * __restrict__ clusterId, // modified: cluster id of each pixel + int numElements) + { + + if (blockIdx.x >= moduleStart[0]) + return; + + auto firstPixel = moduleStart[1 + blockIdx.x]; + auto thisModuleId = id[firstPixel]; + assert(thisModuleId < MaxNumModules); + assert(thisModuleId==moduleId[blockIdx.x]); + + auto nclus = nClustersInModule[thisModuleId]; + if (nclus==0) return; + + assert(nclus<=MaxNumClustersPerModules); + +#ifdef GPU_DEBUG + if (thisModuleId % 100 == 1) + if (threadIdx.x == 0) + printf("start clusterizer for module %d in block %d\n", thisModuleId, blockIdx.x); +#endif + + auto first = firstPixel + threadIdx.x; + + __shared__ int32_t charge[MaxNumClustersPerModules]; + for (int i=threadIdx.x; ichargeCut ? 1 : 0; + } + + __syncthreads(); + + // renumber + __shared__ uint16_t ws[32]; + blockPrefixScan(newclusId, nclus, ws); + + assert(nclus>=newclusId[nclus-1]); + + if(nclus==newclusId[nclus-1]) return; + + nClustersInModule[thisModuleId] = newclusId[nclus-1]; + __syncthreads(); + + // mark bad cluster again + for (int i=threadIdx.x; i +#include + +#include "Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h" +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +#include "gpuClusteringConstants.h" + +namespace gpuClustering { + + __global__ void countModules(uint16_t const * __restrict__ id, + uint32_t * __restrict__ moduleStart, + int32_t * __restrict__ clusterId, + int numElements) + { + int i = blockDim.x * blockIdx.x + threadIdx.x; + if (i >= numElements) + return; + clusterId[i] = i; + if (InvId == id[i]) + return; + auto j = i - 1; + while (j >= 0 and id[j] == InvId) + --j; + if (j < 0 or id[j] != id[i]) { + // boundary... + auto loc = atomicInc(moduleStart, MaxNumModules); + moduleStart[loc + 1] = i; + } + } + + __global__ +// __launch_bounds__(256,4) + void findClus(uint16_t const * __restrict__ id, // module id of each pixel + uint16_t const * __restrict__ x, // local coordinates of each pixel + uint16_t const * __restrict__ y, // + uint32_t const * __restrict__ moduleStart, // index of the first pixel of each module + uint32_t * __restrict__ nClustersInModule, // output: number of clusters found in each module + uint32_t * __restrict__ moduleId, // output: module id of each module + int32_t * __restrict__ clusterId, // output: cluster id of each pixel + int numElements) + { + + if (blockIdx.x >= moduleStart[0]) + return; + + auto firstPixel = moduleStart[1 + blockIdx.x]; + auto thisModuleId = id[firstPixel]; + assert(thisModuleId < MaxNumModules); + +#ifdef GPU_DEBUG + if (thisModuleId % 100 == 1) + if (threadIdx.x == 0) + printf("start clusterizer for module %d in block %d\n", thisModuleId, blockIdx.x); +#endif + + auto first = firstPixel + threadIdx.x; + + // find the index of the first pixel not belonging to this module (or invalid) + __shared__ int msize; + msize = numElements; + __syncthreads(); + + // skip threads not associated to an existing pixel + for (int i = first; i < numElements; i += blockDim.x) { + if (id[i] == InvId) // skip invalid pixels + continue; + if (id[i] != thisModuleId) { // find the first pixel in a different module + atomicMin(&msize, i); + break; + } + } + + //init hist (ymax=416 < 512 : 9bits) + constexpr uint32_t maxPixInModule = 4000; + constexpr auto nbins = phase1PixelTopology::numColsInModule + 2; //2+2; + using Hist = HistoContainer; + __shared__ Hist hist; + __shared__ typename Hist::Counter ws[32]; + for (auto j=threadIdx.x; j60) atomicAdd(&n60,1); + if(hist.size(j)>40) atomicAdd(&n40,1); + } + __syncthreads(); + if (0==threadIdx.x) { + if (n60>0) printf("columns with more than 60 px %d in %d\n",n60,thisModuleId); + else if (n40>0) printf("columns with more than 40 px %d in %d\n",n40,thisModuleId); + } + __syncthreads(); +#endif + + // fill NN + for (int j=threadIdx.x, k = 0; j 1) continue; + auto l = nnn[k]++; + assert(l<5); + nn[k][l]=*p; + } + } + + // for each pixel, look at all the pixels until the end of the module; + // when two valid pixels within +/- 1 in x or y are found, set their id to the minimum; + // after the loop, all the pixel in each cluster should have the id equeal to the lowest + // pixel in the cluster ( clus[i] == i ). + bool more = true; + int nloops=0; + while (__syncthreads_or(more)) { + if (1==nloops%2) { + for (int j=threadIdx.x, k = 0; j= 0) { + // mark each pixel in a cluster with the same id as the first one + clusterId[i] = clusterId[clusterId[i]]; + } + } + __syncthreads(); + + // adjust the cluster id to be a positive value starting from 0 + for (int i = first; i < msize; i += blockDim.x) { + if (id[i] == InvId) { // skip invalid pixels + clusterId[i] = -9999; + continue; + } + clusterId[i] = - clusterId[i] - 1; + } + __syncthreads(); + + if (threadIdx.x == 0) { + nClustersInModule[thisModuleId] = foundClusters; + moduleId[blockIdx.x] = thisModuleId; +#ifdef GPU_DEBUG + if (thisModuleId % 100 == 1) + if (threadIdx.x == 0) + printf("%d clusters in module %d\n", foundClusters, thisModuleId); +#endif + } + } + +} // namespace gpuClustering + +#endif // RecoLocalTracker_SiPixelClusterizer_plugins_gpuClustering_h diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusteringConstants.h b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusteringConstants.h new file mode 100644 index 0000000000000..7b4bb5a1c8c95 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusteringConstants.h @@ -0,0 +1,14 @@ +#ifndef RecoLocalTracker_SiPixelClusterizer_plugins_gpuClusteringConstants_h +#define RecoLocalTracker_SiPixelClusterizer_plugins_gpuClusteringConstants_h + +#include + +namespace gpuClustering { + constexpr uint32_t MaxNumModules = 2000; + constexpr uint32_t MaxNumPixels = 256 * 2000; // this does not mean maxPixelPerModule == 256! + constexpr uint32_t MaxNumClustersPerModules = 1024; + constexpr uint16_t InvId = 9999; // must be > MaxNumModules + +} + +#endif // RecoLocalTracker_SiPixelClusterizer_plugins_gpuClusteringConstants_h diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h b/RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h new file mode 100644 index 0000000000000..3b81e4a16f017 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h @@ -0,0 +1,47 @@ +#ifndef EventFilter_SiPixelRawToDigi_siPixelRawToClusterHeterogeneousProduct_h +#define EventFilter_SiPixelRawToDigi_siPixelRawToClusterHeterogeneousProduct_h + +#include "CUDADataFormats/SiPixelDigi/interface/SiPixelDigisCUDA.h" +#include "CUDADataFormats/SiPixelCluster/interface/SiPixelClustersCUDA.h" +#include "FWCore/Utilities/interface/typedefs.h" +#include "HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +namespace siPixelRawToClusterHeterogeneousProduct { + using CPUProduct = int; // dummy... + + struct error_obj { + uint32_t rawId; + uint32_t word; + unsigned char errorType; + unsigned char fedId; + }; + + // FIXME split in two + struct GPUProduct { + GPUProduct() = default; + GPUProduct(const GPUProduct&) = delete; + GPUProduct& operator=(const GPUProduct&) = delete; + GPUProduct(GPUProduct&&) = default; + GPUProduct& operator=(GPUProduct&&) = default; + + GPUProduct(SiPixelDigisCUDA&& digis, + SiPixelClustersCUDA&& clusters, + uint32_t ndig, uint32_t nmod, uint32_t nclus): + digis_d(std::move(digis)), clusters_d(std::move(clusters)), + nDigis(ndig), nModules(nmod), nClusters(nclus) + {} + + SiPixelDigisCUDA digis_d; + SiPixelClustersCUDA clusters_d; + + uint32_t nDigis; + uint32_t nModules; + uint32_t nClusters; + }; + + using HeterogeneousDigiCluster = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct >; +} + +#endif diff --git a/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizerPreSplitting_cfi.py b/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizerPreSplitting_cfi.py index ba8d492c5f610..bb0bb85697a99 100644 --- a/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizerPreSplitting_cfi.py +++ b/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizerPreSplitting_cfi.py @@ -1,7 +1,11 @@ - import FWCore.ParameterSet.Config as cms -# from CondTools.SiPixel.SiPixelGainCalibrationService_cfi import * from RecoLocalTracker.SiPixelClusterizer.SiPixelClusterizer_cfi import siPixelClusters as _siPixelClusters siPixelClustersPreSplitting = _siPixelClusters.clone() + +from Configuration.ProcessModifiers.gpu_cff import gpu +from RecoLocalTracker.SiPixelClusterizer.siPixelClustersHeterogeneous_cfi import siPixelClustersHeterogeneous as _siPixelClustersHeterogeneous +from RecoLocalTracker.SiPixelClusterizer.siPixelFedCablingMapGPUWrapper_cfi import * +from CalibTracker.SiPixelESProducers.siPixelGainCalibrationForHLTGPU_cfi import * +gpu.toReplaceWith(siPixelClustersPreSplitting, _siPixelClustersHeterogeneous.clone()) diff --git a/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizer_cfi.py b/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizer_cfi.py index bf0bda3b4c013..d52e9fea5a3d6 100644 --- a/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizer_cfi.py +++ b/RecoLocalTracker/SiPixelClusterizer/python/SiPixelClusterizer_cfi.py @@ -32,6 +32,10 @@ Phase2KinkADC = cms.int32(8), ) +# *only for the cms-patatrack repository* +# ensure reproducibility for CPU <--> GPU comparisons +siPixelClusters.payloadType = "HLT" + # phase1 pixel from Configuration.Eras.Modifier_phase1Pixel_cff import phase1Pixel phase1Pixel.toModify(siPixelClusters, diff --git a/RecoLocalTracker/SiPixelClusterizer/python/siPixelClustersHeterogeneous_cfi.py b/RecoLocalTracker/SiPixelClusterizer/python/siPixelClustersHeterogeneous_cfi.py new file mode 100644 index 0000000000000..b86520bb28287 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/python/siPixelClustersHeterogeneous_cfi.py @@ -0,0 +1,37 @@ +import FWCore.ParameterSet.Config as cms + +from RecoLocalTracker.SiPixelClusterizer.siPixelClustersHeterogeneousDefault_cfi import siPixelClustersHeterogeneousDefault as _siPixelClustersHeterogeneousDefault +siPixelClustersHeterogeneous = _siPixelClustersHeterogeneousDefault.clone() + +# following copied from SiPixelRawToDigi_cfi +siPixelClustersHeterogeneous.IncludeErrors = cms.bool(True) +siPixelClustersHeterogeneous.InputLabel = cms.InputTag("rawDataCollector") +siPixelClustersHeterogeneous.UseQualityInfo = cms.bool(False) +## ErrorList: list of error codes used by tracking to invalidate modules +siPixelClustersHeterogeneous.ErrorList = cms.vint32(29) +## UserErrorList: list of error codes used by Pixel experts for investigation +siPixelClustersHeterogeneous.UserErrorList = cms.vint32(40) +## Use pilot blades +siPixelClustersHeterogeneous.UsePilotBlade = cms.bool(False) +## Use phase1 +siPixelClustersHeterogeneous.UsePhase1 = cms.bool(False) +## Empty Regions PSet means complete unpacking +siPixelClustersHeterogeneous.Regions = cms.PSet( ) +siPixelClustersHeterogeneous.CablingMapLabel = cms.string("") + +# The following is copied from siPixelClusters_cfi, clearly not +# maintainable in the long run +from Configuration.Eras.Modifier_phase1Pixel_cff import phase1Pixel +phase1Pixel.toModify(siPixelClustersHeterogeneous, + VCaltoElectronGain = cms.int32(47), # L2-4: 47 +- 4.7 + VCaltoElectronGain_L1 = cms.int32(50), # L1: 49.6 +- 2.6 + VCaltoElectronOffset = cms.int32(-60), # L2-4: -60 +- 130 + VCaltoElectronOffset_L1 = cms.int32(-670), # L1: -670 +- 220 + ChannelThreshold = cms.int32(10), + SeedThreshold = cms.int32(1000), + ClusterThreshold = cms.int32(4000), + ClusterThreshold_L1 = cms.int32(2000) +) + +# The following is copied from SiPixelRawToDigi_cfi +phase1Pixel.toModify(siPixelClustersHeterogeneous, UsePhase1=True) diff --git a/RecoLocalTracker/SiPixelClusterizer/src/ES_SiPixelFedCablingMapGPUWrapper.cc b/RecoLocalTracker/SiPixelClusterizer/src/ES_SiPixelFedCablingMapGPUWrapper.cc new file mode 100644 index 0000000000000..1f68e4bf9a534 --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/src/ES_SiPixelFedCablingMapGPUWrapper.cc @@ -0,0 +1,4 @@ +#include "RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h" +#include "FWCore/Utilities/interface/typelookup.h" + +TYPELOOKUP_DATA_REG(SiPixelFedCablingMapGPUWrapper); diff --git a/RecoLocalTracker/SiPixelClusterizer/src/SiPixelFedCablingMapGPUWrapper.cc b/RecoLocalTracker/SiPixelClusterizer/src/SiPixelFedCablingMapGPUWrapper.cc new file mode 100644 index 0000000000000..b652100f69e9f --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/src/SiPixelFedCablingMapGPUWrapper.cc @@ -0,0 +1,188 @@ +// C++ includes +#include +#include +#include +#include + +// CUDA includes +#include + +// CMSSW includes +#include "CondFormats/SiPixelObjects/interface/SiPixelFedCablingMap.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelFedCablingTree.h" +#include "CondFormats/SiPixelObjects/interface/SiPixelQuality.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "Geometry/CommonDetUnit/interface/GeomDetType.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "RecoLocalTracker/SiPixelClusterizer/interface/SiPixelFedCablingMapGPUWrapper.h" + +SiPixelFedCablingMapGPUWrapper::SiPixelFedCablingMapGPUWrapper(SiPixelFedCablingMap const& cablingMap, + TrackerGeometry const& trackerGeom, + SiPixelQuality const *badPixelInfo): + cablingMap_(&cablingMap), + fedMap(pixelgpudetails::MAX_SIZE), linkMap(pixelgpudetails::MAX_SIZE), rocMap(pixelgpudetails::MAX_SIZE), + RawId(pixelgpudetails::MAX_SIZE), rocInDet(pixelgpudetails::MAX_SIZE), moduleId(pixelgpudetails::MAX_SIZE), + badRocs(pixelgpudetails::MAX_SIZE), modToUnpDefault(pixelgpudetails::MAX_SIZE), + hasQuality_(badPixelInfo != nullptr) +{ + std::vector const& fedIds = cablingMap.fedIds(); + std::unique_ptr const& cabling = cablingMap.cablingTree(); + + unsigned int startFed = *(fedIds.begin()); + unsigned int endFed = *(fedIds.end() - 1); + + sipixelobjects::CablingPathToDetUnit path; + int index = 1; + + for (unsigned int fed = startFed; fed <= endFed; fed++) { + for (unsigned int link = 1; link <= pixelgpudetails::MAX_LINK; link++) { + for (unsigned int roc = 1; roc <= pixelgpudetails::MAX_ROC; roc++) { + path = {fed, link, roc}; + const sipixelobjects::PixelROC* pixelRoc = cabling->findItem(path); + fedMap[index] = fed; + linkMap[index] = link; + rocMap[index] = roc; + if (pixelRoc != nullptr) { + RawId[index] = pixelRoc->rawId(); + rocInDet[index] = pixelRoc->idInDetUnit(); + modToUnpDefault[index] = false; + if (badPixelInfo != nullptr) + badRocs[index] = badPixelInfo->IsRocBad(pixelRoc->rawId(), pixelRoc->idInDetUnit()); + else + badRocs[index] = false; + } else { // store some dummy number + RawId[index] = 9999; + rocInDet[index] = 9999; + badRocs[index] = true; + modToUnpDefault[index] = true; + } + index++; + } + } + } // end of FED loop + + // Given FedId, Link and idinLnk; use the following formula + // to get the RawId and idinDU + // index = (FedID-1200) * MAX_LINK* MAX_ROC + (Link-1)* MAX_ROC + idinLnk; + // where, MAX_LINK = 48, MAX_ROC = 8 for Phase1 as mentioned Danek's email + // FedID varies between 1200 to 1338 (In total 108 FED's) + // Link varies between 1 to 48 + // idinLnk varies between 1 to 8 + + for (int i = 1; i < index; i++) { + if (RawId[i] == 9999) { + moduleId[i] = 9999; + } else { + /* + std::cout << RawId[i] << std::endl; + */ + auto gdet = trackerGeom.idToDetUnit(RawId[i]); + if (!gdet) { + LogDebug("SiPixelFedCablingMapGPU") << " Not found: " << RawId[i] << std::endl; + continue; + } + moduleId[i] = gdet->index(); + } + LogDebug("SiPixelFedCablingMapGPU") << "----------------------------------------------------------------------------" << std::endl; + LogDebug("SiPixelFedCablingMapGPU") << i << std::setw(20) << fedMap[i] << std::setw(20) << linkMap[i] << std::setw(20) << rocMap[i] << std::endl; + LogDebug("SiPixelFedCablingMapGPU") << i << std::setw(20) << RawId[i] << std::setw(20) << rocInDet[i] << std::setw(20) << moduleId[i] << std::endl; + LogDebug("SiPixelFedCablingMapGPU") << i << std::setw(20) << (bool)badRocs[i] << std::setw(20) << std::endl; + LogDebug("SiPixelFedCablingMapGPU") << "----------------------------------------------------------------------------" << std::endl; + + } + + size = index-1; +} + + +SiPixelFedCablingMapGPUWrapper::~SiPixelFedCablingMapGPUWrapper() {} + + +const SiPixelFedCablingMapGPU *SiPixelFedCablingMapGPUWrapper::getGPUProductAsync(cuda::stream_t<>& cudaStream) const { + const auto& data = gpuData_.dataForCurrentDeviceAsync(cudaStream, [this](GPUData& data, cuda::stream_t<>& stream) { + // allocate + cudaCheck(cudaMallocHost((void**) & data.cablingMapHost, sizeof(SiPixelFedCablingMapGPU))); + cudaCheck(cudaMalloc((void**) & data.cablingMapDevice, sizeof(SiPixelFedCablingMapGPU))); + cudaCheck(cudaMalloc((void**) & data.cablingMapHost->fed, pixelgpudetails::MAX_SIZE_BYTE_INT)); + cudaCheck(cudaMalloc((void**) & data.cablingMapHost->link, pixelgpudetails::MAX_SIZE_BYTE_INT)); + cudaCheck(cudaMalloc((void**) & data.cablingMapHost->roc, pixelgpudetails::MAX_SIZE_BYTE_INT)); + cudaCheck(cudaMalloc((void**) & data.cablingMapHost->RawId, pixelgpudetails::MAX_SIZE_BYTE_INT)); + cudaCheck(cudaMalloc((void**) & data.cablingMapHost->rocInDet, pixelgpudetails::MAX_SIZE_BYTE_INT)); + cudaCheck(cudaMalloc((void**) & data.cablingMapHost->moduleId, pixelgpudetails::MAX_SIZE_BYTE_INT)); + cudaCheck(cudaMalloc((void**) & data.cablingMapHost->badRocs, pixelgpudetails::MAX_SIZE_BYTE_BOOL)); + + // transfer + data.cablingMapHost->size = this->size; + cudaCheck(cudaMemcpyAsync(data.cablingMapHost->fed, this->fedMap.data(), this->fedMap.size() * sizeof(unsigned int), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.cablingMapHost->link, this->linkMap.data(), this->linkMap.size() * sizeof(unsigned int), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.cablingMapHost->roc, this->rocMap.data(), this->rocMap.size() * sizeof(unsigned int), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.cablingMapHost->RawId, this->RawId.data(), this->RawId.size() * sizeof(unsigned int), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.cablingMapHost->rocInDet, this->rocInDet.data(), this->rocInDet.size() * sizeof(unsigned int), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.cablingMapHost->moduleId, this->moduleId.data(), this->moduleId.size() * sizeof(unsigned int), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.cablingMapHost->badRocs, this->badRocs.data(), this->badRocs.size() * sizeof(unsigned char), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.cablingMapDevice, data.cablingMapHost, sizeof(SiPixelFedCablingMapGPU), cudaMemcpyDefault, stream.id())); + }); + return data.cablingMapDevice; +} + +const unsigned char *SiPixelFedCablingMapGPUWrapper::getModToUnpAllAsync(cuda::stream_t<>& cudaStream) const { + const auto& data = modToUnp_.dataForCurrentDeviceAsync(cudaStream, [this](ModulesToUnpack& data, cuda::stream_t<>& stream) { + cudaCheck(cudaMalloc((void**) & data.modToUnpDefault, pixelgpudetails::MAX_SIZE_BYTE_BOOL)); + cudaCheck(cudaMemcpyAsync(data.modToUnpDefault, this->modToUnpDefault.data(), this->modToUnpDefault.size() * sizeof(unsigned char), cudaMemcpyDefault, stream.id())); + }); + return data.modToUnpDefault; +} + +edm::cuda::device::unique_ptr SiPixelFedCablingMapGPUWrapper::getModToUnpRegionalAsync(std::set const& modules, cuda::stream_t<>& cudaStream) const { + edm::Service cs; + auto modToUnpDevice = cs->make_device_unique(pixelgpudetails::MAX_SIZE, cudaStream); + auto modToUnpHost = cs->make_host_unique(pixelgpudetails::MAX_SIZE, cudaStream); + + std::vector const& fedIds = cablingMap_->fedIds(); + std::unique_ptr const& cabling = cablingMap_->cablingTree(); + + unsigned int startFed = *(fedIds.begin()); + unsigned int endFed = *(fedIds.end() - 1); + + sipixelobjects::CablingPathToDetUnit path; + int index = 1; + + for (unsigned int fed = startFed; fed <= endFed; fed++) { + for (unsigned int link = 1; link <= pixelgpudetails::MAX_LINK; link++) { + for (unsigned int roc = 1; roc <= pixelgpudetails::MAX_ROC; roc++) { + path = {fed, link, roc}; + const sipixelobjects::PixelROC* pixelRoc = cabling->findItem(path); + if (pixelRoc != nullptr) { + modToUnpHost[index] = (not modules.empty()) and (modules.find(pixelRoc->rawId()) == modules.end()); + } else { // store some dummy number + modToUnpHost[index] = true; + } + index++; + } + } + } + + cuda::memory::async::copy(modToUnpDevice.get(), modToUnpHost.get(), pixelgpudetails::MAX_SIZE * sizeof(unsigned char), cudaStream.id()); + return modToUnpDevice; +} + + +SiPixelFedCablingMapGPUWrapper::GPUData::~GPUData() { + if(cablingMapHost != nullptr) { + cudaCheck(cudaFree(cablingMapHost->fed)); + cudaCheck(cudaFree(cablingMapHost->link)); + cudaCheck(cudaFree(cablingMapHost->roc)); + cudaCheck(cudaFree(cablingMapHost->RawId)); + cudaCheck(cudaFree(cablingMapHost->rocInDet)); + cudaCheck(cudaFree(cablingMapHost->moduleId)); + cudaCheck(cudaFree(cablingMapHost->badRocs)); + cudaCheck(cudaFreeHost(cablingMapHost)); + } + cudaCheck(cudaFree(cablingMapDevice)); +} + +SiPixelFedCablingMapGPUWrapper::ModulesToUnpack::~ModulesToUnpack() { + cudaCheck(cudaFree(modToUnpDefault)); +} diff --git a/RecoLocalTracker/SiPixelClusterizer/test/BuildFile.xml b/RecoLocalTracker/SiPixelClusterizer/test/BuildFile.xml index 3445783781551..335591b583b58 100644 --- a/RecoLocalTracker/SiPixelClusterizer/test/BuildFile.xml +++ b/RecoLocalTracker/SiPixelClusterizer/test/BuildFile.xml @@ -31,3 +31,18 @@ + + + + + + + + + + + + + + + diff --git a/RecoLocalTracker/SiPixelClusterizer/test/gpuClustering.cu b/RecoLocalTracker/SiPixelClusterizer/test/gpuClustering.cu new file mode 100644 index 0000000000000..39c56d674eb1e --- /dev/null +++ b/RecoLocalTracker/SiPixelClusterizer/test/gpuClustering.cu @@ -0,0 +1,337 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "RecoLocalTracker/SiPixelClusterizer/plugins/gpuClustering.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusterChargeCut.h" + +int main(void) +{ + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + using namespace gpuClustering; + + int numElements = MaxNumPixels; + // these in reality are already on GPU + auto h_id = std::make_unique(numElements); + auto h_x = std::make_unique(numElements); + auto h_y = std::make_unique(numElements); + auto h_adc = std::make_unique(numElements); + + auto h_clus = std::make_unique(numElements); + + auto current_device = cuda::device::current::get(); + auto d_id = cuda::memory::device::make_unique(current_device, numElements); + auto d_x = cuda::memory::device::make_unique(current_device, numElements); + auto d_y = cuda::memory::device::make_unique(current_device, numElements); + auto d_adc = cuda::memory::device::make_unique(current_device, numElements); + + auto d_clus = cuda::memory::device::make_unique(current_device, numElements); + + auto d_moduleStart = cuda::memory::device::make_unique(current_device, MaxNumModules+1); + + auto d_clusInModule = cuda::memory::device::make_unique(current_device, MaxNumModules); + auto d_moduleId = cuda::memory::device::make_unique(current_device, MaxNumModules); + + // later random number + int n=0; + int ncl=0; + int y[10]={5,7,9,1,3,0,4,8,2,6}; + + auto generateClusters = [&](int kn) { + auto addBigNoise = 1==kn%2; + if (addBigNoise) { + constexpr int MaxPixels = 1000; + int id = 666; + for (int x=0; x<140; x+=3) { + for (int yy=0; yy<400; yy+=3) { + h_id[n]=id; + h_x[n]=x; + h_y[n]=yy; + h_adc[n]=1000; + ++n; ++ncl; + if (MaxPixels<=ncl) break; + } + if (MaxPixels<=ncl) break; + } + } + + { + // isolated + int id = 42; + int x = 10; + ++ncl; + h_id[n]=id; + h_x[n]=x; + h_y[n]=x; + h_adc[n]= kn==0 ? 100 : 5000; + ++n; + + // first column + ++ncl; + h_id[n]=id; + h_x[n]=x; + h_y[n]=0; + h_adc[n]= 5000; + ++n; + // first columns + ++ncl; + h_id[n]=id; + h_x[n]=x+80; + h_y[n]=2; + h_adc[n]= 5000; + ++n; + h_id[n]=id; + h_x[n]=x+80; + h_y[n]=1; + h_adc[n]= 5000; + ++n; + + // last column + ++ncl; + h_id[n]=id; + h_x[n]=x; + h_y[n]=415; + h_adc[n]= 5000; + ++n; + // last columns + ++ncl; + h_id[n]=id; + h_x[n]=x+80; + h_y[n]=415; + h_adc[n]= 2500; + ++n; + h_id[n]=id; + h_x[n]=x+80; + h_y[n]=414; + h_adc[n]= 2500; + ++n; + + // diagonal + ++ncl; + for (int x=20; x<25; ++x) { + h_id[n]=id; + h_x[n]=x; + h_y[n]=x; + h_adc[n]=1000; + ++n; + } + ++ncl; + // reversed + for (int x=45; x>40; --x) { + h_id[n]=id; + h_x[n]=x; + h_y[n]=x; + h_adc[n]=1000; + ++n; + } + ++ncl; + h_id[n++]=InvId; // error + // messy + int xx[5] = {21,25,23,24,22}; + for (int k=0; k<5; ++k) { + h_id[n]=id; + h_x[n]=xx[k]; + h_y[n]=20+xx[k]; + h_adc[n]=1000; + ++n; + } + // holes + ++ncl; + for (int k=0; k<5; ++k) { + h_id[n]=id; + h_x[n]=xx[k]; + h_y[n]=100; + h_adc[n]= kn==2 ? 100 : 1000; + ++n; + if (xx[k]%2==0) { + h_id[n]=id; + h_x[n]=xx[k]; + h_y[n]=101; + h_adc[n]=1000; + ++n; + } + } + } + { + // id == 0 (make sure it works! + int id = 0; + int x = 10; + ++ncl; + h_id[n]=id; + h_x[n]=x; + h_y[n]=x; + h_adc[n]=5000; + ++n; + } + // all odd id + for(int id=11; id<=1800; id+=2) { + if ( (id/20)%2) h_id[n++]=InvId; // error + for (int x=0; x<40; x+=4) { + ++ncl; + if ((id/10)%2) { + for (int k=0; k<10; ++k) { + h_id[n]=id; + h_x[n]=x; + h_y[n]=x+y[k]; + h_adc[n]=100; + ++n; + h_id[n]=id; + h_x[n]=x+1; + h_y[n]=x+y[k]+2; + h_adc[n]=1000; + ++n; + } + } else { + for (int k=0; k<10; ++k) { + h_id[n]=id; + h_x[n]=x; + h_y[n]=x+y[9-k]; + h_adc[n]= kn==2 ? 10 : 1000; + ++n; + if (y[k]==3) continue; // hole + if (id==51) {h_id[n++]=InvId; h_id[n++]=InvId; }// error + h_id[n]=id; + h_x[n]=x+1; + h_y[n]=x+y[k]+2; + h_adc[n]= kn==2 ? 10 : 1000; + ++n; + } + } + } + } + }; // end lambda + for (auto kkk=0; kkk<5; ++kkk) { + n=0; ncl=0; + generateClusters(kkk); + + std::cout << "created " << n << " digis in " << ncl << " clusters" << std::endl; + assert(n<=numElements); + + + size_t size32 = n * sizeof(unsigned int); + size_t size16 = n * sizeof(unsigned short); + // size_t size8 = n * sizeof(uint8_t); + + uint32_t nModules=0; + cuda::memory::copy(d_moduleStart.get(),&nModules,sizeof(uint32_t)); + + cuda::memory::copy(d_id.get(), h_id.get(), size16); + cuda::memory::copy(d_x.get(), h_x.get(), size16); + cuda::memory::copy(d_y.get(), h_y.get(), size16); + cuda::memory::copy(d_adc.get(), h_adc.get(), size16); + // Launch CUDA Kernels + int threadsPerBlock = (kkk==5) ? 512 : ((kkk==3) ? 128 : 256); + int blocksPerGrid = (numElements + threadsPerBlock - 1) / threadsPerBlock; + std::cout + << "CUDA countModules kernel launch with " << blocksPerGrid + << " blocks of " << threadsPerBlock << " threads\n"; + + cuda::launch( + countModules, + { blocksPerGrid, threadsPerBlock }, + d_id.get(), d_moduleStart.get() ,d_clus.get(),n + ); + + blocksPerGrid = MaxNumModules; //nModules; + + std::cout + << "CUDA findModules kernel launch with " << blocksPerGrid + << " blocks of " << threadsPerBlock << " threads\n"; + + cuda::memory::device::zero(d_clusInModule.get(),MaxNumModules*sizeof(uint32_t)); + + cuda::launch( + findClus, + { blocksPerGrid, threadsPerBlock }, + d_id.get(), d_x.get(), d_y.get(), + d_moduleStart.get(), + d_clusInModule.get(), d_moduleId.get(), + d_clus.get(), + n + ); + cudaDeviceSynchronize(); + + cuda::memory::copy(&nModules,d_moduleStart.get(),sizeof(uint32_t)); + + uint32_t nclus[MaxNumModules], moduleId[nModules]; + + cuda::memory::copy(&nclus,d_clusInModule.get(),MaxNumModules*sizeof(uint32_t)); + std::cout << "before charge cut found " << std::accumulate(nclus,nclus+MaxNumModules,0) << " clusters" << std::endl; + for (auto i=MaxNumModules; i>0; i--) if (nclus[i-1]>0) {std::cout << "last module is " << i-1 << ' ' << nclus[i-1] << std::endl; break;} + if (ncl!=std::accumulate(nclus,nclus+MaxNumModules,0)) std::cout << "ERROR!!!!! wrong number of cluster found" << std::endl; + + cuda::launch( + clusterChargeCut, + { blocksPerGrid, threadsPerBlock }, + d_id.get(), d_adc.get(), + d_moduleStart.get(), + d_clusInModule.get(), d_moduleId.get(), + d_clus.get(), + n + ); + + + cudaDeviceSynchronize(); + + std::cout << "found " << nModules << " Modules active" << std::endl; + + cuda::memory::copy(h_id.get(), d_id.get(), size16); + cuda::memory::copy(h_clus.get(), d_clus.get(), size32); + cuda::memory::copy(&nclus,d_clusInModule.get(),MaxNumModules*sizeof(uint32_t)); + cuda::memory::copy(&moduleId,d_moduleId.get(),nModules*sizeof(uint32_t)); + + + std::set clids; + for (int i=0; i=0); + assert(h_clus[i]0; i--) if (nclus[i-1]>0) {std::cout << "last module is " << i-1 << ' ' << nclus[i-1] << std::endl; break;} + // << " and " << seeds.size() << " seeds" << std::endl; + } /// end loop kkk + return 0; +} diff --git a/RecoLocalTracker/SiPixelRecHits/BuildFile.xml b/RecoLocalTracker/SiPixelRecHits/BuildFile.xml index 4fc33a07b7477..7918c7a4f4d9a 100644 --- a/RecoLocalTracker/SiPixelRecHits/BuildFile.xml +++ b/RecoLocalTracker/SiPixelRecHits/BuildFile.xml @@ -12,6 +12,11 @@ + + + + + diff --git a/RecoLocalTracker/SiPixelRecHits/interface/PixelCPEBase.h b/RecoLocalTracker/SiPixelRecHits/interface/PixelCPEBase.h index 45ce45ea1ac8c..f908325029afe 100644 --- a/RecoLocalTracker/SiPixelRecHits/interface/PixelCPEBase.h +++ b/RecoLocalTracker/SiPixelRecHits/interface/PixelCPEBase.h @@ -80,11 +80,12 @@ class PixelCPEBase : public PixelClusterParameterEstimator struct ClusterParam { + ClusterParam(){} ClusterParam(const SiPixelCluster & cl) : theCluster(&cl) {} virtual ~ClusterParam() = default; - const SiPixelCluster * theCluster; + const SiPixelCluster * theCluster = nullptr;; //--- Cluster-level quantities (filled in computeAnglesFrom....) float cotalpha; diff --git a/RecoLocalTracker/SiPixelRecHits/interface/PixelCPEFast.h b/RecoLocalTracker/SiPixelRecHits/interface/PixelCPEFast.h new file mode 100644 index 0000000000000..9b8924988e848 --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/interface/PixelCPEFast.h @@ -0,0 +1,98 @@ +#ifndef RecoLocalTracker_SiPixelRecHits_PixelCPEFast_h +#define RecoLocalTracker_SiPixelRecHits_PixelCPEFast_h + +#include + +#include + +#include "CalibTracker/SiPixelESProducers/interface/SiPixelCPEGenericDBErrorParametrization.h" +#include "CondFormats/SiPixelTransient/interface/SiPixelGenError.h" +#include "CondFormats/SiPixelTransient/interface/SiPixelTemplate.h" +#include "HeterogeneousCore/CUDACore/interface/CUDAESProduct.h" +#include "HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/PixelCPEBase.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h" + +class MagneticField; +class PixelCPEFast final : public PixelCPEBase +{ +public: + struct ClusterParamGeneric : ClusterParam + { + ClusterParamGeneric() {} + ClusterParamGeneric(const SiPixelCluster & cl) : ClusterParam(cl){} + + // The truncation value pix_maximum is an angle-dependent cutoff on the + // individual pixel signals. It should be applied to all pixels in the + // cluster [signal_i = fminf(signal_i, pixmax)] before the column and row + // sums are made. Morris + int pixmx; + + // These are errors predicted by PIXELAV + float sigmay; // CPE Generic y-error for multi-pixel cluster + float sigmax; // CPE Generic x-error for multi-pixel cluster + float sy1; // CPE Generic y-error for single single-pixel + float sy2; // CPE Generic y-error for single double-pixel cluster + float sx1; // CPE Generic x-error for single single-pixel cluster + float sx2; // CPE Generic x-error for single double-pixel cluster + + }; + + PixelCPEFast(edm::ParameterSet const& conf, const MagneticField *, + const TrackerGeometry&, const TrackerTopology&, const SiPixelLorentzAngle *, + const SiPixelGenErrorDBObject *, const SiPixelLorentzAngle *); + + + ~PixelCPEFast() override; + + // The return value can only be used safely in kernels launched on + // the same cudaStream, or after cudaStreamSynchronize. + const pixelCPEforGPU::ParamsOnGPU *getGPUProductAsync(cuda::stream_t<>& cudaStream) const; + +private: + ClusterParam * createClusterParam(const SiPixelCluster & cl) const override; + + LocalPoint localPosition (DetParam const & theDetParam, ClusterParam & theClusterParam) const override; + LocalError localError (DetParam const & theDetParam, ClusterParam & theClusterParam) const override; + + void errorFromTemplates(DetParam const & theDetParam, ClusterParamGeneric & theClusterParam, float qclus) const; + + static void + collect_edge_charges(ClusterParam & theClusterParam, //!< input, the cluster + int & Q_f_X, //!< output, Q first in X + int & Q_l_X, //!< output, Q last in X + int & Q_f_Y, //!< output, Q first in Y + int & Q_l_Y, //!< output, Q last in Y + bool truncate + ); + + + bool UseErrorsFromTemplates_; + bool TruncatePixelCharge_; + + float EdgeClusterErrorX_; + float EdgeClusterErrorY_; + + std::vector xerr_barrel_l1_, yerr_barrel_l1_, xerr_barrel_ln_; + std::vector yerr_barrel_ln_, xerr_endcap_, yerr_endcap_; + float xerr_barrel_l1_def_, yerr_barrel_l1_def_, xerr_barrel_ln_def_; + float yerr_barrel_ln_def_, xerr_endcap_def_, yerr_endcap_def_; + + //--- DB Error Parametrization object, new light templates + std::vector< SiPixelGenErrorStore > thePixelGenError_; + + std::vector> m_detParamsGPU; + pixelCPEforGPU::CommonParams m_commonParamsGPU; + + struct GPUData { + ~GPUData(); + // not needed if not used on CPU... + pixelCPEforGPU::ParamsOnGPU h_paramsOnGPU; + pixelCPEforGPU::ParamsOnGPU * d_paramsOnGPU = nullptr; // copy of the above on the Device + }; + CUDAESProduct gpuData_; + + void fillParamsForGpu(); +}; + +#endif // RecoLocalTracker_SiPixelRecHits_PixelCPEFast_h diff --git a/RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h b/RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h new file mode 100644 index 0000000000000..fa326865ced73 --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h @@ -0,0 +1,289 @@ +#ifndef RecoLocalTracker_SiPixelRecHits_pixelCPEforGPU_h +#define RecoLocalTracker_SiPixelRecHits_pixelCPEforGPU_h + +#include +#include +#include +#include + +#include "DataFormats/GeometrySurface/interface/SOARotation.h" +#include "Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_cxx17.h" + +namespace pixelCPEforGPU { + + using Frame = SOAFrame; + using Rotation = SOARotation; + + // all modules are identical! + struct CommonParams { + float theThicknessB; + float theThicknessE; + float thePitchX; + float thePitchY; + }; + + struct DetParams { + bool isBarrel; + bool isPosZ; + uint16_t layer; + uint16_t index; + uint32_t rawId; + + float shiftX; + float shiftY; + float chargeWidthX; + float chargeWidthY; + + float x0,y0,z0; // the vertex in the local coord of the detector + + float sx[3], sy[3]; // the errors... + + Frame frame; + }; + + + struct ParamsOnGPU { + CommonParams * m_commonParams; + DetParams * m_detParams; + + constexpr + CommonParams const & __restrict__ commonParams() const { + CommonParams const * __restrict__ l = m_commonParams; + return *l; + } + constexpr + DetParams const & __restrict__ detParams(int i) const { + DetParams const * __restrict__ l = m_detParams; + return l[i]; + } + }; + + // SOA (on device) + template + struct ClusParamsT { + uint32_t minRow[N]; + uint32_t maxRow[N]; + uint32_t minCol[N]; + uint32_t maxCol[N]; + + int32_t Q_f_X[N]; + int32_t Q_l_X[N]; + int32_t Q_f_Y[N]; + int32_t Q_l_Y[N]; + + int32_t charge[N]; + + float xpos[N]; + float ypos[N]; + + float xerr[N]; + float yerr[N]; + }; + + + constexpr uint32_t MaxClusInModule=256; + using ClusParams = ClusParamsT<256>; + + constexpr inline + void computeAnglesFromDet(DetParams const & __restrict__ detParams, float const x, float const y, float & cotalpha, float & cotbeta) { + // x,y local position on det + auto gvx = x - detParams.x0; + auto gvy = y - detParams.y0; + auto gvz = -1.f / detParams.z0; + // normalization not required as only ratio used... + // calculate angles + cotalpha = gvx * gvz; + cotbeta = gvy * gvz; + } + + constexpr inline + float correction( + int sizeM1, + int Q_f, //!< Charge in the first pixel. + int Q_l, //!< Charge in the last pixel. + uint16_t upper_edge_first_pix, //!< As the name says. + uint16_t lower_edge_last_pix, //!< As the name says. + float lorentz_shift, //!< L-shift at half thickness + float theThickness, //detector thickness + float cot_angle, //!< cot of alpha_ or beta_ + float pitch, //!< thePitchX or thePitchY + bool first_is_big, //!< true if the first is big + bool last_is_big ) //!< true if the last is big + { + if (0 == sizeM1) // size 1 + return 0; + + float W_eff = 0; + bool simple = true; + if (1 == sizeM1) { // size 2 + //--- Width of the clusters minus the edge (first and last) pixels. + //--- In the note, they are denoted x_F and x_L (and y_F and y_L) + // assert(lower_edge_last_pix >= upper_edge_first_pix); + auto W_inner = pitch * float(lower_edge_last_pix - upper_edge_first_pix); // in cm + + //--- Predicted charge width from geometry + auto W_pred = theThickness * cot_angle // geometric correction (in cm) + - lorentz_shift; // (in cm) &&& check fpix! + + W_eff = std::abs(W_pred) - W_inner; + + //--- If the observed charge width is inconsistent with the expectations + //--- based on the track, do *not* use W_pred-W_inner. Instead, replace + //--- it with an *average* effective charge width, which is the average + //--- length of the edge pixels. + simple = (W_eff < 0.0f) | (W_eff > pitch); // this produces "large" regressions for very small numeric differences... + } + + if (simple) { + //--- Total length of the two edge pixels (first+last) + float sum_of_edge = 2.0f; + if (first_is_big) sum_of_edge += 1.0f; + if (last_is_big) sum_of_edge += 1.0f; + W_eff = pitch * 0.5f * sum_of_edge; // ave. length of edge pixels (first+last) (cm) + } + + //--- Finally, compute the position in this projection + float Qdiff = Q_l - Q_f; + float Qsum = Q_l + Q_f; + + //--- Temporary fix for clusters with both first and last pixel with charge = 0 + if (Qsum == 0) + Qsum = 1.0f; + + return 0.5f * (Qdiff/Qsum) * W_eff; + } + + constexpr inline + void position(CommonParams const & __restrict__ comParams, DetParams const & __restrict__ detParams, ClusParams & cp, uint32_t ic) { + + //--- Upper Right corner of Lower Left pixel -- in measurement frame + uint16_t llx = cp.minRow[ic]+1; + uint16_t lly = cp.minCol[ic]+1; + + //--- Lower Left corner of Upper Right pixel -- in measurement frame + uint16_t urx = cp.maxRow[ic]; + uint16_t ury = cp.maxCol[ic]; + + auto llxl = phase1PixelTopology::localX(llx); + auto llyl = phase1PixelTopology::localY(lly); + auto urxl = phase1PixelTopology::localX(urx); + auto uryl = phase1PixelTopology::localY(ury); + + auto mx = llxl+urxl; + auto my = llyl+uryl; + + // apply the lorentz offset correction + auto xPos = detParams.shiftX + comParams.thePitchX*(0.5f*float(mx)+float(phase1PixelTopology::xOffset)); + auto yPos = detParams.shiftY + comParams.thePitchY*(0.5f*float(my)+float(phase1PixelTopology::yOffset)); + + float cotalpha=0, cotbeta=0; + + computeAnglesFromDet(detParams, xPos, yPos, cotalpha, cotbeta); + + auto thickness = detParams.isBarrel ? comParams.theThicknessB : comParams.theThicknessE; + + auto xcorr = correction( + cp.maxRow[ic]-cp.minRow[ic], + cp.Q_f_X[ic], cp.Q_l_X[ic], + llxl, urxl, + detParams.chargeWidthX, // lorentz shift in cm + thickness, + cotalpha, + comParams.thePitchX, + phase1PixelTopology::isBigPixX(cp.minRow[ic]), + phase1PixelTopology::isBigPixX(cp.maxRow[ic]) ); + + auto ycorr = correction( + cp.maxCol[ic]-cp.minCol[ic], + cp.Q_f_Y[ic], cp.Q_l_Y[ic], + llyl, uryl, + detParams.chargeWidthY, // lorentz shift in cm + thickness, + cotbeta, + comParams.thePitchY, + phase1PixelTopology::isBigPixY(cp.minCol[ic]), + phase1PixelTopology::isBigPixY(cp.maxCol[ic]) ); + + cp.xpos[ic]=xPos+xcorr; + cp.ypos[ic]=yPos+ycorr; + } + + constexpr inline + void errorFromSize(CommonParams const & __restrict__ comParams, DetParams const & __restrict__ detParams, ClusParams & cp, uint32_t ic) { + // Edge cluster errors + cp.xerr[ic]= 0.0050; + cp.yerr[ic]= 0.0085; + + // FIXME these are errors form Run1 + constexpr float xerr_barrel_l1[] = { 0.00115, 0.00120, 0.00088 }; + constexpr float xerr_barrel_l1_def = 0.00200; // 0.01030; + constexpr float yerr_barrel_l1[] = { 0.00375, 0.00230, 0.00250, 0.00250, 0.00230, 0.00230, 0.00210, 0.00210, 0.00240 }; + constexpr float yerr_barrel_l1_def = 0.00210; + constexpr float xerr_barrel_ln[] = { 0.00115, 0.00120, 0.00088 }; + constexpr float xerr_barrel_ln_def = 0.00200; // 0.01030; + constexpr float yerr_barrel_ln[] = { 0.00375, 0.00230, 0.00250, 0.00250, 0.00230, 0.00230, 0.00210, 0.00210, 0.00240 }; + constexpr float yerr_barrel_ln_def = 0.00210; + constexpr float xerr_endcap[] = { 0.0020, 0.0020 }; + constexpr float xerr_endcap_def = 0.0020; + constexpr float yerr_endcap[] = { 0.00210 }; + constexpr float yerr_endcap_def = 0.00210; + + auto sx = cp.maxRow[ic] - cp.minRow[ic]; + auto sy = cp.maxCol[ic] - cp.minCol[ic]; + + // is edgy ? + bool isEdgeX = cp.minRow[ic] == 0 or cp.maxRow[ic] == phase1PixelTopology::lastRowInModule; + bool isEdgeY = cp.minCol[ic] == 0 or cp.maxCol[ic] == phase1PixelTopology::lastColInModule; + // is one and big? + bool isBig1X = (0==sx) && phase1PixelTopology::isBigPixX(cp.minRow[ic]); + bool isBig1Y = (0==sy) && phase1PixelTopology::isBigPixY(cp.minCol[ic]); + + + if (!isEdgeX && !isBig1X ) { + if (not detParams.isBarrel) { + cp.xerr[ic] = sx - - - - - + + + + + + + + + + + diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/PixelCPEFastESProducer.cc b/RecoLocalTracker/SiPixelRecHits/plugins/PixelCPEFastESProducer.cc new file mode 100644 index 0000000000000..344625cba01b6 --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/plugins/PixelCPEFastESProducer.cc @@ -0,0 +1,103 @@ +#include "RecoLocalTracker/SiPixelRecHits/interface/PixelCPEFast.h" +#include "MagneticField/Engine/interface/MagneticField.h" +#include "MagneticField/Records/interface/IdealMagneticFieldRecord.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "Geometry/Records/interface/TrackerDigiGeometryRecord.h" +#include "Geometry/Records/interface/TrackerTopologyRcd.h" +#include "DataFormats/TrackerCommon/interface/TrackerTopology.h" + +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/ModuleFactory.h" + +// new record +#include "CondFormats/DataRecord/interface/SiPixelGenErrorDBObjectRcd.h" + +#include "FWCore/Framework/interface/ESProducer.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "RecoLocalTracker/Records/interface/TkPixelCPERecord.h" +#include "RecoLocalTracker/ClusterParameterEstimator/interface/PixelClusterParameterEstimator.h" +#include + +class PixelCPEFastESProducer: public edm::ESProducer{ + public: + PixelCPEFastESProducer(const edm::ParameterSet & p); + std::shared_ptr produce(const TkPixelCPERecord &); + private: + std::shared_ptr cpe_; + edm::ParameterSet pset_; + edm::ESInputTag magname_; + bool UseErrorsFromTemplates_; +}; + + +#include +#include + +using namespace edm; + + + + +PixelCPEFastESProducer::PixelCPEFastESProducer(const edm::ParameterSet & p) +{ + std::string myname = p.getParameter("ComponentName"); + magname_ = p.existsAs("MagneticFieldRecord")? + p.getParameter("MagneticFieldRecord"):edm::ESInputTag(""); + UseErrorsFromTemplates_ = p.getParameter("UseErrorsFromTemplates"); + + + pset_ = p; + setWhatProduced(this,myname); + + +} + + +std::shared_ptr +PixelCPEFastESProducer::produce(const TkPixelCPERecord & iRecord){ + + ESHandle magfield; + iRecord.getRecord().get( magname_, magfield ); + + edm::ESHandle pDD; + iRecord.getRecord().get( pDD ); + + edm::ESHandle hTT; + iRecord.getRecord().getRecord().get(hTT); + + // Lorant angle for offsets + ESHandle lorentzAngle; + iRecord.getRecord().get(lorentzAngle ); + + // add the new la width object + ESHandle lorentzAngleWidth; + const SiPixelLorentzAngle * lorentzAngleWidthProduct = nullptr; + iRecord.getRecord().get("forWidth",lorentzAngleWidth ); + lorentzAngleWidthProduct = lorentzAngleWidth.product(); + + const SiPixelGenErrorDBObject * genErrorDBObjectProduct = nullptr; + + // Errors take only from new GenError + ESHandle genErrorDBObject; + if(UseErrorsFromTemplates_) { // do only when generrors are needed + iRecord.getRecord().get(genErrorDBObject); + genErrorDBObjectProduct = genErrorDBObject.product(); + //} else { + //std::cout<<" pass an empty GenError pointer"<( + pset_,magfield.product(),*pDD.product(), + *hTT.product(),lorentzAngle.product(), + genErrorDBObjectProduct,lorentzAngleWidthProduct); + + return cpe_; +} + + +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Utilities/interface/typelookup.h" +#include "FWCore/Framework/interface/eventsetuprecord_registration_macro.h" + +DEFINE_FWK_EVENTSETUP_MODULE(PixelCPEFastESProducer); + diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.cu b/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.cu new file mode 100644 index 0000000000000..947cd20d97919 --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.cu @@ -0,0 +1,206 @@ +// C++ headers +#include +#include + +// CUDA runtime +#include + +// CMSSW headers +#include "RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusteringConstants.h" +#include "PixelRecHits.h" +#include "gpuPixelRecHits.h" + +namespace { + __global__ + void setHitsLayerStart(const uint32_t* hitsModuleStart, const uint32_t* layerStart, uint32_t* hitsLayerStart) { + auto i = blockIdx.x * blockDim.x + threadIdx.x; + + if(i < 10) { + hitsLayerStart[i] = hitsModuleStart[layerStart[i]]; + } + else if(i == 10) { + hitsLayerStart[i] = hitsModuleStart[gpuClustering::MaxNumModules]; + } + } + + template + T *slicePitch(void *ptr, size_t pitch, size_t row) { + return reinterpret_cast( reinterpret_cast(ptr) + pitch*row); + } +} + +namespace pixelgpudetails { + PixelRecHitGPUKernel::PixelRecHitGPUKernel(cuda::stream_t<>& cudaStream) { + + constexpr auto MAX_HITS = siPixelRecHitsHeterogeneousProduct::maxHits(); + + cudaCheck(cudaMalloc((void **) & gpu_.bs_d, 3 * sizeof(float))); + cudaCheck(cudaMalloc((void **) & gpu_.hitsLayerStart_d, 11 * sizeof(uint32_t))); + + // Coalesce all 32bit and 16bit arrays to two big blobs + // + // This is just a toy. Please don't copy-paste the logic but + // create a proper abstraction (e.g. along FWCore/SOA, or + // FWCore/Utilities/interface/SoATuple.h + // + // Order such that the first ones are the ones transferred to CPU + static_assert(sizeof(uint32_t) == sizeof(float)); // just stating the obvious + cudaCheck(cudaMallocPitch(&gpu_.owner_32bit_, &gpu_.owner_32bit_pitch_, MAX_HITS*sizeof(uint32_t), 9)); + cudaCheck(cudaMemsetAsync(gpu_.owner_32bit_, 0x0, gpu_.owner_32bit_pitch_*9, cudaStream.id())); + //edm::LogPrint("Foo") << "Allocate 32bit with pitch " << gpu_.owner_32bit_pitch_; + gpu_.charge_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 0); + gpu_.xl_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 1); + gpu_.yl_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 2); + gpu_.xerr_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 3); + gpu_.yerr_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 4); + gpu_.xg_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 5); + gpu_.yg_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 6); + gpu_.zg_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 7); + gpu_.rg_d = slicePitch(gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, 8); + + // Order such that the first ones are the ones transferred to CPU + cudaCheck(cudaMallocPitch(&gpu_.owner_16bit_, &gpu_.owner_16bit_pitch_, MAX_HITS*sizeof(uint16_t), 5)); + cudaCheck(cudaMemsetAsync(gpu_.owner_16bit_, 0x0, gpu_.owner_16bit_pitch_*5, cudaStream.id())); + //edm::LogPrint("Foo") << "Allocate 16bit with pitch " << gpu_.owner_16bit_pitch_; + gpu_.detInd_d = slicePitch(gpu_.owner_16bit_, gpu_.owner_16bit_pitch_, 0); + gpu_.mr_d = slicePitch(gpu_.owner_16bit_, gpu_.owner_16bit_pitch_, 1); + gpu_.mc_d = slicePitch(gpu_.owner_16bit_, gpu_.owner_16bit_pitch_, 2); + gpu_.iphi_d = slicePitch(gpu_.owner_16bit_, gpu_.owner_16bit_pitch_, 3); + gpu_.sortIndex_d = slicePitch(gpu_.owner_16bit_, gpu_.owner_16bit_pitch_, 4); + + cudaCheck(cudaMalloc((void **) & gpu_.hist_d, sizeof(HitsOnGPU::Hist))); + cudaCheck(cudaMalloc((void **) & gpu_.hws_d, HitsOnGPU::Hist::wsSize())); + cudaCheck(cudaMalloc((void **) & gpu_d, sizeof(HitsOnGPU))); + + // Feels a bit dumb but constexpr arrays are not supported for device code + // TODO: should be moved to EventSetup (or better ideas?) + // Would it be better to use "constant memory"? + cudaCheck(cudaMalloc((void **) & d_phase1TopologyLayerStart_, 11 * sizeof(uint32_t))); + cudaCheck(cudaMemcpyAsync(d_phase1TopologyLayerStart_, phase1PixelTopology::layerStart, 11 * sizeof(uint32_t), cudaMemcpyDefault, cudaStream.id())); + cudaCheck(cudaMalloc((void **) & d_phase1TopologyLayer_, phase1PixelTopology::layer.size() * sizeof(uint8_t))); + cudaCheck(cudaMemcpyAsync(d_phase1TopologyLayer_, phase1PixelTopology::layer.data(), phase1PixelTopology::layer.size() * sizeof(uint8_t), cudaMemcpyDefault, cudaStream.id())); + + gpu_.phase1TopologyLayerStart_d = d_phase1TopologyLayerStart_; + gpu_.phase1TopologyLayer_d = d_phase1TopologyLayer_; + + gpu_.me_d = gpu_d; + cudaCheck(cudaMemcpyAsync(gpu_d, &gpu_, sizeof(HitsOnGPU), cudaMemcpyDefault, cudaStream.id())); + + cudaCheck(cudaMallocHost(&h_hitsModuleStart_, (gpuClustering::MaxNumModules+1) * sizeof(uint32_t))); + + // On CPU we can safely use MAX_HITS*sizeof as the pitch. Thanks + // to '*256' it is even aligned by cache line + h_owner_32bit_pitch_ = MAX_HITS*sizeof(uint32_t); + cudaCheck(cudaMallocHost(&h_owner_32bit_, h_owner_32bit_pitch_ * 5)); + h_charge_ = slicePitch(h_owner_32bit_, h_owner_32bit_pitch_, 0); + h_xl_ = slicePitch(h_owner_32bit_, h_owner_32bit_pitch_, 1); + h_yl_ = slicePitch(h_owner_32bit_, h_owner_32bit_pitch_, 2); + h_xe_ = slicePitch(h_owner_32bit_, h_owner_32bit_pitch_, 3); + h_ye_ = slicePitch(h_owner_32bit_, h_owner_32bit_pitch_, 4); + + h_owner_16bit_pitch_ = MAX_HITS*sizeof(uint16_t); + cudaCheck(cudaMallocHost(&h_owner_16bit_, h_owner_16bit_pitch_ * 3)); + h_detInd_ = slicePitch(h_owner_16bit_, h_owner_16bit_pitch_, 0); + h_mr_ = slicePitch(h_owner_16bit_, h_owner_16bit_pitch_, 1); + h_mc_ = slicePitch(h_owner_16bit_, h_owner_16bit_pitch_, 2); + +#ifdef GPU_DEBUG + cudaCheck(cudaMallocHost(&h_hitsLayerStart_, 11 * sizeof(uint32_t))); +#endif + } + PixelRecHitGPUKernel::~PixelRecHitGPUKernel() { + cudaCheck(cudaFree(gpu_.bs_d)); + cudaCheck(cudaFree(gpu_.hitsLayerStart_d)); + cudaCheck(cudaFree(gpu_.owner_32bit_)); + cudaCheck(cudaFree(gpu_.owner_16bit_)); + cudaCheck(cudaFree(gpu_.hist_d)); + cudaCheck(cudaFree(gpu_.hws_d)); + cudaCheck(cudaFree(gpu_d)); + cudaCheck(cudaFree(d_phase1TopologyLayerStart_)); + cudaCheck(cudaFree(d_phase1TopologyLayer_)); + + cudaCheck(cudaFreeHost(h_hitsModuleStart_)); + cudaCheck(cudaFreeHost(h_owner_32bit_)); + cudaCheck(cudaFreeHost(h_owner_16bit_)); +#ifdef GPU_DEBUG + cudaCheck(cudaFreeHost(h_hitsLayerStart_)); +#endif + } + + void PixelRecHitGPUKernel::makeHitsAsync(const siPixelRawToClusterHeterogeneousProduct::GPUProduct& input, + float const * bs, + pixelCPEforGPU::ParamsOnGPU const * cpeParams, + bool transferToCPU, + cuda::stream_t<>& stream) { + cudaCheck(cudaMemcpyAsync(gpu_.bs_d, bs, 3 * sizeof(float), cudaMemcpyDefault, stream.id())); + gpu_.hitsModuleStart_d = input.clusters_d.clusModuleStart(); + gpu_.cpeParams = cpeParams; // copy it for use in clients + cudaCheck(cudaMemcpyAsync(gpu_d, &gpu_, sizeof(HitsOnGPU), cudaMemcpyDefault, stream.id())); + + int threadsPerBlock = 256; + int blocks = input.nModules; // active modules (with digis) + +#ifdef GPU_DEBUG + std::cout << "launching getHits kernel for " << blocks << " blocks" << std::endl; +#endif + gpuPixelRecHits::getHits<<>>( + cpeParams, + gpu_.bs_d, + input.digis_d.moduleInd(), + input.digis_d.xx(), input.digis_d.yy(), input.digis_d.adc(), + input.clusters_d.moduleStart(), + input.clusters_d.clusInModule(), input.clusters_d.moduleId(), + input.clusters_d.clus(), + input.nDigis, + gpu_.hitsModuleStart_d, + gpu_.charge_d, + gpu_.detInd_d, + gpu_.xg_d, gpu_.yg_d, gpu_.zg_d, gpu_.rg_d, + gpu_.iphi_d, + gpu_.xl_d, gpu_.yl_d, + gpu_.xerr_d, gpu_.yerr_d, + gpu_.mr_d, gpu_.mc_d + ); + cudaCheck(cudaGetLastError()); + + // assuming full warp of threads is better than a smaller number... + setHitsLayerStart<<<1, 32, 0, stream.id()>>>(gpu_.hitsModuleStart_d, d_phase1TopologyLayerStart_, gpu_.hitsLayerStart_d); + cudaCheck(cudaGetLastError()); + + // needed only if hits on CPU are required... + nhits_ = input.nClusters; + if(transferToCPU) { + cudaCheck(cudaMemcpyAsync(h_hitsModuleStart_, gpu_.hitsModuleStart_d, (gpuClustering::MaxNumModules+1) * sizeof(uint32_t), cudaMemcpyDefault, stream.id())); +#ifdef GPU_DEBUG + cudaCheck(cudaMemcpyAsync(h_hitsLayerStart_, gpu_.hitsLayerStart_d, 11 * sizeof(uint32_t), cudaMemcpyDefault, stream.id())); +#endif + + cudaCheck(cudaMemcpy2DAsync(h_owner_16bit_, h_owner_16bit_pitch_, + gpu_.owner_16bit_, gpu_.owner_16bit_pitch_, + nhits_*sizeof(uint16_t), 3, + cudaMemcpyDefault, stream.id())); + + cudaCheck(cudaMemcpy2DAsync(h_owner_32bit_, h_owner_32bit_pitch_, + gpu_.owner_32bit_, gpu_.owner_32bit_pitch_, + nhits_*sizeof(uint32_t), 5, + cudaMemcpyDefault, stream.id())); + +#ifdef GPU_DEBUG + cudaStreamSynchronize(stream.id()); + + std::cout << "hit layerStart "; + for (int i=0;i<10;++i) std::cout << phase1PixelTopology::layerName[i] << ':' << h_hitsLayerStart_[i] << ' '; + std::cout << "end:" << h_hitsLayerStart_[10] << std::endl; +#endif + + // for timing test + // cudaStreamSynchronize(stream.id()); + // auto nhits_ = h_hitsLayerStart_[10]; + // radixSortMultiWrapper<<<10, 256, 0, c.stream>>>(gpu_.iphi_d, gpu_.sortIndex_d, gpu_.hitsLayerStart_d); + } + + cudautils::fillManyFromVector(gpu_.hist_d, gpu_.hws_d, 10, gpu_.iphi_d, gpu_.hitsLayerStart_d, nhits_, 256, stream.id()); + } +} diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h b/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h new file mode 100644 index 0000000000000..dcc80308c4463 --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h @@ -0,0 +1,73 @@ +#ifndef RecoLocalTracker_SiPixelRecHits_plugins_PixelRecHits_h +#define RecoLocalTracker_SiPixelRecHits_plugins_PixelRecHits_h + +#include "RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/gpuClusteringConstants.h" + +#include + +#include +#include + +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" + + +namespace pixelCPEforGPU { + struct ParamsOnGPU; +} + +namespace pixelgpudetails { + using HitsOnGPU = siPixelRecHitsHeterogeneousProduct::HitsOnGPU; + + using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; + + class PixelRecHitGPUKernel { + public: + PixelRecHitGPUKernel(cuda::stream_t<>& cudaStream); + ~PixelRecHitGPUKernel(); + + PixelRecHitGPUKernel(const PixelRecHitGPUKernel&) = delete; + PixelRecHitGPUKernel(PixelRecHitGPUKernel&&) = delete; + PixelRecHitGPUKernel& operator=(const PixelRecHitGPUKernel&) = delete; + PixelRecHitGPUKernel& operator=(PixelRecHitGPUKernel&&) = delete; + + void makeHitsAsync(const siPixelRawToClusterHeterogeneousProduct::GPUProduct& input, + float const * bs, + pixelCPEforGPU::ParamsOnGPU const * cpeParams, + bool transferToCPU, + cuda::stream_t<>& stream); + + HitsOnCPU getOutput() const { + return HitsOnCPU{ + h_hitsModuleStart_, h_detInd_, h_charge_, + h_xl_, h_yl_, h_xe_, h_ye_, h_mr_, h_mc_, + gpu_d, nhits_ + }; + } + + private: + HitsOnGPU * gpu_d; // copy of the structure on the gpu itself: this is the "Product" + HitsOnGPU gpu_; + uint32_t nhits_ = 0; + uint32_t *d_phase1TopologyLayerStart_ = nullptr; + uint8_t *d_phase1TopologyLayer_ = nullptr; + uint32_t *h_hitsModuleStart_ = nullptr; + uint16_t *h_detInd_ = nullptr; + int32_t *h_charge_ = nullptr; + float *h_xl_ = nullptr; + float *h_yl_ = nullptr; + float *h_xe_ = nullptr; + float *h_ye_ = nullptr; + uint16_t *h_mr_ = nullptr; + uint16_t *h_mc_ = nullptr; + void *h_owner_32bit_ = nullptr; + size_t h_owner_32bit_pitch_ = 0; + void *h_owner_16bit_ = nullptr; + size_t h_owner_16bit_pitch_ = 0; +#ifdef GPU_DEBUG + uint32_t *h_hitsLayerStart_ = nullptr; +#endif + }; +} + +#endif // RecoLocalTracker_SiPixelRecHits_plugins_PixelRecHits_h diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc b/RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc new file mode 100644 index 0000000000000..68f53a47157d4 --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc @@ -0,0 +1,324 @@ +#include "DataFormats/Common/interface/DetSetVectorNew.h" +#include "DataFormats/Common/interface/Handle.h" +#include "DataFormats/BeamSpot/interface/BeamSpot.h" +#include "DataFormats/SiPixelCluster/interface/SiPixelCluster.h" +#include "DataFormats/TrackerRecHit2D/interface/SiPixelRecHitCollection.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/Utilities/interface/InputTag.h" +#include "Geometry/Records/interface/TrackerDigiGeometryRecord.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/PixelCPEBase.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/PixelCPEFast.h" +#include "RecoLocalTracker/Records/interface/TkPixelCPERecord.h" + +#include "RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h" // TODO: we need a proper place for this header... + +#include "PixelRecHits.h" // TODO : spit product from kernel + +class SiPixelRecHitHeterogeneous: public HeterogeneousEDProducer > { + +public: + using Input = siPixelRawToClusterHeterogeneousProduct::HeterogeneousDigiCluster; + + using CPUProduct = siPixelRecHitsHeterogeneousProduct::CPUProduct; + using GPUProduct = siPixelRecHitsHeterogeneousProduct::GPUProduct; + using Output = siPixelRecHitsHeterogeneousProduct::HeterogeneousPixelRecHit; + + + explicit SiPixelRecHitHeterogeneous(const edm::ParameterSet& iConfig); + ~SiPixelRecHitHeterogeneous() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + // CPU implementation + void produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) override; + + // GPU implementation + void beginStreamGPUCuda(edm::StreamID streamId, cuda::stream_t<>& cudaStream) override; + void acquireGPUCuda(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) override; + void produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) override; + void convertGPUtoCPU(edm::Event& iEvent, const edm::Handle& inputhandle, const siPixelRecHitsHeterogeneousProduct::GPUProduct & gpu) const; + + // Commonalities + void initialize(const edm::EventSetup& es); + + // CPU + void run(const edm::Handle& inputhandle, SiPixelRecHitCollectionNew &output) const; + // GPU + void run(const edm::Handle& inputhandle, SiPixelRecHitCollectionNew &output, const pixelgpudetails::HitsOnCPU& hoc) const; + + + edm::EDGetTokenT tBeamSpot; + edm::EDGetTokenT token_; + edm::EDGetTokenT clusterToken_; + std::string cpeName_; + + const TrackerGeometry *geom_ = nullptr; + const PixelClusterParameterEstimator *cpe_ = nullptr; + + std::unique_ptr gpuAlgo_; + + bool enableTransfer_; + bool enableConversion_; +}; + +SiPixelRecHitHeterogeneous::SiPixelRecHitHeterogeneous(const edm::ParameterSet& iConfig): + HeterogeneousEDProducer(iConfig), + tBeamSpot(consumes(iConfig.getParameter("beamSpot"))), + token_(consumesHeterogeneous(iConfig.getParameter("heterogeneousSrc"))), + clusterToken_(consumes(iConfig.getParameter("src"))), + cpeName_(iConfig.getParameter("CPE")) +{ + enableConversion_ = iConfig.getParameter("gpuEnableConversion"); + enableTransfer_ = enableConversion_ || iConfig.getParameter("gpuEnableTransfer"); + + produces(); + if(enableConversion_) { + produces(); + } +} + +void SiPixelRecHitHeterogeneous::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + + desc.add("beamSpot", edm::InputTag("offlineBeamSpot")); + desc.add("heterogeneousSrc", edm::InputTag("siPixelClustersPreSplitting")); + desc.add("src", edm::InputTag("siPixelClustersPreSplitting")); + desc.add("CPE", "PixelCPEFast"); + + desc.add("gpuEnableTransfer", true); + desc.add("gpuEnableConversion", true); + + HeterogeneousEDProducer::fillPSetDescription(desc); + + descriptions.add("siPixelRecHitHeterogeneous",desc); +} + +void SiPixelRecHitHeterogeneous::initialize(const edm::EventSetup& es) { + edm::ESHandle geom; + es.get().get( geom ); + geom_ = geom.product(); + + edm::ESHandle hCPE; + es.get().get(cpeName_, hCPE); + cpe_ = dynamic_cast< const PixelCPEBase* >(hCPE.product()); +} + +void SiPixelRecHitHeterogeneous::produceCPU(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup) { + initialize(iSetup); + + edm::Handle hclusters; + iEvent.getByToken(clusterToken_, hclusters); + + auto output = std::make_unique(); + run(hclusters, *output); + + output->shrink_to_fit(); + iEvent.put(std::move(output)); +} + +void SiPixelRecHitHeterogeneous::run(const edm::Handle& inputhandle, SiPixelRecHitCollectionNew &output) const { + const auto& input = *inputhandle; + + edmNew::DetSetVector::const_iterator DSViter=input.begin(); + for ( ; DSViter != input.end() ; DSViter++) { + unsigned int detid = DSViter->detId(); + DetId detIdObject( detid ); + const GeomDetUnit * genericDet = geom_->idToDetUnit( detIdObject ); + const PixelGeomDetUnit * pixDet = dynamic_cast(genericDet); + assert(pixDet); + SiPixelRecHitCollectionNew::FastFiller recHitsOnDetUnit(output,detid); + + edmNew::DetSet::const_iterator clustIt = DSViter->begin(), clustEnd = DSViter->end(); + + for ( ; clustIt != clustEnd; clustIt++) { + std::tuple tuple = cpe_->getParameters( *clustIt, *genericDet ); + LocalPoint lp( std::get<0>(tuple) ); + LocalError le( std::get<1>(tuple) ); + SiPixelRecHitQuality::QualWordType rqw( std::get<2>(tuple) ); + // Create a persistent edm::Ref to the cluster + edm::Ref< edmNew::DetSetVector, SiPixelCluster > cluster = edmNew::makeRefTo( inputhandle, clustIt); + // Make a RecHit and add it to the DetSet + // old : recHitsOnDetUnit.push_back( new SiPixelRecHit( lp, le, detIdObject, &*clustIt) ); + SiPixelRecHit hit( lp, le, rqw, *genericDet, cluster); + // + // Now save it ================= + recHitsOnDetUnit.push_back(hit); + } // <-- End loop on Clusters + } // <-- End loop on DetUnits +} + + +void SiPixelRecHitHeterogeneous::beginStreamGPUCuda(edm::StreamID streamId, cuda::stream_t<>& cudaStream) { + gpuAlgo_ = std::make_unique(cudaStream); +} + +void SiPixelRecHitHeterogeneous::acquireGPUCuda(const edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) { + initialize(iSetup); + + PixelCPEFast const * fcpe = dynamic_cast(cpe_); + if (!fcpe) { + throw cms::Exception("Configuration") << "too bad, not a fast cpe gpu processing not possible...."; + } + + edm::Handle hinput; + iEvent.getByToken(token_, hinput); + + edm::Handle bsHandle; + iEvent.getByToken( tBeamSpot, bsHandle); + float bs[3] = {0.f}; + if(bsHandle.isValid()) { + const auto & bsh = *bsHandle; + bs[0]=bsh.x0(); bs[1]=bsh.y0(); bs[2]=bsh.z0(); + } + + + gpuAlgo_->makeHitsAsync(*hinput, bs, fcpe->getGPUProductAsync(cudaStream), enableTransfer_, cudaStream); + +} + +void SiPixelRecHitHeterogeneous::produceGPUCuda(edm::HeterogeneousEvent& iEvent, const edm::EventSetup& iSetup, cuda::stream_t<>& cudaStream) { + auto output = std::make_unique(gpuAlgo_->getOutput()); + + if(enableConversion_) { + // Need the CPU clusters to + // - properly fill the output DetSetVector of hits + // - to set up edm::Refs to the clusters + edm::Handle hclusters; + iEvent.getByToken(clusterToken_, hclusters); + + convertGPUtoCPU(iEvent.event(), hclusters, *output); + } + + iEvent.put(std::move(output), heterogeneous::DisableTransfer{}); +} + +void SiPixelRecHitHeterogeneous::convertGPUtoCPU(edm::Event& iEvent, const edm::Handle& inputhandle, const pixelgpudetails::HitsOnCPU & hoc) const{ + assert(hoc.gpu_d); + auto output = std::make_unique(); + run(inputhandle, *output, hoc); + iEvent.put(std::move(output)); +} + +void SiPixelRecHitHeterogeneous::run(const edm::Handle& inputhandle, SiPixelRecHitCollectionNew &output, const pixelgpudetails::HitsOnCPU& hoc) const { + auto const & input = *inputhandle; + + int numberOfDetUnits = 0; + int numberOfClusters = 0; + int numberOfLostClusters = 0; + for (auto DSViter=input.begin(); DSViter != input.end() ; DSViter++) { + numberOfDetUnits++; + unsigned int detid = DSViter->detId(); + DetId detIdObject( detid ); + const GeomDetUnit * genericDet = geom_->idToDetUnit( detIdObject ); + auto gind = genericDet->index(); + const PixelGeomDetUnit * pixDet = dynamic_cast(genericDet); + assert(pixDet); + SiPixelRecHitCollectionNew::FastFiller recHitsOnDetUnit(output, detid); + auto fc = hoc.hitsModuleStart[gind]; + auto lc = hoc.hitsModuleStart[gind+1]; + auto nhits = lc-fc; + int32_t ind[nhits]; + auto mrp = &hoc.mr[fc]; + uint32_t ngh=0; + for (uint32_t i=0; i=96 && hoc.charge[fc+i]<4000) ) { ++numberOfLostClusters; continue;} + ind[ngh]=i;std::push_heap(ind, ind+ngh+1,[&](auto a, auto b) { return mrp[a]size()); + for (auto const & clust : *DSViter) { + if (ic>=ngh) { + // FIXME add a way to handle this case, or at least notify via edm::LogError + break; + } + // order is not stable... assume minPixelCol to be unique... + auto ij = jnd(ic); + // assert( clust.minPixelRow()==hoc.mr[ij] ); + if( clust.minPixelRow()!=hoc.mr[ij] ) + edm::LogWarning("GPUHits2CPU") <<"Missing pixels on CPU? " + << gind <<'/'<0 && clust.minPixelRow()==hoc.mr[jnd(--k)]) if(clust.minPixelCol()==hoc.mc[jnd(k)]) {fd=true; break;} + } + // assert(fd && k!=ij); + if(fd) ij=jnd(k); + } + if(clust.charge()!=hoc.charge[ij]) + edm::LogWarning("GPUHits2CPU") << "perfect Match not found " + << gind <<'/'< tuple = cpe_->getParameters( clust, *genericDet ); + LocalPoint lp( std::get<0>(tuple) ); + LocalError le( std::get<1>(tuple) ); + SiPixelRecHitQuality::QualWordType rqw( std::get<2>(tuple) ); + */ + + // Create a persistent edm::Ref to the cluster + edm::Ref< edmNew::DetSetVector, SiPixelCluster > cluster = edmNew::makeRefTo( inputhandle, &clust); + // Make a RecHit and add it to the DetSet + SiPixelRecHit hit( lp, le, rqw, *genericDet, cluster); + // + // Now save it ================= + recHitsOnDetUnit.push_back(hit); + // ============================= + + // std::cout << "SiPixelRecHitGPUVI " << numberOfClusters << ' '<< lp << " " << le << std::endl; + + } // <-- End loop on Clusters + + + // LogDebug("SiPixelRecHitGPU") + //std::cout << "SiPixelRecHitGPUVI " + // << " Found " << recHitsOnDetUnit.size() << " RecHits on " << detid //; + // << std::endl; + + + } // <-- End loop on DetUnits + + /* + std::cout << "SiPixelRecHitGPUVI $ det, clus, lost " + << numberOfDetUnits << ' ' + << numberOfClusters << ' ' + << numberOfLostClusters + << std::endl; + */ +} + +DEFINE_FWK_MODULE(SiPixelRecHitHeterogeneous); diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/gpuPixelRecHits.h b/RecoLocalTracker/SiPixelRecHits/plugins/gpuPixelRecHits.h new file mode 100644 index 0000000000000..6864a046bf1dc --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/plugins/gpuPixelRecHits.h @@ -0,0 +1,157 @@ +#ifndef RecoLocalTracker_SiPixelRecHits_plugins_gpuPixelRecHits_h +#define RecoLocalTracker_SiPixelRecHits_plugins_gpuPixelRecHits_h + +#include +#include +#include + +#include "DataFormats/Math/interface/approx_atan2.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h" + +namespace gpuPixelRecHits { + + + + + __global__ void getHits(pixelCPEforGPU::ParamsOnGPU const * __restrict__ cpeParams, + float const * __restrict__ bs, + uint16_t const * __restrict__ id, + uint16_t const * __restrict__ x, + uint16_t const * __restrict__ y, + uint16_t const * __restrict__ adc, + uint32_t const * __restrict__ digiModuleStart, + uint32_t const * __restrict__ clusInModule, + uint32_t const * __restrict__ moduleId, + int32_t const * __restrict__ clus, + int numElements, + uint32_t const * __restrict__ hitsModuleStart, + int32_t * chargeh, + uint16_t * detInd, + float * xg, float * yg, float * zg, float * rg, int16_t * iph, + float * xl, float * yl, + float * xe, float * ye, + uint16_t * mr, uint16_t * mc) + { + + // to be moved in common namespace... + constexpr uint16_t InvId=9999; // must be > MaxNumModules + constexpr uint32_t MaxClusInModule = pixelCPEforGPU::MaxClusInModule; + + using ClusParams = pixelCPEforGPU::ClusParams; + + + // as usual one block per module + __shared__ ClusParams clusParams; + + auto first = digiModuleStart[1 + blockIdx.x]; + auto me = moduleId[blockIdx.x]; + auto nclus = clusInModule[me]; + + if (0==nclus) return; + +#ifdef GPU_DEBUG + if (threadIdx.x==0) { + auto k=first; + while (id[k]==InvId) ++k; + assert(id[k]==me); + } +#endif + +#ifdef GPU_DEBUG + if (me%100==1) + if (threadIdx.x==0) printf("hitbuilder: %d clusters in module %d. will write at %d\n", nclus, me, hitsModuleStart[me]); +#endif + + assert(blockDim.x >= MaxClusInModule); + + if (threadIdx.x==0 && nclus > MaxClusInModule) { + printf("WARNING: too many clusters %d in Module %d. Only first %d processed\n", nclus,me,MaxClusInModule); + // zero charge: do not bother to do it in parallel + for (auto d=MaxClusInModule; d::max(); + clusParams.maxRow[ic] = 0; + clusParams.minCol[ic] = std::numeric_limits::max(); + clusParams.maxCol[ic] = 0; + clusParams.charge[ic] = 0; + clusParams.Q_f_X[ic] = 0; + clusParams.Q_l_X[ic] = 0; + clusParams.Q_f_Y[ic] = 0; + clusParams.Q_l_Y[ic] = 0; + } + + first += threadIdx.x; + + __syncthreads(); + + // one thead per "digi" + + for (int i = first; i < numElements; i += blockDim.x) { + if (id[i] == InvId) continue; // not valid + if (id[i] != me) break; // end of module + if (clus[i] >= nclus) continue; + atomicMin(&clusParams.minRow[clus[i]], x[i]); + atomicMax(&clusParams.maxRow[clus[i]], x[i]); + atomicMin(&clusParams.minCol[clus[i]], y[i]); + atomicMax(&clusParams.maxCol[clus[i]], y[i]); + } + + __syncthreads(); + + for (int i = first; i < numElements; i += blockDim.x) { + if (id[i] == InvId) continue; // not valid + if (id[i] != me) break; // end of module + if (clus[i] >= nclus) continue; + atomicAdd(&clusParams.charge[clus[i]], adc[i]); + if (clusParams.minRow[clus[i]]==x[i]) atomicAdd(&clusParams.Q_f_X[clus[i]], adc[i]); + if (clusParams.maxRow[clus[i]]==x[i]) atomicAdd(&clusParams.Q_l_X[clus[i]], adc[i]); + if (clusParams.minCol[clus[i]]==y[i]) atomicAdd(&clusParams.Q_f_Y[clus[i]], adc[i]); + if (clusParams.maxCol[clus[i]]==y[i]) atomicAdd(&clusParams.Q_l_Y[clus[i]], adc[i]); + } + + __syncthreads(); + + // next one cluster per thread... + if (ic >= nclus) return; + + first = hitsModuleStart[me]; + auto h = first+ic; // output index in global memory + + assert(h < 2000*256); + + pixelCPEforGPU::position(cpeParams->commonParams(), cpeParams->detParams(me), clusParams, ic); + pixelCPEforGPU::errorFromDB(cpeParams->commonParams(), cpeParams->detParams(me), clusParams, ic); + + chargeh[h] = clusParams.charge[ic]; + + detInd[h] = me; + + xl[h]= clusParams.xpos[ic]; + yl[h]= clusParams.ypos[ic]; + + xe[h]= clusParams.xerr[ic]*clusParams.xerr[ic]; + ye[h]= clusParams.yerr[ic]*clusParams.yerr[ic]; + mr[h]= clusParams.minRow[ic]; + mc[h]= clusParams.minCol[ic]; + + // to global and compute phi... + cpeParams->detParams(me).frame.toGlobal(xl[h],yl[h], xg[h],yg[h],zg[h]); + // here correct for the beamspot... + xg[h]-=bs[0]; + yg[h]-=bs[1]; + zg[h]-=bs[2]; + + rg[h] = std::sqrt(xg[h]*xg[h]+yg[h]*yg[h]); + iph[h] = unsafe_atan2s<7>(yg[h],xg[h]); + + } + +} + +#endif // RecoLocalTracker_SiPixelRecHits_plugins_gpuPixelRecHits_h diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h b/RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h new file mode 100644 index 0000000000000..ea6eaf8458dde --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h @@ -0,0 +1,77 @@ +#ifndef RecoLocalTracker_SiPixelRecHits_plugins_siPixelRecHitsHeterogeneousProduct_h +#define RecoLocalTracker_SiPixelRecHits_plugins_siPixelRecHitsHeterogeneousProduct_h + +#include +#include + +#include "DataFormats/TrackerRecHit2D/interface/SiPixelRecHitCollection.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" +#include "HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h" + +namespace pixelCPEforGPU { + struct ParamsOnGPU; +} + +namespace siPixelRecHitsHeterogeneousProduct { + + using CPUProduct = int; // dummy + + static constexpr uint32_t maxHits() { return 65536;} + using hindex_type = uint16_t; // if above is <=2^16 + + struct HitsOnGPU{ + pixelCPEforGPU::ParamsOnGPU const * cpeParams = nullptr; // forwarded from setup, NOT owned + float * bs_d; + const uint32_t * hitsModuleStart_d; // forwarded from clusters + uint32_t * hitsLayerStart_d; + int32_t * charge_d; + uint16_t * detInd_d; + float *xg_d, *yg_d, *zg_d, *rg_d; + float *xl_d, *yl_d; + float *xerr_d, *yerr_d; + int16_t * iphi_d; + uint16_t * sortIndex_d; + uint16_t * mr_d; + uint16_t * mc_d; + + using Hist = HistoContainer; + Hist * hist_d; + uint8_t * hws_d; + + HitsOnGPU const * me_d = nullptr; + + // Owning pointers to the 32/16 bit arrays with size MAX_HITS + void *owner_32bit_; + size_t owner_32bit_pitch_; + void *owner_16bit_; + size_t owner_16bit_pitch_; + + // forwarded from PixelRecHit (FIXME) + uint32_t const * phase1TopologyLayerStart_d; + uint8_t const * phase1TopologyLayer_d; + + }; + + struct HitsOnCPU { + uint32_t const * hitsModuleStart = nullptr; + uint16_t const * detInd = nullptr; + int32_t const * charge = nullptr; + float const * xl = nullptr; + float const * yl = nullptr; + float const * xe = nullptr; + float const * ye = nullptr; + uint16_t const * mr = nullptr; + uint16_t const * mc = nullptr; + + HitsOnGPU const * gpu_d = nullptr; + uint32_t nHits; + }; + + using GPUProduct = HitsOnCPU; // FIXME fill cpu vectors on demand + + using HeterogeneousPixelRecHit = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct >; +} + +#endif // RecoLocalTracker_SiPixelRecHits_plugins_siPixelRecHitsHeterogeneousProduct_h diff --git a/RecoLocalTracker/SiPixelRecHits/python/PixelCPEESProducers_cff.py b/RecoLocalTracker/SiPixelRecHits/python/PixelCPEESProducers_cff.py index a1ff25af2e697..8e28bbb175181 100644 --- a/RecoLocalTracker/SiPixelRecHits/python/PixelCPEESProducers_cff.py +++ b/RecoLocalTracker/SiPixelRecHits/python/PixelCPEESProducers_cff.py @@ -19,6 +19,7 @@ # 4. Pixel Generic CPE # from RecoLocalTracker.SiPixelRecHits.PixelCPEGeneric_cfi import * +from RecoLocalTracker.SiPixelRecHits.PixelCPEFast_cfi import * # # 5. ESProducer for the Magnetic-field dependent template records # diff --git a/RecoLocalTracker/SiPixelRecHits/python/PixelCPEFast_cfi.py b/RecoLocalTracker/SiPixelRecHits/python/PixelCPEFast_cfi.py new file mode 100644 index 0000000000000..bda1fe45a3705 --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/python/PixelCPEFast_cfi.py @@ -0,0 +1,31 @@ +import FWCore.ParameterSet.Config as cms + +PixelCPEFastESProducer = cms.ESProducer("PixelCPEFastESProducer", + + ComponentName = cms.string('PixelCPEFast'), + Alpha2Order = cms.bool(True), + + # Edge cluster errors in microns (determined by looking at residual RMS) + EdgeClusterErrorX = cms.double( 50.0 ), + EdgeClusterErrorY = cms.double( 85.0 ), + + # these for CPEBase + useLAWidthFromDB = cms.bool(True), + useLAAlignmentOffsets = cms.bool(False), + + + # Can use errors predicted by the template code + # If UseErrorsFromTemplates is False, must also set + # TruncatePixelCharge and LoadTemplatesFromDB to be False + UseErrorsFromTemplates = cms.bool(True), + LoadTemplatesFromDB = cms.bool(True), + + # When set True this gives a slight improvement in resolution at no cost + TruncatePixelCharge = cms.bool(True), + + # petar, for clusterProbability() from TTRHs + ClusterProbComputationFlag = cms.int32(0), + + #MagneticFieldRecord: e.g. "" or "ParabolicMF" + MagneticFieldRecord = cms.ESInputTag(""), +) diff --git a/RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc b/RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc new file mode 100644 index 0000000000000..eb51dd5a2eaeb --- /dev/null +++ b/RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc @@ -0,0 +1,468 @@ +#include + +#include +#include + +#include "CondFormats/SiPixelTransient/interface/SiPixelTemplate.h" +#include "DataFormats/DetId/interface/DetId.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "Geometry/TrackerGeometryBuilder/interface/PixelGeomDetUnit.h" +#include "Geometry/TrackerGeometryBuilder/interface/RectangularPixelTopology.h" +#include "Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h" +#include "HeterogeneousCore/CUDAServices/interface/numberOfCUDADevices.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "MagneticField/Engine/interface/MagneticField.h" + +#include "RecoLocalTracker/SiPixelRecHits/interface/PixelCPEFast.h" + +// Services +// this is needed to get errors from templates + +namespace { + constexpr float micronsToCm = 1.0e-4; +} + +//----------------------------------------------------------------------------- +//! The constructor. +//----------------------------------------------------------------------------- +PixelCPEFast::PixelCPEFast(edm::ParameterSet const & conf, + const MagneticField * mag, + const TrackerGeometry& geom, + const TrackerTopology& ttopo, + const SiPixelLorentzAngle * lorentzAngle, + const SiPixelGenErrorDBObject * genErrorDBObject, + const SiPixelLorentzAngle * lorentzAngleWidth) : + PixelCPEBase(conf, mag, geom, ttopo, lorentzAngle, genErrorDBObject, nullptr, lorentzAngleWidth, 0) +{ + EdgeClusterErrorX_ = conf.getParameter("EdgeClusterErrorX"); + EdgeClusterErrorY_ = conf.getParameter("EdgeClusterErrorY"); + + UseErrorsFromTemplates_ = conf.getParameter("UseErrorsFromTemplates"); + TruncatePixelCharge_ = conf.getParameter("TruncatePixelCharge"); + + // Use errors from templates or from GenError + if ( UseErrorsFromTemplates_ ) { + if ( !SiPixelGenError::pushfile( *genErrorDBObject_, thePixelGenError_) ) + throw cms::Exception("InvalidCalibrationLoaded") + << "ERROR: GenErrors not filled correctly. Check the sqlite file. Using SiPixelTemplateDBObject version " + << ( *genErrorDBObject_ ).version(); + } + + // Rechit errors in case other, more correct, errors are not vailable + // This are constants. Maybe there is a more efficienct way to store them. + xerr_barrel_l1_ = { 0.00115, 0.00120, 0.00088 }; + xerr_barrel_l1_def_ = 0.01030; + yerr_barrel_l1_ = { 0.00375, 0.00230, 0.00250, 0.00250, 0.00230, 0.00230, 0.00210, 0.00210, 0.00240 }; + yerr_barrel_l1_def_ = 0.00210; + xerr_barrel_ln_ = { 0.00115, 0.00120, 0.00088}; + xerr_barrel_ln_def_ = 0.01030; + yerr_barrel_ln_ = { 0.00375, 0.00230, 0.00250, 0.00250, 0.00230, 0.00230, 0.00210, 0.00210, 0.00240 }; + yerr_barrel_ln_def_ = 0.00210; + xerr_endcap_ = { 0.0020, 0.0020 }; + xerr_endcap_def_ = 0.0020; + yerr_endcap_ = { 0.00210 }; + yerr_endcap_def_ = 0.00075; + + fillParamsForGpu(); +} + +const pixelCPEforGPU::ParamsOnGPU *PixelCPEFast::getGPUProductAsync(cuda::stream_t<>& cudaStream) const { + const auto& data = gpuData_.dataForCurrentDeviceAsync(cudaStream, [this](GPUData& data, cuda::stream_t<>& stream) { + // and now copy to device... + cudaCheck(cudaMalloc((void**) & data.h_paramsOnGPU.m_commonParams, sizeof(pixelCPEforGPU::CommonParams))); + cudaCheck(cudaMalloc((void**) & data.h_paramsOnGPU.m_detParams, this->m_detParamsGPU.size()*sizeof(pixelCPEforGPU::DetParams))); + cudaCheck(cudaMalloc((void**) & data.d_paramsOnGPU, sizeof(pixelCPEforGPU::ParamsOnGPU))); + + cudaCheck(cudaMemcpyAsync(data.d_paramsOnGPU, &data.h_paramsOnGPU, sizeof(pixelCPEforGPU::ParamsOnGPU), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.h_paramsOnGPU.m_commonParams, &this->m_commonParamsGPU, sizeof(pixelCPEforGPU::CommonParams), cudaMemcpyDefault, stream.id())); + cudaCheck(cudaMemcpyAsync(data.h_paramsOnGPU.m_detParams, this->m_detParamsGPU.data(), this->m_detParamsGPU.size()*sizeof(pixelCPEforGPU::DetParams), cudaMemcpyDefault, stream.id())); + }); + return data.d_paramsOnGPU; +} + +void PixelCPEFast::fillParamsForGpu() { + m_commonParamsGPU.theThicknessB = m_DetParams.front().theThickness; + m_commonParamsGPU.theThicknessE = m_DetParams.back().theThickness; + m_commonParamsGPU.thePitchX = m_DetParams[0].thePitchX; + m_commonParamsGPU.thePitchY = m_DetParams[0].thePitchY; + + //uint32_t oldLayer = 0; + m_detParamsGPU.resize(m_DetParams.size()); + for (auto i=0U; iindex()==int(i)); + assert(m_commonParamsGPU.thePitchY==p.thePitchY); + assert(m_commonParamsGPU.thePitchX==p.thePitchX); + //assert(m_commonParamsGPU.theThickness==p.theThickness); + + g.isBarrel = GeomDetEnumerators::isBarrel(p.thePart); + g.isPosZ = p.theDet->surface().position().z()>0; + g.layer = ttopo_.layer(p.theDet->geographicalId()); + g.index=i; // better be! + g.rawId = p.theDet->geographicalId(); + + assert( (g.isBarrel ?m_commonParamsGPU.theThicknessB : m_commonParamsGPU.theThicknessE) ==p.theThickness ); + + //if (m_commonParamsGPU.theThickness!=p.theThickness) + // std::cout << i << (g.isBarrel ? "B " : "E ") << m_commonParamsGPU.theThickness<<"!="<surface().position(); + auto rr = pixelCPEforGPU::Rotation(p.theDet->surface().rotation()); + g.frame = pixelCPEforGPU::Frame(vv.x(),vv.y(),vv.z(),rr); + + + // errors ..... + ClusterParamGeneric cp; + auto gvx = p.theOrigin.x() + 40.f*m_commonParamsGPU.thePitchX; + auto gvy = p.theOrigin.y(); + auto gvz = 1.f/p.theOrigin.z(); + //--- Note that the normalization is not required as only the ratio used + + // calculate angles + cp.cotalpha = gvx*gvz; + cp.cotbeta = gvy*gvz; + + cp.with_track_angle = false; + + auto lape = p.theDet->localAlignmentError(); + if ( lape.invalid() ) lape = LocalError(); // zero.... + +#ifdef DUMP_ERRORS + auto m=10000.f; + for (float qclus = 15000; qclus<35000; qclus+=15000){ + errorFromTemplates(p,cp,qclus); + + std::cout << i << ' ' << qclus << ' ' << cp.pixmx + << ' ' << m*cp.sigmax << ' ' << m*cp.sx1 << ' ' << m*cp.sx2 + << ' ' << m*cp.sigmay << ' ' << m*cp.sy1 << ' ' << m*cp.sy2 + << std::endl; + } + std::cout << i << ' ' << m*std::sqrt(lape.xx()) <<' '<< m*std::sqrt(lape.yy()) << std::endl; +#endif + + + errorFromTemplates(p,cp,20000.f); + g.sx[0] = cp.sigmax; + g.sx[1] = cp.sx1; + g.sx[2] = cp.sx2; + + g.sy[0] = cp.sigmay; + g.sy[1] = cp.sy1; + g.sy[2] = cp.sy2; + + + /* + // from run1?? + if (i<96) { + g.sx[0] = 0.00120; + g.sx[1] = 0.00115; + g.sx[2] = 0.0050; + + g.sy[0] = 0.00210; + g.sy[1] = 0.00375; + g.sy[2] = 0.0085; + } else if (g.isBarrel) { + g.sx[0] = 0.00120; + g.sx[1] = 0.00115; + g.sx[2] = 0.0050; + + g.sy[0] = 0.00210; + g.sy[1] = 0.00375; + g.sy[2] = 0.0085; + } else { + g.sx[0] = 0.0020; + g.sx[1] = 0.0020; + g.sx[2] = 0.0050; + + g.sy[0] = 0.0021; + g.sy[1] = 0.0021; + g.sy[2] = 0.0085; + } + */ + + + for (int i=0; i<3; ++i) { + g.sx[i] = std::sqrt(g.sx[i]*g.sx[i]+lape.xx()); + g.sy[i] = std::sqrt(g.sy[i]*g.sy[i]+lape.yy()); + } + + } +} + +PixelCPEFast::~PixelCPEFast() {} + +PixelCPEFast::GPUData::~GPUData() { + if(d_paramsOnGPU != nullptr) { + cudaFree(h_paramsOnGPU.m_commonParams); + cudaFree(h_paramsOnGPU.m_detParams); + cudaFree(d_paramsOnGPU); + } +} + +PixelCPEBase::ClusterParam* PixelCPEFast::createClusterParam(const SiPixelCluster & cl) const +{ + return new ClusterParamGeneric(cl); +} + + + +void +PixelCPEFast::errorFromTemplates(DetParam const & theDetParam, ClusterParamGeneric & theClusterParam, float qclus) const +{ + float locBz = theDetParam.bz; + float locBx = theDetParam.bx; + //cout << "PixelCPEFast::localPosition(...) : locBz = " << locBz << endl; + + theClusterParam.pixmx = std::numeric_limits::max(); // max pixel charge for truncation of 2-D cluster + + theClusterParam.sigmay = -999.9; // CPE Generic y-error for multi-pixel cluster + theClusterParam.sigmax = -999.9; // CPE Generic x-error for multi-pixel cluster + theClusterParam.sy1 = -999.9; // CPE Generic y-error for single single-pixel + theClusterParam.sy2 = -999.9; // CPE Generic y-error for single double-pixel cluster + theClusterParam.sx1 = -999.9; // CPE Generic x-error for single single-pixel cluster + theClusterParam.sx2 = -999.9; // CPE Generic x-error for single double-pixel cluster + + float dummy; + + SiPixelGenError gtempl(thePixelGenError_); + int gtemplID_ = theDetParam.detTemplateId; + + theClusterParam.qBin_ = gtempl.qbin( gtemplID_, theClusterParam.cotalpha, theClusterParam.cotbeta, locBz, locBx, qclus, + false, + theClusterParam.pixmx, theClusterParam.sigmay, dummy, + theClusterParam.sigmax, dummy, theClusterParam.sy1, + dummy, theClusterParam.sy2, dummy, theClusterParam.sx1, + dummy, theClusterParam.sx2, dummy ); + + theClusterParam.sigmax = theClusterParam.sigmax * micronsToCm; + theClusterParam.sx1 = theClusterParam.sx1 * micronsToCm; + theClusterParam.sx2 = theClusterParam.sx2 * micronsToCm; + + theClusterParam.sigmay = theClusterParam.sigmay * micronsToCm; + theClusterParam.sy1 = theClusterParam.sy1 * micronsToCm; + theClusterParam.sy2 = theClusterParam.sy2 * micronsToCm; +} + +//----------------------------------------------------------------------------- +//! Hit position in the local frame (in cm). Unlike other CPE's, this +//! one converts everything from the measurement frame (in channel numbers) +//! into the local frame (in centimeters). +//----------------------------------------------------------------------------- +LocalPoint +PixelCPEFast::localPosition(DetParam const & theDetParam, ClusterParam & theClusterParamBase) const +{ + ClusterParamGeneric & theClusterParam = static_cast(theClusterParamBase); + + assert(!theClusterParam.with_track_angle); + + if ( UseErrorsFromTemplates_ ) { + errorFromTemplates(theDetParam, theClusterParam, theClusterParam.theCluster->charge()); + } + else { + theClusterParam.qBin_ = 0; + } + + int Q_f_X; //!< Q of the first pixel in X + int Q_l_X; //!< Q of the last pixel in X + int Q_f_Y; //!< Q of the first pixel in Y + int Q_l_Y; //!< Q of the last pixel in Y + collect_edge_charges( theClusterParam, + Q_f_X, Q_l_X, + Q_f_Y, Q_l_Y, + UseErrorsFromTemplates_ && TruncatePixelCharge_ + ); + + // do GPU like ... + pixelCPEforGPU::ClusParams cp; + + cp.minRow[0] = theClusterParam.theCluster->minPixelRow(); + cp.maxRow[0] = theClusterParam.theCluster->maxPixelRow(); + cp.minCol[0] = theClusterParam.theCluster->minPixelCol(); + cp.maxCol[0] = theClusterParam.theCluster->maxPixelCol(); + + cp.Q_f_X[0] = Q_f_X; + cp.Q_l_X[0] = Q_l_X; + cp.Q_f_Y[0] = Q_f_Y; + cp.Q_l_Y[0] = Q_l_Y; + + auto ind = theDetParam.theDet->index(); + pixelCPEforGPU::position(m_commonParamsGPU, m_detParamsGPU[ind],cp,0); + auto xPos = cp.xpos[0]; + auto yPos = cp.ypos[0]; + + //--- Now put the two together + LocalPoint pos_in_local( xPos, yPos ); + return pos_in_local; +} + +//----------------------------------------------------------------------------- +//! Collect the edge charges in x and y, in a single pass over the pixel vector. +//! Calculate charge in the first and last pixel projected in x and y +//! and the inner cluster charge, projected in x and y. +//----------------------------------------------------------------------------- +void +PixelCPEFast:: +collect_edge_charges(ClusterParam & theClusterParamBase, //!< input, the cluster + int & Q_f_X, //!< output, Q first in X + int & Q_l_X, //!< output, Q last in X + int & Q_f_Y, //!< output, Q first in Y + int & Q_l_Y, //!< output, Q last in Y + bool truncate +) +{ + ClusterParamGeneric & theClusterParam = static_cast(theClusterParamBase); + + // Initialize return variables. + Q_f_X = Q_l_X = 0; + Q_f_Y = Q_l_Y = 0; + + // Obtain boundaries in index units + int xmin = theClusterParam.theCluster->minPixelRow(); + int xmax = theClusterParam.theCluster->maxPixelRow(); + int ymin = theClusterParam.theCluster->minPixelCol(); + int ymax = theClusterParam.theCluster->maxPixelCol(); + + // Iterate over the pixels. + int isize = theClusterParam.theCluster->size(); + for (int i = 0; i != isize; ++i) + { + auto const & pixel = theClusterParam.theCluster->pixel(i); + // ggiurgiu@fnal.gov: add pixel charge truncation + int pix_adc = pixel.adc; + if ( truncate ) + pix_adc = std::min(pix_adc, theClusterParam.pixmx ); + + // + // X projection + if ( pixel.x == xmin ) Q_f_X += pix_adc; + if ( pixel.x == xmax ) Q_l_X += pix_adc; + // + // Y projection + if ( pixel.y == ymin ) Q_f_Y += pix_adc; + if ( pixel.y == ymax ) Q_l_Y += pix_adc; + } +} + + +//============== INFLATED ERROR AND ERRORS FROM DB BELOW ================ + +//------------------------------------------------------------------------- +// Hit error in the local frame +//------------------------------------------------------------------------- +LocalError +PixelCPEFast::localError(DetParam const & theDetParam, ClusterParam & theClusterParamBase) const +{ + + ClusterParamGeneric & theClusterParam = static_cast(theClusterParamBase); + + // Default errors are the maximum error used for edge clusters. + // These are determined by looking at residuals for edge clusters + float xerr = EdgeClusterErrorX_ * micronsToCm; + float yerr = EdgeClusterErrorY_ * micronsToCm; + + + // Find if cluster is at the module edge. + int maxPixelCol = theClusterParam.theCluster->maxPixelCol(); + int maxPixelRow = theClusterParam.theCluster->maxPixelRow(); + int minPixelCol = theClusterParam.theCluster->minPixelCol(); + int minPixelRow = theClusterParam.theCluster->minPixelRow(); + + bool edgex = phase1PixelTopology::isEdgeX(minPixelRow) | phase1PixelTopology::isEdgeX(maxPixelRow); + bool edgey = phase1PixelTopology::isEdgeY(minPixelCol) | phase1PixelTopology::isEdgeY(maxPixelCol); + + unsigned int sizex = theClusterParam.theCluster->sizeX(); + unsigned int sizey = theClusterParam.theCluster->sizeY(); + + // Find if cluster contains double (big) pixels. + bool bigInX = theDetParam.theRecTopol->containsBigPixelInX( minPixelRow, maxPixelRow ); + bool bigInY = theDetParam.theRecTopol->containsBigPixelInY( minPixelCol, maxPixelCol ); + + if (UseErrorsFromTemplates_ ) { + // + // Use template errors + + if ( !edgex ) { // Only use this for non-edge clusters + if ( sizex == 1 ) { + if ( !bigInX ) {xerr = theClusterParam.sx1;} + else {xerr = theClusterParam.sx2;} + } else {xerr = theClusterParam.sigmax;} + } + + if ( !edgey ) { // Only use for non-edge clusters + if ( sizey == 1 ) { + if ( !bigInY ) {yerr = theClusterParam.sy1;} + else {yerr = theClusterParam.sy2;} + } else {yerr = theClusterParam.sigmay;} + } + + } else { // simple errors + + // This are the simple errors, hardcoded in the code + //cout << "Track angles are not known " << endl; + //cout << "Default angle estimation which assumes track from PV (0,0,0) does not work." << endl; + + if ( GeomDetEnumerators::isTrackerPixel(theDetParam.thePart) ) { + if(GeomDetEnumerators::isBarrel(theDetParam.thePart)) { + + DetId id = (theDetParam.theDet->geographicalId()); + int layer=ttopo_.layer(id); + if ( layer==1 ) { + if ( !edgex ) { + if ( sizex<=xerr_barrel_l1_.size() ) xerr=xerr_barrel_l1_[sizex-1]; + else xerr=xerr_barrel_l1_def_; + } + + if ( !edgey ) { + if ( sizey<=yerr_barrel_l1_.size() ) yerr=yerr_barrel_l1_[sizey-1]; + else yerr=yerr_barrel_l1_def_; + } + } else{ // layer 2,3 + if ( !edgex ) { + if ( sizex<=xerr_barrel_ln_.size() ) xerr=xerr_barrel_ln_[sizex-1]; + else xerr=xerr_barrel_ln_def_; + } + + if ( !edgey ) { + if ( sizey<=yerr_barrel_ln_.size() ) yerr=yerr_barrel_ln_[sizey-1]; + else yerr=yerr_barrel_ln_def_; + } + } + + } else { // EndCap + + if ( !edgex ) { + if ( sizex<=xerr_endcap_.size() ) xerr=xerr_endcap_[sizex-1]; + else xerr=xerr_endcap_def_; + } + + if ( !edgey ) { + if ( sizey<=yerr_endcap_.size() ) yerr=yerr_endcap_[sizey-1]; + else yerr=yerr_endcap_def_; + } + } // end endcap + } + + } // end + + auto xerr_sq = xerr*xerr; + auto yerr_sq = yerr*yerr; + + return LocalError( xerr_sq, 0, yerr_sq ); + +} diff --git a/RecoPixelVertexing/Configuration/python/RecoPixelVertexing_cff.py b/RecoPixelVertexing/Configuration/python/RecoPixelVertexing_cff.py index 34ee6fadb04de..e784b53b7ce1f 100644 --- a/RecoPixelVertexing/Configuration/python/RecoPixelVertexing_cff.py +++ b/RecoPixelVertexing/Configuration/python/RecoPixelVertexing_cff.py @@ -4,7 +4,7 @@ # # for STARTUP ONLY use try and use Offline 3D PV from pixelTracks, with adaptive vertex # -#from RecoPixelVertexing.PixelVertexFinding.PixelVertexes_cff import * -from RecoVertex.PrimaryVertexProducer.OfflinePixel3DPrimaryVertices_cfi import * +from RecoPixelVertexing.PixelVertexFinding.PixelVertexes_cff import * +#from RecoVertex.PrimaryVertexProducer.OfflinePixel3DPrimaryVertices_cfi import * recopixelvertexingTask = cms.Task(pixelTracksTask,pixelVertices) recopixelvertexing = cms.Sequence(recopixelvertexingTask) diff --git a/RecoPixelVertexing/Configuration/python/customizePixelTracksForProfiling.py b/RecoPixelVertexing/Configuration/python/customizePixelTracksForProfiling.py index 4713b64e5e48a..15224adb78cc3 100644 --- a/RecoPixelVertexing/Configuration/python/customizePixelTracksForProfiling.py +++ b/RecoPixelVertexing/Configuration/python/customizePixelTracksForProfiling.py @@ -1,9 +1,12 @@ import FWCore.ParameterSet.Config as cms def customizePixelTracksForProfiling(process): + process.MessageLogger.cerr.FwkReport.reportEvery = 100 + process.out = cms.OutputModule("AsciiOutputModule", outputCommands = cms.untracked.vstring( "keep *_pixelTracks_*_*", + "keep *_pixelVertices_*_*", ), verbosity = cms.untracked.uint32(0), ) @@ -13,3 +16,26 @@ def customizePixelTracksForProfiling(process): process.schedule = cms.Schedule(process.raw2digi_step, process.reconstruction_step, process.outPath) return process + +def customizePixelTracksForProfilingDisableConversion(process): + process = customizePixelTracksForProfiling(process) + + # Disable conversions to legacy + process.siPixelClustersPreSplitting.gpuEnableConversion = False + process.siPixelRecHitsPreSplitting.gpuEnableConversion = False + process.pixelTracksHitQuadruplets.gpuEnableConversion = False + process.pixelTracks.gpuEnableConversion = False + process.pixelVertices.gpuEnableConversion = False + + return process + +def customizePixelTracksForProfilingDisableTransfer(process): + process = customizePixelTracksForProfilingDisableConversion(process) + + # Disable "unnecessary" transfers to CPU + process.siPixelClustersPreSplitting.gpuEnableTransfer = False + process.siPixelRecHitsPreSplitting.gpuEnableTransfer = False + process.pixelTracksHitQuadruplets.gpuEnableTransfer = False + process.pixelVertices.gpuEnableTransfer = False + + return process diff --git a/RecoPixelVertexing/PixelTrackFitting/BuildFile.xml b/RecoPixelVertexing/PixelTrackFitting/BuildFile.xml index b0b2657c46e38..3300d67809f33 100644 --- a/RecoPixelVertexing/PixelTrackFitting/BuildFile.xml +++ b/RecoPixelVertexing/PixelTrackFitting/BuildFile.xml @@ -1,4 +1,5 @@ + diff --git a/RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h b/RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h new file mode 100644 index 0000000000000..ba0f0aa13e1a6 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h @@ -0,0 +1,112 @@ +#ifndef RecoPixelVertexing_PixelTrackFitting_interface_FitResult_h +#define RecoPixelVertexing_PixelTrackFitting_interface_FitResult_h + +#include + +#include +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +namespace Rfit +{ + +constexpr double d = 1.e-4; //!< used in numerical derivative (J2 in Circle_fit()) +constexpr unsigned int max_nop = 4; //!< In order to avoid use of dynamic memory + + +using VectorXd = Eigen::VectorXd; +using MatrixXd = Eigen::MatrixXd; +template +using MatrixNd = Eigen::Matrix; +template +using ArrayNd = Eigen::Array; +template +using Matrix2Nd = Eigen::Matrix; +template +using Matrix3Nd = Eigen::Matrix; +template +using Matrix2xNd = Eigen::Matrix; +template +using Array2xNd = Eigen::Array; +template +using Matrix3xNd = Eigen::Matrix; +template +using MatrixNx3d = Eigen::Matrix; +template +using MatrixNx5d = Eigen::Matrix; +template +using VectorNd = Eigen::Matrix; +template +using Vector2Nd = Eigen::Matrix; +template +using Vector3Nd = Eigen::Matrix; +template +using RowVectorNd = Eigen::Matrix; +template +using RowVector2Nd = Eigen::Matrix; + + + +using Vector2d = Eigen::Vector2d; +using Vector3d = Eigen::Vector3d; +using Vector4d = Eigen::Vector4d; +using Matrix2d = Eigen::Matrix2d; +using Matrix3d = Eigen::Matrix3d; +using Matrix4d = Eigen::Matrix4d; +using Matrix5d = Eigen::Matrix; +using Matrix6d = Eigen::Matrix; +using Vector5d = Eigen::Matrix; + +using Matrix3f = Eigen::Matrix3f; +using Vector3f = Eigen::Vector3f; +using Vector4f = Eigen::Vector4f; +using Vector6f = Eigen::Matrix; + +using u_int = unsigned int; + + +struct circle_fit +{ + Vector3d par; //!< parameter: (X0,Y0,R) + Matrix3d cov; + /*!< covariance matrix: \n + |cov(X0,X0)|cov(Y0,X0)|cov( R,X0)| \n + |cov(X0,Y0)|cov(Y0,Y0)|cov( R,Y0)| \n + |cov(X0, R)|cov(Y0, R)|cov( R, R)| + */ + int32_t q; //!< particle charge + float chi2 = 0.0; +}; + +struct line_fit +{ + Vector2d par; //!<(cotan(theta),Zip) + Matrix2d cov; + /*!< + |cov(c_t,c_t)|cov(Zip,c_t)| \n + |cov(c_t,Zip)|cov(Zip,Zip)| + */ + double chi2 = 0.0; +}; + +struct helix_fit +{ + Vector5d par; //!<(phi,Tip,pt,cotan(theta)),Zip) + Matrix5d cov; + /*!< ()->cov() \n + |(phi,phi)|(Tip,phi)|(p_t,phi)|(c_t,phi)|(Zip,phi)| \n + |(phi,Tip)|(Tip,Tip)|(p_t,Tip)|(c_t,Tip)|(Zip,Tip)| \n + |(phi,p_t)|(Tip,p_t)|(p_t,p_t)|(c_t,p_t)|(Zip,p_t)| \n + |(phi,c_t)|(Tip,c_t)|(p_t,c_t)|(c_t,c_t)|(Zip,c_t)| \n + |(phi,Zip)|(Tip,Zip)|(p_t,Zip)|(c_t,Zip)|(Zip,Zip)| + */ + float chi2_circle; + float chi2_line; +// Vector4d fast_fit; + int32_t q; //!< particle charge +} __attribute__((aligned(16))); + +} // namespace RFit +#endif diff --git a/RecoPixelVertexing/PixelTrackFitting/interface/PixelFitterByRiemannParaboloid.h b/RecoPixelVertexing/PixelTrackFitting/interface/PixelFitterByRiemannParaboloid.h new file mode 100644 index 0000000000000..f9a0f12ecb500 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/interface/PixelFitterByRiemannParaboloid.h @@ -0,0 +1,28 @@ +#ifndef RecoPixelVertexing_PixelTrackFitting_PixelFitterByRiemannParaboloid_H +#define RecoPixelVertexing_PixelTrackFitting_PixelFitterByRiemannParaboloid_H + +#include + +#include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/TrackingRecHit/interface/TrackingRecHit.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelFitterBase.h" +#include "RecoTracker/TkTrackingRegions/interface/TrackingRegion.h" + +class PixelFitterByRiemannParaboloid final : public PixelFitterBase { +public: + explicit PixelFitterByRiemannParaboloid(const edm::EventSetup *es, const MagneticField *field, + bool useErrors, bool useMultipleScattering); + ~PixelFitterByRiemannParaboloid() override = default; + std::unique_ptr run(const std::vector& hits, + const TrackingRegion& region) const override; + +private: + const edm::EventSetup *es_; + const MagneticField *field_; + bool useErrors_; + bool useMultipleScattering_; +}; + +#endif // RecoPixelVertexing_PixelTrackFitting_PixelFitterByRiemannParaboloid_H diff --git a/RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h b/RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h new file mode 100644 index 0000000000000..3e93aab13d00d --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h @@ -0,0 +1,1181 @@ +#ifndef RecoPixelVertexing_PixelTrackFitting_interface_RiemannFit_h +#define RecoPixelVertexing_PixelTrackFitting_interface_RiemannFit_h + +#include "FitResult.h" + + +namespace Rfit +{ + +template +__host__ __device__ void printIt(C* m, const char* prefix = "") +{ +#ifdef RFIT_DEBUG + for (u_int r = 0; r < m->rows(); ++r) + { + for (u_int c = 0; c < m->cols(); ++c) + { + printf("%s Matrix(%d,%d) = %g\n", prefix, r, c, (*m)(r, c)); + } + } +#endif +} + +/*! + \brief raise to square. +*/ +template +__host__ __device__ inline T sqr(const T a) +{ + return a * a; +} + +/*! + \brief Compute cross product of two 2D vector (assuming z component 0), + returning z component of the result. + \param a first 2D vector in the product. + \param b second 2D vector in the product. + \return z component of the cross product. +*/ + +__host__ __device__ inline double cross2D(const Vector2d& a, const Vector2d& b) +{ + return a.x() * b.y() - a.y() * b.x(); +} + +/*! + * load error in CMSSW format to our formalism + * + */ + template + __host__ __device__ void loadCovariance2D(M6x4f const & ge, M2Nd & hits_cov) { + // Index numerology: + // i: index of the hits/point (0,..,3) + // j: index of space component (x,y,z) + // l: index of space components (x,y,z) + // ge is always in sync with the index i and is formatted as: + // ge[] ==> [xx, xy, yy, xz, yz, zz] + // in (j,l) notation, we have: + // ge[] ==> [(0,0), (0,1), (1,1), (0,2), (1,2), (2,2)] + // so the index ge_idx corresponds to the matrix elements: + // | 0 1 3 | + // | 1 2 4 | + // | 3 4 5 | + constexpr uint32_t hits_in_fit = 4; // Fixme + for (uint32_t i=0; i< hits_in_fit; ++i) { + auto ge_idx = 0; auto j=0; auto l=0; + hits_cov(i + j * hits_in_fit, i + l * hits_in_fit) = ge.col(i)[ge_idx]; + ge_idx = 2; j=1; l=1; + hits_cov(i + j * hits_in_fit, i + l * hits_in_fit) = ge.col(i)[ge_idx]; + ge_idx = 1; j=1; l=0; + hits_cov(i + l * hits_in_fit, i + j * hits_in_fit) = + hits_cov(i + j * hits_in_fit, i + l * hits_in_fit) = ge.col(i)[ge_idx]; + } + } + + template + __host__ __device__ void loadCovariance(M6x4f const & ge, Matrix3Nd & hits_cov) { + + // Index numerology: + // i: index of the hits/point (0,..,3) + // j: index of space component (x,y,z) + // l: index of space components (x,y,z) + // ge is always in sync with the index i and is formatted as: + // ge[] ==> [xx, xy, yy, xz, yz, zz] + // in (j,l) notation, we have: + // ge[] ==> [(0,0), (0,1), (1,1), (0,2), (1,2), (2,2)] + // so the index ge_idx corresponds to the matrix elements: + // | 0 1 3 | + // | 1 2 4 | + // | 3 4 5 | + constexpr uint32_t hits_in_fit = 4; // Fixme + for (uint32_t i=0; i +__host__ __device__ inline +void computeRadLenUniformMaterial(const VNd1 &length_values, + VNd2 & rad_lengths) { + // Radiation length of the pixel detector in the uniform assumption, with + // 0.06 rad_len at 16 cm + constexpr double XX_0_inv = 0.06/16.; +// const double XX_0 = 1000.*16.f/(0.06); + u_int n = length_values.rows(); + rad_lengths(0) = length_values(0)*XX_0_inv; + for (u_int j = 1; j < n; ++j) { + rad_lengths(j) = std::abs(length_values(j)-length_values(j-1))*XX_0_inv; + } +} + +/*! + \brief Compute the covariance matrix along cartesian S-Z of points due to + multiple Coulomb scattering to be used in the line_fit, for the barrel + and forward cases. + The input covariance matrix is in the variables s-z, original and + unrotated. + The multiple scattering component is computed in the usual linear + approximation, using the 3D path which is computed as the squared root of + the squared sum of the s and z components passed in. + Internally a rotation by theta is performed and the covariance matrix + returned is the one in the direction orthogonal to the rotated S3D axis, + i.e. along the rotated Z axis. + The choice of the rotation is not arbitrary, but derived from the fact that + putting the horizontal axis along the S3D direction allows the usage of the + ordinary least squared fitting techiques with the trivial parametrization y + = mx + q, avoiding the patological case with m = +/- inf, that would + correspond to the case at eta = 0. + */ + + template +__host__ __device__ inline auto Scatter_cov_line(Matrix2d const * cov_sz, + const V4& fast_fit, + VNd1 const& s_arcs, + VNd2 const& z_values, + const double theta, + const double B, + MatrixNd& ret) +{ +#ifdef RFIT_DEBUG + Rfit::printIt(&s_arcs, "Scatter_cov_line - s_arcs: "); +#endif + constexpr auto n = N; + double p_t = std::min(20.,fast_fit(2) * B); // limit pt to avoid too small error!!! + double p_2 = p_t * p_t * (1. + 1. / (fast_fit(3) * fast_fit(3))); + VectorNd rad_lengths_S; + // See documentation at http://eigen.tuxfamily.org/dox/group__TutorialArrayClass.html + // Basically, to perform cwise operations on Matrices and Vectors, you need + // to transform them into Array-like objects. + VectorNd S_values = s_arcs.array() * s_arcs.array() + z_values.array() * z_values.array(); + S_values = S_values.array().sqrt(); + computeRadLenUniformMaterial(S_values, rad_lengths_S); + VectorNd sig2_S; + sig2_S = .000225 / p_2 * (1. + 0.038 * rad_lengths_S.array().log()).abs2() * rad_lengths_S.array(); +#ifdef RFIT_DEBUG + Rfit::printIt(cov_sz, "Scatter_cov_line - cov_sz: "); +#endif + Matrix2Nd tmp = Matrix2Nd::Zero(); + for (u_int k = 0; k < n; ++k) { + tmp(k, k) = cov_sz[k](0, 0); + tmp(k + n, k + n) = cov_sz[k](1, 1); + tmp(k, k + n) = tmp(k + n, k) = cov_sz[k](0, 1); + } + for (u_int k = 0; k < n; ++k) + { + for (u_int l = k; l < n; ++l) + { + for (u_int i = 0; i < std::min(k, l); ++i) + { + tmp(k + n, l + n) += std::abs(S_values(k) - S_values(i)) * std::abs(S_values(l) - S_values(i)) * sig2_S(i); + } + tmp(l + n, k + n) = tmp(k + n, l + n); + } + } + // We are interested only in the errors orthogonal to the rotated s-axis + // which, in our formalism, are in the lower square matrix. +#ifdef RFIT_DEBUG + Rfit::printIt(&tmp, "Scatter_cov_line - tmp: "); +#endif + ret = tmp.block(n, n, n, n); +} + +/*! + \brief Compute the covariance matrix (in radial coordinates) of points in + the transverse plane due to multiple Coulomb scattering. + \param p2D 2D points in the transverse plane. + \param fast_fit fast_fit Vector4d result of the previous pre-fit + structured in this form:(X0, Y0, R, Tan(Theta))). + \param B magnetic field use to compute p + \return scatter_cov_rad errors due to multiple scattering. + \warning input points must be ordered radially from the detector center + (from inner layer to outer ones; points on the same layer must ordered too). + \details Only the tangential component is computed (the radial one is + negligible). + */ + template + __host__ __device__ inline MatrixNd Scatter_cov_rad(const M2xN& p2D, + const V4& fast_fit, + VectorNd const& rad, + double B) +{ + u_int n = N; + double p_t = std::min(20.,fast_fit(2) * B); // limit pt to avoid too small error!!! + double p_2 = p_t * p_t * (1. + 1. / (fast_fit(3) * fast_fit(3))); + double theta = atan(fast_fit(3)); + theta = theta < 0. ? theta + M_PI : theta; + VectorNd s_values; + VectorNd rad_lengths; + const Vector2d o(fast_fit(0), fast_fit(1)); + + // associated Jacobian, used in weights and errors computation + for (u_int i = 0; i < n; ++i) + { // x + Vector2d p = p2D.block(0, i, 2, 1) - o; + const double cross = cross2D(-o, p); + const double dot = (-o).dot(p); + const double atan2_ = atan2(cross, dot); + s_values(i) = std::abs(atan2_ * fast_fit(2)); + } + computeRadLenUniformMaterial(s_values*sqrt(1. + 1./(fast_fit(3)*fast_fit(3))), rad_lengths); + MatrixNd scatter_cov_rad = MatrixNd::Zero(); + VectorNd sig2 = (1. + 0.038 * rad_lengths.array().log()).abs2() * rad_lengths.array(); + sig2 *= 0.000225 / ( p_2 * sqr(sin(theta)) ); + for (u_int k = 0; k < n; ++k) + { + for (u_int l = k; l < n; ++l) + { + for (u_int i = 0; i < std::min(k, l); ++i) + { + scatter_cov_rad(k, l) += (rad(k) - rad(i)) * (rad(l) - rad(i)) * sig2(i); + } + scatter_cov_rad(l, k) = scatter_cov_rad(k, l); + } + } +#ifdef RFIT_DEBUG + Rfit::printIt(&scatter_cov_rad, "Scatter_cov_rad - scatter_cov_rad: "); +#endif + return scatter_cov_rad; +} + +/*! + \brief Transform covariance matrix from radial (only tangential component) + to Cartesian coordinates (only transverse plane component). + \param p2D 2D points in the transverse plane. + \param cov_rad covariance matrix in radial coordinate. + \return cov_cart covariance matrix in Cartesian coordinates. +*/ + + template + __host__ __device__ inline Matrix2Nd cov_radtocart(const M2xN& p2D, + const MatrixNd& cov_rad, + const VectorNd& rad) +{ +#ifdef RFIT_DEBUG + printf("Address of p2D: %p\n", &p2D); +#endif + printIt(&p2D, "cov_radtocart - p2D:"); + u_int n = p2D.cols(); + Matrix2Nd cov_cart = Matrix2Nd::Zero(); + VectorNd rad_inv = rad.cwiseInverse(); + printIt(&rad_inv, "cov_radtocart - rad_inv:"); + for (u_int i = 0; i < n; ++i) + { + for (u_int j = i; j < n; ++j) + { + cov_cart(i, j) = cov_rad(i, j) * p2D(1, i) * rad_inv(i) * p2D(1, j) * rad_inv(j); + cov_cart(i + n, j + n) = cov_rad(i, j) * p2D(0, i) * rad_inv(i) * p2D(0, j) * rad_inv(j); + cov_cart(i, j + n) = -cov_rad(i, j) * p2D(1, i) * rad_inv(i) * p2D(0, j) * rad_inv(j); + cov_cart(i + n, j) = -cov_rad(i, j) * p2D(0, i) * rad_inv(i) * p2D(1, j) * rad_inv(j); + cov_cart(j, i) = cov_cart(i, j); + cov_cart(j + n, i + n) = cov_cart(i + n, j + n); + cov_cart(j + n, i) = cov_cart(i, j + n); + cov_cart(j, i + n) = cov_cart(i + n, j); + } + } + return cov_cart; +} + +/*! + \brief Transform covariance matrix from Cartesian coordinates (only + transverse plane component) to radial coordinates (both radial and + tangential component but only diagonal terms, correlation between different + point are not managed). + \param p2D 2D points in transverse plane. + \param cov_cart covariance matrix in Cartesian coordinates. + \return cov_rad covariance matrix in raidal coordinate. + \warning correlation between different point are not computed. +*/ + template + __host__ __device__ inline VectorNd cov_carttorad(const M2xN& p2D, + const Matrix2Nd& cov_cart, + const VectorNd& rad) +{ + u_int n = p2D.cols(); + VectorNd cov_rad; + const VectorNd rad_inv2 = rad.cwiseInverse().array().square(); + for (u_int i = 0; i < n; ++i) + { + //!< in case you have (0,0) to avoid dividing by 0 radius + if (rad(i) < 1.e-4) + cov_rad(i) = cov_cart(i, i); + else + { + cov_rad(i) = rad_inv2(i) * (cov_cart(i, i) * sqr(p2D(1, i)) + cov_cart(i + n, i + n) * sqr(p2D(0, i)) - 2. * cov_cart(i, i + n) * p2D(0, i) * p2D(1, i)); + } + } + return cov_rad; +} + +/*! + \brief Transform covariance matrix from Cartesian coordinates (only + transverse plane component) to coordinates system orthogonal to the + pre-fitted circle in each point. + Further information in attached documentation. + \param p2D 2D points in transverse plane. + \param cov_cart covariance matrix in Cartesian coordinates. + \param fast_fit fast_fit Vector4d result of the previous pre-fit + structured in this form:(X0, Y0, R, tan(theta))). + \return cov_rad covariance matrix in the pre-fitted circle's + orthogonal system. +*/ +template + __host__ __device__ inline VectorNd cov_carttorad_prefit(const M2xN& p2D, const Matrix2Nd& cov_cart, + V4& fast_fit, + const VectorNd& rad) +{ + u_int n = p2D.cols(); + VectorNd cov_rad; + for (u_int i = 0; i < n; ++i) + { + //!< in case you have (0,0) to avoid dividing by 0 radius + if (rad(i) < 1.e-4) + cov_rad(i) = cov_cart(i, i); // TO FIX + else + { + Vector2d a = p2D.col(i); + Vector2d b = p2D.col(i) - fast_fit.head(2); + const double x2 = a.dot(b); + const double y2 = cross2D(a, b); + const double tan_c = -y2 / x2; + const double tan_c2 = sqr(tan_c); + cov_rad(i) = 1. / (1. + tan_c2) * (cov_cart(i, i) + cov_cart(i + n, i + n) * tan_c2 + 2 * cov_cart(i, i + n) * tan_c); + } + } + return cov_rad; +} + +/*! + \brief Compute the points' weights' vector for the circle fit when multiple + scattering is managed. + Further information in attached documentation. + \param cov_rad_inv covariance matrix inverse in radial coordinated + (or, beter, pre-fitted circle's orthogonal system). + \return weight VectorNd points' weights' vector. + \bug I'm not sure this is the right way to compute the weights for non + diagonal cov matrix. Further investigation needed. +*/ + + template + __host__ __device__ inline VectorNd Weight_circle(const MatrixNd& cov_rad_inv) +{ + return cov_rad_inv.colwise().sum().transpose(); +} + +/*! + \brief Find particle q considering the sign of cross product between + particles velocity (estimated by the first 2 hits) and the vector radius + between the first hit and the center of the fitted circle. + \param p2D 2D points in transverse plane. + \param par_uvr result of the circle fit in this form: (X0,Y0,R). + \return q int 1 or -1. +*/ +template + __host__ __device__ inline int32_t Charge(const M2xN& p2D, const Vector3d& par_uvr) +{ + return ((p2D(0, 1) - p2D(0, 0)) * (par_uvr.y() - p2D(1, 0)) - (p2D(1, 1) - p2D(1, 0)) * (par_uvr.x() - p2D(0, 0)) > 0)? -1 : 1; +} + +/*! + \brief Transform circle parameter from (X0,Y0,R) to (phi,Tip,p_t) and + consequently covariance matrix. + \param circle_uvr parameter (X0,Y0,R), covariance matrix to + be transformed and particle charge. + \param B magnetic field in Gev/cm/c unit. + \param error flag for errors computation. +*/ + +__host__ __device__ inline void par_uvrtopak(circle_fit& circle, const double B, const bool error) +{ + Vector3d par_pak; + const double temp0 = circle.par.head(2).squaredNorm(); + const double temp1 = sqrt(temp0); + par_pak << atan2(circle.q * circle.par(0), -circle.q * circle.par(1)), + circle.q * (temp1 - circle.par(2)), circle.par(2) * B; + if (error) + { + const double temp2 = sqr(circle.par(0)) * 1. / temp0; + const double temp3 = 1. / temp1 * circle.q; + Matrix3d J4; + J4 << -circle.par(1) * temp2 * 1. / sqr(circle.par(0)), temp2 * 1. / circle.par(0), 0., + circle.par(0) * temp3, circle.par(1) * temp3, -circle.q, + 0., 0., B; + circle.cov = J4 * circle.cov * J4.transpose(); + } + circle.par = par_pak; +} + +/*! + \brief Compute the eigenvector associated to the minimum eigenvalue. + \param A the Matrix you want to know eigenvector and eigenvalue. + \param chi2 the double were the chi2-related quantity will be stored. + \return the eigenvector associated to the minimum eigenvalue. + \warning double precision is needed for a correct assessment of chi2. + \details The minimus eigenvalue is related to chi2. + We exploit the fact that the matrix is symmetrical and small (2x2 for line + fit and 3x3 for circle fit), so the SelfAdjointEigenSolver from Eigen + library is used, with the computedDirect method (available only for 2x2 + and 3x3 Matrix) wich computes eigendecomposition of given matrix using a + fast closed-form algorithm. + For this optimization the matrix type must be known at compiling time. +*/ + +__host__ __device__ inline Vector3d min_eigen3D(const Matrix3d& A, double& chi2) +{ +#ifdef RFIT_DEBUG + printf("min_eigen3D - enter\n"); +#endif + Eigen::SelfAdjointEigenSolver solver(3); + solver.computeDirect(A); + int min_index; + chi2 = solver.eigenvalues().minCoeff(&min_index); +#ifdef RFIT_DEBUG + printf("min_eigen3D - exit\n"); +#endif + return solver.eigenvectors().col(min_index); +} + +/*! + \brief A faster version of min_eigen3D() where double precision is not + needed. + \param A the Matrix you want to know eigenvector and eigenvalue. + \param chi2 the double were the chi2-related quantity will be stored + \return the eigenvector associated to the minimum eigenvalue. + \detail The computedDirect() method of SelfAdjointEigenSolver for 3x3 Matrix + indeed, use trigonometry function (it solves a third degree equation) which + speed up in single precision. +*/ + +__host__ __device__ inline Vector3d min_eigen3D_fast(const Matrix3d& A) +{ + Eigen::SelfAdjointEigenSolver solver(3); + solver.computeDirect(A.cast()); + int min_index; + solver.eigenvalues().minCoeff(&min_index); + return solver.eigenvectors().col(min_index).cast(); +} + +/*! + \brief 2D version of min_eigen3D(). + \param A the Matrix you want to know eigenvector and eigenvalue. + \param chi2 the double were the chi2-related quantity will be stored + \return the eigenvector associated to the minimum eigenvalue. + \detail The computedDirect() method of SelfAdjointEigenSolver for 2x2 Matrix + do not use special math function (just sqrt) therefore it doesn't speed up + significantly in single precision. +*/ + +__host__ __device__ inline Vector2d min_eigen2D(const Matrix2d& A, double& chi2) +{ + Eigen::SelfAdjointEigenSolver solver(2); + solver.computeDirect(A); + int min_index; + chi2 = solver.eigenvalues().minCoeff(&min_index); + return solver.eigenvectors().col(min_index); +} + +/*! + \brief A very fast helix fit: it fits a circle by three points (first, middle + and last point) and a line by two points (first and last). + \param hits points to be fitted + \return result in this form: (X0,Y0,R,tan(theta)). + \warning points must be passed ordered (from internal layer to external) in + order to maximize accuracy and do not mistake tan(theta) sign. + \details This fast fit is used as pre-fit which is needed for: + - weights estimation and chi2 computation in line fit (fundamental); + - weights estimation and chi2 computation in circle fit (useful); + - computation of error due to multiple scattering. +*/ + +template +__host__ __device__ inline void Fast_fit(const M3xN& hits, V4 & result) +{ + u_int n = hits.cols(); // get the number of hits + printIt(&hits, "Fast_fit - hits: "); + + // CIRCLE FIT + // Make segments between middle-to-first(b) and last-to-first(c) hits + const Vector2d b = hits.block(0, n / 2, 2, 1) - hits.block(0, 0, 2, 1); + const Vector2d c = hits.block(0, n - 1, 2, 1) - hits.block(0, 0, 2, 1); + printIt(&b, "Fast_fit - b: "); + printIt(&c, "Fast_fit - c: "); + // Compute their lengths + auto b2 = b.squaredNorm(); + auto c2 = c.squaredNorm(); + // The algebra has been verified (MR). The usual approach has been followed: + // * use an orthogonal reference frame passing from the first point. + // * build the segments (chords) + // * build orthogonal lines through mid points + // * make a system and solve for X0 and Y0. + // * add the initial point + bool flip = abs(b.x()) < abs(b.y()); + auto bx = flip ? b.y() : b.x(); + auto by = flip ? b.x() : b.y(); + auto cx = flip ? c.y() : c.x(); + auto cy = flip ? c.x() : c.y(); + //!< in case b.x is 0 (2 hits with same x) + auto div = 2. * (cx * by - bx*cy); + // if aligned TO FIX + auto Y0 = (cx*b2 - bx*c2) / div; + auto X0 = (0.5*b2 - Y0*by) / bx; + result(0) = hits(0, 0) + ( flip ? Y0 : X0); + result(1) = hits(1, 0) + ( flip ? X0 : Y0); + result(2) = sqrt(sqr(X0) + sqr(Y0)); + printIt(&result, "Fast_fit - result: "); + + // LINE FIT + const Vector2d d = hits.block(0, 0, 2, 1) - result.head(2); + const Vector2d e = hits.block(0, n - 1, 2, 1) - result.head(2); + printIt(&e, "Fast_fit - e: "); + printIt(&d, "Fast_fit - d: "); + // Compute the arc-length between first and last point: L = R * theta = R * atan (tan (Theta) ) + auto dr = result(2) * atan2(cross2D(d, e), d.dot(e)); + // Simple difference in Z between last and first hit + auto dz = hits(2, n - 1) - hits(2, 0); + + result(3) = (dr / dz); + +#ifdef RFIT_DEBUG + printf("Fast_fit: [%f, %f, %f, %f]\n", result(0), result(1), result(2), result(3)); +#endif +} + +/*! + \brief Fit a generic number of 2D points with a circle using Riemann-Chernov + algorithm. Covariance matrix of fitted parameter is optionally computed. + Multiple scattering (currently only in barrel layer) is optionally handled. + \param hits2D 2D points to be fitted. + \param hits_cov2D covariance matrix of 2D points. + \param fast_fit pre-fit result in this form: (X0,Y0,R,tan(theta)). + (tan(theta) is not used). + \param B magnetic field + \param error flag for error computation. + \param scattering flag for multiple scattering + \return circle circle_fit: + -par parameter of the fitted circle in this form (X0,Y0,R); \n + -cov covariance matrix of the fitted parameter (not initialized if + error = false); \n + -q charge of the particle; \n + -chi2. + \warning hits must be passed ordered from inner to outer layer (double hits + on the same layer must be ordered too) so that multiple scattering is + treated properly. + \warning Multiple scattering for barrel is still not tested. + \warning Multiple scattering for endcap hits is not handled (yet). Do not + fit endcap hits with scattering = true ! + \bug for small pt (<0.3 Gev/c) chi2 could be slightly underestimated. + \bug further investigation needed for error propagation with multiple + scattering. +*/ +template +__host__ __device__ inline circle_fit Circle_fit(const M2xN& hits2D, + const Matrix2Nd& hits_cov2D, + const V4& fast_fit, + const VectorNd& rad, + const double B, + const bool error) +{ +#ifdef RFIT_DEBUG + printf("circle_fit - enter\n"); +#endif + // INITIALIZATION + Matrix2Nd V = hits_cov2D; + u_int n = hits2D.cols(); + printIt(&hits2D, "circle_fit - hits2D:"); + printIt(&hits_cov2D, "circle_fit - hits_cov2D:"); + +#ifdef RFIT_DEBUG + printf("circle_fit - WEIGHT COMPUTATION\n"); +#endif + // WEIGHT COMPUTATION + VectorNd weight; + MatrixNd G; + double renorm; + { + MatrixNd cov_rad = cov_carttorad_prefit(hits2D, V, fast_fit, rad).asDiagonal(); + MatrixNd scatter_cov_rad = Scatter_cov_rad(hits2D, fast_fit, rad, B); + printIt(&scatter_cov_rad, "circle_fit - scatter_cov_rad:"); + printIt(&hits2D, "circle_fit - hits2D bis:"); +#ifdef RFIT_DEBUG + printf("Address of hits2D: a) %p\n", &hits2D); +#endif + V += cov_radtocart(hits2D, scatter_cov_rad, rad); + printIt(&V, "circle_fit - V:"); + cov_rad += scatter_cov_rad; + printIt(&cov_rad, "circle_fit - cov_rad:"); + G = cov_rad.inverse(); + renorm = G.sum(); + G *= 1. / renorm; + weight = Weight_circle(G); + } + printIt(&weight, "circle_fit - weight:"); + + // SPACE TRANSFORMATION +#ifdef RFIT_DEBUG + printf("circle_fit - SPACE TRANSFORMATION\n"); +#endif + + // center +#ifdef RFIT_DEBUG + printf("Address of hits2D: b) %p\n", &hits2D); +#endif + const Vector2d h_ = hits2D.rowwise().mean(); // centroid + printIt(&h_, "circle_fit - h_:"); + Matrix3xNd p3D; + p3D.block(0, 0, 2, n) = hits2D.colwise() - h_; + printIt(&p3D, "circle_fit - p3D: a)"); + Vector2Nd mc; // centered hits, used in error computation + mc << p3D.row(0).transpose(), p3D.row(1).transpose(); + printIt(&mc, "circle_fit - mc(centered hits):"); + + // scale + const double q = mc.squaredNorm(); + const double s = sqrt(n * 1. / q); // scaling factor + p3D *= s; + + // project on paraboloid + p3D.row(2) = p3D.block(0, 0, 2, n).colwise().squaredNorm(); + printIt(&p3D, "circle_fit - p3D: b)"); + +#ifdef RFIT_DEBUG + printf("circle_fit - COST FUNCTION\n"); +#endif + // COST FUNCTION + + // compute + Vector3d r0; r0.noalias() = p3D * weight; // center of gravity + const Matrix3xNd X = p3D.colwise() - r0; + Matrix3d A = X * G * X.transpose(); + printIt(&A, "circle_fit - A:"); + +#ifdef RFIT_DEBUG + printf("circle_fit - MINIMIZE\n"); +#endif + // minimize + double chi2; + Vector3d v = min_eigen3D(A, chi2); +#ifdef RFIT_DEBUG + printf("circle_fit - AFTER MIN_EIGEN\n"); +#endif + printIt(&v, "v BEFORE INVERSION"); + v *= (v(2) > 0) ? 1 : -1; // TO FIX dovrebbe essere N(3)>0 + printIt(&v, "v AFTER INVERSION"); + // This hack to be able to run on GPU where the automatic assignment to a + // double from the vector multiplication is not working. +#ifdef RFIT_DEBUG + printf("circle_fit - AFTER MIN_EIGEN 1\n"); +#endif + Eigen::Matrix cm; +#ifdef RFIT_DEBUG + printf("circle_fit - AFTER MIN_EIGEN 2\n"); +#endif + cm = -v.transpose() * r0; +#ifdef RFIT_DEBUG + printf("circle_fit - AFTER MIN_EIGEN 3\n"); +#endif + const double c = cm(0, 0); + // const double c = -v.transpose() * r0; + +#ifdef RFIT_DEBUG + printf("circle_fit - COMPUTE CIRCLE PARAMETER\n"); +#endif + // COMPUTE CIRCLE PARAMETER + + // auxiliary quantities + const double h = sqrt(1. - sqr(v(2)) - 4. * c * v(2)); + const double v2x2_inv = 1. / (2. * v(2)); + const double s_inv = 1. / s; + Vector3d par_uvr_; // used in error propagation + par_uvr_ << -v(0) * v2x2_inv, -v(1) * v2x2_inv, h * v2x2_inv; + + circle_fit circle; + circle.par << par_uvr_(0) * s_inv + h_(0), par_uvr_(1) * s_inv + h_(1), par_uvr_(2) * s_inv; + circle.q = Charge(hits2D, circle.par); + circle.chi2 = abs(chi2) * renorm * 1. / sqr(2 * v(2) * par_uvr_(2) * s); + printIt(&circle.par, "circle_fit - CIRCLE PARAMETERS:"); + printIt(&circle.cov, "circle_fit - CIRCLE COVARIANCE:"); +#ifdef RFIT_DEBUG + printf("circle_fit - CIRCLE CHARGE: %d\n", circle.q); +#endif + +#ifdef RFIT_DEBUG + printf("circle_fit - ERROR PROPAGATION\n"); +#endif + // ERROR PROPAGATION + if (error) + { +#ifdef RFIT_DEBUG + printf("circle_fit - ERROR PRPAGATION ACTIVATED\n"); +#endif + ArrayNd Vcs_[2][2]; // cov matrix of center & scaled points + MatrixNd C[3][3]; // cov matrix of 3D transformed points +#ifdef RFIT_DEBUG + printf("circle_fit - ERROR PRPAGATION ACTIVATED 2\n"); +#endif + { + Eigen::Matrix cm; + Eigen::Matrix cm2; + cm = mc.transpose() * V * mc; + const double c = cm(0, 0); + Matrix2Nd Vcs; Vcs. template triangularView() = (sqr(s) * V + + sqr(sqr(s)) * 1. / (4. * q * n) * + (2. * V.squaredNorm() + 4. * c) * // mc.transpose() * V * mc) * + (mc * mc.transpose())); + + printIt(&Vcs, "circle_fit - Vcs:"); + C[0][0] = Vcs.block(0, 0, n, n). template selfadjointView(); + Vcs_[0][1] = Vcs.block(0, n, n, n); + C[1][1] = Vcs.block(n, n, n, n). template selfadjointView(); + Vcs_[1][0] = Vcs_[0][1].transpose(); + printIt(&Vcs, "circle_fit - Vcs:"); + } + + { + const ArrayNd t0 = (VectorXd::Constant(n, 1.) * p3D.row(0)); + const ArrayNd t1 = (VectorXd::Constant(n, 1.) * p3D.row(1)); + const ArrayNd t00 = p3D.row(0).transpose() * p3D.row(0); + const ArrayNd t01 = p3D.row(0).transpose() * p3D.row(1); + const ArrayNd t11 = p3D.row(1).transpose() * p3D.row(1); + const ArrayNd t10 = t01.transpose(); + Vcs_[0][0] = C[0][0];; + C[0][1] = Vcs_[0][1]; + C[0][2] = 2. * (Vcs_[0][0] * t0 + Vcs_[0][1] * t1); + Vcs_[1][1] = C[1][1]; + C[1][2] = 2. * (Vcs_[1][0] * t0 + Vcs_[1][1] * t1); + MatrixNd tmp; + tmp. template triangularView() + = ( 2. * (Vcs_[0][0] * Vcs_[0][0] + Vcs_[0][0] * Vcs_[0][1] + Vcs_[1][1] * Vcs_[1][0] + + Vcs_[1][1] * Vcs_[1][1]) + + 4. * (Vcs_[0][0] * t00 + Vcs_[0][1] * t01 + Vcs_[1][0] * t10 + Vcs_[1][1] * t11) ).matrix(); + C[2][2] = tmp. template selfadjointView(); + } + printIt(&C[0][0], "circle_fit - C[0][0]:"); + + Matrix3d C0; // cov matrix of center of gravity (r0.x,r0.y,r0.z) + for (u_int i = 0; i < 3; ++i) + { + for (u_int j = i; j < 3; ++j) + { + Eigen::Matrix tmp; + tmp = weight.transpose() * C[i][j] * weight; + const double c = tmp(0, 0); + C0(i, j) = c; //weight.transpose() * C[i][j] * weight; + C0(j, i) = C0(i, j); + } + } + printIt(&C0, "circle_fit - C0:"); + + const MatrixNd W = weight * weight.transpose(); + const MatrixNd H = MatrixXd::Identity(n, n).rowwise() - weight.transpose(); + const MatrixNx3d s_v = H * p3D.transpose(); + printIt(&W, "circle_fit - W:"); + printIt(&H, "circle_fit - H:"); + printIt(&s_v, "circle_fit - s_v:"); + + MatrixNd D_[3][3]; // cov(s_v) + { + D_[0][0] = (H * C[0][0] * H.transpose()).cwiseProduct(W); + D_[0][1] = (H * C[0][1] * H.transpose()).cwiseProduct(W); + D_[0][2] = (H * C[0][2] * H.transpose()).cwiseProduct(W); + D_[1][1] = (H * C[1][1] * H.transpose()).cwiseProduct(W); + D_[1][2] = (H * C[1][2] * H.transpose()).cwiseProduct(W); + D_[2][2] = (H * C[2][2] * H.transpose()).cwiseProduct(W); + D_[1][0] = D_[0][1].transpose(); + D_[2][0] = D_[0][2].transpose(); + D_[2][1] = D_[1][2].transpose(); + } + printIt(&D_[0][0], "circle_fit - D_[0][0]:"); + + constexpr u_int nu[6][2] = {{0, 0}, {0, 1}, {0, 2}, {1, 1}, {1, 2}, {2, 2}}; + + Matrix6d E; // cov matrix of the 6 independent elements of A + #pragma unroll + for (u_int a = 0; a < 6; ++a) + { + const u_int i = nu[a][0], j = nu[a][1]; + #pragma unroll + for (u_int b = a; b < 6; ++b) + { + const u_int k = nu[b][0], l = nu[b][1]; + VectorNd t0(n); + VectorNd t1(n); + if (l == k) + { + t0 = 2. * D_[j][l] * s_v.col(l); + if (i == j) + t1 = t0; + else + t1 = 2. * D_[i][l] * s_v.col(l); + } + else + { + t0 = D_[j][l] * s_v.col(k) + D_[j][k] * s_v.col(l); + if (i == j) + t1 = t0; + else + t1 = D_[i][l] * s_v.col(k) + D_[i][k] * s_v.col(l); + } + + if (i == j) + { + Eigen::Matrix cm; + cm = s_v.col(i).transpose() * (t0 + t1); + const double c = cm(0, 0); + E(a, b) = 0. + c; + } + else + { + Eigen::Matrix cm; + cm = (s_v.col(i).transpose() * t0) + (s_v.col(j).transpose() * t1); + const double c = cm(0, 0); + E(a, b) = 0. + c; //(s_v.col(i).transpose() * t0) + (s_v.col(j).transpose() * t1); + } + if (b != a) + E(b, a) = E(a, b); + } + } + printIt(&E, "circle_fit - E:"); + + Eigen::Matrix J2; // Jacobian of min_eigen() (numerically computed) + #pragma unroll + for (u_int a = 0; a < 6; ++a) + { + const u_int i = nu[a][0], j = nu[a][1]; + Matrix3d Delta = Matrix3d::Zero(); + Delta(i, j) = Delta(j, i) = abs(A(i, j) * d); + J2.col(a) = min_eigen3D_fast(A + Delta); + const int sign = (J2.col(a)(2) > 0) ? 1 : -1; + J2.col(a) = (J2.col(a) * sign - v) / Delta(i, j); + } + printIt(&J2, "circle_fit - J2:"); + + Matrix4d Cvc; // joint cov matrix of (v0,v1,v2,c) + { + Matrix3d t0 = J2 * E * J2.transpose(); + Vector3d t1 = -t0 * r0; + Cvc.block(0, 0, 3, 3) = t0; + Cvc.block(0, 3, 3, 1) = t1; + Cvc.block(3, 0, 1, 3) = t1.transpose(); + Eigen::Matrix cm1; + Eigen::Matrix cm3; + cm1 = (v.transpose() * C0 * v); + // cm2 = (C0.cwiseProduct(t0)).sum(); + cm3 = (r0.transpose() * t0 * r0); + const double c = cm1(0, 0) + (C0.cwiseProduct(t0)).sum() + cm3(0, 0); + Cvc(3, 3) = c; + // (v.transpose() * C0 * v) + (C0.cwiseProduct(t0)).sum() + (r0.transpose() * t0 * r0); + } + printIt(&Cvc, "circle_fit - Cvc:"); + + Eigen::Matrix J3; // Jacobian (v0,v1,v2,c)->(X0,Y0,R) + { + const double t = 1. / h; + J3 << -v2x2_inv, 0, v(0) * sqr(v2x2_inv) * 2., 0, 0, -v2x2_inv, v(1) * sqr(v2x2_inv) * 2., 0, + v(0)*v2x2_inv*t, v(1)*v2x2_inv*t, -h * sqr(v2x2_inv) * 2. - (2. * c + v(2)) * v2x2_inv * t, -t; + } + printIt(&J3, "circle_fit - J3:"); + + const RowVector2Nd Jq = mc.transpose() * s * 1. / n; // var(q) + printIt(&Jq, "circle_fit - Jq:"); + + Matrix3d cov_uvr = J3 * Cvc * J3.transpose() * sqr(s_inv) // cov(X0,Y0,R) + + (par_uvr_ * par_uvr_.transpose()) * (Jq * V * Jq.transpose()); + + circle.cov = cov_uvr; + } + + printIt(&circle.cov, "Circle cov:"); +#ifdef RFIT_DEBUG + printf("circle_fit - exit\n"); +#endif + return circle; +} + + +/*! \brief Perform an ordinary least square fit in the s-z plane to compute + * the parameters cotTheta and Zip. + * + * The fit is performed in the rotated S3D-Z' plane, following the formalism of + * Frodesen, Chapter 10, p. 259. + * + * The system has been rotated to both try to use the combined errors in s-z + * along Z', as errors in the Y direction and to avoid the patological case of + * degenerate lines with angular coefficient m = +/- inf. + * + * The rotation is using the information on the theta angle computed in the + * fast fit. The rotation is such that the S3D axis will be the X-direction, + * while the rotated Z-axis will be the Y-direction. This pretty much follows + * what is done in the same fit in the Broken Line approach. + */ + + template +__host__ __device__ +inline line_fit Line_fit(const M3xN& hits, + const M6xN & hits_ge, + const circle_fit& circle, + const V4& fast_fit, + const double B, + const bool error) { + + constexpr uint32_t N = M3xN::ColsAtCompileTime; + auto n = hits.cols(); + double theta = -circle.q*atan(fast_fit(3)); + theta = theta < 0. ? theta + M_PI : theta; + + // Prepare the Rotation Matrix to rotate the points + Eigen::Matrix rot; + rot << sin(theta), cos(theta), -cos(theta), sin(theta); + + + // PROJECTION ON THE CILINDER + // + // p2D will be: + // [s1, s2, s3, ..., sn] + // [z1, z2, z3, ..., zn] + // s values will be ordinary x-values + // z values will be ordinary y-values + + Matrix2xNd p2D = Matrix2xNd::Zero(); + Eigen::Matrix Jx; + +#ifdef RFIT_DEBUG + printf("Line_fit - B: %g\n", B); + printIt(&hits, "Line_fit points: "); + printIt(&hits_ge, "Line_fit covs: "); + printIt(&rot, "Line_fit rot: "); +#endif + // x & associated Jacobian + // cfr https://indico.cern.ch/event/663159/contributions/2707659/attachments/1517175/2368189/Riemann_fit.pdf + // Slide 11 + // a ==> -o i.e. the origin of the circle in XY plane, negative + // b ==> p i.e. distances of the points wrt the origin of the circle. + const Vector2d o(circle.par(0), circle.par(1)); + + // associated Jacobian, used in weights and errors computation + Matrix6d Cov = Matrix6d::Zero(); + Matrix2d cov_sz[4]; // FIXME: should be "N" + for (u_int i = 0; i < n; ++i) + { + Vector2d p = hits.block(0, i, 2, 1) - o; + const double cross = cross2D(-o, p); + const double dot = (-o).dot(p); + // atan2(cross, dot) give back the angle in the transverse plane so tha the + // final equation reads: x_i = -q*R*theta (theta = angle returned by atan2) + const double atan2_ = -circle.q * atan2(cross, dot); +// p2D.coeffRef(1, i) = atan2_ * circle.par(2); + p2D(0, i) = atan2_ * circle.par(2); + + // associated Jacobian, used in weights and errors- computation + const double temp0 = -circle.q * circle.par(2) * 1. / (sqr(dot) + sqr(cross)); + double d_X0 = 0., d_Y0 = 0., d_R = 0.; // good approximation for big pt and eta + if (error) + { + d_X0 = -temp0 * ((p(1) + o(1)) * dot - (p(0) - o(0)) * cross); + d_Y0 = temp0 * ((p(0) + o(0)) * dot - (o(1) - p(1)) * cross); + d_R = atan2_; + } + const double d_x = temp0 * (o(1) * dot + o(0) * cross); + const double d_y = temp0 * (-o(0) * dot + o(1) * cross); + Jx << d_X0, d_Y0, d_R, d_x, d_y, 0., 0., 0., 0., 0., 0., 1.; + + + + Cov.block(0, 0, 3, 3) = circle.cov; + Cov(3, 3) = hits_ge.col(i)[0]; // x errors + Cov(4, 4) = hits_ge.col(i)[2]; // y errors + Cov(5, 5) = hits_ge.col(i)[5]; // z errors + Cov(3, 4) = Cov(4, 3) = hits_ge.col(i)[1]; // cov_xy + Cov(3, 5) = Cov(5, 3) = hits_ge.col(i)[3]; // cov_xz + Cov(4, 5) = Cov(5, 4) = hits_ge.col(i)[4]; // cov_yz + Matrix2d tmp = Jx * Cov * Jx.transpose(); + cov_sz[i].noalias() = rot*tmp*rot.transpose(); + } + // Math of d_{X0,Y0,R,x,y} all verified by hand + p2D.row(1) = hits.row(2); + + // The following matrix will contain errors orthogonal to the rotated S + // component only, with the Multiple Scattering properly treated!! + MatrixNd cov_with_ms; + Scatter_cov_line(cov_sz, fast_fit, p2D.row(0), p2D.row(1), theta, B,cov_with_ms); +#ifdef RFIT_DEBUG + printIt(cov_sz, "line_fit - cov_sz:"); + printIt(&cov_with_ms, "line_fit - cov_with_ms: "); +#endif + + // Rotate Points with the shape [2, n] + Matrix2xNd p2D_rot = rot*p2D; + +#ifdef RFIT_DEBUG + printf("Fast fit Tan(theta): %g\n", fast_fit(3)); + printf("Rotation angle: %g\n", theta); + printIt(&rot, "Rotation Matrix:"); + printIt(&p2D, "Original Hits(s,z):"); + printIt(&p2D_rot, "Rotated hits(S3D, Z'):"); + printIt(&rot, "Rotation Matrix:"); +#endif + + // Build the A Matrix + Matrix2xNd A; + A << MatrixXd::Ones(1, n), p2D_rot.row(0); // rotated s values + +#ifdef RFIT_DEBUG + printIt(&A, "A Matrix:"); +#endif + + // Build A^T V-1 A, where V-1 is the covariance of only the Y components. + MatrixNd Vy_inv = cov_with_ms.inverse(); + Eigen::Matrix Inv_Cov = A*Vy_inv*A.transpose(); + + // Compute the Covariance Matrix of the fit parameters + Eigen::Matrix Cov_params = Inv_Cov.inverse(); + + // Now Compute the Parameters in the form [2,1] + // The first component is q. + // The second component is m. + Eigen::Matrix sol = Cov_params*A*Vy_inv*p2D_rot.row(1).transpose(); + + +#ifdef RFIT_DEBUG + printIt(&sol, "Rotated solutions:"); +#endif + + // We need now to transfer back the results in the original s-z plane + auto common_factor = 1./(sin(theta)-sol(1,0)*cos(theta)); + Eigen::Matrix J; + J << 0., common_factor*common_factor, common_factor, sol(0,0)*cos(theta)*common_factor*common_factor; + + double m = common_factor*(sol(1,0)*sin(theta)+cos(theta)); + double q = common_factor*sol(0,0); + auto cov_mq = J * Cov_params * J.transpose(); + + VectorNd res = p2D_rot.row(1).transpose() - A.transpose() * sol; + double chi2 = res.transpose()*Vy_inv*res; + chi2 = chi2 / float(n); + + line_fit line; + line.par << m, q; + line.cov << cov_mq; + line.chi2 = chi2; + +#ifdef RFIT_DEBUG + printf("Common_factor: %g\n", common_factor); + printIt(&J, "Jacobian:"); + printIt(&sol, "Rotated solutions:"); + printIt(&Cov_params, "Cov_params:"); + printIt(&cov_mq, "Rotated Covariance Matrix:"); + printIt(&(line.par), "Real Parameters:"); + printIt(&(line.cov), "Real Covariance Matrix:"); + printf("Chi2: %g\n", chi2); +#endif + + return line; +} + +/*! + \brief Helix fit by three step: + -fast pre-fit (see Fast_fit() for further info); \n + -circle fit of hits projected in the transverse plane by Riemann-Chernov + algorithm (see Circle_fit() for further info); \n + -line fit of hits projected on cylinder surface by orthogonal distance + regression (see Line_fit for further info). \n + Points must be passed ordered (from inner to outer layer). + \param hits Matrix3xNd hits coordinates in this form: \n + |x0|x1|x2|...|xn| \n + |y0|y1|y2|...|yn| \n + |z0|z1|z2|...|zn| + \param hits_cov Matrix3Nd covariance matrix in this form (()->cov()): \n + |(x0,x0)|(x1,x0)|(x2,x0)|.|(y0,x0)|(y1,x0)|(y2,x0)|.|(z0,x0)|(z1,x0)|(z2,x0)| \n + |(x0,x1)|(x1,x1)|(x2,x1)|.|(y0,x1)|(y1,x1)|(y2,x1)|.|(z0,x1)|(z1,x1)|(z2,x1)| \n + |(x0,x2)|(x1,x2)|(x2,x2)|.|(y0,x2)|(y1,x2)|(y2,x2)|.|(z0,x2)|(z1,x2)|(z2,x2)| \n + . . . . . . . . . . . \n + |(x0,y0)|(x1,y0)|(x2,y0)|.|(y0,y0)|(y1,y0)|(y2,x0)|.|(z0,y0)|(z1,y0)|(z2,y0)| \n + |(x0,y1)|(x1,y1)|(x2,y1)|.|(y0,y1)|(y1,y1)|(y2,x1)|.|(z0,y1)|(z1,y1)|(z2,y1)| \n + |(x0,y2)|(x1,y2)|(x2,y2)|.|(y0,y2)|(y1,y2)|(y2,x2)|.|(z0,y2)|(z1,y2)|(z2,y2)| \n + . . . . . . . . . . . \n + |(x0,z0)|(x1,z0)|(x2,z0)|.|(y0,z0)|(y1,z0)|(y2,z0)|.|(z0,z0)|(z1,z0)|(z2,z0)| \n + |(x0,z1)|(x1,z1)|(x2,z1)|.|(y0,z1)|(y1,z1)|(y2,z1)|.|(z0,z1)|(z1,z1)|(z2,z1)| \n + |(x0,z2)|(x1,z2)|(x2,z2)|.|(y0,z2)|(y1,z2)|(y2,z2)|.|(z0,z2)|(z1,z2)|(z2,z2)| + \param B magnetic field in the center of the detector in Gev/cm/c + unit, in order to perform pt calculation. + \param error flag for error computation. + \param scattering flag for multiple scattering treatment. + (see Circle_fit() documentation for further info). + \warning see Circle_fit(), Line_fit() and Fast_fit() warnings. + \bug see Circle_fit(), Line_fit() and Fast_fit() bugs. +*/ + +template +inline helix_fit Helix_fit(const Matrix3xNd& hits, const Eigen::Matrix& hits_ge, const double B, + const bool error) +{ + u_int n = hits.cols(); + VectorNd<4> rad = (hits.block(0, 0, 2, n).colwise().norm()); + + // Fast_fit gives back (X0, Y0, R, theta) w/o errors, using only 3 points. + Vector4d fast_fit; + Fast_fit(hits,fast_fit); + Rfit::Matrix2Nd<4> hits_cov = MatrixXd::Zero(2 * n, 2 * n); + Rfit::loadCovariance2D(hits_ge,hits_cov); + circle_fit circle = Circle_fit(hits.block(0, 0, 2, n), + hits_cov, + fast_fit, rad, B, error); + line_fit line = Line_fit(hits, hits_ge, circle, fast_fit, B, error); + + par_uvrtopak(circle, B, error); + + helix_fit helix; + helix.par << circle.par, line.par; + if (error) + { + helix.cov = MatrixXd::Zero(5, 5); + helix.cov.block(0, 0, 3, 3) = circle.cov; + helix.cov.block(3, 3, 2, 2) = line.cov; + } + helix.q = circle.q; + helix.chi2_circle = circle.chi2; + helix.chi2_line = line.chi2; + + return helix; +} + +} // namespace Rfit + +#endif diff --git a/RecoPixelVertexing/PixelTrackFitting/interface/pixelTrackHeterogeneousProduct.h b/RecoPixelVertexing/PixelTrackFitting/interface/pixelTrackHeterogeneousProduct.h new file mode 100644 index 0000000000000..4a1763f312ac4 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/interface/pixelTrackHeterogeneousProduct.h @@ -0,0 +1,35 @@ +#ifndef RecoPixelVertexing_PixelTrackFitting_interface_pixelTrackHeterogeneousProduct_h +#define RecoPixelVertexing_PixelTrackFitting_interface_pixelTrackHeterogeneousProduct_h + +#include "RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h" + +namespace pixelTrackHeterogeneousProduct { + + static constexpr uint32_t maxTracks() { return 10000;} + + using CPUProduct = int; // dummy + + struct TracksOnGPU { + + Rfit::helix_fit * helix_fit_results_d; + + TracksOnGPU const * me_d = nullptr; + + }; + + struct TracksOnCPU { + + Rfit::helix_fit * helix_fit_results; + TracksOnGPU const * gpu_d = nullptr; + uint32_t nTracks; + }; + + using GPUProduct = TracksOnCPU; // FIXME fill cpu vectors on demand + + using HeterogeneousPixelTuples = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct >; +} + +} + +#endif diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/BuildFile.xml b/RecoPixelVertexing/PixelTrackFitting/plugins/BuildFile.xml index bdf7f548b7090..d8177a0e9447c 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/BuildFile.xml +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/BuildFile.xml @@ -1,4 +1,8 @@ - - - + + + + + + + diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelFitterByRiemannParaboloidProducer.cc b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelFitterByRiemannParaboloidProducer.cc new file mode 100644 index 0000000000000..1b471510774aa --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelFitterByRiemannParaboloidProducer.cc @@ -0,0 +1,53 @@ +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/global/EDProducer.h" + +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/ESHandle.h" + +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" + +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelFitter.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelFitterByRiemannParaboloid.h" + +#include "MagneticField/Engine/interface/MagneticField.h" +#include "MagneticField/Records/interface/IdealMagneticFieldRecord.h" + +class PixelFitterByRiemannParaboloidProducer: public edm::global::EDProducer<> { +public: + explicit PixelFitterByRiemannParaboloidProducer(const edm::ParameterSet& iConfig) + : useErrors_(iConfig.getParameter("useErrors")), + useMultipleScattering_(iConfig.getParameter("useMultipleScattering")) + { + produces(); + } + ~PixelFitterByRiemannParaboloidProducer() override {} + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("useErrors", true); + desc.add("useMultipleScattering", true); + descriptions.add("pixelFitterByRiemannParaboloidDefault", desc); + } + +private: + bool useErrors_; + bool useMultipleScattering_; + void produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const override; +}; + + +void PixelFitterByRiemannParaboloidProducer::produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const { + edm::ESHandle fieldESH; + iSetup.get().get(fieldESH); + + auto impl = std::make_unique(&iSetup, + fieldESH.product(), useErrors_, useMultipleScattering_); + auto prod = std::make_unique(std::move(impl)); + iEvent.put(std::move(prod)); +} + +DEFINE_FWK_MODULE(PixelFitterByRiemannParaboloidProducer); diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.cc b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.cc index ee11853729970..7f13c7218eafa 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.cc +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.cc @@ -1,3 +1,4 @@ +#include "storeTracks.h" #include "PixelTrackProducer.h" #include "FWCore/Framework/interface/Event.h" @@ -23,7 +24,9 @@ using namespace pixeltrackfitting; using edm::ParameterSet; PixelTrackProducer::PixelTrackProducer(const ParameterSet& cfg) - : theReconstruction(cfg, consumesCollector()) + : runOnGPU_(cfg.getParameter("runOnGPU")), + theReconstruction(cfg, consumesCollector()), + theGPUReconstruction(cfg, consumesCollector()) { edm::LogInfo("PixelTrackProducer")<<" construction..."; produces(); @@ -37,6 +40,7 @@ void PixelTrackProducer::fillDescriptions(edm::ConfigurationDescriptions& descri edm::ParameterSetDescription desc; desc.add("passLabel", "pixelTracks"); // What is this? It is not used anywhere in this code. + desc.add("runOnGPU", false); PixelTrackReconstruction::fillDescriptions(desc); descriptions.add("pixelTracks", desc); @@ -47,68 +51,15 @@ void PixelTrackProducer::produce(edm::Event& ev, const edm::EventSetup& es) LogDebug("PixelTrackProducer, produce")<<"event# :"< httopo; es.get().get(httopo); // store tracks - store(ev, tracks, *httopo); + storeTracks(ev, tracks, *httopo); } -void PixelTrackProducer::store(edm::Event& ev, const TracksWithTTRHs& tracksWithHits, const TrackerTopology& ttopo) -{ - auto tracks = std::make_unique(); - auto recHits = std::make_unique(); - auto trackExtras = std::make_unique(); - - int cc = 0, nTracks = tracksWithHits.size(); - - for (int i = 0; i < nTracks; i++) - { - reco::Track* track = tracksWithHits.at(i).first; - const SeedingHitSet& hits = tracksWithHits.at(i).second; - - for (unsigned int k = 0; k < hits.size(); k++) - { - TrackingRecHit *hit = hits[k]->hit()->clone(); - - track->appendHitPattern(*hit, ttopo); - recHits->push_back(hit); - } - tracks->push_back(*track); - delete track; - - } - - LogDebug("TrackProducer") << "put the collection of TrackingRecHit in the event" << "\n"; - edm::OrphanHandle ohRH = ev.put(std::move(recHits)); - - edm::RefProd hitCollProd(ohRH); - for (int k = 0; k < nTracks; k++) - { - reco::TrackExtra theTrackExtra{}; - - //fill the TrackExtra with TrackingRecHitRef - unsigned int nHits = tracks->at(k).numberOfValidHits(); - theTrackExtra.setHits(hitCollProd, cc, nHits); - cc +=nHits; - AlgebraicVector5 v = AlgebraicVector5(0,0,0,0,0); - reco::TrackExtra::TrajParams trajParams(nHits,LocalTrajectoryParameters(v,1.)); - reco::TrackExtra::Chi2sFive chi2s(nHits,0); - theTrackExtra.setTrajParams(std::move(trajParams),std::move(chi2s)); - trackExtras->push_back(theTrackExtra); - } - - LogDebug("TrackProducer") << "put the collection of TrackExtra in the event" << "\n"; - edm::OrphanHandle ohTE = ev.put(std::move(trackExtras)); - - for (int k = 0; k < nTracks; k++) - { - const reco::TrackExtraRef theTrackExtraRef(ohTE,k); - (tracks->at(k)).setExtra(theTrackExtraRef); - } - - ev.put(std::move(tracks)); - -} diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.h b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.h index c4bcec747d552..7e0d5d73b03fc 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.h +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.h @@ -1,10 +1,11 @@ -#ifndef PixelTrackProducer_H -#define PixelTrackProducer_H +#ifndef PixelTrackProducer_h +#define PixelTrackProducer_h #include "FWCore/Framework/interface/stream/EDProducer.h" -#include "RecoPixelVertexing/PixelTrackFitting/interface/TracksWithHits.h" #include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackReconstruction.h" +#include "PixelTrackReconstructionGPU.h" + namespace edm { class Event; class EventSetup; class ParameterSet; class ConfigurationDescriptions; } class TrackerTopology; @@ -20,7 +21,9 @@ class PixelTrackProducer : public edm::stream::EDProducer<> { void produce(edm::Event& ev, const edm::EventSetup& es) override; private: - void store(edm::Event& ev, const pixeltrackfitting::TracksWithTTRHs& selectedTracks, const TrackerTopology& ttopo); + bool runOnGPU_; PixelTrackReconstruction theReconstruction; + PixelTrackReconstructionGPU theGPUReconstruction; }; -#endif + +#endif // PixelTrackProducer_h diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc new file mode 100644 index 0000000000000..c05fa9172ffe3 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc @@ -0,0 +1,215 @@ +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" + +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "RecoTracker/TkHitPairs/interface/RegionsSeedingHitSets.h" + +#include "DataFormats/BeamSpot/interface/BeamSpot.h" + +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackBuilder.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackCleaner.h" + +// track stuff +#include "DataFormats/TrajectoryState/interface/LocalTrajectoryParameters.h" +#include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/TrackReco/interface/TrackFwd.h" +#include "DataFormats/TrackReco/interface/TrackExtra.h" +#include "DataFormats/Common/interface/OrphanHandle.h" + +#include "DataFormats/TrackerCommon/interface/TrackerTopology.h" +#include "Geometry/Records/interface/TrackerTopologyRcd.h" + +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" +#include "storeTracks.h" + + +/** + * This class will eventually be the one creating the reco::Track + * objects from the output of GPU CA. Now it is just to produce + * something persistable. + */ +class PixelTrackProducerFromCUDA: public HeterogeneousEDProducer> { + public: + + using Input = pixelTuplesHeterogeneousProduct::HeterogeneousPixelTuples; + using TuplesOnCPU = pixelTuplesHeterogeneousProduct::TuplesOnCPU; + + + using Output = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct >; + + explicit PixelTrackProducerFromCUDA(const edm::ParameterSet& iConfig); + ~PixelTrackProducerFromCUDA() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + + void beginStreamGPUCuda(edm::StreamID streamId, + cuda::stream_t<> &cudaStream) override { + } + void acquireGPUCuda(const edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceGPUCuda(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceCPU(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup) override; + + + private: + + TuplesOnCPU const * tuples_=nullptr; + + edm::EDGetTokenT tBeamSpot_; + edm::EDGetTokenT gpuToken_; + edm::EDGetTokenT srcToken_; + bool enableConversion_; +}; + +PixelTrackProducerFromCUDA::PixelTrackProducerFromCUDA(const edm::ParameterSet& iConfig): + HeterogeneousEDProducer(iConfig), + tBeamSpot_(consumes(iConfig.getParameter("beamSpot"))), + gpuToken_(consumes(iConfig.getParameter("src"))), + enableConversion_ (iConfig.getParameter("gpuEnableConversion")) +{ + if (enableConversion_) { + srcToken_ = consumes(iConfig.getParameter("src")); + produces(); + produces(); + produces(); + } + else { + produces(); // dummy + } +// produces(); +} + +void PixelTrackProducerFromCUDA::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("beamSpot", edm::InputTag("offlineBeamSpot")); + desc.add("src", edm::InputTag("pixelTracksHitQuadruplets")); + desc.add("gpuEnableConversion", true); + + + HeterogeneousEDProducer::fillPSetDescription(desc); + descriptions.addWithDefaultLabel(desc); +} + +void PixelTrackProducerFromCUDA::acquireGPUCuda(const edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) { + + edm::Handle gh; + iEvent.getByToken(gpuToken_, gh); + //auto const & gTuples = *gh; + // std::cout << "tuples from gpu " << gTuples.nTuples << std::endl; + + tuples_ = gh.product(); + +} + + +void PixelTrackProducerFromCUDA::produceGPUCuda(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) { + // iEvent.put(std::make_unique(0)); + if (!enableConversion_) return; + + // std::cout << "Converting gpu helix in reco tracks" << std::endl; + + edm::ESHandle fieldESH; + iSetup.get().get(fieldESH); + + + PixelTrackBuilder builder; + + pixeltrackfitting::TracksWithTTRHs tracks; + edm::ESHandle httopo; + iSetup.get().get(httopo); + + + edm::Handle hitSets; + iEvent.getByToken(srcToken_, hitSets); + const auto & hitSet = *hitSets->begin(); + auto b = hitSet.begin(); auto e = hitSet.end(); + // std::cout << "reading hitset " << e-b << std::endl; + + // const auto & region = hitSet.region(); + // std::cout << "origin " << region.origin() << std::endl; + + edm::Handle bsHandle; + iEvent.getByToken( tBeamSpot_, bsHandle); + const auto & bsh = *bsHandle; + // std::cout << "beamspot " << bsh.x0() << ' ' << bsh.y0() << ' ' << bsh.z0() << std::endl; + GlobalPoint bs(bsh.x0(),bsh.y0(),bsh.z0()); + + std::vector hits; + hits.reserve(4); + + uint32_t nh=0; // current hitset + assert(tuples_->indToEdm.size()==tuples_->nTuples); + for (uint32_t it=0; itnTuples; ++it) { + auto q = tuples_->quality[it]; + if (q != pixelTuplesHeterogeneousProduct::loose ) continue; // FIXME + assert(tuples_->indToEdm[it]==nh); // filled on CPU! + auto const & shits = *(b+nh); + auto nHits = shits.size(); hits.resize(nHits); + for (unsigned int iHit = 0; iHit < nHits; ++iHit) hits[iHit] = shits[iHit]; + + // mind: this values are respect the beamspot! + auto const &fittedTrack = tuples_->helix_fit_results[it]; + + // std::cout << "tk " << it << ": " << fittedTrack.q << ' ' << fittedTrack.par[2] << ' ' << std::sqrt(fittedTrack.cov(2, 2)) << std::endl; + + int iCharge = fittedTrack.q; + float valPhi = fittedTrack.par(0); + float valTip = fittedTrack.par(1); + float valPt = fittedTrack.par(2); + float valCotTheta = fittedTrack.par(3); + float valZip = fittedTrack.par(4); + + float errValPhi = std::sqrt(fittedTrack.cov(0, 0)); + float errValTip = std::sqrt(fittedTrack.cov(1, 1)); + float errValPt = std::sqrt(fittedTrack.cov(2, 2)); + float errValCotTheta = std::sqrt(fittedTrack.cov(3, 3)); + float errValZip = std::sqrt(fittedTrack.cov(4, 4)); + + float chi2 = fittedTrack.chi2_line + fittedTrack.chi2_circle; + + Measurement1D phi(valPhi, errValPhi); + Measurement1D tip(valTip, errValTip); + + Measurement1D pt(valPt, errValPt); + Measurement1D cotTheta(valCotTheta, errValCotTheta); + Measurement1D zip(valZip, errValZip); + + std::unique_ptr track( + builder.build(pt, phi, cotTheta, tip, zip, chi2, iCharge, hits, + fieldESH.product(), bs)); + if (!track) continue; + // filter??? + tracks.emplace_back(track.release(), shits); + ++nh; + } + assert(nh==e-b); + // std::cout << "processed " << nh << " good tuples " << tracks.size() << std::endl; + + // store tracks + storeTracks(iEvent, tracks, *httopo); +} + + +void PixelTrackProducerFromCUDA::produceCPU( + edm::HeterogeneousEvent &iEvent, const edm::EventSetup &iSetup) +{ + throw cms::Exception("NotImplemented") << "CPU version is no longer implemented"; +} + +DEFINE_FWK_MODULE(PixelTrackProducerFromCUDA); diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cc b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cc new file mode 100644 index 0000000000000..2a360b1da58a6 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cc @@ -0,0 +1,195 @@ +#include + +#include + +#include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/TrackReco/interface/TrackExtra.h" +#include "DataFormats/TrackReco/interface/TrackFwd.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelFitter.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackBuilder.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackCleaner.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackCleanerWrapper.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackFilter.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" // for helix_fit +#include "RecoTracker/TkHitPairs/interface/RegionsSeedingHitSets.h" +#include "RecoTracker/TkMSParametrization/interface/PixelRecoUtilities.h" +#include "RecoTracker/TkTrackingRegions/interface/TrackingRegion.h" + +#include "PixelTrackReconstructionGPU.h" + +using namespace pixeltrackfitting; +using edm::ParameterSet; + +PixelTrackReconstructionGPU::PixelTrackReconstructionGPU(const ParameterSet& cfg, + edm::ConsumesCollector && iC) + : theHitSetsToken(iC.consumes(cfg.getParameter("SeedingHitSets"))), + theFitterToken(iC.consumes(cfg.getParameter("Fitter"))), + theCleanerName(cfg.getParameter("Cleaner")) +{ + edm::InputTag filterTag = cfg.getParameter("Filter"); + if(!filterTag.label().empty()) { + theFilterToken = iC.consumes(filterTag); + } +} + +PixelTrackReconstructionGPU::~PixelTrackReconstructionGPU() +{ +} + +void PixelTrackReconstructionGPU::fillDescriptions(edm::ParameterSetDescription& desc) { + desc.add("SeedingHitSets", edm::InputTag("pixelTracksHitTriplets")); + desc.add("Fitter", edm::InputTag("pixelFitterByHelixProjections")); + desc.add("Filter", edm::InputTag("pixelTrackFilterByKinematics")); + desc.add("Cleaner", "pixelTrackCleanerBySharedHits"); +} + +void PixelTrackReconstructionGPU::run(TracksWithTTRHs& tracks, + edm::Event& ev, const edm::EventSetup& es) +{ + edm::ESHandle fieldESH; + es.get().get(fieldESH); + + edm::Handle hhitSets; + ev.getByToken(theHitSetsToken, hhitSets); + const auto& hitSets = *hhitSets; + + const PixelTrackFilter *filter = nullptr; + if(!theFilterToken.isUninitialized()) { + edm::Handle hfilter; + ev.getByToken(theFilterToken, hfilter); + filter = hfilter.product(); + } + + float bField = 1 / PixelRecoUtilities::fieldInInvGev(es); + + std::vector hits_and_covariances; + float * hits_and_covariancesGPU = nullptr; + Rfit::helix_fit * helix_fit_results = nullptr; + Rfit::helix_fit * helix_fit_resultsGPU = nullptr; + + const int points_in_seed = 4; + // We use 3 floats for GlobalPosition and 6 floats for GlobalError (that's what is used by the Riemann fit). + // Assume a safe maximum of 3K seeds: it will dynamically grow, if needed. + int total_seeds = 0; + hits_and_covariances.reserve(sizeof(float)*3000*(points_in_seed*12)); + for (auto const & regionHitSets : hitSets) { + const TrackingRegion& region = regionHitSets.region(); + for (auto const & tuplet : regionHitSets) { + for (unsigned int iHit = 0; iHit < tuplet.size(); ++iHit) { + auto const& recHit = tuplet[iHit]; + auto point = GlobalPoint(recHit->globalPosition().basicVector() - region.origin().basicVector()); + auto errors = recHit->globalPositionError(); + hits_and_covariances.push_back(point.x()); + hits_and_covariances.push_back(point.y()); + hits_and_covariances.push_back(point.z()); + hits_and_covariances.push_back(errors.cxx()); + hits_and_covariances.push_back(errors.cyx()); + hits_and_covariances.push_back(errors.cyy()); + hits_and_covariances.push_back(errors.czx()); + hits_and_covariances.push_back(errors.czy()); + hits_and_covariances.push_back(errors.czz()); + } + total_seeds++; + } + } + + // We pretend to have one fit for every seed + cudaCheck(cudaMallocHost(&helix_fit_results, sizeof(Rfit::helix_fit)*total_seeds)); + cudaCheck(cudaMalloc(&hits_and_covariancesGPU, sizeof(float)*hits_and_covariances.size())); + cudaCheck(cudaMalloc(&helix_fit_resultsGPU, sizeof(Rfit::helix_fit)*total_seeds)); + cudaCheck(cudaMemset(helix_fit_resultsGPU, 0x00, sizeof(Rfit::helix_fit)*total_seeds )); + // CUDA MALLOC OF HITS AND COV AND HELIX_FIT RESULTS + + // CUDA MEMCOPY HOST2DEVICE OF HITS AND COVS AND HELIX_FIT RESULTS + cudaCheck(cudaMemcpy(hits_and_covariancesGPU, hits_and_covariances.data(), + sizeof(float)*hits_and_covariances.size(), cudaMemcpyDefault)); + + // LAUNCH THE KERNEL FIT + launchKernelFit(hits_and_covariancesGPU, 12*4*total_seeds, 4, + bField, helix_fit_resultsGPU); + // CUDA MEMCOPY DEVICE2HOST OF HELIX_FIT + cudaCheck(cudaDeviceSynchronize()); + cudaCheck(cudaGetLastError()); + cudaCheck(cudaMemcpy(helix_fit_results, helix_fit_resultsGPU, + sizeof(Rfit::helix_fit)*total_seeds, cudaMemcpyDefault)); + + cudaCheck(cudaFree(hits_and_covariancesGPU)); + cudaCheck(cudaFree(helix_fit_resultsGPU)); + // Loop on results, create tracks, filter them and pass them down the chain. + // In order to avoid huge mess with indexing and remembering who did what, + // simply iterate again over the main containers in the same order, since we + // are guaranteed that the results have been packed following the very same + // order. If not, we are doomed. + PixelTrackBuilder builder; + int counter = 0; + std::vector hits; + hits.reserve(4); + for (auto const & regionHitSets : hitSets) { + const TrackingRegion& region = regionHitSets.region(); + for(const SeedingHitSet& tuplet: regionHitSets) { + auto nHits = tuplet.size(); hits.resize(nHits); + + for (unsigned int iHit = 0; iHit < nHits; ++iHit) + { + hits[iHit] = tuplet[iHit]; + } + auto const &fittedTrack = helix_fit_results[counter]; + counter++; + int iCharge = fittedTrack.q; + float valPhi = fittedTrack.par(0); + float valTip = fittedTrack.par(1); + float valPt = fittedTrack.par(2); + float valCotTheta = fittedTrack.par(3); + float valZip = fittedTrack.par(4); + + // PixelTrackErrorParam param(valEta, valPt); + float errValPhi = std::sqrt(fittedTrack.cov(0, 0)); + float errValTip = std::sqrt(fittedTrack.cov(1, 1)); + float errValPt = std::sqrt(fittedTrack.cov(2, 2)); + float errValCotTheta = std::sqrt(fittedTrack.cov(3, 3)); + float errValZip = std::sqrt(fittedTrack.cov(4, 4)); + + float chi2 = fittedTrack.chi2_line; + + Measurement1D phi(valPhi, errValPhi); + Measurement1D tip(valTip, errValTip); + + Measurement1D pt(valPt, errValPt); + Measurement1D cotTheta(valCotTheta, errValCotTheta); + Measurement1D zip(valZip, errValZip); + + std::unique_ptr track( + builder.build(pt, phi, cotTheta, tip, zip, chi2, iCharge, hits, + fieldESH.product(), region.origin())); + if (!track) continue; + + if (filter) { + if (!(*filter)(track.get(), hits)) { + continue; + } + } + // add tracks + tracks.emplace_back(track.release(), tuplet); + } + } + + cudaCheck(cudaFreeHost(helix_fit_results)); + + // skip overlapped tracks + if(!theCleanerName.empty()) { + edm::ESHandle hcleaner; + es.get().get(theCleanerName, hcleaner); + const auto& cleaner = *hcleaner; + if(cleaner.fast()) + cleaner.cleanTracks(tracks); + else + tracks = PixelTrackCleanerWrapper(&cleaner).clean(tracks); + } +} diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu new file mode 100644 index 0000000000000..19a91ba8c83b4 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu @@ -0,0 +1,211 @@ +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "PixelTrackReconstructionGPU.h" + +using namespace Eigen; + +__global__ void +KernelFastFitAllHits(float *hits_and_covariances, + int hits_in_fit, + int cumulative_size, + float B, + Rfit::helix_fit *results, + Rfit::Matrix3xNd<4> *hits, + Eigen::Matrix *hits_ge, + Rfit::circle_fit *circle_fit, + Vector4d *fast_fit, + Rfit::line_fit *line_fit) +{ + // Reshape Eigen components from hits_and_covariances, using proper thread and block indices + // Perform the fit + // Store the results in the proper vector, using again correct indices + + // Loop for hits_in_fit times: + // first 3 are the points + // the rest is the covariance matrix, 3x3 + int start = (blockIdx.x * blockDim.x + threadIdx.x) * hits_in_fit * 12; + int helix_start = (blockIdx.x * blockDim.x + threadIdx.x); + if (start >= cumulative_size) { + return; + } + +#ifdef GPU_DEBUG + printf("BlockDim.x: %d, BlockIdx.x: %d, threadIdx.x: %d, start: %d, cumulative_size: %d\n", + blockDim.x, blockIdx.x, threadIdx.x, start, cumulative_size); +#endif + + + // Prepare data structure (stack) + for (unsigned int i = 0; i < hits_in_fit; ++i) { + hits[helix_start].col(i) << hits_and_covariances[start], + hits_and_covariances[start + 1], hits_and_covariances[start + 2]; + start += 3; + + hits_ge[helix_start].col(i) << hits_and_covariances[start], + hits_and_covariances[start + 1], hits_and_covariances[start + 2], + hits_and_covariances[start + 3], hits_and_covariances[start + 4], + hits_and_covariances[start + 5]; + start += 6; + } + + Rfit::Fast_fit(hits[helix_start],fast_fit[helix_start]); +} + +__global__ void +KernelCircleFitAllHits(float *hits_and_covariances, int hits_in_fit, + int cumulative_size, float B, Rfit::helix_fit *results, + Rfit::Matrix3xNd<4> *hits, Eigen::Matrix *hits_ge, + Rfit::circle_fit *circle_fit, Vector4d *fast_fit, + Rfit::line_fit *line_fit) +{ + // Reshape Eigen components from hits_and_covariances, using proper thread and block indices + // Perform the fit + // Store the results in the proper vector, using again correct indices + + // Loop for hits_in_fit times: + // first 3 are the points + // the rest is the covariance matrix, 3x3 + int start = (blockIdx.x * blockDim.x + threadIdx.x) * hits_in_fit * 12; + int helix_start = (blockIdx.x * blockDim.x + threadIdx.x); + if (start >= cumulative_size) { + return; + } + +#ifdef GPU_DEBUG + printf("BlockDim.x: %d, BlockIdx.x: %d, threadIdx.x: %d, start: %d, " + "cumulative_size: %d\n", + blockDim.x, blockIdx.x, threadIdx.x, start, cumulative_size); +#endif + u_int n = hits[helix_start].cols(); + + constexpr uint32_t N = 4; + + Rfit::VectorNd rad = (hits[helix_start].block(0, 0, 2, n).colwise().norm()); + Rfit::Matrix2Nd hits_cov = MatrixXd::Zero(2 * n, 2 * n); + Rfit::loadCovariance2D(hits_ge[helix_start],hits_cov); + circle_fit[helix_start] = + Rfit::Circle_fit(hits[helix_start].block(0, 0, 2, n), + hits_cov, + fast_fit[helix_start], rad, B, true); + +#ifdef GPU_DEBUG + printf("KernelCircleFitAllHits circle.par(0): %d %f\n", helix_start, + circle_fit[helix_start].par(0)); + printf("KernelCircleFitAllHits circle.par(1): %d %f\n", helix_start, + circle_fit[helix_start].par(1)); + printf("KernelCircleFitAllHits circle.par(2): %d %f\n", helix_start, + circle_fit[helix_start].par(2)); +#endif + +} + +__global__ void +KernelLineFitAllHits(float *hits_and_covariances, int hits_in_fit, + int cumulative_size, float B, Rfit::helix_fit *results, + Rfit::Matrix3xNd<4> *hits, Eigen::Matrix *hits_ge, + Rfit::circle_fit *circle_fit, Vector4d *fast_fit, + Rfit::line_fit *line_fit) +{ + // Reshape Eigen components from hits_and_covariances, using proper thread and block indices + // Perform the fit + // Store the results in the proper vector, using again correct indices + + // Loop for hits_in_fit times: + // first 3 are the points + // the rest is the covariance matrix, 3x3 + int start = (blockIdx.x * blockDim.x + threadIdx.x) * hits_in_fit * 12; + int helix_start = (blockIdx.x * blockDim.x + threadIdx.x); + if (start >= cumulative_size) { + return; + } + +#ifdef GPU_DEBUG + + printf("BlockDim.x: %d, BlockIdx.x: %d, threadIdx.x: %d, start: %d, " + "cumulative_size: %d\n", + blockDim.x, blockIdx.x, threadIdx.x, start, cumulative_size); +#endif + + line_fit[helix_start] = + Rfit::Line_fit(hits[helix_start], hits_ge[helix_start], + circle_fit[helix_start], fast_fit[helix_start], B, true); + + par_uvrtopak(circle_fit[helix_start], B, true); + + // Grab helix_fit from the proper location in the output vector + Rfit::helix_fit &helix = results[helix_start]; + helix.par << circle_fit[helix_start].par, line_fit[helix_start].par; + + // TODO: pass properly error booleans + + helix.cov = MatrixXd::Zero(5, 5); + helix.cov.block(0, 0, 3, 3) = circle_fit[helix_start].cov; + helix.cov.block(3, 3, 2, 2) = line_fit[helix_start].cov; + + helix.q = circle_fit[helix_start].q; + helix.chi2_circle = circle_fit[helix_start].chi2; + helix.chi2_line = line_fit[helix_start].chi2; + +#ifdef GPU_DEBUG + + printf("KernelLineFitAllHits line.par(0): %d %f\n", helix_start, + circle_fit[helix_start].par(0)); + printf("KernelLineFitAllHits line.par(1): %d %f\n", helix_start, + line_fit[helix_start].par(1)); +#endif +} + +void PixelTrackReconstructionGPU::launchKernelFit( + float *hits_and_covariancesGPU, int cumulative_size, int hits_in_fit, + float B, Rfit::helix_fit *results) +{ + const dim3 threads_per_block(32, 1); + int num_blocks = cumulative_size / (hits_in_fit * 12) / threads_per_block.x + 1; + auto numberOfSeeds = cumulative_size / (hits_in_fit * 12); + + Rfit::Matrix3xNd<4> *hitsGPU; + cudaCheck(cudaMalloc(&hitsGPU, 48 * numberOfSeeds * sizeof(Rfit::Matrix3xNd<4>))); + cudaCheck(cudaMemset(hitsGPU, 0x00, 48 * numberOfSeeds * sizeof(Rfit::Matrix3xNd<4>))); + + Eigen::Matrix *hits_geGPU = nullptr; + cudaCheck(cudaMalloc(&hits_geGPU, 48 * numberOfSeeds * sizeof(Eigen::Matrix))); + cudaCheck(cudaMemset(hits_geGPU, 0x00, 48 * numberOfSeeds * sizeof(Eigen::Matrix))); + + Vector4d *fast_fit_resultsGPU = nullptr; + cudaCheck(cudaMalloc(&fast_fit_resultsGPU, 48 * numberOfSeeds * sizeof(Vector4d))); + cudaCheck(cudaMemset(fast_fit_resultsGPU, 0x00, 48 * numberOfSeeds * sizeof(Vector4d))); + + Rfit::circle_fit *circle_fit_resultsGPU = nullptr; + cudaCheck(cudaMalloc(&circle_fit_resultsGPU, 48 * numberOfSeeds * sizeof(Rfit::circle_fit))); + cudaCheck(cudaMemset(circle_fit_resultsGPU, 0x00, 48 * numberOfSeeds * sizeof(Rfit::circle_fit))); + + Rfit::line_fit *line_fit_resultsGPU = nullptr; + cudaCheck(cudaMalloc(&line_fit_resultsGPU, numberOfSeeds * sizeof(Rfit::line_fit))); + cudaCheck(cudaMemset(line_fit_resultsGPU, 0x00, numberOfSeeds * sizeof(Rfit::line_fit))); + + KernelFastFitAllHits<<>>( + hits_and_covariancesGPU, hits_in_fit, cumulative_size, B, results, + hitsGPU, hits_geGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, + line_fit_resultsGPU); + cudaCheck(cudaGetLastError()); + + KernelCircleFitAllHits<<>>( + hits_and_covariancesGPU, hits_in_fit, cumulative_size, B, results, + hitsGPU, hits_geGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, + line_fit_resultsGPU); + cudaCheck(cudaGetLastError()); + + KernelLineFitAllHits<<>>( + hits_and_covariancesGPU, hits_in_fit, cumulative_size, B, results, + hitsGPU, hits_geGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, + line_fit_resultsGPU); + cudaCheck(cudaGetLastError()); + + cudaFree(hitsGPU); + cudaFree(hits_geGPU); + cudaFree(fast_fit_resultsGPU); + cudaFree(circle_fit_resultsGPU); + cudaFree(line_fit_resultsGPU); +} diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.h b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.h new file mode 100644 index 0000000000000..7488ba43b6f93 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.h @@ -0,0 +1,43 @@ +#ifndef RecoPixelVertexing_PixelTrackFitting_plugins_PixelTrackReconstructionGPU_h +#define RecoPixelVertexing_PixelTrackFitting_plugins_PixelTrackReconstructionGPU_h + +#include + +#include "FWCore/Framework/interface/ConsumesCollector.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/Utilities/interface/EDGetToken.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/TracksWithHits.h" + +class PixelFitter; +class PixelTrackCleaner; +class PixelTrackFilter; +class RegionsSeedingHitSets; + +namespace edm { + class Event; + class EventSetup; + class Run; + class ParameterSetDescription; +} + +class PixelTrackReconstructionGPU { +public: + + PixelTrackReconstructionGPU( const edm::ParameterSet& conf, edm::ConsumesCollector && iC); + ~PixelTrackReconstructionGPU(); + + static void fillDescriptions(edm::ParameterSetDescription& desc); + + void run(pixeltrackfitting::TracksWithTTRHs& tah, edm::Event& ev, const edm::EventSetup& es); + void launchKernelFit(float * hits_and_covariances, int cumulative_size, int hits_in_fit, float B, + Rfit::helix_fit * results); + +private: + edm::EDGetTokenT theHitSetsToken; + edm::EDGetTokenT theFitterToken; + edm::EDGetTokenT theFilterToken; + std::string theCleanerName; +}; + +#endif // RecoPixelVertexing_PixelTrackFitting_plugins_PixelTrackReconstructionGPU_h diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/storeTracks.h b/RecoPixelVertexing/PixelTrackFitting/plugins/storeTracks.h new file mode 100644 index 0000000000000..48abab5237587 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/storeTracks.h @@ -0,0 +1,77 @@ +#ifndef RecoPixelVertexingPixelTrackFittingStoreTracks_H +#define RecoPixelVertexingPixelTrackFittingStoreTracks_H + +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" + +#include "DataFormats/TrajectoryState/interface/LocalTrajectoryParameters.h" +#include "DataFormats/TrackReco/interface/Track.h" +#include "DataFormats/TrackReco/interface/TrackFwd.h" +#include "DataFormats/TrackReco/interface/TrackExtra.h" +#include "DataFormats/Common/interface/OrphanHandle.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/TracksWithHits.h" + +#include "DataFormats/TrackerCommon/interface/TrackerTopology.h" +#include "Geometry/Records/interface/TrackerTopologyRcd.h" + +template +void storeTracks(Ev & ev, const pixeltrackfitting::TracksWithTTRHs& tracksWithHits, const TrackerTopology& ttopo) +{ + auto tracks = std::make_unique(); + auto recHits = std::make_unique(); + auto trackExtras = std::make_unique(); + + int cc = 0, nTracks = tracksWithHits.size(); + + for (int i = 0; i < nTracks; i++) + { + reco::Track* track = tracksWithHits.at(i).first; + const SeedingHitSet& hits = tracksWithHits.at(i).second; + + for (unsigned int k = 0; k < hits.size(); k++) + { + TrackingRecHit *hit = hits[k]->hit()->clone(); + + track->appendHitPattern(*hit, ttopo); + recHits->push_back(hit); + } + tracks->push_back(*track); + delete track; + + } + + LogDebug("TrackProducer") << "put the collection of TrackingRecHit in the event" << "\n"; + edm::OrphanHandle ohRH = ev.put(std::move(recHits)); + + edm::RefProd hitCollProd(ohRH); + for (int k = 0; k < nTracks; k++) + { + reco::TrackExtra theTrackExtra{}; + + //fill the TrackExtra with TrackingRecHitRef + unsigned int nHits = tracks->at(k).numberOfValidHits(); + theTrackExtra.setHits(hitCollProd, cc, nHits); + cc +=nHits; + AlgebraicVector5 v = AlgebraicVector5(0,0,0,0,0); + reco::TrackExtra::TrajParams trajParams(nHits,LocalTrajectoryParameters(v,1.)); + reco::TrackExtra::Chi2sFive chi2s(nHits,0); + theTrackExtra.setTrajParams(std::move(trajParams),std::move(chi2s)); + trackExtras->push_back(theTrackExtra); + } + + LogDebug("TrackProducer") << "put the collection of TrackExtra in the event" << "\n"; + edm::OrphanHandle ohTE = ev.put(std::move(trackExtras)); + + for (int k = 0; k < nTracks; k++) + { + const reco::TrackExtraRef theTrackExtraRef(ohTE,k); + (tracks->at(k)).setExtra(theTrackExtraRef); + } + + ev.put(std::move(tracks)); + +} + +#endif diff --git a/RecoPixelVertexing/PixelTrackFitting/python/PixelTracks_cff.py b/RecoPixelVertexing/PixelTrackFitting/python/PixelTracks_cff.py index f352af085a0e7..e868ff1921965 100644 --- a/RecoPixelVertexing/PixelTrackFitting/python/PixelTracks_cff.py +++ b/RecoPixelVertexing/PixelTrackFitting/python/PixelTracks_cff.py @@ -12,6 +12,7 @@ from RecoTracker.TkSeedingLayers.PixelLayerTriplets_cfi import * from RecoTracker.TkSeedingLayers.TTRHBuilderWithoutAngle4PixelTriplets_cfi import * from RecoPixelVertexing.PixelTrackFitting.pixelFitterByHelixProjections_cfi import pixelFitterByHelixProjections +from RecoPixelVertexing.PixelTrackFitting.pixelFitterByRiemannParaboloid_cfi import pixelFitterByRiemannParaboloid from RecoPixelVertexing.PixelTrackFitting.pixelTrackFilterByKinematics_cfi import pixelTrackFilterByKinematics from RecoPixelVertexing.PixelTrackFitting.pixelTrackCleanerBySharedHits_cfi import pixelTrackCleanerBySharedHits from RecoPixelVertexing.PixelTrackFitting.pixelTracks_cfi import pixelTracks as _pixelTracks @@ -50,6 +51,11 @@ SeedComparitorPSet = dict(clusterShapeCacheSrc = 'siPixelClusterShapeCachePreSplitting') ) +from Configuration.ProcessModifiers.gpu_cff import gpu +from RecoPixelVertexing.PixelTriplets.caHitQuadrupletHeterogeneousEDProducer_cfi import caHitQuadrupletHeterogeneousEDProducer as _caHitQuadrupletHeterogeneousEDProducer +gpu.toReplaceWith(pixelTracksHitQuadruplets, _caHitQuadrupletHeterogeneousEDProducer) +gpu.toModify(pixelTracksHitQuadruplets, trackingRegions = "pixelTracksTrackingRegions") + # for trackingLowPU pixelTracksHitTriplets = _pixelTripletHLTEDProducer.clone( doublets = "pixelTracksHitDoublets", @@ -64,6 +70,10 @@ ) trackingLowPU.toModify(pixelTracks, SeedingHitSets = "pixelTracksHitTriplets") +from Configuration.ProcessModifiers.gpu_cff import gpu +from RecoPixelVertexing.PixelTrackFitting.pixelTrackProducerFromCUDA_cfi import pixelTrackProducerFromCUDA as _pixelTrackProducerFromCUDA +gpu.toReplaceWith(pixelTracks, _pixelTrackProducerFromCUDA) + pixelTracksTask = cms.Task( pixelTracksTrackingRegions, pixelFitterByHelixProjections, @@ -77,4 +87,13 @@ _pixelTracksTask_lowPU.replace(pixelTracksHitQuadruplets, pixelTracksHitTriplets) trackingLowPU.toReplaceWith(pixelTracksTask, _pixelTracksTask_lowPU) +# Use Riemann fit and substitute previous Fitter producer with the Riemann one +from Configuration.ProcessModifiers.riemannFit_cff import riemannFit +from Configuration.ProcessModifiers.riemannFitGPU_cff import riemannFitGPU +riemannFit.toModify(pixelTracks, Fitter = "pixelFitterByRiemannParaboloid") +riemannFitGPU.toModify(pixelTracks, runOnGPU = True) +_pixelTracksTask_riemannFit = pixelTracksTask.copy() +_pixelTracksTask_riemannFit.replace(pixelFitterByHelixProjections, pixelFitterByRiemannParaboloid) +riemannFit.toReplaceWith(pixelTracksTask, _pixelTracksTask_riemannFit) + pixelTracksSequence = cms.Sequence(pixelTracksTask) diff --git a/RecoPixelVertexing/PixelTrackFitting/python/pixelFitterByRiemannParaboloid_cfi.py b/RecoPixelVertexing/PixelTrackFitting/python/pixelFitterByRiemannParaboloid_cfi.py new file mode 100644 index 0000000000000..0e0ab13b01bd6 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/python/pixelFitterByRiemannParaboloid_cfi.py @@ -0,0 +1,6 @@ +import FWCore.ParameterSet.Config as cms + +from RecoPixelVertexing.PixelTrackFitting.pixelFitterByRiemannParaboloidDefault_cfi import pixelFitterByRiemannParaboloidDefault + +pixelFitterByRiemannParaboloid = pixelFitterByRiemannParaboloidDefault.clone() + diff --git a/RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc b/RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc new file mode 100644 index 0000000000000..45ee6266bcc31 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc @@ -0,0 +1,115 @@ +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelFitterByRiemannParaboloid.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" + +#include "FWCore/ParameterSet/interface/ParameterSet.h" + +#include "FWCore/Framework/interface/EventSetup.h" + +#include "DataFormats/GeometryCommonDetAlgo/interface/GlobalError.h" +#include "DataFormats/GeometryVector/interface/GlobalPoint.h" +#include "DataFormats/GeometryVector/interface/LocalPoint.h" + +#include "DataFormats/TrackingRecHit/interface/TrackingRecHit.h" +#include "Geometry/CommonDetUnit/interface/GeomDet.h" +#include "RecoTracker/TkMSParametrization/interface/PixelRecoUtilities.h" + +#include "DataFormats/GeometryCommonDetAlgo/interface/Measurement1D.h" + +#include "Geometry/CommonDetUnit/interface/GeomDetType.h" + +#include "MagneticField/Engine/interface/MagneticField.h" + +#include "FWCore/MessageLogger/interface/MessageLogger.h" + +#include "DataFormats/GeometryVector/interface/Pi.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackBuilder.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/PixelTrackErrorParam.h" + +#include "CommonTools/Utils/interface/DynArray.h" + +using namespace std; + + +PixelFitterByRiemannParaboloid::PixelFitterByRiemannParaboloid(const edm::EventSetup* es, + const MagneticField* field, + bool useErrors, + bool useMultipleScattering) + : es_(es), field_(field), + useErrors_(useErrors), useMultipleScattering_(useMultipleScattering) {} + +std::unique_ptr PixelFitterByRiemannParaboloid::run( + const std::vector& hits, const TrackingRegion& region) const { + + using namespace Rfit; + + std::unique_ptr ret; + + unsigned int nhits = hits.size(); + + if (nhits < 2) return ret; + + declareDynArray(GlobalPoint, nhits, points); + declareDynArray(GlobalError, nhits, errors); + declareDynArray(bool, nhits, isBarrel); + + for (unsigned int i = 0; i != nhits; ++i) { + auto const& recHit = hits[i]; + points[i] = GlobalPoint(recHit->globalPosition().basicVector() - region.origin().basicVector()); + errors[i] = recHit->globalPositionError(); + isBarrel[i] = recHit->detUnit()->type().isBarrel(); + } + + assert(nhits==4); + Rfit::Matrix3xNd<4> riemannHits; + + Eigen::Matrix riemannHits_ge = Eigen::Matrix::Zero(); + + for (unsigned int i = 0; i < nhits; ++i) { + riemannHits.col(i) << points[i].x(), points[i].y(), points[i].z(); + + riemannHits_ge.col(i) << errors[i].cxx(), errors[i].cyx(), errors[i].cyy(), + errors[i].czx(), errors[i].czy(), errors[i].czz(); + } + + float bField = 1 / PixelRecoUtilities::fieldInInvGev(*es_); + helix_fit fittedTrack = Rfit::Helix_fit(riemannHits, riemannHits_ge, bField, useErrors_); + int iCharge = fittedTrack.q; + + // parameters are: + // 0: phi + // 1: tip + // 2: curvature + // 3: cottheta + // 4: zip + float valPhi = fittedTrack.par(0); + + float valTip = fittedTrack.par(1); + + float valCotTheta = fittedTrack.par(3); + + float valZip = fittedTrack.par(4); + float valPt = fittedTrack.par(2); + // + // PixelTrackErrorParam param(valEta, valPt); + float errValPhi = std::sqrt(fittedTrack.cov(0, 0)); + float errValTip = std::sqrt(fittedTrack.cov(1, 1)); + + float errValPt = std::sqrt(fittedTrack.cov(2, 2)); + + float errValCotTheta = std::sqrt(fittedTrack.cov(3, 3)); + float errValZip = std::sqrt(fittedTrack.cov(4, 4)); + + float chi2 = fittedTrack.chi2_line + fittedTrack.chi2_circle; + + PixelTrackBuilder builder; + Measurement1D phi(valPhi, errValPhi); + Measurement1D tip(valTip, errValTip); + + Measurement1D pt(valPt, errValPt); + Measurement1D cotTheta(valCotTheta, errValCotTheta); + Measurement1D zip(valZip, errValZip); + + ret.reset( + builder.build(pt, phi, cotTheta, tip, zip, chi2, iCharge, hits, field_, region.origin())); + return ret; +} diff --git a/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml b/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml index a80e3015549e3..b4b5e3a335bcb 100644 --- a/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml +++ b/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml @@ -1,3 +1,4 @@ + @@ -11,6 +12,48 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc b/RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc new file mode 100644 index 0000000000000..adcabd7dde508 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc @@ -0,0 +1,435 @@ +#define _USE_MATH_DEFINES + +#include +#include +#include +#include +#include // unique_ptr +#include + +#include +#include + +#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" +//#include "RecoPixelVertexing/PixelTrackFitting/interface/BrokenLine.h" + +using namespace std; +using namespace Eigen; +using namespace Rfit; +using std::unique_ptr; + +namespace Rfit { +using Vector3i = Eigen::Matrix; +using Vector4i = Eigen::Matrix; +using Vector6d = Eigen::Matrix; +using Vector8d = Eigen::Matrix; +}; // namespace Rfit + +// quadruplets... +struct hits_gen { + Matrix3xNd<4> hits; + Eigen::Matrix hits_ge; + Vector5d true_par; +}; + +struct geometry { + Vector8d barrel; + Vector4i barrel_2; + Vector8d R_err; + Vector8d Rp_err; + Vector8d z_err; + Vector6d hand; + Vector3i hand_2; + Vector6d xy_err; + Vector6d zh_err; + double z_max; + double r_max; +}; + +void test_helix_fit(); + +constexpr int c_speed = 299792458; +constexpr double pi = M_PI; +default_random_engine generator(1); + +void smearing(const Vector5d& err, const bool& isbarrel, double& x, double& y, double& z) { + normal_distribution dist_R(0., err[0]); + normal_distribution dist_Rp(0., err[1]); + normal_distribution dist_z(0., err[2]); + normal_distribution dist_xyh(0., err[3]); + normal_distribution dist_zh(0., err[4]); + if (isbarrel) { + double dev_Rp = dist_Rp(generator); + double dev_R = dist_R(generator); + double R = sqrt(Rfit::sqr(x) + Rfit::sqr(y)); + x += dev_Rp * +y / R + dev_R * -x / R; + y += dev_Rp * -x / R + dev_R * -y / R; + z += dist_z(generator); + } else { + x += dist_xyh(generator); + y += dist_xyh(generator); + z += dist_zh(generator); + } +} + +template +void Hits_cov(Eigen::Matrix & V, const unsigned int& i, const unsigned int& n, const Matrix3xNd& hits, + const Vector5d& err, bool isbarrel) { + if (isbarrel) { + double R2 = Rfit::sqr(hits(0, i)) + Rfit::sqr(hits(1, i)); + V.col(i)[0] = + (Rfit::sqr(err[1]) * Rfit::sqr(hits(1, i)) + Rfit::sqr(err[0]) * Rfit::sqr(hits(0, i))) / + R2; + V.col(i)[2] = + (Rfit::sqr(err[1]) * Rfit::sqr(hits(0, i)) + Rfit::sqr(err[0]) * Rfit::sqr(hits(1, i))) / + R2; + V.col(i)[1] = + (Rfit::sqr(err[0]) - Rfit::sqr(err[1])) * hits(1, i) * hits(0, i) / R2; + V.col(i)[5] = Rfit::sqr(err[2]); + } else { + V.col(i)[0] = Rfit::sqr(err[3]); + V.col(i)[2] = Rfit::sqr(err[3]); + V.col(i)[5] = Rfit::sqr(err[4]); + } +} + +hits_gen Hits_gen(const unsigned int& n, const Matrix& gen_par) { + hits_gen gen; + gen.hits = MatrixXd::Zero(3, n); + gen.hits_ge = Eigen::Matrix::Zero(); + // err /= 10000.; + constexpr double rad[8] = {2.95, 6.8, 10.9, 16., 3.1, 7., 11., 16.2}; + // constexpr double R_err[8] = {5./10000, 5./10000, 5./10000, 5./10000, 5./10000, + // 5./10000, 5./10000, 5./10000}; constexpr double Rp_err[8] = {35./10000, 18./10000, + // 15./10000, 34./10000, 35./10000, 18./10000, 15./10000, 34./10000}; constexpr double z_err[8] = + // {72./10000, 38./10000, 25./10000, 56./10000, 72./10000, 38./10000, 25./10000, 56./10000}; + constexpr double R_err[8] = {10. / 10000, 10. / 10000, 10. / 10000, 10. / 10000, + 10. / 10000, 10. / 10000, 10. / 10000, 10. / 10000}; + constexpr double Rp_err[8] = {35. / 10000, 18. / 10000, 15. / 10000, 34. / 10000, + 35. / 10000, 18. / 10000, 15. / 10000, 34. / 10000}; + constexpr double z_err[8] = {72. / 10000, 38. / 10000, 25. / 10000, 56. / 10000, + 72. / 10000, 38. / 10000, 25. / 10000, 56. / 10000}; + const double x2 = gen_par(0) + gen_par(4) * cos(gen_par(3) * pi / 180); + const double y2 = gen_par(1) + gen_par(4) * sin(gen_par(3) * pi / 180); + const double alpha = atan2(y2, x2); + + for (unsigned int i = 0; i < n; ++i) { + const double a = gen_par(4); + const double b = rad[i]; + const double c = sqrt(Rfit::sqr(x2) + Rfit::sqr(y2)); + const double beta = acos((Rfit::sqr(a) - Rfit::sqr(b) - Rfit::sqr(c)) / (-2. * b * c)); + const double gamma = alpha + beta; + gen.hits(0, i) = rad[i] * cos(gamma); + gen.hits(1, i) = rad[i] * sin(gamma); + gen.hits(2, i) = gen_par(2) + 1 / tan(gen_par(5) * pi / 180) * 2. * + asin(sqrt(Rfit::sqr((gen_par(0) - gen.hits(0, i))) + + Rfit::sqr((gen_par(1) - gen.hits(1, i)))) / + (2. * gen_par(4))) * + gen_par(4); + // isbarrel(i) = ?? + Vector5d err; + err << R_err[i], Rp_err[i], z_err[i], 0, 0; + smearing(err, true, gen.hits(0, i), gen.hits(1, i), gen.hits(2, i)); + Hits_cov(gen.hits_ge, i, n, gen.hits, err, true); + } + + return gen; +} + +Vector5d True_par(const Matrix& gen_par, const int& charge, const double& B_field) { + Vector5d true_par; + const double x0 = gen_par(0) + gen_par(4) * cos(gen_par(3) * pi / 180); + const double y0 = gen_par(1) + gen_par(4) * sin(gen_par(3) * pi / 180); + circle_fit circle; + circle.par << x0, y0, gen_par(4); + circle.q = 1; + Rfit::par_uvrtopak(circle, B_field, false); + true_par.block(0, 0, 3, 1) = circle.par; + true_par(3) = 1 / tan(gen_par(5) * pi / 180); + const int dir = ((gen_par(0) - cos(true_par(0) - pi / 2) * true_par(1)) * (gen_par(1) - y0) - + (gen_par(1) - sin(true_par(0) - pi / 2) * true_par(1)) * (gen_par(0) - x0) > + 0) + ? -1 + : 1; + true_par(4) = gen_par(2) + + 1 / tan(gen_par(5) * pi / 180) * dir * 2.f * + asin(sqrt(Rfit::sqr((gen_par(0) - cos(true_par(0) - pi / 2) * true_par(1))) + + Rfit::sqr((gen_par(1) - sin(true_par(0) - pi / 2) * true_par(1)))) / + (2.f * gen_par(4))) * + gen_par(4); + return true_par; +} + +Matrix New_par(const Matrix& gen_par, const int& charge, + const double& B_field) { + Matrix new_par; + new_par.block(0, 0, 3, 1) = gen_par.block(0, 0, 3, 1); + new_par(3) = gen_par(3) - charge * 90; + new_par(4) = gen_par(4) / B_field; +// new_par(5) = atan(sinh(gen_par(5))) * 180 / pi; + new_par(5) = 2.*atan(exp(-gen_par(5))) * 180 / pi; + return new_par; +} + +template +void computePull(std::array & fit, const char * label, + int n_, int iteration, const Vector5d & true_par) { + Eigen::Matrix score(41, iteration); + + std::string histo_name("Phi Pull"); + histo_name += label; + TH1F phi_pull(histo_name.data(), histo_name.data(), 100, -10., 10.); + histo_name = "dxy Pull "; + histo_name += label; + TH1F dxy_pull(histo_name.data(), histo_name.data(), 100, -10., 10.); + histo_name = "dz Pull "; + histo_name += label; + TH1F dz_pull(histo_name.data(), histo_name.data(), 100, -10., 10.); + histo_name = "Theta Pull "; + histo_name += label; + TH1F theta_pull(histo_name.data(), histo_name.data(), 100, -10., 10.); + histo_name = "Pt Pull "; + histo_name += label; + TH1F pt_pull(histo_name.data(), histo_name.data(), 100, -10., 10.); + histo_name = "Phi Error "; + histo_name += label; + TH1F phi_error(histo_name.data(), histo_name.data(), 100, 0., 0.1); + histo_name = "dxy error "; + histo_name += label; + TH1F dxy_error(histo_name.data(), histo_name.data(), 100, 0., 0.1); + histo_name = "dz error "; + histo_name += label; + TH1F dz_error(histo_name.data(), histo_name.data(), 100, 0., 0.1); + histo_name = "Theta error "; + histo_name += label; + TH1F theta_error(histo_name.data(), histo_name.data(), 100, 0., 0.1); + histo_name = "Pt error "; + histo_name += label; + TH1F pt_error(histo_name.data(), histo_name.data(), 100, 0., 0.1); + for (int x = 0; x < iteration; x++) { + // Compute PULLS information + score(0, x) = (fit[x].par(0) - true_par(0)) / sqrt(fit[x].cov(0, 0)); + score(1, x) = (fit[x].par(1) - true_par(1)) / sqrt(fit[x].cov(1, 1)); + score(2, x) = (fit[x].par(2) - true_par(2)) / sqrt(fit[x].cov(2, 2)); + score(3, x) = (fit[x].par(3) - true_par(3)) / sqrt(fit[x].cov(3, 3)); + score(4, x) = (fit[x].par(4) - true_par(4)) / sqrt(fit[x].cov(4, 4)); + phi_pull.Fill(score(0, x)); + dxy_pull.Fill(score(1, x)); + pt_pull.Fill(score(2, x)); + theta_pull.Fill(score(3, x)); + dz_pull.Fill(score(4, x)); + phi_error.Fill(sqrt(fit[x].cov(0, 0))); + dxy_error.Fill(sqrt(fit[x].cov(1, 1))); + pt_error.Fill(sqrt(fit[x].cov(2, 2))); + theta_error.Fill(sqrt(fit[x].cov(3, 3))); + dz_error.Fill(sqrt(fit[x].cov(4, 4))); + score(5, x) = + (fit[x].par(0) - true_par(0)) * (fit[x].par(1) - true_par(1)) / (fit[x].cov(0, 1)); + score(6, x) = + (fit[x].par(0) - true_par(0)) * (fit[x].par(2) - true_par(2)) / (fit[x].cov(0, 2)); + score(7, x) = + (fit[x].par(1) - true_par(1)) * (fit[x].par(2) - true_par(2)) / (fit[x].cov(1, 2)); + score(8, x) = + (fit[x].par(3) - true_par(3)) * (fit[x].par(4) - true_par(4)) / (fit[x].cov(3, 4)); + score(9, x) = fit[x].chi2_circle; + score(25, x) = fit[x].chi2_line; + score(10, x) = sqrt(fit[x].cov(0, 0)) / fit[x].par(0) * 100; + score(13, x) = sqrt(fit[x].cov(3, 3)) / fit[x].par(3) * 100; + score(14, x) = sqrt(fit[x].cov(4, 4)) / fit[x].par(4) * 100; + score(15, x) = (fit[x].par(0) - true_par(0)) * (fit[x].par(3) - true_par(3)) / + sqrt(fit[x].cov(0, 0)) / sqrt(fit[x].cov(3, 3)); + score(16, x) = (fit[x].par(1) - true_par(1)) * (fit[x].par(3) - true_par(3)) / + sqrt(fit[x].cov(1, 1)) / sqrt(fit[x].cov(3, 3)); + score(17, x) = (fit[x].par(2) - true_par(2)) * (fit[x].par(3) - true_par(3)) / + sqrt(fit[x].cov(2, 2)) / sqrt(fit[x].cov(3, 3)); + score(18, x) = (fit[x].par(0) - true_par(0)) * (fit[x].par(4) - true_par(4)) / + sqrt(fit[x].cov(0, 0)) / sqrt(fit[x].cov(4, 4)); + score(19, x) = (fit[x].par(1) - true_par(1)) * (fit[x].par(4) - true_par(4)) / + sqrt(fit[x].cov(1, 1)) / sqrt(fit[x].cov(4, 4)); + score(20, x) = (fit[x].par(2) - true_par(2)) * (fit[x].par(4) - true_par(4)) / + sqrt(fit[x].cov(2, 2)) / sqrt(fit[x].cov(4, 4)); + score(21, x) = (fit[x].par(0) - true_par(0)) * (fit[x].par(1) - true_par(1)) / + sqrt(fit[x].cov(0, 0)) / sqrt(fit[x].cov(1, 1)); + score(22, x) = (fit[x].par(0) - true_par(0)) * (fit[x].par(2) - true_par(2)) / + sqrt(fit[x].cov(0, 0)) / sqrt(fit[x].cov(2, 2)); + score(23, x) = (fit[x].par(1) - true_par(1)) * (fit[x].par(2) - true_par(2)) / + sqrt(fit[x].cov(1, 1)) / sqrt(fit[x].cov(2, 2)); + score(24, x) = (fit[x].par(3) - true_par(3)) * (fit[x].par(4) - true_par(4)) / + sqrt(fit[x].cov(3, 3)) / sqrt(fit[x].cov(4, 4)); + score(30, x) = fit[x].par(0); + score(31, x) = fit[x].par(1); + score(32, x) = fit[x].par(2); + score(33, x) = fit[x].par(3); + score(34, x) = fit[x].par(4); + score(35, x) = sqrt(fit[x].cov(0,0)); + score(36, x) = sqrt(fit[x].cov(1,1)); + score(37, x) = sqrt(fit[x].cov(2,2)); + score(38, x) = sqrt(fit[x].cov(3,3)); + score(39, x) = sqrt(fit[x].cov(4,4)); + + } + + double phi_ = score.row(0).mean(); + double a_ = score.row(1).mean(); + double pt_ = score.row(2).mean(); + double coT_ = score.row(3).mean(); + double Zip_ = score.row(4).mean(); + std::cout << std::setprecision(5) << std::scientific << label << " AVERAGE FITTED VALUES: \n" + << "phi: " << score.row(30).mean() << " +/- " << score.row(35).mean() << " [+/-] " << sqrt(score.row(35).array().abs2().mean() - score.row(35).mean()*score.row(35).mean()) << std::endl + << "d0: " << score.row(31).mean() << " +/- " << score.row(36).mean() << " [+/-] " << sqrt(score.row(36).array().abs2().mean() - score.row(36).mean()*score.row(36).mean()) << std::endl + << "pt: " << score.row(32).mean() << " +/- " << score.row(37).mean() << " [+/-] " << sqrt(score.row(37).array().abs2().mean() - score.row(37).mean()*score.row(37).mean()) << std::endl + << "coT: " << score.row(33).mean() << " +/- " << score.row(38).mean() << " [+/-] " << sqrt(score.row(38).array().abs2().mean() - score.row(38).mean()*score.row(38).mean()) << std::endl + << "Zip: " << score.row(34).mean() << " +/- " << score.row(39).mean() << " [+/-] " << sqrt(score.row(39).array().abs2().mean() - score.row(39).mean()*score.row(39).mean()) << std::endl; + + Matrix5d correlation; + correlation << 1., score.row(21).mean(), score.row(22).mean(), score.row(15).mean(), + score.row(20).mean(), score.row(21).mean(), 1., score.row(23).mean(), score.row(16).mean(), + score.row(19).mean(), score.row(22).mean(), score.row(23).mean(), 1., score.row(17).mean(), + score.row(20).mean(), score.row(15).mean(), score.row(16).mean(), score.row(17).mean(), 1., + score.row(24).mean(), score.row(18).mean(), score.row(19).mean(), score.row(20).mean(), + score.row(24).mean(), 1.; + + cout << "\n" << label << " PULLS (mean, sigma, relative_error):\n" + << "phi: " << phi_ << " " + << sqrt((score.row(0).array() - phi_).square().sum() / (iteration - 1)) << " " + << abs(score.row(10).mean()) << "%\n" + << "a0 : " << a_ << " " + << sqrt((score.row(1).array() - a_).square().sum() / (iteration - 1)) << " " + << abs(score.row(11).mean()) << "%\n" + << "pt : " << pt_ << " " + << sqrt((score.row(2).array() - pt_).square().sum() / (iteration - 1)) << " " + << abs(score.row(12).mean()) << "%\n" + << "coT: " << coT_ << " " + << sqrt((score.row(3).array() - coT_).square().sum() / (iteration - 1)) << " " + << abs(score.row(13).mean()) << "%\n" + << "Zip: " << Zip_ << " " + << sqrt((score.row(4).array() - Zip_).square().sum() / (iteration - 1)) << " " + << abs(score.row(14).mean()) << "%\n\n" + << "cov(phi,a0)_: " << score.row(5).mean() << "\n" + << "cov(phi,pt)_: " << score.row(6).mean() << "\n" + << "cov(a0,pt)_: " << score.row(7).mean() << "\n" + << "cov(coT,Zip)_: " << score.row(8).mean() << "\n\n" + << "chi2_circle: " << score.row(9).mean() << " vs " << n_ - 3 << "\n" + << "chi2_line: " << score.row(25).mean() << " vs " << n_ - 2 << "\n\n" + << "correlation matrix:\n" + << correlation << "\n\n" + << endl; + + phi_pull.Fit("gaus", "Q"); + dxy_pull.Fit("gaus", "Q"); + dz_pull.Fit("gaus", "Q"); + theta_pull.Fit("gaus", "Q"); + pt_pull.Fit("gaus", "Q"); + phi_pull.Write(); + dxy_pull.Write(); + dz_pull.Write(); + theta_pull.Write(); + pt_pull.Write(); + phi_error.Write(); + dxy_error.Write(); + dz_error.Write(); + theta_error.Write(); + pt_error.Write(); +} + + +void test_helix_fit(bool getcin) { + int n_; + bool return_err; + const double B_field = 3.8 * c_speed / pow(10, 9) / 100; + Matrix gen_par; + Vector5d true_par; + Vector5d err; + generator.seed(1); + std::cout << std::setprecision(6); + cout << "_________________________________________________________________________\n"; + cout << "n x(cm) y(cm) z(cm) phi(grad) R(Gev/c) eta iteration return_err debug" << endl; + if (getcin) { + cout << "hits: "; + cin >> n_; + cout << "x: "; + cin >> gen_par(0); + cout << "y: "; + cin >> gen_par(1); + cout << "z: "; + cin >> gen_par(2); + cout << "phi: "; + cin >> gen_par(3); + cout << "p_t: "; + cin >> gen_par(4); + cout << "eta: "; + cin >> gen_par(5); + } else { + n_ = 4; + gen_par(0) = -0.1; // x + gen_par(1) = 0.1; // y + gen_par(2) = -1.; // z + gen_par(3) = 45.; // phi + gen_par(4) = 10.; // R (p_t) + gen_par(5) = 1.; // eta + } + return_err = true; + + const int iteration = 5000; + gen_par = New_par(gen_par, 1, B_field); + true_par = True_par(gen_par, 1, B_field); + // Matrix3xNd<4> hits; + std::array helixRiemann_fit; +// std::array helixBrokenLine_fit; + + std::cout << "\nTrue parameters: " + << "phi: " << true_par(0) << " " + << "dxy: " << true_par(1) << " " + << "pt: " << true_par(2) << " " + << "CotT: " << true_par(3) << " " + << "Zip: " << true_par(4) << " " + << std::endl; + auto start = std::chrono::high_resolution_clock::now(); + auto delta = start-start; + for (int i = 0; i < 100*iteration; i++) { + hits_gen gen; + gen = Hits_gen(n_, gen_par); + // gen.hits = MatrixXd::Zero(3, 4); + // gen.hits_cov = MatrixXd::Zero(3 * 4, 3 * 4); + // gen.hits.col(0) << 1.82917642593, 2.0411875248, 7.18495464325; + // gen.hits.col(1) << 4.47041416168, 4.82704305649, 18.6394691467; + // gen.hits.col(2) << 7.25991010666, 7.74653434753, 30.6931324005; + // gen.hits.col(3) << 8.99161434174, 9.54262828827, 38.1338043213; + delta -= std::chrono::high_resolution_clock::now()-start; + helixRiemann_fit[i%iteration] = Rfit::Helix_fit(gen.hits, gen.hits_ge, B_field, return_err); + delta += std::chrono::high_resolution_clock::now()-start; + +// helixBrokenLine_fit[i] = BrokenLine::Helix_fit(gen.hits, gen.hits_cov, B_field); + + if (helixRiemann_fit[i%iteration].par(0)>10.) std::cout << "error" << std::endl; + if (0==i) + cout << std::setprecision(6) + << "phi: " << helixRiemann_fit[i].par(0) << " +/- " << sqrt(helixRiemann_fit[i].cov(0, 0)) << " vs " + << true_par(0) << endl + << "Tip: " << helixRiemann_fit[i].par(1) << " +/- " << sqrt(helixRiemann_fit[i].cov(1, 1)) << " vs " + << true_par(1) << endl + << "p_t: " << helixRiemann_fit[i].par(2) << " +/- " << sqrt(helixRiemann_fit[i].cov(2, 2)) << " vs " + << true_par(2) << endl + << "theta:" << helixRiemann_fit[i].par(3) << " +/- " << sqrt(helixRiemann_fit[i].cov(3, 3)) << " vs " + << true_par(3) << endl + << "Zip: " << helixRiemann_fit[i].par(4) << " +/- " << sqrt(helixRiemann_fit[i].cov(4, 4)) << " vs " + << true_par(4) << endl + << "charge:" << helixRiemann_fit[i].q << " vs 1" << endl + << "covariance matrix:" << endl + << helixRiemann_fit[i].cov << endl + << "Initial hits:\n" << gen.hits << endl + << "Initial Covariance:\n" << gen.hits_ge << endl; + + } + std::cout << "elapsted time " << double(std::chrono::duration_cast(delta).count())/1.e6 << std::endl; + computePull(helixRiemann_fit, "Riemann", n_, iteration, true_par); +// computePull(helixBrokenLine_fit, "BrokenLine", n_, iteration, true_par); +} + +int main(int nargs, char**) { + TFile f("TestFitResults.root", "RECREATE"); + test_helix_fit(nargs>1); + f.Close(); + return 0; +} + diff --git a/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPU.cu b/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPU.cu new file mode 100644 index 0000000000000..a60eeda935d79 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPU.cu @@ -0,0 +1,224 @@ +#include + +#include +#include + + +#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" + +#include "test_common.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +using namespace Eigen; + +namespace Rfit { + constexpr uint32_t maxNumberOfTracks() { return 5*1024; } + constexpr uint32_t stride() { return maxNumberOfTracks();} + using Matrix3x4d = Eigen::Matrix; + using Map3x4d = Eigen::Map >; + using Matrix6x4f = Eigen::Matrix; + using Map6x4f = Eigen::Map >; + using Map4d = Eigen::Map >; + +} + +__global__ +void kernelFastFit(double * __restrict__ phits, double * __restrict__ presults) { + auto i = blockIdx.x*blockDim.x + threadIdx.x; + Rfit::Map3x4d hits(phits+i,3,4); + Rfit::Map4d result(presults+i,4); + Rfit::Fast_fit(hits, result); +} + +__global__ +void kernelCircleFit(double * __restrict__ phits, + float * __restrict__ phits_ge, + double * __restrict__ pfast_fit_input, + double B, + Rfit::circle_fit * circle_fit_resultsGPU) { + +auto i = blockIdx.x*blockDim.x + threadIdx.x; + Rfit::Map3x4d hits(phits+i,3,4); + Rfit::Map4d fast_fit_input(pfast_fit_input+i,4); + Rfit::Map6x4f hits_ge(phits_ge+i,6,4); + + constexpr uint32_t N = Rfit::Map3x4d::ColsAtCompileTime; + constexpr auto n = N; + + Rfit::VectorNd rad = (hits.block(0, 0, 2, n).colwise().norm()); + + Rfit::Matrix2Nd hits_cov = MatrixXd::Zero(2 * n, 2 * n); + Rfit::loadCovariance2D(hits_ge,hits_cov); + +#ifdef TEST_DEBUG +if (0==i) { + printf("hits %f, %f\n", hits.block(0,0,2,n)(0,0), hits.block(0,0,2,n)(0,1)); + printf("hits %f, %f\n", hits.block(0,0,2,n)(1,0), hits.block(0,0,2,n)(1,1)); + printf("fast_fit_input(0): %f\n", fast_fit_input(0)); + printf("fast_fit_input(1): %f\n", fast_fit_input(1)); + printf("fast_fit_input(2): %f\n", fast_fit_input(2)); + printf("fast_fit_input(3): %f\n", fast_fit_input(3)); + printf("rad(0,0): %f\n", rad(0,0)); + printf("rad(1,1): %f\n", rad(1,1)); + printf("rad(2,2): %f\n", rad(2,2)); + printf("hits_cov(0,0): %f\n", (*hits_cov)(0,0)); + printf("hits_cov(1,1): %f\n", (*hits_cov)(1,1)); + printf("hits_cov(2,2): %f\n", (*hits_cov)(2,2)); + printf("hits_cov(11,11): %f\n", (*hits_cov)(11,11)); + printf("B: %f\n", B); +} +#endif + circle_fit_resultsGPU[i] = + Rfit::Circle_fit(hits.block(0,0,2,n), hits_cov, + fast_fit_input, rad, B, true); +#ifdef TEST_DEBUG +if (0==i) { + printf("Circle param %f,%f,%f\n",circle_fit_resultsGPU[i].par(0),circle_fit_resultsGPU[i].par(1),circle_fit_resultsGPU[i].par(2)); +} +#endif +} + +__global__ +void kernelLineFit(double * __restrict__ phits, + float * __restrict__ phits_ge, + double B, + Rfit::circle_fit * circle_fit, + double * __restrict__ pfast_fit, + Rfit::line_fit * line_fit) +{ + auto i = blockIdx.x*blockDim.x + threadIdx.x; + Rfit::Map3x4d hits(phits+i,3,4); + Rfit::Map4d fast_fit(pfast_fit+i,4); + Rfit::Map6x4f hits_ge(phits_ge+i,6,4); + line_fit[i] = Rfit::Line_fit(hits, hits_ge, circle_fit[i], fast_fit, B, true); +} + +template +__device__ __host__ +void fillHitsAndHitsCov(M3x4 & hits, M6x4 & hits_ge) { + hits << 1.98645, 4.72598, 7.65632, 11.3151, + 2.18002, 4.88864, 7.75845, 11.3134, + 2.46338, 6.99838, 11.808, 17.793; + hits_ge.col(0)[0] = 7.14652e-06; + hits_ge.col(1)[0] = 2.15789e-06; + hits_ge.col(2)[0] = 1.63328e-06; + hits_ge.col(3)[0] = 6.27919e-06; + hits_ge.col(0)[2] = 6.10348e-06; + hits_ge.col(1)[2] = 2.08211e-06; + hits_ge.col(2)[2] = 1.61672e-06; + hits_ge.col(3)[2] = 6.28081e-06; + hits_ge.col(0)[5] = 5.184e-05; + hits_ge.col(1)[5] = 1.444e-05; + hits_ge.col(2)[5] = 6.25e-06; + hits_ge.col(3)[5] = 3.136e-05; + hits_ge.col(0)[1] = -5.60077e-06; + hits_ge.col(1)[1] = -1.11936e-06; + hits_ge.col(2)[1] = -6.24945e-07; + hits_ge.col(3)[1] = -5.28e-06; +} + +__global__ +void kernelFillHitsAndHitsCov(double * __restrict__ phits, + float * phits_ge) { + auto i = blockIdx.x*blockDim.x + threadIdx.x; + Rfit::Map3x4d hits(phits+i,3,4); + Rfit::Map6x4f hits_ge(phits_ge+i,6,4); + hits_ge = MatrixXf::Zero(6,4); + fillHitsAndHitsCov(hits,hits_ge); +} + +void testFit() { + constexpr double B = 0.0113921; + Rfit::Matrix3xNd<4> hits; + Rfit::Matrix6x4f hits_ge = MatrixXf::Zero(6,4); + double * hitsGPU = nullptr;; + float * hits_geGPU = nullptr; + double * fast_fit_resultsGPU = nullptr; + double * fast_fit_resultsGPUret = new double[Rfit::maxNumberOfTracks()*sizeof(Vector4d)]; + Rfit::circle_fit * circle_fit_resultsGPU = nullptr; + Rfit::circle_fit * circle_fit_resultsGPUret = new Rfit::circle_fit(); + Rfit::line_fit * line_fit_resultsGPU = nullptr; + + fillHitsAndHitsCov(hits, hits_ge); + + std::cout << "sizes " << sizeof(hits) << ' ' << sizeof(hits_ge) + << ' ' << sizeof(Vector4d)<< std::endl; + + std::cout << "Generated hits:\n" << hits << std::endl; + std::cout << "Generated cov:\n" << hits_ge << std::endl; + + // FAST_FIT_CPU + Vector4d fast_fit_results; Rfit::Fast_fit(hits, fast_fit_results); + std::cout << "Fitted values (FastFit, [X0, Y0, R, tan(theta)]):\n" << fast_fit_results << std::endl; + + // for timing purposes we fit 4096 tracks + constexpr uint32_t Ntracks = 4096; + cudaCheck(cudaMalloc(&hitsGPU, Rfit::maxNumberOfTracks()*sizeof(Rfit::Matrix3xNd<4>))); + cudaCheck(cudaMalloc(&hits_geGPU, Rfit::maxNumberOfTracks()*sizeof(Rfit::Matrix6x4f))); + cudaCheck(cudaMalloc(&fast_fit_resultsGPU, Rfit::maxNumberOfTracks()*sizeof(Vector4d))); + cudaCheck(cudaMalloc((void **)&line_fit_resultsGPU, Rfit::maxNumberOfTracks()*sizeof(Rfit::line_fit))); + cudaCheck(cudaMalloc((void **)&circle_fit_resultsGPU, Rfit::maxNumberOfTracks()*sizeof(Rfit::circle_fit))); + + + kernelFillHitsAndHitsCov<<>>(hitsGPU,hits_geGPU); + + // FAST_FIT GPU + kernelFastFit<<>>(hitsGPU, fast_fit_resultsGPU); + cudaDeviceSynchronize(); + + cudaMemcpy(fast_fit_resultsGPUret, fast_fit_resultsGPU, Rfit::maxNumberOfTracks()*sizeof(Vector4d), cudaMemcpyDeviceToHost); + Rfit::Map4d fast_fit(fast_fit_resultsGPUret+10,4); + std::cout << "Fitted values (FastFit, [X0, Y0, R, tan(theta)]): GPU\n" << fast_fit << std::endl; + assert(isEqualFuzzy(fast_fit_results, fast_fit)); + + // CIRCLE_FIT CPU + constexpr uint32_t N = Rfit::Map3x4d::ColsAtCompileTime; + constexpr auto n = N; + Rfit::VectorNd rad = (hits.block(0, 0, 2, n).colwise().norm()); + + Rfit::Matrix2Nd hits_cov = MatrixXd::Zero(2 * n, 2 * n); + Rfit::loadCovariance2D(hits_ge,hits_cov); + Rfit::circle_fit circle_fit_results = Rfit::Circle_fit(hits.block(0, 0, 2, n), + hits_cov, + fast_fit_results, rad, B, true); + std::cout << "Fitted values (CircleFit):\n" << circle_fit_results.par << std::endl; + + // CIRCLE_FIT GPU + + kernelCircleFit<<>>(hitsGPU, hits_geGPU, + fast_fit_resultsGPU, B, circle_fit_resultsGPU); + cudaDeviceSynchronize(); + + cudaMemcpy(circle_fit_resultsGPUret, circle_fit_resultsGPU, + sizeof(Rfit::circle_fit), cudaMemcpyDeviceToHost); + std::cout << "Fitted values (CircleFit) GPU:\n" << circle_fit_resultsGPUret->par << std::endl; + assert(isEqualFuzzy(circle_fit_results.par, circle_fit_resultsGPUret->par)); + + // LINE_FIT CPU + Rfit::line_fit line_fit_results = Rfit::Line_fit(hits, hits_ge, circle_fit_results, fast_fit_results, B, true); + std::cout << "Fitted values (LineFit):\n" << line_fit_results.par << std::endl; + + // LINE_FIT GPU + Rfit::line_fit * line_fit_resultsGPUret = new Rfit::line_fit(); + + kernelLineFit<<>>(hitsGPU, hits_geGPU, B, circle_fit_resultsGPU, fast_fit_resultsGPU, line_fit_resultsGPU); + cudaDeviceSynchronize(); + + cudaMemcpy(line_fit_resultsGPUret, line_fit_resultsGPU, sizeof(Rfit::line_fit), cudaMemcpyDeviceToHost); + std::cout << "Fitted values (LineFit) GPU:\n" << line_fit_resultsGPUret->par << std::endl; + assert(isEqualFuzzy(line_fit_results.par, line_fit_resultsGPUret->par)); + + std::cout << "Fitted cov (CircleFit) CPU:\n" << circle_fit_results.cov << std::endl; + std::cout << "Fitted cov (LineFit): CPU\n" << line_fit_results.cov << std::endl; + std::cout << "Fitted cov (CircleFit) GPU:\n" << circle_fit_resultsGPUret->cov << std::endl; + std::cout << "Fitted cov (LineFit): GPU\n" << line_fit_resultsGPUret->cov << std::endl; + +} + +int main (int argc, char * argv[]) { + testFit(); + std::cout << "TEST FIT, NO ERRORS" << std::endl; + + return 0; +} + diff --git a/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPUNoFit.cu b/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPUNoFit.cu new file mode 100644 index 0000000000000..ead2e3cc00504 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPUNoFit.cu @@ -0,0 +1,213 @@ +#include + +#include +#include + +#include "test_common.h" + +using namespace Eigen; + +__host__ __device__ void eigenValues(Matrix3d * m, Eigen::SelfAdjointEigenSolver::RealVectorType * ret) { +#if TEST_DEBUG + printf("Matrix(0,0): %f\n", (*m)(0,0)); + printf("Matrix(1,1): %f\n", (*m)(1,1)); + printf("Matrix(2,2): %f\n", (*m)(2,2)); +#endif + SelfAdjointEigenSolver es; + es.computeDirect(*m); + (*ret) = es.eigenvalues(); + return; +} + +__global__ void kernel(Matrix3d * m, Eigen::SelfAdjointEigenSolver::RealVectorType * ret) { + eigenValues(m, ret); +} + +__global__ void kernelInverse3x3(Matrix3d * in, Matrix3d * out) { + (*out) = in->inverse(); +} + +__global__ void kernelInverse4x4(Matrix4d * in, Matrix4d * out) { + (*out) = in->inverse(); +} + + +template +__global__ void kernelMultiply(M1 * J, + M2 * C, + M3 * result) { +// Map res(result->data()); +#if TEST_DEBUG + printf("*** GPU IN ***\n"); +#endif + printIt(J); + printIt(C); +// res.noalias() = (*J) * (*C); +// printIt(&res); + (*result) = (*J) * (*C); +#if TEST_DEBUG + printf("*** GPU OUT ***\n"); +#endif + return; +} + +template +void testMultiply() { + std::cout << "TEST MULTIPLY" << std::endl; + std::cout << "Product of type " << row1 << "x" << col1 + << " * " << row2 << "x" << col2 << std::endl; + Eigen::Matrix J; + fillMatrix(J); + Eigen::Matrix C; + fillMatrix(C); + Eigen::Matrix multiply_result = J * C; +#if TEST_DEBUG + std::cout << "Input J:" << std::endl; printIt(&J); + std::cout << "Input C:" << std::endl; printIt(&C); + std::cout << "Output:" << std::endl; + printIt(&multiply_result); +#endif + // GPU + Eigen::Matrix *JGPU = nullptr; + Eigen::Matrix *CGPU = nullptr; + Eigen::Matrix *multiply_resultGPU = nullptr; + Eigen::Matrix *multiply_resultGPUret = new Eigen::Matrix(); + + cudaMalloc((void **)&JGPU, sizeof(Eigen::Matrix)); + cudaMalloc((void **)&CGPU, sizeof(Eigen::Matrix)); + cudaMalloc((void **)&multiply_resultGPU, sizeof(Eigen::Matrix)); + cudaMemcpy(JGPU, &J, sizeof(Eigen::Matrix), cudaMemcpyHostToDevice); + cudaMemcpy(CGPU, &C, sizeof(Eigen::Matrix), cudaMemcpyHostToDevice); + cudaMemcpy(multiply_resultGPU, &multiply_result, sizeof(Eigen::Matrix), cudaMemcpyHostToDevice); + + kernelMultiply<<<1,1>>>(JGPU, CGPU, multiply_resultGPU); + cudaDeviceSynchronize(); + + cudaMemcpy(multiply_resultGPUret, multiply_resultGPU, + sizeof(Eigen::Matrix), cudaMemcpyDeviceToHost); + printIt(multiply_resultGPUret); + assert(isEqualFuzzy(multiply_result, (*multiply_resultGPUret))); +} + +void testInverse3x3() { + std::cout << "TEST INVERSE 3x3" << std::endl; + Matrix3d m; + fillMatrix(m); + m += m.transpose().eval(); + + Matrix3d m_inv = m.inverse(); + Matrix3d *mGPU = nullptr; + Matrix3d *mGPUret = nullptr; + Matrix3d *mCPUret = new Matrix3d(); + +#if TEST_DEBUG + std::cout << "Here is the matrix m:" << std::endl << m << std::endl; + std::cout << "Its inverse is:" << std::endl << m.inverse() << std::endl; +#endif + cudaMalloc((void **)&mGPU, sizeof(Matrix3d)); + cudaMalloc((void **)&mGPUret, sizeof(Matrix3d)); + cudaMemcpy(mGPU, &m, sizeof(Matrix3d), cudaMemcpyHostToDevice); + + kernelInverse3x3<<<1,1>>>(mGPU, mGPUret); + cudaDeviceSynchronize(); + + cudaMemcpy(mCPUret, mGPUret, sizeof(Matrix3d), cudaMemcpyDeviceToHost); +#if TEST_DEBUG + std::cout << "Its GPU inverse is:" << std::endl << (*mCPUret) << std::endl; +#endif + assert(isEqualFuzzy(m_inv, *mCPUret)); +} + +void testInverse4x4() { + std::cout << "TEST INVERSE 4x4" << std::endl; + Matrix4d m; + fillMatrix(m); + m += m.transpose().eval(); + + Matrix4d m_inv = m.inverse(); + Matrix4d *mGPU = nullptr; + Matrix4d *mGPUret = nullptr; + Matrix4d *mCPUret = new Matrix4d(); + +#if TEST_DEBUG + std::cout << "Here is the matrix m:" << std::endl << m << std::endl; + std::cout << "Its inverse is:" << std::endl << m.inverse() << std::endl; +#endif + cudaMalloc((void **)&mGPU, sizeof(Matrix4d)); + cudaMalloc((void **)&mGPUret, sizeof(Matrix4d)); + cudaMemcpy(mGPU, &m, sizeof(Matrix4d), cudaMemcpyHostToDevice); + + kernelInverse4x4<<<1,1>>>(mGPU, mGPUret); + cudaDeviceSynchronize(); + + cudaMemcpy(mCPUret, mGPUret, sizeof(Matrix4d), cudaMemcpyDeviceToHost); +#if TEST_DEBUG + std::cout << "Its GPU inverse is:" << std::endl << (*mCPUret) << std::endl; +#endif + assert(isEqualFuzzy(m_inv, *mCPUret)); +} + +void testEigenvalues() { + std::cout << "TEST EIGENVALUES" << std::endl; + Matrix3d m; + fillMatrix(m); + m += m.transpose().eval(); + + Matrix3d * m_gpu = nullptr; + Matrix3d * mgpudebug = new Matrix3d(); + Eigen::SelfAdjointEigenSolver::RealVectorType *ret = new Eigen::SelfAdjointEigenSolver::RealVectorType; + Eigen::SelfAdjointEigenSolver::RealVectorType *ret1 = new Eigen::SelfAdjointEigenSolver::RealVectorType; + Eigen::SelfAdjointEigenSolver::RealVectorType *ret_gpu = nullptr; + eigenValues(&m, ret); +#if TEST_DEBUG + std::cout << "Generated Matrix M 3x3:\n" << m << std::endl; + std::cout << "The eigenvalues of M are:" << std::endl << (*ret) << std::endl; + std::cout << "*************************\n\n" << std::endl; +#endif + cudaMalloc((void **)&m_gpu, sizeof(Matrix3d)); + cudaMalloc((void **)&ret_gpu, sizeof(Eigen::SelfAdjointEigenSolver::RealVectorType)); + cudaMemcpy(m_gpu, &m, sizeof(Matrix3d), cudaMemcpyHostToDevice); + + kernel<<<1,1>>>(m_gpu, ret_gpu); + cudaDeviceSynchronize(); + + cudaMemcpy(mgpudebug, m_gpu, sizeof(Matrix3d), cudaMemcpyDeviceToHost); + cudaMemcpy(ret1, ret_gpu, sizeof(Eigen::SelfAdjointEigenSolver::RealVectorType), cudaMemcpyDeviceToHost); +#if TEST_DEBUG +std::cout << "GPU Generated Matrix M 3x3:\n" << (*mgpudebug) << std::endl; +std::cout << "GPU The eigenvalues of M are:" << std::endl << (*ret1) << std::endl; +std::cout << "*************************\n\n" << std::endl; +#endif + assert(isEqualFuzzy(*ret, *ret1)); +} + + +int main (int argc, char * argv[]) { + + testEigenvalues(); + testInverse3x3(); + testInverse4x4(); + testMultiply<1, 2, 2, 1>(); + testMultiply<1, 2, 2, 2>(); + testMultiply<1, 2, 2, 3>(); + testMultiply<1, 2, 2, 4>(); + testMultiply<1, 2, 2, 5>(); + testMultiply<2, 1, 1, 2>(); + testMultiply<2, 1, 1, 3>(); + testMultiply<2, 1, 1, 4>(); + testMultiply<2, 1, 1, 5>(); + testMultiply<2, 2, 2, 2>(); + testMultiply<2, 3, 3, 1>(); + testMultiply<2, 3, 3, 2>(); + testMultiply<2, 3, 3, 4>(); + testMultiply<2, 3, 3, 5>(); + testMultiply<3, 2, 2, 3>(); + testMultiply<2, 3, 3, 3>(); // DOES NOT COMPILE W/O PATCHING EIGEN + testMultiply<3, 3, 3, 3>(); + testMultiply<8, 8, 8, 8>(); + testMultiply<3, 4, 4, 3>(); + testMultiply<2, 4, 4, 2>(); + testMultiply<3, 4, 4, 2>(); // DOES NOT COMPILE W/O PATCHING EIGEN + + return 0; +} diff --git a/RecoPixelVertexing/PixelTrackFitting/test/testEigenJacobian.cpp b/RecoPixelVertexing/PixelTrackFitting/test/testEigenJacobian.cpp new file mode 100644 index 0000000000000..e01aa30efc656 --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/test/testEigenJacobian.cpp @@ -0,0 +1,94 @@ +#include "RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h" +#include + +using Rfit::Vector5d; +using Rfit::Matrix5d; + + +Vector5d transf(Vector5d p) { + auto sinTheta = 1/std::sqrt(1+p(3)*p(3)); + p(2) = sinTheta/p(2); + return p; +} + +Matrix5d transfFast(Matrix5d cov, Vector5d const & p) { + auto sqr = [](auto x) { return x*x;}; + auto sinTheta = 1/std::sqrt(1+p(3)*p(3)); + auto cosTheta = p(3)*sinTheta; + cov(2,2) = sqr(sinTheta) * ( + cov(2,2)*sqr(1./(p(2)*p(2))) + + cov(3,3)*sqr(cosTheta*sinTheta/p(2)) + ); + cov(3,2) = cov(2,3) = cov(3,3) * cosTheta * sqr(sinTheta) / p(2); + // for (int i=0; i<5; ++i) cov(i,2) *= -sinTheta/(p(2)*p(2)); + // for (int i=0; i<5; ++i) cov(2,i) *= -sinTheta/(p(2)*p(2)); + return cov; + + +} + +Matrix5d Jacobian(Vector5d const & p) { + + Matrix5d J = Matrix5d::Identity(); + + auto sinTheta2 = 1/(1+p(3)*p(3)); + auto sinTheta = std::sqrt(sinTheta2); + J(2,2) = -sinTheta/(p(2)*p(2)); + J(2,3) = -sinTheta2*sinTheta*p(3)/p(2); + return J; +} + +Matrix5d transf(Matrix5d const & cov, Matrix5d const& J) { + + return J*cov*J.transpose(); + +} + +Matrix5d loadCov(Vector5d const & e) { + + Matrix5d cov = Matrix5d::Zero(); + for (int i=0; i<5; ++i) cov(i,i) = e(i)*e(i); + return cov; +} + + +#include +int main() { + + //!<(phi,Tip,pt,cotan(theta)),Zip) + Vector5d par0; par0 << 0.2,0.1,3.5,0.8,0.1; + Vector5d del0; del0 << 0.01,0.01,0.035,-0.03,-0.01; + + Matrix5d J = Jacobian(par0); + + + Vector5d par1 = transf(par0); + Vector5d par2 = transf(par0+del0); + Vector5d del1 = par2-par1; + + Matrix5d cov0 = loadCov(del0); + Matrix5d cov1 = transf(cov0,J); + Matrix5d cov2 = transfFast(cov0,par0); + + // don't ask: guess + std::cout << "par0 " << par0.transpose() << std::endl; + std::cout << "del0 " << del0.transpose() << std::endl; + + + std::cout << "par1 " << par1.transpose() << std::endl; + std::cout << "del1 " << del1.transpose() << std::endl; + std::cout << "del2 " << (J*del0).transpose() << std::endl; + + std::cout << "del1^2 " << (del1.array()*del1.array()).transpose() << std::endl; + std::cout << std::endl; + std::cout << "J\n" << J << std::endl; + + std::cout << "cov0\n" << cov0 << std::endl; + std::cout << "cov1\n" << cov1 << std::endl; + std::cout << "cov2\n" << cov2 << std::endl; + + + return 0; + + +} diff --git a/RecoPixelVertexing/PixelTrackFitting/test/testRiemannFit.cpp b/RecoPixelVertexing/PixelTrackFitting/test/testRiemannFit.cpp new file mode 100644 index 0000000000000..af4a3e52f46fa --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/test/testRiemannFit.cpp @@ -0,0 +1,88 @@ +#include + +#include +#include + +#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" + +#include "test_common.h" + +using namespace Eigen; + +namespace Rfit { + constexpr uint32_t maxNumberOfTracks() { return 5*1024; } + constexpr uint32_t stride() { return maxNumberOfTracks();} + using Matrix3x4d = Eigen::Matrix; + using Map3x4d = Eigen::Map >; + using Matrix6x4f = Eigen::Matrix; + using Map6x4f = Eigen::Map >; + using Map4d = Eigen::Map >; + +} + +template +void fillHitsAndHitsCov(M3x4 & hits, M6x4 & hits_ge) { + hits << 1.98645, 4.72598, 7.65632, 11.3151, + 2.18002, 4.88864, 7.75845, 11.3134, + 2.46338, 6.99838, 11.808, 17.793; + hits_ge.col(0)[0] = 7.14652e-06; + hits_ge.col(1)[0] = 2.15789e-06; + hits_ge.col(2)[0] = 1.63328e-06; + hits_ge.col(3)[0] = 6.27919e-06; + hits_ge.col(0)[2] = 6.10348e-06; + hits_ge.col(1)[2] = 2.08211e-06; + hits_ge.col(2)[2] = 1.61672e-06; + hits_ge.col(3)[2] = 6.28081e-06; + hits_ge.col(0)[5] = 5.184e-05; + hits_ge.col(1)[5] = 1.444e-05; + hits_ge.col(2)[5] = 6.25e-06; + hits_ge.col(3)[5] = 3.136e-05; + hits_ge.col(0)[1] = -5.60077e-06; + hits_ge.col(1)[1] = -1.11936e-06; + hits_ge.col(2)[1] = -6.24945e-07; + hits_ge.col(3)[1] = -5.28e-06; +} + +void testFit() { + constexpr double B = 0.0113921; + Rfit::Matrix3xNd<4> hits; + Rfit::Matrix6x4f hits_ge = MatrixXf::Zero(6,4); + + fillHitsAndHitsCov(hits, hits_ge); + + std::cout << "sizes " << sizeof(hits) << ' ' << sizeof(hits_ge) + << ' ' << sizeof(Vector4d)<< std::endl; + + std::cout << "Generated hits:\n" << hits << std::endl; + std::cout << "Generated cov:\n" << hits_ge << std::endl; + + // FAST_FIT_CPU + Vector4d fast_fit_results; Rfit::Fast_fit(hits, fast_fit_results); + std::cout << "Fitted values (FastFit, [X0, Y0, R, tan(theta)]):\n" << fast_fit_results << std::endl; + + + // CIRCLE_FIT CPU + constexpr uint32_t N = Rfit::Map3x4d::ColsAtCompileTime; + constexpr auto n = N; + Rfit::VectorNd rad = (hits.block(0, 0, 2, n).colwise().norm()); + + Rfit::Matrix2Nd hits_cov = MatrixXd::Zero(2 * n, 2 * n); + Rfit::loadCovariance2D(hits_ge,hits_cov); + Rfit::circle_fit circle_fit_results = Rfit::Circle_fit(hits.block(0, 0, 2, n), + hits_cov, + fast_fit_results, rad, B, true); + std::cout << "Fitted values (CircleFit):\n" << circle_fit_results.par << std::endl; + + // LINE_FIT CPU + Rfit::line_fit line_fit_results = Rfit::Line_fit(hits, hits_ge, circle_fit_results, fast_fit_results, B, true); + std::cout << "Fitted values (LineFit):\n" << line_fit_results.par << std::endl; + + std::cout << "Fitted cov (CircleFit) CPU:\n" << circle_fit_results.cov << std::endl; + std::cout << "Fitted cov (LineFit): CPU\n" << line_fit_results.cov << std::endl; +} + +int main (int argc, char * argv[]) { + testFit(); + return 0; +} + diff --git a/RecoPixelVertexing/PixelTrackFitting/test/test_common.h b/RecoPixelVertexing/PixelTrackFitting/test/test_common.h new file mode 100644 index 0000000000000..79bb128eeec8a --- /dev/null +++ b/RecoPixelVertexing/PixelTrackFitting/test/test_common.h @@ -0,0 +1,51 @@ +#ifndef RecoPixelVertexing__PixelTrackFitting__test_common_h +#define RecoPixelVertexing__PixelTrackFitting__test_common_h + +#include +#include +#include + +template +__host__ __device__ +void printIt(C * m) { +#ifdef TEST_DEBUG + printf("\nMatrix %dx%d\n", (int)m->rows(), (int)m->cols()); + for (u_int r = 0; r < m->rows(); ++r) { + for (u_int c = 0; c < m->cols(); ++c) { + printf("Matrix(%d,%d) = %f\n", r, c, (*m)(r,c)); + } + } +#endif +} + +template +bool isEqualFuzzy(C1 a, C2 b, double epsilon = 1e-6) { + for (unsigned int i = 0; i < a.rows(); ++i) { + for (unsigned int j = 0; j < a.cols(); ++j) { + assert(std::abs(a(i,j)-b(i,j)) + < std::min(std::abs(a(i,j)), std::abs(b(i,j)))*epsilon); + } + } + return true; +} + +bool isEqualFuzzy(double a, double b, double epsilon=1e-6) { + return std::abs(a-b) < std::min(std::abs(a), std::abs(b))*epsilon; +} + + +template +void fillMatrix(T & t) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0.0, 2.0); + for (int row = 0; row < t.rows(); ++row) { + for (int col = 0; col < t.cols(); ++col) { + t(row, col) = dis(gen); + } + } + return; +} + + +#endif diff --git a/RecoPixelVertexing/PixelTriplets/interface/CAHitQuadrupletGenerator.h b/RecoPixelVertexing/PixelTriplets/interface/CAHitQuadrupletGenerator.h index 2d745de9e544a..721a5dfaef07e 100644 --- a/RecoPixelVertexing/PixelTriplets/interface/CAHitQuadrupletGenerator.h +++ b/RecoPixelVertexing/PixelTriplets/interface/CAHitQuadrupletGenerator.h @@ -41,7 +41,7 @@ class CAHitQuadrupletGenerator { ~CAHitQuadrupletGenerator() = default; static void fillDescriptions(edm::ParameterSetDescription& desc); - static const char *fillDescriptionsLabel() { return "caHitQuadruplet"; } + static const char *fillDescriptionsLabel() { return "caHitQuadrupletDefault"; } void initEvent(const edm::Event& ev, const edm::EventSetup& es); diff --git a/RecoPixelVertexing/PixelTriplets/interface/CircleEq.h b/RecoPixelVertexing/PixelTriplets/interface/CircleEq.h new file mode 100644 index 0000000000000..fa538256ed010 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/interface/CircleEq.h @@ -0,0 +1,128 @@ +#ifndef RecoPixelVertexingPixelTripletsCircleEq_H +#define RecoPixelVertexingPixelTripletsCircleEq_H +/** +| 1) circle is parameterized as: | +| C*[(X-Xp)**2+(Y-Yp)**2] - 2*alpha*(X-Xp) - 2*beta*(Y-Yp) = 0 | +| Xp,Yp is a point on the track; | +| C = 1/r0 is the curvature ( sign of C is charge of particle ); | +| alpha & beta are the direction cosines of the radial vector at Xp,Yp | +| i.e. alpha = C*(X0-Xp), | +| beta = C*(Y0-Yp), | +| where center of circle is at X0,Y0. | +| | +| Slope dy/dx of tangent at Xp,Yp is -alpha/beta. | +| 2) the z dimension of the helix is parameterized by gamma = dZ/dSperp | +| this is also the tangent of the pitch angle of the helix. | +| with this parameterization, (alpha,beta,gamma) rotate like a vector. | +| 3) For tracks going inward at (Xp,Yp), C, alpha, beta, and gamma change sign| +| +*/ + +#include + +template +class CircleEq { + +public: + + CircleEq(){} + + constexpr CircleEq(T x1, T y1, + T x2, T y2, + T x3, T y3) { + compute(x1,y1,x2,y2,x3,y3); + } + + constexpr void compute(T x1, T y1, + T x2, T y2, + T x3, T y3); + + // dca to origin divided by curvature + constexpr T dca0() const { + auto x = m_c*m_xp + m_alpha; + auto y = m_c*m_yp + m_beta; + return std::sqrt(x*x+y*y) - T(1); + } + + // dca to given point (divided by curvature) + constexpr T dca(T x, T y) const { + x = m_c*(m_xp-x) + m_alpha; + y = m_c*(m_yp-y) + m_beta; + return std::sqrt(x*x+y*y) - T(1); + + } + + // curvature + constexpr auto curvature() const { return m_c;} + + + // alpha and beta + constexpr std::pair cosdir() const { + return std::make_pair(m_alpha, m_beta); + } + + + // alpha and beta af given point + constexpr std::pair cosdir(T x, T y) const { + return std::make_pair(m_alpha - m_c*(x-m_xp), m_beta - m_c*(y-m_yp)); + } + + // center + constexpr std::pair center() const { + return std::make_pair(m_xp + m_alpha/m_c, m_yp + m_beta/m_c); + } + + constexpr auto radius() const { return T(1)/m_c;} + + T m_xp=0; + T m_yp=0; + T m_c=0; + T m_alpha=0; + T m_beta=0; + +}; + + +template +constexpr void CircleEq::compute(T x1, T y1, + T x2, T y2, + T x3, T y3) { + bool noflip = std::abs(x3-x1) < std::abs(y3-y1); + + auto x1p = noflip ? x1-x2 : y1-y2; + auto y1p = noflip ? y1-y2 : x1-x2; + auto d12 = x1p*x1p + y1p*y1p; + auto x3p = noflip ? x3-x2 : y3-y2; + auto y3p = noflip ? y3-y2 : x3-x2; + auto d32 = x3p*x3p + y3p*y3p; + + auto num = x1p*y3p-y1p*x3p; // num also gives correct sign for CT + auto det = d12*y3p-d32*y1p; + + /* + auto ct = num/det; + auto sn = det>0 ? T(1.) : T(-1.); + auto st2 = (d12*x3p-d32*x1p)/det; + auto seq = T(1.) +st2*st2; + auto al2 = sn/std::sqrt(seq); + auto be2 = -st2*al2; + ct *= T(2.)*al2; + */ + + auto st2 = (d12*x3p-d32*x1p); + auto seq = det*det +st2*st2; + auto al2 = T(1.)/std::sqrt(seq); + auto be2 = -st2*al2; + auto ct = T(2.)*num*al2; + al2 *=det; + + m_xp = x2; + m_yp = y2; + m_c = noflip ? ct : -ct; + m_alpha = noflip ? al2 : -be2; + m_beta = noflip ? be2 : -al2; + +} + +#endif + diff --git a/RecoPixelVertexing/PixelTriplets/plugins/BuildFile.xml b/RecoPixelVertexing/PixelTriplets/plugins/BuildFile.xml index 6b1a66a0725a3..3c8397cf572f6 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/BuildFile.xml +++ b/RecoPixelVertexing/PixelTriplets/plugins/BuildFile.xml @@ -1,9 +1,18 @@ - - - - - - + + + + + + + + + + + + + + + + + - - diff --git a/RecoPixelVertexing/PixelTriplets/plugins/CAConstants.h b/RecoPixelVertexing/PixelTriplets/plugins/CAConstants.h new file mode 100644 index 0000000000000..942404a9313e3 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAConstants.h @@ -0,0 +1,34 @@ +#ifndef RecoPixelVertexing_PixelTriplets_plugins_CAConstants_h +#define RecoPixelVertexing_PixelTriplets_plugins_CAConstants_h + +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" +#include "HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h" +#include "RecoLocalTracker/SiPixelClusterizer/interface/PixelTrackingGPUConstants.h" + + +namespace CAConstants { + + // constants + constexpr uint32_t maxNumberOfQuadruplets() { return 10000; } + constexpr uint32_t maxCellsPerHit() { return 128; } + constexpr uint32_t maxNumberOfLayerPairs() { return 13; } + constexpr uint32_t maxNumberOfLayers() { return 10; } + constexpr uint32_t maxNumberOfDoublets() { return 262144; } + constexpr uint32_t maxTuples() { return 10000;} + + // types + using hindex_type = uint16_t; // FIXME from siPixelRecHitsHeterogeneousProduct + using tindex_type = uint16_t; // for tuples + using OuterHitOfCell = GPU::VecArray< uint32_t, maxCellsPerHit()>; + using TuplesContainer = OneToManyAssoc; + using HitToTuple = OneToManyAssoc; // 3.5 should be enough + +} + + + +#endif + diff --git a/RecoPixelVertexing/PixelTriplets/plugins/CAHitNtupletHeterogeneousEDProducer.cc b/RecoPixelVertexing/PixelTriplets/plugins/CAHitNtupletHeterogeneousEDProducer.cc new file mode 100644 index 0000000000000..141b960b993f0 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitNtupletHeterogeneousEDProducer.cc @@ -0,0 +1,182 @@ +#include "DataFormats/Common/interface/Handle.h" +#include "DataFormats/Common/interface/OwnVector.h" +#include "FWCore/Framework/interface/ConsumesCollector.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/PluginManager/interface/ModuleDef.h" +#include "FWCore/Utilities/interface/EDGetToken.h" +#include "FWCore/Utilities/interface/RunningAverage.h" +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" +#include "RecoPixelVertexing/PixelTriplets/interface/OrderedHitSeeds.h" +#include "RecoTracker/TkHitPairs/interface/IntermediateHitDoublets.h" +#include "RecoTracker/TkHitPairs/interface/RegionsSeedingHitSets.h" + +#include "CAHitQuadrupletGeneratorGPU.h" + +namespace { +void fillNtuplets(RegionsSeedingHitSets::RegionFiller &seedingHitSetsFiller, + const OrderedHitSeeds &quadruplets) { + for (const auto &quad : quadruplets) { + seedingHitSetsFiller.emplace_back(quad[0], quad[1], quad[2], quad[3]); + } +} +} // namespace + +class CAHitNtupletHeterogeneousEDProducer + : public HeterogeneousEDProducer> { +public: + + using PixelRecHitsH = siPixelRecHitsHeterogeneousProduct::HeterogeneousPixelRecHit; + using GPUProduct = pixelTuplesHeterogeneousProduct::GPUProduct; + using CPUProduct = pixelTuplesHeterogeneousProduct::CPUProduct; + using Output = pixelTuplesHeterogeneousProduct::HeterogeneousPixelTuples; + + + CAHitNtupletHeterogeneousEDProducer(const edm::ParameterSet &iConfig); + ~CAHitNtupletHeterogeneousEDProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions &descriptions); + void beginStreamGPUCuda(edm::StreamID streamId, + cuda::stream_t<> &cudaStream) override; + void acquireGPUCuda(const edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceGPUCuda(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceCPU(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup) override; + +private: + edm::EDGetTokenT> regionToken_; + + edm::EDGetTokenT gpuHits_; + edm::EDGetTokenT cpuHits_; + + edm::RunningAverage localRA_; + CAHitQuadrupletGeneratorGPU GPUGenerator_; + + bool emptyRegions = false; + std::unique_ptr seedingHitSets_; + + const bool doRiemannFit_; + const bool enableConversion_; + const bool enableTransfer_; +}; + +CAHitNtupletHeterogeneousEDProducer::CAHitNtupletHeterogeneousEDProducer( + const edm::ParameterSet &iConfig) + : HeterogeneousEDProducer(iConfig), + gpuHits_(consumesHeterogeneous(iConfig.getParameter("heterogeneousPixelRecHitSrc"))), + cpuHits_(consumes(iConfig.getParameter("heterogeneousPixelRecHitSrc"))), + GPUGenerator_(iConfig, consumesCollector()), + doRiemannFit_(iConfig.getParameter("doRiemannFit")), + enableConversion_(iConfig.getParameter("gpuEnableConversion")), + enableTransfer_(enableConversion_ || iConfig.getParameter("gpuEnableTransfer")) +{ + produces(); + if(enableConversion_) { + regionToken_ = consumes>(iConfig.getParameter("trackingRegions")); + produces(); + } +} + +void CAHitNtupletHeterogeneousEDProducer::fillDescriptions( + edm::ConfigurationDescriptions &descriptions) { + edm::ParameterSetDescription desc; + + desc.add("trackingRegions", edm::InputTag("globalTrackingRegionFromBeamSpot")); + desc.add("heterogeneousPixelRecHitSrc", edm::InputTag("siPixelRecHitsPreSplitting")); + desc.add("doRiemannFit", true); // mandatory! + desc.add("gpuEnableTransfer", true); + desc.add("gpuEnableConversion", true); + + CAHitQuadrupletGeneratorGPU::fillDescriptions(desc); + HeterogeneousEDProducer::fillPSetDescription(desc); + auto label = "caHitQuadrupletHeterogeneousEDProducer"; + descriptions.add(label, desc); +} + +void CAHitNtupletHeterogeneousEDProducer::beginStreamGPUCuda( + edm::StreamID streamId, cuda::stream_t<> &cudaStream) { + GPUGenerator_.allocateOnGPU(); +} + +void CAHitNtupletHeterogeneousEDProducer::acquireGPUCuda( + const edm::HeterogeneousEvent &iEvent, const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) { + + edm::Handle gh; + iEvent.getByToken(gpuHits_, gh); + auto const & gHits = *gh; + + GPUGenerator_.buildDoublets(gHits,cudaStream.id()); + + GPUGenerator_.initEvent(iEvent.event(), iSetup); + + LogDebug("CAHitNtupletHeterogeneousEDProducer") + << "Creating ntuplets on GPU"; + + GPUGenerator_.hitNtuplets(gHits, iSetup, doRiemannFit_, enableTransfer_, cudaStream.id()); + + + +} + +void CAHitNtupletHeterogeneousEDProducer::produceGPUCuda( + edm::HeterogeneousEvent &iEvent, const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) { + + if (enableConversion_) { + edm::Handle> hregions; + iEvent.getByToken(regionToken_, hregions); + const auto ®ions = *hregions; + + assert(regions.size()<=1); + + if (regions.empty()) { + emptyRegions = true; + return; + } + + seedingHitSets_ = std::make_unique(); + seedingHitSets_->reserve(regions.size(), localRA_.upper()); + + edm::Handle gh; + iEvent.getByToken(cpuHits_, gh); + auto const & rechits = *gh; + + std::vector ntuplets(regions.size()); + for (auto &ntuplet : ntuplets) ntuplet.reserve(localRA_.upper()); + int index = 0; + for (const auto ®ion : regions) { + auto seedingHitSetsFiller = seedingHitSets_->beginRegion(®ion); + GPUGenerator_.fillResults(region, rechits, ntuplets, iSetup); + fillNtuplets(seedingHitSetsFiller, ntuplets[index]); + ntuplets[index].clear(); + index++; + } + localRA_.update(seedingHitSets_->size()); + iEvent.put(std::move(seedingHitSets_)); + } + + auto output = std::make_unique(GPUGenerator_.getOutput()); + iEvent.put(std::move(output), heterogeneous::DisableTransfer{}); + GPUGenerator_.cleanup(cudaStream.id()); + +} + +void CAHitNtupletHeterogeneousEDProducer::produceCPU( + edm::HeterogeneousEvent &iEvent, const edm::EventSetup &iSetup) +{ + throw cms::Exception("NotImplemented") << "CPU version is no longer implemented"; +} + +DEFINE_FWK_MODULE(CAHitNtupletHeterogeneousEDProducer); diff --git a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc new file mode 100644 index 0000000000000..4963bd68c712c --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc @@ -0,0 +1,239 @@ +// +// Author: Felice Pantaleo, CERN +// + +#include + +#include "CommonTools/Utils/interface/DynArray.h" +#include "DataFormats/Common/interface/Handle.h" +#include "FWCore/Framework/interface/ConsumesCollector.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Utilities/interface/isFinite.h" +#include "RecoPixelVertexing/PixelTriplets/interface/ThirdHitPredictionFromCircle.h" +#include "TrackingTools/DetLayers/interface/BarrelDetLayer.h" + +#include "CAHitQuadrupletGeneratorGPU.h" + +namespace { + + template T sqr(T x) { return x * x; } + +} // namespace + +using namespace std; + +constexpr unsigned int CAHitQuadrupletGeneratorGPU::minLayers; + +CAHitQuadrupletGeneratorGPU::CAHitQuadrupletGeneratorGPU( + const edm::ParameterSet &cfg, + edm::ConsumesCollector &iC) : + kernels(cfg.getParameter("earlyFishbone"),cfg.getParameter("lateFishbone")), + caThetaCut(cfg.getParameter("CAThetaCut")), + caPhiCut(cfg.getParameter("CAPhiCut")), + caHardPtCut(cfg.getParameter("CAHardPtCut")) +{ +} + +void CAHitQuadrupletGeneratorGPU::fillDescriptions(edm::ParameterSetDescription &desc) { + desc.add("CAThetaCut", 0.00125); + desc.add("CAPhiCut", 10); + desc.add("CAHardPtCut", 0); + desc.add("earlyFishbone",false); + desc.add("lateFishbone",true); +} + +void CAHitQuadrupletGeneratorGPU::initEvent(edm::Event const& ev, edm::EventSetup const& es) { + fitter.setBField(1 / PixelRecoUtilities::fieldInInvGev(es)); +} + + +CAHitQuadrupletGeneratorGPU::~CAHitQuadrupletGeneratorGPU() { + deallocateOnGPU(); +} + +void CAHitQuadrupletGeneratorGPU::hitNtuplets( + HitsOnCPU const& hh, + edm::EventSetup const& es, + bool doRiemannFit, + bool transferToCPU, + cudaStream_t cudaStream) +{ + hitsOnCPU = &hh; + launchKernels(hh, doRiemannFit, transferToCPU, cudaStream); +} + +void CAHitQuadrupletGeneratorGPU::fillResults( + const TrackingRegion ®ion, SiPixelRecHitCollectionNew const & rechits, + std::vector &result, const edm::EventSetup &es) +{ + hitmap_.clear(); + auto const & rcs = rechits.data(); + for (auto const & h : rcs) hitmap_.add(h, &h); + + assert(hitsOnCPU); + auto nhits = hitsOnCPU->nHits; + int index = 0; + + auto const & foundQuads = fetchKernelResult(index); + unsigned int numberOfFoundQuadruplets = foundQuads.size(); + + std::array gps; + std::array ges; + std::array barrels; + std::array phits; + + indToEdm.clear(); + indToEdm.resize(numberOfFoundQuadruplets,64000); + + int nbad=0; + // loop over quadruplets + for (unsigned int quadId = 0; quadId < numberOfFoundQuadruplets; ++quadId) { + auto isBarrel = [](const unsigned id) -> bool { + return id == PixelSubdetector::PixelBarrel; + }; + bool bad = pixelTuplesHeterogeneousProduct::bad == quality_[quadId]; + for (unsigned int i = 0; i < 4; ++i) { + auto k = foundQuads[quadId][i]; + assert(k(hp); + auto const &ahit = *phits[i]; + gps[i] = ahit.globalPosition(); + ges[i] = ahit.globalPositionError(); + barrels[i] = isBarrel(ahit.geographicalId().subdetId()); + + } + if (bad) { nbad++; quality_[quadId] = pixelTuplesHeterogeneousProduct::bad; continue;} + if (quality_[quadId] != pixelTuplesHeterogeneousProduct::loose) continue; // FIXME remove dup + + result[index].emplace_back(phits[0], phits[1], phits[2], phits[3]); + indToEdm[quadId] = result[index].size()-1; + } // end loop over quads + +#ifdef GPU_DEBUG + std::cout << "Q Final quads " << result[index].size() << ' ' << nbad << std::endl; +#endif + +} + + +void CAHitQuadrupletGeneratorGPU::deallocateOnGPU() +{ + //product + cudaFree(gpu_.tuples_d); + cudaFree(gpu_.helix_fit_results_d); + cudaFree(gpu_.quality_d); + cudaFree(gpu_.apc_d); + cudaFree(gpu_d); + cudaFreeHost(tuples_); + cudaFreeHost(helix_fit_results_); + cudaFreeHost(quality_); +} + +void CAHitQuadrupletGeneratorGPU::allocateOnGPU() +{ + constexpr auto maxNumberOfQuadruplets_ = CAConstants::maxNumberOfQuadruplets(); + + // allocate and initialise the GPU memory + cudaCheck(cudaMalloc(&gpu_.tuples_d, sizeof(TuplesOnGPU::Container))); + cudaCheck(cudaMemset(gpu_.tuples_d, 0x00, sizeof(TuplesOnGPU::Container))); + cudaCheck(cudaMalloc(&gpu_.apc_d, sizeof(AtomicPairCounter))); + cudaCheck(cudaMemset(gpu_.apc_d, 0x00, sizeof(AtomicPairCounter))); + cudaCheck(cudaMalloc(&gpu_.helix_fit_results_d, sizeof(Rfit::helix_fit)*maxNumberOfQuadruplets_)); + cudaCheck(cudaMemset(gpu_.helix_fit_results_d, 0x00, sizeof(Rfit::helix_fit)*maxNumberOfQuadruplets_)); + cudaCheck(cudaMalloc(&gpu_.quality_d, sizeof(Quality)*maxNumberOfQuadruplets_)); + cudaCheck(cudaMemset(gpu_.quality_d, 0x00, sizeof(Quality)*maxNumberOfQuadruplets_)); + + cudaCheck(cudaMalloc(&gpu_d, sizeof(TuplesOnGPU))); + gpu_.me_d = gpu_d; + cudaCheck(cudaMemcpy(gpu_d, &gpu_, sizeof(TuplesOnGPU), cudaMemcpyDefault)); + + cudaCheck(cudaMallocHost(&tuples_, sizeof(TuplesOnGPU::Container))); + cudaCheck(cudaMallocHost(&helix_fit_results_, sizeof(Rfit::helix_fit)*maxNumberOfQuadruplets_)); + cudaCheck(cudaMallocHost(&quality_, sizeof(Quality)*maxNumberOfQuadruplets_)); + + kernels.allocateOnGPU(); + fitter.allocateOnGPU(gpu_.tuples_d, gpu_.helix_fit_results_d); + + +} + +void CAHitQuadrupletGeneratorGPU::launchKernels(HitsOnCPU const & hh, + bool doRiemannFit, + bool transferToCPU, + cudaStream_t cudaStream) +{ + + kernels.launchKernels(hh, gpu_, cudaStream); + if (doRiemannFit) { + fitter.launchKernels(hh, hh.nHits, CAConstants::maxNumberOfQuadruplets(), cudaStream); + kernels.classifyTuples(hh, gpu_, cudaStream); + } + if (transferToCPU) { + cudaCheck(cudaMemcpyAsync(tuples_,gpu_.tuples_d, + sizeof(TuplesOnGPU::Container), + cudaMemcpyDeviceToHost, cudaStream)); + + cudaCheck(cudaMemcpyAsync(helix_fit_results_,gpu_.helix_fit_results_d, + sizeof(Rfit::helix_fit)*CAConstants::maxNumberOfQuadruplets(), + cudaMemcpyDeviceToHost, cudaStream)); + + cudaCheck(cudaMemcpyAsync(quality_,gpu_.quality_d, + sizeof(Quality)*CAConstants::maxNumberOfQuadruplets(), + cudaMemcpyDeviceToHost, cudaStream)); + + } + +} + +void CAHitQuadrupletGeneratorGPU::cleanup(cudaStream_t cudaStream) { + kernels.cleanup(cudaStream); +} + + + +std::vector> +CAHitQuadrupletGeneratorGPU::fetchKernelResult(int) +{ + assert(tuples_); + auto const & tuples = *tuples_; + + uint32_t sizes[7]={0}; + std::vector ntk(10000); + auto add = [&](uint32_t hi) { if (hi>=ntk.size()) ntk.resize(hi+1); ++ntk[hi];}; + + std::vector> quadsInterface; quadsInterface.reserve(10000); + + nTuples_=0; + for (auto i = 0U; i < tuples.nbins(); ++i) { + auto sz = tuples.size(i); + if (sz==0) break; // we know cannot be less then 3 + ++nTuples_; + ++sizes[sz]; + for (auto j=tuples.begin(i); j!=tuples.end(i); ++j) add(*j); + if (sz<4) continue; + quadsInterface.emplace_back(std::array()); + quadsInterface.back()[0] = tuples.begin(i)[0]; + quadsInterface.back()[1] = tuples.begin(i)[1]; + quadsInterface.back()[2] = tuples.begin(i)[2]; // [sz-2]; + quadsInterface.back()[3] = tuples.begin(i)[3]; // [sz-1]; + } + +#ifdef GPU_DEBUG + long long ave =0; int nn=0; for (auto k : ntk) if(k>0){ave+=k; ++nn;} + std::cout << "Q Produced " << quadsInterface.size() << " quadruplets: "; + for (auto i=3; i<7; ++i) std::cout << sizes[i] << ' '; + std::cout << "max/ave " << *std::max_element(ntk.begin(),ntk.end())<<'/'< + +#include "DataFormats/SiPixelDetId/interface/PixelSubdetector.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/Utilities/interface/EDGetToken.h" +#include "HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h" +#include "RecoLocalTracker/SiPixelClusterizer/interface/PixelTrackingGPUConstants.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/RZLine.h" +#include "RecoPixelVertexing/PixelTriplets/interface/OrderedHitSeeds.h" +#include "RecoPixelVertexing/PixelTriplets/plugins/RecHitsMap.h" +#include "RecoTracker/TkHitPairs/interface/HitPairGeneratorFromLayerPair.h" +#include "RecoTracker/TkHitPairs/interface/IntermediateHitDoublets.h" +#include "RecoTracker/TkHitPairs/interface/LayerHitMapCache.h" +#include "RecoTracker/TkMSParametrization/interface/LongitudinalBendingCorrection.h" +#include "RecoTracker/TkMSParametrization/interface/PixelRecoUtilities.h" +#include "RecoTracker/TkSeedGenerator/interface/FastCircleFit.h" +#include "RecoTracker/TkSeedingLayers/interface/SeedComparitor.h" +#include "RecoTracker/TkSeedingLayers/interface/SeedComparitorFactory.h" +#include "RecoPixelVertexing/PixelTriplets/plugins/RecHitsMap.h" + +#include "CAHitQuadrupletGeneratorKernels.h" +#include "RiemannFitOnGPU.h" + +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" + +// FIXME (split header???) +#include "GPUCACell.h" + +class TrackingRegion; + +namespace edm { + class Event; + class EventSetup; + class ParameterSetDescription; +} + +class CAHitQuadrupletGeneratorGPU { +public: + + using HitsOnGPU = siPixelRecHitsHeterogeneousProduct::HitsOnGPU; + using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; + using hindex_type = siPixelRecHitsHeterogeneousProduct::hindex_type; + + using TuplesOnGPU = pixelTuplesHeterogeneousProduct::TuplesOnGPU; + using TuplesOnCPU = pixelTuplesHeterogeneousProduct::TuplesOnCPU; + using Quality = pixelTuplesHeterogeneousProduct::Quality; + using Output = pixelTuplesHeterogeneousProduct::HeterogeneousPixelTuples; + + static constexpr unsigned int minLayers = 4; + using ResultType = OrderedHitSeeds; + +public: + + CAHitQuadrupletGeneratorGPU(const edm::ParameterSet& cfg, edm::ConsumesCollector&& iC): CAHitQuadrupletGeneratorGPU(cfg, iC) {} + CAHitQuadrupletGeneratorGPU(const edm::ParameterSet& cfg, edm::ConsumesCollector& iC); + + ~CAHitQuadrupletGeneratorGPU(); + + static void fillDescriptions(edm::ParameterSetDescription& desc); + static const char *fillDescriptionsLabel() { return "caHitQuadrupletGPU"; } + + void initEvent(const edm::Event& ev, const edm::EventSetup& es); + + void buildDoublets(HitsOnCPU const & hh, cudaStream_t stream); + + void hitNtuplets(HitsOnCPU const & hh, + const edm::EventSetup& es, + bool doRiemannFit, + bool transferToCPU, + cudaStream_t stream); + + TuplesOnCPU getOutput() const { + return TuplesOnCPU { std::move(indToEdm), hitsOnCPU->gpu_d, tuples_, helix_fit_results_, quality_, gpu_d, nTuples_}; + } + + void cleanup(cudaStream_t stream); + void fillResults(const TrackingRegion ®ion, SiPixelRecHitCollectionNew const & rechits, + std::vector& result, + const edm::EventSetup& es); + + void allocateOnGPU(); + void deallocateOnGPU(); + +private: + + void launchKernels(HitsOnCPU const & hh, bool doRiemannFit, bool transferToCPU, cudaStream_t); + + + std::vector> fetchKernelResult(int); + + + CAHitQuadrupletGeneratorKernels kernels; + RiemannFitOnGPU fitter; + + // not really used at the moment + const float caThetaCut = 0.00125f; + const float caPhiCut = 0.1f; + const float caHardPtCut = 0.f; + + + // products + std::vector indToEdm; // index of tuple in reco tracks.... + TuplesOnGPU * gpu_d = nullptr; // copy of the structure on the gpu itself: this is the "Product" + TuplesOnGPU::Container * tuples_ = nullptr; + Rfit::helix_fit * helix_fit_results_ = nullptr; + Quality * quality_ = nullptr; + uint32_t nTuples_ = 0; + TuplesOnGPU gpu_; + + // input + HitsOnCPU const * hitsOnCPU=nullptr; + + RecHitsMap hitmap_ = RecHitsMap(nullptr); + +}; + +#endif // RecoPixelVertexing_PixelTriplets_plugins_CAHitQuadrupletGeneratorGPU_h diff --git a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cc b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cc new file mode 100644 index 0000000000000..d00a5db18610c --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cc @@ -0,0 +1,44 @@ +#include "CAHitQuadrupletGeneratorKernels.h" + + +void +CAHitQuadrupletGeneratorKernels::deallocateOnGPU() +{ + + cudaFree(device_theCells_); + cudaFree(device_isOuterHitOfCell_); + cudaFree(device_nCells_); +// cudaFree(device_hitToTuple_); + cudaFree(device_hitToTuple_apc_); + +} + +void CAHitQuadrupletGeneratorKernels::allocateOnGPU() +{ + ////////////////////////////////////////////////////////// + // ALLOCATIONS FOR THE INTERMEDIATE RESULTS (STAYS ON WORKER) + ////////////////////////////////////////////////////////// + + cudaCheck(cudaMalloc(&device_theCells_, + CAConstants::maxNumberOfLayerPairs() * CAConstants::maxNumberOfDoublets() * sizeof(GPUCACell))); + cudaCheck(cudaMalloc(&device_nCells_, sizeof(uint32_t))); + cudaCheck(cudaMemset(device_nCells_, 0, sizeof(uint32_t))); + + cudaCheck(cudaMalloc(&device_isOuterHitOfCell_, + PixelGPUConstants::maxNumberOfHits * sizeof(CAConstants::OuterHitOfCell))); + cudaCheck(cudaMemset(device_isOuterHitOfCell_, 0, + PixelGPUConstants::maxNumberOfHits * sizeof(CAConstants::OuterHitOfCell))); + +// cudaCheck(cudaMalloc(&device_hitToTuple_, sizeof(HitToTuple))); + cudaCheck(cudaMalloc(&device_hitToTuple_apc_, sizeof(AtomicPairCounter))); + +} + +void CAHitQuadrupletGeneratorKernels::cleanup(cudaStream_t cudaStream) { + // this lazily resets temporary memory for the next event, and is not needed for reading the output + cudaCheck(cudaMemsetAsync(device_isOuterHitOfCell_, 0, + PixelGPUConstants::maxNumberOfHits * sizeof(CAConstants::OuterHitOfCell), + cudaStream)); + cudaCheck(cudaMemsetAsync(device_nCells_, 0, sizeof(uint32_t), cudaStream)); +} + diff --git a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cu b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cu new file mode 100644 index 0000000000000..85dc10ee04587 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cu @@ -0,0 +1,333 @@ +// +// Author: Felice Pantaleo, CERN +// + +#include "CAHitQuadrupletGeneratorKernels.h" +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h" +#include "GPUCACell.h" +#include "gpuPixelDoublets.h" +#include "gpuFishbone.h" +#include "CAConstants.h" + +using namespace gpuPixelDoublets; + +using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; +using TuplesOnGPU = pixelTuplesHeterogeneousProduct::TuplesOnGPU; +using Quality = pixelTuplesHeterogeneousProduct::Quality; + + + +__global__ +void kernel_checkOverflows(TuplesOnGPU::Container * foundNtuplets, AtomicPairCounter * apc, + GPUCACell const * __restrict__ cells, uint32_t const * __restrict__ nCells, + GPUCACell::OuterHitOfCell const * __restrict__ isOuterHitOfCell, + uint32_t nHits) { + + __shared__ uint32_t killedCell; + killedCell=0; + __syncthreads(); + + auto idx = threadIdx.x + blockIdx.x * blockDim.x; + #ifdef GPU_DEBUG + if (0==idx) { + printf("number of found cells %d, found tuples %d with total hits %d,%d\n",*nCells, apc->get().m, foundNtuplets->size(), apc->get().n); + assert(foundNtuplets->size(apc->get().m)==0); + assert(foundNtuplets->size()==apc->get().n); + } + + if(idxnbins()) { + if (foundNtuplets->size(idx)>5) printf("ERROR %d, %d\n", idx, foundNtuplets->size(idx)); + assert(foundNtuplets->size(idx)<6); + for (auto ih = foundNtuplets->begin(idx); ih!=foundNtuplets->end(idx); ++ih) assert(*ih=CAConstants::maxNumberOfDoublets()) printf("Cells overflow\n"); + } + + if (idx < (*nCells) ) { + auto &thisCell = cells[idx]; + if (thisCell.theOuterNeighbors.full()) //++tooManyNeighbors[thisCell.theLayerPairId]; + printf("OuterNeighbors overflow %d in %d\n", idx, thisCell.theLayerPairId); + if (thisCell.theTracks.full()) //++tooManyTracks[thisCell.theLayerPairId]; + printf("Tracks overflow %d in %d\n", idx, thisCell.theLayerPairId); + if (thisCell.theDoubletId<0) atomicAdd(&killedCell,1); + } + if (idx < nHits) { + if (isOuterHitOfCell[idx].full()) // ++tooManyOuterHitOfCell; + printf("OuterHitOfCell overflow %d\n", idx); + } + + __syncthreads(); +// if (threadIdx.x==0) printf("number of killed cells %d\n",killedCell); +} + + +__global__ +void +kernel_fishboneCleaner(GPUCACell const * cells, uint32_t const * __restrict__ nCells, + pixelTuplesHeterogeneousProduct::Quality * quality + ) { + + constexpr auto bad = pixelTuplesHeterogeneousProduct::bad; + + auto cellIndex = threadIdx.x + blockIdx.x * blockDim.x; + + if (cellIndex >= (*nCells) ) return; + auto const & thisCell = cells[cellIndex]; + if (thisCell.theDoubletId>=0) return; + + for (auto it : thisCell.theTracks) quality[it] = bad; + +} + +__global__ +void +kernel_fastDuplicateRemover(GPUCACell const * cells, uint32_t const * __restrict__ nCells, + TuplesOnGPU::Container * foundNtuplets, + Rfit::helix_fit const * __restrict__ hfit, + pixelTuplesHeterogeneousProduct::Quality * quality + ) { + + constexpr auto bad = pixelTuplesHeterogeneousProduct::bad; + constexpr auto dup = pixelTuplesHeterogeneousProduct::dup; + // constexpr auto loose = pixelTuplesHeterogeneousProduct::loose; + + auto cellIndex = threadIdx.x + blockIdx.x * blockDim.x; + + if (cellIndex >= (*nCells) ) return; + auto const & thisCell = cells[cellIndex]; + if (thisCell.theDoubletId<0) return; + + float mc=1000.f; uint16_t im=60000; uint32_t maxNh=0; + + // find maxNh + for (auto it : thisCell.theTracks) { + if (quality[it] == bad) continue; + auto nh = foundNtuplets->size(it); + maxNh = std::max(nh,maxNh); + } + // find min chi2 + for (auto it : thisCell.theTracks) { + auto nh = foundNtuplets->size(it); + if (nh!=maxNh) continue; + if (quality[it]!= bad && + hfit[it].chi2_line+hfit[it].chi2_circle < mc) { + mc=hfit[it].chi2_line+hfit[it].chi2_circle; + im=it; + } + } + // mark duplicates + for (auto it : thisCell.theTracks) { + if (quality[it]!= bad && it!=im) quality[it] = dup; //no race: simple assignment of the same constant + } +} + +__global__ +void +kernel_connect(AtomicPairCounter * apc1, AtomicPairCounter * apc2, // just to zero them, + GPUCACell::Hits const * __restrict__ hhp, + GPUCACell * cells, uint32_t const * __restrict__ nCells, + GPUCACell::OuterHitOfCell const * __restrict__ isOuterHitOfCell) { + + auto const & hh = *hhp; + + // 87 cm/GeV = 1/(3.8T * 0.3) + // take less than radius given by the hardPtCut and reject everything below + // auto hardCurvCut = 1.f/(hardPtCut * 87.f); + constexpr auto hardCurvCut = 1.f/(0.35f * 87.f); // FIXME VI tune + constexpr auto ptmin = 0.9f; // FIXME original "tune" + + auto cellIndex = threadIdx.x + blockIdx.x * blockDim.x; + + if (0==cellIndex) { (*apc1)=0; (*apc2)=0; }// ready for next kernel + + if (cellIndex >= (*nCells) ) return; + auto const & thisCell = cells[cellIndex]; + if (thisCell.theDoubletId<0) return; + auto innerHitId = thisCell.get_inner_hit_id(); + auto numberOfPossibleNeighbors = isOuterHitOfCell[innerHitId].size(); + auto vi = isOuterHitOfCell[innerHitId].data(); + for (auto j = 0; j < numberOfPossibleNeighbors; ++j) { + auto otherCell = __ldg(vi+j); + if (cells[otherCell].theDoubletId<0) continue; + if (thisCell.check_alignment(hh, + cells[otherCell], ptmin, hardCurvCut) + ) { + cells[otherCell].theOuterNeighbors.push_back(cellIndex); + } + } +} + +__global__ +void kernel_find_ntuplets( + GPUCACell * __restrict__ cells, uint32_t const * nCells, + TuplesOnGPU::Container * foundNtuplets, AtomicPairCounter * apc, + unsigned int minHitsPerNtuplet) +{ + + auto cellIndex = threadIdx.x + blockIdx.x * blockDim.x; + if (cellIndex >= (*nCells) ) return; + auto &thisCell = cells[cellIndex]; + if (thisCell.theLayerPairId!=0 && thisCell.theLayerPairId!=3 && thisCell.theLayerPairId!=8) return; // inner layer is 0 FIXME + GPUCACell::TmpTuple stack; + stack.reset(); + thisCell.find_ntuplets(cells, *foundNtuplets, *apc, stack, minHitsPerNtuplet); + assert(stack.size()==0); + // printf("in %d found quadruplets: %d\n", cellIndex, apc->get()); +} + + +__global__ +void kernel_VerifyFit(TuplesOnGPU::Container const * __restrict__ tuples, + Rfit::helix_fit const * __restrict__ fit_results, + Quality * __restrict__ quality) { + + auto idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx>= tuples->nbins()) return; + if (tuples->size(idx)==0) { + return; + } + + quality[idx] = pixelTuplesHeterogeneousProduct::bad; + + // only quadruplets + if (tuples->size(idx)<4) { + return; + } + + bool isNaN = false; + for (int i=0; i<5; ++i) { + isNaN |= fit_results[idx].par(i)!=fit_results[idx].par(i); + } + isNaN |= !(fit_results[idx].chi2_line+fit_results[idx].chi2_circle < 100.f); // catch NaN as well + +#ifdef GPU_DEBUG + if (isNaN) printf("NaN or Bad Fit %d %f/%f\n",idx,fit_results[idx].chi2_line,fit_results[idx].chi2_circle); +#endif + + // impose "region cuts" (NaN safe) + // phi,Tip,pt,cotan(theta)),Zip + bool ok = std::abs(fit_results[idx].par(1)) < 0.1f + && fit_results[idx].par(2) > 0.3f + && std::abs(fit_results[idx].par(4)) < 12.f; + ok &= (!isNaN); + quality[idx] = ok ? pixelTuplesHeterogeneousProduct::loose : pixelTuplesHeterogeneousProduct::bad; +} + +__global__ +void kernel_print_found_ntuplets(TuplesOnGPU::Container * foundNtuplets, uint32_t maxPrint) { + for (int i = 0; i < std::min(maxPrint, foundNtuplets->size()); ++i) { + printf("\nquadruplet %d: %d %d %d %d\n", i, + (*(*foundNtuplets).begin(i)), + (*(*foundNtuplets).begin(i)+1), + (*(*foundNtuplets).begin(i)+2), + (*(*foundNtuplets).begin(i)+3) + ); + } +} + +void CAHitQuadrupletGeneratorKernels::launchKernels( // here goes algoparms.... + HitsOnCPU const & hh, + TuplesOnGPU & tuples_d, + cudaStream_t cudaStream) +{ + auto & gpu_ = tuples_d; + auto maxNumberOfDoublets_ = CAConstants::maxNumberOfDoublets(); + + + auto nhits = hh.nHits; + assert(nhits <= PixelGPUConstants::maxNumberOfHits); + + if (earlyFishbone_) { + auto blockSize = 128; + auto stride = 4; + auto numberOfBlocks = (nhits + blockSize - 1)/blockSize; + numberOfBlocks *=stride; + + fishbone<<>>( + hh.gpu_d, + device_theCells_, device_nCells_, + device_isOuterHitOfCell_, + nhits, stride, false + ); + cudaCheck(cudaGetLastError()); + } + + auto blockSize = 64; + auto numberOfBlocks = (maxNumberOfDoublets_ + blockSize - 1)/blockSize; + kernel_connect<<>>( + gpu_.apc_d, device_hitToTuple_apc_, // needed only to be reset, ready for next kernel + hh.gpu_d, + device_theCells_, device_nCells_, + device_isOuterHitOfCell_ + ); + cudaCheck(cudaGetLastError()); + + kernel_find_ntuplets<<>>( + device_theCells_, device_nCells_, + gpu_.tuples_d, + gpu_.apc_d, + 4 + ); + cudaCheck(cudaGetLastError()); + + numberOfBlocks = (TuplesOnGPU::Container::totbins() + blockSize - 1)/blockSize; + cudautils::finalizeBulk<<>>(gpu_.apc_d,gpu_.tuples_d); + + if (lateFishbone_) { + auto stride=4; + numberOfBlocks = (nhits + blockSize - 1)/blockSize; + numberOfBlocks *=stride; + fishbone<<>>( + hh.gpu_d, + device_theCells_, device_nCells_, + device_isOuterHitOfCell_, + nhits, stride, true + ); + cudaCheck(cudaGetLastError()); + } + +#ifndef NO_CHECK_OVERFLOWS + numberOfBlocks = (std::max(nhits, maxNumberOfDoublets_) + blockSize - 1)/blockSize; + kernel_checkOverflows<<>>( + gpu_.tuples_d, gpu_.apc_d, + device_theCells_, device_nCells_, + device_isOuterHitOfCell_, nhits + ); + cudaCheck(cudaGetLastError()); +#endif + + + // kernel_print_found_ntuplets<<<1, 1, 0, cudaStream>>>(gpu_.tuples_d, 10); + } + + +void CAHitQuadrupletGeneratorKernels::buildDoublets(HitsOnCPU const & hh, cudaStream_t stream) { + auto nhits = hh.nHits; + + int threadsPerBlock = gpuPixelDoublets::getDoubletsFromHistoMaxBlockSize; + int blocks = (3 * nhits + threadsPerBlock - 1) / threadsPerBlock; + gpuPixelDoublets::getDoubletsFromHisto<<>>(device_theCells_, device_nCells_, hh.gpu_d, device_isOuterHitOfCell_); + cudaCheck(cudaGetLastError()); +} + +void CAHitQuadrupletGeneratorKernels::classifyTuples(HitsOnCPU const & hh, TuplesOnGPU & tuples, cudaStream_t cudaStream) { + auto blockSize = 64; + auto numberOfBlocks = (CAConstants::maxNumberOfQuadruplets() + blockSize - 1)/blockSize; + kernel_VerifyFit<<>>(tuples.tuples_d, tuples.helix_fit_results_d, tuples.quality_d); + + numberOfBlocks = (CAConstants::maxNumberOfDoublets() + blockSize - 1)/blockSize; + kernel_fishboneCleaner<<>>(device_theCells_, device_nCells_,tuples.quality_d); + + numberOfBlocks = (CAConstants::maxNumberOfDoublets() + blockSize - 1)/blockSize; + kernel_fastDuplicateRemover<<>>(device_theCells_, device_nCells_,tuples.tuples_d,tuples.helix_fit_results_d, tuples.quality_d); + +} + diff --git a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.h b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.h new file mode 100644 index 0000000000000..97b5617a54f89 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.h @@ -0,0 +1,51 @@ +#ifndef RecoPixelVertexing_PixelTriplets_plugins_CAHitQuadrupletGeneratorKernels_h +#define RecoPixelVertexing_PixelTriplets_plugins_CAHitQuadrupletGeneratorKernels_h + + +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" + +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" + +#include "GPUCACell.h" + +class CAHitQuadrupletGeneratorKernels { +public: + + using HitsOnGPU = siPixelRecHitsHeterogeneousProduct::HitsOnGPU; + using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; + + using TuplesOnGPU = pixelTuplesHeterogeneousProduct::TuplesOnGPU; + + using HitToTuple = CAConstants::HitToTuple; + + CAHitQuadrupletGeneratorKernels(bool earlyFishbone, bool lateFishbone) : + earlyFishbone_(earlyFishbone), + lateFishbone_(lateFishbone){} + ~CAHitQuadrupletGeneratorKernels() { deallocateOnGPU();} + + void launchKernels(HitsOnCPU const & hh, TuplesOnGPU & tuples_d, cudaStream_t cudaStream); + + void classifyTuples(HitsOnCPU const & hh, TuplesOnGPU & tuples_d, cudaStream_t cudaStream); + + void buildDoublets(HitsOnCPU const & hh, cudaStream_t stream); + void allocateOnGPU(); + void deallocateOnGPU(); + void cleanup(cudaStream_t cudaStream); + +private: + + // workspace + GPUCACell* device_theCells_ = nullptr; + GPUCACell::OuterHitOfCell* device_isOuterHitOfCell_ = nullptr; + uint32_t* device_nCells_ = nullptr; + + HitToTuple * device_hitToTuple_ = nullptr; + AtomicPairCounter * device_hitToTuple_apc_ = nullptr; + + + const bool earlyFishbone_; + const bool lateFishbone_; + +}; + +#endif // RecoPixelVertexing_PixelTriplets_plugins_CAHitQuadrupletGeneratorKernels_h diff --git a/RecoPixelVertexing/PixelTriplets/plugins/GPUCACell.h b/RecoPixelVertexing/PixelTriplets/plugins/GPUCACell.h new file mode 100644 index 0000000000000..dbd4eecbaab3c --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/GPUCACell.h @@ -0,0 +1,196 @@ +// +// Author: Felice Pantaleo, CERN +// +#ifndef RecoPixelVertexing_PixelTriplets_plugins_GPUCACell_h +#define RecoPixelVertexing_PixelTriplets_plugins_GPUCACell_h + +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h" +#include "HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" +#include "RecoPixelVertexing/PixelTriplets/interface/CircleEq.h" + + +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" + +class GPUCACell { +public: + + static constexpr int maxCellsPerHit = 128; // was 256 + using OuterHitOfCell = GPU::VecArray< unsigned int, maxCellsPerHit>; + + + using Hits = siPixelRecHitsHeterogeneousProduct::HitsOnGPU; + using hindex_type = siPixelRecHitsHeterogeneousProduct::hindex_type; + + using TmpTuple = GPU::VecArray; + + using TuplesOnGPU = pixelTuplesHeterogeneousProduct::TuplesOnGPU; + + GPUCACell() = default; +#ifdef __CUDACC__ + + __device__ __forceinline__ + void init(Hits const & hh, + int layerPairId, int doubletId, + hindex_type innerHitId, hindex_type outerHitId) + { + theInnerHitId = innerHitId; + theOuterHitId = outerHitId; + theDoubletId = doubletId; + theLayerPairId = layerPairId; + + theInnerZ = __ldg(hh.zg_d+innerHitId); + theInnerR = __ldg(hh.rg_d+innerHitId); + theOuterNeighbors.reset(); + theTracks.reset(); + } + + __device__ __forceinline__ float get_inner_x(Hits const & hh) const { return __ldg(hh.xg_d+theInnerHitId); } + __device__ __forceinline__ float get_outer_x(Hits const & hh) const { return __ldg(hh.xg_d+theOuterHitId); } + __device__ __forceinline__ float get_inner_y(Hits const & hh) const { return __ldg(hh.yg_d+theInnerHitId); } + __device__ __forceinline__ float get_outer_y(Hits const & hh) const { return __ldg(hh.yg_d+theOuterHitId); } + __device__ __forceinline__ float get_inner_z(Hits const & hh) const { return theInnerZ; } // { return __ldg(hh.zg_d+theInnerHitId); } // { return theInnerZ; } + __device__ __forceinline__ float get_outer_z(Hits const & hh) const { return __ldg(hh.zg_d+theOuterHitId); } + __device__ __forceinline__ float get_inner_r(Hits const & hh) const { return theInnerR; } // { return __ldg(hh.rg_d+theInnerHitId); } // { return theInnerR; } + __device__ __forceinline__ float get_outer_r(Hits const & hh) const { return __ldg(hh.rg_d+theOuterHitId); } + + __device__ __forceinline__ float get_inner_detId(Hits const & hh) const { return __ldg(hh.detInd_d+theInnerHitId); } + __device__ __forceinline__ float get_outer_detId(Hits const & hh) const { return __ldg(hh.detInd_d+theOuterHitId); } + + constexpr unsigned int get_inner_hit_id() const { + return theInnerHitId; + } + constexpr unsigned int get_outer_hit_id() const { + return theOuterHitId; + } + + + __device__ + void print_cell() const { + printf("printing cell: %d, on layerPair: %d, innerHitId: %d, outerHitId: " + "%d, innerradius %f, outerRadius %f \n", + theDoubletId, theLayerPairId, theInnerHitId, theOuterHitId + ); + } + + + __device__ + bool check_alignment(Hits const & hh, + GPUCACell const & otherCell, + const float ptmin, + const float hardCurvCut) const + { + auto ri = get_inner_r(hh); + auto zi = get_inner_z(hh); + + auto ro = get_outer_r(hh); + auto zo = get_outer_z(hh); + + auto r1 = otherCell.get_inner_r(hh); + auto z1 = otherCell.get_inner_z(hh); + bool aligned = areAlignedRZ(r1, z1, ri, zi, ro, zo, ptmin, 0.003f); // 2.f*thetaCut); // FIXME tune cuts + return (aligned && dcaCut(hh, otherCell, otherCell.get_inner_detId(hh)<96 ? 0.15f : 0.25f, hardCurvCut)); // FIXME tune cuts + // region_origin_radius_plus_tolerance, hardCurvCut)); + } + + __device__ __forceinline__ + static bool areAlignedRZ(float r1, float z1, float ri, float zi, float ro, float zo, + const float ptmin, + const float thetaCut) { + float radius_diff = std::abs(r1 - ro); + float distance_13_squared = + radius_diff * radius_diff + (z1 - zo) * (z1 - zo); + + float pMin = + ptmin * std::sqrt(distance_13_squared); // this needs to be divided by + // radius_diff later + + float tan_12_13_half_mul_distance_13_squared = + fabs(z1 * (ri - ro) + zi * (ro - r1) + zo * (r1 - ri)); + return tan_12_13_half_mul_distance_13_squared * pMin <= thetaCut * distance_13_squared * radius_diff; + } + + + __device__ + bool + dcaCut(Hits const & hh, GPUCACell const & otherCell, + const float region_origin_radius_plus_tolerance, + const float maxCurv) const { + + auto x1 = otherCell.get_inner_x(hh); + auto y1 = otherCell.get_inner_y(hh); + + auto x2 = get_inner_x(hh); + auto y2 = get_inner_y(hh); + + auto x3 = get_outer_x(hh); + auto y3 = get_outer_y(hh); + + CircleEq eq(x1,y1,x2,y2,x3,y3); + + if (eq.curvature() > maxCurv) return false; + + return std::abs(eq.dca0()) < region_origin_radius_plus_tolerance*std::abs(eq.curvature()); + + } + + // trying to free the track building process from hardcoded layers, leaving + // the visit of the graph based on the neighborhood connections between cells. + +// #ifdef __CUDACC__ + + __device__ + inline void find_ntuplets( + GPUCACell * __restrict__ cells, + TuplesOnGPU::Container & foundNtuplets, + AtomicPairCounter & apc, + TmpTuple & tmpNtuplet, + const unsigned int minHitsPerNtuplet) const + { + // the building process for a track ends if: + // it has no right neighbor + // it has no compatible neighbor + // the ntuplets is then saved if the number of hits it contains is greater + // than a threshold + + tmpNtuplet.push_back_unsafe(theDoubletId); + assert(tmpNtuplet.size()<=4); + + if(theOuterNeighbors.size()>0) { // continue + for (int j = 0; j < theOuterNeighbors.size(); ++j) { + auto otherCell = theOuterNeighbors[j]; + cells[otherCell].find_ntuplets(cells, foundNtuplets, apc, tmpNtuplet, + minHitsPerNtuplet); + } + } else { // if long enough save... + if ((unsigned int)(tmpNtuplet.size()) >= minHitsPerNtuplet-1) { + hindex_type hits[6]; auto nh=0U; + for (auto c : tmpNtuplet) hits[nh++] = cells[c].theInnerHitId; + hits[nh] = theOuterHitId; + uint16_t it = foundNtuplets.bulkFill(apc,hits,tmpNtuplet.size()+1); + for (auto c : tmpNtuplet) cells[c].theTracks.push_back(it); + } + } + tmpNtuplet.pop_back(); + assert(tmpNtuplet.size() < 4); + } + +#endif // __CUDACC__ + + GPU::VecArray< uint32_t, 36> theOuterNeighbors; + GPU::VecArray< uint16_t, 42> theTracks; + + int32_t theDoubletId; + int32_t theLayerPairId; + +private: + float theInnerZ; + float theInnerR; + hindex_type theInnerHitId; + hindex_type theOuterHitId; +}; + +#endif // RecoPixelVertexing_PixelTriplets_plugins_GPUCACell_h diff --git a/RecoPixelVertexing/PixelTriplets/plugins/RecHitsMap.h b/RecoPixelVertexing/PixelTriplets/plugins/RecHitsMap.h new file mode 100644 index 0000000000000..d27a639a5a9bf --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/RecHitsMap.h @@ -0,0 +1,76 @@ +//FIXME move it to a better place... + +#ifndef RecoPixelVertexing_PixelTriplets_plugins_RecHitsMap_h +#define RecoPixelVertexing_PixelTriplets_plugins_RecHitsMap_h + + +#include +#include + +#include "DataFormats/TrackerRecHit2D/interface/BaseTrackerRecHit.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" + +// store T for each cluster +template +class RecHitsMap { +public: + explicit RecHitsMap(T const & d=T()) : dummy(d){} + + void clear() {m_map.clear();} + + void error(const GeomDetUnit& gd) const {edm::LogError("RecHitMap") << "hit not found in det " << gd.index(); } + void error(uint32_t ind) const {edm::LogError("RecHitMap") << "hit not found in det " << ind; } + + // does not work for matched hits... (easy to extend) + void add(TrackingRecHit const & hit, T const & v) { + auto const & thit = static_cast(hit); + auto const & clus = thit.firstClusterRef(); + + if (clus.isPixel()) + add(clus.pixelCluster(), *thit.detUnit(),v); + else + add(clus.stripCluster(), *thit.detUnit(),v); + } + + template + void add(const Cluster& cluster, const GeomDetUnit& gd, T const & v) { m_map[encode(cluster,gd)] = v; } + + template + T const & get(const Cluster& cluster, const GeomDetUnit& gd) const { + auto p = m_map.find(encode(cluster,gd)); + if (p!=m_map.end()) { return (*p).second; } + error(gd); + return dummy; + } + + T const & get(uint32_t ind, uint16_t mr, uint16_t mc) const { + auto p = m_map.find(encode(ind,mr,mc)); + if (p!=m_map.end()) { return (*p).second; } + error(ind); + return dummy; + } + + static uint64_t encode(uint32_t ind, uint16_t mr, uint16_t mc) { + uint64_t u1 = ind; + uint64_t u2 = mr; + uint64_t u3 = mc; + return (u1<<32) | (u2<<16) | u3; + } + + static uint64_t encode(const SiPixelCluster& cluster, const GeomDetUnit& det) { + uint64_t u1 = det.index(); + uint64_t u2 = cluster.minPixelRow(); + uint64_t u3 = cluster.minPixelCol(); + return (u1<<32) | (u2<<16) | u3; + } + static uint64_t encode(const SiStripCluster& cluster, const GeomDetUnit& det) { + uint64_t u1 = det.index(); + uint64_t u2 = cluster.firstStrip(); + return (u1<<32) | u2; + } + + std::unordered_map m_map; + T dummy; + }; + +#endif // RecoPixelVertexing_PixelTriplets_plugins_RecHitsMap_h diff --git a/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cc b/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cc new file mode 100644 index 0000000000000..fe95e10a48b5a --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cc @@ -0,0 +1,35 @@ +#include "RiemannFitOnGPU.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" + +void RiemannFitOnGPU::allocateOnGPU(TuplesOnGPU::Container const * tuples, Rfit::helix_fit * helix_fit_results) { + + tuples_d = tuples; + helix_fit_results_d = helix_fit_results; + + assert(tuples_d); assert(helix_fit_results_d); + + cudaCheck(cudaMalloc(&hitsGPU_, maxNumberOfConcurrentFits_ * sizeof(Rfit::Matrix3xNd<4>))); + cudaCheck(cudaMemset(hitsGPU_, 0x00, maxNumberOfConcurrentFits_ * sizeof(Rfit::Matrix3xNd<4>))); + + cudaCheck(cudaMalloc(&hits_geGPU_, maxNumberOfConcurrentFits_ * sizeof(Rfit::Matrix6x4f))); + cudaCheck(cudaMemset(hits_geGPU_, 0x00, maxNumberOfConcurrentFits_ * sizeof(Rfit::Matrix6x4f))); + + cudaCheck(cudaMalloc(&fast_fit_resultsGPU_, maxNumberOfConcurrentFits_ * sizeof(Rfit::Vector4d))); + cudaCheck(cudaMemset(fast_fit_resultsGPU_, 0x00, maxNumberOfConcurrentFits_ * sizeof(Rfit::Vector4d))); + + cudaCheck(cudaMalloc(&circle_fit_resultsGPU_, maxNumberOfConcurrentFits_ * sizeof(Rfit::circle_fit))); + cudaCheck(cudaMemset(circle_fit_resultsGPU_, 0x00, maxNumberOfConcurrentFits_ * sizeof(Rfit::circle_fit))); + +} + +void RiemannFitOnGPU::deallocateOnGPU() { + + cudaFree(hitsGPU_); + cudaFree(hits_geGPU_); + cudaFree(fast_fit_resultsGPU_); + cudaFree(circle_fit_resultsGPU_); + +} + + + diff --git a/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cu b/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cu new file mode 100644 index 0000000000000..1bcfb847d2ae8 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.cu @@ -0,0 +1,195 @@ +// +// Author: Felice Pantaleo, CERN +// + +#include "RiemannFitOnGPU.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" + +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" + + +using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; + +using HitsOnGPU = siPixelRecHitsHeterogeneousProduct::HitsOnGPU; +using TuplesOnGPU = pixelTuplesHeterogeneousProduct::TuplesOnGPU; + +using namespace Eigen; + +__global__ +void kernelFastFitAllHits(TuplesOnGPU::Container const * __restrict__ foundNtuplets, + HitsOnGPU const * __restrict__ hhp, + int hits_in_fit, + double * __restrict__ phits, + float * __restrict__ phits_ge, + double * __restrict__ pfast_fit, + uint32_t offset) +{ + + assert(hits_in_fit==4); // FixMe later template + + assert(pfast_fit); assert(foundNtuplets); + + auto local_start = (blockIdx.x * blockDim.x + threadIdx.x); + auto helix_start = local_start + offset; + + if (helix_start>=foundNtuplets->nbins()) return; + if (foundNtuplets->size(helix_start)begin(helix_start); + for (unsigned int i = 0; i < hits_in_fit; ++i) { + auto hit = hitId[i]; + // printf("Hit global: %f,%f,%f\n", hhp->xg_d[hit],hhp->yg_d[hit],hhp->zg_d[hit]); + float ge[6]; + hhp->cpeParams->detParams(hhp->detInd_d[hit]).frame.toGlobal(hhp->xerr_d[hit], 0, hhp->yerr_d[hit], ge); + // printf("Error: %d: %f,%f,%f,%f,%f,%f\n",hhp->detInd_d[hit],ge[0],ge[1],ge[2],ge[3],ge[4],ge[5]); + + hits.col(i) << hhp->xg_d[hit], hhp->yg_d[hit], hhp->zg_d[hit]; + hits_ge.col(i) << ge[0],ge[1],ge[2],ge[3],ge[4],ge[5]; + } + Rfit::Fast_fit(hits,fast_fit); + + // no NaN here.... + assert(fast_fit(0)==fast_fit(0)); + assert(fast_fit(1)==fast_fit(1)); + assert(fast_fit(2)==fast_fit(2)); + assert(fast_fit(3)==fast_fit(3)); + +} + +__global__ +void kernelCircleFitAllHits(TuplesOnGPU::Container const * __restrict__ foundNtuplets, + int hits_in_fit, + double B, + double * __restrict__ phits, + float * __restrict__ phits_ge, + double * __restrict__ pfast_fit_input, + Rfit::circle_fit *circle_fit, + uint32_t offset) +{ + assert(circle_fit); + + auto local_start = (blockIdx.x * blockDim.x + threadIdx.x); + auto helix_start = local_start + offset; + + if (helix_start>=foundNtuplets->nbins()) return; + if (foundNtuplets->size(helix_start) rad = (hits.block(0, 0, 2, n).colwise().norm()); + + Rfit::Matrix2Nd hits_cov = Rfit::Matrix2Nd<4>::Zero(); + Rfit::loadCovariance2D(hits_ge,hits_cov); + + circle_fit[local_start] = + Rfit::Circle_fit(hits.block(0, 0, 2, n), + hits_cov, + fast_fit, rad, B, true); + +#ifdef GPU_DEBUG +// printf("kernelCircleFitAllHits circle.par(0,1,2): %d %f,%f,%f\n", helix_start, +// circle_fit[local_start].par(0), circle_fit[local_start].par(1), circle_fit[local_start].par(2)); +#endif +} + +__global__ +void kernelLineFitAllHits(TuplesOnGPU::Container const * __restrict__ foundNtuplets, + int hits_in_fit, + double B, + Rfit::helix_fit *results, + double * __restrict__ phits, + float * __restrict__ phits_ge, + double * __restrict__ pfast_fit, + Rfit::circle_fit * __restrict__ circle_fit, + uint32_t offset) +{ + + assert(results); assert(circle_fit); + + auto local_start = (blockIdx.x * blockDim.x + threadIdx.x); + auto helix_start = local_start + offset; + + if (helix_start>=foundNtuplets->nbins()) return; + if (foundNtuplets->size(helix_start)>>( + tuples_d, hh.gpu_d, 4, + hitsGPU_, hits_geGPU_, fast_fit_resultsGPU_,offset); + cudaCheck(cudaGetLastError()); + + kernelCircleFitAllHits<<>>( + tuples_d, 4, bField_, + hitsGPU_, hits_geGPU_, fast_fit_resultsGPU_, circle_fit_resultsGPU_, offset); + cudaCheck(cudaGetLastError()); + + + kernelLineFitAllHits<<>>( + tuples_d, 4, bField_, helix_fit_results_d, + hitsGPU_, hits_geGPU_, fast_fit_resultsGPU_, circle_fit_resultsGPU_, + offset); + cudaCheck(cudaGetLastError()); + } +} diff --git a/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.h b/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.h new file mode 100644 index 0000000000000..fac88ac2c2bd4 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/RiemannFitOnGPU.h @@ -0,0 +1,60 @@ +#ifndef RecoPixelVertexing_PixelTrackFitting_plugins_RiemannFitOnGPU_h +#define RecoPixelVertexing_PixelTrackFitting_plugins_RiemannFitOnGPU_h + +#include "RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h" +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" + +namespace siPixelRecHitsHeterogeneousProduct { + struct HitsOnCPU; +} + +namespace Rfit { + constexpr uint32_t maxNumberOfConcurrentFits() { return 2*1024;} + constexpr uint32_t stride() { return maxNumberOfConcurrentFits();} + using Matrix3x4d = Eigen::Matrix; + using Map3x4d = Eigen::Map >; + using Matrix6x4f = Eigen::Matrix; + using Map6x4f = Eigen::Map >; + using Map4d = Eigen::Map >; + +} + + +class RiemannFitOnGPU { +public: + + using HitsOnGPU = siPixelRecHitsHeterogeneousProduct::HitsOnGPU; + using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; + + using TuplesOnGPU = pixelTuplesHeterogeneousProduct::TuplesOnGPU; + + RiemannFitOnGPU() = default; + ~RiemannFitOnGPU() { deallocateOnGPU();} + + void setBField(double bField) { bField_ = bField;} + void launchKernels(HitsOnCPU const & hh, uint32_t nhits, uint32_t maxNumberOfTuples, cudaStream_t cudaStream); + + void allocateOnGPU(TuplesOnGPU::Container const * tuples, Rfit::helix_fit * helix_fit_results); + void deallocateOnGPU(); + + +private: + + static constexpr uint32_t maxNumberOfConcurrentFits_ = Rfit::maxNumberOfConcurrentFits(); + + // fowarded + TuplesOnGPU::Container const * tuples_d = nullptr; + double bField_; + Rfit::helix_fit * helix_fit_results_d = nullptr; + + + + // Riemann Fit internals + double *hitsGPU_ = nullptr; + float *hits_geGPU_ = nullptr; + double *fast_fit_resultsGPU_ = nullptr; + Rfit::circle_fit *circle_fit_resultsGPU_ = nullptr; + +}; + +#endif diff --git a/RecoPixelVertexing/PixelTriplets/plugins/gpuFishbone.h b/RecoPixelVertexing/PixelTriplets/plugins/gpuFishbone.h new file mode 100644 index 0000000000000..717cbf777fcdb --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/gpuFishbone.h @@ -0,0 +1,93 @@ +#ifndef RecoLocalTracker_SiPixelRecHits_plugins_gpuFishbone_h +#define RecoLocalTracker_SiPixelRecHits_plugins_gpuFishbone_h + +#include +#include +#include +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +#include "DataFormats/Math/interface/approx_atan2.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" +#include "Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h" + +#include "GPUCACell.h" +#include "HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h" + +namespace gpuPixelDoublets { + +// __device__ +// __forceinline__ + __global__ + void fishbone( + GPUCACell::Hits const * __restrict__ hhp, + GPUCACell * cells, uint32_t const * __restrict__ nCells, + GPUCACell::OuterHitOfCell const * __restrict__ isOuterHitOfCell, + uint32_t nHits, + uint32_t stride, bool checkTrack) { + + constexpr auto maxCellsPerHit = GPUCACell::maxCellsPerHit; + + + auto const & hh = *hhp; + uint8_t const * __restrict__ layerp = hh.phase1TopologyLayer_d; + auto layer = [&](uint16_t id) { return __ldg(layerp+id/phase1PixelTopology::maxModuleStride);}; + + auto ldx = threadIdx.x + blockIdx.x * blockDim.x; + auto idx = ldx/stride; + auto first = ldx - idx*stride; + assert(first=nHits) return; + auto const & vc = isOuterHitOfCell[idx]; + auto s = vc.size(); + if (s<2) return; + // if alligned kill one of the two. + auto const & c0 = cells[vc[0]]; + auto xo = c0.get_outer_x(hh); + auto yo = c0.get_outer_y(hh); + auto zo = c0.get_outer_z(hh); + float x[maxCellsPerHit], y[maxCellsPerHit],z[maxCellsPerHit], n[maxCellsPerHit]; + uint16_t d[maxCellsPerHit]; // uint8_t l[maxCellsPerHit]; + uint32_t cc[maxCellsPerHit]; + auto sg=0; + for (uint32_t ic=0; ic= 0.99999f*n[ic]*n[jc]) { + // alligned: kill farthest (prefer consecutive layers) + if (n[ic]>n[jc]) { + ci.theDoubletId=-1; + break; + } else { + cj.theDoubletId=-1; + } + } + } //cj + } // ci + } + +} + +#endif diff --git a/RecoPixelVertexing/PixelTriplets/plugins/gpuPixelDoublets.h b/RecoPixelVertexing/PixelTriplets/plugins/gpuPixelDoublets.h new file mode 100644 index 0000000000000..02a175fcc2903 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/gpuPixelDoublets.h @@ -0,0 +1,199 @@ +#ifndef RecoLocalTracker_SiPixelRecHits_plugins_gpuPixelDoublets_h +#define RecoLocalTracker_SiPixelRecHits_plugins_gpuPixelDouplets_h + +#include +#include +#include +#include +#include + +#include "DataFormats/Math/interface/approx_atan2.h" +#include "HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" + +#include "GPUCACell.h" +#include "CAConstants.h" + +namespace gpuPixelDoublets { + + constexpr uint32_t MaxNumOfDoublets = CAConstants::maxNumberOfDoublets(); // not really relevant + + template + __device__ + __forceinline__ + void doubletsFromHisto(uint8_t const * __restrict__ layerPairs, + uint32_t nPairs, + GPUCACell * cells, + uint32_t * nCells, + int16_t const * __restrict__ iphi, + Hist const & __restrict__ hist, + uint32_t const * __restrict__ offsets, + siPixelRecHitsHeterogeneousProduct::HitsOnGPU const & __restrict__ hh, + GPUCACell::OuterHitOfCell * isOuterHitOfCell, + int16_t const * __restrict__ phicuts, + float const * __restrict__ minz, + float const * __restrict__ maxz, + float const * __restrict__ maxr) + { + auto layerSize = [=](uint8_t li) { return offsets[li+1]-offsets[li]; }; + + // nPairsMax to be optimized later (originally was 64). + // If it should be much bigger, consider using a block-wide parallel prefix scan, + // e.g. see https://nvlabs.github.io/cub/classcub_1_1_warp_scan.html + const int nPairsMax = 16; + assert(nPairs <= nPairsMax); + uint32_t innerLayerCumulativeSize[nPairsMax]; + innerLayerCumulativeSize[0] = layerSize(layerPairs[0]); + for (uint32_t i = 1; i < nPairs; ++i) { + innerLayerCumulativeSize[i] = innerLayerCumulativeSize[i-1] + layerSize(layerPairs[2*i]); + } + auto ntot = innerLayerCumulativeSize[nPairs-1]; + + auto idx = blockIdx.x * blockDim.x + threadIdx.x; + for (auto j = idx; j < ntot; j += blockDim.x * gridDim.x) { + + uint32_t pairLayerId=0; + while (j >= innerLayerCumulativeSize[pairLayerId++]); + --pairLayerId; // move to lower_bound ?? + + assert(pairLayerId < nPairs); + assert(j < innerLayerCumulativeSize[pairLayerId]); + assert(0 == pairLayerId || j >= innerLayerCumulativeSize[pairLayerId-1]); + + uint8_t inner = layerPairs[2*pairLayerId]; + uint8_t outer = layerPairs[2*pairLayerId+1]; + assert(outer > inner); + + auto hoff = Hist::histOff(outer); + + auto i = (0 == pairLayerId) ? j : j-innerLayerCumulativeSize[pairLayerId-1]; + i += offsets[inner]; + + // printf("Hit in Layer %d %d %d %d\n", i, inner, pairLayerId, j); + + assert(i >= offsets[inner]); + assert(i < offsets[inner+1]); + + // found hit corresponding to our cuda thread, now do the job + auto mep = iphi[i]; + auto mez = __ldg(hh.zg_d+i); + auto mer = __ldg(hh.rg_d+i); + + constexpr float z0cut = 12.f; // cm + constexpr float hardPtCut = 0.5f; // GeV + constexpr float minRadius = hardPtCut * 87.78f; // cm (1 GeV track has 1 GeV/c / (e * 3.8T) ~ 87 cm radius in a 3.8T field) + constexpr float minRadius2T4 = 4.f*minRadius*minRadius; + auto ptcut = [&](int j) { + auto r2t4 = minRadius2T4; + auto ri = mer; + auto ro = __ldg(hh.rg_d+j); + auto dphi = short2phi( min( abs(int16_t(mep-iphi[j])), abs(int16_t(iphi[j]-mep)) ) ); + return dphi*dphi * (r2t4 - ri*ro) > (ro-ri)*(ro-ri); + }; + auto z0cutoff = [&](int j) { + auto zo = __ldg(hh.zg_d+j); + auto ro = __ldg(hh.rg_d+j); + auto dr = ro-mer; + return dr > maxr[pairLayerId] || + dr<0 || std::abs((mez*ro - mer*zo)) > z0cut*dr; + }; + + auto iphicut = phicuts[pairLayerId]; + + auto kl = Hist::bin(int16_t(mep-iphicut)); + auto kh = Hist::bin(int16_t(mep+iphicut)); + auto incr = [](auto & k) { return k = (k+1) % Hist::nbins();}; + int tot = 0; + int nmin = 0; + auto khh = kh; + incr(khh); + + int tooMany=0; + for (auto kk = kl; kk != khh; incr(kk)) { + if (kk != kl && kk != kh) + nmin += hist.size(kk+hoff); + auto const * __restrict__ p = hist.begin(kk+hoff); + auto const * __restrict__ e = hist.end(kk+hoff); + for (;p < e; ++p) { + auto oi=__ldg(p); + assert(oi>=offsets[outer]); + assert(oi iphicut) + continue; + if (z0cutoff(oi) || ptcut(oi)) continue; + auto ind = atomicAdd(nCells, 1); + if (ind>=MaxNumOfDoublets) {atomicSub(nCells, 1); break; } // move to SimpleVector?? + // int layerPairId, int doubletId, int innerHitId, int outerHitId) + cells[ind].init(hh, pairLayerId, ind, i, oi); + isOuterHitOfCell[oi].push_back(ind); + if (isOuterHitOfCell[oi].full()) ++tooMany; + ++tot; + } + } +#ifdef GPU_DEBUG + if (tooMany > 0) + printf("OuterHitOfCell full for %d in layer %d/%d, %d,%d %d\n", i, inner, outer, nmin, tot, tooMany); +#endif + } // loop in block... + } + + constexpr auto getDoubletsFromHistoMaxBlockSize = 64; + constexpr auto getDoubletsFromHistoMinBlocksPerMP = 16; + + __global__ + __launch_bounds__(getDoubletsFromHistoMaxBlockSize,getDoubletsFromHistoMinBlocksPerMP) + void getDoubletsFromHisto(GPUCACell * cells, + uint32_t * nCells, + siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * __restrict__ hhp, + GPUCACell::OuterHitOfCell * isOuterHitOfCell) + { + constexpr int nPairs = 13; + constexpr const uint8_t layerPairs[2*nPairs] = { + 0, 1, 1, 2, 2, 3, + // 0, 4, 1, 4, 2, 4, 4, 5, 5, 6, + 0, 7, 1, 7, 2, 7, 7, 8, 8, 9, + 0, 4, 1, 4, 2, 4, 4, 5, 5, 6 + }; + + constexpr int16_t phi0p05 = 522; // round(521.52189...) = phi2short(0.05); + constexpr int16_t phi0p06 = 626; // round(625.82270...) = phi2short(0.06); + constexpr int16_t phi0p07 = 730; // round(730.12648...) = phi2short(0.07); + + constexpr const int16_t phicuts[nPairs] { + phi0p05, phi0p05, phi0p06, + phi0p07, phi0p06, phi0p06, phi0p05, phi0p05, + phi0p07, phi0p06, phi0p06, phi0p05, phi0p05 + }; + + float const minz[nPairs] = { + 0., 0., 0., + 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0. + }; + + float const maxz[nPairs] = { + 20., 15., 12., + 30., 20., 20., 50., 50., + 30., 20., 20., 50., 50. + }; + + float const maxr[nPairs] = { + 20., 20., 20., + 9., 7., 6., 5., 5., + 9., 7., 6., 5., 5. + }; + + auto const & __restrict__ hh = *hhp; + doubletsFromHisto(layerPairs, nPairs, cells, nCells, + hh.iphi_d, *hh.hist_d, hh.hitsLayerStart_d, + hh, isOuterHitOfCell, + phicuts, minz, maxz, maxr); + } + + + +} // namespace end + +#endif // RecoLocalTracker_SiPixelRecHits_plugins_gpuPixelDouplets_h diff --git a/RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h b/RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h new file mode 100644 index 0000000000000..a1c4a26f2fff6 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h @@ -0,0 +1,65 @@ +#ifndef RecoPixelVertexingPixelTripletsPixelTuplesHeterogeneousProduct_H +#define RecoPixelVertexingPixelTripletsPixelTuplesHeterogeneousProduct_H + + +#include +#include + +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" +#include "RecoPixelVertexing/PixelTrackFitting/interface/FitResult.h" + +#include "HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h" + +#include "RecoPixelVertexing/PixelTriplets/plugins/CAConstants.h" + +namespace siPixelRecHitsHeterogeneousProduct { + struct HitsOnGPU; +} + +namespace pixelTuplesHeterogeneousProduct { + + enum Quality: uint8_t { bad, dup, loose, strict, tight, highPurity }; + + using CPUProduct = int; // dummy + + struct TuplesOnGPU { + using Container = CAConstants::TuplesContainer; + + Container * tuples_d; + AtomicPairCounter * apc_d; + + Rfit::helix_fit * helix_fit_results_d = nullptr; + Quality * quality_d = nullptr; + + TuplesOnGPU const * me_d = nullptr; + + }; + + struct TuplesOnCPU { + + std::vector indToEdm; // index of tuple in reco tracks.... + + + using Container = TuplesOnGPU::Container; + + siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * hitsOnGPU_d = nullptr; // forwarding + + Container const * tuples = nullptr; + + Rfit::helix_fit const * helix_fit_results = nullptr; + Quality * quality = nullptr; + + TuplesOnGPU const * gpu_d = nullptr; + uint32_t nTuples; + }; + + using GPUProduct = TuplesOnCPU; // FIXME fill cpu vectors on demand + + using HeterogeneousPixelTuples = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct >; +} + + + +#endif + diff --git a/RecoPixelVertexing/PixelTriplets/python/caHitQuadrupletEDProducer_cfi.py b/RecoPixelVertexing/PixelTriplets/python/caHitQuadrupletEDProducer_cfi.py new file mode 100644 index 0000000000000..c72c07ae5a721 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/python/caHitQuadrupletEDProducer_cfi.py @@ -0,0 +1,4 @@ +import FWCore.ParameterSet.Config as cms +from RecoPixelVertexing.PixelTriplets.caHitQuadrupletDefaultEDProducer_cfi import caHitQuadrupletDefaultEDProducer as _caHitQuadrupletDefaultEDProducer + +caHitQuadrupletEDProducer = _caHitQuadrupletDefaultEDProducer.clone() diff --git a/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml b/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml index 54d7eaccd1760..9f5d10ad020e9 100644 --- a/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml +++ b/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml @@ -18,4 +18,7 @@ - \ No newline at end of file + + + + diff --git a/RecoPixelVertexing/PixelTriplets/test/CircleEq_t.cpp b/RecoPixelVertexing/PixelTriplets/test/CircleEq_t.cpp new file mode 100644 index 0000000000000..cbbcea96d1ee8 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/test/CircleEq_t.cpp @@ -0,0 +1,99 @@ +#include "RecoPixelVertexing/PixelTriplets/interface/CircleEq.h" +#include + + +struct OriCircle { + + using T = float; + + float radius=0; + float x_center=0; + float y_center=0; + + + constexpr OriCircle(T x1, T y1, + T x2, T y2, + T x3, T y3) { + compute(x1,y1,x2,y2,x3,y3); + } + + // dca to origin + constexpr T dca0() const { + return std::sqrt(x_center*x_center + y_center*y_center) - radius; + } + + // dca to given point + constexpr T dca(T x, T y) const { + x-=x_center; + y-=y_center; + return std::sqrt(x*x+y*y)-radius; + } + + + constexpr void compute(T x1, T y1, + T x2, T y2, + T x3, T y3) { + + auto det = (x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2); + + auto offset = x2 * x2 + y2 * y2; + + auto bc = (x1 * x1 + y1 * y1 - offset) * 0.5f; + + auto cd = (offset - x3 * x3 - y3 * y3) * 0.5f; + + auto idet = 1.f / det; + + x_center = (bc * (y2 - y3) - cd * (y1 - y2)) * idet; + y_center = (cd * (x1 - x2) - bc * (x2 - x3)) * idet; + + radius = std::sqrt((x2 - x_center) * (x2 - x_center) + + (y2 - y_center) * (y2 - y_center)); + + } +}; + + +#include + +template +bool equal(T a, T b) { + // return float(a-b)==0; + return std::abs(float(a-b)) < std::abs(0.01f*a); +} + + + +int main() { + + float r1=4, r2=8, r3=15; + for(float phi=-3; phi<3.1; phi+=0.5) { + float x1=r1*cos(phi); + float x2=r2*cos(phi); + float y1=r1*sin(phi); + float y2=r2*sin(phi); + for(float phi3=phi-0.31; phi3 eq(x1,y1,x2,y2,x3,y3); + // std::cout << "r " << ori.radius <<' '<< eq.radius() << std::endl; + assert( equal(ori.radius, std::abs(eq.radius())) ); + auto c = eq.center(); + auto dir = eq.cosdir(); + assert (equal(1.f,dir.first*dir.first+dir.second*dir.second)); + assert( equal(ori.x_center,c.first) ); + assert( equal(ori.y_center,c.second) ); + // std::cout << "dca " << ori.dca0() <<' '<< eq.radius()*eq.dca0() << std::endl; + assert( equal( std::abs(ori.dca0()), std::abs(eq.radius()*eq.dca0())) ); + // std::cout << "dca " << ori.dca(1.,1.) <<' '<< eq.radius()*eq.dca(1.,1.) << std::endl; + assert( equal( std::abs(ori.dca(1.,1.)), std::abs(eq.radius()*eq.dca(1.,1.))) ); + + } + } + + + + return 0; +} diff --git a/RecoPixelVertexing/PixelTriplets/test/fastDPHI_t.cpp b/RecoPixelVertexing/PixelTriplets/test/fastDPHI_t.cpp new file mode 100644 index 0000000000000..58c7f832627fb --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/test/fastDPHI_t.cpp @@ -0,0 +1,197 @@ +// this test documents the derivation of the fast deltaphi used in gpu doublet code.. +// +// +// +#include +#include +#include +#include + +/** +| 1) circle is parameterized as: | +| C*[(X-Xp)**2+(Y-Yp)**2] - 2*alpha*(X-Xp) - 2*beta*(Y-Yp) = 0 | +| Xp,Yp is a point on the track (Yp is at the center of the chamber); | +| C = 1/r0 is the curvature ( sign of C is charge of particle ); | +| alpha & beta are the direction cosines of the radial vector at Xp,Yp | +| i.e. alpha = C*(X0-Xp), | +| beta = C*(Y0-Yp), | +| where center of circle is at X0,Y0. | +| Alpha > 0 | +| Slope dy/dx of tangent at Xp,Yp is -alpha/beta. | +| 2) the z dimension of the helix is parameterized by gamma = dZ/dSperp | +| this is also the tangent of the pitch angle of the helix. | +| with this parameterization, (alpha,beta,gamma) rotate like a vector. | +| 3) For tracks going inward at (Xp,Yp), C, alpha, beta, and gamma change sign| +| +*/ + +template +class FastCircle { + +public: + + FastCircle(){} + FastCircle(T x1, T y1, + T x2, T y2, + T x3, T y3) { + compute(x1,y1,x2,y2,x3,y3); + } + + void compute(T x1, T y1, + T x2, T y2, + T x3, T y3); + + + T m_xp; + T m_yp; + T m_c; + T m_alpha; + T m_beta; + +}; + + +template +void FastCircle::compute(T x1, T y1, + T x2, T y2, + T x3, T y3) { + bool flip = std::abs(x3-x1) > std::abs(y3-y1); + + auto x1p = x1-x2; + auto y1p = y1-y2; + auto d12 = x1p*x1p + y1p*y1p; + auto x3p = x3-x2; + auto y3p = y3-y2; + auto d32 = x3p*x3p + y3p*y3p; + + if (flip) { + std::swap(x1p,y1p); + std::swap(x3p,y3p); + } + + auto num = x1p*y3p-y1p*x3p; // num also gives correct sign for CT + auto det = d12*y3p-d32*y1p; + if( std::abs(det)==0 ) { + // and why we flip???? + } + auto ct = num/det; + auto sn = det>0 ? T(1.) : T(-1.); + auto st2 = (d12*x3p-d32*x1p)/det; + auto seq = T(1.) +st2*st2; + auto al2 = sn/std::sqrt(seq); + auto be2 = -st2*al2; + ct *= T(2.)*al2; + + if (flip) { + std::swap(x1p,y1p); + std::swap(al2,be2); + al2 = -al2; + be2 = -be2; + ct = -ct; + } + + m_xp = x1; + m_yp = y1; + m_c= ct; + m_alpha = al2 - ct*x1p; + m_beta = be2 - ct*y1p; + +} + + + +// compute curvature given two points (and origin) +float fastDPHI(float ri, float ro, float dphi) { + + /* + x3=0 y1=0 x1=0; + y3=ro + */ + + // auto x2 = ri*dphi; + // auto y2 = ri*(1.f-0.5f*dphi*dphi); + + + /* + auto x1p = x1-x2; + auto y1p = y1-y2; + auto d12 = x1p*x1p + y1p*y1p; + auto x3p = x3-x2; + auto y3p = y3-y2; + auto d32 = x3p*x3p + y3p*y3p; + */ + + /* + auto x1p = -x2; + auto y1p = -y2; + auto d12 = ri*ri; + auto x3p = -x2; + auto y3p = ro-y2; + auto d32 = ri*ri + ro*ro - 2.f*ro*y2; + */ + + + // auto rat = (ro -2.f*y2); + // auto det = ro - ri - (ro - 2.f*ri -0.5f*ro)*dphi*dphi; + + //auto det2 = (ro-ri)*(ro-ri) -2.*(ro-ri)*(ro - 2.f*ri -0.5f*ro)*dphi*dphi; + // auto seq = det2 + dphi*dphi*(ro-2.f*ri)*(ro-2.f*ri); // *rat2; + // auto seq = (ro-ri)*(ro-ri) + dphi*dphi*ri*ro; + + // and little by little simplifing and removing higher over terms + // we get + auto r2 = (ro-ri)*(ro-ri)/(dphi*dphi) + ri*ro; + + + // d2 = (ro-ri)*(ro-ri)/(4.f*r2 -ri*ro); + // return -2.f*dphi/std::sqrt(seq); + + return -1.f/std::sqrt(r2/4.f); + +} + + + +#include + +template +bool equal(T a, T b) { + // return float(a-b)==0; + return std::abs(float(a-b)) < std::abs(0.01f*a); +} + + + +int n=0; +void go(float ri, float ro, float dphi, bool print=false) { + ++n; + float x3 = 0.f, y3 = ro; + float x2 = ri*sin(dphi); + float y2 = ri*cos(dphi); + + + FastCircle c(0,0, + x2,y2, + x3,y3); + + auto cc = fastDPHI(ri,ro,dphi); + if (print) std::cout << c.m_c << ' ' << cc << std::endl; + assert(equal(c.m_c,cc)); + + +} + +int main() { + + + go(4.,7.,0.1, true); + + for (float r1=2; r1<15; r1+=1) + for (float dr=0.5; dr<10; dr+=0.5) + for (float dphi=0.02; dphi<0.2; dphi+=0.2) + go(r1,r1+dr,dphi); + + std::cout << "done " << n << std::endl; + return 0; +}; + diff --git a/RecoPixelVertexing/PixelTriplets/test/pixHits.ipynb b/RecoPixelVertexing/PixelTriplets/test/pixHits.ipynb new file mode 100644 index 0000000000000..12027fe01cdd8 --- /dev/null +++ b/RecoPixelVertexing/PixelTriplets/test/pixHits.ipynb @@ -0,0 +1,3120 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "IPython.OutputArea.prototype._should_scroll = function(lines) {\n", + " return false;\n", + "}" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%javascript\n", + "IPython.OutputArea.prototype._should_scroll = function(lines) {\n", + " return false;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib\n", + "import matplotlib.pyplot as plt\n", + "import glob\n", + "import math\n", + "import numpy as np\n", + "import pandas as pd\n", + "from bisect import *" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 1 1 2 2 9 10 10\n" + ] + } + ], + "source": [ + "layerStart = [0,96,320,672,1184,1296,1408,1520,1632,1744,1856]\n", + "layerName = [\"BL1\",\"BL2\",\"BL3\",\"BL4\",\"E+1\", \"E+2\", \"E+3\",\"E-1\", \"E-2\", \"E-3\"]\n", + "def layer(x) :\n", + " return bisect_right(layerStart, x)\n", + "\n", + "print layer(0),layer(1),layer(95),layer(96),layer(97),layer(1743),layer(1744),layer(1855)\n", + "\n", + "i2p = math.pi/32769.0" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def curvature(h, ptmin, region_origin_radius,hardPtCut, first, rad=False) :\n", + " region_origin_x = 0\n", + " region_origin_y = 0\n", + " x1 = h['r1']*np.cos(h['phi1']) if first else h['r2']*np.cos(h['phi2'])\n", + " y1 = h['r1']*np.sin(h['phi1']) if first else h['r2']*np.sin(h['phi2'])\n", + " x2 = h['r2']*np.cos(h['phi2']) if first else h['r3']*np.cos(h['phi3'])\n", + " y2 = h['r2']*np.sin(h['phi2']) if first else h['r3']*np.sin(h['phi3'])\n", + " x3 = h['r3']*np.cos(h['phi3']) if first else h['r4']*np.cos(h['phi4'])\n", + " y3 = h['r3']*np.sin(h['phi3']) if first else h['r4']*np.sin(h['phi4'])\n", + " \n", + " distance_13_squared = (x1 - x3)*(x1 - x3) + (y1 - y3)*(y1 - y3)\n", + " tan_12_13_half_mul_distance_13_squared = abs(y1 * (x2 - x3) + y2 * (x3 - x1) + y3 * (x1 - x2))\n", + " # high pt : just straight\n", + " straight = tan_12_13_half_mul_distance_13_squared * ptmin <= 1.0e-4*distance_13_squared\n", + " def ifStraight() :\n", + " distance_3_beamspot_squared = (x3-region_origin_x) * (x3-region_origin_x) + (y3-region_origin_y) * (y3-region_origin_y)\n", + " dot_bs3_13 = ((x1 - x3)*( region_origin_x - x3) + (y1 - y3) * (region_origin_y-y3))\n", + " proj_bs3_on_13_squared = dot_bs3_13*dot_bs3_13/distance_13_squared\n", + " distance_13_beamspot_squared = distance_3_beamspot_squared - proj_bs3_on_13_squared\n", + " return distance_13_beamspot_squared < (region_origin_radius+phiCut)*(region_origin_radius+phiCut)\n", + " \n", + " def standard() :\n", + " # 87 cm/GeV = 1/(3.8T * 0.3)\n", + " # 165 cm/GeV = 1/(2T * 0.3)\n", + " \n", + " # take less than radius given by the hardPtCut and reject everything below\n", + " minRadius = hardPtCut*87 # // FIXME move out and use real MagField\n", + " \n", + " det = (x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2)\n", + " \n", + " offset = x2 * x2 + y2*y2\n", + " \n", + " bc = (x1 * x1 + y1 * y1 - offset)*0.5\n", + " \n", + " cd = (offset - x3 * x3 - y3 * y3)*0.5\n", + " \n", + " \n", + " \n", + " idet = 1./ det;\n", + " \n", + " x_center = (bc * (y2 - y3) - cd * (y1 - y2)) * idet\n", + " y_center = (cd * (x1 - x2) - bc * (x2 - x3)) * idet\n", + " \n", + " radius = np.sqrt((x2 - x_center)*(x2 - x_center) + (y2 - y_center)*(y2 - y_center))\n", + " if rad: return radius\n", + " def domore() :\n", + " centers_distance_squared = (x_center - region_origin_x)*(x_center - region_origin_x) + (y_center - region_origin_y)*(y_center - region_origin_y)\n", + " #minimumOfIntersectionRange = (radius - region_origin_radius_plus_tolerance)*(radius - region_origin_radius_plus_tolerance)\n", + " #ok = centers_distance_squared >= minimumOfIntersectionRange\n", + " return np.sqrt(centers_distance_squared)-radius # - region_origin_radius\n", + "\n", + "\n", + " # return domore().where(radius > minRadius, radius <= minRadius)\n", + " return domore()\n", + " \n", + " #return ifStraight().where(straight,standard())\n", + " return standard()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "def dca(h, first, curv=False):\n", + " \n", + " x1 = h['r1']*np.cos(h['phi1']) if first else h['r2']*np.cos(h['phi2'])\n", + " y1 = h['r1']*np.sin(h['phi1']) if first else h['r2']*np.sin(h['phi2'])\n", + " x2 = h['r2']*np.cos(h['phi2']) if first else h['r3']*np.cos(h['phi3'])\n", + " y2 = h['r2']*np.sin(h['phi2']) if first else h['r3']*np.sin(h['phi3'])\n", + " x3 = h['r3']*np.cos(h['phi3']) if first else h['r4']*np.cos(h['phi4'])\n", + " y3 = h['r3']*np.sin(h['phi3']) if first else h['r4']*np.sin(h['phi4'])\n", + " \n", + " \n", + " noflip = abs(x3-x1) < abs(y3-y1)\n", + " x1p = np.where(noflip, x1-x2, y1-y2)\n", + " y1p = np.where(noflip, y1-y2, x1-x2)\n", + " d12 = x1p*x1p + y1p*y1p\n", + " x3p = np.where(noflip, x3-x2, y3-y2)\n", + " y3p = np.where(noflip, y3-y2, x3-x2)\n", + " d32 = x3p*x3p + y3p*y3p\n", + " num = x1p*y3p-y1p*x3p # num also gives correct sign for CT\n", + " det = d12*y3p-d32*y1p\n", + "\n", + " st2 = d12*x3p-d32*x1p\n", + " seq = det*det +st2*st2\n", + " al2 = 1./np.sqrt(seq)\n", + " be2 = -st2*al2\n", + " ct = 2.*num*al2\n", + " al2 *=det\n", + " m_xp = x2\n", + " m_yp = y2\n", + " m_c = np.where(noflip, ct, -ct)\n", + " m_alpha = np.where(noflip, al2, -be2)\n", + " m_beta = np.where(noflip, be2, -al2)\n", + "\n", + " if curv : return m_c\n", + " \n", + " x = m_c*m_xp + m_alpha\n", + " y = m_c*m_yp + m_beta\n", + " return (np.sqrt(x*x+y*y) - 1.)/m_c\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "def deltaphi(a,b,ch) :\n", + " ch *=ch\n", + " cb = ch*b\n", + " ca = ch*a\n", + " return np.arcsin(cb) - np.arcsin(ca)\n", + "\n", + "\n", + "def deltaphiA(a,b,ch) :\n", + " ch *=ch\n", + " d = b-a\n", + " cd = ch*d\n", + " ca = ch*a\n", + " return cd*(1.+0.5*ca*(ca+cd)+cd*cd*0.1667)\n", + "\n", + "\n", + "def eta(r,z) :\n", + " t = z/r\n", + " return np.arcsinh(t);\n", + "\n", + "def sag(r,c) :\n", + " return 0.5*c*r*r\n", + "\n", + "def phicutOld(a,b,c) :\n", + " o = np.maximum(a,b)\n", + " i = np.minimum(a,b)\n", + " d = np.minimum(i,o-i)\n", + " m = sag(o,c)\n", + " return d*m/(0.5*o*i)\n", + "\n", + "def phicut(a,b,c) :\n", + " ro = np.maximum(a,b)\n", + " ri = np.minimum(a,b)\n", + " dr = ro-ri\n", + " return dr/np.sqrt(4./(c*c) -ri*ro);\n", + "\n", + "def zAtR(h,r) :\n", + " zi = h['z1']\n", + " zo = h['z3']\n", + " ri = h['r1']\n", + " ro = h['r3']\n", + " return zi + (r-ri)*(zo-zi)/(ro-ri)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "def alignRZ(h, rp, ptmin, first) :\n", + " '''\n", + " float radius_diff = std::abs(r1 - ro);\n", + " float distance_13_squared = radius_diff*radius_diff + (z1 - zo)*(z1 - zo);\n", + " \n", + " float pMin = ptmin*std::sqrt(distance_13_squared); //this needs to be divided by radius_diff later\n", + " \n", + " float tan_12_13_half_mul_distance_13_squared = fabs(z1 * (getInnerR() - ro) + getInnerZ() * (ro - r1) + zo * (r1 - getInnerR())) ;\n", + " return tan_12_13_half_mul_distance_13_squared * pMin <= thetaCut * distance_13_squared * radius_diff;\n", + " '''\n", + " ri = h[rp+'1'] if first else h[rp+'2']\n", + " zi = h['z1'] if first else h['z2']\n", + " rm = h[rp+'2'] if first else h[rp+'3']\n", + " zm = h['z2'] if first else h['z3']\n", + " ro = h[rp+'3'] if first else h[rp+'4']\n", + " zo = h['z3'] if first else h['z4']\n", + " fact = 1. if (rp=='r') else 10.\n", + " radius_diff = fact*abs(ri - ro)\n", + " distance_13_squared = radius_diff*radius_diff + (zi - zo)*(zi - zo)\n", + " \n", + " pMin = ptmin*np.sqrt(distance_13_squared) #this needs to be divided by radius_diff later\n", + " \n", + " tan_12_13_half_mul_distance_13_squared = fact*abs(zi * (rm - ro) + zm * (ro - ri) + zo * (ri - rm)) \n", + " return tan_12_13_half_mul_distance_13_squared * pMin/(distance_13_squared * radius_diff)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "def alignRPZ(h, rp, first) :\n", + " ri = h[rp+'1'] if first else h[rp+'2']\n", + " zi = h['z1'] if first else h['z2']\n", + " rm = h[rp+'2'] if first else h[rp+'3']\n", + " zm = h['z2'] if first else h['z3']\n", + " ro = h[rp+'3'] if first else h[rp+'4']\n", + " zo = h['z3'] if first else h['z4']\n", + " \n", + " return (rm-ri)*(zo-zm) - (ro-rm)*(zm-zi)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "def doublets(hits,l1,l2,cut=0.2) :\n", + " nd=0\n", + " for h1 in l1.itertuples() :\n", + " phi = h1.phi\n", + " hh = l2['phi'].searchsorted([phi-cut,phi+cut])\n", + " hits.loc[h1.Index,'up0'] = hh[0]\n", + " hits.loc[h1.Index,'up1'] = hh[1]\n", + " nd += hh[1]-hh[0]\n", + " return nd" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "def ml(pt1,pz1,pt2,pz2) :\n", + " dp = pt1*pt2+pz1*pz2\n", + " m1 = pt1*pt1+pz1*pz1\n", + " m2 = pt2*pt2+pz2*pz2\n", + " corr = pt1/np.sqrt(m1)\n", + " dp /=np.sqrt(m1*m2)\n", + " dt = np.arccos(dp[dp<1]) \n", + " dtn = dt*np.sqrt(m1)*corr\n", + " return dt,dtn" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "#rawhits 6842643\n" + ] + } + ], + "source": [ + "file = '/Users/innocent/data/ttbarPU50Hits.csv'\n", + "# file = '/Users/innocent/data/ttbarPU0Hits.csv'\n", + "rawHits = pd.read_csv(file, delimiter=\" \")\n", + "print '#rawhits', len(rawHits)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "#hits 2865257\n", + " ev ind det charge xg yg zg rg \\\n", + "2068 1 3380 261 39382 -0.719951 -7.235875 12.478751 7.271603 \n", + "5523 1 4947 582 61290 -0.775613 -11.347950 19.134975 11.374425 \n", + "109 1 1549 66 75059 -0.153022 -3.402119 -9.927705 3.405559 \n", + "2865 1 3345 256 114378 -0.533277 -7.253811 -22.095730 7.273387 \n", + "10198 1 9090 1567 25476 -1.101961 -10.707680 -33.038609 10.764234 \n", + "\n", + " iphi tkId pt n1 tkId2 pt2 n2 phi eta seq trackID \n", + "2068 -17419 0 769 3 0 0 0 -1.669967 1.308952 2 10000000 \n", + "5523 -17096 0 769 3 0 0 0 -1.639038 1.291801 3 10000000 \n", + "109 -16853 2 740 7 0 0 0 -1.615744 -1.791266 1 10000002 \n", + "2865 -17150 2 740 8 0 0 0 -1.644181 -1.830360 2 10000002 \n", + "10198 -17455 2 740 3 0 0 0 -1.673348 -1.840135 8 10000002 \n", + " ev ind det charge xg yg zg rg \\\n", + "6839298 500 9339 1420 34842 -9.546642 -3.838250 48.859261 10.289341 \n", + "6841294 500 9575 1449 27589 -9.140972 -3.633035 46.708469 9.836479 \n", + "6837952 500 10073 1505 23716 -9.103679 -3.615468 46.529385 9.795334 \n", + "6834639 500 7848 1239 24579 14.649280 -2.504236 30.849955 14.861783 \n", + "6838867 500 9420 1428 27807 3.696498 -3.074924 48.992340 4.808249 \n", + "\n", + " iphi tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "6839298 -28781 114033 685 3 0 0 0 -2.759318 2.261890 7 \n", + "6841294 -28822 114033 685 2 0 0 0 -2.763291 2.261882 7 \n", + "6837952 -28825 114033 685 3 0 0 0 -2.763551 2.262225 7 \n", + "6834639 -1767 114052 414 3 0 0 0 -0.169309 1.477025 5 \n", + "6838867 -7238 114059 556 2 0 0 0 -0.693861 3.016878 7 \n", + "\n", + " trackID \n", + "6839298 5000114033 \n", + "6841294 5000114033 \n", + "6837952 5000114033 \n", + "6834639 5000114052 \n", + "6838867 5000114059 \n" + ] + } + ], + "source": [ + "rawHits['phi'] = np.arctan2(rawHits['yg'],rawHits['xg']) # rawHits['iphi']*i2p\n", + "rawHits['eta'] = eta(rawHits['rg'],rawHits['zg'])\n", + "rawHits['seq'] = rawHits['det'].apply(layer)\n", + "rawHits['trackID'] = rawHits['tkId']+10000000*rawHits['ev']\n", + "rawHits.sort_values(by=['ev','tkId','det'],inplace=True)\n", + "hits = rawHits[rawHits['pt']>400]\n", + "print '#hits', len(hits)\n", + "print hits.head()\n", + "print hits.tail()" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "#print hits[hits['tkId']==3].head(50)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "648743 455518 369878 305588 355791 363545 366194\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYcAAAD8CAYAAACcjGjIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAF7FJREFUeJzt3X+w3XV95/HnSxDH1VJCiSkS0mAbdNFRlLuA9cfSskqgW4OdHRa2KxGpkRF2dLbtgu4PHC072NY6ZevSjZoVZixIRUpWw2JKq8xujRI05YeABISSbCCRoPijQwu+94/zCRzyvTf3cM+995wkz8fMmfM97+/n+z3vc3Ny3ufz+Xy/35OqQpKkfs8bdQKSpPFjcZAkdVgcJEkdFgdJUofFQZLUYXGQJHVYHCRJHRYHSVKHxUGS1HHgqBOYqcMOO6yWLl066jQkaa9y6623fq+qFk7XbtrikORI4EpgEVDA6qr64ySHAp8DlgIPAGdU1WNJAvwxcBrwE+CdVfXNtq+VwH9qu/69qrqixY8DPgO8EFgHvK+mua7H0qVL2bhx43TpS5L6JHlwkHaDDCs9Cfx2VR0DnAicn+QY4CLgpqpaBtzUHgOcCixrt1XA5S2hQ4GLgROA44GLkyxo21wOvLtvu+WDJC9JmhvTFoeq2rbrm39V/RC4CzgCWAFc0ZpdAZzellcAV1bPBuCQJIcDpwDrq2pnVT0GrAeWt3UHV9WG1lu4sm9fkqQReE4T0kmWAq8Fvg4sqqptbdXD9IadoFc4HurbbEuL7Sm+ZZK4JGlEBi4OSV4MXAu8v6oe71/XvvHP+bW/k6xKsjHJxh07dsz100nSfmug4pDk+fQKw2er6gst/EgbEqLdb2/xrcCRfZsvbrE9xRdPEu+oqtVVNVFVEwsXTjvZLkmaoWmLQzv66NPAXVX1R32r1gIr2/JK4Pq++NnpORH4QRt+uhF4a5IFbSL6rcCNbd3jSU5sz3V2374kSSMwyHkObwDeAdyeZFOLfRC4FLgmybnAg8AZbd06eoexbqZ3KOs5AFW1M8lHgFtauw9X1c62/F6eOZT1hnaTJI1I9tafCZ2YmCjPc5Ck5ybJrVU1MV07L58hSerYay+fMSpLL/rS08sPXPprI8xEkuaOPQdJUofFQZLUYXGQJHVYHCRJHRYHSVKHxUGS1GFxkCR1WBwkSR0WB0lSh8VBktRhcZAkdVgcJEkdFgdJUofFQZLUYXGQJHVYHCRJHdMWhyRrkmxPckdf7HNJNrXbA7t+WzrJ0iR/37fuT/u2OS7J7Uk2J7ksSVr80CTrk9zb7hfMxQuVJA1ukJ7DZ4Dl/YGq+tdVdWxVHQtcC3yhb/V9u9ZV1Xl98cuBdwPL2m3XPi8CbqqqZcBN7bEkaYSm/ZnQqro5ydLJ1rVv/2cAv7qnfSQ5HDi4qja0x1cCpwM3ACuAk1rTK4CvABcOkvxs8ydAJaln2DmHNwGPVNW9fbGjknwryVeTvKnFjgC29LXZ0mIAi6pqW1t+GFg01ZMlWZVkY5KNO3bsGDJ1SdJUhi0OZwFX9T3eBiypqtcC/x74syQHD7qzqiqg9rB+dVVNVNXEwoULZ5qzJGka0w4rTSXJgcBvAMftilXVE8ATbfnWJPcBRwNbgcV9my9uMYBHkhxeVdva8NP2meYkSZodw/Qc/gVwd1U9PVyUZGGSA9ryy+hNPN/fho0eT3Jim6c4G7i+bbYWWNmWV/bFJUkjMsihrFcBXwNenmRLknPbqjN59pASwJuB29qhrZ8HzquqnW3de4FPAZuB++hNRgNcCrwlyb30Cs6lQ7weSdIsGORopbOmiL9zkti19A5tnaz9RuBVk8QfBU6eLg89m0dWSZpLniEtSeqwOEiSOiwOkqQOi4MkqcPiIEnqsDhIkjosDpKkDouDJKnD4iBJ6pjxhfc0GM9klrQ3sucgSeqwOEiSOiwOkqQO5xz2Mc5xSJoN9hwkSR0WB0lSh8VBktQxyM+ErkmyPckdfbEPJdmaZFO7nda37gNJNie5J8kpffHlLbY5yUV98aOSfL3FP5fkoNl8gXujpRd96embJI3CID2HzwDLJ4l/vKqObbd1AEmOoffb0q9s2/z3JAckOQD4BHAqcAxwVmsL8NG2r18CHgPO3f2JJEnza9riUFU3AzsH3N8K4OqqeqKqvgtsBo5vt81VdX9V/QNwNbAiSYBfBT7ftr8COP05vgZJ0iwbZs7hgiS3tWGnBS12BPBQX5stLTZV/OeA71fVk7vFJUkjNNPicDnwi8CxwDbgY7OW0R4kWZVkY5KNO3bsmI+nlKT90oyKQ1U9UlVPVdVPgU/SGzYC2Aoc2dd0cYtNFX8UOCTJgbvFp3re1VU1UVUTCxcunEnqkqQBzKg4JDm87+HbgV1HMq0FzkzygiRHAcuAbwC3AMvakUkH0Zu0XltVBfw18K/a9iuB62eSkyRp9kx7+YwkVwEnAYcl2QJcDJyU5FiggAeA9wBU1Z1JrgG+DTwJnF9VT7X9XADcCBwArKmqO9tTXAhcneT3gG8Bn561VyfNMi9Pov3FtMWhqs6aJDzlB3hVXQJcMkl8HbBukvj9PDMsJUkaA54hLUnqsDhIkjosDpKkDouDJKnD4iBJ6rA4SJI6LA6SpA5/Q3of4O8+SJpt9hwkSR0WB0lSh8VBktRhcZAkdVgcJEkdFgdJUofFQZLU4XkO0l7IHx3SXLPnIEnqsDhIkjqmLQ5J1iTZnuSOvtgfJLk7yW1JrktySIsvTfL3STa125/2bXNcktuTbE5yWZK0+KFJ1ie5t90vmIsXKkka3CA9h88Ay3eLrQdeVVWvBr4DfKBv3X1VdWy7ndcXvxx4N7Cs3Xbt8yLgpqpaBtzUHkuSRmja4lBVNwM7d4t9uaqebA83AIv3tI8khwMHV9WGqirgSuD0tnoFcEVbvqIvLkkakdmYc3gXcEPf46OSfCvJV5O8qcWOALb0tdnSYgCLqmpbW34YWDQLOUmShjDUoaxJ/iPwJPDZFtoGLKmqR5McB/xFklcOur+qqiS1h+dbBawCWLJkycwTlyTt0Yx7DkneCfxL4DfbUBFV9URVPdqWbwXuA44GtvLsoafFLQbwSBt22jX8tH2q56yq1VU1UVUTCxcunGnqkqRpzKg4JFkO/AfgbVX1k774wiQHtOWX0Zt4vr8NGz2e5MR2lNLZwPVts7XAyra8si8uSRqRaYeVklwFnAQclmQLcDG9o5NeAKxvR6RuaEcmvRn4cJJ/BH4KnFdVuyaz30vvyKcX0puj2DVPcSlwTZJzgQeBM2bllUn7Oc+i1jCmLQ5VddYk4U9P0fZa4Nop1m0EXjVJ/FHg5OnykPZGfkBrb+UZ0pKkDouDJKljv7wqq119Sdozew6SpA6LgySpw+IgSeqwOEiSOiwOkqQOi4MkqcPiIEnq2C/Pc5DGlefgaFxYHIbgf2RJ+yqHlSRJHRYHSVKHxUGS1GFxkCR1WBwkSR0erbQP82gqSTM1UM8hyZok25Pc0Rc7NMn6JPe2+wUtniSXJdmc5LYkr+vbZmVrf2+SlX3x45Lc3ra5LO2HqSVJozHosNJngOW7xS4CbqqqZcBN7THAqcCydlsFXA69YgJcDJwAHA9cvKugtDbv7ttu9+eSJM2jgYaVqurmJEt3C68ATmrLVwBfAS5s8SurqoANSQ5Jcnhru76qdgIkWQ8sT/IV4OCq2tDiVwKnAzfM9EVJejaHGPVcDTMhvaiqtrXlh4FFbfkI4KG+dltabE/xLZPEO5KsSrIxycYdO3YMkbokaU9mZUK6qipJzca+pnme1cBqgImJiTl9Pr9pSdqfDVMcHklyeFVta8NG21t8K3BkX7vFLbaVZ4ahdsW/0uKLJ2kv7ZX6v1hIe6thhpXWAruOOFoJXN8XP7sdtXQi8IM2/HQj8NYkC9pE9FuBG9u6x5Oc2I5SOrtvX5KkERio55DkKnrf+g9LsoXeUUeXAtckORd4EDijNV8HnAZsBn4CnANQVTuTfAS4pbX78K7JaeC99I6IeiG9iWgnoyVphAY9WumsKVadPEnbAs6fYj9rgDWTxDcCrxokF0nS3PPyGZKkDouDJKnD4iBJ6rA4SJI6LA6SpA4v2S3N0LicRT8ueWjfYs9BktRhcZAkdVgcJEkdFgdJUofFQZLUYXGQJHVYHCRJHRYHSVKHJ8HNo91/IcwTliSNK4vDAPzZR+0tfK9qtjisJEnqmHFxSPLyJJv6bo8neX+SDyXZ2hc/rW+bDyTZnOSeJKf0xZe32OYkFw37oiRJw5nxsFJV3QMcC5DkAGArcB2934z+eFX9YX/7JMcAZwKvBF4K/GWSo9vqTwBvAbYAtyRZW1Xfnmlu0kx4ATvpGbM153AycF9VPZhkqjYrgKur6gngu0k2A8e3dZur6n6AJFe3thaHWeQH3/hynkDjaLbmHM4Erup7fEGS25KsSbKgxY4AHuprs6XFpopLkkZk6OKQ5CDgbcCft9DlwC/SG3LaBnxs2Ofoe65VSTYm2bhjx47Z2q0kaTezMax0KvDNqnoEYNc9QJJPAl9sD7cCR/Ztt7jF2EP8WapqNbAaYGJiomYh97HncJCkUZiNYaWz6BtSSnJ437q3A3e05bXAmUlekOQoYBnwDeAWYFmSo1ov5MzWVpI0IkP1HJK8iN5RRu/pC/9+kmOBAh7Yta6q7kxyDb2J5ieB86vqqbafC4AbgQOANVV15zB5SZKGM1RxqKofAz+3W+wde2h/CXDJJPF1wLphcpEkzR7PkJYkdez311aarWPMnTiWtC/Z74uDNBmLvfZ3FgdpFgzSAx2XgjMueWi8OecgSeqwOEiSOiwOkqQOi4MkqcMJaWlM+ZvjGiV7DpKkDouDJKnD4iBJ6rA4SJI6nJAeoVGdqeoZspKmY3EYE8/18guSNJccVpIkdVgcJEkdDitJ0jzY2+b6hu45JHkgye1JNiXZ2GKHJlmf5N52v6DFk+SyJJuT3JbkdX37Wdna35tk5bB5SZJmbrZ6Dr9SVd/re3wRcFNVXZrkovb4QuBUYFm7nQBcDpyQ5FDgYmACKODWJGur6rFZyk8aKx5coHE3V3MOK4Ar2vIVwOl98SurZwNwSJLDgVOA9VW1sxWE9cDyOcpNkjSN2eg5FPDlJAX8j6paDSyqqm1t/cPAorZ8BPBQ37ZbWmyq+F7Jb4XaW+xt4+CaP7NRHN5YVVuTvARYn+Tu/pVVVa1wDC3JKmAVwJIlS2Zjl9K0LPbaHw09rFRVW9v9duA64HjgkTZcRLvf3ppvBY7s23xxi00V3/25VlfVRFVNLFy4cNjUJUlTGKrnkORFwPOq6odt+a3Ah4G1wErg0nZ/fdtkLXBBkqvpTUj/oKq2JbkR+K+7jmpq+/nAMLlJ+xp7MJpPww4rLQKuS7JrX39WVf87yS3ANUnOBR4Ezmjt1wGnAZuBnwDnAFTVziQfAW5p7T5cVTuHzE2SNENDFYequh94zSTxR4GTJ4kXcP4U+1oDrBkmH0nS7PDyGZKkDi+fsZ/zUEZJk7HnIEnqsDhIkjosDpKkDuccJI0958bmnz0HSVKHxUGS1GFxkCR1WBwkSR0WB0lSh8VBktThoaySgPE7XNRLlI+WPQdJUofFQZLUYXGQJHVYHCRJHRYHSVLHjItDkiOT/HWSbye5M8n7WvxDSbYm2dRup/Vt84Ekm5Pck+SUvvjyFtuc5KLhXpIkaVjDHMr6JPDbVfXNJD8D3JpkfVv38ar6w/7GSY4BzgReCbwU+MskR7fVnwDeAmwBbkmytqq+PURu0kA8XFKa3IyLQ1VtA7a15R8muQs4Yg+brACurqongO8m2Qwc39Ztrqr7AZJc3dpaHCRpRGblJLgkS4HXAl8H3gBckORsYCO93sVj9ArHhr7NtvBMMXlot/gJUzzPKmAVwJIlS2YjdUmad+N2wuFkhi4OSV4MXAu8v6oeT3I58BGg2v3HgHcN+zwAVbUaWA0wMTFRs7FPPWNveMNqfozze2H3ocBxy29fMVRxSPJ8eoXhs1X1BYCqeqRv/SeBL7aHW4Ej+zZf3GLsIS5JGoFhjlYK8Gngrqr6o7744X3N3g7c0ZbXAmcmeUGSo4BlwDeAW4BlSY5KchC9Seu1M81LkjS8YXoObwDeAdyeZFOLfRA4K8mx9IaVHgDeA1BVdya5ht5E85PA+VX1FECSC4AbgQOANVV15xB5SZKGNMzRSv8HyCSr1u1hm0uASyaJr9vTdpI0lXGeH9mbeYa0JKnD33OQpBEa156PxUHSPmNcP2j3Rg4rSZI67DlI0pgYp56PxUHSPmmcPmhnYtT5O6wkSeqwOEiSOiwOkqQOi4MkqcPiIEnqsDhIkjo8lFXSHo36kEqNhj0HSVKHxUGS1OGwkqR93t4+NDaK/C0OkjRH+j/U9zZjM6yUZHmSe5JsTnLRqPORpP3ZWBSHJAcAnwBOBY6h9zvUx4w2K0naf43LsNLxwOaquh8gydXACuDbI81qP7a3j9FKGs5Y9ByAI4CH+h5vaTFJ0giMS89hIElWAavawx8luWcGuzkM+N7sZTUrxjqnfHTEmTzbWP+txsic5DTke2Es/k67vYaxyGkSU+Y1C/8ff2GQRuNSHLYCR/Y9Xtxiz1JVq4HVwzxRko1VNTHMPmabOQ1uHPMyp8GY0+DGIa9xGVa6BViW5KgkBwFnAmtHnJMk7bfGoudQVU8muQC4ETgAWFNVd444LUnab41FcQCoqnXAunl4qqGGpeaIOQ1uHPMyp8GY0+BGnleqatQ5SJLGzLjMOUiSxsg+WxySHJLk80nuTnJXktfvtv6kJD9Isqnd/suoc+rLa1OSO5N8ddQ5Jfndvr/RHUmeSnLoiHP62ST/K8nftr/TOXOZz3PIa0GS65LcluQbSV41x/m8vO/fZlOSx5O8f7c2SXJZuyzNbUleNwY5vSLJ15I8keR35jKf55DTb7a/z+1J/ibJa8YkrxUtr01JNiZ541zn9bSq2idvwBXAb7Xlg4BDdlt/EvDFMcvpEHpnhS9pj18y6px2a/vrwF+NOifgg8BH2/JCYCdw0Bjk9QfAxW35FcBN8/jeOgB4GPiF3eKnATcAAU4Evj4GOb0E+GfAJcDvzFc+0+T0y8CCtnzqfP6dpsnrxTwz/P9q4O75ymmf7Dkk+VngzcCnAarqH6rq+3tBTv8G+EJV/V1rs30Mcup3FnDVGORUwM8kCb3/PDuBJ8cgr2OAv2rr7waWJlk0l3n1ORm4r6oe3C2+AriyejYAhyQ5fJQ5VdX2qroF+Md5ymOQnP6mqh5rDzfQO9dqHPL6UbXKALyI3nt/XuyTxQE4CtgB/M8k30ryqSQvmqTd69vQxA1JXjkGOR0NLEjylSS3Jjl7DHICIMk/AZYD145BTn8C/FPg/wG3A++rqp+OQV5/C/wGQJLj6Z2JOl8fMmcyeeEe5aVppspplAbJ6Vx6va35NGVeSd6e5G7gS8C75iuhfbU4HAi8Dri8ql4L/BjY/TLg36TXhXsN8N+AvxiDnA4EjgN+DTgF+M9Jjh5xTrv8OvB/q2rnHOYzaE6nAJuAlwLHAn+S5OAxyOtSet/MNwH/DvgW8NQc50V6J46+DfjzuX6uQe2tOSX5FXrF4cJxyauqrquqVwCnAx+Zr7z21eKwBdhSVV9vjz9P7z/206rq8ar6UVteBzw/yWGjzKm1ubGqflxV3wNuBuZyYmyQnHaZr2+Bg+R0Dr3ht6qqzcB36Y3xjzSv9p46p6qOBc6mNx9y/xznBb0x8m9W1SOTrBvo0jTznNOo7DGnJK8GPgWsqKpHxyWvXarqZuBlc/w59bR9sjhU1cPAQ0le3kIns9vlv5P8fBuz3jUE8Dxgzt4Qg+QEXA+8McmBbRjnBOCuEee0a7z9n7f85tSAOf1di9PG9F/OHH8ID/ieOqR9CwT4LeDmqnp8LvNq9jQXtBY4ux21dCLwg6raNuKcRmXKnJIsAb4AvKOqvjOvWe05r1/q+5x6HfAC5vBz6lnma+Z7vm/0hhs2ArfRGzJaAJwHnNfWXwDcSW+ceAPwy6POqbX5XXofOncA7x+TnN4JXD1G/3YvBb5Mb77hDuDfjklerwe+A9xD74NmwTzk9CJ6HxY/2xfrzyn0fkjrvvb3mhiDnH6eXk/sceD7bfngEef0KeAxesOVm4CN8/Semi6vC9vn1Cbga8Ab5yOvqvIMaUlS1z45rCRJGo7FQZLUYXGQJHVYHCRJHRYHSVKHxUGS1GFxkCR1WBwkSR3/H7+mLuwWkff0AAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYcAAAD8CAYAAACcjGjIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAFdpJREFUeJzt3X+QXeV93/H3J8L4R/xD/FApkcQsrVW3mLo22WJSN4kDNQjwWGTGptAfyK4m+iOkdZpkHJF0QmubGZN0TMwkplWNapE6xoQ4RWNwZBXbw7QFjAwYAzJmjbGRBizVAhzH4x9yvv3jPsIXnV3tcu9q713t+zVzZs/5nuec+7270v3e5znPPTdVhSRJ/X5q1AlIksaPxUGS1GFxkCR1WBwkSR0WB0lSh8VBktRhcZAkdVgcJEkdFgdJUscxo05gUCeeeGJNTEyMOg1JWjROPPFEtm/fvr2q1s7WdtEWh4mJCXbu3DnqNCRpUUly4lzaOawkSeqwOEiSOiwOkqQOi4MkqcPiIEnqsDhIkjosDpKkDouDJKnD4iBJ6li0n5A+0iY23frc+uMfuHCEmUjSwpu155BkS5K9SR6cZt9vJqmDH8dOz7VJppI8kOSMvrbrkzzalvV98Z9N8uV2zLVJMl9PTpI0mLkMK30U6NykKclq4Fzgm33h84E1bdkIXNfaHg9cCbwROBO4Mslx7ZjrgF/pO27WG0JJko6sWYtDVd0B7J9m1zXAe4Dqi60Dbqieu4DlSU4GzgN2VNX+qnoa2AGsbfteWVV3VVUBNwAXDfeUJEnDGuiCdJJ1wJ6q+tIhu1YCT/Rt726xw8V3TxOf6XE3JtmZZOe+ffsGSV2SNAcvuDgkeRnwO8DvzX86h1dVm6tqsqomV6xYsdAPL0lLxiA9h78LnAp8KcnjwCrg3iR/G9gDrO5ru6rFDhdfNU1ckjRCL7g4VNWXq+pvVdVEVU3QGwo6o6qeArYBl7VZS2cBz1bVk8B24Nwkx7UL0ecC29u+7yQ5q81Sugy4ZZ6emyRpQHOZyvpx4E7gNUl2J9lwmOa3AY8BU8B/A34VoKr2A+8D7mnLe1uM1uYj7ZivAZ8e7KlIkubLrB+Cq6pLZ9k/0bdewOUztNsCbJkmvhM4fbY8JEkLx9tnSJI6LA6SpA6LgySpw+IgSeqwOEiSOiwOkqQOv8/hKOZ3UkgalD0HSVKHxUGS1GFxkCR1WBwkSR0WB0lSh8VBktRhcZAkdVgcJEkdFgdJUofFQZLUYXGQJHVYHCRJHbMWhyRbkuxN8mBf7A+SfCXJA0n+Isnyvn1XJJlK8kiS8/ria1tsKsmmvvipSe5u8U8kOXY+n6Ak6YWbS8/ho8DaQ2I7gNOr6nXAV4ErAJKcBlwCvLYd8+Eky5IsA/4YOB84Dbi0tQW4Grimql4NPA1sGOoZLXETm259bpGkQc1aHKrqDmD/IbHPVNWBtnkXsKqtrwNurKofVNXXgSngzLZMVdVjVfVD4EZgXZIAZwM3t+O3AhcN+ZwkSUOaj2sO/wb4dFtfCTzRt293i80UPwF4pq/QHIxLkkZoqOKQ5HeBA8DH5iedWR9vY5KdSXbu27dvIR5Skpakgb8JLsk7gbcC51RVtfAeYHVfs1UtxgzxbwPLkxzTeg/97TuqajOwGWBycrJmavdC+G1pktQ1UM8hyVrgPcDbqup7fbu2AZckeXGSU4E1wBeAe4A1bWbSsfQuWm9rReVzwNvb8euBWwZ7KpKk+TKXqawfB+4EXpNkd5INwB8BrwB2JLk/yX8BqKqHgJuAh4G/BC6vqh+3XsGvAduBXcBNrS3AbwO/kWSK3jWI6+f1GUqSXrBZh5Wq6tJpwjO+gFfVVcBV08RvA26bJv4YvdlMkqQx4SekJUkdA1+QljTenGyhYSzJ4uCnhyXp8BxWkiR1WBwkSR0WB0lSh8VBktSxJC9IS7Nxpo+WOnsOkqQOi4MkqcPiIEnqsDhIkjosDpKkDouDJKnD4iBJ6rA4SJI6LA6SpA6LgySpw+IgSeqwOEiSOma98V6SLcBbgb1VdXqLHQ98ApgAHgcurqqnkwT4EHAB8D3gnVV1bztmPfAf2mnfX1VbW/xngY8CLwVuA95dVTVPz29J8JvtFo435NNSMZeew0eBtYfENgG3V9Ua4Pa2DXA+sKYtG4Hr4LliciXwRuBM4Mokx7VjrgN+pe+4Qx9LkrTAZi0OVXUHsP+Q8Dpga1vfClzUF7+heu4Clic5GTgP2FFV+6vqaWAHsLbte2VV3dV6Czf0nUuSNCKDfp/DSVX1ZFt/Cjipra8Enuhrt7vFDhffPU18UXPoQdJiN/SX/VRVJVmQawRJNtIbruKUU05ZiIeUxtqh15t8M6L5MuhspW+1ISHaz70tvgdY3dduVYsdLr5qmvi0qmpzVU1W1eSKFSsGTF2SNJtBi8M2YH1bXw/c0he/LD1nAc+24aftwLlJjmsXos8Ftrd930lyVpvpdFnfucbSxKZbn1sk6Wg1l6msHwfeDJyYZDe9WUcfAG5KsgH4BnBxa34bvWmsU/Smsr4LoKr2J3kfcE9r996qOniR+1f5yVTWT7dFkjRCsxaHqrp0hl3nTNO2gMtnOM8WYMs08Z3A6bPlIUlaOH5CWpLUMfRsJUlz4xRnLSb2HCRJHRYHSVKHw0rSIuCQlBaaPQdJUofFQZLU4bBSHz/1LEk99hwkSR0WB0lSh8VBktThNYcx4/35JY0Dew6SpA57DiPih5qOLv49dbSx5yBJ6rA4SJI6LA6SpA6vOUhLgNdE9ELZc5AkdVgcJEkdQxWHJP8+yUNJHkzy8SQvSXJqkruTTCX5RJJjW9sXt+2ptn+i7zxXtPgjSc4b7ilJczex6dbnFkk/MXBxSLIS+HfAZFWdDiwDLgGuBq6pqlcDTwMb2iEbgKdb/JrWjiSnteNeC6wFPpxk2aB5SZKGN+wF6WOAlyb5EfAy4EngbOBftP1bgf8IXAesa+sANwN/lCQtfmNV/QD4epIp4EzgziFzk5Yce0CaLwP3HKpqD/CfgW/SKwrPAl8EnqmqA63ZbmBlW18JPNGOPdDan9Afn+YYSdIIDNxzSHIcvXf9pwLPAH9Gb1joiEmyEdgIcMoppxzJh5JGwimnGhfDDCv9M+DrVbUPIMkngTcBy5Mc03oHq4A9rf0eYDWwO8kxwKuAb/fFD+o/5nmqajOwGWBycrKGyH3Rc/hA0pE0zGylbwJnJXlZu3ZwDvAw8Dng7a3NeuCWtr6tbdP2f7aqqsUvabOZTgXWAF8YIi9J0pAG7jlU1d1JbgbuBQ4A99F7V38rcGOS97fY9e2Q64E/aRec99OboURVPZTkJnqF5QBweVX9eNC8JEnDG2q2UlVdCVx5SPgxerONDm37feAdM5znKuCqYXKRJM0fPyEtSerwxntLhLNgJL0QFodFxBlKkhaKw0qSpA57DnPgO3ZJS409B0lSh8VBktRhcZAkdVgcJEkdXpCWZuGEBC1F9hwkSR0WB0lSh8NKQ/CWFJKOVvYcJEkdFgdJUofFQZLU4TUHqXHKqvQT9hwkSR0WB0lSh8VBktQxVHFIsjzJzUm+kmRXkp9LcnySHUkebT+Pa22T5NokU0keSHJG33nWt/aPJlk/7JOSJA1n2J7Dh4C/rKq/D/wjYBewCbi9qtYAt7dtgPOBNW3ZCFwHkOR44ErgjcCZwJUHC4okaTQGLg5JXgX8AnA9QFX9sKqeAdYBW1uzrcBFbX0dcEP13AUsT3IycB6wo6r2V9XTwA5g7aB5SZKGN0zP4VRgH/Dfk9yX5CNJfho4qaqebG2eAk5q6yuBJ/qO391iM8UlSSMyTHE4BjgDuK6q3gD8NT8ZQgKgqgqoIR7jeZJsTLIzyc59+/bN12klSYcYpjjsBnZX1d1t+2Z6xeJbbbiI9nNv278HWN13/KoWmyneUVWbq2qyqiZXrFgxROrSkTOx6dbnFmmxGrg4VNVTwBNJXtNC5wAPA9uAgzOO1gO3tPVtwGVt1tJZwLNt+Gk7cG6S49qF6HNbTJI0IsPePuPfAh9LcizwGPAuegXnpiQbgG8AF7e2twEXAFPA91pbqmp/kvcB97R2762q/UPmJUkawlDFoaruByan2XXONG0LuHyG82wBtgyTiyRp/vgJaUlSh3dlHXNe1JQ0ChaHBeQLvaTFwmElSVKHPQdpxOxRahxZHKRFxmKiheCwkiSpw56DNCDfwetoZs9BktRhz0FaYvp7PI9/4MIRZqJxZnE4whx6kLQYOawkSeqw57AEOawgaTYWB2kEHG7UuHNYSZLUYXGQJHU4rDQGHGKQNG7sOUiSOiwOkqQOi4MkqWPo4pBkWZL7knyqbZ+a5O4kU0k+keTYFn9x255q+yf6znFFiz+S5Lxhc5IkDWc+eg7vBnb1bV8NXFNVrwaeBja0+Abg6Ra/prUjyWnAJcBrgbXAh5Msm4e8JEkDGqo4JFkFXAh8pG0HOBu4uTXZClzU1te1bdr+c1r7dcCNVfWDqvo6MAWcOUxekqThDDuV9Q+B9wCvaNsnAM9U1YG2vRtY2dZXAk8AVNWBJM+29iuBu/rO2X+MNO8Wy9ThxZKnjk4DF4ckbwX2VtUXk7x5/lI67GNuBDYCnHLKKQvxkHPmf2RJR5NhhpXeBLwtyePAjfSGkz4ELE9ysOisAva09T3AaoC2/1XAt/vj0xzzPFW1uaomq2pyxYoVQ6QuSTqcgXsOVXUFcAVA6zn8VlX9yyR/BrydXsFYD9zSDtnWtu9s+z9bVZVkG/CnST4I/AywBvjCoHlJ48QepRarI3H7jN8GbkzyfuA+4PoWvx74kyRTwH56M5SoqoeS3AQ8DBwALq+qHx+BvCRJczQvxaGqPg98vq0/xjSzjarq+8A7Zjj+KuCq+chFkjQ8PyEtSeqwOEiSOiwOkqQOi4MkqcPiIEnqsDhIkjosDpKkDr9DWlrC+j/B/fgHLhxhJoe3WPI8mthzkCR1WBwkSR0WB0lSh8VBktRhcZAkdVgcJEkdTmWVpBEa12m69hwkSR0WB0lSh8VBktRhcZAkdVgcJEkdAxeHJKuTfC7Jw0keSvLuFj8+yY4kj7afx7V4klybZCrJA0nO6DvX+tb+0STrh39amquJTbc+t0jSQcP0HA4Av1lVpwFnAZcnOQ3YBNxeVWuA29s2wPnAmrZsBK6DXjEBrgTeCJwJXHmwoEiSRmPg4lBVT1bVvW39r4BdwEpgHbC1NdsKXNTW1wE3VM9dwPIkJwPnATuqan9VPQ3sANYOmpckaXjz8iG4JBPAG4C7gZOq6sm26yngpLa+Enii77DdLTZTXJI6xvVDY0eboS9IJ3k58OfAr1fVd/r3VVUBNexj9D3WxiQ7k+zct2/ffJ1WknSIoYpDkhfRKwwfq6pPtvC32nAR7efeFt8DrO47fFWLzRTvqKrNVTVZVZMrVqwYJnVJ0mEMM1spwPXArqr6YN+ubcDBGUfrgVv64pe1WUtnAc+24aftwLlJjmsXos9tMUnSiAxzzeFNwL8Gvpzk/hb7HeADwE1JNgDfAC5u+24DLgCmgO8B7wKoqv1J3gfc09q9t6r2D5GXJGlIAxeHqvrfQGbYfc407Qu4fIZzbQG2DJqLJGl++QlpSVKH3+cgSQtspjsSjNM0XXsOkqQOi4MkqcNhJUmL1jgNwxxt7DlIkjrsOUjSGBp1r8jiIOmoMOoX06ONw0qSpA6LgySpw2ElSYDDMno+ew6SpA6LgySpw+IgSerwmoMkjblRXA+y5yBJ6rA4SJI6LA6SpA6LgySpwwvSko46h37Tmh/qe+HGpueQZG2SR5JMJdk06nwkaSkbi+KQZBnwx8D5wGnApUlOG21WkrR0jUVxAM4Epqrqsar6IXAjsG7EOUnSkjUu1xxWAk/0be8G3jiiXJYsb7wm6aBxKQ5zkmQjsLFtfjfJI8CJwP8bXVaHtWhzy9ULmMn0Fu3vbgwMnd8R/PuP5Hc3x+ezKP6uQ/5t5vz8xqU47AFW922varHnqarNwOb+WJKdVTV5ZNMbjLkNbpzzG+fcYLzzM7fBLXR+43LN4R5gTZJTkxwLXAJsG3FOkrRkjUXPoaoOJPk1YDuwDNhSVQ+NOC1JWrLGojgAVNVtwG0DHLp59iYjY26DG+f8xjk3GO/8zG1wC5pfqmohH0+StAiMyzUHSdIYGdvikGRLkr1JHuyLHZ9kR5JH28/jZjj2lCSfSbIrycNJJsYhtyS/lOT+vuX7SS4ah9xau99P8lD7vV2bJPOZ2zzkd3WSB9vyzxcot3e038nfJJlxpshC3P5lyPw6x45DbklWJ/lc+3/6UJJ3j1FuL0nyhSRfam3/03znNkx+fW2XJbkvyafmNbGqGssF+AXgDODBvtjvA5va+ibg6hmO/Tzwlrb+cuBl45JbX/vjgf3jkhvwT4D/Q29CwDLgTuDN4/J3BS4EdtC7TvbT9Ga4vXIBcvsHwGvav6nJGY5bBnwN+DvAscCXgNMW6Hc3a34zHTsOuQEnA2e09VcAX53v390QuQV4eVt/EXA3cNa4/O762v4G8KfAp+Yzr7HtOVTVHfRePPutA7a29a1A5113uyfTMVW1o53nu1X1vXHI7RBvBz49RrkV8BJ6L24vpvef4VvzmduQ+Z0G3FFVB6rqr4EHgLVHOreq2lVVj8xy6ILc/mWI/Gb6vY88t6p6sqrubet/Beyid8eEccitquq7bfNFbZn3i7TD/F2TrKL3xukj853X2BaHGZxUVU+29aeAk6Zp8/eAZ5J8snW1/iC9G/uNQ279LgE+fmRTes6suVXVncDngCfbsr2qdo1LfvTeja9N8rIkJwK/xPM/ODlK093+ZV5f4JaCNvz7Bnrv0MdCG7K5H9gL7Kiqscmt+UPgPcDfzPeJF1txeE71+lPTVfFjgJ8Hfgv4x/S6+u9cuMwOmxsASU4G/iG9z3UsqJlyS/Jqel3ZVfRe2M5O8vMLnN6M+VXVZ+hNdf6/9IrqncCPFzY7HSlJXg78OfDrVfWdUedzUFX9uKpeT+//xZlJTh91TgcleSuwt6q+eCTOv9iKw7faC+vBF9i907TZDdzfuvgHgP9JbzxvHHI76GLgL6rqRwuQF8wtt18G7mrDcN8FPg383BjlR1VdVVWvr6q30BsP/uoC5TebOd3+RdNL8iJ6heFjVfXJUecznap6hl7Pel6HMof0JuBtSR6nN5R5dpL/MV8nX2zFYRuwvq2vB26Zps09wPIkK9r22cDDY5LbQZeycENKMLfcvgn8YpJj2n/WX6Q3/jsW+bXu/Qlt/XXA64DPLFB+s/H2LwNqM+KuB3ZV1QdHnU+/JCuSLG/rLwXeAnxltFn9RFVdUVWrqmqC3r+5z1bVv5rPBxjLhd6L55PAj+j1BjYAJwC3A48C/ws4vrWdBD7Sd+xb6F2w/DLwUeDYMcptgt67yp8ap98bvRk3/5VeQXgY+OCY5feSltfDwF3A6xcot19u6z+gd4F+e2v7M8BtfcdeQK8n8zXgdxfwdzfX/DrHjkNuwD+lN4z4AHB/Wy4Yk9xeB9zXcnsQ+L1x+7v2nePNzPNsJT8hLUnqWGzDSpKkBWBxkCR1WBwkSR0WB0lSh8VBktRhcZAkdVgcJEkdFgdJUsf/B25EeCQ0e0vjAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYAAAAD8CAYAAAB+UHOxAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAE6xJREFUeJzt3XGMpdV93vHvEyA4SqKwmClZL0uWxBtZuFLBnWJaV6qLawxY8uI2cSCSvbGo1pZATdS0KiSVsOzQkioxqlWXamO2XqqYDSWx2Dqb0jUmQpaCzeISYCEOY4zFrtaw8WIS5JYE8usf96x7s+zs3Jm5c+/MnO9HurrvPe9533vO3Jn3ue95z72TqkKS1J8fmHYDJEnTYQBIUqcMAEnqlAEgSZ0yACSpUwaAJHXKAJCkThkAktQpA0CSOnX6tBtwKuecc05t2bJl2s2QpDXlkUce+bOqmlmo3qoOgC1btnDgwIFpN0OS1pQk3xqlnkNAktQpA0CSOmUASFKnDABJ6pQBIEmdMgAkqVMGgCR1ygCQpE4ZAJLUqVX9SeBJ23Lj739/+dlb3zvFlkjSyvMMQJI6ZQBIUqcMAEnqlAEgSZ0yACSpU13OAnK2jyR1FADDB31JUkcBMJ/5gsGzBEnrndcAJKlTBoAkdcoAkKROLXgNIMkbgAeBM1v9e6rq5iSfBf4R8FKr+gtV9WiSAP8RuAr4Xiv/WtvXduDftvq/VlW7x9kZSVpN5rvGuFquK45yEfgV4LKqejnJGcCXk/xBW/evq+qeE+pfCWxtt7cDtwNvT3I2cDMwCxTwSJK9VfXiODoiSVqcBQOgqgp4uT08o93qFJtsA+5s2z2U5KwkG4F3Avur6hhAkv3AFcBdS2++JK1t05xxONI1gCSnJXkUeIHBQfwrbdUtSR5LcluSM1vZJuC5oc0PtbL5yle9LTf+/vdvkrRejBQAVfVaVV0EnAdckuRvAzcBbwH+HnA28G/G0aAkO5IcSHLg6NGj49ilJOkkFvVBsKr6bpIHgCuq6jda8StJ/ivwr9rjw8Dmoc3Oa2WHGQwDDZf/4UmeYyewE2B2dvZUQ02StOqspZGCBc8AkswkOast/xDwbuBP2rg+bdbP1cATbZO9wIcycCnwUlUdAe4DLk+yIckG4PJWJkmaglHOADYCu5OcxiAw7q6qLyT5UpIZIMCjwEdb/X0MpoDOMZgG+mGAqjqW5BPAw63ex49fEO6FXy8hrR4nvlNfzt/kYt/1r5azhFFmAT0GXHyS8svmqV/A9fOs2wXsWmQb1yXDQNK0df9lcCtttSS9JJ3IAJCkEa23N3QGwCoz39CQQ0aSxs0AWKSVOBCP8j8JJK2sHt9k+W2gktQpA0CSOuUQkKR1rcehnVEZAJJWvcUexL2uNhoDYAX4SyZpLTAA1iBPadWz1f5fttYSA0DSuuCZ9+IZAMuw2t+Jr/b2SZoup4FKUqc8A5A0cZ6drg4GgKSpMgymxwDQgvwD1Th4kXb1MQA6Ma4P0khaPwyADq30PGrPGPowyleXr1W9/A4vGABJ3gA8CJzZ6t9TVTcnuQDYA7wReAT4YFX9ZZIzgTuBvwt8B/i5qnq27esm4DrgNeBfVJX/FH4VGeUP1w/h9KeXg2GPRjkDeAW4rKpeTnIG8OUkfwD8S+C2qtqT5L8wOLDf3u5frKo3J7kG+HXg55JcCFwDvBV4E/DFJD9dVa+tQL8kTdB6eNffo1H+KXwBL7eHZ7RbAZcBP9/KdwMfYxAA29oywD3Af0qSVr6nql4BvplkDrgE+KNxdETS+HhA78NI1wCSnMZgmOfNwKeBbwDfrapXW5VDwKa2vAl4DqCqXk3yEoNhok3AQ0O7Hd5Ga9wkDxgOSUyPwbC+jBQAbZjmoiRnAZ8H3rJSDUqyA9gBcP7556/U02gV8YAuTceiZgFV1XeTPAD8feCsJKe3s4DzgMOt2mFgM3AoyenAjzG4GHy8/LjhbYafYyewE2B2drYW1x2tNoudKWIYLI4/Ly3HKLOAZoC/agf/HwLezeDC7gPAzzCYCbQduLdtsrc9/qO2/ktVVUn2Ap9L8kkGF4G3Al8dc3+kbi0lDAyQvo1yBrAR2N2uA/wAcHdVfSHJk8CeJL8G/G/gjlb/DuC/tYu8xxjM/KGqDia5G3gSeBW43hlAmraVPgCO6wN4622evVaHUWYBPQZcfJLyZxjM4jmx/P8CPzvPvm4Bbll8M6XJWk4wLPYAvZzPX4xSfylnA+qDnwRe4078o+3tNP5UB7rlXH+QemAAjIkHDy2Gvy9aDQwArQkrMUwyaau9feqPAaAuePCVXs8A0Kq1lg7aa6mt0nEGgLREHvS11vlP4SWpUwaAJHXKAJCkThkAktQpA0CSOmUASFKnnAYqSaewnqf7egYgSZ0yACSpUwaAJHXKAJCkTnkRWNKqtJ4vvq4WBoCkVcOD/mQtOASUZHOSB5I8meRgkl9s5R9LcjjJo+121dA2NyWZS/L1JO8ZKr+ilc0luXFluiRJGsUoZwCvAr9cVV9L8qPAI0n2t3W3VdVvDFdOciFwDfBW4E3AF5P8dFv9aeDdwCHg4SR7q+rJcXREkrQ4CwZAVR0BjrTlv0jyFLDpFJtsA/ZU1SvAN5PMAZe0dXNV9QxAkj2trgEgTZDDLDpuUbOAkmwBLga+0opuSPJYkl1JNrSyTcBzQ5sdamXzlZ/4HDuSHEhy4OjRo4tpniRpEUYOgCQ/Avwu8EtV9efA7cBPARcxOEP4zXE0qKp2VtVsVc3OzMyMY5eSpJMYaRZQkjMYHPx/u6p+D6Cqnh9a/1vAF9rDw8Dmoc3Pa2WcolySNGELBkCSAHcAT1XVJ4fKN7brAwDvB55oy3uBzyX5JIOLwFuBrwIBtia5gMGB/xrg58fVEQ04vitpVKOcAbwD+CDweJJHW9mvANcmuQgo4FngIwBVdTDJ3Qwu7r4KXF9VrwEkuQG4DzgN2FVVB8fYF0nSIowyC+jLDN69n2jfKba5BbjlJOX7TrWdtBoNn1U9e+t7p9gSabz8LiBJ6pQBIEmdMgAkqVMGgCR1ygCQpE4ZAJLUKQNAkjplAEhSpwwASeqUASBJnTIAJKlTBoAkdcoAkKROGQCS1CkDQJI6NdK/hJS0dvlf4jQfzwAkqVMGgCR1asEASLI5yQNJnkxyMMkvtvKzk+xP8nS739DKk+RTSeaSPJbkbUP72t7qP51k+8p1S5K0kFHOAF4FfrmqLgQuBa5PciFwI3B/VW0F7m+PAa4EtrbbDuB2GAQGcDPwduAS4ObjoSFJmrwFA6CqjlTV19ryXwBPAZuAbcDuVm03cHVb3gbcWQMPAWcl2Qi8B9hfVceq6kVgP3DFWHsjSRrZomYBJdkCXAx8BTi3qo60Vd8Gzm3Lm4DnhjY71MrmK5ck8TdnbD1763tX/PlGvgic5EeA3wV+qar+fHhdVRVQ42hQkh1JDiQ5cPTo0XHsUpJ0EiMFQJIzGBz8f7uqfq8VP9+Gdmj3L7Tyw8Dmoc3Pa2Xzlf8NVbWzqmaranZmZmYxfZEkLcKCQ0BJAtwBPFVVnxxatRfYDtza7u8dKr8hyR4GF3xfqqojSe4D/t3Qhd/LgZvG0w1pMvxQldaTUa4BvAP4IPB4kkdb2a8wOPDfneQ64FvAB9q6fcBVwBzwPeDDAFV1LMkngIdbvY9X1bGx9EKStGgLBkBVfRnIPKvfdZL6BVw/z752AbsW08Dl8N2aJM3PTwJLUqcMAEnqlAEgSZ0yACSpUwaAJHXKAJCkThkAktQpA0CSOmUASFKnDABJ6pQBIEmdMgAkqVMGgCR1alH/ElJaDr+dVVpdPAOQpE4ZAJLUKQNAkjplAEhSpxYMgCS7kryQ5Imhso8lOZzk0Xa7amjdTUnmknw9yXuGyq9oZXNJbhx/VyRJizHKGcBngStOUn5bVV3UbvsAklwIXAO8tW3zn5OcluQ04NPAlcCFwLWtriRpSkb5p/APJtky4v62AXuq6hXgm0nmgEvaurmqegYgyZ5W98lFt1iSNBbLuQZwQ5LH2hDRhla2CXhuqM6hVjZfuSRpSpYaALcDPwVcBBwBfnNcDUqyI8mBJAeOHj06rt1Kkk6wpACoquer6rWq+mvgt/j/wzyHgc1DVc9rZfOVn2zfO6tqtqpmZ2ZmltI8SdIIlhQASTYOPXw/cHyG0F7gmiRnJrkA2Ap8FXgY2JrkgiQ/yOBC8d6lN1uStFwLXgROchfwTuCcJIeAm4F3JrkIKOBZ4CMAVXUwyd0MLu6+ClxfVa+1/dwA3AecBuyqqoNj740kaWSjzAK69iTFd5yi/i3ALScp3wfsW1TrJEkrxk8CS1KnDABJ6pQBIEmdMgAkqVMGgCR1ygCQpE4ZAJLUKQNAkjplAEhSpwwASeqUASBJnTIAJKlTBoAkdcoAkKROGQCS1CkDQJI6ZQBIUqcMAEnqlAEgSZ1aMACS7EryQpInhsrOTrI/ydPtfkMrT5JPJZlL8liStw1ts73VfzrJ9pXpjiRpVKOcAXwWuOKEshuB+6tqK3B/ewxwJbC13XYAt8MgMICbgbcDlwA3Hw8NSdJ0LBgAVfUgcOyE4m3A7ra8G7h6qPzOGngIOCvJRuA9wP6qOlZVLwL7eX2oSJImaKnXAM6tqiNt+dvAuW15E/DcUL1DrWy+8tdJsiPJgSQHjh49usTmSZIWsuyLwFVVQI2hLcf3t7OqZqtqdmZmZly7lSSdYKkB8Hwb2qHdv9DKDwObh+qd18rmK5ckTclSA2AvcHwmz3bg3qHyD7XZQJcCL7WhovuAy5NsaBd/L29lkqQpOX2hCknuAt4JnJPkEIPZPLcCdye5DvgW8IFWfR9wFTAHfA/4MEBVHUvyCeDhVu/jVXXihWVJ0gQtGABVde08q951kroFXD/PfnYBuxbVOknSivGTwJLUKQNAkjplAEhSpwwASeqUASBJnTIAJKlTBoAkdcoAkKROGQCS1CkDQJI6ZQBIUqcMAEnqlAEgSZ0yACSpUwaAJHXKAJCkThkAktQpA0CSOrWsAEjybJLHkzya5EArOzvJ/iRPt/sNrTxJPpVkLsljSd42jg5IkpZmHGcA/7iqLqqq2fb4RuD+qtoK3N8eA1wJbG23HcDtY3huSdISrcQQ0DZgd1veDVw9VH5nDTwEnJVk4wo8vyRpBMsNgAL+V5JHkuxoZedW1ZG2/G3g3La8CXhuaNtDrUySNAWnL3P7f1hVh5P8LWB/kj8ZXllVlaQWs8MWJDsAzj///GU2T5I0n2WdAVTV4Xb/AvB54BLg+eNDO+3+hVb9MLB5aPPzWtmJ+9xZVbNVNTszM7Oc5kmSTmHJAZDkh5P86PFl4HLgCWAvsL1V2w7c25b3Ah9qs4EuBV4aGiqSJE3YcoaAzgU+n+T4fj5XVf8zycPA3UmuA74FfKDV3wdcBcwB3wM+vIznliQt05IDoKqeAf7OScq/A7zrJOUFXL/U55MkjZefBJakThkAktQpA0CSOmUASFKnDABJ6pQBIEmdMgAkqVMGgCR1ygCQpE4ZAJLUKQNAkjplAEhSpwwASeqUASBJnTIAJKlTBoAkdcoAkKROGQCS1KmJB0CSK5J8Pclckhsn/fySpIGJBkCS04BPA1cCFwLXJrlwkm2QJA1M+gzgEmCuqp6pqr8E9gDbJtwGSRKTD4BNwHNDjw+1MknShJ0+7QacKMkOYEd7+HKSr0+zPSvkHODPpt2ICeiln9BPX3vpJ0y5r/n1ZW3+E6NUmnQAHAY2Dz0+r5V9X1XtBHZOslGTluRAVc1Oux0rrZd+Qj997aWf0EdfJz0E9DCwNckFSX4QuAbYO+E2SJKY8BlAVb2a5AbgPuA0YFdVHZxkGyRJAxO/BlBV+4B9k37eVWZdD3EN6aWf0E9fe+kndNDXVNW02yBJmgK/CkKSOmUATECSn01yMMlfJ5l3VsFa/5qMJGcn2Z/k6Xa/YZ56ryV5tN3W1CSAhV6jJGcm+Z22/itJtky+lcs3Qj9/IcnRodfxn0+jncuVZFeSF5I8Mc/6JPlU+zk8luRtk27jSjIAJuMJ4J8CD85XYZ18TcaNwP1VtRW4vz0+mf9TVRe12/sm17zlGfE1ug54sareDNwGLG829xQs4nfxd4Zex89MtJHj81ngilOsvxLY2m47gNsn0KaJMQAmoKqeqqqFPtC2Hr4mYxuwuy3vBq6eYltWwiiv0fDP4B7gXUkywTaOw3r4XRxJVT0IHDtFlW3AnTXwEHBWko2Tad3KMwBWj/XwNRnnVtWRtvxt4Nx56r0hyYEkDyVZSyExymv0/TpV9SrwEvDGibRufEb9XfxnbVjkniSbT7J+PVgPf5fzWnVfBbFWJfki8OMnWfWrVXXvpNuzUk7Vz+EHVVVJ5pti9hNVdTjJTwJfSvJ4VX1j3G3VivofwF1V9UqSjzA467lsym3SIhkAY1JV/2SZu1jwazJWg1P1M8nzSTZW1ZF2mvzCPPs43O6fSfKHwMXAWgiAUV6j43UOJTkd+DHgO5Np3tiM8pUtw336DPAfJtCuaVgTf5dL5RDQ6rEeviZjL7C9LW8HXnfmk2RDkjPb8jnAO4AnJ9bC5RnlNRr+GfwM8KVaex+2WbCfJ4yDvw94aoLtm6S9wIfabKBLgZeGhjnXvqrytsI34P0Mxg5fAZ4H7mvlbwL2DdW7CvhTBu+Gf3Xa7V5CP9/IYPbP08AXgbNb+Szwmbb8D4DHgT9u99dNu92L7OPrXiPg48D72vIbgP8OzAFfBX5y2m1eoX7+e+Bgex0fAN4y7TYvsZ93AUeAv2p/o9cBHwU+2taHwYyob7Tf19lpt3mcNz8JLEmdcghIkjplAEhSpwwASeqUASBJnTIAJKlTBoAkdcoAkKROGQCS1Kn/B+HrzO1nLxcyAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYcAAAD8CAYAAACcjGjIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzt3X+YnGV97/H3lw2EH2azIBtIdrNNjkQFST3qFjxSe1HoIUHRcK4LDdRKEEoKTZS2nMoS7SFV4VqqSBEoGCWHxCKBg3pIC4qpgaMFIfyoGiHapoBkN4FEl00ikECS7/njeWb23pl5dmfmmZ87n9d17bUz9zwzc8+1s8/3uX99b3N3REREQgfVuwIiItJ4FBxERCSPgoOIiORRcBARkTwKDiIikkfBQURE8ig4iIhIHgUHERHJo+AgIiJ5JtW7AuU6+uijfdasWfWuhohIU3nyySd/7e6d4x3XtMFh1qxZPPHEE/WuhohIUzGzXxVznLqVREQkj4KDiIjkUXAQEZE8Cg4iIpJHwUFERPIoOIiISB4FBxERyaPgICIieRQcREQkT9OukJ6otvVvYP/w3uz9to7JTO87qY41EpFWpODQYPYP76W7//3Z+wN9P6pjbUSkValbSURE8owbHMxspZltN7Of55R/0sx+YWZPm9nfBeVXmtlmM/ulmc0LyufHZZvNrC8on21mj8Xld5nZIZX6cCIiUp5iWg63A/PDAjP7Q2AB8E53fwfwpbj8BOBc4B3xc/7BzNrMrA24GTgTOAE4Lz4W4Frgenc/DngZuCjthxIRkXTGDQ7u/kNgKKf4UqDf3ffGx2yPyxcAa9x9r7s/B2wGTop/Nrv7s+7+OrAGWGBmBpwG3BM/fxVwdsrPJCIiKZU75vBW4P1xd9D/M7Pfi8u7gC3BcQNxWVL5m4Fhd9+XUy4iInVU7mylScBRwHuB3wPuNrP/UrFaJTCzxcBigJ6enmq/nYhIyyq35TAAfNsjG4ADwNHAIDAzOK47Lksq/w3QYWaTcsoLcvcV7t7r7r2dnePuciciImUqt+Xwf4E/BB40s7cChwC/BtYC3zSzLwMzgDnABsCAOWY2m+jkfy7wx+7uZvYgcA7ROMQi4N4Un0dEpCzhAlQtPi0iOJjZncCpwNFmNgBcBawEVsbTW18HFrm7A0+b2d3AM8A+YIm7749fZynwANAGrHT3p+O3uAJYY2ZfAP4NuK2Cn09EpCjhAlQtPi0iOLj7eQkP/UnC8VcDVxcovx+4v0D5s0SzmUREpEFohbSIiORRcBARkTwKDiIikkfBQURE8ihld5P62tIL2bUjylrS3jmNi29aWecaichEouDQpHbt2M7ld/0zANctPKvOtRGRiUbdSiIikkfBQURE8ig4iIhIHgUHERHJo+AgIiJ5FBxERCSPgoOIiORRcBARkTwKDiIikkfBQURE8owbHMxspZltj3d9y33scjNzMzs6vm9m9hUz22xmPzOzdwfHLjKz/4h/FgXl7zGzjfFzvmJmVqkPJyIi5Smm5XA7MD+30MxmAmcALwTFZxLtGz0HWAzcEh97FNH2oicT7fp2lZkdGT/nFuDi4Hl57yUiIrU1bnBw9x8CQwUeuh74NOBB2QJgtUceBTrMbDowD1jn7kPu/jKwDpgfP9bu7o/Ge1CvBs5O95FERCStssYczGwBMOjuP815qAvYEtwfiMvGKh8oUC4iInVUcspuMzscWEbUpVRTZraYqLuKnp6eWr+9iEjLKKfl8BZgNvBTM3se6AaeMrNjgUFgZnBsd1w2Vnl3gfKC3H2Fu/e6e29nZ2cZVRcRkWKUHBzcfaO7T3P3We4+i6gr6N3u/iKwFjg/nrX0XmCnu28DHgDOMLMj44HoM4AH4sd2mdl741lK5wP3VuiziYhImYqZynon8GPgbWY2YGYXjXH4/cCzwGbga8CfA7j7EPB54PH453NxGfExX4+f85/Ad8v7KCIiUinjjjm4+3njPD4ruO3AkoTjVgJ5Gx27+xPAiePVQ0REakcrpEVEJI+Cg4iI5FFwEBGRPCWvcxARaTZfW3ohu3ZsB6C9cxoX35Q3/Ck5FBxEZMLbtWM7l9/1zwBct/CsOtemOSg4iEhTWb3sEXYP7QFgylGHcv4176tzjSYmBQcRaSq7h/aw5NbTALj5kvV1rs3EpQFpERHJo+AgIiJ5FBxERCSPgoOIiORRcBARkTwKDiIikkdTWSeA9s5p2YU9Wv0pIpWg4DABhMFAqz9FRqfLgOiiSUqj4CAiE06YLkPKU8xOcCvNbLuZ/Two+6KZ/cLMfmZm3zGzjuCxK81ss5n90szmBeXz47LNZtYXlM82s8fi8rvM7JBKfkARESldMQPStwPzc8rWASe6++8C/w5cCWBmJwDnAu+In/MPZtZmZm3AzcCZwAnAefGxANcC17v7ccDLwFjbkEqZVi97hJsvWc/Nl6xn9bJH6l0dEWlw4wYHd/8hMJRT9n133xfffRTojm8vANa4+153f45oX+iT4p/N7v6su78OrAEWmJkBpwH3xM9fBZyd8jNJAZl8NEtuPS2btExEJEklprJeCHw3vt0FbAkeG4jLksrfDAwHgSZTLiIidZRqQNrMPgPsA+6oTHXGfb/FwGKAnp6eWrxlU8tNbSwiUqyyg4OZXQCcBZzu7h4XDwIzg8O64zISyn8DdJjZpLj1EB6fx91XACsAent7Pek4iYSpjUVESlFWt5KZzQc+DXzY3V8NHloLnGtmk81sNjAH2AA8DsyJZyYdQjRovTYOKg8C58TPXwTcW95HERGRSilmKuudwI+Bt5nZgJldBNwETAHWmdlPzOxWAHd/GrgbeAb4HrDE3ffHrYKlwAPAJuDu+FiAK4C/MrPNRGMQt1X0E4qISMnG7VZy9/MKFCeewN39auDqAuX3A/cXKH+WaDaTiIg0CCXeExGRPAoOIiKSR8FBRETyKDiIiEgeBQcREcmj4CAiInkUHEREJI+Cg4iI5NFOcCV656fvZudBRwAw9cAr/PTvPlrnGomIVJ6CQ4l2HnQEz/d/EIBZffdV/f3aOiYz0Pej7O3pfVpMLiLVp+BQIeGG5u2d07j4ppUVed0wGGSChIhItSk4pHTdwrOAKCBkNjS/6eLvcPMl64FoH4Xzr3lf3eonIsm29W9g//BeIGqZy4iWDw65X45Su20yASFkbVOz+yhk9m4GBQqRRrN/eC/d/e+vdzUaUssHh/DLsa1/Q8X798NgkAkSIiKNruWDQ0j9+yIiEQWHFLo6Dis4Y6m93VhSh/qIiFTKuMHBzFYS7RW93d1PjMuOAu4CZgHPAx9195fNzIAbgA8ArwIXuPtT8XMWAZ+NX/YL7r4qLn8PcDtwGNFmQJcFe1I3tIf7Cu/PXIspriKtZPWyR9g9tAeIxu6k+oppOdxOtC3o6qCsD/iBu/ebWV98/wrgTKJ9o+cAJwO3ACfHweQqoBdw4EkzW+vuL8fHXAw8RhQc5gPfTf/RmodmTIiMbffQnuwkD6mNYrYJ/aGZzcopXgCcGt9eBTxEFBwWAKvjK/9HzazDzKbHx65z9yEAM1sHzDezh4B2d380Ll8NnE2LBQfNmBCZ+E7pX8/g8GtA1CWd1PPQKModczjG3bfFt18EjolvdwFbguMG4rKxygcKlDe9cP1DpRbEiUjzGhx+rabZFdJKPSDt7m5mNRkjMLPFwGKAnp6eWrwlMDriT3lj17jHtx8wbpx9afb4i+PyKUcd2hBrHhqlHiITRbO1CopRbnB4ycymu/u2uNtoe1w+CMwMjuuOywYZ6YbKlD8Ul3cXOL4gd18BrADo7e2takAKcxoNMhLxoxbBeWM+9892HZrtH53Vd9+oVsSSW6NWRD3XPGjthUh64SD5YEdztQqKUW5wWAssAvrj3/cG5UvNbA3RgPTOOIA8AFxjZkfGx50BXOnuQ2a2y8zeSzQgfT5wY5l1qqhRC+BS/LG7Og4r2IoQkfTq2QoOB8m/mHCOCFsUUw+8kr1QJD4nNLJiprLeSXTVf7SZDRDNOuoH7jazi4BfAZm81fcTTWPdTDSV9RMAcRD4PPB4fNznMoPTwJ8zMpX1u0ywweiweVmLK4r2zmnZL+ChR/5V1d9PpJ7q2Qr+avuebFDo4KCR9+8YOSYcZ7hu4VnZdDs3NkHropjZSkl9KKcXONah8Povd18J5I3MuvsTwInj1UNGd3UlCQe/1WUkUj27DvLsiT/01U/fn70Q7Oo4rKTXDKe1h+qRrl8rpJvIqC/HwmvrVxGRBhWO79VLOOZYqqRp7fVI56Pg0AC+v/MNXtPsIZHUCmVJLkfSJlvhIHTYfTQRKTjU0JQ3dmWbm2H+pdec7JWGuoJEIvVMmZGUhPNL+4bZ1RFNlCy1y6jZKDgUqRLNuk/tXceugWjW742zL80GgsMs9UuLTDjVTJkRtgpKkTTOkCR3h8hmouBQpLuei/r4D5/UXvZrhIPFt19+J1+Mm6XHYlyYqnYirSk8+ZYyO69W6Wp27dhesa6uWlNwKFLmD1ypgaELBu7IvmY9F81otbQ0s/DkW60u2XPYzYuZ7uADrdPMV3BoMOHJOnO/midsrZaWVpA0RbQYLzLSldRK/yMKDg0mNxCM9WXMtGKOPfQiJvFSVDi1B/5yY/YYJQAUyZki2mDTwBs1Zb+CQ4mSprjVQ/bLvvwlWL4zvj111DGZJnd22b6IjCtMe3Es1e1KatSU/QoOJQqDwbb+DRUPFOGXErTlqEg9hGkvVl66ftS4XKtQcEghaS50McIcSGESrvBLCcUPVmfev7vBvrubTzudN7ZuBeDgGTM4bv0P6lwjkdKE65BaiYJDnYT9/5VIwpVplu67ahqT4q6lj3d2EuVArJ83tm7l+F9sAmDT24+va11EamW8NB7hgthjMR6tWc2Kp+BQIcUkxauFF/euzAaK9mD8IWypJA1Oa1qrNINmWFg23tqGCwbuYOHsKwD4fcbfQKweFBwqZNR4Q4Wnu1XihB0Gg6TB6WpPaz14xoxs60FdTK1n3j3z2PrK1rzyGUfM4IFzHgCKS5nRzAvLQtlB6AZN363g0ADCJmahfC0TJe9SGAzUxdR6tr6ylY2LNuaVz101N3u7Uikzctc1NNIU0Wah4NAAwtXSlbRrf+dI11LO+odShFd84VWetJ5wgkGo0VqClZge2uq5z1IFBzP7S+BPAQc2Eu38Nh1YA7wZeBL4uLu/bmaTgdXAe4DfAAvd/fn4da4ELgL2A59yd519KuAbO1aMXIXlrH8Yzz++62+5ddVlQBQQMld84VWetJ5wgkGobi3B6+fCzhcA+GbHDdmd2boY4mFKCw7hNPL2A5b932mEscR6KDs4mFkX8CngBHd/zczuBs4l2ib0endfY2a3Ep30b4l/v+zux5nZucC1wEIzOyF+3juAGcC/mNlb3X1/qk9WZ6Wsfzh8UnvFVjJXamD8t4cOFewCmHHEjGyASGpFhC2NzkvbKKUzTFNfpSQ7X8guAB3su49/JUqMuZAdo6aBd3UcNmrL3oxwjGOw47WqpslIk8KjHtJ2K00CDjOzN4DDgW3AacAfx4+vApYTBYcF8W2Ae4CbzMzi8jXuvhd4zsw2AycBP05Zt7rKNGmLOVF/aOal2ePTrmQuZmB88VufzLYkdu8/nCmf31bwuEID4WEwSGpFhH3LpbY0NPVVsoJWwe79h7Pi398DjD1DKfN/9PDys0ayBpC8Xigc4/hilQeGGzmFRyFlBwd3HzSzLwEvAK8B3yfqRhp2933xYQNAV3y7C9gSP3efme0k6nrqglHTfMPnNL206Ta6Og4ruEFQ6JQ9NzBY4IuddPyUtlez/zhTxuhuKmUgPHdcohI0u6kx5LbmqqHgCuSgVTBl+dSqjMt9tX1PNihUI+Nqo+ZNKkaabqUjia76ZwPDwP8B5leoXknvuRhYDNDT01PNt6qYNKuogVFN4Vl992X/iVb1XJJdPNcFBTcgKWZ19c7XJzO1AoPWSTNRipF08tHsptpK6tJLGmeopOzY2PVzYXnUWmDq+P/j4YXRWCf38CJrSvfHuDwuDzfvWb3skbLTZCTtK9GoeZOKkaZb6Y+A59x9B4CZfRs4Begws0lx66EbGIyPHwRmAgNmNgmYSjQwnSnPCJ8ziruvAFYA9Pb2eoq6N62wCZwNCMunAhfkHdt+wEamyHIDDxd4va//50kjV2QJrYhqL44r5uQTtiIy99WSqKw0XXpLLm1jR4GxqJJblEFr4WtLL2RX3M16eVCdcOC4C7LjDPfueiPxZXMvsgop9Xuduxgv3Fei3J3mGkma4PAC8F4zO5yoW+l04AngQeAcohlLi4B74+PXxvd/HD++3t3dzNYC3zSzLxMNSM8BNqSoV1MblXOJ0QPUUw+8MtLFVEQT+M92HZoNJqf03T5uoEiSZnFc57AXHHcodaA6NxCoJVEZpXYZJU1r3tFhBceZklqU4UBwe++bs88Jjxy12C24cBmVf2z51JGxhSK/m6P2ci+xKykpIORq1tZCKM2Yw2Nmdg/wFLAP+Deiq/r7gDVm9oW47Lb4KbcB34gHnIeIZijh7k/HM52eiV9nSbPPVEojd6ZSGCjO/9Xq7JcxbAIvOXb81334mOuyg3uz9nyz8EFTe7L/hN+b1FZq1Qu6+Zb9BVsFmhJbP7kBoZQuo/BkP++eedm/Y+dwaQ35s9vOp/3YHQAsGRgpH5zUVnDAMew+KrRQNE/wXc7ej7tMw3VFpV7sTJTV2cVINVvJ3a8CrsopfpZotlHusXuAjyS8ztXA1WnqUmlh03XKG5XJfVLJvSBGNYGXF/GEYCyhq3999spp6u+cD9lptL/HxTdFx3WVuC5CmkelxhDCmWub3n48XFb8c9vbdoyaTZTx+3/zj3iBsYFBOnn+0HgS5OQeRrcxCsgdOwu+z5PbL6p4Cu5mm6ZaDK2QThA2XaOr9/NSv2bawelyFApGuf2vhTYEGpzUNhIgShyorsXsFpmY/I0js/93cy+/c3Ramb44mFw/N3uy3+fTSj6JWdvUiqfgHjXw3ORpbjIUHCaIpAHjUtZbhObP7BrpKy6xFVGL2S1SO7mDzeX67VVzeJNF/fW/9Wm8aZzjE9PKBBcqL/b9iO6ya1Scf9pyC6/G6xIaNQtsNSg4TBATJTlfOZol30+ljLWKvBorzMPB5mJ8b8tgwbGrN9n2bFfSeIGhkby6b1fLjDOEFBwaXO4+DI0qPCG85UPFDWaHC9zSSGqpbD7t9Am5iC78vOFnhNEDzFWf0RWsYD7+XEZamJPaskGgmLGrcHzPDn65KlWtlNxFbfXcQ77aFBwqpJg89KHcwekkafIsVVsYEMITAn991Ej5GOMVhU7olcwAm3tFPREDRbGfoyrjQMGahE1vPz7797zwnnlsLTA1NUk4vhfNfvqTytQvhXDK6uGT2rPl4dhC7h7yE42CQ4WUmod+IlxxdO3bnz05zF81N3si+M9/OmbkxF/ieEWavExjafXV1mFrY15w8g5VKh37qNdo0llv4ZTVpPG6ifA/PBYFhwkmdzVzRQRzxu/ouDFIi3wDu8YbqAznm5c466mYDLDNqtRxkkpe+Rez6c6oFelXBqeJoCupmPQW5ahEN2rYkofi1gLJaAoOE0ya1Ba3d38sm6/JDr5i5IHghL617z7+ejhahPTFjk6eH2+gMgwGCVeRSSkWiskAWy3VTh1e6r4IaWeAFTPjKAzG605/kWPfeB2Ajc8xOsAXWJ9QSZUY/M1ryS9P/ZItR8FBsnYf3M4nn7sFgBtnX5otH53LZgdLbr0AKCPFcdCK2AjZ2ysntdH12aExnxqeuGB0+o2kvD6lSlo5nDvoW0gxV/y5x1dbePW/48pJ4844yusOioPA3FVzy06q2EgyLWrfPxLcctNh1GqMb1SanOB/rZEoOMgomau2G4MTf14umzjJX1ewoUrSZiqjBK2I8IRTzIyW3BN+GCiS8vqEitmAKOnqvJhWQ7Wu+EsVfk4WQeZfvFJp1JtZpiXxtaUXct3CKKtPmB8pKs/v0ip28kgpwiB0Y5X3kSiXgoOU7eFDL8teXRaTHjxJOauxw4R+xeT1qdZAdyFpxgeS9rAoJutp5rGJcJVfTUmtg6TyiT7wnETBQUoTdA3t2t9JZpJfmC9/2hnLSkqrW85q7DChX6l5fULVmOKZprWw5M8nsfWV6N+yc/ilbAun1KynRcsdYC5zPw+ZeBQcpCA7+OXReW0ygpPHNy5Zn91prph8+aHcmUi1NKrVscBZf1n+FM9KzY4qdd1GeLI/7YYTy856WrRgrUKYsygpO2qSRWcsY3uBv3up6eHTqMpMvRoIU4gX1T1bIwoOUtCbjru27CvSaa8OjftlT3vize1+KUXY6ghPwGGXTKW6norpzkqarVWp1lHRgsAfrlspxvbDjxp3N8LctNuVPglWehOqWglzSKXpnq00BQepuFXfvyZ7UqvWl71SU0uT9puo5RqL1F1DoQbuJhqkMxtAGukkKIUpOEhzCE96uapwEgyDQbipTaghF+YldBPVNVBkx5ESNpmShpQqOJhZB/B14ETAgQuBXwJ3AbOA54GPuvvLZmbADcAHgFeBC9z9qfh1FgGfjV/2C+6+Kk29pHjhQHJRO2ylfP2yuxLCk16u4CQYZgFN6q4ptUsqKQCk6XoqeW/lchSxADFJ0pjQqDUvxf49M383tRaaStqWww3A99z9HDM7BDgcWAb8wN37zawP6AOuAM4k2h96DnAycAtwspkdRbSbXC9RgHnSzNa6e2OnZ5wgqj34Fb7+KcEOdKHU/c/hLnfBSTCpu6YRku6l6UpKSoldSUkBMVzzEv49p7069iJGaT5lBwczmwr8AfGKKHd/HXjdzBYAp8aHrQIeIgoOC4DV7u7Ao2bWYWbT42PXuftQ/LrrgPnAneXWrSXUIMdNpSUFgPAkU8vZLdWSlKqiUjO0woSHfOEo8vZKrpHw7xm1xD5es/eW6kvTcpgN7AD+t5m9E3iSaD7FMe6+LT7mReCY+HYXsCV4/kBcllQuYxmrm6XJlDoNttElbY5T6vjEWz700rh7ZIxaI1IluV1J0hrSBIdJwLuBT7r7Y2Z2A1EXUpa7u5lVbIK2mS0GFgP09DTH1bKUpuSUHGMYtd9EqEKDs2FLICklR8mCFqG1HcKmNdG11XFn/7rsNQhpjUqfIi0jTXAYAAbc/bH4/j1EweElM5vu7tvibqPt8eODwMzg+d1x2SAj3VCZ8ocKvaG7rwBWAPT29lZpVZDUU9qUHKNScYQbEIUqNIunKlljgxbhwcDx/fmHlLoGoZ7CCQntB6zOtZFSlB0c3P1FM9tiZm9z918CpwPPxD+LgP74973xU9YCS81sDdGA9M44gDwAXGNmR8bHnQFcWW69pLWF3SyJJ9EwGFQoUCQOEietO0iamtsg40eV6koKW36tuL95M0s7W+mTwB3xTKVngU8ABwF3m9lFwK+Aj8bH3k80jXUz0VTWTwC4+5CZfR54PD7uc5nBaZGqSzHdMxQOEo/KMjvWuoMGHjOqdldStadQS3qpgoO7/4RoCmqu0wsc65BNxZP72EqgcTdLltoJd44rY9FUqhlBxexaV8QK5LwssxkNtFq5kFoOPKcZS9rWv4H9w3uBibl3c6PQCumAZmU0gHDNQt/tJU9xTbViOam7KRRe8Se0NGoxg6hScvMdNcPA8/7hvXT3v7/e1ZjwFBwCE3lWRu6GJc2Qo/7hY67LXqXP2lPj1AtlXOUX3HO5EQWtn0G+OWG/85JOg3+LpVLCYJAJEg0vbEXkrK5upNTGGdkEfjXe77pk4TjIBFhXItWh4CBNITcQTITFciKNTMFBpAWc8vrN2bEF+Ga2xaCxNUmi4CDSAgYPHKmxBSmJgoM0pYqlAheRghQcpCklJesra7+BCUpTsyUNBQdpermtiEL7DUykQDHWQr/cgKCuJCmXgoM0vaST/kRLBZ4x1kK/eq3Vyd1drxE2VJJ0FByk5VSr66kSG/mUKvwsUL/uozAYZBcDSlNTcJCWE15dV7JFUauUGeo6klpQcJCW0OxZQBUQpNYUHKQlNPtg9ETO+yWNScFBWprWS4gUpuAgLS0MBmmmvuamvh7vuUmD4rkDzBnN2BUmzS11cDCzNuAJYNDdzzKz2cAa4M3Ak8DH3f11M5sMrAbeA/wGWOjuz8evcSVwEbAf+JS7p0jKL1KeNFNfB+kcd5A7adwgPF7dR9IoKtFyuAzYBLTH968Frnf3NWZ2K9FJ/5b498vufpyZnRsft9DMTgDOBd4BzAD+xcze6u77K1A3kYoafWUfJLBjx7jHJw0kN/tguUxMqYKDmXUDHwSuBv7KzAw4Dfjj+JBVwHKi4LAgvg1wD3BTfPwCYI277wWeM7PNwEnAj9PUTaQaRl3ZL5+asyvcBUDyiu0kGueQRpS25fD3wKeBKfH9NwPD7r4vvj8AdMW3u4AtAO6+z8x2xsd3AY8Grxk+R6Tp6GTf2qYcdSg3X7I+e/v8a95X5xqVp+zgYGZnAdvd/UkzO7VyVRrzPRcDiwF6enrGOVpKNe+eeWx9ZSsAR7zaVufaiDSnMBhkgkQzOijFc08BPmxmzxMNQJ8G3AB0mFkm6HQDg/HtQWAmQPz4VKKB6Wx5geeM4u4r3L3X3Xs7OztTVF0K2frKVjYu2sjGRRv5yEPd9a6OiNRR2cHB3a909253n0U0oLze3T8GPAicEx+2CLg3vr02vk/8+Hp397j8XDObHM90mgNsKLdeIiKSXjXWOVwBrDGzLwD/BtwWl98GfCMecB4iCii4+9NmdjfwDLAPWKKZSiIi9VWR4ODuDwEPxbefJZptlHvMHuAjCc+/mmjGk4iINIA0Yw4iIjJBKTiIiEielsyttK1/A/uH9wLQ1jG5zrURkaqb2hMvVIxv/2Vt9t4oVSMlgmzJ4LB/eC/d/e+vdzVEpFbCYJAJEg2okba2VbeSiIjkacmWg4jUxuplj7B7aA8QpZKQ5qHgICJVs3toD0tuVa6pZqTgIFKu3EFOkQlEwUGkXA0640WkEjQgLSIieRQcREQkj7qVREQaXFvHZAb6fpS9Pb0vL31dxSk4iIg0uDAYZIJEtalbSUTcvS/CAAAHLElEQVRE8ig4iIhIHgUHERHJU3ZwMLOZZvagmT1jZk+b2WVx+VFmts7M/iP+fWRcbmb2FTPbbGY/M7N3B6+1KD7+P8xsUdJ7iohIbaRpOewDLnf3E4D3AkvM7ASgD/iBu88BfhDfBziTaH/oOcBi4BaIgglwFXAy0Q5yV2UCioiI1EfZwcHdt7n7U/Ht3cAmoAtYAKyKD1sFnB3fXgCs9sijQIeZTQfmAevcfcjdXwbWAfPLrZeIiKRXkamsZjYLeBfwGHCMu2+LH3oROCa+3QVsCZ42EJcllYs0hFP61zM4/BoQbcAi0gpSBwczexPwLeAv3H2XmWUfc3c3M0/7HsF7LSbqkqKnR4nOpDYGh1/j+f4P1rsaIjWVKjiY2cFEgeEOd/92XPySmU13921xt9H2uHwQmBk8vTsuGwROzSl/qND7ufsKYAVAb29vxYKOSK7c7RpFWk3ZwcGiJsJtwCZ3/3Lw0FpgEdAf/743KF9qZmuIBp93xgHkAeCaYBD6DODKcuslUgn13LtXpBGkaTmcAnwc2GhmP4nLlhEFhbvN7CLgV8BH48fuBz4AbAZeBT4B4O5DZvZ54PH4uM+5+1CKekmNTDnqUG6+ZH329vnXvK/ONRKRSik7OLj7vwKW8PDpBY53YEnCa60EVpZbF6mPMBhkgoSITAxaIS0iInmUlVVE6uprSy9k14543soH6lsXGaHgICJ1tWvHdi6/658BuH3V3DrXRjIUHESkYcw4YgZz4wAx44gZPHDOA3WuUetScBCRhhEGg7lqRdSVBqRFRCSPWg4JwkGy9s5pda6NiEhtKTgkCAfJWlF75zSuW3hW9vbFN2kZikgrUXCQgsJgkAkSItI6Wj44NFU65uvnws4XottTlZVWRKqn5YNDU6Vj3vkCLN9Z71qISAvQbCUREcmj4CAiInkUHEREJE/LjzmksXrZI+we2gNE+xmIiJSqUaeNKziksHtoD0tu1Y5hIlK+Rp023jDdSmY238x+aWabzayv3vUREWllDREczKwNuBk4EzgBOM/MTqhvrUREWlejdCudBGx292cBzGwNsAB4pq61mqDaOiYz0Pej7O3pfSelfs3/9uO/YdPbo11gD54xI/XriYS29W9g//BeIPrONotm3me9UYJDF7AluD8AnFynukx4YTDIBIm0Dts7xPG/2FSR1xLJtX94L9397693NUqWZp/1ro7DmNV3X/b2w321Hd80d6/pGxashNk5wHx3/9P4/seBk919ac5xi4HF8d23Ab+scFWOBn5d4desNX2GxqDP0Bj0GfL9jrt3jndQo7QcBoGZwf3uuGwUd18BrKhWJczsCXfvrdbr14I+Q2PQZ2gM+gzla4gBaeBxYI6ZzTazQ4BzgbV1rpOISMtqiJaDu+8zs6XAA0AbsNLdn65ztUREWlZDBAcAd78fuL/O1ahal1UN6TM0Bn2GxqDPUKaGGJAWEZHG0ihjDiIi0kAUHHKY2efN7Gdm9hMz+76ZNdWKLjP7opn9Iv4M3zGzjnrXqRxm9hEze9rMDphZ08w2mQhpYMxspZltN7Of17su5TKzmWb2oJk9E3+PLqt3nUplZoea2QYz+2n8Gf62pu+vbqXRzKzd3XfFtz8FnODul9S5WkUzszOA9fEg/7UA7n5FnatVMjM7HjgAfBX4n+7+RJ2rNK44Dcy/A/+daCHn48B57t5UK/3N7A+A3wKr3f3EetenHGY2HZju7k+Z2RTgSeDsZvpbmJkBR7j7b83sYOBfgcvc/dFavL9aDjkygSF2BNBU0dPdv+/u++K7jxKtGWk67r7J3Su9yLHasmlg3P11IJMGpqm4+w+BoXrXIw133+buT8W3dwObiDIxNA2P/Da+e3D8U7PzkYJDAWZ2tZltAT4G/K961yeFC4Hv1rsSLaRQGpimOiFNRGY2C3gX8Fh9a1I6M2szs58A24F17l6zz9CSwcHM/sXMfl7gZwGAu3/G3WcCdwBLx3612huv/vExnwH2EX2GhlTM5xBJw8zeBHwL+IucXoGm4O773f2/EvUAnGRmNevma5h1DrXk7n9U5KF3EK29uKqK1SnZePU3swuAs4DTvYEHlUr4OzSLotLASG3E/fTfAu5w92/Xuz5puPuwmT0IzAdqMlGgJVsOYzGzOcHdBcAv6lWXcpjZfODTwIfd/dV616fFKA1Mg4gHc28DNrn7l+tdn3KYWWdmtqGZHUY00aFm5yPNVsphZt8iyvh6APgVcIm7N83Vn5ltBiYDv4mLHm2m2VYZZvY/gBuBTmAY+Im7z6tvrcZnZh8A/p6RNDBX17lKJTOzO4FTibKBvgRc5e631bVSJTKz3wd+BGwk+l8GWBZnYmgKZva7wCqi79JBwN3u/rmavb+Cg4iI5FK3koiI5FFwEBGRPAoOIiKSR8FBRETyKDiIiEgeBQcREcmj4CAiInkUHEREJM//B0yGI/5kRBrvAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "hb1 = hits.query('seq==1')\n", + "hb2 = hits.query('seq==2')\n", + "hb3 = hits.query('seq==3')\n", + "hb4 = hits.query('seq==4')\n", + "hf1 = hits.query('seq==5 or seq==8')\n", + "hf2 = hits.query('seq==6 or seq==9')\n", + "hf3 = hits.query('seq==7 or seq==10')\n", + "\n", + "print len(hb1), len(hb2), len(hb3), len(hb4), len(hf1), len(hf2), len(hf3)\n", + "plt.hist(hb1['zg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb1['eta'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb1['rg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb2['zg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb2['eta'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb2['rg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb3['zg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb3['eta'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb3['rg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb4['zg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hb4['eta'],log=False, bins=100)\n", + "plt.show()\n", + "data = [hb1['eta'],hb2['eta'],hb3['eta'],hb4['eta'],hf1['eta'],hf2['eta'],hf3['eta']]\n", + "plt.hist(data,log=False, bins=100, histtype='barstacked')\n", + "plt.show()\n", + "plt.hist(data,log=False, bins=100, histtype='step')\n", + "plt.show()\n", + "\n", + "\n", + "plt.hist(hb4['rg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hf1['zg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hf1['rg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hf2['zg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hf2['rg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hf3['zg'],log=False, bins=100)\n", + "plt.show()\n", + "plt.hist(hf3['rg'],log=False, bins=100)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "min/max\n", + " ev ind det charge xg yg zg rg iphi \\\n", + "min 1 0 0 696 -3.133961 -3.443828 -27.465214 2.664958 -32766 \n", + "max 500 4931 95 6379805 3.184488 3.305549 25.885403 3.443848 32766 \n", + "\n", + " tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "min 0 401 1 0 0 0 -3.141559 -3.027653 1 \n", + "max 145940 236569 173 144781 155324 70 3.141505 2.968422 1 \n", + "\n", + " trackID \n", + "min 10000002 \n", + "max 5000114033 \n", + " ev ind det charge xg yg zg rg iphi \\\n", + "min 1 1349 96 627 -6.956163 -7.319355 -27.465387 6.502839 -32767 \n", + "max 500 8491 319 6790313 7.005610 7.180817 25.886454 7.320724 32766 \n", + "\n", + " tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "min 0 401 1 0 0 0 -3.141582 -2.147105 2 \n", + "max 145940 255145 241 145934 81469 164 3.141493 2.089339 2 \n", + "\n", + " trackID \n", + "min 10000000 \n", + "max 5000114028 \n", + " ev ind det charge xg yg zg rg \\\n", + "min 1 2404 320 641 -11.041545 -11.406182 -27.465218 10.633793 \n", + "max 500 11579 671 4936108 11.091197 11.267007 25.886602 11.406182 \n", + "\n", + " iphi tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "min -32766 0 401 1 0 0 0 -3.141551 -1.677385 3 \n", + "max 32766 145940 255145 174 143437 60151 19 3.141545 1.622253 3 \n", + "\n", + " trackID \n", + "min 10000000 \n", + "max 5000114028 \n", + " ev ind det charge xg yg zg rg \\\n", + "min 1 3271 672 350 -16.142319 -16.506441 -27.465492 15.738359 \n", + "max 500 14303 1183 5762376 16.191597 16.367704 25.885468 16.526356 \n", + "\n", + " iphi tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "min -32766 0 401 1 0 0 0 -3.141524 -1.323260 4 \n", + "max 32767 145940 255145 175 140301 20956 54 3.141583 1.272212 4 \n", + "\n", + " trackID \n", + "min 10000005 \n", + "max 5000114028 \n", + " ev ind det charge xg yg zg rg \\\n", + "min 1 4041 1184 797 -16.040541 -16.070353 -35.943035 4.541135 \n", + "max 500 20919 1631 7674224 16.086744 15.934120 34.369064 16.135990 \n", + "\n", + " iphi tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "min -32767 0 401 1 0 0 0 -3.141586 -2.718763 5 \n", + "max 32767 146066 121703 324 140871 40124 65 3.141585 2.671790 8 \n", + "\n", + " trackID \n", + "min 10000002 \n", + "max 5000114052 \n", + " ev ind det charge xg yg zg rg \\\n", + "min 1 4487 1296 805 -16.04524 -16.070236 -43.468384 4.539285 \n", + "max 500 22665 1743 3752645 16.08004 15.932957 41.899700 16.137133 \n", + "\n", + " iphi tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "min -32766 0 401 1 0 0 0 -3.141546 -2.914465 6 \n", + "max 32767 146066 115736 233 143908 60793 59 3.141591 2.876371 9 \n", + "\n", + " trackID \n", + "min 10000002 \n", + "max 5000114033 \n", + " ev ind det charge xg yg zg rg \\\n", + "min 1 4974 1408 650 -16.038914 -16.070156 -52.979748 4.543934 \n", + "max 500 24450 1855 8573611 16.078123 15.933924 51.400307 16.140593 \n", + "\n", + " iphi tkId pt n1 tkId2 pt2 n2 phi eta seq \\\n", + "min -32766 0 401 1 0 0 0 -3.141555 -3.118078 7 \n", + "max 32766 146066 146147 417 141195 60793 50 3.141576 3.087029 10 \n", + "\n", + " trackID \n", + "min 10000010 \n", + "max 5000114059 \n" + ] + } + ], + "source": [ + "print 'min/max'\n", + "print hb1.agg(['min','max'])\n", + "print hb2.agg(['min','max'])\n", + "print hb3.agg(['min','max'])\n", + "print hb4.agg(['min','max'])\n", + "print hf1.agg(['min','max'])\n", + "print hf2.agg(['min','max'])\n", + "print hf3.agg(['min','max'])" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "def build(hh,n) :\n", + " return pd.DataFrame({ 'z'+n : hh['zg'],\n", + " 'r'+n : hh['rg'],\n", + " 'phi'+n : hh['phi'],\n", + " 'pt'+n : hh['pt'],\n", + " 'det'+n : hh['det'],\n", + " 'trackID' : hh['trackID']\n", + " })\n", + "\n", + "def buildXYZ(hh,n) :\n", + " return pd.DataFrame({ 'z'+n : hh['zg'],\n", + " 'x'+n : hh['xg'],\n", + " 'y'+n : hh['yg'],\n", + " 'pt'+n : hh['pt'],\n", + " 'det'+n : hh['det'],\n", + " 'phi'+n : hh['phi'],\n", + " 'trackID' : hh['trackID']\n", + " })\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "def fishBone(hi,hj) :\n", + " mpt=600\n", + "# maxc = 1000./(mpt*87.)\n", + " fb = pd.merge(pd.merge(buildXYZ(hi,'0'),buildXYZ(hj,'1'),on='trackID'),buildXYZ(hj,'2'),on='trackID')\n", + "# pc = phicut(quadc['r1'],quadc['r2'],maxc)\n", + "# d1 = (quadc['phi2']-quadc['phi1'])/pc\n", + " cut = np.logical_and(abs(fb['phi0']-fb['phi1'])<0.05,abs(fb['phi0']-fb['phi2'])<0.05)\n", + " cut = np.logical_and(cut,fb['pt0']>mpt)\n", + " return fb[np.logical_and(cut,fb['det1'].995], bins=100,log=True)\n", + " plt.show()\n", + " plt.hist(d[d>.9999], bins=100,log=True)\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAD1lJREFUeJzt3X+s3Xddx/Hnyy2bEVyBbSRkXemwc1ITfnntjEZBdLHbLANcZI1RhEozzDSamFCCf5EsjpiIWVhCqs4yTTanKOlcySDgUo0DOlBYt2ajFMg6SOqcTmPUOXj7x/0ODpfe9vz63u+5n/t8JCf3nM/5cT/ve9rX/dz393POSVUhSWrX9ww9AUlSvwx6SWqcQS9JjTPoJalxBr0kNc6gl6TGGfSS1DiDXpIaZ9BLUuPOHXoCABdddFFt3bp16GlI0rry2c9+9smquvhstxs06JPsAnZt27aNBx98cMipSNK6k+Sr49xu0NZNVd1TVXs3bdo05DQkqWn26CWpcQa9JDXOoJekxhn0ktQ4g16SGmfQS1LjDHpJatxCvDJWkjaSrfvu/db5r9xybe/fzxW9JDWul6BP8rwkDyb5+T4eX5I0vrGCPsntSU4lObpifGeSR5McT7Jv5Kp3AXfPc6KSpOmMu6I/AOwcHUhyDnAbcDWwHdidZHuSq4BHgFNznKckaUpjHYytqsNJtq4Y3gEcr6oTAEnuAq4Dng88j+Xw/+8kh6rqm3ObsSRpIrPsurkEeHzk8kngyqq6CSDJrwJPrhbySfYCewG2bNkywzQkSWfS266bqjpQVX97huv3V9VSVS1dfPFZ3zdfkjSlWYL+CeDSkcubuzFJ0gKZJeiPAJcnuSzJecANwMFJHiDJriT7n3766RmmIUk6k3G3V94JPABckeRkkj1V9SxwE3AfcAy4u6oenuSb+wlTktS/cXfd7F5l/BBwaNpvPvqZsZKkfviZsZLUON/rRpIaN2jQezBWkvpn60aSGmfrRpIaZ9BLUuPs0UtS4+zRS1LjbN1IUuMMeklqnD16SWqcPXpJapytG0lqnEEvSY0z6CWpcR6MlaTGeTBWkhpn60aSGmfQS1LjDHpJapxBL0mNc9eNJDXOXTeS1DhbN5LUOINekhpn0EtS4wx6SWqcQS9JjTPoJalxBr0kNc4XTElS43zBlCQ1ztaNJDXOoJekxhn0ktQ4g16SGmfQS1LjDHpJapxBL0mNM+glqXEGvSQ1zqCXpMbNPeiTvDzJB5P8VZJ3zvvxJUmTGSvok9ye5FSSoyvGdyZ5NMnxJPsAqupYVd0I/CLwE/OfsiRpEuOu6A8AO0cHkpwD3AZcDWwHdifZ3l33BuBe4NDcZipJmspYQV9Vh4GnVgzvAI5X1Ymqega4C7iuu/3Bqroa+KV5TlaSNLlzZ7jvJcDjI5dPAlcmeR3wZuB8zrCiT7IX2AuwZcuWGaYhSTqTWYL+tKrqfuD+MW63H9gPsLS0VPOehyRp2Sy7bp4ALh25vLkbG5ufMCVJ/Zsl6I8Alye5LMl5wA3AwUkewE+YkqT+jbu98k7gAeCKJCeT7KmqZ4GbgPuAY8DdVfVwf1OVJE1jrB59Ve1eZfwQM2yhTLIL2LVt27ZpH0KSdBZ+OLgkNc73upGkxg0a9O66kaT+2bqRpMbZupGkxhn0ktQ4e/SS1Dh79JLUOFs3ktQ4g16SGmePXpIaZ49ekhpn60aSGmfQS1LjDHpJapwHYyWpcR6MlaTG2bqRpMYZ9JLUOINekhpn0EtS49x1I0mNO3fIb15V9wD3LC0tvWPIeUhS37buu3ew723rRpIaZ9BLUuMMeklqnEEvSY0z6CWpcQa9JDXOoJekxvmCKUlqnG9TLEmNs3UjSY0b9C0QJKlVQ77lwUqu6CWpcQa9JDXOoJekxhn0ktQ4g16SGueuG0mak0XaaTPKFb0kNc6gl6TG9dK6SfJG4FrgAuBPqupjfXwfSdLZjb2iT3J7klNJjq4Y35nk0STHk+wDqKqPVNU7gBuBt8x3ypKkSUyyoj8AfAC447mBJOcAtwFXASeBI0kOVtUj3U1+t7tekpq0qAdgR429oq+qw8BTK4Z3AMer6kRVPQPcBVyXZe8DPlpVn5vfdCVJk5q1R38J8PjI5ZPAlcBvAD8LbEqyrao+uPKOSfYCewG2bNky4zQkqV+jK/ev3HLtgDOZXC8HY6vqVuDWs9xmP7AfYGlpqfqYhyT1YT20a0bNur3yCeDSkcubu7Gx+AlTktS/WYP+CHB5ksuSnAfcABwc985+wpQk9W+S7ZV3Ag8AVyQ5mWRPVT0L3ATcBxwD7q6qh/uZqiRpGmP36Ktq9yrjh4BD03zzJLuAXdu2bZvm7pI0d+v5oOtqBn1Ts6q6B7hnaWnpHUPOQ9LGttrB1fV20HU1vteNJDVu0KB3140k9W/QoHfXjST1z9aNJDXO1o0kNc5dN5Kas9oWyRa3To7Dz4yVtCG1snVyHPboJalxg67ofWWspL5tpJX7atxeKUmNs3UjSY3zYKykJtiiWZ1BL2ld2ahbJGfhwVhJC8/V+mx8wZSkheFqvR+2biQZsI0z6CUtJNs18+P2SklqnCt6aYNyxbxxGPSS1i1/WY3H7ZWSBmVY98/3upGkxtm6kTSTlStyt2cuHoNe0lyN8+lOWlsGvbSODfVCJ0N7fXEfvSQ1zhW9mufL+4fjyn8xGPTSgprlF9RqATvLLzpDe/0y6KUptdof9y+g9viCKS2UjRgyG7FmrS3fj16aM4Nbi8ZdN5LUOINekhrnwVg1aa13iKzlAVJpUga9BjGvPrb98Mn4C2NjsnUjSY1zRS8NYLWV9aKtuBdtPppOU0G/KH/GL8o8pjXN284uQs0bPZQ2ev1ana0bSWpcUyv69arv1fAirLbnaaiVqytmrVeu6CWpcXNf0Sd5GfAeYFNVXT/vxx9Xa6vYcaxFzev95zrO/Nd7jdJKY63ok9ye5FSSoyvGdyZ5NMnxJPsAqupEVe3pY7KSpMmNu6I/AHwAuOO5gSTnALcBVwEngSNJDlbVI/Oe5Jls9L7petmmB4s5p+cs8tykWY21oq+qw8BTK4Z3AMe7FfwzwF3AdXOenyRpRrP06C8BHh+5fBK4MsmFwM3Aq5O8u6p+73R3TrIX2AuwZcuWGaZxduP2XGf5VB5XhPO3CO8f4/OqFsz9YGxV/Stw4xi32w/sB1haWqp5z0OStGyWoH8CuHTk8uZubGwb7ROmZtnx0febgA25cnXVLPVrln30R4DLk1yW5DzgBuDgJA9QVfdU1d5NmzbNMA1J0pmMu73yTuAB4IokJ5PsqapngZuA+4BjwN1V9XB/U5UkTWOs1k1V7V5l/BBwaNpvPkTrZpoWyEZ/AY2tFWl9G/QtEGzdSFL/fK8bSWrcoO9euRF23aynV65OapzaNmKrS1o0tm4kqXG2biSpcbZuFljL7Z2NyJ+FhmLrRpIaZ+tGkhpn0EtS4zZ0j36anmlLfdaWallU/oy1COzRS1LjbN1IUuMMeklqnEEvSY0bNOiT7Eqy/+mnnx5yGpLUNA/GSlLjbN1IUuMMeklqnEEvSY0z6CWpcQa9JDXO7ZWS1Di3V0pS42zdSFLjDHpJapxBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUuA394eDrlR84LWkSvmBKkhpn60aSGmfQS1LjDHpJapxBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUuFTV0HMgyb8AX53y7hcBT85xOuuBNW8M1rwxzFLzS6vq4rPdaCGCfhZJHqyqpaHnsZaseWOw5o1hLWq2dSNJjTPoJalxLQT9/qEnMABr3hiseWPoveZ136OXJJ1ZCyt6SdIZLFTQJ9mZ5NEkx5PsO831L03yiSRfSHJ/ks0j170vydHu9JaR8QNJvpzkn7vTq9aqnnH0VHOS3JzksSTHkvzmWtUzjp5q/vuR5/hrST6yVvWMo6eafybJ57qa/yHJQn2CT081v76r+WiSDyUZ9MOTVkpye5JTSY6ucn2S3Nr9TL6Q5DUj1701yRe701tHxn8kyUPdfW5NkoknVlULcQLOAb4EvAw4D/g8sH3Fbf4SeGt3/vXAn3XnrwU+zvInZj0POAJc0F13ALh+6PrWuOa3AXcA39NdfvHQtfZd84r7fxj4laFrXYPn+THg5d35XwcODF1rnzWzvDB9HPjB7nbvBfYMXeuKmn4KeA1wdJXrrwE+CgT4MeDT3fiLgBPd1xd251/YXfeZ7rbp7nv1pPNapBX9DuB4VZ2oqmeAu4DrVtxmO/DJ7vzfjVy/HThcVc9W1X8BXwB2rsGcZ9VXze8E3ltV3wSoqlM91jCpXp/nJBewHBqLtKLvq+ZiOQABNgFf62n+0+ij5guBZ6rqse52Hwd+occaJlZVh4GnznCT64A7atmngBckeQnwc8DHq+qpqvo3lmvb2V13QVV9qpZT/w7gjZPOa5GC/hKWf1s/52Q3NurzwJu7828Cvj/Jhd34ziTfl+Qi4KeBS0fud3P3Z9L7k5zfz/Sn0lfNPwC8JcmDST6a5PLeKphcn88zLP8n+ERV/cfcZz69vmr+NeBQkpPALwO39DT/afRR85PAuUmee3HR9Xz387/oVvu5nGn85GnGJ7JIQT+O3wFem+SfgNcCTwDfqKqPAYeAfwTuBB4AvtHd593ADwE/yvKfRe9a60nPaJqazwf+p5ZfbfdHwO1rPuvZTFPzc3Z3160309T828A1VbUZ+FPgD9Z81rOZqOZuRXsD8P4knwH+k+9+/nUaixT0T/Cdv503d2PfUlVfq6o3V9Wrgfd0Y//efb25ql5VVVex3Mt6rBv/evdn0v+y/J9hR/+ljK2Xmln+rf/X3fm/AV7RXwkT66tmutXfDuDefkuY2NxrTnIx8Mqq+nT3EH8B/HjPdUyir//PD1TVT1bVDuAwI8//OrHaz+VM45tPMz6ZeR+MmPbE8oGXE8BlfPvgzQ+vuM1FfPsA480s96Fh+cDPhd35VwBHgXO7yy/pvgb4Q+CWoWtdg5pvAd7enX8dcGToWvuuuRu7EfjQ0DWuRc3d6Um+fWByD/DhoWtdg3/bL+6+ng98Anj90LWepvatrH4w9lq+82DsZ7rxFwFfZvlA7Au78y/qrlt5MPaaiec09A9lxQ/hGpZ/Q38JeE839l7gDd3564Evdrf5Y+D8bvx7gUe606eAV4085ieBh7p/LH8OPH/oOteg5hewvKp9iOU/e185dJ1919xdfz+wc+j61vB5flP3HH++q/1lQ9e5BjX/PnAMeBT4raFrPE3NdwJfB/6P5b+s97C8ALmxuz7Abd3P5CFgaeS+bweOd6e3jYwvdfn1JeADdC90neTkK2MlqXGL1KOXJPXAoJekxhn0ktQ4g16SGmfQS1LjDHpJapxBL0mNM+glqXH/D5EFVIGJvGleAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAEJCAYAAACXCJy4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAEABJREFUeJzt3W2sZdVdx/HvrzMOWhumULBFYBzIIHbii9ZeodU0JaatQ3GKkmpnUlOohAlt8F0TIfjCmJi0NZqUgKGTlhCN8lCidSZMM221BDWUMmgfgHFkStoyBIWWME19IWL/vjh7yuFyz51z79n3nnPv+n6SG85ZZ5+115qHH3v+e++1U1VIkta/V017AJKk1WHgS1IjDHxJaoSBL0mNMPAlqREGviQ1wsCXpEYY+JLUCANfkhqxcdoDADjjjDNq69at0x6GJK0pDz/88Peq6sxxt5+JwN+6dSuHDh2a9jAkaU1J8p2lbG9JR5IaYeBLUiMMfElqxFQDP8nOJHuPHz8+zWFIUhOmGvhVtb+q9mzevHmaw5CkJljSkaRGGPiS1AgDX5IaMRM3XklSS7Zef++PX3/7Y5et2n49wpekRhj4ktQIA1+SGmHgS1IjDHxJaoSBL0mNMPAlqREGviQ1ovfAT/LGJLcmuSfJh/vuX5K0PGMFfpLbkjyT5JF57TuSHElyNMn1AFV1uKquBX4H+NX+hyxJWo5xj/BvB3YMNyTZANwCXApsB3Yn2d599l7gXuBAbyOVJE1krMCvqvuB5+Y1XwQcraonquoF4E7g8m77fVV1KfCBPgcrSVq+SRZPOxt4cuj9MeDiJJcAVwCnsMgRfpI9wB6ALVu2TDAMSZp9wwumTUvvq2VW1X3AfWNstxfYCzA3N1d9j0OS9HKTXKXzFHDu0PtzujZJ0gyaJPAfAi5Icl6STcAuYN9SOvAh5pK0esa9LPMO4AHgwiTHklxdVS8C1wEHgcPA3VX16FJ27kPMJWn1jFXDr6rdI9oP4KWXkrQmTHVpBUs6krR6phr4lnQkafW4eJokNcLAl6RGWMOXpEZYw5ekRljSkaRG9L6WjiRpNhZLm88aviQ1whq+JDXCGr4kNcLAl6RGGPiS1AhP2kpSIzxpK0mNsKQjSY0w8CWpEQa+JDXCwJekRniVjiQ1YqqLp1XVfmD/3NzcNdMchyT1YRYXTBtmSUeSGmHgS1IjDHxJaoSBL0mNMPAlqRFelilJjXDxNElqhCUdSWqEgS9JjTDwJakRBr4kNcLAl6RGGPiS1IiprpYpSWvdrK+QOcwjfElqhIEvSY1waQVJaoRLK0hSIyzpSFIjDHxJaoSBL0mNMPAlqREGviQ1wjttJWmJ1tLdtcM8wpekRhj4ktQIA1+SGmHgS1IjDHxJaoSBL0mNMPAlqRErch1+kt8ELgNOBT5TVV9Yif1IksY39hF+ktuSPJPkkXntO5IcSXI0yfUAVfW5qroGuBZ4f79DliQtx1JKOrcDO4YbkmwAbgEuBbYDu5NsH9rkD7vPJUlTNnbgV9X9wHPzmi8CjlbVE1X1AnAncHkGPg58vqr+tb/hSpKWa9KTtmcDTw69P9a1/T7wTuB9Sa5d6ItJ9iQ5lOTQs88+O+EwJEknsyInbavqJuCmk2yzF9gLMDc3VysxDknqy1pdMG3YpEf4TwHnDr0/p2uTJM2YSQP/IeCCJOcl2QTsAvaN++UkO5PsPX78+ITDkCSdzFIuy7wDeAC4MMmxJFdX1YvAdcBB4DBwd1U9Om6fVbW/qvZs3rx5qeOWJC3R2DX8qto9ov0AcKC3EUmSVsRUl1awpCNJq2eqgW9JR5JWj4unSVIjLOlIUiMs6UhSIyzpSFIjDHxJasSKrKUzriQ7gZ3btm2b5jAkaUHrYf2cYdbwJakRlnQkqREGviQ1wsCXpEZ445UkNWKqV+lU1X5g/9zc3DXTHIcknbDerswZZklHkhph4EtSIwx8SWqEgS9JjfAqHUlqhEsrSFIjLOlIUiMMfElqhIEvSY0w8CWpEQa+JDXCwJekRviIQ0nNW88Lpg3zOnxJasRUj/AlaVpaOaofZg1fkhph4EtSIwx8SWqEgS9JjfCkraRmtHiidphH+JLUCANfkhph4EtSI3zEoSQ1wqUVJKkRlnQkqREGviQ1wsCXpEYY+JLUCANfkhph4EtSI1xLR9K61vr6OcM8wpekRhj4ktQIA1+SGmHgS1IjDHxJaoSBL0mN6D3wk5yf5DNJ7um7b0nS8o11HX6S24DfAJ6pql8cat8BfBLYAHy6qj5WVU8AVxv4kqbFa+8XNu4R/u3AjuGGJBuAW4BLge3A7iTbex2dJKk3Yx3hV9X9SbbOa74IONod0ZPkTuBy4LFx+kyyB9gDsGXLljGHK0kL86j+5Cap4Z8NPDn0/hhwdpLXJbkVeHOSG0Z9uar2VtVcVc2deeaZEwxDkjSO3tfSqarvA9f23a8kaTKTHOE/BZw79P6crm1sPsRcklbPJIH/EHBBkvOSbAJ2AfuW0oEPMZek1TNW4Ce5A3gAuDDJsSRXV9WLwHXAQeAwcHdVPbpyQ5UkTWLcq3R2j2g/ABxY7s6T7AR2btu2bbldSFrA8BUr3/7YZVMcycJmfXzr1VSXVrCkI0mrx7V0JKkRBr4kNWKqz7S1hi/NhlE19VF3r/ZZd19qPd/6//JZw5ekRljSkaRGGPiS1Ahr+FrTrOdO3/w6v78Ps8saviQ1wpKOJDXCwJekRhj4ktQIT9pKa8wkJ6pX4yR3X+Mb5ongfnjSVpIaYUlHkhph4EtSIwx8SWqEgS9JjfAqHfVuWldarIdlFlZiqeBRvx+j2lfKau9Pr+RVOpLUCEs6ktQIA1+SGmHgS1IjDHxJaoSBL0mNMPAlqRFr/jr8pV7zPe61wH1dAz1r+rxGvq/5z/Kv43LGttRr42dtzuMY9+9RX9fez8q9BGud1+FLUiMs6UhSIwx8SWqEgS9JjTDwJakRBr4kNcLAl6RGGPiS1AgDX5IasebvtF0Nq3k336g7MMe5Q7avu2gXm+9avCt02CS/l/O/2+ev68m2X81fd+9eXb+801aSGmFJR5IaYeBLUiMMfElqhIEvSY0w8CWpEQa+JDXCwJekRhj4ktQIA1+SGmHgS1IjDHxJaoSBL0mNMPAlqRG9L4+c5KeBvwBeAO6rqr/uex+SpKUb6wg/yW1JnknyyLz2HUmOJDma5Pqu+Qrgnqq6Bnhvz+OVJC3TuCWd24Edww1JNgC3AJcC24HdSbYD5wBPdpv9Xz/DlCRNaqzAr6r7gefmNV8EHK2qJ6rqBeBO4HLgGIPQH7t/SdLKm6SGfzYvHcnDIOgvBm4Cbk5yGbB/1JeT7AH2AGzZsmWCYSxs0se09fU4urXyuLhxx9nXIwJHPbKvr8c0jtPnpMbpd1qPx5QW0vtJ26r6b+BDY2y3F9gLMDc3V32PQ5L0cpOUXJ4Czh16f07XJkmaQZME/kPABUnOS7IJ2AXsW0oHSXYm2Xv8+PEJhiFJGse4l2XeATwAXJjkWJKrq+pF4DrgIHAYuLuqHl3Kzqtqf1Xt2bx581LHLUlaorFq+FW1e0T7AeBAryOSJK2IqV42aUlHklbPVAPfko4krR5vjJKkRljSkaRGpGr69zwleRb4zhK/dgbwvRUYzqxrcd7OuQ0tzhkmm/fPVdWZ4248E4G/HEkOVdXctMex2lqct3NuQ4tzhtWdtzV8SWqEgS9JjVjLgb932gOYkhbn7Zzb0OKcYRXnvWZr+JKkpVnLR/iSpCWYWuAnOT3JF5M83v33tBHbXdlt83iSK4fa35Lkm93zdG9KksX6TfILSR5I8j9JPjpvHws9m3c9zDnddkeTfCPJLw319YkkjyY5PNzXOp/zliRf6Ob8WJKtKzHnWZt39/mpGSx8ePN6n3OSN2Xwd/3Rrv39KzDXRTMjySlJ7uo+f3D4z1qSG7r2I0l+/WR9ZrAi8YNd+10ZrE686D5Gqqqp/ACfAK7vXl8PfHyBbU4Hnuj+e1r3+rTus68CbwUCfB64dLF+gZ8Bfhn4E+CjQ/vYAHwLOB/YBHwd2L5O5vyebrt033uwa/8V4F+6uW9gsBLqJet5zt1n9wHv6l6/Bnj1OvrzPXLe3eefBP4GuHm9zxn4eeCC7vXPAk8Dr+1xnifNDOAjwK3d613AXd3r7d32pwDndf1sWKxP4G5gV/f6VuDDi+1j0bGv1G/+GL9oR4CzutdnAUcW2GY38Kmh95/q2s4C/n2h7U7WL/BHvDzw3wYcHHp/A3DDepjzie/O338354eBnwJeDRwC3rjO57wd+Of1+ud71Ly7129h8Mzpq1jZwJ+ZOc/b59fp/gfQ0zxPmhkMlo1/W/d6I4MbqzJ/2xPbjeqz+873gI3z9z1qH4uNfZo1/NdX1dPd6/8EXr/ANgs9N/fs7ufYAu3j9jvOPlbCas95wb6q6gHgywyOfJ5m8Afo8LJmdHIzMWcGR33PJ/nbJP+W5E+TbFjmnMYxE/NO8irgz4CXlTFXyEzMeXhnSS5icMT8rSXNZHHjZMaPt6nBs0OOA69b5Luj2l8HPN/1MX9fo/YxUu/PtB2W5EvAGxb46MbhN1VVSXq/XGil+l3MWphzkm3AGxk8lhLgi0neXlX/tJx9roU5M/iz/nbgzcB3gbsYHPF+Zrn7XSPz/ghwoKqOpYfTNGtkzgAkOQv4K+DKqvpR32NZi1Y08KvqnaM+S/JfSc6qqqe735hnFtjsKeCSoffnMKjDPsVLYXWi/cTzdMfpd/4+ens274zNedTcfhf4SlX9sBvX5xn8U3FZgb9G5rwR+FpVPdGN63MM6r7LDvw1Mu+3AW9P8hEG5y02JflhVS3r4oQ1MmeSnArcC9xYVV8Zc3rjGiczTmxzLMlGYDPw/ZN8d6H27wOvTbKxO4of3n7UPkaaZklnH3DiDP2VwN8vsM1B4N1JTuvOzL+bQfnhaeAHSd7ancn/4ND3x+l32MTP5l2C1Z7zPuCD3dUMbwWOd/18F3hHko1JfgJ4B4PHVK6EWZnzQwz+4pxYaOrXgMd6m+UrzcS8q+oDVbWlqrYyKOv85XLDfgwzMefu7/HfMZjrPT3PEcbLjOExvw/4xxoU2/cBu7orbM4DLmBwsnrBPrvvfLnrA145/4X2MVpfJzKW+sOg1vQPwOPAl4DTu/Y54NND2/0ecLT7+dBQ+xzwCIPa3M28dBPZqH7fwKD+9QPg+e71qd1n7wH+o+vrxnU05wC3dNt/E5jr2jcwOOF1mEHo/fl6n3P32buAb3TttwObWpj3UJ9XsbInbWdizgz+Bfu/wNeGft7U81xfkRnAHwPv7V7/JPDZbo5fBc4f+u6N3feO0F2JNKrPrv38ro+jXZ+nnGwfo36801aSGuGdtpLUCANfkhph4EtSIwx8SWqEgS9JMyzJb2ewENyPkkz0KEQDX5JmRJJLktw+r/kR4Arg/kn7X9E7bSVJk6lunas+lsbwCF+SGuERviRNWZIHGayR/xrg9CRf6z76g6o62Nd+DHxJmrKquhgGNXzgqqq6aiX2Y0lHkhph4EvSDEvyW0mOMVjq+t4kyy7xuHiaJDXCI3xJaoSBL0mNMPAlqREGviQ1wsCXpEYY+JLUCANfkhph4EtSI/4fVErfaeADFJIAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAEHJJREFUeJzt3X+s3Xddx/Hniy0bEVyBrSRkXemwc1ITfnktRqNMlNBtlsFcZNUoQqUZZhpNTCjBxIhZMmMiZmEJqVLLMNmcgGRzJYOASzEOaIfCujUbpUDWQVLnsP6IOgdv/7jfscOl9+6ce873fu/93OcjObnnfL7f8z2f9z3t+3zu+/P5nm+qCklSu541dAckSf0y0UtS40z0ktQ4E70kNc5EL0mNM9FLUuNM9JLUOBO9JDXORC9JjTt76A4AXHDBBbVly5ahuyFJa8p99933WFVtfKb9Zp7ok1wG/BHwAHBbVd3zTM/ZsmULR44cmXVXJKlpSb4+zn5jlW6S7E9yKsnRBe07kjyU5HiSvV1zAf8JPBs4OUmnJUmzN26N/gCwY7QhyVnAzcDlwDZgV5JtwGeq6nLgncAfzq6rkqTlGCvRV9Uh4PEFzduB41V1oqqeAG4Drqqq73TbvwWcO7OeSpKWZZoa/YXAIyOPTwKvTnI18HrgecD7Fntykj3AHoDNmzdP0Q1J0lJmPhlbVR8FPjrGfvuAfQBzc3N+Kb4k9WSadfSPAheNPN7UtY0tyc4k+06fPj1FNyRJS5km0R8GLklycZJzgGuBOyY5QFXdWVV7NmzYMEU3JElLGXd55a3AvcClSU4m2V1VTwLXA3cDx4Dbq+qB/roqSVqOsWr0VbVrkfaDwMHlvniSncDOrVu3LvcQkrTmbNl713fvf+3GK3t/vUG/68bSjST1zy81k6TGDZroXXUjSf2zdCNJjbN0I0mNs3QjSY2zdCNJjbN0I0mNM9FLUuOs0UtS46zRS1LjLN1IUuNM9JLUOGv0ktQ4a/SS1DhLN5LUOBO9JDXORC9JjTPRS1LjXHUjSY1z1Y0kNc7SjSQ1zkQvSY0z0UtS40z0ktQ4E70kNc5EL0mNM9FLUuM8YUqSGucJU5LUOEs3ktQ4E70kNc5EL0mNM9FLUuNM9JLUOBO9JDXORC9JjTPRS1LjTPSS1LheEn2S5yQ5kuQX+ji+JGl8YyX6JPuTnEpydEH7jiQPJTmeZO/IpncCt8+yo5Kk5Rl3RH8A2DHakOQs4GbgcmAbsCvJtiSvAx4ETs2wn5KkZTp7nJ2q6lCSLQuatwPHq+oEQJLbgKuA5wLPYT75/3eSg1X1nZn1WJI0kbES/SIuBB4ZeXwSeHVVXQ+Q5NeBxxZL8kn2AHsANm/ePEU3JElL6W3VTVUdqKq/W2L7vqqaq6q5jRs39tUNSVr3pkn0jwIXjTze1LWNzQuPSFL/pkn0h4FLklyc5BzgWuCOSQ7ghUckqX/jLq+8FbgXuDTJySS7q+pJ4HrgbuAYcHtVPdBfVyVJyzHuqptdi7QfBA4u98WT7AR2bt26dbmHkCQ9A68ZK0mNGzTROxkrSf1zRC9JjfPbKyWpcSZ6SWqcNXpJapw1eklqnKUbSWqcpRtJapylG0lqnKUbSWqciV6SGmeil6TGORkrSY1zMlaSGmfpRpIaZ6KXpMaZ6CWpcSZ6SWqcq24kqXGuupGkxlm6kaTGmeglqXEmeklqnIlekhpnopekxpnoJalxJnpJapwnTElS4zxhSpIaZ+lGkhpnopekxpnoJalxJnpJapyJXpIaZ6KXpMaZ6CWpcSZ6SWrc2UN3QJLWgy177xrstWc+ok/y0iTvT/LhJO+Y9fElSZMZK9En2Z/kVJKjC9p3JHkoyfEkewGq6lhVXQf8EvBTs++yJGkS447oDwA7RhuSnAXcDFwObAN2JdnWbXsDcBdwcGY9lSQty1iJvqoOAY8vaN4OHK+qE1X1BHAbcFW3/x1VdTnwK7PsrCRpctNMxl4IPDLy+CTw6iSXAVcD57LEiD7JHmAPwObNm6fohiRpKTNfdVNV9wD3jLHfPmAfwNzcXM26H5KkedOsunkUuGjk8aaubWxeeESS+jdNoj8MXJLk4iTnANcCd0xyAC88Ikn9G3d55a3AvcClSU4m2V1VTwLXA3cDx4Dbq+qBSV7cEb0k9S9Vw5fH5+bm6siRI0N3Q5JmZtwzYb9245XLfo0k91XV3DPt53fdSFLjBk30lm4kqX+DJnonYyWpf5ZuJKlxlm4kqXGDfh99Vd0J3Dk3N/f2IfshSbMw5HfOL8XSjSQ1zkQvSY2zRi9JjXN5pSQ1ztKNJDVu0FU3krTWrdaVNqMc0UtS45yMlaTGORkrSY2zdCNJjTPRS1LjXHUjSRNaCyttRjmil6TGuepGkhrnqhtJapylG0lqnIlekhrnqhtJGsNaW2kzykQvSYtYy8l9lKUbSWqciV6SGmfpRpJGtFKuGeUJU5LUOE+YkqTGWaOXpMaZ6CWpcU7GSlr3WpyAHeWIXpIa54heUtNGR+tfu/HKAXsyHEf0ktQ4R/SS1qXW6/KjTPSS1o31lNxHmeglNWe9JvTF9JLok7wRuBI4D/hAVX2ij9eRJD2zsSdjk+xPcirJ0QXtO5I8lOR4kr0AVfWxqno7cB3w5tl2WZI0iUlG9AeA9wG3PNWQ5CzgZuB1wEngcJI7qurBbpff77ZLUq8s1yxu7ERfVYeSbFnQvB04XlUnAJLcBlyV5BhwI/DxqvrCmY6XZA+wB2Dz5s2T91zSuuFa+OlMu47+QuCRkccnu7bfAn4euCbJdWd6YlXtq6q5qprbuHHjlN2QJC2ml8nYqroJuKmPY0ta3xzdT27aEf2jwEUjjzd1bWPxwiOS1L9pR/SHgUuSXMx8gr8W+OVxn1xVdwJ3zs3NvX3Kfkhah5yAHc8kyytvBe4FLk1yMsnuqnoSuB64GzgG3F5VD0xwTEf0ktSzSVbd7Fqk/SBwcDkv7oheWp+ss68svwJB0qpkWWZ2Bk30SXYCO7du3TpkNyRNqK8Rucm9H4N+H31V3VlVezZs2DBkNySpaZZuJM2U9ffVx9KNpEFZrumfpRtJapylG0lTcUS++pnoJa3IKhrr9cOxRi9pUY7W2zBoovfMWGllDZm4/dAYjqUbaRXpo9RhgpWJXuuKNWOtRyZ6aQlr6YNhViN3/wJoj5Ox0gDG+QBZbB8TsSblCVOS1DhLN9KYFo6kV3spR3qKiV5aYKVLI5Zi1DcTvbRMKzlR64eBpmGil2ZsLa3U0frgqhutWn0nTBOy1gtX3UhS4yzdrDKOMs9smnXnK2GxGrrvp1YDE73WtHESrLTemejVK0e00vBM9Brcahh9r0QfVkOcWp8GnYyVJPXPEb1mYtISjaNbaeWY6NeBaZOwF8CQ1rZBSzdJdibZd/r06SG7IUlNa+qasa2t8GgtHknDsHTTkNXywdB3icYSkDSZNZ/o1+oZiSv5PS599cGEK60NLq+UpMaZ6CWpcWu+dDMNLw33NMswUrvWdaJfj6ZN6H4gSGuPpRtJapwj+jFMszplta/+GYejeGltM9GvEYt9YJiEJT2TmZdukrwkyQeSfHjWx5YkTW6sRJ9kf5JTSY4uaN+R5KEkx5PsBaiqE1W1u4/OSpImN+6I/gCwY7QhyVnAzcDlwDZgV5JtM+2dJGlqY9Xoq+pQki0LmrcDx6vqBECS24CrgAfHOWaSPcAegM2bN4/Z3ZXTR+3berqkIUxTo78QeGTk8UngwiTnJ3k/8Mok71rsyVW1r6rmqmpu48aNU3RDkrSUma+6qap/Ba6b9XElScszTaJ/FLho5PGmrm1sSXYCO7du3TpFNyazEmeGTrPWvo/9Ja1v05RuDgOXJLk4yTnAtcAdkxygqu6sqj0bNmyYohuSpKWMNaJPcitwGXBBkpPAH1TVB5JcD9wNnAXsr6oHJnnxlRrROwKWtJ6Nu+pm1yLtB4GDy33xWV9KUJL0/fxSM0lq3KDfdTPEZOxSZlXisVQkaTUZdETvZKwk9c/SjSQ1btBEn2Rnkn2nT58eshuS1DRLN5LUOEs3ktQ4E70kNc4avSQ1zhq9JDXO0o0kNS5VNXQfSPIvwNeX+fQLgMdm2J21wJjXB2NeH6aJ+cVV9YxXbloViX4aSY5U1dzQ/VhJxrw+GPP6sBIxW7qRpMaZ6CWpcS0k+n1Dd2AAxrw+GPP60HvMa75GL0laWgsjeknSElZVok+yI8lDSY4n2XuG7S9O8qkkX0pyT5JNI9v+OMnR7vbmkfYDSb6a5J+72ytWKp5x9BRzktyQ5OEkx5L89krFM46eYv7MyHv8jSQfW6l4xtFTzD+X5AtdzP+QZHVcwafTU8yv7WI+muSDSQa9eNJCSfYnOZXk6CLbk+Sm7nfypSSvGtn2liRf7m5vGWn/sST3d8+5KUkm7lhVrYob8xcY/wrwEuAc4IvAtgX7/A3wlu7+a4EPdfevBD7J/BWzngMcBs7rth0Arhk6vhWO+a3ALcCzuscvHDrWvmNe8PyPAL82dKwr8D4/DLy0u/+bwIGhY+0zZuYHpo8AP9zt9x5g99CxLojpZ4BXAUcX2X4F8HEgwE8An+vaXwCc6H4+v7v//G7b57t90z338kn7tZpG9NuB41V1oqqeAG4Drlqwzzbg0939vx/Zvg04VFVPVtV/AV8CdqxAn6fVV8zvAN5TVd8BqKpTPcYwqV7f5yTnMZ80VtOIvq+Yi/kECLAB+EZP/V+OPmI+H3iiqh7u9vsk8Is9xjCxqjoEPL7ELlcBt9S8zwLPS/Ii4PXAJ6vq8ar6FvOx7ei2nVdVn635rH8L8MZJ+7WaEv2FzH9aP+Vk1zbqi8DV3f03AT+Y5PyufUeSH0hyAfCzwEUjz7uh+zPpvUnO7af7y9JXzD8EvDnJkSQfT3JJbxFMrs/3Geb/E3yqqv595j1fvr5i/g3gYJKTwK8CN/bU/+XoI+bHgLOTPHVy0TV8//u/2i32e1mq/eQZ2ieymhL9OH4PeE2SfwJeAzwKfLuqPgEcBP4RuBW4F/h295x3AT8C/Djzfxa9c6U7PaXlxHwu8D81f7bdnwP7V7zX01lOzE/Z1W1ba5YT8+8CV1TVJuAvgT9d8V5PZ6KYuxHttcB7k3we+A++//3XGaymRP8o3/vpvKlr+66q+kZVXV1VrwTe3bX9W/fzhqp6RVW9jvla1sNd+ze7P5P+l/n/DNv7D2VsvcTM/Kf+R7v7fwu8rL8QJtZXzHSjv+3AXf2GMLGZx5xkI/Dyqvpcd4i/Bn6y5zgm0df/53ur6qerajtwiJH3f41Y7PeyVPumM7RPZtaTEcu9MT/xcgK4mKcnb350wT4X8PQE4w3M16FhfuLn/O7+y4CjwNnd4xd1PwP8GXDj0LGuQMw3Am/r7l8GHB461r5j7tquAz44dIwrEXN3e4ynJyZ3Ax8ZOtYV+Lf9wu7nucCngNcOHesZYt/C4pOxV/K9k7Gf79pfAHyV+YnY53f3X9BtWzgZe8XEfRr6l7Lgl3AF85/QXwHe3bW9B3hDd/8a4MvdPn8BnNu1Pxt4sLt9FnjFyDE/Ddzf/WP5K+C5Q8e5AjE/j/lR7f3M/9n78qHj7Dvmbvs9wI6h41vB9/lN3Xv8xS72lwwd5wrE/CfAMeAh4HeGjvEMMd8KfBP4P+b/st7N/ADkum57gJu738n9wNzIc98GHO9ubx1pn+vy11eA99Gd6DrJzTNjJalxq6lGL0nqgYlekhpnopekxpnoJalxJnpJapyJXpIaZ6KXpMaZ6CWpcf8PCWO/Yv9v/0gAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAEKCAYAAAARnO4WAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAEUBJREFUeJzt3X+s3XV9x/Hny3bFqQFBmDLgrpAyZrM/dJ6BbnGSRVzRVTazzTYuwkZo1LD/TCxhfyxLljiXmcxAgs0kRJOBzDjXhpqqmwa3IBY2fwC1WhuVEjaGmzUmyzbCe3+cb/Vwubf93nvOvefc83k+khvO+Zzv+fyg7auffr6f7/ebqkKSNP9eMO0OSJLWh4EvSY0w8CWpEQa+JDXCwJekRhj4ktQIA1+SGmHgS1IjJh74Sa5O8sUkdyS5etL1S5JWp1fgJ7kzyVNJHllUviPJ0STHkuztigv4EfBC4MRkuytJWq30ubVCkl9jGOIfrapf7Mo2Ad8ErmEY7IeB3cA3qurZJC8HPlhV7zhT/eeff35t3bp11YOQpBY9/PDDT1fVBX2P39znoKq6P8nWRcVXAseq6jhAknuA66rqse7z/wLOWq7OJHuAPQALCws89NBDffssSQKSfHclx4+zhn8R8PjI+xPARUneluTDwMeA25b7clXtq6pBVQ0uuKD3X1CSpFXqNcNfiar6JPDJSdcrSRrPODP8J4BLRt5f3JX1lmRnkn0nT54coxuSpD7GCfzDwOVJLk2yBdgF7F9JBVV1oKr2nHPOOWN0Q5LUR99tmXcDDwBXJDmR5Maqega4GTgEHAHurapHV9K4M3xJWj+9tmWutcFgUO7SkaSVSfJwVQ36Hu+tFSSpEVMNfJd0JGn9THxb5kpU1QHgwGAwuGma/ZCk9bR1730/fv2d979l3dp1SUeSGmHgS1IjXMOXpEZMNfC98EqS1o9LOpLUCANfkhrhGr4kNcI1fElqhEs6ktQIA1+SGmHgS1IjPGkrSY3wpK0kNcIlHUlqhIEvSY0w8CWpEQa+JDXCXTqS1Ah36UhSI1zSkaRGGPiS1AgDX5IaYeBLUiMMfElqhIEvSY0w8CWpEV54JUmN8MIrSWqESzqS1AgDX5IaYeBLUiMMfElqhIEvSY0w8CWpEQa+JDXCwJekRhj4ktQIA1+SGmHgS1Ij1iTwk7w4yUNJfnMt6pckrVyvwE9yZ5KnkjyyqHxHkqNJjiXZO/LR+4B7J9lRSdJ4+s7w7wJ2jBYk2QTcDlwLbAd2J9me5BrgMeCpCfZTkjSmzX0Oqqr7k2xdVHwlcKyqjgMkuQe4DngJ8GKGfwn8d5KDVfXs4jqT7AH2ACwsLKy2/5KknnoF/jIuAh4feX8CuKqqbgZIcgPw9FJhD1BV+4B9AIPBoMbohySph3EC/7Sq6q4zHZNkJ7Bz27Zta9UNSVJnnF06TwCXjLy/uCvrzSdeSdL6GSfwDwOXJ7k0yRZgF7B/Mt2SJE1a322ZdwMPAFckOZHkxqp6BrgZOAQcAe6tqkdX0rgPMZek9dN3l87uZcoPAgdX23hVHQAODAaDm1ZbhySpH2+tIEmNmGrgu6QjSetnqoHvLh1JWj8u6UhSI1zSkaRGuKQjSY1wSUeSGmHgS1IjDHxJaoQnbSWpEZ60laRGuKQjSY0w8CWpEWv2xCtJ0k9s3XvftLvgSVtJaoUnbSWpEa7hS1IjDHxJaoSBL0mNMPAlqRHu0pGkRrhLR5Ia4ZKOJDXCwJekRhj4ktQIA1+SGmHgS1IjDHxJaoSBL0mN8MIrSWqEF15JUiNc0pGkRhj4ktQIA1+SGmHgS1IjDHxJaoSBL0mN2DztDkjSPNq6975pd+F5nOFLUiMMfElqhIEvSY2YeOAneWWSO5J8Ism7J12/JGl1egV+kjuTPJXkkUXlO5IcTXIsyV6AqjpSVe8Cfg/41cl3WZK0Gn1n+HcBO0YLkmwCbgeuBbYDu5Ns7z57K3AfcHBiPZUkjaVX4FfV/cB/Liq+EjhWVcer6n+Be4DruuP3V9W1wDsm2VlJ0uqNsw//IuDxkfcngKuSXA28DTiL08zwk+wB9gAsLCyM0Q1JUh8Tv/Cqqr4AfKHHcfuAfQCDwaAm3Q9J0nONs0vnCeCSkfcXd2W9+cQrSVo/4wT+YeDyJJcm2QLsAvavpAKfeCVJ66fvtsy7gQeAK5KcSHJjVT0D3AwcAo4A91bVo2vXVUnSOHqt4VfV7mXKDzLG1sskO4Gd27ZtW20VkqSefIi5JDXCe+lIUiOmGvju0pGk9eOSjiQ1wideSdKEzOJTrka5pCNJjXBJR5Ia4S4dSWqEgS9JjXANX5Ia4Rq+JDXCJR1JaoSBL0mN8MIrSRrDrF9sNcqTtpLUCE/aSlIjXMOXpEYY+JLUCANfkhph4EtSIwx8SWqE2zIlqRFTvfCqqg4ABwaDwU3T7IckrcRGuthqlEs6ktQIA1+SGmHgS1IjvHmaJPWwUdftRznDl6RGGPiS1AiXdCRpGfOwjDPKC68kqRFeeCVJI+ZtVj/KNXxJaoSBL0mNMPAlqREGviQ1wsCXpEa4D19SM0Z34Hzn/W+ZYk+mwxm+JDXCGb6kubbcvvp53m+/HANf0txpMcz7cElHkhqxJjP8JL8FvAU4G/hIVX1mLdqRJPXXe4af5M4kTyV5ZFH5jiRHkxxLshegqj5VVTcB7wLePtkuS5JWYyUz/LuA24CPnipIsgm4HbgGOAEcTrK/qh7rDvnj7nNJmjjX6lemd+BX1f1Jti4qvhI4VlXHAZLcA1yX5AjwfuDTVfUvS9WXZA+wB2BhYWHlPZc015bbM2/Ir964a/gXAY+PvD8BXAX8EfBG4Jwk26rqjsVfrKp9wD6AwWBQY/ZD0hwz5CdjTU7aVtWHgA+tRd2SNj5n79MxbuA/AVwy8v7irqyXJDuBndu2bRuzG5JmnRdATd+4+/APA5cnuTTJFmAXsL/vl6vqQFXtOeecc8bshiTpTFayLfNu4AHgiiQnktxYVc8ANwOHgCPAvVX16Arq9Jm2krROVrJLZ/cy5QeBg6tp3GfaSrOtz1p7i3ed3Ki8l47UqPUIbdfnZ8tUA9+TttJ41nOm3fekqzP+2TXVwHdJR5o/zupnl0s6klyTb4SBL82h5WbZhnnbXMOX5oRLKToT1/ClNbTSpZJZWFrxL4755ZKONAF9gnql94+ZhfDXfDHwpQ1gUrNuZ+9tcw1fmgKDV9Mw1YeYe/M0SVo/Uw18SdL6cQ1fTRnnZmDeQkAbnYEvTZjr85pVnrSVVslg10bjSVtJaoRLOjqjeb0AyBm6WmPga01N6y+Lef1LShqHga+54YxdOj0DX1rEfx1oXrlLZ06td2iN095a93Wcmb//atA88fbIWrVphaEzcGl1XNLpYV4DZiONy5m2ND4Df42tRaiu9L7q49R5uuPWqo1JtCXp+bx5miQ1whn+BrdRZ8F9nvI0Tj2Sns/AnzEbaV19OYawNJsM/CmZh2CXtLHMbeCv1f3Nl5u9zlNoO0OX5pMXXo2YtVn3Wu/wkdQWb48sSY2Y2yWd5WzUGa63B5A0riYCfzWBtxYBa/BKmqYNH/iGqCT145W2ktQIA1+SGmHgS1IjDHxJaoSBL0mN2PC7dOaBO40krQdn+JLUCANfkhox8cBPclmSjyT5xKTrliStXq/AT3JnkqeSPLKofEeSo0mOJdkLUFXHq+rGteisJGn1+s7w7wJ2jBYk2QTcDlwLbAd2J9k+0d5Jkiam1y6dqro/ydZFxVcCx6rqOECSe4DrgMf61JlkD7AHYGFhoWd3Z5c7bSTNunHW8C8CHh95fwK4KMnLktwBvDrJLct9uar2VdWgqgYXXHDBGN2QJPUx8X34VfV94F19jp21J15J0jwbZ4b/BHDJyPuLu7LefOKVJK2fcQL/MHB5kkuTbAF2Afsn0y1J0qT13ZZ5N/AAcEWSE0lurKpngJuBQ8AR4N6qenQljSfZmWTfyZMnV9pvSdIK9d2ls3uZ8oPAwdU2XlUHgAODweCm1dYhSerHWytIUiOmGvgu6UjS+plq4LtLR5LWT6pq2n0gyX8A313l188Hnp5gdzYCx9wGx9yGccb8c1XV+8rVmQj8cSR5qKoG0+7HenLMbXDMbVjPMXvSVpIaYeBLUiPmIfD3TbsDU+CY2+CY27BuY97wa/iSpH7mYYYvSerBwJekRkw98JOcl+SzSb7V/ffcZY67vjvmW0muHyl/TZKvd8/V/VCSnK7eJL+Q5IEk/5PkvYvaeN4zeudkzOmOO5bka0l+aaSuDyR5NMmR0brmeLwLST7TjfexJZ7kNndj7j4/O8MbH962FuOdpTEneVWGf8Yf7crfvgZjPW1WJDkryce7zx8c/X2W5Jau/GiS3zhTnRnekfjBrvzjGd6d+LRtLKuqpvoDfADY273eC/z5EsecBxzv/ntu9/rc7rMvA68FAnwauPZ09QI/A/wy8GfAe0fa2AR8G7gM2AJ8Fdg+J2N+c3dcuu892JX/CvDP3dg3Mbwj6tXzOt7usy8A13SvXwK8aJ5/jUfa+ivgb4Db1mK8szRm4OeBy7vXPws8Cbx0guM8Y1YA7wHu6F7vAj7evd7eHX8WcGlXz6bT1QncC+zqXt8BvPt0bZy272v1i7+C/3lHgQu71xcCR5c4Zjfw4ZH3H+7KLgS+sdRxZ6oX+BOeG/ivAw6NvL8FuGUexnzqu4vb78b8MPDTwIuAh4BXzvF4twP/NI+/r5cbc/f6NcA9wA2sbeDPzJgXtflVur8AJjTOM2YFw9vGv657vZnhlbRZfOyp45ars/vO08DmxW0v18bp+j71JR3g5VX1ZPf634CXL3HMks/P7X5OLFHet94+bayF9R7zknVV1QPA5xnOgJ5k+BvpyKpGdHozMV6GM78fJPlkkn9N8hdJNq1yTGcyE2NO8gLgL4HnLF+ukZkY82hjSa5kOGP+9opGcnp9suLHx9Tw2SEngZed5rvLlb8M+EFXx+K2lmtjWRN/pu1SknwOeMUSH906+qaqKsnE94muVb2nsxHGnGQb8EqGj6cE+GyS11fVF1fa3kYYL8Pf768HXg18D/g4w1nvR1bT5gYZ83uAg1V1IhM4PbNBxgxAkguBjwHXV9Wzk+7LRrQugV9Vb1zusyT/nuTCqnqy+wV6aonDngCuHnl/McO12Cf4SVidKj/1XN0+9S5uY6xn9I6asTEvN7bfB75UVT/q+vVphv9kXHHgb5Dxbga+UlXHu359iuHa76oCf4OM+XXA65O8h+E5iy1JflRVq9qUsEHGTJKzgfuAW6vqSz2H11efrDh1zIkkm4FzgO+f4btLlX8feGmSzd0sfvT45dpY1iws6ewHTp2pvx74+yWOOQS8Kcm53Rn6NzFcfngS+GGS13Zn9N858v0+9Y5az2f0rveY9wPv7HY1vBY42dXzPeANSTYn+SngDQwfVzlpszLewwz/8Jy6u+CvA49NbJTPNRNjrqp3VNVCVW1luKzz0dWGfQ8zMebuz+/fMRzrJyY8RuiXFaN9/h3gH2u42L4f2NXtsLkUuJzhyeol6+y+8/muDnj++JdqY3mTOpGx2h+Ga07/AHwL+BxwXlc+AP565Lg/BI51P38wUj4AHmG4RncbP7l6eLl6X8FwHeyHwA+612d3n70Z+GZX161zNOYAt3fHfx0YdOWbGJ74OsIw+D44z+PtPrsG+FpXfhewZd7HPFLnDaztSduZGDPDf7n+H/CVkZ9XTXisz8sK4E+Bt3avXwj8bTfGLwOXjXz31u57R+l2Ii1XZ1d+WVfHsa7Os87UxnI/3lpBkhoxC0s6kqR1YOBLUiMMfElqhIEvSY0w8CVphiX53QxvBPdskrGefWvgS9KMSHJ1krsWFT8CvA24f9z61+VKW0nS6lR3f6tJ3BrDGb4kNcIZviRNWZIHGd4j/yXAeUm+0n30vqo6NKl2DHxJmrKqugqGa/jADVV1w1q045KOJDXCwJekGZbkt5OcYHir6/uSrHqJx5unSVIjnOFLUiMMfElqhIEvSY0w8CWpEQa+JDXCwJekRhj4ktSI/wcg+J8dJV+RxQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotFB(hb1,hb2)\n", + "plotFB(hb2,hb3)\n", + "plotFB(hb3,hb4)\n", + "plotFB(hb1,hf1)\n", + "plotFB(hf1,hf2)\n", + "plotFB(hf2,hf3)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "548570\n", + "493149\n", + "434057\n", + "792527\n", + "2274850\n", + "540108\n", + "483260\n", + "931967\n", + "pentuplets\n", + "612902\n", + "triplets\n", + "111601\n", + "47552\n", + "4230185\n", + " det1 phi1 pt1 r1 trackID z1 det2 phi2 pt2 \\\n", + "0 83 -0.534839 1698 3.209876 10000005 -2.03853 299 -0.522814 1698 \n", + "1 83 -0.534839 1698 3.209876 10000005 -2.03853 299 -0.522814 1698 \n", + "2 83 -0.534839 1698 3.209876 10000005 -2.03853 299 -0.522814 1698 \n", + "3 83 -0.534839 1698 3.209876 10000005 -2.03853 299 -0.522814 1698 \n", + "4 83 -0.534839 1698 3.209876 10000005 -2.03853 299 -0.508068 1698 \n", + "\n", + " r2 ... det3 phi3 pt3 r3 z3 det4 \\\n", + "0 6.636066 ... 642 -0.506715 1698 11.091832 -8.901971 1146 \n", + "1 6.636066 ... 642 -0.506715 1698 11.091832 -8.901971 1146 \n", + "2 6.636066 ... 642 -0.506715 1698 11.091832 -8.901971 1146 \n", + "3 6.636066 ... 642 -0.506715 1698 11.091832 -8.901971 1146 \n", + "4 6.638935 ... 642 -0.506715 1698 11.091832 -8.901971 1146 \n", + "\n", + " phi4 pt4 r4 z4 \n", + "0 -0.469588 1698 16.203526 -13.062537 \n", + "1 -0.464408 1698 16.201468 -13.123804 \n", + "2 -0.488653 1698 16.214857 -13.366333 \n", + "3 -0.471780 1698 16.204531 -13.442224 \n", + "4 -0.469588 1698 16.203526 -13.062537 \n", + "\n", + "[5 rows x 21 columns]\n", + " det1 phi1 pt1 r1 trackID z1 det2 phi2 pt2 \\\n", + "0 19 1.342908 791 3.215434 10000094 -6.674059 137 1.316544 791 \n", + "1 3 0.042201 662 3.147929 10000100 -6.337495 97 0.071146 662 \n", + "2 3 0.038396 662 3.150830 10000100 -6.388875 97 0.071146 662 \n", + "3 92 -0.362996 487 2.798809 10000109 0.902371 308 -0.413166 487 \n", + "4 92 -0.362996 487 2.798809 10000109 0.902371 308 -0.413166 487 \n", + "\n", + " r2 ... det3 phi3 pt3 r3 z3 det4 \\\n", + "0 6.533934 ... 392 1.287120 791 10.669562 -23.916361 1605 \n", + "1 6.962423 ... 320 0.107237 662 11.064787 -24.246792 1599 \n", + "2 6.962423 ... 320 0.107237 662 11.064787 -24.246792 1599 \n", + "3 7.010263 ... 644 -0.463537 487 11.100529 1.352261 1195 \n", + "4 7.010263 ... 644 -0.463537 487 11.100529 1.352261 1196 \n", + "\n", + " phi4 pt4 r4 z4 \n", + "0 1.260540 791 14.376982 -32.514194 \n", + "1 0.133849 662 14.090476 -31.091267 \n", + "2 0.133849 662 14.090476 -31.091267 \n", + "3 -2.994291 487 8.460827 30.964153 \n", + "4 -2.864020 487 5.959154 31.328598 \n", + "\n", + "[5 rows x 21 columns]\n", + " det1 phi1 pt1 r1 trackID z1 det2 phi2 pt2 \\\n", + "0 66 -1.615744 740 3.405559 10000002 -9.927705 256 -1.644181 740 \n", + "1 66 -1.615744 740 3.405559 10000002 -9.927705 256 -1.644181 740 \n", + "2 26 1.914170 2464 2.671153 10000010 -9.608046 160 1.903031 2464 \n", + "3 26 1.910948 2464 2.670602 10000010 -9.678793 160 1.903031 2464 \n", + "4 26 1.922151 2464 2.672636 10000010 -9.662353 160 1.903031 2464 \n", + "\n", + " r2 ... det3 phi3 pt3 r3 z3 det4 \\\n", + "0 7.273387 ... 1567 -1.673348 740 10.764234 -33.038609 1735 \n", + "1 7.273387 ... 1623 -1.672782 740 10.705758 -32.855053 1735 \n", + "2 6.863056 ... 1583 1.898713 2464 8.804473 -33.619747 1664 \n", + "3 6.863056 ... 1583 1.898713 2464 8.804473 -33.619747 1664 \n", + "4 6.863056 ... 1583 1.898713 2464 8.804473 -33.619747 1664 \n", + "\n", + " phi4 pt4 r4 z4 \n", + "0 -1.692311 740 13.137235 -40.457005 \n", + "1 -1.692311 740 13.137235 -40.457005 \n", + "2 1.895945 2464 10.021165 -38.362865 \n", + "3 1.895945 2464 10.021165 -38.362865 \n", + "4 1.895945 2464 10.021165 -38.362865 \n", + "\n", + "[5 rows x 21 columns]\n", + " det1 phi1 pt1 r1 trackID z1 det2 phi2 pt2 \\\n", + "0 26 1.914170 2464 2.671153 10000010 -9.608046 1583 1.898713 2464 \n", + "1 26 1.910948 2464 2.670602 10000010 -9.678793 1583 1.898713 2464 \n", + "2 26 1.922151 2464 2.672636 10000010 -9.662353 1583 1.898713 2464 \n", + "3 42 2.955944 1148 2.702642 10000032 -10.496306 1530 2.928084 1148 \n", + "4 42 2.955944 1148 2.702642 10000032 -10.496306 1530 2.928084 1148 \n", + "\n", + " r2 ... det3 phi3 pt3 r3 z3 det4 \\\n", + "0 8.804473 ... 1664 1.895945 2464 10.021165 -38.362865 1776 \n", + "1 8.804473 ... 1664 1.895945 2464 10.021165 -38.362865 1776 \n", + "2 8.804473 ... 1664 1.895945 2464 10.021165 -38.362865 1776 \n", + "3 8.442572 ... 1642 2.919404 1148 10.328422 -42.279213 1838 \n", + "4 8.442572 ... 1670 2.921753 1148 9.830442 -40.189011 1838 \n", + "\n", + " phi4 pt4 r4 z4 \n", + "0 1.890452 2464 12.449927 -47.847328 \n", + "1 1.890452 2464 12.449927 -47.847328 \n", + "2 1.890452 2464 12.449927 -47.847328 \n", + "3 2.910286 1148 12.075643 -49.587833 \n", + "4 2.910286 1148 12.075643 -49.587833 \n", + "\n", + "[5 rows x 21 columns]\n", + " det1 phi1 pt1 r1 trackID z1 det2 phi2 pt2 \\\n", + "0 35 2.637022 2080 3.098070 10000034 -4.770354 186 2.646844 2080 \n", + "1 43 2.640189 2080 2.788378 10000034 -4.232756 186 2.646844 2080 \n", + "2 27 1.656182 419 2.715456 10000037 -2.543939 155 1.604027 419 \n", + "3 20 1.074587 775 3.318337 10000053 4.562479 133 1.103368 775 \n", + "4 19 1.363694 1260 3.217331 10000077 -5.626595 146 1.380944 1260 \n", + "\n", + " r2 ... det4 phi4 pt4 r4 z4 det5 phi5 pt5 r5 z5 \n", + "0 6.528466 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN \n", + "1 6.528466 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN \n", + "2 6.535251 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN \n", + "3 6.909348 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN \n", + "4 7.170426 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN \n", + "\n", + "[5 rows x 26 columns]\n", + " det1 phi1 pt1 r1 trackID z1 det2 phi2 pt2 \\\n", + "0 26 1.914170 2464 2.671153 10000010 -9.608046 160 1.903031 2464 \n", + "1 26 1.910948 2464 2.670602 10000010 -9.678793 160 1.903031 2464 \n", + "2 26 1.922151 2464 2.672636 10000010 -9.662353 160 1.903031 2464 \n", + "3 42 2.955944 1148 2.702642 10000032 -10.496306 200 2.936726 1148 \n", + "4 42 2.955944 1148 2.702642 10000032 -10.496306 200 2.936726 1148 \n", + "\n", + " r2 ... det4 phi4 pt4 r4 z4 det5 \\\n", + "0 6.863056 ... 1664 1.895945 2464 10.021165 -38.362865 1776 \n", + "1 6.863056 ... 1664 1.895945 2464 10.021165 -38.362865 1776 \n", + "2 6.863056 ... 1664 1.895945 2464 10.021165 -38.362865 1776 \n", + "3 6.584924 ... 1642 2.919404 1148 10.328422 -42.279213 1838 \n", + "4 6.584924 ... 1670 2.921753 1148 9.830442 -40.189011 1838 \n", + "\n", + " phi5 pt5 r5 z5 \n", + "0 1.890452 2464 12.449927 -47.847328 \n", + "1 1.890452 2464 12.449927 -47.847328 \n", + "2 1.890452 2464 12.449927 -47.847328 \n", + "3 2.910286 1148 12.075643 -49.587833 \n", + "4 2.910286 1148 12.075643 -49.587833 \n", + "\n", + "[5 rows x 26 columns]\n" + ] + } + ], + "source": [ + "t12 = pd.merge(build(hb1,'1'),build(hb2,'2'),on='trackID')\n", + "t23 = pd.merge(build(hb2,'1'),build(hb3,'2'),on='trackID')\n", + "t34 = pd.merge(build(hb3,'1'),build(hb4,'2'),on='trackID')\n", + "t123 = pd.merge(t12,build(hb3,'3'),on='trackID')\n", + "print len(t12)\n", + "print len(t23)\n", + "print len(t34)\n", + "print len(t123)\n", + "t1234 = pd.merge(t123,build(hb4,'4'),on='trackID')\n", + "print len(t1234)\n", + "t1231 = pd.merge(t123,build(hf1,'4'),on='trackID')\n", + "print len(t1231)\n", + "t121 = pd.merge(t12,build(hf1,'3'),on='trackID')\n", + "t1212 = pd.merge(t121,build(hf2,'4'),on='trackID')\n", + "print len(t1212)\n", + "t11 = pd.merge(build(hb1,'1'),build(hf1,'2'),on='trackID')\n", + "t112 = pd.merge(t11,build(hf2,'3'),on='trackID')\n", + "t1123 = pd.merge(t112,build(hf3,'4'),on='trackID')\n", + "print len(t1123)\n", + "print 'pentuplets'\n", + "t12123 = pd.merge(t1212,build(hf3,'5'),on='trackID')\n", + "print len(t12123)\n", + "\n", + "# try to get triplets in gap\n", + "print 'triplets'\n", + "t1230 = pd.merge(t123,build(hb4,'4'),on='trackID',how='left')\n", + "t1230 = t1230[t1230.isnull()['z4']]\n", + "print len(t1230)\n", + "t1230 = pd.merge(t1230,build(hf1,'5'),on='trackID',how='left')\n", + "t1230 = t1230[t1230.isnull()['z5']]\n", + "print len(t1230)\n", + "\n", + "qall = pd.concat([t1234,t1231,t1212,t1123])\n", + "print len(qall)\n", + "print t1234.head()\n", + "print t1231.head()\n", + "print t1212.head()\n", + "print t1123.head()\n", + "print t1230.head()\n", + "print t12123.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "hole = zAtR(t1230[t1230['pt3']>600],16)\n", + "plt.hist(hole[abs(hole)<40],log=True, bins=100)\n", + "plt.show()\n", + "plt.hist(abs(hole[abs(hole)<40]),log=True, bins=100)\n", + "plt.show()\n", + "penta = zAtR(t12123[t12123['pt3']>600],6.5)\n", + "plt.hist(penta[abs(penta)<50],log=True, bins=100)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [], + "source": [ + "def plotTriplets(quad,mpt) :\n", + " \n", + " d1 = (quad['r1']*quad['z2']-quad['z1']*quad['r2'])/(quad['r1']-quad['r2'])\n", + " d2 = (quad['r2']*quad['z3']-quad['z2']*quad['r3'])/(quad['r2']-quad['r3'])\n", + " d3 = (quad['r3']*quad['z4']-quad['z3']*quad['r4'])/(quad['r3']-quad['r4'])\n", + " \n", + " z0cut = np.logical_and(abs(d1)<10.,np.logical_and(abs(d2)<10.,abs(d3)<10.))\n", + "\n", + " quadc = quad[np.logical_and(z0cut,quad['pt1']>mpt)]\n", + "\n", + "# print 'dpt'\n", + "# plt.hist(quad['pt1']-quad['pt2'],log=True, bins=100)\n", + "# plt.show()\n", + "# plt.hist(quad['pt2']-quad['pt3'],log=True, bins=100)\n", + "# plt.show()\n", + "# plt.hist(quad['pt3']-quad['pt4'],log=True, bins=100)\n", + "# plt.show()\n", + "\n", + "\n", + " print 'delta123', len(quadc)\n", + " \n", + "#dt,dtn = ml(t123['tpt'],t123['tpz'],t123['tpt2'],t123['tpz2'])\n", + "#plt.hist(dt[dt<0.1], log=True, bins=100)\n", + "#plt.show()\n", + "#plt.hist(dtn[dtn<0.1], log=True, bins=100)\n", + "#plt.show()\n", + "\n", + " \n", + " thcut = alignRZ(quadc,'r',0.6,True)\n", + " pzcut = alignRZ(quadc,'phi',1.0,True)\n", + " thcut2 = alignRPZ(quadc,'r',True)\n", + " pzcut2 = alignRPZ(quadc,'phi',True)\n", + "\n", + " dc = dca(quadc,True)\n", + " curv1 = dca(quadc,True,True)\n", + " field = curv1-1/(0.087*quadc['pt1'])\n", + " print 'field'\n", + " plt.hist(field[abs(field)<5],log=True, bins=100)\n", + " plt.show()\n", + " print 'thcut,pzcut,dcacut',len(thcut)\n", + " plt.hist(thcut[thcut<0.004],log=True, bins=100)\n", + " plt.show()\n", + " plt.hist(pzcut[pzcut<0.4],log=True, bins=100)\n", + " plt.show()\n", + " plt.hist(dc[abs(dc)<0.3],log=True, bins=100)\n", + " plt.show()\n", + "\n", + " print 'thcut2,pzcut2',len(thcut2)\n", + " plt.hist(thcut2[abs(thcut2)<0.6],log=True, bins=100)\n", + " plt.show()\n", + " plt.hist(pzcut2[abs(pzcut2)<0.2],log=True, bins=100)\n", + " plt.show()\n", + " \n", + " print 'delta234'\n", + "\n", + " thcut = alignRZ(quadc,'r',0.6,False)\n", + " pzcut = alignRZ(quadc,'phi',1.0,False)\n", + " thcut2 = alignRPZ(quadc,'r',False)\n", + " pzcut2 = alignRPZ(quadc,'phi',False)\n", + " dc = dca(quadc,False)\n", + " curv2 = dca(quadc,False,True)\n", + " field =curv2 -1/(0.087*quadc['pt1'])\n", + " print 'field'\n", + " plt.hist(field[abs(field)<5],log=True, bins=100)\n", + " plt.show()\n", + " print 'delta curv'\n", + " dcu = curv2-curv1\n", + " plt.hist(dcu[abs(dcu)<0.15],log=True, bins=100)\n", + " plt.show() \n", + " print 'thcut,pzcut,dcacut',len(thcut)\n", + " plt.hist(thcut[thcut<0.004],log=True, bins=100)\n", + " plt.show()\n", + " plt.hist(pzcut[pzcut<0.4],log=True, bins=100)\n", + " plt.show()\n", + " plt.hist(dc[abs(dc)<0.3],log=True, bins=100)\n", + " plt.show()\n", + "\n", + " \n", + " print 'thcut2,pzcut2',len(thcut2)\n", + " plt.hist(thcut2[abs(thcut2)<0.6],log=True, bins=100)\n", + " plt.show()\n", + " plt.hist(pzcut2[abs(pzcut2)<0.2],log=True, bins=100)\n", + " plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [], + "source": [ + "def plotDoublets(quad,mpt) :\n", + " quadc = quad[quad['pt1']>mpt]\n", + "\n", + " maxc = 1000./(mpt*87.)\n", + "\n", + " print 'dphi'\n", + " d1 = quadc['phi2']-quadc['phi1']\n", + " plt.hist(d1[abs(d1)<.1], bins=100,log=True)\n", + " plt.show()\n", + " d2 = quadc['phi3']-quadc['phi2']\n", + " plt.hist(d2[abs(d2)<.1], bins=100,log=True)\n", + " plt.show()\n", + " d3 = quadc['phi4']-quadc['phi3']\n", + " plt.hist(d3[abs(d3)<.1], bins=100,log=True)\n", + " plt.show()\n", + "\n", + " pcut = np.logical_and(abs(d1)<0.05,np.logical_and(abs(d2)<0.05,abs(d3)<0.05))\n", + "\n", + "\n", + " print 'dphiNor'\n", + " \n", + " pc = phicut(quadc['r1'],quadc['r2'],maxc)\n", + " d1 = (quadc['phi2']-quadc['phi1'])/pc\n", + " plt.hist(d1[abs(d1)<2.], bins=100,log=True)\n", + " plt.show()\n", + " pc = phicut(quadc['r2'],quadc['r3'],maxc)\n", + " d2 = (quadc['phi3']-quadc['phi2'])/pc\n", + " plt.hist(d2[abs(d2)<2.], bins=100,log=True)\n", + " plt.show()\n", + " pc = phicut(quadc['r3'],quadc['r4'],maxc)\n", + " d3 = (quadc['phi4']-quadc['phi3'])/pc\n", + " plt.hist(d3[abs(d3)<2.], bins=100,log=True)\n", + " plt.show()\n", + "\n", + "\n", + " print 'dz'\n", + " d1 = quadc['z2']-quadc['z1']\n", + " plt.hist(d1[np.logical_and(pcut,abs(d1)<35)], bins=100,log=True)\n", + " plt.show()\n", + " d2 = quadc['z3']-quadc['z2']\n", + " plt.hist(d2[np.logical_and(pcut,abs(d2)<35)], bins=100,log=True)\n", + " plt.show()\n", + " d3 = quadc['z4']-quadc['z3']\n", + " plt.hist(d3[np.logical_and(pcut,abs(d3)<35)], bins=100,log=True)\n", + " plt.show()\n", + "\n", + " print 'z0'\n", + " d1 = (quadc['r1']*quadc['z2']-quadc['z1']*quadc['r2'])/(quadc['r1']-quadc['r2'])\n", + " plt.hist(d1[np.logical_and(pcut,abs(d1)<50)], bins=100,log=True)\n", + " plt.show()\n", + " d2 = (quadc['r2']*quadc['z3']-quadc['z2']*quadc['r3'])/(quadc['r2']-quadc['r3'])\n", + " plt.hist(d2[np.logical_and(pcut,abs(d2)<50)], bins=100,log=True)\n", + " plt.show()\n", + " d3 = (quadc['r3']*quadc['z4']-quadc['z3']*quadc['r4'])/(quadc['r3']-quadc['r4'])\n", + " plt.hist(d3[np.logical_and(pcut,abs(d3)<50)], bins=100,log=True)\n", + " plt.show()\n", + " \n", + "\n", + " pcut = np.logical_and(abs(d1)<10.,np.logical_and(abs(d2)<10.,abs(d3)<10.))\n", + "\n", + "\n", + " print 'dr'\n", + " d1 = quadc['r2']-quadc['r1']\n", + " plt.hist(d1[np.logical_and(pcut,abs(d1)<20)], bins=100,log=True)\n", + " plt.show()\n", + " d2 = quadc['r3']-quadc['r2']\n", + " plt.hist(d2[np.logical_and(pcut,abs(d2)<20)], bins=100,log=True)\n", + " plt.show()\n", + " d3 = quadc['r4']-quadc['r3']\n", + " plt.hist(d3[np.logical_and(pcut,abs(d3)<20)], bins=100,log=True)\n", + " plt.show()\n", + "\n", + " print 'dphi zcut'\n", + " d1 = quadc['phi2']-quadc['phi1']\n", + " plt.hist(d1[np.logical_and(pcut,abs(d1)<.1)], bins=100,log=True)\n", + " plt.show()\n", + " d2 = quadc['phi3']-quadc['phi2']\n", + " plt.hist(d2[np.logical_and(pcut,abs(d2)<.1)], bins=100,log=True)\n", + " plt.show()\n", + " d3 = quadc['phi4']-quadc['phi3']\n", + " plt.hist(d3[np.logical_and(pcut,abs(d3)<.1)], bins=100,log=True)\n", + " plt.show()\n", + "\n", + "\n", + " \n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta123 227975\n", + "field\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 227975\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 227975\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADxZJREFUeJzt3X2sZHddx/H3x9aWCLIUuyK2LHeb1sY1MRCvJZEoVRBay1KijbQBU7XpBkz9x5iwpPoPiUnxHyMJSd0oFDRSSo24SxcrTyv+0Spb5KEPKd2Wkm6ttIBcUUmx9usfc7YMt3vvztx5ODO/+34lN3fmPMx875m5n/nN7/zOOakqJEnt+oG+C5AkzZZBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUOINekhpn0EtS4wx6SWrc6X0XAHD22WfXyspK32VI0lK56667vl5VO0+13EIE/crKCkePHu27DElaKkm+Ospydt1IUuMMeklqXK9Bn2RvkgNra2t9liFJTes16KvqUFXt27FjR59lSFLT7LqRpMYZ9JLUOINekhpn0EtS43o9YCrJXmDv+eef32cZ0oZW9t/2zO2Hb7isx0qkrXPUjSQ1biFOgSAtkuFWvNQC++glqXEGvSQ1zqCXpMYZ9JLUOE9qJkmNc3ilJDXOrhtJapxBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUOINekhrnAVOS1DgPmJKkxtl1I0mNM+glqXEGvSQ1zqCXpMYZ9JLUOINekhpn0EtS4wx6SWqcQS9JjTPoJalxBr0kNc6TmklS4zypmSQ1zq4bSWqcQS9JjTPoJalxBr0kNc6gl6TGGfSS1DiDXpIaZ9BLUuMMeklqnEEvSY0z6CWpcQa9JDXOoJekxhn0ktQ4g16SGnd63wVIy2Jl/23fd//hGy7rqRJpPAa9xLNDXGrJTLpukjw3ydEkr5/F40uSRjdS0Cd5b5LHk9y9bvolSe5PcizJ/qFZbwdumWahkqStGbVFfxNwyfCEJKcB7wEuBfYAVyXZk+SXgXuBx6dYpyRpi0bqo6+qzyRZWTf5IuBYVT0EkORm4HLgecBzGYT/d5Icrqqnp1axJGksk+yMPQd4ZOj+ceAVVXUdQJLfBL6+Ucgn2QfsA9i1a9cEZUiSNjOzcfRVdVNVfXST+QeqarWqVnfu3DmrMiRp25sk6B8FXjJ0/9xumiRpgUwS9J8FLkiyO8kZwJXAwXEeIMneJAfW1tYmKEOStJlRh1d+ELgDuDDJ8STXVNVTwHXA7cB9wC1Vdc84T15Vh6pq344dO8atW5I0olFH3Vy1wfTDwOGpViRJmipPaiZJjes16O2jl6TZ6zXo7aOXpNmz60aSGmfQS1LjDHpJapw7YyWpce6MlaTG2XUjSY0z6CWpcQa9JDXOnbGS1Dh3xkpS4+y6kaTGTXLNWGlbW9l/2zO3H77hsh4rkTZni16SGmeLXtvWcItcapmjbiSpcY66kaTG2UcvSY0z6CWpcQa9JDXOoJekxhn0ktQ4h1dKUuMcXilJjbPrRpIaZ9BLUuMMeklqnEEvSY0z6CWpcQa9JDXOoJekxnnAlCQ1zgOmJKlxdt1IUuO8Zqy2Fa8Tq+3IoJemYPgD5OEbLuuxEunZ7LqRpMYZ9JLUOINekhpn0EtS4wx6SWqcQS9JjTPoJalxBr0kNc6gl6TGefZKSWqcZ6+UpMbZdSNJjTPoJalxnr1SmjLPZKlFY4tekhpn0EtS4+y6UfO8qpS2O1v0ktQ4g16SGmfQS1LjDHpJapxBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUOINekho39aBP8pNJbkxya5K3TfvxJUnjGSnok7w3yeNJ7l43/ZIk9yc5lmQ/QFXdV1VvBX4deOX0S5aWx8r+2575kfoyaov+JuCS4QlJTgPeA1wK7AGuSrKnm/cG4Dbg8NQqlSRtyUhBX1WfAb65bvJFwLGqeqiqvgvcDFzeLX+wqi4F3jzNYiVJ45vkNMXnAI8M3T8OvCLJxcCvAmeySYs+yT5gH8CuXbsmKEN6NrtKpO+Z+vnoq+oIcGSE5Q4ABwBWV1dr2nVIkgYmGXXzKPCSofvndtMkSQtkkqD/LHBBkt1JzgCuBA5OpyxJ0rSMOrzyg8AdwIVJjie5pqqeAq4DbgfuA26pqnvGefIke5McWFtbG7duSdKIRuqjr6qrNph+mAmGUFbVIeDQ6urqtVt9DEnS5rw4uJrhSBvp5HoN+iR7gb3nn39+n2VIczH8QfTwDZf1WIm2m15PalZVh6pq344dO/osQ5Ka5tkrJalxBr0kNc6gl6TG9Rr0jqOXpNlzZ6wkNc5x9Fo6DlOUxmPQa6l5kJR0aga91AO/lWiePDJW6pmhr1lzZ6wkNc5x9JLUOINekhrnzlhpgdhfr1kw6LVQHC4pTZ+jbqQFZete0+KoG0lqnDtjJalxBr0kNc6gl6TGOepGvXOkzXjcSatx2aKXpMZ5hSlJalyvXTdVdQg4tLq6em2fdWj+7K6R5sc+emkJbPTBaH+9RmHQa25sxUv9MOg1U4b7/Ni610YMem3ZRsFiuEuLxaCXGmTrXsMcRy9JjTPoJalxno9eU2G//PKxe2f78IApqXHTCnQ/GJaXO2M1Flvuy22U12/SQPcDYfHYRy9JjbNFv+RsPUk6FYN+Gxv1gCc/QDRtNlDmy6Bv1DSPWrVfvn2jnDRto+kG9eIz6CVNZN4NAT9kxmfQS1pahv5oDHoBds9o9nyP9ceg78kkLZFx/2H8B5O2N4N+TPMMaGnZjXuA1jC7YqbHoJ+SWRxmPsw3vaStMugXwLRa+n5jkHQyvZ4CIcneJAfW1tb6LEOSmubZKyVpA60M37TrZknYLaPtzPf/ZAz6Ie4IlbSRZW7dG/QzsMxvCGlZ+X+3Mc9HL0mNs0UvaSHNs19+kqPNl+HbQ1NBv4gb351I0vwtYhb0ya4bSWrc0rfobTFL2syitO77rGPpg16SFtEiNUIN+hEs0gsmSeNqNujn8TXJDwBpucw6FxY1E5oNeknaTJ9njZ13f72jbiSpcduiRe85bCRtZ7boJalxBr0kNW4mXTdJ3ghcBjwf+Iuq+odZPM+kFnUPuSRN08gt+iTvTfJ4krvXTb8kyf1JjiXZD1BVH6mqa4G3Am+absmSpHGM03VzE3DJ8IQkpwHvAS4F9gBXJdkztMgfdPMlST0ZOeir6jPAN9dNvgg4VlUPVdV3gZuByzPwLuBjVfW56ZUrSRrXpDtjzwEeGbp/vJv2u8BrgCuSvPVkKybZl+RokqNPPPHEhGVIkjYyk52xVfVu4N2nWOYAcABgdXW1ZlGHJGnyFv2jwEuG7p/bTZMkLYhJg/6zwAVJdic5A7gSODh5WZKkaRlneOUHgTuAC5McT3JNVT0FXAfcDtwH3FJV94zxmHuTHFhbWxu3bknSiEbuo6+qqzaYfhg4vJUnr6pDwKHV1dVrt7K+JOnUUtX/ftAkTwBf3eLqZwNfn2I502Jd47Gu8S1qbdY1nknqemlV7TzVQgsR9JNIcrSqVvuuYz3rGo91jW9Ra7Ou8cyjLk9qJkmNM+glqXEtBP2BvgvYgHWNx7rGt6i1Wdd4Zl7X0vfRS5I210KLXpK0iaUI+iQvTPLxJA90v886yTIvS3JHknuSfDHJm4bm7U7yz9058z/UHcU7l7q65f4+ybeSfHTd9JuSfCXJ57ufly1IXX1vr6u7ZR5IcvXQ9CPdtQ9ObK8fnbCeZ11LYd38M7u//1i3PVaG5r2jm35/ktdNUse06kqykuQ7Q9vnxjnX9QtJPpfkqSRXrJt30td0Aer6v6HtNdWj+keo6/eS3Nvl1SeTvHRo3nS3V1Ut/A/wx8D+7vZ+4F0nWeYngAu62z8OPAa8oLt/C3Bld/tG4G3zqqub92pgL/DRddNvAq7oY3udoq7ethfwQuCh7vdZ3e2zunlHgNUp1XIa8CBwHnAG8AVgz7plfge4sbt9JfCh7vaebvkzgd3d45y2AHWtAHdP+/00Rl0rwE8DHxh+X2/2mvZZVzfvv3rcXr8I/FB3+21Dr+PUt9dStOiBy4H3d7ffD7xx/QJV9eWqeqC7/W/A48DOJAF+Cbh1s/VnVVdXzyeBb0/pOUex5boWYHu9Dvh4VX2zqv4D+DjrLngzJSe9lsIm9d4KvLrbPpcDN1fVk1X1FeBY93h91zVLp6yrqh6uqi8CT69bd5av6SR1zdIodX26qv6nu3sng5NCwgy217IE/Yuq6rHu9r8DL9ps4SQXMfgUfRD4EeBbNTgvD3zvnPlzr2sDf9R9dfuTJGcuQF19b6+NrnFwwvu6r9l/OGG4nep5vm+ZbnusMdg+o6zbR10Au5P8a5J/TPLzU6pp1Lpmse6sH/s5GVwX484MrnU9LePWdQ3wsS2ue0ozOR/9ViT5BPBjJ5l1/fCdqqokGw4VSvJi4C+Bq6vq6UkbOtOqawPvYBB4ZzAYYvV24J0LUNeWzbiuN1fVo0l+GPgb4DcYfB3XwGPArqr6RpKfAT6S5Keq6j/7LmyBvbR7T50HfCrJl6rqwXkWkOQtwCrwqlk9x8IEfVW9ZqN5Sb6W5MVV9VgX5I9vsNzzgduA66vqzm7yN4AXJDm9a/2Mdc78adS1yWOfaN0+meR9wO8vQF19b69HgYuH7p/LoG+eqnq0+/3tJH/N4OvxVoN+lGspnFjmeJLTgR0Mts8sr8Ow5bpq0MH7JEBV3ZXkQQb7ro7Oqa7N1r143bpHplDTicfe8msx9J56KMkR4OUMegLmUleS1zBoBL2qqp4cWvfidesemaSYZem6OQic2PN8NfB36xfIYGTI3wIfqKoT/ct0b/5PA1dstv6s6tpMF3Yn+sXfCNzdd10LsL1uB16b5KwMRuW8Frg9yelJzgZI8oPA65lse41yLYXheq8APtVtn4PAld3ol93ABcC/TFDLVOpKsjPJaQBdC/UCBjvy5lXXRk76mvZdV1fPmd3ts4FXAvfOq64kLwf+DHhDVQ03eqa/vWaxx3naPwz6Hz8JPAB8AnhhN30V+PPu9luA/wU+P/Tzsm7eeQz+EY8BHwbOnFdd3f1/Ap4AvsOgv+113fRPAV9iEFh/BTxvQerqe3v9dvfcx4Df6qY9F7gL+CJwD/CnTDjSBfgV4MsMWnDXd9PeyeAfD+A53d9/rNse5w2te3233v3ApVN+v2+pLuDXum3zeeBzwN451/Wz3fvovxl887lns9e077qAn+v+/77Q/b5mznV9Avga38urg7PaXh4ZK0mNW5auG0nSFhn0ktQ4g16SGmfQS1LjDHpJapxBL0mNM+glqXEGvSQ17v8BZM1XtbjjJ/4AAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta234\n", + "field\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta curv\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 227975\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 227975\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta123 40709\n", + "field\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 40709\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 40709\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta234\n", + "field\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta curv\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 40709\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 40709\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta123 152561\n", + "field\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 152561\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 152561\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta234\n", + "field\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta curv\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 152561\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 152561\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta123 400905\n", + "field\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAES5JREFUeJzt3W2MXGd5xvH/hSODCiS8xAXqxDgoToSVStCOkvIBkRZoHYITRKvWFkhQWbEAhS9VJVzRD335EtpSCZS0qQWRAZWENKKp3RiFlxIFIYfaFErjWAHjBrKBYkPAEn0LKXc/7JgMi9d7dud1n/3/pFVmzpw9c2U9c++z93nmOakqJEntetq0A0iSxstCL0mNs9BLUuMs9JLUOAu9JDXOQi9JjbPQS1LjLPSS1DgLvSQ17rxpBwC48MILa/PmzdOOIUmryhe/+MXvVtWGpfabiUK/efNmjhw5Mu0YkrSqJPlGl/2m2rpJsj3J3tOnT08zhiQ1baqFvqoOVNXuCy64YJoxJKlpnoyVpMZZ6CWpcRZ6SWqchV6SGmehl6TGOb1Skho31Q9MVdUB4ECv17thmjk0+zbvuecntx+56dopJpFWH1s3ktQ4C70kNc5CL0mNs9BLUuMs9JLUOAu9JDVu5IU+ydVJPpfk1iRXj/r4kqTl6VTok9yW5GSSBxds35bk4STHk+zpby7gh8AzgLnRxpUkLVfXEf0+YNvghiTrgFuAa4CtwM4kW4HPVdU1wLuAPx5dVEnSSnQq9FV1P/D4gs1XAser6kRVPQHcAVxfVT/uP/594OmLHTPJ7iRHkhw5derUCqJLkroYZgmEjcCjA/fngKuSvBH4DeA5wM2LfXNV7QX2AvR6vRoihxo1uOyBpJUb+Vo3VfVx4ONd9k2yHdh+6aWXjjqGJKlvmFk3jwEXD9y/qL+tM68ZK0njN0yhPwxsSXJJkvXADmD/cg7gMsWSNH5dp1feDhwCLk8yl2RXVT0J3AjcCxwD7qyqo8t5ckf0kjR+nXr0VbVzke0HgYMrfXJ79FrIE7DS6E11CQRH9JI0fq51I0mN85qxktQ4WzeS1DhbN5LUOFs3ktQ4WzeS1DhbN5LUOFs3ktQ4WzeS1DhbN5LUOAu9JDXOQi9JjfNkrCQ1zpOxktS4kV8zVhq3wTXrH7np2ikmkVYHe/SS1DgLvSQ1zkIvSY1z1o0kNc5ZN5LUOFs3ktQ4C70kNc5CL0mNs9BLUuMs9JLUOAu9JDVuLIU+yTOTHEny+nEcX5LUXadCn+S2JCeTPLhg+7YkDyc5nmTPwEPvAu4cZVBJ0sp0HdHvA7YNbkiyDrgFuAbYCuxMsjXJa4GHgJMjzClJWqFOyxRX1f1JNi/YfCVwvKpOACS5A7geeBbwTOaL/38nOVhVPx5ZYknSsgyzHv1G4NGB+3PAVVV1I0CStwLfXazIJ9kN7AbYtGnTEDEkSecytlk3VbWvqv7xHI/vrapeVfU2bNgwrhiStOYNU+gfAy4euH9Rf1tnrl4pSeM3TKE/DGxJckmS9cAOYP9oYkmSRqXr9MrbgUPA5UnmkuyqqieBG4F7gWPAnVV1dDlP7jLFkjR+XWfd7Fxk+0Hg4EgTSZJGyitMSVLjvMKUJDXOEb0kNc4RvSQ1bphPxkojsXnPPdOOIDXN1o0kNc7WjSQ1zitMSVLjLPSS1Dh79JLUuKnOuqmqA8CBXq93wzRzaPUanLHzyE3XTjGJNLts3UhS4yz0ktQ4e/SS1Dh79JoKPw0rTY6tG0lqnIVekhpnoZekxlnoJalxFnpJatxUZ90k2Q5sv/TSS6cZQ43wU7LS2blMsSQ1zitMaWKcOy9Nhz16SWqchV6SGmehl6TGWeglqXEWeklq3MgLfZKXJrk1yV1J3j7q40uSlqfT9MoktwGvB05W1RUD27cB7wPWAR+oqpuq6hjwtiRPAz4M/PXoY2u1mNaUSj88JT2l64h+H7BtcEOSdcAtwDXAVmBnkq39x64D7gEOjiypJGlFOhX6qrofeHzB5iuB41V1oqqeAO4Aru/vv7+qrgHetNgxk+xOciTJkVOnTq0svSRpScN8MnYj8OjA/TngqiRXA28Ens45RvRVtRfYC9Dr9WqIHJKkcxj5EghVdR9wX5d9XdSsTS51IM2WYWbdPAZcPHD/ov62zlzUTJLGb5hCfxjYkuSSJOuBHcD+5RwgyfYke0+fPj1EDEnSuXQq9EluBw4BlyeZS7Krqp4EbgTuBY4Bd1bV0eU8uSN6TcLmPff85Etaizr16Ktq5yLbDzLEFEp79O2wiEqza6rr0VfVAeBAr9e7YZo5tHb4QSqtRa51I0mNm2qh92SsJI2f14yVpMbZupGkxtm6kaTGOetGa5YzcLRWTLXQa3Vz7ry0Oky10PuBKc0KR/dqmbNuJKlxzrqRpMbZo9ey2JeXVh9H9JLUOOfRS1LjPBkrSY2zdSNJjfNkrJa01k7AOqderXFEL0mNs9BLUuNs3QiwXSG1zLVu9DPWWk/+XPwFqBY4vVKSGmePXpIaZ49+DbNFI60NFvo1xuK+cgt/dvbstVpY6KUV8kStVgt79JLUuLGM6JO8AbgWOB/4YFV9chzPI80KR/eaZZ0LfZLbgNcDJ6vqioHt24D3AeuAD1TVTVV1N3B3kucCfwFY6KfIvry0ti2ndbMP2Da4Ick64BbgGmArsDPJ1oFd/rD/uCRpSjoX+qq6H3h8weYrgeNVdaKqngDuAK7PvPcAn6iqfxldXEnScg3bo98IPDpwfw64Cngn8BrggiSXVtWtC78xyW5gN8CmTZuGjCHNDvv1mjVjORlbVe8H3r/EPnuBvQC9Xq/GkUOSNHyhfwy4eOD+Rf1tnbio2fh4AlbSGcPOoz8MbElySZL1wA5g//CxJEmj0rnQJ7kdOARcnmQuya6qehK4EbgXOAbcWVVHux7T1Sslafw6t26qauci2w8CB0eWSGrILJ+YneVsGq2pLoGQZHuSvadPn55mDElqmhcekaTGeSnBVc4/vyUtZaqFvqoOAAd6vd4N08yx2jh1cu1Z7i90BwAa5Hr0q4TFXdJK2bqZYRb3djni1iR5MlaSGmfrpiH+BaCV8i+MtnkpQUlqnD16ST/F0X177NFLUuNs3UhS4zwZK03IOE6W22ZRF/bopcaN6pdB1+P4y2f22KOXpMbZo5ekxtmjlxrhB+a0GAu9pKH4C2b2WeinxBNWkibFQi9pUcOO1h3QzAanVw5hHC9i/wyWNGpOr5Skxjm9UpIaZ6GXpMZ5MlaaMk9Yatws9BPkiVYtR2uvF3+hTY+Ffga09obWynV5Lfh60XJZ6KU1xF8Sa9PIT8YmeUmSDya5a9THliQtX6dCn+S2JCeTPLhg+7YkDyc5nmQPQFWdqKpd4wgrSVq+rq2bfcDNwIfPbEiyDrgFeC0wBxxOsr+qHhp1SElrgydsx6NToa+q+5NsXrD5SuB4VZ0ASHIHcD3QqdAn2Q3sBti0aVPHuKuPPVFJ0zZMj34j8OjA/TlgY5LnJ7kVeHmSP1jsm6tqb1X1qqq3YcOGIWJIks5l5LNuqup7wNu67DvORc2m+Sego3hJs2SYEf1jwMUD9y/qb+vMRc0kafyGKfSHgS1JLkmyHtgB7F/OAZJsT7L39OnTQ8SQJJ1L1+mVtwOHgMuTzCXZVVVPAjcC9wLHgDur6uhyntwRvSSNX9dZNzsX2X4QOLjSJ1/tFx6RpNXAC49IUuNcj16SGuc1Y5dpsamTTqmUVsb3zvjZupGkxtm6kaTGTbXQO49eksbP1o0kNc7WjSQ1zlk3AxY7+++62NLsW2whQ9e4t3UjSc2zdSNJjbPQS1LjLPSS1Djn0UtS4zwZK0mNs3UjSY2z0EtS4yz0ktQ4C70kNc5CL0mNa2qtmy5Xf1psDYyVHFfSygzznmph7ZpJ/z84vVKSGmfrRpIaZ6GXpMZZ6CWpcRZ6SWqchV6SGjfy6ZVJngn8FfAEcF9V/e2on0OS1F2nEX2S25KcTPLggu3bkjyc5HiSPf3NbwTuqqobgOtGnFeStExdWzf7gG2DG5KsA24BrgG2AjuTbAUuAh7t7/Z/o4kpSVqpToW+qu4HHl+w+UrgeFWdqKongDuA64E55ot95+NLksZnmB79Rp4aucN8gb8KeD9wc5JrgQOLfXOS3cBugE2bNq04hMsTSJM3ifddl2UCfP93M/KTsVX1n8DvdthvL7AXoNfr1ahzSJLmDdNaeQy4eOD+Rf1tnXnNWEkav2EK/WFgS5JLkqwHdgD7l3MAFzWTpPHrOr3yduAQcHmSuSS7qupJ4EbgXuAYcGdVHV3Okzuil6Tx69Sjr6qdi2w/CBxc6ZNX1QHgQK/Xu2Glx5AkndtUpz86opek8fPCI5LUOEf0ktQ4R/SS1LhUTf+zSklOAd8Y4SEvBL47wuONmvmGY77hmG84s5TvxVW1YamdZqLQj1qSI1XVm3aOxZhvOOYbjvmGM+v5zsZFxySpcRZ6SWpcq4V+77QDLMF8wzHfcMw3nFnP9zOa7NFLkp7S6ohektTXRKFP8rwkn0rytf5/n7vIfpuSfDLJsSQPJdk8S/n6+57fXzju5klk65ovycuSHEpyNMlXkvzOmDOd7XrEg48/PcnH+o9/YVL/lsvI93v919hXknwmyYtnKd/Afr+ZpJJMdBZJl3xJfrv/Mzya5KOzlK9fSz6b5Ev9f+PXTTLfslXVqv8C/gzY07+9B3jPIvvdB7y2f/tZwM/NUr7+4+8DPgrcPEs/P+AyYEv/9i8A3waeM6Y864CvAy8B1gP/CmxdsM87gFv7t3cAH5vgz6tLvl898/oC3j5r+fr7PRu4H3gA6M1SPmAL8CXguf37Pz9j+fYCb+/f3go8Mql8K/lqYkTP/LVqP9S//SHgDQt36F+4/Lyq+hRAVf2wqv5rVvIBJPll4AXAJyeU64wl81XVV6vqa/3b3wJOAkt+UGOFFrse8WKZ7wJenSRjyrPsfFX12YHX1wM8dR3lmcjX96fAe4D/mWA26JbvBuCWqvo+QFWdnLF8BZzfv30B8K0J5lu2Vgr9C6rq2/3b/8F8sVzoMuAHST7e/3Prz5Osm5V8SZ4GvBf4/QllGtTl5/cTSa5kfqTz9THlOdv1iDcutk/NXxvhNPD8MeVZqEu+QbuAT4w10U9bMl+SXwIurqppXHS1y8/vMuCyJJ9P8kCSbRNL1y3fHwFvTjLH/FLt75xMtJUZ+TVjxyXJp4EXnuWhdw/eqapKcrapROcBrwReDnwT+BjwVuCDM5LvHcDBqpobx8B0BPnOHOdFwEeAt1TVj0ebsj1J3gz0gFdNO8sZ/UHFXzL/+p9V5zHfvrma+b+G7k/yi1X1g6mmespOYF9VvTfJK4CPJLliVt8Tq6bQV9VrFnssyXeSvKiqvt0vRGf7M28O+HJVneh/z93ArzCiQj+CfK8AXpnkHcyfP1if5IdVteiJtAnnI8n5wD3Au6vqgVHkWkSX6xGf2WcuyXnM//n8vTFmOttzn3HW6yUneQ3zv0hfVVX/O6FssHS+ZwNXAPf1BxUvBPYnua6qjsxAPph/v36hqn4E/HuSrzJf+A/PSL5dwDaAqjqU5BnMr4EzyRZTZ620bvYDb+nffgvwD2fZ5zDwnCRn+sq/Bjw0gWzQIV9VvamqNlXVZubbNx8eVZEfRb7MXxf47/u57hpzni7XIx7M/FvAP1X/zNgELJkvycuBvwGum3B/ecl8VXW6qi6sqs3919sD/ZyTKPJL5uu7m/nRPEkuZL6Vc2KG8n0TeHU/30uBZwCnJpRv+aZ9NngUX8z3Zj8DfA34NPC8/vYe8IGB/V4LfAX4N2AfsH6W8g3s/1YmO+tmyXzAm4EfAV8e+HrZGDO9Dvgq8+cB3t3f9ifMFySYf2P9HXAc+GfgJRN+zS2V79PAdwZ+VvtnKd+Cfe9jgrNuOv78wnx76aH++3XHjOXbCnye+Rk5XwZ+fZL5lvvlJ2MlqXGttG4kSYuw0EtS4yz0ktQ4C70kNc5CL0mNs9BLUuMs9JLUOAu9JDXu/wFt8WASYhhN8wAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 400905\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADNhJREFUeJzt3V+MXHUZxvHnsYSqEFagBIEWFtJqrAnBZCwX/gEDxCIuGEO0IAkmhA0q0cQbm2Bi4pV650WDbpQAXlCQRO0CQqRC0ASUrcFKIYVCIC0gLRpXo0QkvF7sAcdl/5yZOTPnnHe+n6Rh5sxh9n13Z5/5ze+c81tHhAAAeb2j7gIAAMNF0ANAcgQ9ACRH0ANAcgQ9ACRH0ANAcgQ9ACRH0ANAcgQ9ACR3VN0FSNK6deticnKy7jIAoFX27NnzSkSctNp+jQj6yclJzc3N1V0GALSK7efL7MfUDQAkR9ADQHIEPQAkV2vQ256yPTM/P19nGQCQWq1BHxGzETE9MTFRZxkAkBpTNwCQHEEPAMkR9ACQXCMumAKaanL73W/dfu47l9RYCdA/gh5YpDvcgQyYugGA5DiPHgCS4zx6AEiOqRsASI6gB4DkCHoASI7TKwGVO6Vy8T6cV4+2YEQPAMkR9ACQHEEPAMkR9ACQHEEPAMmxBAIAJMcSCACQHFM3AJAcQQ8AyRH0AJAcSyBgbA36l6T4M4NoC0b0AJAcQQ8AyRH0AJAcQQ8AyRH0AJAcQQ8AyRH0AJAcQQ8AyRH0AJAcyxQDQHIsUwwAyTF1AwDJEfQAkByrV2KsDLpiZZnnZSVLNA0jegBIjqAHgOQIegBIjqAHgOQIegBIjqAHgOQIegBIjqAHgOQIegBIjqAHgOQIegBIjqAHgOQIegBIjtUrkd6wVqws8/VYyRJNwIgeAJIj6AEgOYIeAJIj6AEguaEEve1jbM/Z/vQwnh8AUF6poLd9k+3Dth9ftH2r7f22D9je3vXQNyTdUWWhAID+lB3R3yxpa/cG22sk7ZB0saTNkq6wvdn2RZKekHS4wjoBAH0qdR59RDxke3LR5i2SDkTEs5Jke6ekyyQdK+kYLYT/q7bviYg3Fj+n7WlJ05J0+umn91s/AGAVg1wwdZqkg133D0k6NyKulyTbX5T0ylIhL0kRMSNpRpI6nU4MUAcAYAVDuzI2Im4e1nMDAMob5KybFyRt6Lq/vtgGAGiQQYL+UUmbbJ9p+2hJ2yTt6uUJbE/Znpmfnx+gDADASsqeXnmbpIclvd/2IdvXRMTrkq6XdJ+kJyXdERH7evniETEbEdMTExO91g0AKMkR9R8H7XQ6MTc3V3cZSGTUK1aWwUqWqJrtPRHRWW0/lkAAgOQIegBIrtag52AsAAxfrUHPwVgAGD6mbgAgOYIeAJIj6AEgOQ7GAkByHIwFgOSYugGA5Ah6AEiOoAeA5Ah6AEhuaH9hqgzbU5KmNm7cWGcZSKKJK1YCTVBr0EfErKTZTqdzbZ11AKPQ/UbEksUYJaZuACA5gh4AkiPoASA5gh4AkiPoASA5FjUDgORY1AwAkmPqBgCSI+gBIDmCHgCSI+gBILla17oBxhXr3mCUCHq0GitWAqvjPHoASI7z6AEgOQ7GAkByBD0AJEfQA0ByBD0AJEfQA0ByBD0AJMcFU2gdLpICekPQAzVjOQQMG1M3AJAcSyAAQHIsgQAAyTF1AwDJEfQAkBxBDwDJEfQAkBxBDwDJEfQAkBxXxgINwlWyGAaCHq3A+jZA/5i6AYDkCHoASI6gB4DkCHoASI6gB4DkWKYYAJJjmWIASI7z6NFY437uPBdPoSrM0QNAcgQ9ACRH0ANAcgQ9ACRH0ANAcgQ9ACTH6ZVolHE/pRIYBkb0AJAcQQ8AyTF1A7QAV8liEIzoASA5gh4AkiPoASA5gh4AkiPoASA5gh4AkuP0StSOq2GB4SLogZbhnHr0iqkbAEiOoAeA5CqfurH9AUlfk7RO0u6IuLHqr4H2Y14eGJ1SI3rbN9k+bPvxRdu32t5v+4Dt7ZIUEU9GxHWSPifpI9WXDADoRdmpm5slbe3eYHuNpB2SLpa0WdIVtjcXj10q6W5J91RWKQCgL6WCPiIekvTXRZu3SDoQEc9GxGuSdkq6rNh/V0RcLOkLyz2n7Wnbc7bnjhw50l/1AIBVDTJHf5qkg133D0k61/b5kj4raa1WGNFHxIykGUnqdDoxQB0AgBVUfjA2Ih6U9GDVz4v24wAsUI9Bgv4FSRu67q8vtgFvIdyHi4unUMYgQf+opE22z9RCwG+TdGUvT2B7StLUxo0bBygDgEToY3mlgt72bZLOl7TO9iFJ34qIH9u+XtJ9ktZIuiki9vXyxSNiVtJsp9O5trey0WSM4oFmKRX0EXHFMtvvEadQQoQ70GQsgQAAyRH0AJBcrcsUczAWGD4O0qLWEX1EzEbE9MTERJ1lAEBqTN0AQHIEPQAkx58SBBLidFd042AsMEY4MDueag16roxth+VGhwQF0A7M0QNAcszRo2/MAwPtQNADY4opufHB1A0AJMdZN3gbpmSAXFgCAQCSY+oGAJIj6AEgOc66gSTm5bE0rqTNgaAfY4Q7lsLrIh+CHkApjO7bi9MrAfSM0G8XFjUbM3wsB8YPUzdJMeJCXXjtNQ9BD2AkWFunPgR9y5UZPTFdg2Hi9dV8BD2AdJg++n8EfQstN4JiZIWm4TXZDCyBAADJ1Rr0tqdsz8zPz9dZBgCkxnn0A1huHnAYZxfwERjjhnn26jBHD6C1eDMoh6CvyCAjbl6swAI+uQ4HQT9Cvb6IedEDqAJB3zCEO8ZNVQMgfneWR9ADaDxCfDAEPQAsI8vxM4IewNioM7jr/NoEfQlZ3tUBjCeCHkBqzO/zpwR7xosGQLc2fOIf6yUQFod2U39IAKrXhoCuCqtXAkByaefox+ndGgBWkjbol7PSHDtvDgCq0qTjeWMX9GU16YcEYLiyD/JSBT3hDABv1/qgJ9wBVKnMomnLjfqbmketD3oAGLWmBvpyCHoAGLFRHxMYi6Bv27svAFSJC6YAIDmCHgCSI+gBIDmCHgCSqzXobU/Znpmfn6+zDABIrdagj4jZiJiemJioswwASI2pGwBIjqAHgOQIegBIzhFRdw2yfUTS833+7+skvVJhOXWil+bJ0odEL001SC9nRMRJq+3UiKAfhO25iOjUXUcV6KV5svQh0UtTjaIXpm4AIDmCHgCSyxD0M3UXUCF6aZ4sfUj00lRD76X1c/QAgJVlGNEDAFbQuqC3fYLtX9l+uvjv8Uvsc4btP9h+zPY+29fVUetqSvZyju2Hiz722v58HbWupkwvxX732v6b7btGXeNKbG+1vd/2Advbl3h8re3bi8d/Z3ty9FWWU6KXjxe/H6/bvryOGssq0cvXbT9R/G7stn1GHXWupkQf19n+U5FZv7W9udICIqJV/yR9T9L24vZ2Sd9dYp+jJa0tbh8r6TlJp9Zde5+9vE/SpuL2qZJekvSeumvvp5fisQskTUm6q+6au2paI+kZSWcVr50/Stq8aJ8vS/pBcXubpNvrrnuAXiYlnS3pVkmX113zgL18QtK7i9tfauLPpWQfx3XdvlTSvVXW0LoRvaTLJN1S3L5F0mcW7xARr0XEv4u7a9XcTy5lenkqIp4ubr8o6bCkVS+QqMGqvUhSROyW9I9RFVXSFkkHIuLZiHhN0k4t9NOtu787JV1g2yOssaxVe4mI5yJir6Q36iiwB2V6eSAi/lXcfUTS+hHXWEaZPv7edfcYSZUePG1qAK7k5Ih4qbj9Z0knL7WT7Q2290o6qIXR5YujKrAHpXp5k+0tWhgRPDPswvrQUy8Nc5oWXidvOlRsW3KfiHhd0rykE0dSXW/K9NIWvfZyjaRfDrWi/pTqw/ZXbD+jhU/HX62ygEb+cXDb90t67xIP3dB9JyLC9pLvfBFxUNLZtk+V9HPbd0bEy9VXu7Iqeime5xRJP5F0dUTUMhKrqhegaravktSRdF7dtfQrInZI2mH7SknflHR1Vc/dyKCPiAuXe8z2y7ZPiYiXivA7vMpzvWj7cUkf08JH7pGqohfbx0m6W9INEfHIkEpdVZU/l4Z5QdKGrvvri21L7XPI9lGSJiT9ZTTl9aRML21RqhfbF2phsHFe15Rtk/T6M9kp6cYqC2jj1M0u/e+d7mpJv1i8g+31tt9V3D5e0kcl7R9ZheWV6eVoST+TdGtEjPyNqger9tJgj0raZPvM4vu9TQv9dOvu73JJv47iyFnDlOmlLVbtxfaHJP1Q0qUR0dTBRZk+NnXdvUTS05VWUPcR6T6OYJ8oaXfxjbhf0gnF9o6kHxW3L5K0VwtHt/dKmq677gF6uUrSfyQ91vXvnLpr76eX4v5vJB2R9KoW5io/WXftRV2fkvSUFo5/3FBs+7YWAkSS3inpp5IOSPq9pLPqrnmAXj5cfO//qYVPJfvqrnmAXu6X9HLX78auumvus4/vS9pX9PCApA9W+fW5MhYAkmvj1A0AoAcEPQAkR9ADQHIEPQAkR9ADQHIEPQAkR9ADQHIEPQAk91/SRQTRujmMXQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 400905\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta234\n", + "field\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "delta curv\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 400905\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut2,pzcut2 400905\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for quad in [t1234,t1231,t1212,t1123] :\n", + " plotTriplets(quad,600)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYsAAAD8CAYAAACGsIhGAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAFBhJREFUeJzt3XHw5HV93/Hny0NIaOmpIKnlaO/sEZXSKg1FOwxKMehhvKOjlkJoUDEgdqhkpjMd0qTDtMO0hj86amQgaIihjYCQYg+PhiGGqwljDBxBwnFS7y4y3NUUjZWIUZDy7h/7Pbpu93ef3d9v9/fb/f2ej5mb2+/3+/l+v+/97m/3tZ/P97u7qSokSTqcl6x0AZKk2WdYSJKaDAtJUpNhIUlqMiwkSU2GhSSpybCQJDUZFpKkJsNCktR0xEoXMCnHHXdcbdy4caXLkKS5smvXrm9V1Stb7eY+LJJsBbZu3ryZBx98cKXLkaS5kuSJUdrN/TBUVd1VVZetX79+pUuRpFVr7sNCkjR9hoUkqcmwkCQ1GRaSpCbDQpLUZFhIkpoMC0lS09x/KE/SbNh41Y4Xb3/9Iz+zgpVoGuxZSJKaDAtJUtPch0WSrUlufPrpp1e6FElateY+LPxuKEmavrkPC0nS9BkWkqQmw0KS1OTnLDQzvE5fml2GhWaSwTFb+h+Pfj42a4fDUJKkJnsWkl40bo9uoR6HVh97FpKkJnsWWnaej5Dmj2GhmWe4rAyHmNTPsNDc8gqd2WXArz5zHxZJtgJbN2/evNKlaBHGfffqu93J85hqFHMfFlV1F3DXaaeddulK1yLNCwNC45r7sNDs84VJmn+GhbRGGNpaCsNC0lR5snt1MCykVczehCbFsNCqsxbfyRoKmja/7kOS1GRYSJKaHIbSVDgsIq0u9iwkSU32LDQxs9ibWIsnu6VpmOmwSPI64ErgOOALVXX9CpekVchAkdpGGoZK8rIkdyT5apI9Sf7hYnaW5KYkTyV5dMiyLUkeT7I3yVUAVbWnqi4HzgfOWMw+JUlLN+o5i48Bv1NVrwVeD+zpX5jk+CTHDMwb9jWwnwa2DM5Msg64DjgXOBm4MMnJ3bJtwA7g7hFrlYbaeNWOF/9JGk9zGCrJeuDNwPsAquo54LmBZm8BLk/yjqp6NsmlwLvovfi/qKq+mGTjkN2cDuytqv3dPm8FzgMeq6rtwPYkO4DPjH7XpNXN0NNyGuWcxSbgm8BvJHk9sAu4sqq+d6hBVd2eZBNwW5LbgUuAc8ao4wTgyb7pA8Abk5xFL3SOYoGehb9nocXwhVYazyjDUEcAfx+4vqpOBb4HXDXYqKquBX4AXA9sq6pnllpcVe2sqg9X1Qer6roF2txVVZetX79+qbuTJC1glLA4AByoqi9303fQC48fkeRM4BTgTuDqMes4CJzYN72hmydJmgHNYaiq+rMkTyZ5TVU9DrwVeKy/TZJTgRuBdwJ/CvxWkmuq6pdHrOMB4KRuKOsgcAHws2PcD2kivIx2ujy+82vUq6H+Bb0AeAR4A/DvB5YfDZxfVfuq6gXgYuCJwY0kuQX4EvCaJAeSfACgqp4HrgDuoXel1Weravdi7pAkafJG+lBeVT0MnHaY5fcPTP8Q+OSQdhceZht34+WxkjST/G4oSVKTYSFJajIsJElNM/1FgpJ+lB8m1EqxZyFJajIsJElNhoUkqcmwkCQ1eYJbmnGe1NYssGchSWqyZyEtwC+9k/4fexaSpKa571n4S3lajTxPoVkz9z0LfylPkqZv7sNCkjR9hoUkqcmwkCQ1GRaSpKa5vxpK0nzycyzzxbDQkniJp7Q2OAwlSWoyLCRJTYaFJKnJsJAkNXmCWxqBV+5orbNnIUlqMiwkSU2GhSSpybCQJDUZFpKkJsNCktQ002GR5HVJbkhyR5IPrXQ9krRWjfw5iyTrgAeBg1X1zsXsLMlNwDuBp6rqlIFlW4CPAeuAT1XVR6pqD3B5kpcANwPXL2a/0jzwSxk1y8bpWVwJ7Bm2IMnxSY4ZmLd5SNNPA1uGrL8OuA44FzgZuDDJyd2ybcAO4O4xapUkTdBIYZFkA/AzwKcWaPIW4HNJjuraXwr86mCjqvoi8O0h658O7K2q/VX1HHArcF63zvaqOhe4aJRaJUmTN+ow1EeBfwUcM2xhVd2eZBNwW5LbgUuAc8ao4wTgyb7pA8Abk5wFvAs4igV6Fkm2Als3bx7WkZEkTUKzZ5Hk0DmGXYdrV1XXAj+gd15hW1U9s9TiqmpnVX24qj5YVdct0Oauqrps/fr1S92dJGkBowxDnQFsS/J1esNDZyf5z4ONkpwJnALcCVw9Zh0HgRP7pjd08yRJM6AZFlX1i1W1oao2AhcAv1dV/6y/TZJTgRvpnWd4P3BskmvGqOMB4KQkm5Ic2e1n+xjrS5KmaFKfszgaOL+q9lXVC8DFwBODjZLcAnwJeE2SA0k+AFBVzwNXAPfQu+Lqs1W1e0K1SZKWaKzfs6iqncDOIfPvH5j+IfDJIe0uPMy278bLYyVpJs30J7glSbPBsJAkNfmzqpJWnD9bO/vsWUiSmgwLSVKTYSFJavKchTQmx9e1FhkW0hIsJTj8/QrNE4ehJElNhoUkqcmwkCQ1ec5CY3OsXVp77FlIkpoMC0lSk2EhSWoyLCRJTYaFJKnJsJAkNXnprLSMvOxY82qmexZJXpfkhiR3JPnQStcjSWtVMyyS/FiSP0rylSS7k/zbxe4syU1Jnkry6JBlW5I8nmRvkqsAqmpPVV0OnA+csdj9SpKWZpSexbPA2VX1euANwJYkb+pvkOT4JMcMzNs8ZFufBrYMzkyyDrgOOBc4Gbgwycndsm3ADuDuEWqVJE1B85xFVRXwTDf50u5fDTR7C3B5kndU1bNJLgXeRe/Fv39bX0yycchuTgf2VtV+gCS3AucBj1XVdmB7kh3AZ0a9Y9JyW+h8hL95odVgpBPc3Tv/XcBm4Lqq+nL/8qq6Pckm4LYktwOXAOeMUccJwJN90weANyY5i17oHMUCPYskW4GtmzcP68hIkiZhpBPcVfV/quoNwAbg9CSnDGlzLfAD4HpgW1U9M9hmXFW1s6o+XFUfrKrrFmhzV1Vdtn79+qXuTpK0gLGuhqqq7wD3Mfy8w5nAKcCdwNVj1nEQOLFvekM3T5I0A0a5GuqVSV7W3f5xesNLXx1ocypwI73zDO8Hjk1yzRh1PACclGRTkiOBC4DtY6wvSZqiUXoWrwLuS/IIvRf1e6vq8wNtjgbOr6p9VfUCcDHwxOCGktwCfAl4TZIDST4AUFXPA1cA9wB7gM9W1e7F3ilJ0mSNcjXUI8CpjTb3D0z/EPjkkHYXHmYbd+PlsZI0k2b6E9ySpNlgWEiSmgwLSVKTYSFJajIsJElNhoUkqckfP9JI/NEeaW2zZyFJarJnIWmmDPZi/Yr32WDPQpLUZM9CmjLP92g1sGchSWoyLCRJTYaFJKnJsJAkNRkWkqQmw0KS1GRYSJKaDAtJUpNhIUlqMiwkSU2GhSSpybCQJDUZFpKkJsNCktTkV5RL0ozq/3r7lf4RKMNC0tyYpRfPtcZhKElSk2EhSWoyLCRJTYaFJKnJsJAkNc301VBJXgdcCRwHfKGqrp/2Pr3aQpL+f82eRZITk9yX5LEku5NcudidJbkpyVNJHh2ybEuSx5PsTXIVQFXtqarLgfOBMxa7X0nS0ozSs3ge+JdV9VCSY4BdSe6tqscONUhyPPD9qvpu37zNVbV3YFufBj4B3Nw/M8k64DrgHOAA8ECS7VX1WJJtwIeA/zT+3Zs+eyLSyvC5t7yaPYuq+kZVPdTd/i6wBzhhoNlbgM8lOQogyaXArw7Z1heBbw/ZzenA3qraX1XPAbcC53XrbK+qc4GLhtWXZGuSG59++unWXZEkLdJYJ7iTbAROBb7cP7+qbgfuAW5LchFwCfBPxtj0CcCTfdMHgBOSnJXk40l+Dbh72IpVdVdVXbZ+/foxdidJGsfIJ7iT/FXgt4FfqKq/GFxeVdcmuRW4HvjbVfXMUourqp3AzqVuR4vT382XtLaNFBZJXkovKH6rqv7LAm3OBE4B7gSuBq4Yo46DwIl90xu6eVoEx3Klw5vGc2S1P+9GuRoqwK8De6rqPy7Q5lTgRnrnGd4PHJvkmjHqeAA4KcmmJEcCFwDbx1hfkjRFo/QszgB+DviTJA938/51VfWfQzgaOL+q9gEkuRh43+CGktwCnAUcl+QAcHVV/XpVPZ/kCnrnPdYBN1XV7kXep1Vh2u9SVvu7IEmT1QyLqvoDII029w9M/xD45JB2Fx5mG3ezwElsLR/PU0gaxq/7kCQ1zfTXfUjSPPZ2V+Mwr2FxGKvxAZdWo5V6rs5jkC2Ww1CSpCZ7FqucvSNJk2DPQpLUZFhIkpochmJtnaSSpMUwLCSp4zm+hRkWK2xSf5z2jiRNk2ExQ1bqBd+gkZbfvPViDAtJa4JvipbGsJigwT/GeXi3IEmjMCwWwXcokkY1b8NNCzEsJK1a03hjt1bfLBoWc2a1vEuRNF/8BLckqcmexRTZC5C0WhgWklaVeTynMA9vLA2LOTaPTwppXviDSj/KcxaSpCZ7FnNgVt9pSFo7DIsRTfIF2xd/SfPGYShJUpNhIUlqchhqmTj0JGme2bOQJDUZFpKkJoehJGkOLfeHBu1ZSJKaDAtJUpNhIUlqMiwkSU2e4JakObDSn9WyZyFJajIsJElNhoUkqcmwkCQ1GRaSpCbDQpLU5KWzkta0lb4kdV7Ys5AkNRkWkqQmh6HWELvbkhbLnoUkqcmwkCQ1OQwlSQ0O4dqzkCSNwJ6FJC2Tee6h2LOQJDUZFpKkJsNCktRkWEiSmgwLSVKTYSFJajIsJElNhoUkqcmwkCQ1papWuoaJSPJN4IlFrn4c8K0JljMp1jUe6xqPdY1vVmtbSl1/q6pe2Wq0asJiKZI8WFWnrXQdg6xrPNY1Husa36zWthx1OQwlSWoyLCRJTYZFz40rXcACrGs81jUe6xrfrNY29bo8ZyFJarJnIUlqWtVhkeQVSe5N8rXu/5cv0O53knwnyecH5m9K8uUke5PcluTIbv5R3fTebvnGKdX13q7N15K8t5t3TJKH+/59K8lHu2XvS/LNvmU/v1x1dfN3Jnm8b//Hd/NX8ngdnWRHkq8m2Z3kI33tF3W8kmzp7ufeJFcNWb7g/U3yi938x5O8fdRtTrOuJOck2ZXkT7r/z+5bZ+hjukx1bUzy/b5939C3zk919e5N8vEkWca6Lhp4Dr6Q5A3dsuU4Xm9O8lCS55O8Z2DZQs/NJR8vqmrV/gOuBa7qbl8F/MoC7d4KbAU+PzD/s8AF3e0bgA91t/85cEN3+wLgtknXBbwC2N/9//Lu9suHtNsFvLm7/T7gE9M8XoerC9gJnDZknRU7XsDRwD/q2hwJ/D5w7mKPF7AO2Ae8utveV4CTR7m/wMld+6OATd121o2yzSnXdSrwN7rbpwAH+9YZ+pguU10bgUcX2O4fAW8CAvy3Q4/pctQ10ObvAvuW+XhtBP4ecDPwnhGfm0s6XlW1unsWwHnAb3a3fxP4x8MaVdUXgO/2z+uS92zgjiHr92/3DuCtYyb1KHW9Hbi3qr5dVf8buBfYMlDjTwLH03sBnISJ1NXY7rIer6r6y6q6D6CqngMeAjaMse9BpwN7q2p/t71bu/oWqrf//p4H3FpVz1bVnwJ7u+2Nss2p1VVVf1xV/7Obvxv48SRHjbn/ide10AaTvAr4a1X1h9V7JbyZBZ7by1DXhd26k9Ksq6q+XlWPAC8MrDv0OTCh47Xqw+Inquob3e0/A35ijHWPBb5TVc930weAE7rbJwBPAnTLn+7aT7KuF/cxZP+HHHq303+VwruTPJLkjiQnjlHTpOr6ja77/W/6nlgzcbySvIxeD/ILfbPHPV6jPC4L3d+F1h1lm9Osq9+7gYeq6tm+ecMe0+Wqa1OSP07y35Oc2df+QGOb067rkH8K3DIwb9rHa9x1J3G8OGLcFWZNkt8F/vqQRb/UP1FVlWTZLv1aprouAH6ub/ou4JaqejbJB+m9Kzq7f4Up13VRVR1Mcgzw211tN4+y4rSPV5Ij6D2pP15V+7vZzeO1liT5O8CvAG/rm73ox3QCvgH8zar68yQ/BXyuq3EmJHkj8JdV9Wjf7JU8XlM192FRVT+90LIk/yvJq6rqG11X7KkxNv3nwMuSHNG9q9gAHOyWHQROBA50L0Lru/aTrOsgcFbf9AZ646GHtvF64Iiq2tW3z/4aPkVvrP9HTLOuqjrY/f/dJJ+h16W+mRk4XvSuQ/9aVX20b5/N47XAfvp7IP1/F4NtBu/v4dZtbXOadZFkA3AncHFV7Tu0wmEe06nX1fWYn+32vyvJPuAnu/b9Q4nLfrw6FzDQq1im43W4dc8aWHcnkzleq34Yajtw6IqA9wL/ddQVuz/U+4BDVxv0r9+/3fcAvzcwFDSJuu4B3pbk5eld/fO2bt4hFzLwh9q9kB6yDdgzRk1LqivJEUmO6+p4KfBO4NA7rhU9XkmuofdE/4X+FRZ5vB4ATkrvSrkj6b1gbD9Mvf33dztwQXpX2WwCTqJ34nGUbU6trm54bge9iwjuP9S48ZguR12vTLKu2/+r6R2v/d2Q5F8keVM3zHMxYzy3l1pXV89LgPPpO1+xjMdrIUOfAxM6Xqv+aqhj6Y1Pfw34XeAV3fzTgE/1tft94JvA9+mN5729m/9qek/mvcDtwFHd/B/rpvd2y189pbou6faxF3j/wDb2A68dmPcf6J2g/Aq9oHvtctUF/BV6V2Y90tXwMWDdSh8veu+iil4QPNz9+/mlHC/gHcD/oHfVyi918/4dsK11f+kNq+0DHqfvipRh21zE3/ui6gJ+Gfhe3/F5mN6FEws+pstU17u7/T5M78KErX3bPI3eC/E+4BN0HzBejrq6ZWcBfziwveU6Xv+A3uvU9+j1dHa3XjMmcbz8BLckqWm1D0NJkibAsJAkNRkWkqQmw0KS1GRYSJKaDAtJUpNhIUlqMiwkSU3/F9Kc0e/Ju8HDAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphiNor\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADI9JREFUeJzt3W2MrGdZB/D/RSslUbK8HITaFw5NG6X6xWZTEYlpxJhaOK2vCfhBGpFjY0jwk2lCoolfLJqYaETNSWmEBAsRRXtoCe8Nn1o5JW0PbUHapoQ2lYImq8QERW8/zBycbM/umT07M88z9/x+yaazM0/PXPvszH+u53petlprAaBfLxi6AACWS9ADdE7QA3RO0AN0TtADdE7QA3RO0AN0TtADdE7QA3TuwqELSJIjR460o0ePDl0GwFp54IEHvtVae8W5lhtF0B89ejSnTp0augyAtVJVX5tnOaMbgM4JeoDOCXqAzgl6gM4NGvRVdayqTuzs7AxZBkDXBg361trJ1trxra2tIcsA6JrRDUDnBD1A50ZxwhQwHkdvvft7t5+67U3nvJ/x09EDdE5Hz9xmO7pEt7fu/M42h6BnX7vDfd7HgPEQ9MCefJj3QdDDBhHcm8mZsQCdG7Sjb62dTHJye3v7HUPWAZtOp983oxuSLPaN7miOcRHiOI4eoHOCHqBzRjesjJEODEPQsxZ8SMD5M7oB6JyOHjphq4e9CHoGIZRgdQQ9rIGDfjA6dp5Zgn6DCYNxm+f343fIPAQ9S3XQsDLGgcVzUTOAzrmoGQxsHccvtsLWi+PoATpnRs+o6BTPbR23ABiWjh6gc4IeoHOCHqBzZvSM1l6z6L3uN9OHs9PRA3RO0AN0TtADdE7QA3RO0AN0zlE3sCLOaGUorl4J0LlBg761drK1dnxra2vIMgC6ZkYP0Dkzerrhypdwdjp6gM7p6IFDsSU1fjp6gM4JeoDOGd3QJeME+H86eoDO6eg3jNPwYfPo6AE6J+gBOifoATon6AE6Z2csLJGd34yBjh6gc4IeoHP+whRA5/yFKYDO2RlL91z3hk1nRg/QOUEP0DlBD9A5QQ/QOUEP0DlBD9A5QQ/QOUEP0DknTMGCuWIlY6OjB+icjh7iMgn0TUcP0DkdPRySmTxjp6MH6JygB+ic0Q0bxU5XNpGOHqBzgh6gc0Y3bCxHy7ApdPQAnRs06KvqWFWd2NnZGbIMgK4NGvSttZOtteNbW1tDlgHQNaMbgM4JeoDOCXqAzgl6gM4JeoDOCXqAzgl6gM65BAKwMK4OOk6CHs6D6+SwTgT9BhBKsNnM6AE6J+gBOifoATon6AE6J+gBOifoATon6AE6J+gBOifoATrnzFiYkzOMWVc6eoDOCXqAzgl6gM4JeoDOCXqAzgl6gM4JeoDOCXqAzgl6gM4JeoDODXoJhKo6luTYlVdeOWQZXXK6PnDGoB19a+1ka+341tbWkGUAdM1FzWCX2a2hp25704CVwGKY0QN0TtADdE7QA3RO0AN0zs5Y2IfDVOmBjh6gc4IeoHOCHqBzgh6gc4IeoHOCHqBzgh6gc4IeoHOCHqBzgh6gc4IeoHOudQMsxe7rBPkjLsMR9CPmLx0BiyDogdHQ3CyHGT1A5wQ9QOeMbtaETVrgfOnoATqnowdWwlbpcHT0AJ0T9ACdE/QAnRP0AJ0T9ACdE/QAnRP0AJ3r6jh6x+kCPF9XQb/pdl//GyAxugHono4eWLmxjVnHVs+iCfo1Z1wDnIvRDUDnBD1A54xuAPbQy+xeRw/QubXv6O2MhPXmPbx8OnqAzq19Rw8wr1XO3Mc03xf0ADN6HCUZ3QB0Tkc/h2Vsgo1psw7om44eoHOCHqBzRjfA6Bl1Hs7Cg76qXpvkXUmOJPlMa+0vF/0c8/DCAPbT49E1e5kr6KvqjiRvTvJca+3HZu6/PsmfJrkgye2ttdtaa48luaWqXpDkA0kGCfp1tUkvPjgfmriDm3dG/9dJrp+9o6ouSPLeJD+f5Ookb62qq6eP3Zjk7iT3LKxSAM7LXB19a+3zVXV0193XJnm8tfZkklTVh5LclOTR1tpdSe6qqruT/M3iyl1vunVgCIeZ0V+S5Osz3z+d5Ceq6rokv5TkouzT0VfV8STHk+Tyyy8/RBkA7GfhO2Nba/cmuXeO5U4kOZEk29vbbdF1ADBxmOPon0ly2cz3l07vA2BEDtPRfyHJVVX1mkwC/i1Jfm0hVQGM2Lod+TPv4ZV3JrkuyZGqejrJ77fW3ldV70zyiUwOr7yjtfbI0ipdI+v2IoB15b02n3mPunnrHvffE4dQAiMg9Pe2cZdA8GIANs3GBT3AkHafT7OKhnPQq1dW1bGqOrGzszNkGQBdG7Sjb62dTHJye3v7HUPWsZszWKEf3s+uRw/QPTN6gENYhwM8BP0BjeGXalMUOAijG4DO6eiXTPcNDG2jg34MYxiAZdvooF8knTswVmb0AJ1zZixA55wZC7AgYx3hmtED3Rlr4A7FjB6gczr6KR0A0CsdPUDnBD1A5wQ9QOcEPUDn7IwdATuCgWVyZixA5wYN+tbaydba8a2trSHLAOia0Q0wSkaai7MRQe8FA2yyjQj6ZfEBAptjnd/vDq8E6JygB+icoAfonBk9wJINPd/X0QN0TtADdE7QA3RO0AN0zkXNADrnomYAnTO6AeicoAfonKAH6Fy11oauIVX1zSRfO8///UiSby2wnEVR18Go62DUdTBjrSs5XG2vbq294lwLjSLoD6OqTrXWtoeuYzd1HYy6DkZdBzPWupLV1GZ0A9A5QQ/QuR6C/sTQBexBXQejroNR18GMta5kBbWt/YwegP310NEDsI+1C/qq+uOq+nJVPVxVH62ql+yx3PVV9ZWqeryqbl1BXb9aVY9U1f9W1Z570Kvqqao6XVUPVtWpEdW16vX1sqr6VFV9dfrfl+6x3P9M19WDVXXXEuvZ9+evqouq6sPTx++vqqPLquWAdd1cVd+cWUe/uaK67qiq56rqS3s8XlX1Z9O6H66qa0ZS13VVtTOzvn5vBTVdVlWfq6pHp+/Fd51lmeWur9baWn0l+bkkF05vvyfJe86yzAVJnkhyRZIXJnkoydVLruu1SX44yb1JtvdZ7qkkR1a4vs5Z10Dr64+S3Dq9fevZfo/Tx769gnV0zp8/yW8n+avp7bck+fBI6ro5yZ+v6vU087w/neSaJF/a4/Ebknw8SSV5XZL7R1LXdUk+tuJ1dXGSa6a3X5zkn8/ye1zq+lq7jr619snW2nen396X5NKzLHZtksdba0+21v4ryYeS3LTkuh5rrX1lmc9xPuasa+Xra/rvv396+/1JfmHJz7efeX7+2Xo/kuSNVVUjqGsQrbXPJ/m3fRa5KckH2sR9SV5SVRePoK6Va60921r74vT2fyR5LMkluxZb6vpau6Df5Tcy+RTc7ZIkX5/5/uk8f8UOpSX5ZFU9UFXHhy5maoj19crW2rPT2/+S5JV7LPeiqjpVVfdV1bI+DOb5+b+3zLTR2Eny8iXVc5C6kuSXp5v7H6mqy5Zc07zG/B78yap6qKo+XlU/usonno78fjzJ/bseWur6GuUfB6+qTyd51Vkeendr7R+ny7w7yXeTfHBMdc3hDa21Z6rqB5N8qqq+PO1Chq5r4fara/ab1lqrqr0O/3r1dH1dkeSzVXW6tfbEomtdYyeT3Nla+05V/VYmWx0/M3BNY/bFTF5T366qG5L8Q5KrVvHEVfUDSf4uye+01v59Fc95xiiDvrX2s/s9XlU3J3lzkje26YBrl2eSzHY2l07vW2pdc/4bz0z/+1xVfTSTzfNDBf0C6lr5+qqqb1TVxa21Z6ebqM/t8W+cWV9PVtW9mXRDiw76eX7+M8s8XVUXJtlK8q8LruPAdbXWZmu4PZN9H2OwlNfUYc0GbGvtnqr6i6o60lpb6nVwqur7Mgn5D7bW/v4siyx1fa3d6Kaqrk/yu0lubK395x6LfSHJVVX1mqp6YSY7z5Z2xMa8qur7q+rFZ25nsmP5rEcHrNgQ6+uuJG+b3n5bkudteVTVS6vqountI0l+KsmjS6hlnp9/tt5fSfLZPZqMlda1a457Yybz3zG4K8mvT48meV2SnZlR3WCq6lVn9q1U1bWZZOBSP7Cnz/e+JI+11v5kj8WWu75Wufd5EV9JHs9klvXg9OvMkRA/lOSemeVuyGTv9hOZjDCWXdcvZjJX+06SbyT5xO66Mjl64qHp1yNjqWug9fXyJJ9J8tUkn07ysun920lun95+fZLT0/V1Osnbl1jP837+JH+QSUORJC9K8rfT198/Jbli2etozrr+cPpaeijJ55L8yIrqujPJs0n+e/r6enuSW5LcMn28krx3Wvfp7HMk2orreufM+rovyetXUNMbMtk39/BMbt2wyvXlzFiAzq3d6AaAgxH0AJ0T9ACdE/QAnRP0AJ0T9ACdE/QAnRP0AJ37P+TldXJN+/ZqAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dz\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "z0\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dr\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADr1JREFUeJzt3W+IZfddx/H3p5v+0aadSnYFyWa7kd1KlzywMiSFPlm1hU2TTUShZrUPCmGXVFOEFjWiEP8gthREi8EydpetSpPGPpDddEMsJSEiTczG/iGbElljSjY+2PzRBfFP1H59MNc4vZnZOXfm3nvO/Ob9goV7z5y58+XObz/zu9/zO+ekqpAktesNfRcgSZotg16SGmfQS1LjDHpJapxBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUuCv6LgBg586dtXfv3r7LkKQt5cknn3ypqnatt98ggn7v3r2cPXu27zIkaUtJ8p0u+9m6kaTGGfSS1DiDXpIaZ9BLUuMMeklqXK9Bn+RwkqVLly71WYYkNa3XoK+q01V1bGFhoc8yJKlptm4kqXGDOGFK6tveu7782uPnPnlTj5VI02fQSx2t/GMA/kHQ1mHrRpIaZ9BLUuMMeklqnD16aYwHZtUaZ/SS1DiDXpIaZ+tG0ppsY7XBGb0kNc6gl6TG2brRtjJ+dqtez/eoPTOZ0Sd5a5KzSW6exetLkrrrNKNPcgK4GbhYVdet2H4I+ENgB/C5qvrk6Eu/Ctw/5VqluXN2qxZ0ndGfBA6t3JBkB3APcCNwADiS5ECSDwBPAxenWKckaYM6zeir6tEke8c2Xw+cr6pnAZLcB9wKXAm8leXw//ckZ6rqu1OrWJI0kc0cjL0aeH7F8wvADVV1J0CSjwAvrRXySY4BxwD27NmziTIkSZczs+WVVXWyqh64zNeXqmqxqhZ37do1qzIkadvbzIz+BeCaFc93j7ZJg+IBVW13m5nRPwHsT3JtkjcBtwGnJnmBJIeTLF26dGkTZUiSLqfr8sp7gYPAziQXgLur6niSO4GHWF5eeaKqzk3yw6vqNHB6cXHx6GRlS5omP/W0reuqmyNrbD8DnJlqReqd90btxgt+aavo9Vo3tm4kafZ6DfqqOl1VxxYWFvosQ5Ka5kXN1CR7ztL/6zXokxwGDu/bt6/PMiR14DGJravXoHfVzXA4A5ba5Y1HJKlxBr0kNc7llZLUOHv0WpcH4aStzeWVc2RgSuqDQb+NudJG2h4Memkb8Y/79uQJU9IUDLktZ7jLg7GayJADTdLqbN2oGc5cpdV5wpQkNc4Z/TZgu0Xa3gz6bcb2hqbBycPW4qqbRhno25u/f63kHaYkqXG2bnriR19J8+KqG0lqnDP6GbNXOlu+v9L6nNFLUuMMeklqnHeYkqTGubxSkhpn60aSGueqG2nKPEdCQ+OMXpIa54xeaoTnFGgtBv0ATOujvv/RJa3GoNeG2YuWtgaDXluOn1ykyRj00gz5qUdD4KobSWqcl0CQpMb12rqpqtPA6cXFxaN91qHZsXUh9c/WjSQ1zoOxM+CqEElD4oxekhrnjF5TYS9eGi6DXnPjHwOpH7ZuJKlxzug1dR6MXp2faNQXg169MwCl2bJ1I0mNc0Y/MNtldmt7R5ofZ/SS1DiDXpIaZ9BLUuOm3qNP8m7gl4CdwFer6o+n/TPUru1yjKIl48db/L0NT6cZfZITSS4meWps+6EkzyQ5n+QugKr6dlXdAXwIeN/0S5YkTaJr6+YkcGjlhiQ7gHuAG4EDwJEkB0ZfuwX4MnBmapVKkjakU9BX1aPAK2ObrwfOV9WzVfUqcB9w62j/U1V1I/Dza71mkmNJziY5++KLL26seknSujbTo78aeH7F8wvADUkOAj8NvJnLzOiraglYAlhcXKxN1CFJuoypH4ytqkeAR6b9upJezxPP1MVmlle+AFyz4vnu0bbOvDm4JM3eZmb0TwD7k1zLcsDfBvzcJC/gzcHVlTNXaeM6BX2Se4GDwM4kF4C7q+p4kjuBh4AdwImqOjezSgfOIJKWeS7E8HQK+qo6ssb2M2xiCWWSw8Dhffv2bfQlJEnr6PXqlbZudDl+SpKmw2vdSFLjDHpJalyvQe/ySkmavV6DvqpOV9WxhYWFPsuQpKZ5K0FJM+NSy2GwRy9Jjet1Ru86ekmb4SeGblxHL/XAgNI82aOXtKV4It3kDPotzkEvLfNT0toMemkL2O4h5oRmczxhSpIa5wlTktQ4WzcDtt0/rkuaDoN+C7Jfub35+9ekPDNWkhrnjH5CzqakjbEV2R8vgSANlJOKjfOPyvdy1Y0kNc4evSQ1zh59B36E1izZZlid/++mx6DfIhz0kjbKoJfUND8xGfSSemD4zpfLK6UBsUWnWXB5pSQ1zuWVktQ4e/Qr2DeU5s//d7PnjF6SGueMXtJgeDB6NpzRS1LjDHpJapxBL0mNM+glqXEGvSQ1rtegT3I4ydKlS5f6LEOSmuYlECSpca6jl7RtbNezcA36NXjihqRWeDBWkhpn0EtS4wx6SWqcPXpJ29Jax+FaPEjrjF6SGmfQS1LjDHpJapxBL0mNM+glqXEGvSQ1bibLK5P8FHAT8HbgeFX91Sx+jiRpfZ1n9ElOJLmY5Kmx7YeSPJPkfJK7AKrqL6vqKHAH8LPTLVmSNIlJZvQngT8C/vT/NiTZAdwDfAC4ADyR5FRVPT3a5TdGX5ekZmy1q2B2ntFX1aPAK2ObrwfOV9WzVfUqcB9wa5Z9Cniwqv5ueuVKkia12R791cDzK55fAG4APga8H1hIsq+qPjv+jUmOAccA9uzZs8kyNsZLEUvaDmZyMLaqPgN8Zp19loAlgMXFxZpFHZKkzS+vfAG4ZsXz3aNtkqSB2GzQPwHsT3JtkjcBtwGnun6zNweXpNnr3LpJci9wENiZ5AJwd1UdT3In8BCwAzhRVee6vmZVnQZOLy4uHp2sbEmar618TK9z0FfVkTW2nwHOTK0iSerRpEsnt8JSy14vgWDrRpJmr9c7TNm6kdSq8VZPn7P9bXcrwa3cZ5OkjfDqlZLUOHv0ktQ4e/SSNCVDbQ3bupGkxhn0ktQ4e/SS1Lheg76qTlfVsYWFhT7LkKSm2bqRpMYZ9JLUuF6XVyY5DBzet29fn2VI0lzN+0Jo9uglqXG2biSpcc1e1GwrXCNakubBGb0kNc6gl6TGuepGktYw1IuUTcpVN5LUOFs3ktQ4g16SGmfQS1LjDHpJapxBL0mNM+glqXHeYUqSGuc6eklqnK0bSWqcQS9JjTPoJalxBr0kNc6gl6TGGfSS1LhmbyW4UivXlJakjXBGL0mNM+glqXFeAkGSGuclECSpcbZuJKlxBr0kNc6gl6TGGfSS1LimTpjyxChJej1n9JLUOINekhpn0EtS4wx6SWpcUwdjJWmo+lws4oxekhpn0EtS46Ye9El+OMnxJF+a9mtLkibXKeiTnEhyMclTY9sPJXkmyfkkdwFU1bNVdfssipUkTa7rjP4kcGjlhiQ7gHuAG4EDwJEkB6ZanSRp0zoFfVU9Crwytvl64PxoBv8qcB9w65TrkyRt0maWV14NPL/i+QXghiRXAb8LvCfJr1XV7632zUmOAccA9uzZs+EivL6NJF3e1NfRV9XLwB0d9lsClgAWFxdr2nVIkpZtZtXNC8A1K57vHm2TJA3IZoL+CWB/kmuTvAm4DTg1yQt4c3BJmr2uyyvvBb4G/EiSC0lur6r/Bu4EHgK+DdxfVecm+eHeHFySZq9Tj76qjqyx/QxwZqoVSZKmqtdLINi6kaTZ6zXobd1I0ux5UTNJapytG0lqXKr6P1cpyYvAd0ZPdwIv9VjOWqxrckOtzbomN9Tatntd76yqXevtNIigXynJ2apa7LuOcdY1uaHWZl2TG2pt1tWNPXpJapxBL0mNG2LQL/VdwBqsa3JDrc26JjfU2qyrg8H16CVJ0zXEGb0kaYp6Cfokb0nyt0m+meRckt9aZZ83J/ni6H60jyfZO5C6Pp7k6STfSvLVJO8cQl0r9v2ZJJVkLkf8u9aW5EOj9+1cki8Moa4ke5I8nOTro9/nB2dd14qfvWP0cx9Y5WtzH/sd65r72O9a24p95jr+u9Q177G/qqqa+z8gwJWjx28EHgfeO7bPLwCfHT2+DfjiQOr6ceD7R48/OpS6Rl97G/Ao8BiwOKDf5X7g68APjJ7/4EDqWgI+Onp8AHhuHu/Z6Od9HPgC8MAqX5v72O9Y19zHftfaRl+f+/jv8J7Nfeyv9q+XGX0t+9fR0zeO/o0fLLgV+Pzo8ZeAn0ySvuuqqoer6t9GTx9j+YYrM9Xx/QL4HeBTwH/MuqYJazsK3FNV/zz6nosDqauAt48eLwD/NOu6AJLsBm4CPrfGLnMf+13q6mPsd61tZO7jv0Ndcx/7q+mtRz/6uPMN4CLwlap6fGyX1+5JW8vXvr8EXDWAula6HXhw1jV1qSvJjwHXVNXcb6Lb4T17F/CuJH+T5LEkhwZS128CH05ygeXLbX9sHnUBfwD8CvDdNb7ey9jvUNdKcxv7I5etrcfxv9571svYH9db0FfV/1TVj7I8K7g+yXV91bJS17qSfBhYBD7dd11J3gD8PvCJedQySW0jV7D8EfYgcAT4kyTvGEBdR4CTVbUb+CDwZ6P3cmaS3AxcrKonZ/lzJjVJXfMe++vV1tf47/ie9TL2x/W+6qaq/gV4GBj/S/faPWmTXMHyR+uXB1AXSd4P/DpwS1X957xqukxdbwOuAx5J8hzwXuDUPA9IXaY2gAvAqar6r6r6R+DvWR78fdd1O3D/aJ+vAW9h+Rols/Q+4JbR7+k+4CeS/PnYPn2M/S519TX216utr/Hf5T3rdey/po8DA8Au4B2jx98H/DVw89g+v8j3HpC6fyB1vQf4B2D/kN6vsf0fYX4HY7u8Z4eAz48e72S5LXHVAOp6EPjI6PG7We7RZ46/14OsfgBv7mO/Y11zH/tdaxvbZ27jv8N7Nvexv9q/vmb0PwQ8nORbLN9k/CtV9UCS305yy2if48BVSc6zfFT7roHU9WngSuAvknwjyUQ3RJ9hXX3pUttDwMtJnmZ5Zv3LVTXrGWqXuj4BHE3yTeBelkO/lzMIBzD2u9TVx9hf00DG/+sMYOy/vqaexrUkaU5679FLkmbLoJekxhn0ktQ4g16SGmfQS1LjDHpJapxBL0mNM+glqXH/C+4ViryBxesyAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD9CAYAAACyYrxEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAEVpJREFUeJzt3X+MZWddx/H3hzYFLaUYuhKz7bjVLZWmatRrq0K0oOiWshSx0S4YU7LZTY0lxBilJMQQjRH+kGDTUlyhWYvQZoNgdmFhMcS6RLa6W0X6YynZlEKn/rHlh2tETV369Y972w7DzsyZuT/nmfcrmezc554557t35n7vc77Pc56TqkKS1K7nTDsASdJ4meglqXEmeklqnIlekhpnopekxpnoJalxJnpJapyJXpIaN/JEn+SqJJ9N8r4kV416/5Kk1emU6JPckeRkkgcWtW9L8nCSE0luHjQX8F/A84D50YYrSVqtdFkCIcnP00/ed1bV5YO2s4AvAa+in9CPAjuAL1bVU0leDLy7qt640v4vuOCC2rJly5r/E5K0Ed13331fq6pNK213dpedVdXhJFsWNV8BnKiqRwCS3A1cW1UPDZ7/JvDcpfaZZDewG2Bubo5jx451CUWSNJDkK122G6ZGvxl4bMHjeWBzktcn+Qvgg8CtS/1wVe2pql5V9TZtWvEDSZK0Rp169KtRVR8FPtpl2yTbge1bt24ddRiSpIFhevSPAxcteHzhoK2zqjpQVbvPP//8IcKQJC1nmER/FLgkycVJzgGuB/avZgdJtifZc+rUqSHCkCQtp+v0yruAI8ClSeaT7Kyq08BNwCHgOLCvqh5czcHt0UvS+HWddbNjifaDwMGRRiRJGqmpLoFg6UaSxm+qid7SjSSN38inV0rr3ZabP/HM94++85opRiKNhqUbSWqcpRtJapzr0UtS4yzdSFLjLN1IUuMs3UhS40z0ktQ4a/SS1Dhr9JLUOK+MlfjOq2Gl1lijl6TGmeglqXEOxkpS46Zao6+qA8CBXq+3a5pxyBUbpZZZupGkxpnoJalxTq+U9B2WmmpqSW/9skcvSY2zRy8tw0FqtcDplZLUONe6kaTGWbrRd1k8GGfJQlrfHIyVpMaZ6CWpcSZ6SWqciV6SGmeil6TGmeglqXFjSfRJzk1yLMlrxrF/SVJ3nRJ9kjuSnEzywKL2bUkeTnIiyc0LnnorsG+UgUqS1qZrj34vsG1hQ5KzgNuAq4HLgB1JLkvyKuAh4OQI45QkrVGnK2Or6nCSLYuarwBOVNUjAEnuBq4Fng+cSz/5/0+Sg1X11Mgi1sgstRytpLYMswTCZuCxBY/ngSur6iaAJDcAX1sqySfZDewGmJubGyIMSdJyxrbWTVXtXeH5PcAegF6vV+OKQ1qKZzTP8rVo2zCzbh4HLlrw+MJBW2cuUyxJ4zdMoj8KXJLk4iTnANcD+1ezA5cplqTx6zq98i7gCHBpkvkkO6vqNHATcAg4DuyrqgdXc3B79JI0fl1n3exYov0gcHCtB6+qA8CBXq+3a637kCQtz1sJSlLjvJWgJDXORc0kqXGWbiSpcZZuJKlxlm4kqXGWbiSpcZZuJKlxlm4kqXEmeklq3NiWKe4iyXZg+9atW6cZhqQOFi5l/Og7r5liJFota/SS1DhLN5LUOBO9JDXORC9JjfOCKUlqnIOxktQ4SzeS1DgTvSQ1bqoXTGl98EIZaX2zRy9JjTPRS1LjLN1IHS0sYYFlLK0fzqOXpMZNtUdfVQeAA71eb9c041gNByYlrTfW6CWpcSZ6SWqcg7FDsIwjaT2wRy9JjTPRS1LjLN1oQ1k8F17aCEz0atKkx08cr9EsG3miT/JS4C3ABcBnqur2UR9DWo1p9uL9ANAs6JTok9wBvAY4WVWXL2jfBvw5cBbw/qp6Z1UdB25M8hzgTsBEP0MsXYzOUq9ll9fYDwBNUtce/V7gVvqJG4AkZwG3Aa8C5oGjSfZX1UNJXgv8NvDB0YYrqYsuHyR+6G8cnRJ9VR1OsmVR8xXAiap6BCDJ3cC1wENVtR/Yn+QTwIfPtM8ku4HdAHNzc2sKXpM3Cz3RWYhhLUysmpZhavSbgccWPJ4HrkxyFfB64LnAwaV+uKr2AHsAer1eDRGHNrD1mvSlSRr5YGxV3QPc02XbJNuB7Vu3bh11GJKkgWES/ePARQseXzho62w9rl6p2bVeSyPjPivxrOdZG/W1GCbRHwUuSXIx/QR/PfCG1ezAHr26Wq9JfNZsxNdxI/6fF+u0BEKSu4AjwKVJ5pPsrKrTwE3AIeA4sK+qHlzNwavqQFXtPv/881cbtySpo66zbnYs0X6QZQZcV2KPfn3bqKfB0nrjHaY0EkudHvsBMDmTLFH4Ib++uNZNB+u9xjcrSwAsZHJolx8Cs2eqid7SzfB8U21s670TosmwdNMQ3/SSzsQbj0hS4yzdSBobS4uzwdLNiPgHrY1q2JKh753xs3QjSY1zeqW0zqzXQXd77tNjjX4dWq9vdGmWbKQPnqmWblzrRpLGz9KNpsJb3W1s/m4ny8FYSWqcPfp1wh6QpLWyRy9JjXPWjbQOeEanYTjrRpIaZ+lGkhpnopekxjnrRpohG+lqTU2OiX6GOQAnaRQs3UhS45xeKWlmWLoaD6dXSlLjrNFLao7jW9/JGr0kNc4evWaKPTFp9Ez0mjqTuzRelm4kqXH26KUZ5ZmORsVEL2kmOad+dMaS6JO8DrgGeAHwgar69DiO0yJ7cZJGrXONPskdSU4meWBR+7YkDyc5keRmgKr626raBdwI/MZoQ5YkrcZqBmP3AtsWNiQ5C7gNuBq4DNiR5LIFm7x98LwkaUo6J/qqOgx8Y1HzFcCJqnqkqp4E7gauTd+7gE9W1b+caX9Jdic5luTYE088sdb4JUkrGHZ65WbgsQWP5wdtbwZ+CbguyY1n+sGq2lNVvarqbdq0acgwJElLGctgbFXdAtyy0nauXilJ4zdsj/5x4KIFjy8ctHXi6pWSNH7DJvqjwCVJLk5yDnA9sL/rDyfZnmTPqVOnhgxDkrSU1UyvvAs4AlyaZD7Jzqo6DdwEHAKOA/uq6sGu+xxFj37LzZ945kuS9N061+irascS7QeBgyOLSJLWkfVwBa+3ElyCZwiSWuGtBCWpcVNN9A7GStL42aOXpMZ54xFJalyz69Gvh5FwSZoEa/SS1Dhr9JLUuGZLN5I2Fq99WZqJXtKGt9oxvfX2oeKVsZLWrfWWcKfFGr0kNc7SjSSNweKzjWlO8/aCKUlqXFM9eut1kvTdvGBKkhrnYKwkNa6p0s0scs0dabQs0a6eg7GS1DgTvSQ1ztKNJI3IrJaV7NFLUuNc60bSzJvVnvJ64fRKSWqcpRtJapyJXpIaZ6KXpMaZ6CWpcSZ6SWrchrtgyrVnJG00GyLROwdX0kY28tJNkh9K8oEkHxn1viVJq9cp0Se5I8nJJA8sat+W5OEkJ5LcDFBVj1TVznEEK0lava6lm73ArcCdTzckOQu4DXgVMA8cTbK/qh4adZAtcqxA0qR06tFX1WHgG4uarwBODHrwTwJ3A9eOOD5J0pCGGYzdDDy24PE8cGWSFwF/AvxEkrdV1Z+e6YeT7AZ2A8zNzQ0RhiSNzlKTN9bzmffIZ91U1deBGztstwfYA9Dr9WrUcUiS+oZJ9I8DFy14fOGgrbONtkyx0zwlTcMw0yuPApckuTjJOcD1wP7V7MBliiVp/LpOr7wLOAJcmmQ+yc6qOg3cBBwCjgP7qurB1Rw8yfYke06dOrXauCVJHXUq3VTVjiXaDwIH13rwqjoAHOj1ervWug9J0vK8leAYWIuXtJxJX0fjrQQlqXEuUyxJjZtqoncwVpLGz9KNJDXO0o0kNc7SjSQ1ztKNJDXO0o0kNc5EL0mN88rYGeCVtFL7pvk+t0YvSY2zdCNJjTPRS1LjTPSS1DgHYxdwUFRSixyMlaTGWbqRpMaZ6CWpcSZ6SWqciV6SGmeil6TGuR69JDXO6ZWS1DhLN5LUOBO9JDXORC9JjTPRS1LjTPSS1DgTvSQ1zkQvSY0b+Xr0Sc4F3gs8CdxTVR8a9TEkSd116tEnuSPJySQPLGrfluThJCeS3Dxofj3wkaraBbx2xPFKklapa+lmL7BtYUOSs4DbgKuBy4AdSS4DLgQeG2z27dGEKUlaq06JvqoOA99Y1HwFcKKqHqmqJ4G7gWuBefrJvvP+JUnjM0yNfjPP9tyhn+CvBG4Bbk1yDXBgqR9OshvYDTA3NzdEGGvnPWIlbQQjH4ytqm8Bb+qw3R5gD0Cv16tRxyFJ6humtPI4cNGCxxcO2jpzmWJJGr9hEv1R4JIkFyc5B7ge2L+aHbhMsSSNX9fplXcBR4BLk8wn2VlVp4GbgEPAcWBfVT24moPbo5ek8etUo6+qHUu0HwQOrvXgVXUAONDr9XatdR+SpOV5K0FJapy3EpSkxnlBkyQ1ztKNJDUuVdO/VinJE8BXxnyYC4CvjfkYw5jl+GY5NjC+YRnf2k07th+sqk0rbTQTiX4Skhyrqt6041jKLMc3y7GB8Q3L+NZulmNbyBq9JDXORC9JjdtIiX7PtANYwSzHN8uxgfENy/jWbpZje8aGqdFL0ka1kXr0krQhNZfok5yV5F+TfHyZbX4tSSWZ+Gj5SvEl+fUkDyV5MMmHZym+JHNJ/n7w/BeSvHrCsT2a5P4kn09y7AzPJ8ktg3sYfyHJT85YfG8cxHV/ks8l+fFZim/Bdj+d5HSS62YptiRXDZ5/MMk/TCq2LvElOT/JgST/NohvxXtyTNLIbzwyA95CfzXNF5zpySTnDbb5p0kGtcCS8SW5BHgb8LKq+maS7590cCz/+r2d/iqltw/uD3wQ2DLB2ABeUVVLzVu+Grhk8HUlcPvg30laLr4vA78w+N1eTb++O0vxPX0v6HcBn55cSM9YMrYkLwTeC2yrqq9O6b2x3Gv3O8BDVbU9ySbg4SQfGtxmdeqa6tEnuRC4Bnj/Mpv9Mf0/5P+dSFALdIhvF3BbVX0ToKpOTio26BRf8ewHwPnAv08irlW4Friz+u4FXpjkB6Yd1NOq6nNP/26Be3n23sqz5M3A3wAT/dvr4A3AR6vqqzD590YHBZyXJMDz6d9j+/R0Q3pWU4keeA/wB8BTZ3pycCp/UVVN62axy8YHvAR4SZJ/THJvkm2TCw1YOb53AL+ZZJ5+b/7NE4rraQV8Osl9g3sOL3am+xhvnkhkfSvFt9BO4JMTiGmhZeNLshn4VfpnQpO20mv3EuD7ktwz2Oa3Ziy+W4GX0u/83A+8paqWeh9NXDOlmySvAU5W1X1JrjrD888B3g3cMOHQnj7+svENnE2/7HAV/d7e4SQ/WlX/MSPx7QD2VtWfJflZ4INJLp/gH/TLq+rxwWn73yX5YlUdntCxu+gUX5JX0E/0L5+x+N4DvLWqnup3TGcqtrOBnwJ+Efge4EiSe6vqSzMS368AnwdeCfzwYJvPVtV/Tii+ZbXUo38Z8NokjwJ3A69M8tcLnj8PuBy4Z7DNzwD7Jzggu1J80O+B7q+q/6uqLwNfop/4ZyW+ncA+gKo6AjyP/lofE1FVjw/+PQl8DLhi0SZD38d4GB3iI8mP0S+NXVtVX59UbB3j6wF3D/4GrgPem+R1MxLbPHCoqr41qJMfBiY2mN0hvjfRLy1VVZ2gPx7zI5OKb0VV1dwX/R7xx1fY5h6gN0vxAduAvxp8fwH9MsSLZii+TwI3DL5/+jQ1E4rpXOC8Bd9/jv7A3MJtrhnEGPof5P88wdesS3xzwAng56bwO10xvkXb7wWum5XYBn9vn6Hfs/9e4AHg8hmK73bgHYPvX0y/g3HBpH/PS301U7pZSpI/Ao5V1apuXD4pi+I7BPxykoeAbwO/XxPu9a0Q3+8Bf5nkd+nXLG+owV/2BLwY+NigpHA28OGq+lSSGwGq6n30xw1eTT+Z/jf9XtakdInvD4EX0e8pA5yuyS2I1SW+aVkxtqo6nuRTwBfojyG9v6oemJX46E/y2JvkfvodjbfWMrObJs0rYyWpcS3V6CVJZ2Cil6TGmeglqXEmeklqnIlekhpnopekxpnoJalxJnpJatz/AwuFUKDanDmeAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi zcut\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphiNor\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADKZJREFUeJzt3V2MXHUZx/HfTxBIlCwvi4BAWMgSBK8kG0Qghogxpbjge0ATISKVGBK8Mk1IvPBG0MQLI2oaJGJCAEXRrpTw3nBjkS2hFChIISW0qbRIskpMQPTxYk5xst3ZndmZ8/bM95NsOjtzuvPM2elvnnnO/0wdEQIA5PW+ugsAAJSLoAeA5Ah6AEiOoAeA5Ah6AEiOoAeA5Ah6AEiOoAeA5Ah6AEju0LoLkKTJycmYmpqquwwAaJWtW7e+ERHHrbRdI4J+ampK8/PzdZcBAK1i+9V+tmN0AwDJEfQAkBxBDwDJEfQAkBxBDwDJEfQAkFytQW971vaGhYWFOssAgNRqDfqImIuIdRMTE3WWAQCpNeKEKTTX1Pr73ru866ZLa6wEwGoxoweA5OjogTHFu7XxQUcPAMnR0QNjpLuLx/gg6AH0xHgnB0Y3AJAcHT36tvhtfz8dXq9RAd3hygbtpnttz7gGBD0ARjTJEfSoHSEDlIugx6oR0Dkx6smHoMdIDDNPxuixf9GNoMfIETKr18++450UBkXQAy3Giyr6QdADLUCgYxgEPRqLEUWz8PtoL4IeB6F7BHIh6NEKdJPA6vFZNwCQHB09gIHxDqtdCHo0yjgeHxjHx4xqEfSQRNgAmTGjB4DkCHoASI6gB4DkmNGj1Vj9AayMoEfrcOAYGAyjGwBIjo4eqAjvRFAXOnoASI6gB4DkGN0gDVbgAEujoweA5OjoxxgHB4HxQEcPAMkR9ACQHEEPAMkxo0d6rMbBuCPogRJxwBtNwOgGAJIj6AEgOYIeAJIj6AEgOYIeAJJj1Q1SYrUL8H909ACQHB09MGK8m0DTEPQAhsKZx8038qC3fZakGyRNSnokIn4+6vsAVotQwjjqa0Zv+zbb+2w/u+j6NbZftL3T9npJiogdEXGdpK9IumD0JQMABtHvwdhfSVrTfYXtQyTdIukSSWdLutL22cVtl0m6T9KmkVUKAFiVvoI+Ih6X9Oaiq8+VtDMiXomIdyTdJenyYvuNEXGJpK/1+pm219metz2/f//+1VUPAFjRMDP6kyS91vX9bkkft32RpC9IOlzLdPQRsUHSBkmamZmJIeoAACxj5AdjI2KzpM2j/rkAgNUZ5oSpPZJO6fr+5OI6AECDDNPRPynpDNunqRPwV0j66kiqQmk4mQcYP/0ur7xT0p8lnWl7t+1rIuJdSddLekDSDkm/iYjnyisVALAafXX0EXFlj+s3iSWUANBofKgZACRXa9DbnrW9YWFhoc4yACC1WoM+IuYiYt3ExESdZQBAaoxuACA5PqYYGBJLVtF0dPQAkBxBDwDJEfQAkBxBDwDJEfQAkBwnTAFAcpwwBQDJMboBgOQ4YQpjq/tEp103XVpjJUC56OgBIDmCHgCSI+gBIDmCHgCS42DsGODTFYHxRkcPAMkR9ACQXK2jG9uzkmanp6frLAPAiHBuQjPxEQgAkByjGwBIjqAHgORYXgmsAktW0SZ09ACQHEEPAMkxugHEskDkRkcPAMnR0QNoDN5ZlYOgB5ZB8CCDWkc3tmdtb1hYWKizDABIjY9AAIDkOBgLAMkR9ACQHEEPAMkR9ACQHEEPAMmxjh5YhE+mRDZ09ACQHEEPAMkxugH6xEgHbUVHDwDJEfQAkByjm6QYMwA4oNagtz0raXZ6enokP4+PlAWAg9Ua9BExJ2luZmbm2jrraDNe3ACshBk9ACTHjL7B6NYBjAJBD6AUyy0IoHGpFqMbAEiOoAeA5Ah6AEiOoAeA5Fp/MLaKM0BZ/QKgzejoASA5gh4AkiPoASA5gh4Akmv9wVgAaKImLeKgoweA5Ah6AEgu1X88Mi56nTvA/yoFrE6TxixlqLWjj4i5iFg3MTFRZxkAkBqjGwBIjqAHgOQIegBIjqAHgOQIegBIjjNjAYyN7Msoe6GjB4DkCHoASI6gB4DkCHoASI6gB4DkCHoASI7llQAar9cnsw6zRHKcPu2VoAeAHrKsu2d0AwDJ0dEDSCdLJz4qBH1NeCJinPH8rxajGwBIjo4eQK2atvqlafWMAh09ACRXa0dve1bS7PT0dJ1lAGgpZv39qTXoI2JO0tzMzMy1ddYBAFVZPBqq4gWK0Q0AJEfQA0ByBD0AJMfyypbIuOQLQDXo6AEgOYIeAJIj6AEgOWb0S2AeDiCTtEHflDPmmlIHgPGVNugBjBfeifdG0I8InTuAphrroG9iONOVABi1sQ761WjiiwMALIfllQCQHEEPAMkR9ACQHEEPAMkR9ACQHEEPAMkR9ACQHOvoS8BJTwCahI4eAJKjo28A3gEAKBNBDwAlq7uZY3QDAMnR0Q+h7ldpAOgHHT0AJEdHDwB9aPNHlNPRA0BytXb0tmclzU5PT5d6P21+JQaAYdXa0UfEXESsm5iYqLMMAEiN0Q0AJEfQA0ByrLoBgBFp6rk1Yxf0Tf1FAEBZxi7oAbQDTdnoMKMHgOQIegBIjqAHgOQIegBIjqAHgOQIegBIjqAHgORYRw8gtTLW47ftE3EJegAYQhtO7GJ0AwDJEfQAkBxBDwDJEfQAkBwHYwttOKACAKtBRw8AyRH0AJAcQQ8AyTGjrxDHAQDUgY4eAJIj6AEgOYIeAJIj6AEgOYIeAJIj6AEgOYIeAJIj6AEgOYIeAJJzRNRdg2zvl/TqKv/6pKQ3RljOqFDXYKhrMNQ1mKbWJQ1X26kRcdxKGzUi6Idhez4iZuquYzHqGgx1DYa6BtPUuqRqamN0AwDJEfQAkFyGoN9QdwE9UNdgqGsw1DWYptYlVVBb62f0AIDlZejoAQDLaF3Q2/6R7RdsP2P7XttH9dhuje0Xbe+0vb6Cur5s+znb/7Xd8wi67V22t9t+2vZ8g+qqen8dY/sh2y8Vfx7dY7v/FPvqadsbS6xn2cdv+3Dbdxe3P2F7qqxaBqzratv7u/bRNyuq6zbb+2w/2+N22/5JUfczts9pSF0X2V7o2l/fq6CmU2w/Zvv54t/iDUtsU+7+iohWfUn6jKRDi8s3S7p5iW0OkfSypNMlHSZpm6SzS67rLElnStosaWaZ7XZJmqxwf61YV03764eS1heX1y/1eyxue6uCfbTi45f0bUm/KC5fIenuhtR1taSfVvV86rrfT0o6R9KzPW5fK+l+SZZ0nqQnGlLXRZL+VPG+OlHSOcXlIyX9dYnfY6n7q3UdfUQ8GBHvFt9ukXTyEpudK2lnRLwSEe9IukvS5SXXtSMiXizzPlajz7oq31/Fz7+9uHy7pM+VfH/L6efxd9d7j6SLbbsBddUiIh6X9OYym1wu6dfRsUXSUbZPbEBdlYuIvRHxVHH5n5J2SDpp0Wal7q/WBf0i31DnVXCxkyS91vX9bh28Y+sSkh60vdX2urqLKdSxv46PiL3F5b9JOr7HdkfYnre9xXZZLwb9PP73tikajQVJx5ZUzyB1SdIXi7f799g+peSa+tXkf4OfsL3N9v22P1rlHRcjv49JemLRTaXur0b+5+C2H5Z0whI33RgRfyy2uVHSu5LuaFJdfbgwIvbY/pCkh2y/UHQhddc1csvV1f1NRITtXsu/Ti321+mSHrW9PSJeHnWtLTYn6c6IeNv2t9R51/GpmmtqsqfUeU69ZXutpD9IOqOKO7b9QUm/k/SdiPhHFfd5QCODPiI+vdzttq+W9FlJF0cx4Fpkj6Tuzubk4rpS6+rzZ+wp/txn+1513p4PFfQjqKvy/WX7ddsnRsTe4i3qvh4/48D+esX2ZnW6oVEHfT+P/8A2u20fKmlC0t9HXMfAdUVEdw23qnPsowlKeU4NqztgI2KT7Z/ZnoyIUj8Hx/b71Qn5OyLi90tsUur+at3oxvYaSd+VdFlE/KvHZk9KOsP2abYPU+fgWWkrNvpl+wO2jzxwWZ0Dy0uuDqhYHftro6SristXSTronYfto20fXlyelHSBpOdLqKWfx99d75ckPdqjyai0rkVz3MvUmf82wUZJXy9Wk5wnaaFrVFcb2yccOLZi+1x1MrDUF+zi/n4paUdE/LjHZuXuryqPPo/iS9JOdWZZTxdfB1ZCfFjSpq7t1qpzdPtldUYYZdf1eXXmam9Lel3SA4vrUmf1xLbi67mm1FXT/jpW0iOSXpL0sKRjiutnJN1aXD5f0vZif22XdE2J9Rz0+CV9X52GQpKOkPTb4vn3F0mnl72P+qzrB8VzaZukxyR9pKK67pS0V9K/i+fXNZKuk3Rdcbsl3VLUvV3LrESruK7ru/bXFknnV1DTheocm3umK7fWVrm/ODMWAJJr3egGADAYgh4AkiPoASA5gh4AkiPoASA5gh4AkiPoASA5gh4AkvsfpYNiFQgmfDEAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dz\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "z0\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dr\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi zcut\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphiNor\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADWFJREFUeJzt3V+MXGUZx/HfTxBIFMufIiAUFrIEwSvJBBGIIYKmFhdQUeFGiLW1GIxemSYYL7wRNPGCgCENEMBgAVGxKyX8ERpuKNISSmkLUkgNbSotmqwSExR9vJizOG53dme658w588z3kzSdnTndefbt7G+eed93zjgiBADI6311FwAAqBZBDwDJEfQAkBxBDwDJEfQAkBxBDwDJEfQAkBxBDwDJEfQAkNyhdRcgSYsXL46xsbG6ywCAobJ58+a3IuK4+Y5rRNCPjY1p06ZNdZcBAEPF9p96OY6pGwBIjqAHgOQIegBIjqAHgOQIegBIrtagtz1he83U1FSdZQBAarUGfURMRsTKRYsW1VkGAKTG1A0AJNeIN0whl7HVD793edeNl9ZYCQCJoEfFuoU+TwblYBzRC4IeSKjzCaATT7ajiaDHnLoFxqDvmyCqFmOdG0GPganzSWPY9RLEvYxvv8cQ+jkQ9KgdwdIfnjDRr1qD3vaEpInx8fE6y0CDEGJA+WoN+oiYlDTZarVW1FkH0EQ86aEsTN1AEqECZMY7YwEgOTp6oEGa9sqKhfIc6OgBIDk6egwFOsv68X8wvAh6DB0CB+gPQQ/UrGnz8siHoMdQo7sH5sdiLAAkx2fGAkByfGYsACTHHP0Iy7YIyHz94DDWw4WgB2qQ7UkWzcZiLAAkR9ADQHIEPQAkR9ADQHIEPQAkR9ADQHJsrwQGhC2VqAsdPQAkR0ePlHjnJvA/BD1QIaZr0ARM3QBAcrV29LYnJE2Mj4/XWQaABWCarPk4TTEAJMfUDQAkR9ADQHLsuhkx7AIBRg8dPQAkR0eP9NgVglFHRw8AyRH0AJAcQQ8AyTFHD5SMnU1oGjp6AEiOoAeA5Ah6AEiOoAeA5Ah6AEiOoAeA5Ah6AEiOoAeA5Ah6AEiOz4zFSOFMlhhFtQZ9RExKmmy1WivqrANYCE55gKZj6gYAkiPoASA5gh4AkiPoASA5zkc/AlgsBEYbHT0AJEfQA0ByTN0AKA1vSGsmOnoASI6gB4DkCHoASI6gB4DkWIzFyGLhEKOCjh4AkiPoASA5gh4AkmOOHjgInD8Iw4SOHgCSI+gBIDmCHgCSY44e6BHz8hhWdPQAkBxBDwDJEfQAkBxBDwDJsRgLiBOcITc6egBIjo4eQCVmbkfllVJ96OgBILnSg972WbZvs/2g7evK/v4AgP70FPS277S9z/ZLM65favsV2zttr5akiNgREaskfUXSBeWXDADoR69z9HdJukXSPdNX2D5E0q2SPiNpt6TnbK+LiO22L5N0naSfl1suesXb9QFM6ynoI+Jp22Mzrj5X0s6IeF2SbN8n6XJJ2yNinaR1th+W9IvyygWqx1ZLZLOQXTcnSXqj4+vdkj5h+yJJX5R0uKT13f6x7ZWSVkrSKaecsoAyAABzKX17ZURskLShh+PWSFojSa1WK8quAwDQtpCg3yNpScfXJxfXAWmw1oEMFrK98jlJZ9g+zfZhkq6StK6csgAAZel1e+VaSc9IOtP2btvLI+JdSddLelTSDkkPRMS26koFAByMXnfdXN3l+vWaY8F1PrYnJE2Mj48f7LcAAMyj1lMgRMRkRKxctGhRnWUAQGqc6wYAkiPoASA5gh4AkiPoASC5WoPe9oTtNVNTU3WWAQCpsesGAJJj6gYAkiPoASA5gh4AkiPoASA5gh4AkmN7JQAkx/ZKAEiOqRsASI6gB4DkCHoASI6gB4DkevooQQBYqLHVD793edeNl9ZYyehheyUAJFdrRx8Rk5ImW63WijrryKKzYwKAaczRA0ByBD0AJMdi7BBiUQtAP+joASA5gh4AkiPoASA5gh4AkmMxFsDAsaFgsHhnLAAkxwePAEByzNEDQHLM0Q85zm8DYD509ACQHEEPAMkR9ACQHHP0Q4K5eAAHi44eAJKjo28wungAZaCjB4Dkau3obU9ImhgfH6+zjNpx3g8AVeIUCACQHFM3AJAcQQ8AyRH0AJAc2ysB1KrbZgQ2KZRn6IO+6Q+Gfutj7zxwoKb/njcdUzcAkNzQd/QA8uAVbTUIegBDhWmc/jF1AwDJEfQAkBxBDwDJEfQAkBxnrwSAHgzzInCtQR8Rk5ImW63WijrrGJRhfqAAGF5srwSALrLs6yfoAQwtXiX3hsVYAEiOjh7AyBjVVwB09ACQHB19TbIs8gBoPjp6AEiOjr4kozr3BzRdt1fPo/Q7S9ADSGGUgrtfBP0s5po/5wEENB9rYP+POXoASI6OvkAHACArOnoASI6OHsDIy76QS9D3iSkeAMOGqRsASI6gB4DkCHoASC7VHH32BRUAOBgj9+HgPBkAGDV8OHgF2JkDDK+Mv7/M0QNAcgQ9ACRH0ANAcql23QBAE9V96nM6egBILm1HzzZKAGijoweA5NJ29AAwCMMwe0BHDwDJjURH323FO+M74ABgJjp6AEiOoAeA5Ah6AEhuJOboAWDQmrQGSEcPAMkR9ACQHEEPAMkR9ACQHIuxANCnJi209oKOHgCSI+gBIDmCHgCSI+gBIDmCHgCSI+gBIDmCHgCSI+gBIDmCHgCSK/2dsbavkHSppA9JuiMiHiv7PgCgiZr6jtmeOnrbd9reZ/ulGdcvtf2K7Z22V0tSRDwUESskrZL01fJLBgD0o9epm7skLe28wvYhkm6V9DlJZ0u62vbZHYd8v7gdAFCjnoI+Ip6W9NcZV58raWdEvB4R/5R0n6TL3XaTpEci4vlyywUA9Gshi7EnSXqj4+vdxXXflnSJpCttr+r2j22vtL3J9qb9+/cvoAwAwFxKX4yNiJsl3dzDcWskrZGkVqsVZdcBAGhbSEe/R9KSjq9PLq4DADTIQoL+OUln2D7N9mGSrpK0rpyyAABl6XV75VpJz0g60/Zu28sj4l1J10t6VNIOSQ9ExLbqSgUAHIye5ugj4uou16+XtL7UigAApXJEfeugtickTaj9xqpXD/LbLJb0VmlFlYe6+kNd/WlqXVJza8tY16kRcdx8B9Ua9GWwvSkiWnXXMRN19Ye6+tPUuqTm1jbKdXFSMwBIjqAHgOQyBP2augvogrr6Q139aWpdUnNrG9m6hn6OHgAwtwwdPQBgDkMX9LZ/Yvtl2y/a/o3to7ocd8C58iuu68u2t9n+j+2uK+i2d9neavsF25saVNegx+sY24/bfrX4++gux/27GKsXbFf2zuv5fn7bh9u+v7j9WdtjVdXSZ13X2t7fMUbfGFBds35GRcfttn1zUfeLts9pSF0X2Z7qGK8fDKCmJbafsr29+F38zizHVDteETFUfyR9VtKhxeWbJN00yzGHSHpN0umSDpO0RdLZFdd1lqQzJW2Q1JrjuF2SFg9wvOatq6bx+rGk1cXl1bP9Pxa3vT2AMZr355f0LUm3FZevknR/Q+q6VtItg3o8ddzvpySdI+mlLrcvk/SIJEs6T9KzDanrIkm/G/BYnSjpnOLykZL+OMv/Y6XjNXQdfUQ8Fu3TL0jSRrVPpjbTrOfKr7iuHRHxSpX3cTB6rGvg41V8/7uLy3dLuqLi+5tLLz9/Z70PSrrYthtQVy1i9s+o6HS5pHuibaOko2yf2IC6Bi4i9kbx2RwR8Xe1Txlz0ozDKh2voQv6Gb6u9rPgTN3Old8EIekx25ttr6y7mEId43V8ROwtLv9Z0vFdjjui+NyCjcXnEVehl5//vWOKRmNK0rEV1dNPXZL0peLl/oO2l8xyex2a/Dv4SdtbbD9i+2ODvONiyu/jkp6dcVOl41X6+ejLYPsJSSfMctMNEfHb4pgbJL0r6d4m1dWDCyNij+0PS3rc9stFF1J3XaWbq67OLyIibHfb/nVqMV6nS3rS9taIeK3sWofYpKS1EfGO7W+q/arj0zXX1GTPq/2Yetv2MkkPSTpjEHds+4OSfiXpuxHxt0Hc57RGBn1EXDLX7bavlfR5SRdHMcE1QyXnyp+vrh6/x57i7322f6P2y/MFBX0JdQ18vGy/afvEiNhbvETd1+V7TI/X67Y3qN0NlR30vfz808fstn2opEWS/lJyHX3XFRGdNdyu9tpHEzTy8yo6AzYi1tv+me3FEVHpOXBsv1/tkL83In49yyGVjtfQTd3YXirpe5Iui4h/dDmskefKt/0B20dOX1Z7YXnW3QEDVsd4rZN0TXH5GkkHvPKwfbTtw4vLiyVdIGl7BbX08vN31nulpCe7NBkDrWvGPO5las//NsE6SV8rdpOcJ2mqY6quNrZPmF5bsX2u2hlY6RN2cX93SNoRET/tcli14zXI1ecy/kjaqfZc1gvFn+mdEB+RtL7juGVqr26/pvYURtV1fUHtebV3JL0p6dGZdam9e2JL8WdbU+qqabyOlfR7tc9a+oSkY4rrW5JuLy6fL2lrMV5bJS2vsJ4Dfn5JP1S7oZCkIyT9snj8/UHS6VWPUY91/ah4LG2R9JSkjw6orrWS9kr6V/H4Wi5plaRVxe2WdGtR91bNsRNtwHVd3zFeGyWdP4CaLlR7be7FjtxaNsjx4p2xAJDc0E3dAAD6Q9ADQHIEPQAkR9ADQHIEPQAkR9ADQHIEPQAkR9ADQHL/BeVJzs7B7soTAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dz\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAD6FJREFUeJzt3X+s3Xddx/Hny8KmGVqENQS7XlvSZtI/jJCTDaMxRpl2zFIkqK0mgjZrRjJ//KUlM6gxRNDEPxZmliYsAzM35/BHKyUDDHP/DOiGMLuVSpmQdplUJNSfAQdv/7jfwrH29n7vPef0e86nz0dys3M+53u/5/3ZPX3fz31/Pt/vJ1WFJKld3zZ0AJKk2TLRS1LjTPSS1DgTvSQ1zkQvSY0z0UtS40z0ktQ4E70kNc5EL0mNe8HQAQBce+21tXXr1qHDkKSF8sQTT3ypqjatdtxMEn2Sa4C/A36nqv5mteO3bt3K448/PotQJKlZSb7Q57hepZsk9yQ5m+T4Be27kpxMcirJwbGXfhN4sH+4kqRZ6VujvxfYNd6QZANwF3AzsBPYl2RnkpuAp4GzU4xTkrROvUo3VfVokq0XNN8AnKqqZwCSPADsAV4EXMNy8v/vJEer6htTi1iStCaT1Og3A6fHnp8Bbqyq2wGSvAX40kpJPskB4ADA0tLSBGFIki5lZssrq+reS03EVtWhqhpV1WjTplUnjSVJ6zRJon8W2DL2/Lqurbcku5McOnfu3ARhSJIuZZJEfwzYkWRbkquAvcDhtZygqo5U1YGNGzdOEIYk6VL6Lq+8H3gMuD7JmST7q+p54HbgYeAE8GBVPTW7UCVJ69F31c2+FdqPAkfX++ZJdgO7t2/fvt5TaA5tPfiBbz7+/DtvGTASSQCZh83BR6NReWXsYhtP7isx6Q9jpZ+NP4/Fl+SJqhqtdtxc3OtGi6lPcl/peJOMdPkMmugt3Uiz0eeXsCP9K8egib6qjgBHRqPRrUPGof7WOorvcx4TizRblm4k/R/+Em6PpRupEdP6a0vtsXSjVc06gTiClGbL0o2kFflLuA0memmBWa5RH9boNVccQUrTZ41e/4+jRKktM7sfvSRpPpjoJalxTsZKC8bSmtbKyVhJvThRvricjBUwn6NEE4s0HdboJalxJnpJapyJXpIaZ6KXpMa56uYKNo8TsJKmz1U3ktbMFVGLxQumpAXgX1+ahDV6SWqciV6SGmfpRtJELiwrWbOfP47oJalxJnpJapylmyvMoq7ecDmftH6DjuiT7E5y6Ny5c0OGIUlNGzTRV9WRqjqwcePGIcOQpKZZo5ekxpnoJalxTsZKmionzuePI3pJapyJXpIaZ+nmCrCoa+clTYeJXppT/oLWtFi6kaTGmeglqXEmeklq3NQTfZJXJrk7yUNJ3jrt80uS1qZXok9yT5KzSY5f0L4ryckkp5IcBKiqE1V1G/CzwA9NP2RJ0lr0XXVzL/Bu4H3nG5JsAO4CbgLOAMeSHK6qp5O8Hngr8CfTDVd9uWJD0nm9En1VPZpk6wXNNwCnquoZgCQPAHuAp6vqMHA4yQeAP51euJIWibdDmA+TrKPfDJwee34GuDHJjwJvBK4Gjq70zUkOAAcAlpaWJghDknQpU79gqqoeAR7pcdwh4BDAaDSqacchSVo2SaJ/Ftgy9vy6rq23JLuB3du3b58gDF1pLAdIazPJ8spjwI4k25JcBewFDq/lBO4wJUmz13d55f3AY8D1Sc4k2V9VzwO3Aw8DJ4AHq+qp2YUqSVqPvqtu9q3QfpRLTLiuxtKNJM3eoHevrKojwJHRaHTrkHG0wrXzmmfOrQxn0HvdJNmd5NC5c+eGDEOSmjZooncyVpJmz7tXSlLjTPSS1Dhr9JLUOGv0ktQ4SzeS1DgTvSQ1btALprwyVroyefHU5WWNXpIaZ+lGkho3aOlGk/P+NpJW44hekhrnBVOS1DgnYyWpcZZuJKlxJnpJapyJXpIaZ6KXpMa56kaSGueqG0lqnFfGSnPkSrzS2RuczZ41eklqnIlekhpnopekxlmjX0BXYh1X0vo5opekxpnoJalxXjAlSY3zgilJapylG0lqnIlekhpnopekxpnoJalxXjAlaS55s7PpMdEvCK+GlbRelm4kqXEmeklqnIlekhpnjV7S3HAuajZmkuiTvAG4Bfgu4D1V9aFZvI8kaXW9SzdJ7klyNsnxC9p3JTmZ5FSSgwBV9VdVdStwG/Bz0w1ZkrQWa6nR3wvsGm9IsgG4C7gZ2AnsS7Jz7JDf6l6XJA2kd+mmqh5NsvWC5huAU1X1DECSB4A9SU4A7wQ+WFWfvNj5khwADgAsLS2tPfJGeZHI2vj/68rgz3kyk6662QycHnt+pmv7FeC1wJuS3Haxb6yqQ1U1qqrRpk2bJgxDkrSSmUzGVtWdwJ2zOLckaW0mHdE/C2wZe35d19aLO0xJ0uxNOqI/BuxIso3lBL8X+Pm+31xVR4Ajo9Ho1gnjaJJriiVNw1qWV94PPAZcn+RMkv1V9TxwO/AwcAJ4sKqeWsM5HdFL0oytZdXNvhXajwJH1/Pmjuglafa8140kNW7QRG/pRpJmb9BEX1VHqurAxo0bhwxDkppm6UaSGmeil6TGWaOXpMZZo5ekxlm6kaTGmeglqXHW6CWpcdboJalxlm4kqXEmeklqnIlekhrnZKwkNc7JWElqnKUbSWrcpHvGStJlNb6X8uffecuAkSwOE/0ccBNwSbNk6UaSGueqG0lq3KClm6o6AhwZjUa3DhnHECzXSLpcLN1IUuNM9JLUOBO9JDXORC9JjTPRS1LjTPSS1DgTvSQ1zgumJKlx3qZYkhrnTc0kLSzvZNmPNXpJapyJXpIaZ6KXpMZZo5cG5p1MNWuO6CWpcY7opQE4ih/GlbpKxxG9JDXORC9JjZt6ok/yiiTvSfLQtM8tSVq7Xok+yT1JziY5fkH7riQnk5xKchCgqp6pqv2zCFaStHZ9J2PvBd4NvO98Q5INwF3ATcAZ4FiSw1X19LSDlKTVzGKitZXJ214j+qp6FPjyBc03AKe6EfzXgAeAPVOOT5I0oUmWV24GTo89PwPcmOSlwDuAVyV5W1X9/sW+OckB4ADA0tLSBGFI0uwt8uh+6uvoq+pfgdt6HHcIOAQwGo1q2nFIkpZNsurmWWDL2PPrurbe3HhEkmZvkkR/DNiRZFuSq4C9wOG1nMCNRyRp9vour7wfeAy4PsmZJPur6nngduBh4ATwYFU9NbtQJUnr0atGX1X7Vmg/Chxd75sn2Q3s3r59+3pPIX3TIk+WSbPknrGS1DjvdSNJjRs00bvqRpJmz9KNJDXO0o0kNW7QHabmedXNrG+QJEmXi6UbSWqcpRtJapyJXpIaZ41eUtNWmhubZB5uPd875JXb1uglqXGWbiSpcSZ6SWqciV6SGudk7BqtNKHSp12Xj7cslr7FyVhJapylG0lqnIlekhpnopekxpnoJalxJnpJapxbCUpS41xeKUmNs3QjSY0z0UtS40z0ktQ4E70kNc5EL0mNM9FLUuNM9JLUOO9H34P3lJfadrn/jV/u/RK8YEqSGmfpRpIaZ6KXpMaZ6CWpcSZ6SWqciV6SGmeil6TGmeglqXEmeklqnIlekho39VsgJLkG+GPga8AjVXXftN9DktRfrxF9knuSnE1y/IL2XUlOJjmV5GDX/Ebgoaq6FXj9lOOVJK1R39LNvcCu8YYkG4C7gJuBncC+JDuB64DT3WFfn06YkqT16pXoq+pR4MsXNN8AnKqqZ6rqa8ADwB7gDMvJvvf5JUmzM0mNfjPfGrnDcoK/EbgTeHeSW4AjK31zkgPAAYClpaV1B9Hndp9D3oJUw7vct4TV8Obh3+A8xHDe1Cdjq+o/gV/qcdwh4BDAaDSqacchSVo2SWnlWWDL2PPrurbekuxOcujcuXMThCFJupRJEv0xYEeSbUmuAvYCh9dyAjcekaTZ67u88n7gMeD6JGeS7K+q54HbgYeBE8CDVfXU7EKVJK1Hrxp9Ve1bof0ocHS9b74oe8ZK0iJzz1hJapzr3CWpcYMmelfdSNLsWbqRpMalavhrlZL8C/CFCU5xLfClKYUzNPsyv1rqT0t9gbb6s5a+fG9VbVrtoLlI9JNK8nhVjYaOYxrsy/xqqT8t9QXa6s8s+uJkrCQ1zkQvSY1rJdEfGjqAKbIv86ul/rTUF2irP1PvSxM1eknSyloZ0UuSVrCwiT7J7yV5Msmnknwoyfd07UlyZ7eP7ZNJXj10rH0k+cMkn+li/sskLx577W1df04m+ckh4+wjyc8keSrJN5KMLnhtofoCK+6NvDAutudzkpck+XCSz3b//e4hY+wryZYkH03ydPcZ+7WufeH6k+Tbk3wiyae7vvxu174tyce7z9ufdXcHnkxVLeQX8F1jj38VuLt7/Drgg0CA1wAfHzrWnv35CeAF3eN3Ae/qHu8EPg1cDWwDPgdsGDreVfrySuB64BFgNNa+iH3Z0MX5CuCqLv6dQ8e1xj78CPBq4PhY2x8AB7vHB89/3ub9C3g58Oru8XcC/9h9rhauP12OelH3+IXAx7uc9SCwt2u/G3jrpO+1sCP6qvq3safXAOcnG/YA76tlHwNenOTllz3ANaqqD9XyrZ8BPsa39t3dAzxQVV+tqn8CTrG8X+/cqqoTVXXyIi8tXF9YeW/khVEX3/N5D/De7vF7gTdc1qDWqaqeq6pPdo//neVbpG9mAfvT5aj/6J6+sPsq4MeAh7r2qfRlYRM9QJJ3JDkN/ALw9q75YnvZbr7csU3ol1n+qwTa6M95i9iXRYy5j5dV1XPd438GXjZkMOuRZCvwKpZHwgvZnyQbknwKOAt8mOW/Hr8yNuibyudtrhN9ko8kOX6Rrz0AVXVHVW0B7mN5E5S5tlp/umPuAJ5nuU9zq09ftBhquUawUMvvkrwIeD/w6xf8db9Q/amqr1fVD7D8F/wNwPfN4n2mvjn4NFXVa3seeh/LG6D8NlPYy3ZWVutPkrcAPwX8ePdhhTntzxp+NuPmsi+rWMSY+/hikpdX1XNdafPs0AH1leSFLCf5+6rqL7rmhe0PQFV9JclHgR9kudz8gm5UP5XP21yP6C8lyY6xp3uAz3SPDwO/2K2+eQ1wbuxPurmVZBfwG8Drq+q/xl46DOxNcnWSbcAO4BNDxDgFi9iXifdGnlOHgTd3j98M/PWAsfSWJMB7gBNV9UdjLy1cf5JsOr+6Lsl3ADexPOfwUeBN3WHT6cvQM88TzFi/HzgOPAkcATaPzWTfxXKt6x8YW/Uxz18sT0yeBj7Vfd099todXX9OAjcPHWuPvvw0y7XFrwJfBB5e1L50Mb+O5dUdnwPuGDqedcR/P/Ac8D/dz2U/8FLgb4HPAh8BXjJ0nD378sMsl2WeHPu38rpF7A/w/cDfd305Dry9a38FywOgU8CfA1dP+l5eGStJjVvY0o0kqR8TvSQ1zkQvSY0z0UtS40z0ktQ4E70kNc5EL0mNM9FLUuP+F8bpmqbPXqHnAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "z0\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dr\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAD75JREFUeJzt3X2IZXd9x/H3x7XR4sMIyf5Rshk3ZdfgVlotQyL4T7AKG5NNihWbbf3DsuyQ0ohFaRtR6BOlaYU+iBGZalj7lDTNH2U3rqRSElJKtEnqA9mElG2qZNI/tj50oU+m0W//mJtkMtmZOXfm3nvu/d33Cxbmnjlz7mfv3POd3/2e3zknVYUkqV0v6zuAJGm8LPSS1DgLvSQ1zkIvSY2z0EtS4yz0ktQ4C70kNc5CL0mNs9BLUuNe3ncAgEsuuaT279/fdwxJmimPPPLIt6pq73brTUWh379/Pw8//HDfMSRppiT5Zpf1em3dJDmSZOX8+fN9xpCkpvVa6KvqVFUtLyws9BlDkprmwVhJapyFXpIaZ6GXpMZZ6CWpcRZ6SWqchV6SGjcVJ0xJs2D/LZ9/0eNv3HptT0mk4Tiil6TGOaKXRmz9yN9Rv6bBWEb0SV6V5OEk141j+5Kk7jqN6JPcDlwHnKuqN61bfhj4Y2AP8JmqunXwrV8D7hpxVmmqOHLXrOg6oj8BHF6/IMke4DbgGuAQcDTJoSTvBB4Dzo0wpyRphzqN6KvqgST7Nyy+EjhbVU8CJLkTuAF4NfAq1or//yQ5XVU/GFliSdJQdnMw9lLgqXWPV4GrqupmgCTvB761WZFPsgwsAywuLu4ihiRpK2ObXllVJ6rqni2+v1JVS1W1tHfvtjdIkSTt0G4K/dPAZese7xss68wbj0jS+O2m0D8EHExyeZKLgBuBk8NswBuPSNL4dZ1eeQdwNXBJklXg16vqs0luBu5lbXrl7VV1ZpgnT3IEOHLgwIHhUksTsvGyBxqOU1CnQ9dZN0c3WX4aOL3TJ6+qU8CppaWl4zvdhjRqFne1xmvdSFLjer3Wja0baX7YxulPr4Xe1o1aYbtH08zWjSQ1ztaNhCNytc3WjaSR8o/m9PHGI3oJb5kntcVCr7nlyFPzwh69NEZOKdQ0sEcvwNGt1DJbN2PgKE7SNLHQa1v+4dKo+Z6aLAu95ootKs0jD8bOMYueNB96vQSCNx6RpPGzdTNm9iIl9c1CL01Iq3/0bQFOP69eKUmNs9BLUuOcdaOhtNp+kFrmJRAaZUHWqPmeml0ejG1Il4NiHjjTtPEPyPhZ6OfAvBf3ef//j4Ov6WzxYKwkNc5CL0mNs3UzIn6UnS7+PkbD17ENjuglqXG9FvokR5KsnD9/vs8YktQ0r14pSY2zR68dc/5zm+zLt8dCP0EWRkl98GCsJDXOEb3UAz/daZIs9GqGvWXpwmzdSFLjHNH3xI/ukibFEb0kNc5CL0mNG3mhT/LGJJ9OcneSXxz19iVJw+lU6JPcnuRckkc3LD+c5IkkZ5PcAlBVj1fVTcB7gbeNPrIkaRhdR/QngMPrFyTZA9wGXAMcAo4mOTT43vXA54HTI0sqSdqRToW+qh4AvrNh8ZXA2ap6sqqeAe4Ebhisf7KqrgF+fpRhJUnD2830ykuBp9Y9XgWuSnI18G7gFWwxok+yDCwDLC4u7iJGf6bhBJ1pyKDZ5/uobSOfR19V9wP3d1hvBVgBWFpaqlHnkGaF51Ro3HYz6+Zp4LJ1j/cNlnXmjUckafx2U+gfAg4muTzJRcCNwMlhNuCNRyRp/LpOr7wDeBC4IslqkmNV9SxwM3Av8DhwV1WdGebJHdFL0vh16tFX1dFNlp9mF1Moq+oUcGppaen4TrchSdqaFzWTpogHZjUOvV7rxtaNJI1fr4Xeg7GSNH62biRNDVtX42HrRpIaZ+tGkhrnjUckqXEWeklqnD16SWpcr7NuPDO2Hc6WkKaXrRtJapzz6KUp5ackjYojeklqnAdjJalxHowdkvfW3DlbEVI/bN1IUuM8GKuR81OPZk3rnzYt9OpF6zuWNE0s9OqdRV8aL2fdSFLjnHUjSeu0+AnTWTeS1Dh79FOgxRHETvlaSKNnoZc0l+ZpGrCtG0lqnCN6SdpEK61ER/SS1Djn0UtS43ot9FV1qqqWFxYW+owhSU2zRy9JHcxyv94evSQ1zkIvSY2zddPBPJ1YIak9juglqXEWeklqnIVekhpnoZekxlnoJalxY5l1k+SngWuB1wKfraq/HcfzSJK213lEn+T2JOeSPLph+eEkTyQ5m+QWgKr6m6o6DtwE/OxoI0uShjHMiP4E8EngT59bkGQPcBvwTmAVeCjJyap6bLDKxwbfl6Tezes5MZ0LfVU9kGT/hsVXAmer6kmAJHcCNyR5HLgV+EJV/dOFtpdkGVgGWFxcHD65mjfL1xaRpsluD8ZeCjy17vHqYNkHgHcA70ly04V+sKpWqmqpqpb27t27yxiSpM2M5WBsVX0C+MQ4ti1JGs5uR/RPA5ete7xvsKwTbzwiSeO32xH9Q8DBJJezVuBvBH6u6w9X1Sng1NLS0vFd5pA0pHk9MDmPhpleeQfwIHBFktUkx6rqWeBm4F7gceCuqjozxDYd0UvSmA0z6+boJstPA6d38uSO6CXNolmbEeYlECSpcb0Wels3kjR+vRb6qjpVVcsLCwt9xpCkptm6kaTG2bqRpMbZupGkxtm6kaTGWeglqXFjuahZV0mOAEcOHDjQZwzNgFk7QUWaJr0Wes+MlbrxD91smNbfU6+FXpJm3SxcHM5Cv4lZ+OVJUhfOo5ekxjmPXpIa5/RKSWqcPXpJGoONx/n6nIXjiF6SGueIXlLTnEHnrBtJap5nxk6ZaT2zTtLsskcvSY2zR6+ZZv+1XX66HR1H9JLUOEf06zg6lNQiR/SS1DgLvSQ1zkIvSY3zhClJapwnTE0xp5dJGgVn3UjShE16EGePXpIaZ6GXpMZZ6CWpcfbopRnmAXt1YaGX5oiX+ehPn6+9rRtJapwjes0cR6XScEZe6JP8KPBRYKGq3jPq7Uu6MPv12kyn1k2S25OcS/LohuWHkzyR5GySWwCq6smqOjaOsJKk4XXt0Z8ADq9fkGQPcBtwDXAIOJrk0EjTSZJ2rVOhr6oHgO9sWHwlcHYwgn8GuBO4YcT5JEm7tJse/aXAU+serwJXJbkY+B3gLUk+UlW/e6EfTrIMLAMsLi7uIsb88WCkpGGM/GBsVX0buKnDeivACsDS0lKNOockac1uCv3TwGXrHu8bLOssyRHgyIEDB3YRQ5JezE+9L7abE6YeAg4muTzJRcCNwMlhNlBVp6pqeWFhYRcxJElb6Tq98g7gQeCKJKtJjlXVs8DNwL3A48BdVXVmfFElSTvRqXVTVUc3WX4aOL3TJ7d1I0nj1+u1bmzdSNL4eVEzSWpcr4U+yZEkK+fPn+8zhiQ1zdaNJDXO1o0kNa7X69H3PevGkyo0i+bxfdvlEszz+Lp0ZetGkhpn60aSGmehl6TGzV2P3j6epHljj16SGmfrRpIaZ6GXpMbNXY9eUjs85taNPXpJapytG0lqnIVekhpnoZekxlnoJalxzrqRGtTlao+aH866kaTG2bqRpMZZ6CWpcRZ6SWqchV6SGmehl6TGWeglqXEWeklqnCdMzQgvxyqtcV8YnidMSVLjbN1IUuMs9JLUOAu9JDXOQi9JjbPQS1LjLPSS1DgLvSQ1zkIvSY0b+ZmxSV4FfAp4Bri/qv5i1M8hSequ04g+ye1JziV5dMPyw0meSHI2yS2Dxe8G7q6q48D1I84rSRpS19bNCeDw+gVJ9gC3AdcAh4CjSQ4B+4CnBqt9fzQxJUk71anQV9UDwHc2LL4SOFtVT1bVM8CdwA3AKmvFvvP2JUnjs5se/aW8MHKHtQJ/FfAJ4JNJrgVObfbDSZaBZYDFxcUdh1h/Jbtv3HrtjrcjtcqrPWrkB2Or6r+AX+iw3gqwArC0tFSjziFJWrOb1srTwGXrHu8bLOssyZEkK+fPn99FDEnSVnZT6B8CDia5PMlFwI3AyWE24PXoJWn8uk6vvAN4ELgiyWqSY1X1LHAzcC/wOHBXVZ0ZX1RJ0k506tFX1dFNlp8GTu/0yb2VoCSNn7cSlKTG9VroPRgrSePniF6SGueZq5LUuFT1f65Skn8HvtlzjEuAb/WcYRizlHeWssJs5Z2lrDBbeWch6+urau92K01FoZ8GSR6uqqW+c3Q1S3lnKSvMVt5ZygqzlXeWsm7H1o0kNc5CL0mNs9C/YKXvAEOapbyzlBVmK+8sZYXZyjtLWbdkj16SGueIXpIaN1eFPskrk/xjkq8lOZPkNy+wzoeSPJbk60n+LsnrpzXrunV/Jkkl6W2GQNe8Sd47eH3PJPnLSedcl6PLe2ExyX1JvjJ4P7yrj6zr8uwZZLnnAt97RZK/Gty/+ctJ9k8+4YvybJV1KvaxDZk2zbtund73s52aq0IPfA94e1X9BPBm4HCSt25Y5yvAUlX9OHA38PsTzvicLllJ8hrgg8CXJ5xvo23zJjkIfAR4W1X9GPDLk4/5vC6v78dYuyrrW1i7DPenJpxxow+ydqXYCzkGfLeqDgB/CPzexFJd2FZZp2UfW2+rvNO0n+3IXBX6WvOfg4c/NPhXG9a5r6r+e/DwS7xw/9uJ6pJ14LdZ26n/d1LZLqRj3uPAbVX13cHPnJtgxBfpmLeA1w6+XgD+bULxXiLJPuBa4DObrHID8LnB13cDP5Ukk8i20XZZp2Ufe06H1xamZD/bqbkq9PD8R7SvAueAL1bVVn+hjwFfmEyyl9oua5KfBC6rqqm4KWiH1/YNwBuS/EOSLyU5PPmUL+iQ9zeA9yVZZe1y3B+YcMT1/gj4VeAHm3z/+Xs4D+4VcR64eDLRXmK7rOv1uo8NbJl32vaznZi7Ql9V36+qN7M2irgyyZsutF6S9wFLwMcnmW+9rbImeRnwB8CH+8q3UYfX9uXAQeBq4CjwJ0leN9mUL+iQ9yhwoqr2Ae8C/mzwuk9UkuuAc1X1yKSfe1jDZJ2GfWy7vNO4n+3E3BX651TVfwD3AS8ZVSZ5B/BR4Pqq+t6ks220SdbXAG8C7k/yDeCtwMlpOFC0xWu7Cpysqv+rqn8F/pm1wt+rLfIeA+4arPMg8ErWrn8yaW8Drh/8nu8E3p7kzzes8/w9nJO8nLVW07cnGXKgS9Zp2se2yzu1+9lQqmpu/gF7gdcNvv5h4O+B6zas8xbgX4CD0551w/r3s3aAa2rzslZIPzf4+hLWWg0XT3HeLwDvH3z9RtZ69On5fXE1cM8Flv8S8OnB1zeydhC5t5zbZJ2Kfaxr3g3r9Lqf7fTfvI3ofwS4L8nXWbu5+Rer6p4kv5Xk+sE6HwdeDfx1kq8mGeqG5xPOOk265L0X+HaSx1gbQf9KVfUx6uya98PA8SRfA+5grehPzRmGG7J+Frg4yVngQ8At/SV7qSndxzY1xfvZjnhmrCQ1bt5G9JI0dyz0ktQ4C70kNc5CL0mNs9BLUuMs9JLUOAu9JDXOQi9Jjft/jpFjSZy2xJIAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi zcut\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphiNor\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dz\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "z0\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADp5JREFUeJzt3W2MXOdZxvH/hUuC1KpbqEup/IKN1ooIL1KrkVMpXyJowGniukJVsamgBSurVDUqUiXqNEh8ARGERCFKoFoRKw2qYqxSqA2u0lBa5UtSnKSUxjYBK7TEVooJBYNURGR682EndFm89uzOzJ6ZZ/4/KcqeM7Oz98lkr33mfp5zTqoKSVK7vqPrAiRJ42XQS1LjDHpJapxBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUOINekhr3qq4LANi8eXPt2LGj6zIkaao8/fTTL1XVG671vIkI+h07dvDUU091XYYkTZUkXxvkebZuJKlxnQZ9kr1JFi9dutRlGZLUtE6DvqpOVNXC3Nxcl2VIUtNs3UhS42zdSFLjbN1IUuNs3UhS4wx6SWpcpydMJdkL7J2fn++yDGlVOw7/+f9+/dV7b++wEmn9Og36qjoBnOj1end2WYe03PJwl1owEZdAkKbByj8AjvA1LQx6CUfxapuTsZLUOE+YkqTGORmrmWW7RrPCHr20Ti691LSwRy9JjTPoJalxBr0kNc5VN5LUOFfdSCPgxKwmmatuNFNcUqlZZI9ekhpn0EtS42zdSCNmv16TxhG9JDXOoJekxtm6UfNcaaNZ5wlTktS4ToO+qk5U1cLc3FyXZUhS0+zRS1LjDHpJapxBL0mNM+glqXEur5TGyLNkNQkc0UtS4xzRq0meJCV9myN6SWqcQS9JjRtL0Cd5dZKnktwxjteXJA1uoKBPciTJxSTPrti/J8lzSc4lObzsoQ8Dx0ZZqCRpfQYd0T8E7Fm+I8km4AHgNuBG4ECSG5PcCpwBLo6wTknSOg206qaqHk+yY8Xu3cC5qnoeIMlRYB/wGuDVLIX/fyY5WVXfGlnFkqQ1GWZ55RbghWXb54GbquoQQJL3AS+tFvJJFoAFgO3btw9RhiTpasa2jr6qHrrG44vAIkCv16tx1aHZ4dp56cqGCfoLwLZl21v7+waWZC+wd35+fogypOng5RDUlWGWV54CdiXZmeQ6YD9wfC0v4I1HJGn8Bl1e+QjwBHBDkvNJDlbVZeAQ8ChwFjhWVafX8sO9laAkjd+gq24OrLL/JHByvT+8qk4AJ3q93p3rfQ1J0tV5CQRJalynQW/rRpLGr9OgdzJWksbP1o0kNc6gl6TGdXqHKU+Y0qzy5CltJHv0ktQ4WzeS1DiXV0pS42zdSFLjOp2MlYblpYmla7NHL0mNM+glqXFOxkpS45yMlaTGORmrqeMErLQ29uglqXEGvSQ1ztaN1DEvcKZxc9WNJDXOVTeS1Dh79JLUOINekhpn0EtS4wx6SWqcQS9JjTPoJalxBr0kNc4TpiSpcZ4wJUmNs3UjSY0z6CWpcQa9JDXOoJekxhn0ktQ4g16SGucdpjQVvCG4tH6O6CWpcQa9JDVu5EGf5AeTfCzJJ5O8f9SvL0lam4GCPsmRJBeTPLti/54kzyU5l+QwQFWdraq7gHcDN4++ZEnSWgw6GfsQcD/w8Cs7kmwCHgBuBc4Dp5Icr6ozSd4BvB/4w9GWK7Vt+aTzV++9vcNK1JKBRvRV9TjwjRW7dwPnqur5qnoZOArs6z//eFXdBrxnlMVKktZumOWVW4AXlm2fB25KcgvwU8D1wMnVvjnJArAAsH379iHKkCRdzcjX0VfVF4AvDPC8RWARoNfr1ajrkCQtGWbVzQVg27Ltrf19A/PGI5I0fsME/SlgV5KdSa4D9gPH1/IC3nhEksZv0OWVjwBPADckOZ/kYFVdBg4BjwJngWNVdXp8pUqS1mOgHn1VHVhl/0muMuF6LUn2Anvn5+fX+xKSpGvwnrGS1DivdSNJjes06F11I0nj1+n16KvqBHCi1+vd2WUd0iTycggaFW88oonlzUak0bB1I0mNc9WNJDXO1o0miu0aafRcXilJjbNHL0mNs0cvSY2zdSNJjTPoJalxrrqRpoBnyWoYTsZKUuOcjJWkxtmjl6TGGfSS1DgnY6Up48Ss1sqgV+e8vo00Xq66kaTGuepGkhrnZKwkNc6gl6TGORmrTjgBK20cg14j5/K/yeL7IYNeY2XIjJf/fTUIg14jYSume74HWo1BrzVxBClNn06DPsleYO/8/HyXZWid1jqCdMQpdaPToK+qE8CJXq93Z5d1SLPOT2pts3UjzSg/Yc0OT5iSpMY5opdmiKP42eSIXpIaZ9BLUuMMeklqnD16/T8r+7gut5OmmyN6SWqcQS9JjRtL6ybJO4HbgdcCD1bVZ8fxc7QxXJKntfAs28kz8Ig+yZEkF5M8u2L/niTPJTmX5DBAVf1pVd0J3AX89GhLliStxVpG9A8B9wMPv7IjySbgAeBW4DxwKsnxqjrTf8qv9B+XNIUcnbdh4KCvqseT7FixezdwrqqeB0hyFNiX5CxwL/CZqnpmRLVK2gC26tozbI9+C/DCsu3zwE3ALwJvA+aSzFfVx1Z+Y5IFYAFg+/btQ5ahYfnLLbVrLJOxVXUfcN81nrMILAL0er0aRx2SpOGXV14Ati3b3trfN5Ake5MsXrp0acgyJEmrGXZEfwrYlWQnSwG/H/iZQb/ZG49I08OJ2ek1cNAneQS4Bdic5Dzwq1X1YJJDwKPAJuBIVZ1ew2t6K8EN5i+rNHvWsurmwCr7TwIn1/PDHdF3ywlYaTZ4CQRJalynQe9krCSNX6dBX1Unqmphbm6uyzIkqWm2biSpcQa9JDWu0ztMubxyY7i6RpptnQa9yyultnnexmSwdSNJjWuqdePoQdp4tgYnn8srJalxnY7oJclP4uNn0EvaEAZ6d5yMlaTGNTUZK0mDmqVPGM2uo5+lN1GSrsYevaQ1m6YllYMM+jZ6YLjRP8+glzQx/CQ+Hga9JK1irX94JvUPlatuJKlxrrppyKSOJqRpMk3zD4PyEgiS1Dh79JIm0mqfUCfhk+sk1LAWBv2IjOqNn7b/gSRNPoN+DAxrSZPEoJ9yq00ctTihJGl9DHpJG24WBiIrj7HLT/euo5ekxjmil6QBTPOnkKk/YWqQ//hOjkqzZRJDucuamr1MsaTZMonhPins0UtS4wx6SWqck7EDmIQe/yTUIGk6zVzQG5iSZs3MBX2X/CMjqQv26CWpcY7ox8xr0Ujq2kwH/SRdi0LS6hwwDcfWjSQ1buQj+iQ/ANwDzFXVu0b9+hvFkYKkVgw0ok9yJMnFJM+u2L8nyXNJziU5DFBVz1fVwXEUK0lau0FH9A8B9wMPv7IjySbgAeBW4DxwKsnxqjoz6iInlaN+SdOQAwON6KvqceAbK3bvBs71R/AvA0eBfSOuT5I0pGF69FuAF5ZtnwduSvJ64NeBNye5u6p+40rfnGQBWADYvn37EGXMnmkYQUiaHCOfjK2qfwHuGuB5i8AiQK/Xq1HXIUlaMkzQXwC2Ldve2t83sFHceGSjOZqWNG2GWUd/CtiVZGeS64D9wPG1vEBVnaiqhbm5uSHKkCRdzaDLKx8BngBuSHI+ycGqugwcAh4FzgLHqur0+EqVJK3HQK2bqjqwyv6TwMn1/vBpbN1I0rTp9BIItm4kafy81o0kNa7Tq1faurk6V/hIGgVbN5LUOFs3ktS4ToM+yd4ki5cuXeqyDElqmq0bSWqcrRtJapxBL0mNc3nlMi5nlNo2q7/j9uglqXG2biSpcQa9JDXOoJekxnnClCQ1zslYSWqcrRtJapxBL0mNM+glqXGpqq5rIMk/A1/ruo512Ay81HURG2zWjnnWjhc85mny/VX1hms9aSKCfloleaqqel3XsZFm7Zhn7XjBY26RrRtJapxBL0mNM+iHs9h1AR2YtWOeteMFj7k59uglqXGO6CWpcQb9OiX5UJJKsrm/nST3JTmX5G+SvKXrGkclyW8l+dv+cf1Jktcte+zu/jE/l+Qnu6xz1JLs6R/XuSSHu65nHJJsS/L5JGeSnE7ywf7+70nyWJK/7//7u7uudZSSbErypSR/1t/emeSL/ff6j5Jc13WNo2TQr0OSbcBPAP+4bPdtwK7+PwvA73dQ2rg8BvxwVf0o8HfA3QBJbgT2Az8E7AF+L8mmzqocof5xPMDS+3ojcKB/vK25DHyoqm4E3gp8oH+ch4HPVdUu4HP97ZZ8EDi7bPs3gY9W1Tzwr8DBTqoaE4N+fT4K/DKwfIJjH/BwLXkSeF2SN3VS3YhV1Wer6nJ/80lga//rfcDRqvqvqvoH4Bywu4sax2A3cK6qnq+ql4GjLB1vU6rqxap6pv/1f7AUfltYOtaP95/2ceCd3VQ4ekm2ArcDf9DfDvBjwCf7T2nqeMGgX7Mk+4ALVfXlFQ9tAV5Ytn2+v681vwB8pv91y8fc8rFdUZIdwJuBLwJvrKoX+w99HXhjR2WNw++wNFD7Vn/79cC/LRvMNPded3pz8EmV5C+A77vCQ/cAH2GpbdOUqx1zVX26/5x7WPqo/4mNrE3jl+Q1wB8Dv1RV/740yF1SVZWkieV5Se4ALlbV00lu6bqejWLQX0FVve1K+5P8CLAT+HL/F2Er8EyS3cAFYNuyp2/t75sKqx3zK5K8D7gD+PH69prcqT7ma2j52P6PJN/JUsh/oqo+1d/9T0neVFUv9luQF7urcKRuBt6R5O3AdwGvBX6XpVbrq/qj+ubea1s3a1BVX6mq762qHVW1g6WPeG+pqq8Dx4Gf66++eStwadlH36mWZA9LH3XfUVXfXPbQcWB/kuuT7GRpIvqvuqhxDE4Bu/qrMa5jadL5eMc1jVy/P/0gcLaqfnvZQ8eB9/a/fi/w6Y2ubRyq6u6q2tr//d0P/GVVvQf4PPCu/tOaOd5XOKIfnZPA21makPwm8PPdljNS9wPXA4/1P8k8WVV3VdXpJMeAMyy1dD5QVf/dYZ0jU1WXkxwCHgU2AUeq6nTHZY3DzcDPAl9J8tf9fR8B7gWOJTnI0pVl391RfRvlw8DRJL8GfImlP37N8MxYSWqcrRtJapxBL0mNM+glqXEGvSQ1zqCXpMYZ9JLUOINekhpn0EtS4/4HKLjMkCwAsx0AAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dr\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dphi zcut\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for quad in [t1234,t1231,t1212,t1123] :\n", + " plotDoublets(quad,500)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/RecoPixelVertexing/PixelVertexFinding/BuildFile.xml b/RecoPixelVertexing/PixelVertexFinding/BuildFile.xml index 6960b522dbf34..43261b0417410 100644 --- a/RecoPixelVertexing/PixelVertexFinding/BuildFile.xml +++ b/RecoPixelVertexing/PixelVertexFinding/BuildFile.xml @@ -17,3 +17,7 @@ + + + + diff --git a/RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h b/RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h new file mode 100644 index 0000000000000..691c3e9ef429c --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h @@ -0,0 +1,43 @@ +#ifndef RecoPixelVertexing_PixelVertexFinding_pixelVertexHeterogeneousProduct_H +#define RecoPixelVertexing_PixelVertexFinding_pixelVertexHeterogeneousProduct_H + +#include "DataFormats/VertexReco/interface/Vertex.h" +#include "DataFormats/VertexReco/interface/VertexFwd.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" +#include "HeterogeneousCore/CUDAUtilities/interface/CUDAHostAllocator.h" + +namespace pixelVertexHeterogeneousProduct { + + struct CPUProduct { + reco::VertexCollection collection; + }; + + struct VerticesOnGPU{ + float * z_d; + float * zerr_d; + float * chi2_d; + uint16_t * sortInd_d; + int32_t * ivtx_d; // this should be indexed with the original tracks, not the reduced set (oops) + }; + + + struct VerticesOnCPU { + VerticesOnCPU() = default; + + float const *z, *zerr, *chi2; + int16_t const * sortInd; + int32_t const * ivtx; + uint16_t const * itrk; + + uint32_t nVertices=0; + uint32_t nTracks=0; + VerticesOnGPU const * gpu_d = nullptr; + }; + + + using GPUProduct = VerticesOnCPU; // FIXME fill cpu vectors on demand + + using HeterogeneousPixelVertices = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct >; +} +#endif diff --git a/RecoPixelVertexing/PixelVertexFinding/python/PixelVertexes_cfi.py b/RecoPixelVertexing/PixelVertexFinding/python/PixelVertexes_cfi.py index 77a9f367b9d9b..ea9e4b1e4e037 100644 --- a/RecoPixelVertexing/PixelVertexFinding/python/PixelVertexes_cfi.py +++ b/RecoPixelVertexing/PixelVertexFinding/python/PixelVertexes_cfi.py @@ -20,3 +20,6 @@ ) +from Configuration.ProcessModifiers.gpu_cff import gpu +from RecoPixelVertexing.PixelVertexFinding.pixelVertexHeterogeneousProducer_cfi import pixelVertexHeterogeneousProducer as _pixelVertexHeterogeneousProducer +gpu.toReplaceWith(pixelVertices, _pixelVertexHeterogeneousProducer) diff --git a/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousConverter.cc b/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousConverter.cc new file mode 100644 index 0000000000000..e6a78e1c438a9 --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousConverter.cc @@ -0,0 +1,55 @@ +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +#include "RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h" + + +class PixelVertexHeterogeneousConverter: public edm::global::EDProducer<> { +public: + explicit PixelVertexHeterogeneousConverter(edm::ParameterSet const& iConfig); + ~PixelVertexHeterogeneousConverter() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const override; + + edm::EDGetTokenT token_; +}; + +PixelVertexHeterogeneousConverter::PixelVertexHeterogeneousConverter(edm::ParameterSet const& iConfig): + token_(consumes(iConfig.getParameter("src"))) +{ + produces(); +} + +void PixelVertexHeterogeneousConverter::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("pixelVerticesHeterogeneous")); + + descriptions.addWithDefaultLabel(desc); +} + +namespace { + template + auto copy_unique(const T& t) { + return std::make_unique(t); + } +} + +void PixelVertexHeterogeneousConverter::produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const { + edm::Handle hinput; + iEvent.getByToken(token_, hinput); + + const auto& input = hinput->get().getProduct(); + + iEvent.put(copy_unique(input.collection)); +} + + +DEFINE_FWK_MODULE(PixelVertexHeterogeneousConverter); diff --git a/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousProducer.cc b/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousProducer.cc new file mode 100644 index 0000000000000..3bd1e522cb776 --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousProducer.cc @@ -0,0 +1,283 @@ +#include "DataFormats/VertexReco/interface/Vertex.h" +#include "DataFormats/VertexReco/interface/VertexFwd.h" +#include "DataFormats/GeometryCommonDetAlgo/interface/GlobalError.h" +#include "FWCore/Utilities/interface/InputTag.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include +#include +#include + +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +# +#include "DataFormats/BeamSpot/interface/BeamSpot.h" +#include "DataFormats/TrackReco/interface/TrackFwd.h" + +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" + +#include "RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h" +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" +#include "gpuVertexFinder.h" + + +class PixelVertexHeterogeneousProducer : public HeterogeneousEDProducer> { +public: + + using Input = pixelTuplesHeterogeneousProduct::HeterogeneousPixelTuples; + using TuplesOnCPU = pixelTuplesHeterogeneousProduct::TuplesOnCPU; + + using GPUProduct = pixelVertexHeterogeneousProduct::GPUProduct; + using CPUProduct = pixelVertexHeterogeneousProduct::CPUProduct; + using Output = pixelVertexHeterogeneousProduct::HeterogeneousPixelVertices; + + + explicit PixelVertexHeterogeneousProducer(const edm::ParameterSet&); + ~PixelVertexHeterogeneousProducer() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions &descriptions); + void beginStreamGPUCuda(edm::StreamID streamId, + cuda::stream_t<> &cudaStream) override { + m_gpuAlgo.allocateOnGPU(); + } + void acquireGPUCuda(const edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceGPUCuda(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceCPU(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup) override; + private: + // ----------member data --------------------------- + + TuplesOnCPU const * tuples_=nullptr; + + + // Tracking cuts before sending tracks to vertex algo + const float m_ptMin; + + + const bool enableConversion_; + const bool enableTransfer_; + const edm::EDGetTokenT gpuToken_; + edm::EDGetTokenT token_Tracks_; + edm::EDGetTokenT token_BeamSpot_; + + + gpuVertexFinder::Producer m_gpuAlgo; + + bool verbose_ = false; + +}; + + +void PixelVertexHeterogeneousProducer::fillDescriptions(edm::ConfigurationDescriptions &descriptions) { + edm::ParameterSetDescription desc; + desc.add("minT",2); // min number of neighbours to be "core" + desc.add("eps",0.07); // max absolute distance to cluster + desc.add("errmax",0.01); // max error to be "seed" + desc.add("chi2max",9.); // max normalized distance to cluster + + desc.add("PtMin", 0.5); + desc.add("TrackCollection", edm::InputTag("pixelTracks")); + desc.add("beamSpot", edm::InputTag("offlineBeamSpot")); + desc.add("src", edm::InputTag("pixelTracksHitQuadruplets")); + desc.add("gpuEnableConversion", true); + desc.add("gpuEnableTransfer", true); + + HeterogeneousEDProducer::fillPSetDescription(desc); + auto label = "pixelVertexHeterogeneousProducer"; + descriptions.add(label, desc); +} + + +PixelVertexHeterogeneousProducer::PixelVertexHeterogeneousProducer(const edm::ParameterSet& conf) : + HeterogeneousEDProducer(conf) + , m_ptMin(conf.getParameter("PtMin")) // 0.5 GeV + , enableConversion_(conf.getParameter("gpuEnableConversion")) + , enableTransfer_(enableConversion_ || conf.getParameter("gpuEnableTransfer")) + , gpuToken_(consumes(conf.getParameter("src"))) + , m_gpuAlgo( conf.getParameter("minT") + ,conf.getParameter("eps") + ,conf.getParameter("errmax") + ,conf.getParameter("chi2max") + ,enableTransfer_ + ) +{ + produces(); + if (enableConversion_) { + token_Tracks_ = consumes(conf.getParameter("TrackCollection")); + token_BeamSpot_ =consumes(conf.getParameter("beamSpot") ); + // Register my product + produces(); + } else { + produces(); // dummy + } +} + + + +void PixelVertexHeterogeneousProducer::acquireGPUCuda( + const edm::HeterogeneousEvent & e, + const edm::EventSetup & es, + cuda::stream_t<> &cudaStream) { + + // First fish the pixel tracks out of the event + edm::Handle gh; + e.getByToken(gpuToken_, gh); + auto const & gTuples = *gh; + // std::cout << "Vertex Producers: tuples from gpu " << gTuples.nTuples << std::endl; + + tuples_ = gh.product(); + + m_gpuAlgo.produce(cudaStream.id(),gTuples,m_ptMin); + + +} + +void PixelVertexHeterogeneousProducer::produceGPUCuda( + edm::HeterogeneousEvent & e, const edm::EventSetup & es, + cuda::stream_t<> &cudaStream) { + + + auto const & gpuProduct = m_gpuAlgo.fillResults(cudaStream.id()); + + auto output = std::make_unique(); + e.put(std::move(output), heterogeneous::DisableTransfer{}); + + if (!enableConversion_) return; + + edm::Handle trackCollection; + e.getByToken(token_Tracks_,trackCollection); + const reco::TrackCollection tracks = *(trackCollection.product()); + if (verbose_) std::cout << "PixelVertexHeterogeneousProducer" << ": Found " << tracks.size() << " tracks in TrackCollection" << "\n"; + + + edm::Handle bsHandle; + e.getByToken(token_BeamSpot_,bsHandle); + + auto vertexes = std::make_unique(); + + + float x0=0,y0=0,z0=0,dxdz=0,dydz=0; + std::vector itrk; + if(!bsHandle.isValid()) { + edm::LogWarning("PixelVertexHeterogeneousProducer") << "No beamspot found. Using returning vertexes with (0,0,Z) "; + } else { + const reco::BeamSpot & bs = *bsHandle; + x0=bs.x0();y0=bs.y0();z0=bs.z0(); dxdz=bs.dxdz();dydz=bs.dydz(); + } + + // fill legacy data format + if (verbose_) std::cout << "found " << gpuProduct.nVertices << " vertices on GPU using " << gpuProduct.nTracks << " tracks"<< std::endl; + if (verbose_) std::cout << "original tuple size " << (*tuples_).indToEdm.size() << std::endl; + + std::set uind; // fort verifing index consistency + for (int j=int(gpuProduct.nVertices)-1; j>=0; --j) { + auto i = gpuProduct.sortInd[j]; // on gpu sorted in ascending order.... + assert(i>=0); + assert(isize() << " vertexes\n"; + for (unsigned int i=0; isize(); ++i) { + edm::LogInfo("PixelVertexHeterogeneousProducer") << "Vertex number " << i << " has " << (*vertexes)[i].tracksSize() << " tracks with a position of " << (*vertexes)[i].z() << " +- " << std::sqrt( (*vertexes)[i].covariance(2,2) ); + } + + std::cout << ": Found " << vertexes->size() << " vertexes\n"; + for (unsigned int i=0; isize(); ++i) { + std::cout << "Vertex number " << i << " has " << (*vertexes)[i].tracksSize() << " tracks with a position of " << (*vertexes)[i].z() << " +- " << std::sqrt( (*vertexes)[i].covariance(2,2) ) + << " chi2 " << (*vertexes)[i].normalizedChi2() << std::endl; + } + } + + if(vertexes->empty() && bsHandle.isValid()){ + + const reco::BeamSpot & bs = *bsHandle; + + GlobalError bse(bs.rotatedCovariance3D()); + if ( (bse.cxx() <= 0.) || + (bse.cyy() <= 0.) || + (bse.czz() <= 0.) ) { + AlgebraicSymMatrix33 we; + we(0,0)=10000; + we(1,1)=10000; + we(2,2)=10000; + vertexes->push_back(reco::Vertex(bs.position(), we,0.,0.,0)); + + edm::LogInfo("PixelVertexHeterogeneousProducer") << "No vertices found. Beamspot with invalid errors " << bse.matrix() + << "\nWill put Vertex derived from dummy-fake BeamSpot into Event.\n" + << (*vertexes)[0].x() << "\n" + << (*vertexes)[0].y() << "\n" + << (*vertexes)[0].z() << "\n"; + } else { + vertexes->push_back(reco::Vertex(bs.position(), + bs.rotatedCovariance3D(),0.,0.,0)); + + edm::LogInfo("PixelVertexHeterogeneousProducer") << "No vertices found. Will put Vertex derived from BeamSpot into Event:\n" + << (*vertexes)[0].x() << "\n" + << (*vertexes)[0].y() << "\n" + << (*vertexes)[0].z() << "\n"; + } + } + + else if(vertexes->empty() && !bsHandle.isValid()) + { + edm::LogWarning("PixelVertexHeterogeneousProducer") << "No beamspot and no vertex found. No vertex returned."; + } + + e.put(std::move(vertexes)); +} + + +void PixelVertexHeterogeneousProducer::produceCPU( + edm::HeterogeneousEvent &iEvent, const edm::EventSetup &iSetup) +{ + throw cms::Exception("NotImplemented") << "CPU version is no longer implemented"; +} + + + +#include "FWCore/PluginManager/interface/ModuleDef.h" +#include "FWCore/Framework/interface/MakerMacros.h" +DEFINE_FWK_MODULE(PixelVertexHeterogeneousProducer); diff --git a/RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h b/RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h new file mode 100644 index 0000000000000..dfc9efe28a81d --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h @@ -0,0 +1,202 @@ +#ifndef RecoPixelVertexing_PixelVertexFinding_clusterTracks_H +#define RecoPixelVertexing_PixelVertexFinding_clusterTracks_H + +#include +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "HeterogeneousCore/CUDAUtilities/interface/radixSort.h" + +#include "gpuVertexFinder.h" + +namespace gpuVertexFinder { + + // this algo does not really scale as it works in a single block... + // enough for <10K tracks we have + __global__ + void clusterTracks( + OnGPU * pdata, + int minT, // min number of neighbours to be "core" + float eps, // max absolute distance to cluster + float errmax, // max error to be "seed" + float chi2max // max normalized distance to cluster + ) { + + constexpr bool verbose = false; // in principle the compiler should optmize out if false + + + if(verbose && 0==threadIdx.x) printf("params %d %f %f %f\n",minT,eps,errmax,chi2max); + + auto er2mx = errmax*errmax; + + auto & __restrict__ data = *pdata; + auto nt = *data.ntrks; + float const * __restrict__ zt = data.zt; + float const * __restrict__ ezt2 = data.ezt2; + + uint32_t & nvFinal = *data.nvFinal; + uint32_t & nvIntermediate = *data.nvIntermediate; + + uint8_t * __restrict__ izt = data.izt; + int32_t * __restrict__ nn = data.nn; + int32_t * __restrict__ iv = data.iv; + + assert(pdata); + assert(zt); + + using Hist=HistoContainer; + __shared__ Hist hist; + __shared__ typename Hist::Counter ws[32]; + for (auto j=threadIdx.x; j= 0); + assert(iz-INT8_MIN < 256); + hist.count(izt[i]); + iv[i]=i; + nn[i]=0; + } + __syncthreads(); + if (threadIdx.x<32) ws[threadIdx.x]=0; // used by prefix scan... + __syncthreads(); + hist.finalize(ws); + __syncthreads(); + assert(hist.size()==nt); + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + hist.fill(izt[i],uint16_t(i)); + } + __syncthreads(); + + + // count neighbours + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + if (ezt2[i]>er2mx) continue; + auto loop = [&](int j) { + if (i==j) return; + auto dist = std::abs(zt[i]-zt[j]); + if (dist>eps) return; + if (dist*dist>chi2max*(ezt2[i]+ezt2[j])) return; + nn[i]++; + }; + + forEachInBins(hist,izt[i],1,loop); + } + + + __shared__ int nloops; + nloops=0; + + __syncthreads(); + + + + // cluster seeds only + bool more = true; + while (__syncthreads_or(more)) { + if (1==nloops%2) { + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + auto m = iv[i]; + while (m!=iv[m]) m=iv[m]; + iv[i]=m; + } + } else { + more=false; + for (int k = threadIdx.x; k < hist.size(); k += blockDim.x) { + auto p = hist.begin()+k; + auto i = (*p); + auto be = std::min(Hist::bin(izt[i])+1,int(hist.nbins()-1)); + if (nn[i]eps) return; + if (dist*dist>chi2max*(ezt2[i]+ezt2[j])) return; + auto old = atomicMin(&iv[j], iv[i]); + if (old != iv[i]) { + // end the loop only if no changes were applied + more = true; + } + atomicMin(&iv[i], old); + }; + ++p; + for (;p=minT) continue; // DBSCAN edge rule + if (nn[i]>=minT) continue; // DBSCAN edge rule + float mdist=eps; + auto loop = [&](int j) { + if (nn[j]mdist) return; + if (dist*dist>chi2max*(ezt2[i]+ezt2[j])) return; // needed? + mdist=dist; + iv[i] = iv[j]; // assign to cluster (better be unique??) + }; + forEachInBins(hist,izt[i],1,loop); + } + + + __shared__ unsigned int foundClusters; + foundClusters = 0; + __syncthreads(); + + // find the number of different clusters, identified by a tracks with clus[i] == i; + // mark these tracks with a negative id. + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + if (iv[i] == i) { + if (nn[i]>=minT) { + auto old = atomicInc(&foundClusters, 0xffffffff); + iv[i] = -(old + 1); + } else { // noise + iv[i] = -9998; + } + } + } + __syncthreads(); + + assert(foundClusters= 0) { + // mark each track in a cluster with the same id as the first one + iv[i] = iv[iv[i]]; + } + } + __syncthreads(); + + // adjust the cluster id to be a positive value starting from 0 + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + iv[i] = - iv[i] - 1; + } + + nvIntermediate = nvFinal = foundClusters; + + if(verbose && 0==threadIdx.x) printf("found %d proto vertices\n",foundClusters); + + } + +} +#endif diff --git a/RecoPixelVertexing/PixelVertexFinding/src/gpuFitVertices.h b/RecoPixelVertexing/PixelVertexFinding/src/gpuFitVertices.h new file mode 100644 index 0000000000000..e9124816ab37e --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/src/gpuFitVertices.h @@ -0,0 +1,101 @@ +#ifndef RecoPixelVertexing_PixelVertexFinding_fitVertices_H +#define RecoPixelVertexing_PixelVertexFinding_fitVertices_H + +#include +#include +#include +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" +#include "HeterogeneousCore/CUDAUtilities/interface/radixSort.h" + + +#include "gpuVertexFinder.h" + +namespace gpuVertexFinder { + + + __global__ + void fitVertices( + OnGPU * pdata, + float chi2Max // for outlier rejection + ) { + + constexpr bool verbose = false; // in principle the compiler should optmize out if false + + auto & __restrict__ data = *pdata; + auto nt = *data.ntrks; + float const * __restrict__ zt = data.zt; + float const * __restrict__ ezt2 = data.ezt2; + float * __restrict__ zv = data.zv; + float * __restrict__ wv = data.wv; + float * __restrict__ chi2 = data.chi2; + uint32_t & nvFinal = *data.nvFinal; + uint32_t & nvIntermediate = *data.nvIntermediate; + + int32_t * __restrict__ nn = data.nn; + int32_t * __restrict__ iv = data.iv; + + assert(pdata); + assert(zt); + + assert(nvFinal<=nvIntermediate); + nvFinal = nvIntermediate; + auto foundClusters = nvFinal; + + // zero + for (int i = threadIdx.x; i < foundClusters; i += blockDim.x) { + zv[i]=0; + wv[i]=0; + chi2[i]=0; + } + + // only for test + __shared__ int noise; + if(verbose && 0==threadIdx.x) noise = 0; + + __syncthreads(); + + // compute cluster location + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + if (iv[i]>9990) { + if (verbose) atomicAdd(&noise, 1); + continue; + } + assert(iv[i]>=0); + assert(iv[i]0.f); + zv[i]/=wv[i]; + nn[i]=-1; // ndof + } + __syncthreads(); + + + // compute chi2 + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + if (iv[i]>9990) continue; + + auto c2 = zv[iv[i]]-zt[i]; c2 *=c2/ezt2[i]; + if (c2 > chi2Max ) {iv[i] = 9999; continue;} + atomicAdd(&chi2[iv[i]],c2); + atomicAdd(&nn[iv[i]],1); + } + __syncthreads(); + for (int i = threadIdx.x; i < foundClusters; i += blockDim.x) if(nn[i]>0) wv[i] *= float(nn[i])/chi2[i]; + + if(verbose && 0==threadIdx.x) printf("found %d proto clusters ",foundClusters); + if(verbose && 0==threadIdx.x) printf("and %d noise\n",noise); + + } + +} + +#endif diff --git a/RecoPixelVertexing/PixelVertexFinding/src/gpuSortByPt2.h b/RecoPixelVertexing/PixelVertexFinding/src/gpuSortByPt2.h new file mode 100644 index 0000000000000..d6f2c20a4420d --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/src/gpuSortByPt2.h @@ -0,0 +1,59 @@ +#ifndef RecoPixelVertexing_PixelVertexFinding_sortByPt2_H +#define RecoPixelVertexing_PixelVertexFinding_sortByPt2_H + + +#include +#include +#include +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" +#include "HeterogeneousCore/CUDAUtilities/interface/radixSort.h" + + +#include "gpuVertexFinder.h" + +namespace gpuVertexFinder { + + __global__ + void sortByPt2( + OnGPU * pdata + ) { + auto & __restrict__ data = *pdata; + auto nt = *data.ntrks; + float const * __restrict__ ptt2 = data.ptt2; + uint32_t const & nvFinal = *data.nvFinal; + + int32_t const * __restrict__ iv = data.iv; + float * __restrict__ ptv2 = data.ptv2; + uint16_t * __restrict__ sortInd = data.sortInd; + + if (nvFinal<1) return; + + // can be done asynchronoisly at the end of previous event + for (int i = threadIdx.x; i < nvFinal; i += blockDim.x) { + ptv2[i]=0; + } + __syncthreads(); + + + for (int i = threadIdx.x; i < nt; i += blockDim.x) { + if (iv[i]>9990) continue; + atomicAdd(&ptv2[iv[i]], ptt2[i]); + } + __syncthreads(); + + if (1==nvFinal) { + if (threadIdx.x==0) sortInd[0]=0; + return; + } + __shared__ uint16_t ws[1024]; + radixSort(ptv2,sortInd,ws,nvFinal); + + assert(ptv2[sortInd[nvFinal-1]]>=ptv2[sortInd[nvFinal-2]]); + assert(ptv2[sortInd[1]]>=ptv2[sortInd[0]]); + } + +} + +#endif diff --git a/RecoPixelVertexing/PixelVertexFinding/src/gpuSplitVertices.h b/RecoPixelVertexing/PixelVertexFinding/src/gpuSplitVertices.h new file mode 100644 index 0000000000000..56dc6ed41a00f --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/src/gpuSplitVertices.h @@ -0,0 +1,129 @@ +#ifndef RecoPixelVertexing_PixelVertexFinding_splitVertices_H +#define RecoPixelVertexing_PixelVertexFinding_splitVertices_H + +#include +#include +#include +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" + +#include "HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h" +#include "HeterogeneousCore/CUDAUtilities/interface/radixSort.h" + + +#include "gpuVertexFinder.h" + +namespace gpuVertexFinder { + + + __global__ + void splitVertices( + OnGPU * pdata, + float maxChi2 + ) { + + constexpr bool verbose = false; // in principle the compiler should optmize out if false + + + auto & __restrict__ data = *pdata; + auto nt = *data.ntrks; + float const * __restrict__ zt = data.zt; + float const * __restrict__ ezt2 = data.ezt2; + float * __restrict__ zv = data.zv; + float * __restrict__ wv = data.wv; + float const * __restrict__ chi2 = data.chi2; + uint32_t & nvFinal = *data.nvFinal; + + int32_t const * __restrict__ nn = data.nn; + int32_t * __restrict__ iv = data.iv; + + assert(pdata); + assert(zt); + + // one vertex per block + auto kv = blockIdx.x; + + if (kv>= nvFinal) return; + if (nn[kv]<4) return; + if (chi2[kv]tuples_d; + auto const * fit = tracks->helix_fit_results_d; + auto const * quality = tracks->quality_d; + + auto idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx>= tuples.nbins()) return; + if (tuples.size(idx)==0) { + return; + } + + if(quality[idx] != pixelTuplesHeterogeneousProduct::loose ) return; + + auto const & fittedTrack = fit[idx]; + + if (fittedTrack.par(2)>>(tracks.gpu_d,onGPU_d, ptMin); + cudaCheck(cudaGetLastError()); + + clusterTracks<<<1,1024-256,0,stream>>>(onGPU_d,minT,eps,errmax,chi2max); + cudaCheck(cudaGetLastError()); + fitVertices<<<1,1024-256,0,stream>>>(onGPU_d,50.); + cudaCheck(cudaGetLastError()); + + splitVertices<<<1024,128,0,stream>>>(onGPU_d,9.f); + cudaCheck(cudaGetLastError()); + fitVertices<<<1,1024-256,0,stream>>>(onGPU_d,5000.); + cudaCheck(cudaGetLastError()); + + sortByPt2<<<1,256,0,stream>>>(onGPU_d); + cudaCheck(cudaGetLastError()); + + if(enableTransfer) { + cudaCheck(cudaMemcpyAsync(&gpuProduct.nVertices, onGPU.nvFinal, sizeof(uint32_t), + cudaMemcpyDeviceToHost, stream)); + cudaCheck(cudaMemcpyAsync(&gpuProduct.nTracks, onGPU.ntrks, sizeof(uint32_t), + cudaMemcpyDeviceToHost, stream)); + } + } + + Producer::OnCPU const & Producer::fillResults(cudaStream_t stream) { + + if(!enableTransfer) return gpuProduct; + + // finish copy + gpuProduct.ivtx.resize(gpuProduct.nTracks); + cudaCheck(cudaMemcpyAsync(gpuProduct.ivtx.data(),onGPU.iv,sizeof(int32_t)*gpuProduct.nTracks, + cudaMemcpyDeviceToHost, stream)); + gpuProduct.itrk.resize(gpuProduct.nTracks); + cudaCheck(cudaMemcpyAsync(gpuProduct.itrk.data(),onGPU.itrk,sizeof(int16_t)*gpuProduct.nTracks, + cudaMemcpyDeviceToHost, stream)); + + gpuProduct.z.resize(gpuProduct.nVertices); + cudaCheck(cudaMemcpyAsync(gpuProduct.z.data(),onGPU.zv,sizeof(float)*gpuProduct.nVertices, + cudaMemcpyDeviceToHost, stream)); + gpuProduct.zerr.resize(gpuProduct.nVertices); + cudaCheck(cudaMemcpyAsync(gpuProduct.zerr.data(),onGPU.wv,sizeof(float)*gpuProduct.nVertices, + cudaMemcpyDeviceToHost, stream)); + gpuProduct.chi2.resize(gpuProduct.nVertices); + cudaCheck(cudaMemcpyAsync(gpuProduct.chi2.data(),onGPU.chi2,sizeof(float)*gpuProduct.nVertices, + cudaMemcpyDeviceToHost, stream)); + + gpuProduct.sortInd.resize(gpuProduct.nVertices); + cudaCheck(cudaMemcpyAsync(gpuProduct.sortInd.data(),onGPU.sortInd,sizeof(uint16_t)*gpuProduct.nVertices, + cudaMemcpyDeviceToHost, stream)); + + cudaStreamSynchronize(stream); + + return gpuProduct; + } + +} // end namespace diff --git a/RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h b/RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h new file mode 100644 index 0000000000000..8f005052da95c --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h @@ -0,0 +1,101 @@ +#ifndef RecoPixelVertexing_PixelVertexFinding_gpuVertexFinder_H +#define RecoPixelVertexing_PixelVertexFinding_gpuVertexFinder_H + +#include + +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" +#include "RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h" + + +namespace gpuVertexFinder { + + struct OnGPU { + + static constexpr uint32_t MAXTRACKS = 16000; + static constexpr uint32_t MAXVTX= 1024; + + uint32_t * ntrks; // number of "selected tracks" + uint16_t * itrk; // index of original track + float * zt; // input track z at bs + float * ezt2; // input error^2 on the above + float * ptt2; // input pt^2 on the above + + + float * zv; // output z-posistion of found vertices + float * wv; // output weight (1/error^2) on the above + float * chi2; // vertices chi2 + float * ptv2; // vertices pt^2 + uint32_t * nvFinal; // the number of vertices + uint32_t * nvIntermediate; // the number of vertices after splitting pruning etc. + int32_t * iv; // vertex index for each associated track + uint16_t * sortInd; // sorted index (by pt2) + + // workspace + uint8_t * izt; // interized z-position of input tracks + int32_t * nn; // number of nearest neighbours (reused as number of dof for output vertices) + + }; + + + struct OnCPU { + OnCPU() = default; + + std::vector> z,zerr, chi2; + std::vector> sortInd; + std::vector> ivtx; + std::vector> itrk; + + uint32_t nVertices=0; + uint32_t nTracks=0; + OnGPU const * gpu_d = nullptr; + }; + + class Producer { + public: + + using TuplesOnCPU = pixelTuplesHeterogeneousProduct::TuplesOnCPU; + + using OnCPU = gpuVertexFinder::OnCPU; + using OnGPU = gpuVertexFinder::OnGPU; + + + Producer( + int iminT, // min number of neighbours to be "core" + float ieps, // max absolute distance to cluster + float ierrmax, // max error to be "seed" + float ichi2max, // max normalized distance to cluster + bool ienableTransfer + ) : + minT(iminT), + eps(ieps), + errmax(ierrmax), + chi2max(ichi2max), + enableTransfer(ienableTransfer) + {} + + ~Producer() { deallocateOnGPU();} + + void produce(cudaStream_t stream, TuplesOnCPU const & tuples, float ptMin); + + OnCPU const & fillResults(cudaStream_t stream); + + + void allocateOnGPU(); + void deallocateOnGPU(); + + private: + OnCPU gpuProduct; + OnGPU onGPU; + OnGPU * onGPU_d=nullptr; + + int minT; // min number of neighbours to be "core" + float eps; // max absolute distance to cluster + float errmax; // max error to be "seed" + float chi2max; // max normalized distance to cluster + const bool enableTransfer; + + }; + +} // end namespace + +#endif diff --git a/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml b/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml index 708bcb9ea8bbd..dc3b98f8456a5 100644 --- a/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml +++ b/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml @@ -17,3 +17,10 @@ + + + + + + + diff --git a/RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu b/RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu new file mode 100644 index 0000000000000..d1f508ca98798 --- /dev/null +++ b/RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu @@ -0,0 +1,298 @@ +#include +#include +#include +#include + +#include "RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h" +#include "RecoPixelVertexing/PixelVertexFinding/src/gpuFitVertices.h" +#include "RecoPixelVertexing/PixelVertexFinding/src/gpuSortByPt2.h" +#include "RecoPixelVertexing/PixelVertexFinding/src/gpuSplitVertices.h" + + +using namespace gpuVertexFinder; +#include + + +struct Event { + std::vector zvert; + std::vector itrack; + std::vector ztrack; + std::vector eztrack; + std::vector pttrack; + std::vector ivert; +}; + +struct ClusterGenerator { + + explicit ClusterGenerator(float nvert, float ntrack) : + rgen(-13.,13), errgen(0.005,0.025), clusGen(nvert), trackGen(ntrack), gauss(0.,1.), ptGen(1.) + {} + + void operator()(Event & ev) { + + int nclus = clusGen(reng); + ev.zvert.resize(nclus); + ev.itrack.resize(nclus); + for (auto & z : ev.zvert) { + z = 3.5f*gauss(reng); + } + + ev.ztrack.clear(); + ev.eztrack.clear(); + ev.ivert.clear(); + for (int iv=0; iv rgen; + std::uniform_real_distribution errgen; + std::poisson_distribution clusGen; + std::poisson_distribution trackGen; + std::normal_distribution gauss; + std::exponential_distribution ptGen; + +}; + + +#include + +int main() { + + if (cuda::device::count() == 0) { + std::cerr << "No CUDA devices on this system" << "\n"; + exit(EXIT_FAILURE); + } + + auto current_device = cuda::device::current::get(); + + auto ntrks_d = cuda::memory::device::make_unique(current_device, 1); + auto zt_d = cuda::memory::device::make_unique(current_device, 64000); + auto ezt2_d = cuda::memory::device::make_unique(current_device, 64000); + auto ptt2_d = cuda::memory::device::make_unique(current_device, 64000); + auto zv_d = cuda::memory::device::make_unique(current_device, 256); + auto wv_d = cuda::memory::device::make_unique(current_device, 256); + auto chi2_d = cuda::memory::device::make_unique(current_device, 256); + auto ptv2_d = cuda::memory::device::make_unique(current_device, 256); + auto ind_d = cuda::memory::device::make_unique(current_device, 256); + + auto izt_d = cuda::memory::device::make_unique(current_device, 64000); + auto nn_d = cuda::memory::device::make_unique(current_device, 64000); + auto iv_d = cuda::memory::device::make_unique(current_device, 64000); + + auto nv_d = cuda::memory::device::make_unique(current_device, 1); + auto nv2_d = cuda::memory::device::make_unique(current_device, 1); + + auto onGPU_d = cuda::memory::device::make_unique(current_device, 1); + + OnGPU onGPU; + + onGPU.ntrks = ntrks_d.get(); + onGPU.zt = zt_d.get(); + onGPU.ezt2 = ezt2_d.get(); + onGPU.ptt2 = ptt2_d.get(); + onGPU.zv = zv_d.get(); + onGPU.wv = wv_d.get(); + onGPU.chi2 = chi2_d.get(); + onGPU.ptv2 = ptv2_d.get(); + onGPU.sortInd = ind_d.get(); + onGPU.nvFinal = nv_d.get(); + onGPU.nvIntermediate = nv2_d.get(); + onGPU.izt = izt_d.get(); + onGPU.nn = nn_d.get(); + onGPU.iv = iv_d.get(); + + + cuda::memory::copy(onGPU_d.get(), &onGPU, sizeof(OnGPU)); + + + Event ev; + + for (int nav=30;nav<80;nav+=20){ + + ClusterGenerator gen(nav,10); + + for (int i=8; i<20; ++i) { + + auto kk=i/4; // M param + + gen(ev); + + std::cout << ev.zvert.size() << ' ' << ev.ztrack.size() << std::endl; + auto nt = ev.ztrack.size(); + cuda::memory::copy(onGPU.ntrks,&nt,sizeof(uint32_t)); + cuda::memory::copy(onGPU.zt,ev.ztrack.data(),sizeof(float)*ev.ztrack.size()); + cuda::memory::copy(onGPU.ezt2,ev.eztrack.data(),sizeof(float)*ev.eztrack.size()); + cuda::memory::copy(onGPU.ptt2,ev.pttrack.data(),sizeof(float)*ev.eztrack.size()); + + float eps = 0.1f; + + std::cout << "M eps " << kk << ' ' << eps << std::endl; + + if ( (i%4) == 0 ) + cuda::launch(clusterTracks, + { 1, 512+256 }, + onGPU_d.get(),kk,eps, + 0.02f,12.0f + ); + + if ( (i%4) == 1 ) + cuda::launch(clusterTracks, + { 1, 512+256 }, + onGPU_d.get(),kk,eps, + 0.02f,9.0f + ); + + if ( (i%4) == 2 ) + cuda::launch(clusterTracks, + { 1, 512+256 }, + onGPU_d.get(),kk,eps, + 0.01f,9.0f + ); + + if ( (i%4) == 3 ) + cuda::launch(clusterTracks, + { 1, 512+256 }, + onGPU_d.get(),kk,0.7f*eps, + 0.01f,9.0f + ); + cudaCheck(cudaGetLastError()); + cudaDeviceSynchronize(); + + cuda::launch(fitVertices, + { 1,1024-256 }, + onGPU_d.get(),50.f + ); + cudaCheck(cudaGetLastError()); + + uint32_t nv; + cuda::memory::copy(&nv, onGPU.nvFinal, sizeof(uint32_t)); + if (nv==0) { + std::cout << "NO VERTICES???" << std::endl; + continue; + } + float chi2[2*nv]; // make space for splitting... + float zv[2*nv]; + float wv[2*nv]; + float ptv2[2*nv]; + int32_t nn[2*nv]; + uint16_t ind[2*nv]; + + cuda::memory::copy(&nn, onGPU.nn, nv*sizeof(int32_t)); + cuda::memory::copy(&chi2, onGPU.chi2, nv*sizeof(float)); + for (auto j=0U; j0) chi2[j]/=float(nn[j]); + { + auto mx = std::minmax_element(chi2,chi2+nv); + std::cout << "after fit min max chi2 " << nv << " " << *mx.first << ' ' << *mx.second << std::endl; + } + + cuda::launch(fitVertices, + { 1,1024-256 }, + onGPU_d.get(), 50.f + ); + cuda::memory::copy(&nv, onGPU.nvFinal, sizeof(uint32_t)); + cuda::memory::copy(&nn, onGPU.nn, nv*sizeof(int32_t)); + cuda::memory::copy(&chi2, onGPU.chi2, nv*sizeof(float)); + for (auto j=0U; j0) chi2[j]/=float(nn[j]); + { + auto mx = std::minmax_element(chi2,chi2+nv); + std::cout << "before splitting min max chi2 " << nv << " " << *mx.first << ' ' << *mx.second << std::endl; + } + + cuda::launch(splitVertices, + { 1024, 64 }, + onGPU_d.get(), + 9.f + ); + cuda::memory::copy(&nv, onGPU.nvIntermediate, sizeof(uint32_t)); + std::cout << "after split " << nv << std::endl; + + cuda::launch(fitVertices, + { 1,1024-256 }, + onGPU_d.get(),5000.f + ); + cudaCheck(cudaGetLastError()); + + + cuda::launch(sortByPt2, + { 1, 256 }, + onGPU_d.get() + ); + + cuda::memory::copy(&nv, onGPU.nvFinal, sizeof(uint32_t)); + + if (nv==0) { + std::cout << "NO VERTICES???" << std::endl; + continue; + } + + + cuda::memory::copy(&zv, onGPU.zv, nv*sizeof(float)); + cuda::memory::copy(&wv, onGPU.wv, nv*sizeof(float)); + cuda::memory::copy(&chi2, onGPU.chi2, nv*sizeof(float)); + cuda::memory::copy(&ptv2, onGPU.ptv2, nv*sizeof(float)); + cuda::memory::copy(&nn, onGPU.nn, nv*sizeof(int32_t)); + cuda::memory::copy(&ind, onGPU.sortInd, nv*sizeof(uint16_t)); + for (auto j=0U; j0) chi2[j]/=float(nn[j]); + { + auto mx = std::minmax_element(chi2,chi2+nv); + std::cout << "min max chi2 " << nv << " " << *mx.first << ' ' << *mx.second << std::endl; + } + + { + auto mx = std::minmax_element(wv,wv+nv); + std::cout << "min max error " << 1./std::sqrt(*mx.first) << ' ' << 1./std::sqrt(*mx.second) << std::endl; + } + + { + auto mx = std::minmax_element(ptv2,ptv2+nv); + std::cout << "min max ptv2 " << *mx.first << ' ' << *mx.second << std::endl; + std::cout << "min max ptv2 " << ptv2[ind[0]] << ' ' << ptv2[ind[nv-1]] << " at " << ind[0] << ' ' << ind[nv-1] << std::endl; + + } + + float dd[nv]; + for (auto kv=0U; kv + + + + + + - + + + + + diff --git a/SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.cu b/SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.cu new file mode 100644 index 0000000000000..b402daef07a05 --- /dev/null +++ b/SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.cu @@ -0,0 +1,196 @@ +#include +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" +#include "CUDADataFormats/SiPixelDigi/interface/SiPixelDigisCUDA.h" +#include "CUDADataFormats/SiPixelCluster/interface/SiPixelClustersCUDA.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudastdAlgorithm.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/SiPixelRawToClusterGPUKernel.h" +#include "RecoLocalTracker/SiPixelRecHits/interface/pixelCPEforGPU.h" + +#include "ClusterSLOnGPU.h" + +using ClusterSLGPU = trackerHitAssociationHeterogeneousProduct::ClusterSLGPU; + +__global__ +void simLink(const SiPixelDigisCUDA::DeviceConstView *dd, uint32_t ndigis, const SiPixelClustersCUDA::DeviceConstView *cc, clusterSLOnGPU::HitsOnGPU const * hhp, ClusterSLGPU const * slp, uint32_t n) +{ + assert(slp == slp->me_d); + + constexpr int32_t invTK = 0; // std::numeric_limits::max(); + constexpr uint16_t InvId = 9999; // must be > MaxNumModules + + auto const & hh = *hhp; + auto const & sl = *slp; + auto i = blockIdx.x * blockDim.x + threadIdx.x; + + if (i >= ndigis) + return; + + auto id = dd->moduleInd(i); + if (InvId == id) + return; + assert(id < 2000); + + auto ch = pixelgpudetails::pixelToChannel(dd->xx(i), dd->yy(i)); + auto first = hh.hitsModuleStart_d[id]; + auto cl = first + cc->clus(i); + assert(cl < 2000 * blockDim.x); + + const std::array me{{id, ch, 0, 0}}; + + auto less = [] __host__ __device__ (std::array const & a, std::array const & b)->bool { + // in this context we do not care of [2] + return a[0] < b[0] or (not b[0] < a[0] and a[1] < b[1]); + }; + + auto equal = [] __host__ __device__ (std::array const & a, std::array const & b)->bool { + // in this context we do not care of [2] + return a[0] == b[0] and a[1] == b[1]; + }; + + auto const * b = sl.links_d; + auto const * e = b + n; + + auto p = cuda_std::lower_bound(b, e, me, less); + int32_t j = p-sl.links_d; + assert(j >= 0); + + auto getTK = [&](int i) { auto const & l = sl.links_d[i]; return l[2];}; + + j = std::min(int(j), int(n-1)); + if (equal(me, sl.links_d[j])) { + auto const itk = j; + auto const tk = getTK(j); + auto old = atomicCAS(&sl.tkId_d[cl], invTK, itk); + if (invTK == old or tk == getTK(old)) { + atomicAdd(&sl.n1_d[cl], 1); + } else { + auto old = atomicCAS(&sl.tkId2_d[cl], invTK, itk); + if (invTK == old or tk == getTK(old)) atomicAdd(&sl.n2_d[cl], 1); + } + } +} + +__global__ +void verifyZero(uint32_t nhits, ClusterSLGPU const * slp) { + auto i = blockIdx.x*blockDim.x + threadIdx.x; + if (i > nhits) + return; + + auto const & sl = *slp; + + assert(sl.tkId_d[i]==0); + auto const & tk = sl.links_d[0]; + assert(tk[0]==0); + assert(tk[1]==0); + assert(tk[2]==0); + assert(tk[3]==0); +} + +__global__ +void dumpLink(int first, int ev, clusterSLOnGPU::HitsOnGPU const * hhp, uint32_t nhits, ClusterSLGPU const * slp) { + auto i = first + blockIdx.x*blockDim.x + threadIdx.x; + if (i>nhits) return; + + auto const & hh = *hhp; + auto const & sl = *slp; + + /* just an example of use of global error.... + assert(hh.cpeParams); + float ge[6]; + hh.cpeParams->detParams(hh.detInd_d[i]).frame.toGlobal(hh.xerr_d[i], 0, hh.yerr_d[i],ge); + printf("Error: %d: %f,%f,%f,%f,%f,%f\n",hh.detInd_d[i],ge[0],ge[1],ge[2],ge[3],ge[4],ge[5]); + */ + + auto const & tk1 = sl.links_d[sl.tkId_d[i]]; + auto const & tk2 = sl.links_d[sl.tkId2_d[i]]; + + printf("HIT: %d %d %d %d %f %f %f %f %d %d %d %d %d %d %d\n", ev, i, + hh.detInd_d[i], hh.charge_d[i], + hh.xg_d[i], hh.yg_d[i], hh.zg_d[i], hh.rg_d[i], hh.iphi_d[i], + tk1[2], tk1[3], sl.n1_d[i], + tk2[2], tk2[3], sl.n2_d[i] + ); + +} + +namespace clusterSLOnGPU { + + constexpr uint32_t invTK = 0; // std::numeric_limits::max(); + + void printCSVHeader() { + printf("HIT: %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n", "ev", "ind", + "det", "charge", + "xg","yg","zg","rg","iphi", + "tkId","pt","n1","tkId2","pt2","n2" + ); + } + + std::atomic evId(0); + std::once_flag doneCSVHeader; + + Kernel::Kernel(cuda::stream_t<>& stream, bool dump) : doDump(dump) { + if (doDump) std::call_once(doneCSVHeader, printCSVHeader); + alloc(stream); + } + + void Kernel::alloc(cuda::stream_t<>& stream) { + cudaCheck(cudaMalloc((void**) & slgpu.links_d, (ClusterSLGPU::MAX_DIGIS)*sizeof(std::array))); + cudaCheck(cudaMalloc((void**) & slgpu.tkId_d, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t))); + cudaCheck(cudaMalloc((void**) & slgpu.tkId2_d, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t))); + cudaCheck(cudaMalloc((void**) & slgpu.n1_d, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t))); + cudaCheck(cudaMalloc((void**) & slgpu.n2_d, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t))); + cudaCheck(cudaMalloc((void**) & slgpu.me_d, sizeof(ClusterSLGPU))); + cudaCheck(cudaMemcpyAsync(slgpu.me_d, &slgpu, sizeof(ClusterSLGPU), cudaMemcpyDefault, stream.id())); + } + + void Kernel::deAlloc() { + cudaCheck(cudaFree(slgpu.links_d)); + cudaCheck(cudaFree(slgpu.tkId_d)); + cudaCheck(cudaFree(slgpu.tkId2_d)); + cudaCheck(cudaFree(slgpu.n1_d)); + cudaCheck(cudaFree(slgpu.n2_d)); + cudaCheck(cudaFree(slgpu.me_d)); + } + + void Kernel::zero(cudaStream_t stream) { + cudaCheck(cudaMemsetAsync(slgpu.tkId_d, invTK, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t), stream)); + cudaCheck(cudaMemsetAsync(slgpu.tkId2_d, invTK, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t), stream)); + cudaCheck(cudaMemsetAsync(slgpu.n1_d, 0, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t), stream)); + cudaCheck(cudaMemsetAsync(slgpu.n2_d, 0, (ClusterSLGPU::MaxNumModules*256)*sizeof(uint32_t), stream)); + } + + void Kernel::algo(DigisOnGPU const & dd, uint32_t ndigis, HitsOnCPU const & hh, uint32_t nhits, uint32_t n, cuda::stream_t<>& stream) { + zero(stream.id()); + + ClusterSLGPU const & sl = slgpu; + + int ev = ++evId; + int threadsPerBlock = 256; + + int blocks = (nhits + threadsPerBlock - 1) / threadsPerBlock; + verifyZero<<>>(nhits, sl.me_d); + cudaCheck(cudaGetLastError()); + + blocks = (ndigis + threadsPerBlock - 1) / threadsPerBlock; + + assert(sl.me_d); + simLink<<>>(dd.digis_d.view(), ndigis, dd.clusters_d.view(), hh.gpu_d, sl.me_d, n); + cudaCheck(cudaGetLastError()); + + if (doDump) { + cudaStreamSynchronize(stream.id()); // flush previous printf + // one line == 200B so each kernel can print only 5K lines.... + blocks = 16; // (nhits + threadsPerBlock - 1) / threadsPerBlock; + for (int first=0; first>>(first, ev, hh.gpu_d, nhits, sl.me_d); + cudaCheck(cudaGetLastError()); + cudaStreamSynchronize(stream.id()); + } + } + cudaCheck(cudaGetLastError()); + } + +} diff --git a/SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.h b/SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.h new file mode 100644 index 0000000000000..00b0e34b301c8 --- /dev/null +++ b/SimTracker/TrackerHitAssociation/plugins/ClusterSLOnGPU.h @@ -0,0 +1,40 @@ +#ifndef SimTracker_TrackerHitAssociation_plugins_ClusterSLOnGPU_h +#define SimTracker_TrackerHitAssociation_plugins_ClusterSLOnGPU_h + +#include +#include + +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" + +#include "trackerHitAssociationHeterogeneousProduct.h" + +namespace clusterSLOnGPU { + + using ClusterSLGPU = trackerHitAssociationHeterogeneousProduct::ClusterSLGPU; + using GPUProduct = trackerHitAssociationHeterogeneousProduct::GPUProduct; + + using DigisOnGPU = siPixelRawToClusterHeterogeneousProduct::GPUProduct; + using HitsOnGPU = siPixelRecHitsHeterogeneousProduct::HitsOnGPU; + using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; + + class Kernel { + public: + Kernel(cuda::stream_t<>& stream, bool dump); + ~Kernel() {deAlloc();} + void algo(DigisOnGPU const & dd, uint32_t ndigis, HitsOnCPU const & hh, uint32_t nhits, uint32_t n, cuda::stream_t<>& stream); + GPUProduct getProduct() { return GPUProduct{slgpu.me_d};} + + private: + void alloc(cuda::stream_t<>& stream); + void deAlloc(); + void zero(cudaStream_t stream); + + public: + ClusterSLGPU slgpu; + bool doDump; + }; +} + +#endif // SimTracker_TrackerHitAssociation_plugins_ClusterSLOnGPU_h diff --git a/SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneous.cc b/SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneous.cc new file mode 100644 index 0000000000000..8690d78aace86 --- /dev/null +++ b/SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneous.cc @@ -0,0 +1,409 @@ +#include +#include +#include + +#include + +#include "DataFormats/Common/interface/DetSetVector.h" +#include "DataFormats/Common/interface/DetSetVectorNew.h" +#include "DataFormats/Common/interface/Handle.h" +#include "DataFormats/DetId/interface/DetId.h" +#include "DataFormats/Phase2TrackerCluster/interface/Phase2TrackerCluster1D.h" +#include "DataFormats/Phase2TrackerDigi/interface/Phase2TrackerDigi.h" +#include "DataFormats/SiPixelCluster/interface/SiPixelCluster.h" +#include "DataFormats/SiPixelDetId/interface/PixelChannelIdentifier.h" +#include "DataFormats/SiStripCluster/interface/SiStripCluster.h" +#include "DataFormats/TrackerRecHit2D/interface/OmniClusterRef.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/stream/EDProducer.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Utilities/interface/EDGetToken.h" +#include "FWCore/Utilities/interface/InputTag.h" +#include "Geometry/Records/interface/TrackerDigiGeometryRecord.h" +#include "Geometry/TrackerGeometryBuilder/interface/TrackerGeometry.h" +#include "HeterogeneousCore/CUDACore/interface/GPUCuda.h" +#include "HeterogeneousCore/CUDAServices/interface/CUDAService.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" +#include "HeterogeneousCore/Producer/interface/HeterogeneousEDProducer.h" +#include "RecoLocalTracker/SiPixelClusterizer/plugins/siPixelRawToClusterHeterogeneousProduct.h" +#include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" +#include "SimDataFormats/Track/interface/SimTrackContainer.h" +#include "SimDataFormats/TrackerDigiSimLink/interface/PixelDigiSimLink.h" +#include "SimDataFormats/TrackerDigiSimLink/interface/StripDigiSimLink.h" +#include "SimDataFormats/TrackingAnalysis/interface/TrackingParticle.h" +#include "SimDataFormats/TrackingAnalysis/interface/TrackingParticleFwd.h" +#include "SimTracker/TrackerHitAssociation/interface/ClusterTPAssociation.h" + +#include "ClusterSLOnGPU.h" + +class ClusterTPAssociationHeterogeneous : public HeterogeneousEDProducer> +{ +public: + typedef std::vector OmniClusterCollection; + + using ClusterSLGPU = trackerHitAssociationHeterogeneousProduct::ClusterSLGPU; + using GPUProduct = trackerHitAssociationHeterogeneousProduct::GPUProduct; + using CPUProduct = trackerHitAssociationHeterogeneousProduct::CPUProduct; + using Output = trackerHitAssociationHeterogeneousProduct::ClusterTPAHeterogeneousProduct; + + using PixelDigiClustersH = siPixelRawToClusterHeterogeneousProduct::HeterogeneousDigiCluster; + using PixelRecHitsH = siPixelRecHitsHeterogeneousProduct::HeterogeneousPixelRecHit; + + explicit ClusterTPAssociationHeterogeneous(const edm::ParameterSet&); + ~ClusterTPAssociationHeterogeneous() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + + void beginStreamGPUCuda(edm::StreamID streamId, + cuda::stream_t<> &cudaStream) override; + + void acquireGPUCuda(const edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceGPUCuda(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) override; + void produceCPU(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup) override; + + void makeMap(const edm::HeterogeneousEvent &iEvent); + std::unique_ptr produceLegacy(edm::HeterogeneousEvent &iEvent, const edm::EventSetup& es); + + template + std::vector> + getSimTrackId(const edm::Handle>& simLinks, const DetId& detId, uint32_t channel) const; + + edm::EDGetTokenT> sipixelSimLinksToken_; + edm::EDGetTokenT> sistripSimLinksToken_; + edm::EDGetTokenT> siphase2OTSimLinksToken_; + edm::EDGetTokenT> pixelClustersToken_; + edm::EDGetTokenT> stripClustersToken_; + edm::EDGetTokenT> phase2OTClustersToken_; + edm::EDGetTokenT trackingParticleToken_; + + edm::EDGetTokenT tGpuDigis; + edm::EDGetTokenT tGpuHits; + + std::unique_ptr gpuAlgo; + + std::map, TrackingParticleRef> mapping; + + std::vector> digi2tp; + + bool doDump; + +}; + +ClusterTPAssociationHeterogeneous::ClusterTPAssociationHeterogeneous(const edm::ParameterSet & cfg) + : HeterogeneousEDProducer(cfg), + sipixelSimLinksToken_(consumes>(cfg.getParameter("pixelSimLinkSrc"))), + sistripSimLinksToken_(consumes>(cfg.getParameter("stripSimLinkSrc"))), + siphase2OTSimLinksToken_(consumes>(cfg.getParameter("phase2OTSimLinkSrc"))), + pixelClustersToken_(consumes>(cfg.getParameter("pixelClusterSrc"))), + stripClustersToken_(consumes>(cfg.getParameter("stripClusterSrc"))), + phase2OTClustersToken_(consumes>(cfg.getParameter("phase2OTClusterSrc"))), + trackingParticleToken_(consumes(cfg.getParameter("trackingParticleSrc"))), + tGpuDigis(consumesHeterogeneous(cfg.getParameter("heterogeneousPixelDigiClusterSrc"))), + tGpuHits(consumesHeterogeneous(cfg.getParameter("heterogeneousPixelRecHitSrc"))), + doDump(cfg.getParameter("dumpCSV")) +{ + produces(); +} + +void ClusterTPAssociationHeterogeneous::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("simTrackSrc", edm::InputTag("g4SimHits")); + desc.add("pixelSimLinkSrc", edm::InputTag("simSiPixelDigis")); + desc.add("stripSimLinkSrc", edm::InputTag("simSiStripDigis")); + desc.add("phase2OTSimLinkSrc", edm::InputTag("simSiPixelDigis","Tracker")); + desc.add("pixelClusterSrc", edm::InputTag("siPixelClusters")); + desc.add("stripClusterSrc", edm::InputTag("siStripClusters")); + desc.add("phase2OTClusterSrc", edm::InputTag("siPhase2Clusters")); + desc.add("trackingParticleSrc", edm::InputTag("mix", "MergedTrackTruth")); + desc.add("heterogeneousPixelDigiClusterSrc", edm::InputTag("siPixelClustersPreSplitting")); + desc.add("heterogeneousPixelRecHitSrc", edm::InputTag("siPixelRecHitsPreSplitting")); + + desc.add("dumpCSV", false); + + HeterogeneousEDProducer::fillPSetDescription(desc); + + descriptions.add("tpClusterProducerHeterogeneousDefault", desc); +} + +void ClusterTPAssociationHeterogeneous::beginStreamGPUCuda(edm::StreamID streamId, cuda::stream_t<> & cudaStream) { + gpuAlgo = std::make_unique(cudaStream, doDump); +} + +void ClusterTPAssociationHeterogeneous::makeMap(const edm::HeterogeneousEvent &iEvent) { + // TrackingParticle + edm::Handle TPCollectionH; + iEvent.getByToken(trackingParticleToken_, TPCollectionH); + + // prepare temporary map between SimTrackId and TrackingParticle index + mapping.clear(); + for (TrackingParticleCollection::size_type itp = 0; + itp < TPCollectionH.product()->size(); ++itp) { + TrackingParticleRef trackingParticle(TPCollectionH, itp); + + // SimTracks inside TrackingParticle + EncodedEventId eid(trackingParticle->eventId()); + for (auto itrk = trackingParticle->g4Track_begin(); + itrk != trackingParticle->g4Track_end(); ++itrk) { + std::pair trkid(itrk->trackId(), eid); + //std::cout << "creating map for id: " << trkid.first << " with tp: " << trackingParticle.key() << std::endl; + mapping.insert(std::make_pair(trkid, trackingParticle)); + } + } +} + +void ClusterTPAssociationHeterogeneous::acquireGPUCuda(const edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) { + + edm::ESHandle geom; + iSetup.get().get( geom ); + + // Pixel DigiSimLink + edm::Handle> sipixelSimLinks; + // iEvent.getByLabel(_pixelSimLinkSrc, sipixelSimLinks); + iEvent.getByToken(sipixelSimLinksToken_, sipixelSimLinks); + + // TrackingParticle + edm::Handle TPCollectionH; + iEvent.getByToken(trackingParticleToken_, TPCollectionH); + + makeMap(iEvent); + + // gpu stuff ------------------------ + + edm::Handle gd; + edm::Handle gh; + iEvent.getByToken(tGpuDigis, gd); + iEvent.getByToken(tGpuHits, gh); + auto const & gDigis = *gd; + auto const & gHits = *gh; + auto ndigis = gDigis.nDigis; + auto nhits = gHits.nHits; + + digi2tp.clear(); + digi2tp.push_back({{0, 0, 0, 0}}); // put at 0 0 + for (auto const & links : *sipixelSimLinks) { + DetId detId(links.detId()); + const GeomDetUnit * genericDet = geom->idToDetUnit(detId); + uint32_t gind = genericDet->index(); + for (auto const & link : links) { + if (link.fraction() < 0.5f) { + continue; + } + auto tkid = std::make_pair(link.SimTrackId(), link.eventId()); + auto ipos = mapping.find(tkid); + if (ipos != mapping.end()) { + uint32_t pt = 1000*(*ipos).second->pt(); + digi2tp.push_back({{gind, uint32_t(link.channel()),(*ipos).second.key(), pt}}); + } + } + } + std::sort(digi2tp.begin(), digi2tp.end()); + cudaCheck(cudaMemcpyAsync(gpuAlgo->slgpu.links_d, digi2tp.data(), sizeof(std::array)*digi2tp.size(), cudaMemcpyDefault, cudaStream.id())); + gpuAlgo->algo(gDigis, ndigis, gHits, nhits, digi2tp.size(), cudaStream); + + // end gpu stuff --------------------- +} + +void ClusterTPAssociationHeterogeneous::produceGPUCuda(edm::HeterogeneousEvent &iEvent, + const edm::EventSetup &iSetup, + cuda::stream_t<> &cudaStream) { + auto output = std::make_unique(gpuAlgo->getProduct()); + auto legacy = produceLegacy(iEvent, iSetup).release(); + + iEvent.put(std::move(output), [legacy](const GPUProduct& hits, CPUProduct& cpu) { + cpu = *legacy; delete legacy; + }); +} + +void ClusterTPAssociationHeterogeneous::produceCPU(edm::HeterogeneousEvent &iEvent, const edm::EventSetup& es) { + + makeMap(iEvent); + iEvent.put(std::move(produceLegacy(iEvent, es))); + +} + +std::unique_ptr +ClusterTPAssociationHeterogeneous::produceLegacy(edm::HeterogeneousEvent &iEvent, const edm::EventSetup& es) { + // Pixel DigiSimLink + edm::Handle> sipixelSimLinks; + // iEvent.getByLabel(_pixelSimLinkSrc, sipixelSimLinks); + iEvent.getByToken(sipixelSimLinksToken_, sipixelSimLinks); + + // SiStrip DigiSimLink + edm::Handle> sistripSimLinks; + iEvent.getByToken(sistripSimLinksToken_, sistripSimLinks); + + // Phase2 OT DigiSimLink + edm::Handle> siphase2OTSimLinks; + iEvent.getByToken(siphase2OTSimLinksToken_, siphase2OTSimLinks); + + // Pixel Cluster + edm::Handle> pixelClusters; + bool foundPixelClusters = iEvent.getByToken(pixelClustersToken_, pixelClusters); + + // Strip Cluster + edm::Handle> stripClusters; + bool foundStripClusters = iEvent.getByToken(stripClustersToken_, stripClusters); + + // Phase2 Cluster + edm::Handle> phase2OTClusters; + bool foundPhase2OTClusters = iEvent.getByToken(phase2OTClustersToken_, phase2OTClusters); + + // TrackingParticle + edm::Handle TPCollectionH; + iEvent.getByToken(trackingParticleToken_, TPCollectionH); + + auto output = std::make_unique(TPCollectionH); + auto & clusterTPList = output->collection; + + if ( foundPixelClusters ) { + // Pixel Clusters + for (edmNew::DetSetVector::const_iterator iter = pixelClusters->begin(); + iter != pixelClusters->end(); ++iter) { + uint32_t detid = iter->id(); + DetId detId(detid); + edmNew::DetSet link_pixel = (*iter); + for (edmNew::DetSet::const_iterator di = link_pixel.begin(); + di != link_pixel.end(); ++di) { + const SiPixelCluster& cluster = (*di); + edm::Ref, SiPixelCluster> c_ref = + edmNew::makeRefTo(pixelClusters, di); + + std::set> simTkIds; + for (int irow = cluster.minPixelRow(); irow <= cluster.maxPixelRow(); ++irow) { + for (int icol = cluster.minPixelCol(); icol <= cluster.maxPixelCol(); ++icol) { + uint32_t channel = PixelChannelIdentifier::pixelToChannel(irow, icol); + std::vector> trkid(getSimTrackId(sipixelSimLinks, detId, channel)); + if (trkid.empty()) continue; + simTkIds.insert(trkid.begin(), trkid.end()); + } + } + for (std::set>::const_iterator iset = simTkIds.begin(); + iset != simTkIds.end(); iset++) { + auto ipos = mapping.find(*iset); + if (ipos != mapping.end()) { + //std::cout << "cluster in detid: " << detid << " from tp: " << ipos->second.key() << " " << iset->first << std::endl; + clusterTPList.emplace_back(OmniClusterRef(c_ref), ipos->second); + } + } + } + } + } + + if ( foundStripClusters ) { + // Strip Clusters + for (edmNew::DetSetVector::const_iterator iter = stripClusters->begin(false), eter = stripClusters->end(false); + iter != eter; ++iter) { + if (!(*iter).isValid()) continue; + uint32_t detid = iter->id(); + DetId detId(detid); + edmNew::DetSet link_strip = (*iter); + for (edmNew::DetSet::const_iterator di = link_strip.begin(); + di != link_strip.end(); di++) { + const SiStripCluster& cluster = (*di); + edm::Ref, SiStripCluster> c_ref = + edmNew::makeRefTo(stripClusters, di); + + std::set> simTkIds; + int first = cluster.firstStrip(); + int last = first + cluster.amplitudes().size(); + + for (int istr = first; istr < last; ++istr) { + std::vector> trkid(getSimTrackId(sistripSimLinks, detId, istr)); + if (trkid.empty()) continue; + simTkIds.insert(trkid.begin(), trkid.end()); + } + for (std::set>::const_iterator iset = simTkIds.begin(); + iset != simTkIds.end(); iset++) { + auto ipos = mapping.find(*iset); + if (ipos != mapping.end()) { + //std::cout << "cluster in detid: " << detid << " from tp: " << ipos->second.key() << " " << iset->first << std::endl; + clusterTPList.emplace_back(OmniClusterRef(c_ref), ipos->second); + } + } + } + } + } + + if ( foundPhase2OTClusters ) { + + // Phase2 Clusters + if(phase2OTClusters.isValid()){ + for (edmNew::DetSetVector::const_iterator iter = phase2OTClusters->begin(false), eter = phase2OTClusters->end(false); + iter != eter; ++iter) { + if (!(*iter).isValid()) continue; + uint32_t detid = iter->id(); + DetId detId(detid); + edmNew::DetSet link_phase2 = (*iter); + for (edmNew::DetSet::const_iterator di = link_phase2.begin(); + di != link_phase2.end(); di++) { + const Phase2TrackerCluster1D& cluster = (*di); + edm::Ref, Phase2TrackerCluster1D> c_ref = + edmNew::makeRefTo(phase2OTClusters, di); + + std::set> simTkIds; + + for (unsigned int istr(0); istr < cluster.size(); ++istr) { + uint32_t channel = Phase2TrackerDigi::pixelToChannel(cluster.firstRow() + istr, cluster.column()); + std::vector> trkid(getSimTrackId(siphase2OTSimLinks, detId, channel)); + if (trkid.empty()) continue; + simTkIds.insert(trkid.begin(), trkid.end()); + } + + for (std::set>::const_iterator iset = simTkIds.begin(); + iset != simTkIds.end(); iset++) { + auto ipos = mapping.find(*iset); + if (ipos != mapping.end()) { + clusterTPList.emplace_back(OmniClusterRef(c_ref), ipos->second); + } + } + } + } + } + } + + clusterTPList.sortAndUnique(); + + return output; + mapping.clear(); +} + +template +std::vector> +//std::pair +ClusterTPAssociationHeterogeneous::getSimTrackId(const edm::Handle>& simLinks, + const DetId& detId, uint32_t channel) const +{ + //std::pair simTrkId; + std::vector> simTrkId; + auto isearch = simLinks->find(detId); + if (isearch != simLinks->end()) { + // Loop over DigiSimLink in this det unit + edm::DetSet link_detset = (*isearch); + for (typename edm::DetSet::const_iterator it = link_detset.data.begin(); + it != link_detset.data.end(); ++it) { + if (channel == it->channel()) { + simTrkId.push_back(std::make_pair(it->SimTrackId(), it->eventId())); + } + } + } + return simTrkId; +} +#include "FWCore/PluginManager/interface/ModuleDef.h" +#include "FWCore/Framework/interface/MakerMacros.h" + +DEFINE_FWK_MODULE(ClusterTPAssociationHeterogeneous); diff --git a/SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneousConverter.cc b/SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneousConverter.cc new file mode 100644 index 0000000000000..25e6e78560c45 --- /dev/null +++ b/SimTracker/TrackerHitAssociation/plugins/ClusterTPAssociationHeterogeneousConverter.cc @@ -0,0 +1,59 @@ +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + +#include "trackerHitAssociationHeterogeneousProduct.h" + + +class ClusterTPAssociationHeterogeneousConverter: public edm::global::EDProducer<> { +public: + + using Input = trackerHitAssociationHeterogeneousProduct::ClusterTPAHeterogeneousProduct; + using Product = ClusterTPAssociation; + + explicit ClusterTPAssociationHeterogeneousConverter(edm::ParameterSet const& iConfig); + ~ClusterTPAssociationHeterogeneousConverter() override = default; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + +private: + void produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const override; + + edm::EDGetTokenT token_; +}; + +ClusterTPAssociationHeterogeneousConverter::ClusterTPAssociationHeterogeneousConverter(edm::ParameterSet const& iConfig): + token_(consumes(iConfig.getParameter("src"))) +{ + produces(); +} + +void ClusterTPAssociationHeterogeneousConverter::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("src", edm::InputTag("tpClusterProducerHeterogeneos")); + + descriptions.add("tpClusterHeterogeneousConverter",desc); +} + +namespace { + template + auto copy_unique(const T& t) { + return std::make_unique(t); + } +} + +void ClusterTPAssociationHeterogeneousConverter::produce(edm::StreamID, edm::Event& iEvent, const edm::EventSetup& iSetup) const { + edm::Handle hinput; + iEvent.getByToken(token_, hinput); + + const auto& input = hinput->get().getProduct(); + + iEvent.put(copy_unique(input.collection)); +} + + +DEFINE_FWK_MODULE(ClusterTPAssociationHeterogeneousConverter); diff --git a/SimTracker/TrackerHitAssociation/plugins/trackerHitAssociationHeterogeneousProduct.h b/SimTracker/TrackerHitAssociation/plugins/trackerHitAssociationHeterogeneousProduct.h new file mode 100644 index 0000000000000..c3f86ddf855c8 --- /dev/null +++ b/SimTracker/TrackerHitAssociation/plugins/trackerHitAssociationHeterogeneousProduct.h @@ -0,0 +1,47 @@ +#ifndef SimTrackerTrackerHitAssociationClusterHeterogeneousProduct_H +#define SimTrackerTrackerHitAssociationClusterHeterogeneousProduct_H + +#ifndef __CUDACC__ +#include "SimTracker/TrackerHitAssociation/interface/ClusterTPAssociation.h" +#endif + +#include "HeterogeneousCore/Product/interface/HeterogeneousProduct.h" + + +namespace trackerHitAssociationHeterogeneousProduct { + +#ifndef __CUDACC__ + struct CPUProduct { + CPUProduct() = default; + template + explicit CPUProduct(T const & t) : collection(t){} + ClusterTPAssociation collection; + }; +#endif + + struct ClusterSLGPU { + + ClusterSLGPU * me_d=nullptr; + std::array * links_d; + uint32_t * tkId_d; + uint32_t * tkId2_d; + uint32_t * n1_d; + uint32_t * n2_d; + + static constexpr uint32_t MAX_DIGIS = 2000*150; + static constexpr uint32_t MaxNumModules = 2000; + + }; + + struct GPUProduct { + ClusterSLGPU * gpu_d=nullptr; + }; + +#ifndef __CUDACC__ + using ClusterTPAHeterogeneousProduct = HeterogeneousProductImpl, + heterogeneous::GPUCudaProduct >; +#endif + +} + +#endif diff --git a/SimTracker/TrackerHitAssociation/python/tpClusterProducer_cfi.py b/SimTracker/TrackerHitAssociation/python/tpClusterProducer_cfi.py index 8757a67226fb8..a8de0a96e4678 100644 --- a/SimTracker/TrackerHitAssociation/python/tpClusterProducer_cfi.py +++ b/SimTracker/TrackerHitAssociation/python/tpClusterProducer_cfi.py @@ -18,3 +18,11 @@ stripSimLinkSrc = "mixData:StripDigiSimLink", phase2OTSimLinkSrc = "mixData:Phase2OTDigiSimLink", ) + + +from SimTracker.TrackerHitAssociation.tpClusterProducerHeterogeneousDefault_cfi import tpClusterProducerHeterogeneousDefault as _tpClusterProducerHeterogeneous +tpClusterProducerHeterogeneous = _tpClusterProducerHeterogeneous.clone() + +from SimTracker.TrackerHitAssociation.tpClusterHeterogeneousConverter_cfi import tpClusterHeterogeneousConverter as _tpHeterogeneousConverter +tpClusterProducerConverter = _tpHeterogeneousConverter.clone() + diff --git a/Validation/RecoTrack/python/PostProcessorTracker_cfi.py b/Validation/RecoTrack/python/PostProcessorTracker_cfi.py index 3776c2cd45b9b..6b5a19f799035 100644 --- a/Validation/RecoTrack/python/PostProcessorTracker_cfi.py +++ b/Validation/RecoTrack/python/PostProcessorTracker_cfi.py @@ -270,9 +270,9 @@ def _addNoFlow(module): ) postProcessorTrackTrackingOnly = postProcessorTrack.clone() -postProcessorTrackTrackingOnly.subDirs.extend(["Tracking/TrackSeeding/*", "Tracking/PixelTrack/*"]) +postProcessorTrackTrackingOnly.subDirs.extend(["Tracking/TrackSeeding/*", "Tracking/PixelTrack/*", "Tracking/PixelTrackFromPV/*", "Tracking/PixelTrackFromPVAllTP/*", "Tracking/PixelTrackBHadron/*"]) postProcessorTrackSummaryTrackingOnly = postProcessorTrackSummary.clone() -postProcessorTrackSummaryTrackingOnly.subDirs.extend(["Tracking/TrackSeeding", "Tracking/PixelTrack"]) +postProcessorTrackSummaryTrackingOnly.subDirs.extend(["Tracking/TrackSeeding", "Tracking/PixelTrack", "Tracking/PixelTrackFromPV/*", "Tracking/PixelTrackFromPVAllTP/*", "Tracking/PixelTrackBHadron/*"]) postProcessorTrackSequenceTrackingOnly = cms.Sequence( postProcessorTrackTrackingOnly+ diff --git a/Validation/RecoTrack/python/TrackValidation_cff.py b/Validation/RecoTrack/python/TrackValidation_cff.py index 8b3bb4cb88ab8..bc84c87cf191f 100644 --- a/Validation/RecoTrack/python/TrackValidation_cff.py +++ b/Validation/RecoTrack/python/TrackValidation_cff.py @@ -451,6 +451,13 @@ def _getMVASelectors(postfix): # Built tracks, in the standard sequence mainly for monitoring the track selection MVA tpClusterProducerPreSplitting = tpClusterProducer.clone(pixelClusterSrc = "siPixelClustersPreSplitting") quickTrackAssociatorByHitsPreSplitting = quickTrackAssociatorByHits.clone(cluster2TPSrc = "tpClusterProducerPreSplitting") +tpClusterProducerHeterogeneousPreSplitting = tpClusterProducerHeterogeneous.clone( + pixelClusterSrc = "siPixelClustersPreSplitting" +) +from Configuration.ProcessModifiers.gpu_cff import gpu +gpu.toReplaceWith(tpClusterProducerPreSplitting, tpClusterProducerConverter.clone( + src = "tpClusterProducerHeterogeneousPreSplitting" +)) _trackValidatorSeedingBuilding = trackValidator.clone( # common for built tracks and seeds (in trackingOnly) associators = ["quickTrackAssociatorByHits"], UseAssociators = True, @@ -554,6 +561,7 @@ def _uniqueFirstLayers(layerList): ) tracksValidationTruth = cms.Task( tpClusterProducer, + tpClusterProducerHeterogeneousPreSplitting, tpClusterProducerPreSplitting, quickTrackAssociatorByHits, quickTrackAssociatorByHitsPreSplitting, @@ -748,6 +756,12 @@ def _uniqueFirstLayers(layerList): trackAssociation = "trackingParticlePixelTrackAsssociation" ) +pixelTracksFromPV = generalTracksFromPV.clone( + src = "pixelTracks", + vertexTag = "pixelVertices", + quality = "undefQuality", +) + trackValidatorPixelTrackingOnly = trackValidator.clone( dirName = "Tracking/PixelTrack/", label = ["pixelTracks"], @@ -758,16 +772,57 @@ def _uniqueFirstLayers(layerList): vertexAssociator = "PixelVertexAssociatorByPositionAndTracks", dodEdxPlots = False, ) +trackValidatorFromPVPixelTrackingOnly = trackValidatorPixelTrackingOnly.clone( + dirName = "Tracking/PixelTrackFromPV/", + label = ["pixelTracksFromPV"], + label_tp_effic = "trackingParticlesSignal", + label_tp_fake = "trackingParticlesSignal", + label_tp_effic_refvector = True, + label_tp_fake_refvector = True, + trackCollectionForDrCalculation = "pixelTracksFromPV", + doPlotsOnlyForTruePV = True, + doPVAssociationPlots = False, + doResolutionPlotsForLabels = ["disabled"], +) +trackValidatorFromPVAllTPPixelTrackingOnly = trackValidatorFromPVPixelTrackingOnly.clone( + dirName = "Tracking/PixelTrackFromPVAllTP/", + label_tp_effic = trackValidatorPixelTrackingOnly.label_tp_effic.value(), + label_tp_fake = trackValidatorPixelTrackingOnly.label_tp_fake.value(), + label_tp_effic_refvector = False, + label_tp_fake_refvector = False, + doSimPlots = False, + doSimTrackPlots = False, +) +trackValidatorBHadronPixelTrackingOnly = trackValidatorPixelTrackingOnly.clone( + dirName = "Tracking/PixelTrackBHadron/", + label_tp_effic = "trackingParticlesBHadron", + label_tp_effic_refvector = True, + doSimPlots = True, + doRecoTrackPlots = False, # Fake rate is defined wrt. all TPs, and that is already included in trackValidator + dodEdxPlots = False, +) + tracksValidationTruthPixelTrackingOnly = tracksValidationTruth.copy() tracksValidationTruthPixelTrackingOnly.replace(trackingParticleRecoTrackAsssociation, trackingParticlePixelTrackAsssociation) tracksValidationTruthPixelTrackingOnly.replace(VertexAssociatorByPositionAndTracks, PixelVertexAssociatorByPositionAndTracks) +tracksValidationTruthPixelTrackingOnly.add(trackingParticlesBHadron) + +tracksPreValidationPixelTrackingOnly = cms.Task( + tracksValidationTruthPixelTrackingOnly, + trackingParticlesSignal, + pixelTracksFromPV, +) tracksValidationPixelTrackingOnly = cms.Sequence( - trackValidatorPixelTrackingOnly, - tracksValidationTruthPixelTrackingOnly + trackValidatorPixelTrackingOnly + + trackValidatorFromPVPixelTrackingOnly + + trackValidatorFromPVAllTPPixelTrackingOnly + + trackValidatorBHadronPixelTrackingOnly, + tracksPreValidationPixelTrackingOnly ) + ### Lite mode (only generalTracks and HP) trackValidatorLite = trackValidator.clone( label = ["generalTracks", "cutsRecoTracksHp"] diff --git a/Validation/RecoTrack/python/plotting/html.py b/Validation/RecoTrack/python/plotting/html.py index 238e59eb2c4d4..1cac97b736941 100644 --- a/Validation/RecoTrack/python/plotting/html.py +++ b/Validation/RecoTrack/python/plotting/html.py @@ -63,6 +63,10 @@ def _allToBTV(s): return s.replace("All", "BTV-like") def _ptCut(s): return s.replace("Tracks", "Tracks pT > 0.9 GeV").replace("tracks", "tracks pT > 0.9 GeV") +def _allToPixel(s): + return s.replace("All", "Pixel") +def _toPixel(s): + return s.replace("Tracks", "Pixel tracks") _trackQualityNameOrder = collections.OrderedDict([ ("seeding_seeds", "Seeds"), ("seeding_seedsa", "Seeds A"), @@ -194,6 +198,9 @@ def _ptCut(s): ("bhadron_highPurity", _allToHP(_bhadronName)), # Pixel tracks ("pixel", "Pixel tracks"), + ("pixelFromPV", _toPixel(_fromPVName)), + ("pixelFromPVAllTP", _toPixel(_fromPVAllTPName)), + ("pixelbhadron", _allToPixel(_bhadronName)), # These are for vertices ("genvertex", "Gen vertices"), ("pixelVertices", "Pixel vertices"), @@ -242,6 +249,9 @@ def _sectionNameLegend(): "bhadron_": _bhadronLegend, "bhadron_highPurity": _allToHP(_bhadronLegend), "bhadron_btvLike": _bhadronLegend.replace("All tracks", _btvLegend), + "pixelFromPV": _fromPVLegend, + "pixelFromPVAllTP": _fromPVAllTPLegend, + "pixelbhadron": _bhadronLegend, } class Table: diff --git a/Validation/RecoTrack/python/plotting/trackingPlots.py b/Validation/RecoTrack/python/plotting/trackingPlots.py index 84079e9a62a92..a14dbe41dfc9b 100644 --- a/Validation/RecoTrack/python/plotting/trackingPlots.py +++ b/Validation/RecoTrack/python/plotting/trackingPlots.py @@ -1348,11 +1348,19 @@ def _appendTrackingPlots(lastDirName, name, algoPlots, onlyForPileup=False, only _appendTrackingPlots("TrackGsf", "gsf", _simBasedPlots+_recoBasedPlots, onlyForElectron=True, rawSummary=True, highPuritySummary=False) _appendTrackingPlots("TrackBHadron", "bhadron", _simBasedPlots+_recoBasedPlots, onlyForBHadron=True) # Pixel tracks -_common = dict(purpose=PlotPurpose.Pixel, page="pixel") -plotter.append("pixelTrack", _trackingFolders("PixelTrack"), TrackingPlotFolder(*(_simBasedPlots+_recoBasedPlots), **_common)) -plotterExt.append("pixelTrack", _trackingFolders("PixelTrack"), TrackingPlotFolder(*_extendedPlots, **_common)) -plotter.append("pixelTrack_summary", _trackingFolders("PixelTrack"), PlotFolder(_summaryRaw, _summaryRawN, loopSubFolders=False, purpose=PlotPurpose.TrackingSummary, page="summary", section="pixel")) -plotter.appendTable("pixelTrack_summary", _trackingFolders("PixelTrack"), TrackingSummaryTable(section="pixel", collection=TrackingSummaryTable.Pixel)) +def _appendPixelTrackingPlots(lastDirName, name): + _common = dict(section=name, purpose=PlotPurpose.Pixel, page="pixel") + _folders = _trackingFolders(lastDirName) + + plotter.append(name, _folders, TrackingPlotFolder(*(_simBasedPlots+_recoBasedPlots), **_common)) + plotterExt.append(name, _folders, TrackingPlotFolder(*_extendedPlots, **_common)) + + plotter.append(name+"_summary", _folders, PlotFolder(_summaryRaw, _summaryRawN, loopSubFolders=False, purpose=PlotPurpose.TrackingSummary, page="summary", section=name)) + plotter.appendTable(name+"_summary", _folders, TrackingSummaryTable(section=name, collection=TrackingSummaryTable.Pixel)) +_appendPixelTrackingPlots("PixelTrack", "pixel") +_appendPixelTrackingPlots("PixelTrackFromPV", "pixelFromPV") +_appendPixelTrackingPlots("PixelTrackFromPVAllTP", "pixelFromPVAllTP") +_appendPixelTrackingPlots("PixelTrackBHadron", "pixelbhadron") # MiniAOD