diff --git a/Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h b/Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h index 37c97a92a3eaa..68fb60361d40d 100644 --- a/Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h +++ b/Geometry/TrackerGeometryBuilder/interface/phase1PixelTopology.h @@ -2,6 +2,7 @@ #define Geometry_TrackerGeometryBuilder_phase1PixelTopology_h #include +#include namespace phase1PixelTopology { @@ -29,6 +30,66 @@ namespace phase1PixelTopology { }; + 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 +#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/GPUSimpleVector.h b/HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h index 8652aa97a709f..66525d13db8dc 100644 --- a/HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h +++ b/HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h @@ -80,7 +80,8 @@ template struct SimpleVector { } #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; } diff --git a/HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h b/HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h index 8dcefdce65ab4..a060fe6799b6c 100644 --- a/HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h +++ b/HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h @@ -80,6 +80,10 @@ template struct VecArray { 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]; } diff --git a/HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h b/HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h index 3a6545bed17df..61ccc7484d68b 100644 --- a/HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h +++ b/HeterogeneousCore/CUDAUtilities/interface/HistoContainer.h @@ -19,6 +19,8 @@ #ifdef __CUDACC__ #include "HeterogeneousCore/CUDAUtilities/interface/prefixScan.h" #endif +#include "HeterogeneousCore/CUDAUtilities/interface/AtomicPairCounter.h" + #ifdef __CUDACC__ namespace cudautils { @@ -38,8 +40,7 @@ namespace cudautils { template __global__ - void fillFromVector(Histo * __restrict__ h, uint32_t nh, T const * __restrict__ v, uint32_t const * __restrict__ offsets, - uint32_t * __restrict__ ws ) { + 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); @@ -47,27 +48,43 @@ namespace cudautils { int32_t ih = off - offsets - 1; assert(ih >= 0); assert(ih < nh); - (*h).fill(v[i], i, ws, ih); + (*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, typename Histo::Counter * __restrict__ ws, + 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) { - uint32_t * off = (uint32_t *)( (char*)(h) +offsetof(Histo,off)); - cudaMemsetAsync(off,0, 4*Histo::totbins(),stream); + launchZero(h,stream); auto nblocks = (totSize + nthreads - 1) / nthreads; countFromVector<<>>(h, nh, v, offsets); cudaCheck(cudaGetLastError()); - size_t wss = Histo::totbins(); - CubDebugExit(cub::DeviceScan::InclusiveSum(ws, wss, off, off, Histo::totbins(), stream)); - cudaMemsetAsync(ws,0, 4*Histo::totbins(),stream); - fillFromVector<<>>(h, nh, v, offsets,ws); + 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 @@ -149,8 +166,8 @@ class HistoContainer { 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()-1); - return std::max(temp_storage_bytes,size_t(totbins())); + cub::DeviceScan::InclusiveSum(d_temp_storage, temp_storage_bytes, v, v, totbins()); + return temp_storage_bytes; } #endif @@ -176,22 +193,79 @@ class HistoContainer { #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; } @@ -202,31 +276,35 @@ class HistoContainer { assert(b0); + bins[w-1] = j; } #ifdef __CUDACC__ __device__ __forceinline__ void finalize(Counter * ws) { - blockPrefixScan(off+1,totbins()-1,ws); + assert(off[totbins()-1]==0); + blockPrefixScan(off,totbins(),ws); + assert(off[totbins()-1]==off[totbins()-2]); } __host__ #endif void finalize() { - for(uint32_t i=2; i +using OneToManyAssoc = HistoContainer; + #endif // HeterogeneousCore_CUDAUtilities_HistoContainer_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 index 1049aed41fd5f..57df6834bba83 100644 --- a/HeterogeneousCore/CUDAUtilities/test/BuildFile.xml +++ b/HeterogeneousCore/CUDAUtilities/test/BuildFile.xml @@ -1,21 +1,35 @@ - + + + + + + + + + + + + + + + - + @@ -25,21 +39,34 @@ + - + + + + + + + + + + + + + diff --git a/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp index ca8167ab10894..c61a5004f8bd9 100644 --- a/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp +++ b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cpp @@ -35,22 +35,20 @@ void go() { Hist h; Hist4 h4; - typename Hist::Counter ws[Hist::totbins()]; - typename Hist4::Counter ws4[Hist4::totbins()]; 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 (auto & i: ws) i=0; - for (auto & i: ws4) i=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.off[0]==0); assert(h.size()==N); - assert(h4.off[0]==0); assert(h4.size()==N); - for (long long j = 0; j < N; j++) { h.fill(v[j],j,ws); if(j<2000) h4.fill(v[j],j,ws4,2); else h4.fill(v[j],j,ws4,j%4); } + 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); diff --git a/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu index 8d640b728e25c..67cbd30b7e786 100644 --- a/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu +++ b/HeterogeneousCore/CUDAUtilities/test/HistoContainer_t.cu @@ -42,7 +42,7 @@ void go() { Hist h; auto h_d = cuda::memory::device::make_unique(current_device, 1); - auto ws_d = cuda::memory::device::make_unique(current_device, Hist::totbins()); + auto ws_d = cuda::memory::device::make_unique(current_device, Hist::wsSize()); auto off_d = cuda::memory::device::make_unique(current_device, nParts+1); diff --git a/HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu b/HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu index c3682578163c8..0d529fd7e7b03 100644 --- a/HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu +++ b/HeterogeneousCore/CUDAUtilities/test/OneHistoContainer_t.cu @@ -18,27 +18,23 @@ void mykernel(T const * __restrict__ v, uint32_t N) { if (threadIdx.x==0) printf("start kernel for %d data\n",N); using Hist = HistoContainer; - constexpr auto wss = Hist::totbins(); - - if (threadIdx.x==0) printf("ws size %d\n",wss); __shared__ Hist hist; - __shared__ typename Hist::Counter ws[wss]; + __shared__ typename Hist::Counter ws[32]; - for (auto j=threadIdx.x; j +#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/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClustering.h b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClustering.h index 26bb4f9244c6a..dba9fe12f5492 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClustering.h +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/gpuClustering.h @@ -81,10 +81,9 @@ namespace gpuClustering { constexpr uint32_t maxPixInModule = 4000; constexpr auto nbins = phase1PixelTopology::numColsInModule + 2; //2+2; using Hist = HistoContainer; - constexpr auto wss = Hist::totbins(); __shared__ Hist hist; - __shared__ typename Hist::Counter ws[wss]; - 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 + // 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; while (__syncthreads_or(more)) { - more = false; + if (1==nloops%2) { + for (int j=threadIdx.x, k = 0; j 1) return; // if (std::abs(int(y[m]) - int(y[i])) > 1) return; // binssize is 1 auto old = atomicMin(&clusterId[m], clusterId[i]); @@ -185,9 +206,8 @@ namespace gpuClustering { ++p; for (;p(gpu_.owner_16bit_, gpu_.owner_16bit_pitch_, 4); cudaCheck(cudaMalloc((void **) & gpu_.hist_d, sizeof(HitsOnGPU::Hist))); - cudaCheck(cudaMalloc((void **) & gpu_.hws_d, 4*HitsOnGPU::Hist::totbins())); + cudaCheck(cudaMalloc((void **) & gpu_.hws_d, HitsOnGPU::Hist::wsSize())); cudaCheck(cudaMalloc((void **) & gpu_d, sizeof(HitsOnGPU))); - gpu_.me_d = gpu_d; - cudaCheck(cudaMemcpyAsync(gpu_d, &gpu_, sizeof(HitsOnGPU), cudaMemcpyDefault, cudaStream.id())); // 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))); @@ -113,6 +119,7 @@ namespace pixelgpudetails { 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_)); diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h b/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h index aff529791aeca..dcc80308c4463 100644 --- a/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h +++ b/RecoLocalTracker/SiPixelRecHits/plugins/PixelRecHits.h @@ -50,6 +50,7 @@ namespace pixelgpudetails { 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; diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc b/RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc index 00cfbe0baa5c0..68f53a47157d4 100644 --- a/RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc +++ b/RecoLocalTracker/SiPixelRecHits/plugins/SiPixelRecHitHeterogeneous.cc @@ -277,7 +277,7 @@ void SiPixelRecHitHeterogeneous::run(const edm::HandlecommonParams(), cpeParams->detParams(me), clusParams, ic); - pixelCPEforGPU::error(cpeParams->commonParams(), cpeParams->detParams(me), clusParams, ic); + pixelCPEforGPU::errorFromDB(cpeParams->commonParams(), cpeParams->detParams(me), clusParams, ic); chargeh[h] = clusParams.charge[ic]; @@ -135,8 +135,8 @@ namespace gpuPixelRecHits { xl[h]= clusParams.xpos[ic]; yl[h]= clusParams.ypos[ic]; - xe[h]= clusParams.xerr[ic]; - ye[h]= clusParams.yerr[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]; diff --git a/RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h b/RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h index 9d0fe7a279799..ea6eaf8458dde 100644 --- a/RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h +++ b/RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h @@ -37,7 +37,7 @@ namespace siPixelRecHitsHeterogeneousProduct { using Hist = HistoContainer; Hist * hist_d; - typename Hist::Counter * hws_d; + uint8_t * hws_d; HitsOnGPU const * me_d = nullptr; @@ -46,6 +46,11 @@ namespace siPixelRecHitsHeterogeneousProduct { 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 { diff --git a/RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc b/RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc index af7dd7337084e..eb51dd5a2eaeb 100644 --- a/RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc +++ b/RecoLocalTracker/SiPixelRecHits/src/PixelCPEFast.cc @@ -125,7 +125,84 @@ void PixelCPEFast::fillParamsForGpu() { auto vv = p.theDet->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() {} @@ -143,25 +220,15 @@ PixelCPEBase::ClusterParam* PixelCPEFast::createClusterParam(const SiPixelCluste return new ClusterParamGeneric(cl); } -//----------------------------------------------------------------------------- -//! 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_ ) { - - float qclus = theClusterParam.theCluster->charge(); + +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 @@ -170,28 +237,43 @@ PixelCPEFast::localPosition(DetParam const & theDetParam, ClusterParam & theClus 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, + + 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; - - } // if ( UseErrorsFromTemplates_ ) +} + +//----------------------------------------------------------------------------- +//! 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; } 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 99a3a9321062b..15224adb78cc3 100644 --- a/RecoPixelVertexing/Configuration/python/customizePixelTracksForProfiling.py +++ b/RecoPixelVertexing/Configuration/python/customizePixelTracksForProfiling.py @@ -6,6 +6,7 @@ def customizePixelTracksForProfiling(process): process.out = cms.OutputModule("AsciiOutputModule", outputCommands = cms.untracked.vstring( "keep *_pixelTracks_*_*", + "keep *_pixelVertices_*_*", ), verbosity = cms.untracked.uint32(0), ) @@ -19,17 +20,12 @@ def customizePixelTracksForProfiling(process): def customizePixelTracksForProfilingDisableConversion(process): process = customizePixelTracksForProfiling(process) - # Turn off cluster shape filter so that CA doesn't depend on clusters - process.pixelTracksHitQuadruplets.SeedComparitorPSet = cms.PSet(ComponentName = cms.string("none")) - - # Replace pixel track producer with a dummy one for now - from RecoPixelVertexing.PixelTrackFitting.pixelTrackProducerFromCUDA_cfi import pixelTrackProducerFromCUDA as _pixelTrackProducerFromCUDA - process.pixelTracks = _pixelTrackProducerFromCUDA.clone() - # 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 @@ -40,5 +36,6 @@ def customizePixelTracksForProfilingDisableTransfer(process): process.siPixelClustersPreSplitting.gpuEnableTransfer = False process.siPixelRecHitsPreSplitting.gpuEnableTransfer = False process.pixelTracksHitQuadruplets.gpuEnableTransfer = False + process.pixelVertices.gpuEnableTransfer = False return process 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/RiemannFit.h b/RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h index 6de1c77bbac12..3e93aab13d00d 100644 --- a/RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h +++ b/RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h @@ -1,90 +1,16 @@ #ifndef RecoPixelVertexing_PixelTrackFitting_interface_RiemannFit_h #define RecoPixelVertexing_PixelTrackFitting_interface_RiemannFit_h -#include +#include "FitResult.h" -#include -#include -#include - -#include "HeterogeneousCore/CUDAUtilities/interface/cuda_assert.h" - -#ifndef RFIT_DEBUG -#define RFIT_DEBUG 0 -#endif // RFIT_DEBUG namespace Rfit { -using namespace Eigen; - -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 MatrixNd = Eigen::Matrix; -using ArrayNd = Eigen::Array; -using Matrix2Nd = Eigen::Matrix; -using Matrix3Nd = Eigen::Matrix; -using Matrix2xNd = Eigen::Matrix; -using Array2xNd = Eigen::Array; -using Matrix3xNd = Eigen::Matrix; -using MatrixNx3d = Eigen::Matrix; -using MatrixNx5d = Eigen::Matrix; -using VectorNd = Eigen::Matrix; -using Vector2Nd = Eigen::Matrix; -using Vector3Nd = Eigen::Matrix; -using RowVectorNd = Eigen::Matrix; -using RowVector2Nd = Eigen::Matrix; -using Matrix5d = Eigen::Matrix; -using Matrix6d = Eigen::Matrix; -using Vector5d = 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)| - */ - int64_t q; //!< particle charge - double 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)| - */ - double chi2_circle = 0.0; - double chi2_line = 0.0; - Vector4d fast_fit; - int64_t q; //!< particle charge - // VectorXd time; // TO FIX just for profiling -} __attribute__((aligned(16))); template __host__ __device__ void printIt(C* m, const char* prefix = "") { -#if RFIT_DEBUG +#ifdef RFIT_DEBUG for (u_int r = 0; r < m->rows(); ++r) { for (u_int c = 0; c < m->cols(); ++c) @@ -107,10 +33,8 @@ __host__ __device__ inline T sqr(const T 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. */ @@ -119,158 +43,234 @@ __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= 2.743); - if (in_forward) - radlen_eff = X_forward / std::abs(cos(theta)); - assert(radlen_eff > 0.); - double p_t = fast_fit(2) * B; - // We have also to correct the radiation lenght in the x-y plane. Since we - // do not know the angle of incidence of the track at this point, we - // arbitrarily set the correction proportional to the inverse of the - // transerse momentum. The cut-off is at 1 Gev, set using a single Muon Pt - // gun and verifying that, at that momentum, not additional correction is, - // in fact, needed. This is an approximation. - if (std::abs(p_t/1.) < 1.) - radlen_eff /= std::abs(p_t/1.); + + template +__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. */ -__host__ __device__ inline MatrixNd Scatter_cov_line(Matrix2Nd& cov_sz, - const Vector4d& fast_fit, - VectorNd const& s_arcs, - VectorNd const& z_values, - const double B) + + 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) { -#if RFIT_DEBUG +#ifdef RFIT_DEBUG Rfit::printIt(&s_arcs, "Scatter_cov_line - s_arcs: "); #endif - u_int n = s_arcs.rows(); - double p_t = fast_fit(2) * B; + 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))); - double radlen_eff = 0.; - double theta = 0.; - bool in_forward = false; - computeRadLenEff(fast_fit, B, radlen_eff, theta, in_forward); - - const double sig2 = .000225 / p_2 * sqr(1 + 0.038 * log(radlen_eff)) * radlen_eff; + 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 l = k; l < n; ++l) + { + for (u_int i = 0; i < std::min(k, l); ++i) { - for (u_int i = 0; i < std::min(k, l); ++i) - { -#if RFIT_DEBUG - printf("Scatter_cov_line - B: %f\n", B); - printf("Scatter_cov_line - radlen_eff: %f, p_t: %f, p2: %f\n", radlen_eff, p_t, p_2); - printf("Scatter_cov_line - sig2:%f, theta: %f\n", sig2, theta); - printf("Scatter_cov_line - Adding to element %d, %d value %f\n", n + k, n + l, (s_arcs(k) - s_arcs(i)) * (s_arcs(l) - s_arcs(i)) * sig2 / sqr(sqr(sin(theta)))); -#endif - if (in_forward) { - cov_sz(k, l) += (z_values(k) - z_values(i)) * (z_values(l) - z_values(i)) * sig2 / sqr(sqr(cos(theta))); - cov_sz(l, k) = cov_sz(k, l); - } else { - cov_sz(n + k, n + l) += (s_arcs(k) - s_arcs(i)) * (s_arcs(l) - s_arcs(i)) * sig2 / sqr(sqr(sin(theta))); - cov_sz(n + l, n + k) = cov_sz(n + k, n + l); - } - } + 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); + } } -#if RFIT_DEBUG - Rfit::printIt(&cov_sz, "Scatter_cov_line - cov_sz: "); -#endif - Matrix2Nd rot = MatrixXd::Zero(2 * n, 2 * n); - for (u_int i = 0; i < n; ++i) { - rot(i, i) = cos(theta); - rot(n + i, n + i) = cos(theta); - u_int j = (i + n); - // Signs seem to be wrong for the off-diagonal element, but we are - // inverting x-y in the input vector, since theta is the angle between - // the z axis and the line, and we are putting the s values, which are Y, - // in the first position. A simple sign flip will take care of it. - rot(i, j) = i < j ? sin(theta) : -sin(theta); - } - -#if RFIT_DEBUG - Rfit::printIt(&rot, "Scatter_cov_line - rot: "); -#endif - - Matrix2Nd tmp = rot*cov_sz*rot.transpose(); - // We are interested only in the errors in the rotated s -axis which, in - // our formalism, are in the upper square matrix. -#if RFIT_DEBUG + // 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 - return tmp.block(0, 0, n, n); + 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). - \bug currently works only for points in the barrel. - \details Only the tangential component is computed (the radial one is negligible). - */ -// X in input TO FIX -__host__ __device__ inline MatrixNd Scatter_cov_rad(const Matrix2xNd& p2D, - const Vector4d& fast_fit, - VectorNd const& rad, - double B) + template + __host__ __device__ inline MatrixNd Scatter_cov_rad(const M2xN& p2D, + const V4& fast_fit, + VectorNd const& rad, + double B) { - u_int n = p2D.cols(); - double p_t = fast_fit(2) * 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 radlen_eff = 0.; - double theta = 0.; - bool in_forward = false; - computeRadLenEff(fast_fit, B, radlen_eff, theta, in_forward); + 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)); - MatrixNd scatter_cov_rad = MatrixXd::Zero(n, n); - const double sig2 = .000225 / p_2 * sqr(1 + 0.038 * log(radlen_eff)) * radlen_eff; + // 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) { - if (in_forward) { - scatter_cov_rad(k, l) += (rad(k) - rad(i)) * (rad(l) - rad(i)) * sig2 / sqr(cos(theta)); - } else { - scatter_cov_rad(k, l) += (rad(k) - rad(i)) * (rad(l) - rad(i)) * sig2 / sqr(sin(theta)); - } - scatter_cov_rad(l, k) = scatter_cov_rad(k, l); + 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); } } -#if RFIT_DEBUG +#ifdef RFIT_DEBUG Rfit::printIt(&scatter_cov_rad, "Scatter_cov_rad - scatter_cov_rad: "); #endif return scatter_cov_rad; @@ -279,24 +279,23 @@ __host__ __device__ inline MatrixNd Scatter_cov_rad(const Matrix2xNd& p2D, /*! \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. */ -__host__ __device__ inline Matrix2Nd cov_radtocart(const Matrix2xNd& p2D, - const MatrixNd& cov_rad, - const VectorNd& rad) + template + __host__ __device__ inline Matrix2Nd cov_radtocart(const M2xN& p2D, + const MatrixNd& cov_rad, + const VectorNd& rad) { -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("Address of p2D: %p\n", &p2D); #endif printIt(&p2D, "cov_radtocart - p2D:"); u_int n = p2D.cols(); - Matrix2Nd cov_cart = MatrixXd::Zero(2 * n, 2 * n); - VectorNd rad_inv = rad.cwiseInverse(); + 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) { @@ -306,7 +305,6 @@ __host__ __device__ inline Matrix2Nd cov_radtocart(const Matrix2xNd& p2D, 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); @@ -321,29 +319,27 @@ __host__ __device__ inline Matrix2Nd cov_radtocart(const Matrix2xNd& p2D, 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. */ -__host__ __device__ inline MatrixNd cov_carttorad(const Matrix2xNd& p2D, - const Matrix2Nd& cov_cart, - const VectorNd& rad) + template + __host__ __device__ inline VectorNd cov_carttorad(const M2xN& p2D, + const Matrix2Nd& cov_cart, + const VectorNd& rad) { u_int n = p2D.cols(); - MatrixNd cov_rad = MatrixXd::Zero(n, n); - const VectorNd rad_inv2 = rad.cwiseInverse().array().square(); + 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, i) = cov_cart(i, i); + cov_rad(i) = cov_cart(i, i); else { - cov_rad(i, 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)); + 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; @@ -354,28 +350,25 @@ __host__ __device__ inline MatrixNd cov_carttorad(const Matrix2xNd& p2D, 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. - */ - -__host__ __device__ inline MatrixNd cov_carttorad_prefit(const Matrix2xNd& p2D, const Matrix2Nd& cov_cart, - const Vector4d& fast_fit, - const VectorNd& rad) +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(); - MatrixNd cov_rad = MatrixXd::Zero(n, n); + 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, i) = cov_cart(i, i); // TO FIX + cov_rad(i) = cov_cart(i, i); // TO FIX else { Vector2d a = p2D.col(i); @@ -384,7 +377,7 @@ __host__ __device__ inline MatrixNd cov_carttorad_prefit(const Matrix2xNd& p2D, const double y2 = cross2D(a, b); const double tan_c = -y2 / x2; const double tan_c2 = sqr(tan_c); - cov_rad(i, 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); + 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; @@ -394,50 +387,29 @@ __host__ __device__ inline MatrixNd cov_carttorad_prefit(const Matrix2xNd& p2D, \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. */ -__host__ __device__ inline VectorNd Weight_circle(const MatrixNd& cov_rad_inv) + template + __host__ __device__ inline VectorNd Weight_circle(const MatrixNd& cov_rad_inv) { return cov_rad_inv.colwise().sum().transpose(); } -/*! - \brief Compute the points' weights' vector for the line fit (ODR). - Results from a pre-fit is needed in order to take the orthogonal (to the - line) component of the errors. - - \param x_err2 squared errors in the x axis. - \param y_err2 squared errors in the y axis. - \param tan_theta tangent of theta (angle between y axis and line). - - \return weight points' weights' vector for the line fit (ODR). -*/ - -__host__ __device__ inline VectorNd Weight_line(const ArrayNd& x_err2, const ArrayNd& y_err2, const double& tan_theta) -{ - return (1. + sqr(tan_theta)) * 1. / (x_err2 + y_err2 * sqr(tan_theta)); -} - /*! \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. */ - -__host__ __device__ inline int64_t Charge(const Matrix2xNd& p2D, const Vector3d& par_uvr) +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; } @@ -445,7 +417,6 @@ __host__ __device__ inline int64_t Charge(const Matrix2xNd& p2D, const Vector3d& /*! \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. @@ -464,56 +435,20 @@ __host__ __device__ inline void par_uvrtopak(circle_fit& circle, const double B, 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; + 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 error propagation to obtain the square errors in the - x axis for the line fit. If errors have not been computed in the circle fit - than an'approximation is made. - Further information in attached documentation. - - \param V hits' covariance matrix. - \param circle result of the previous circle fit (only the covariance matrix - is needed) TO FIX - \param J Jacobian of the transformation producing x values. - \param error flag for error computation. - - \return x_err2 squared errors in the x axis. -*/ - -__host__ __device__ inline VectorNd X_err2(const Matrix3Nd& V, const circle_fit& circle, const MatrixNx5d& J, - const bool error, u_int n) -{ - VectorNd x_err2(n); - for (u_int i = 0; i < n; ++i) - { - Matrix5d Cov = MatrixXd::Zero(5, 5); - if (error) - Cov.block(0, 0, 3, 3) = circle.cov; - Cov(3, 3) = V(i, i); - Cov(4, 4) = V(i + n, i + n); - Cov(3, 4) = Cov(4, 3) = V(i, i + n); - Eigen::Matrix tmp; - tmp = J.row(i) * Cov * J.row(i).transpose().eval(); - x_err2(i) = tmp(0, 0); - } - return x_err2; -} - /*! \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 @@ -521,19 +456,18 @@ __host__ __device__ inline VectorNd X_err2(const Matrix3Nd& V, const circle_fit& 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) { -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("min_eigen3D - enter\n"); #endif - SelfAdjointEigenSolver solver(3); + Eigen::SelfAdjointEigenSolver solver(3); solver.computeDirect(A); int min_index; chi2 = solver.eigenvalues().minCoeff(&min_index); -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("min_eigen3D - exit\n"); #endif return solver.eigenvectors().col(min_index); @@ -542,12 +476,9 @@ __host__ __device__ inline Vector3d min_eigen3D(const Matrix3d& A, double& chi2) /*! \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. @@ -555,7 +486,7 @@ __host__ __device__ inline Vector3d min_eigen3D(const Matrix3d& A, double& chi2) __host__ __device__ inline Vector3d min_eigen3D_fast(const Matrix3d& A) { - SelfAdjointEigenSolver solver(3); + Eigen::SelfAdjointEigenSolver solver(3); solver.computeDirect(A.cast()); int min_index; solver.eigenvalues().minCoeff(&min_index); @@ -564,12 +495,9 @@ __host__ __device__ inline Vector3d min_eigen3D_fast(const Matrix3d& A) /*! \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. @@ -577,7 +505,7 @@ __host__ __device__ inline Vector3d min_eigen3D_fast(const Matrix3d& A) __host__ __device__ inline Vector2d min_eigen2D(const Matrix2d& A, double& chi2) { - SelfAdjointEigenSolver solver(2); + Eigen::SelfAdjointEigenSolver solver(2); solver.computeDirect(A); int min_index; chi2 = solver.eigenvalues().minCoeff(&min_index); @@ -587,23 +515,19 @@ __host__ __device__ inline Vector2d min_eigen2D(const Matrix2d& A, double& chi2) /*! \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. */ -__host__ __device__ inline Vector4d Fast_fit(const Matrix3xNd& hits) +template +__host__ __device__ inline void Fast_fit(const M3xN& hits, V4 & result) { - Vector4d result; u_int n = hits.cols(); // get the number of hits printIt(&hits, "Fast_fit - hits: "); @@ -614,35 +538,26 @@ __host__ __device__ inline Vector4d Fast_fit(const Matrix3xNd& hits) printIt(&b, "Fast_fit - b: "); printIt(&c, "Fast_fit - c: "); // Compute their lengths - const double b2 = b.squaredNorm(); - const double c2 = c.squaredNorm(); - double X0; - double Y0; + 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 - if (abs(b.x()) > abs(b.y())) - { //!< in case b.x is 0 (2 hits with same x) - const double k = c.x() / b.x(); - const double div = 2. * (k * b.y() - c.y()); - // if aligned TO FIX - Y0 = (k * b2 - c2) / div; - X0 = b2 / (2 * b.x()) - b.y() / b.x() * Y0; - } - else - { - const double k = c.y() / b.y(); - const double div = 2. * (k * b.x() - c.x()); - // if aligned TO FIX - X0 = (k * b2 - c2) / div; - Y0 = b2 / (2 * b.y()) - b.x() / b.y() * X0; - } - - result(0) = X0 + hits(0, 0); - result(1) = Y0 + hits(1, 0); + 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: "); @@ -652,23 +567,21 @@ __host__ __device__ inline Vector4d Fast_fit(const Matrix3xNd& hits) 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) ) - const double dr = result(2) * atan2(cross2D(d, e), d.dot(e)); + auto dr = result(2) * atan2(cross2D(d, e), d.dot(e)); // Simple difference in Z between last and first hit - const double dz = hits(2, n - 1) - hits(2, 0); + auto dz = hits(2, n - 1) - hits(2, 0); result(3) = (dr / dz); -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("Fast_fit: [%f, %f, %f, %f]\n", result(0), result(1), result(2), result(3)); #endif - return result; } /*! \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)). @@ -676,92 +589,80 @@ __host__ __device__ inline Vector4d Fast_fit(const Matrix3xNd& hits) \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. */ - -__host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, - const Matrix2Nd& hits_cov2D, - const Vector4d& fast_fit, - const VectorNd& rad, +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 = true) + const bool error) { -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - enter\n"); #endif // INITIALIZATION - Matrix2Nd V = hits_cov2D; + Matrix2Nd V = hits_cov2D; u_int n = hits2D.cols(); printIt(&hits2D, "circle_fit - hits2D:"); printIt(&hits_cov2D, "circle_fit - hits_cov2D:"); -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - WEIGHT COMPUTATION\n"); #endif // WEIGHT COMPUTATION - VectorNd weight; - MatrixNd G; + VectorNd weight; + MatrixNd G; double renorm; { - MatrixNd cov_rad; - cov_rad = cov_carttorad_prefit(hits2D, V, fast_fit, rad); - printIt(&cov_rad, "circle_fit - cov_rad:"); - // cov_rad = cov_carttorad(hits2D, V); - - MatrixNd scatter_cov_rad = Scatter_cov_rad(hits2D, fast_fit, rad, B); + 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:"); -#if RFIT_DEBUG +#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:"); - Matrix4d cov_rad4 = cov_rad; - Matrix4d G4; - G4 = cov_rad4.inverse(); - printIt(&G4, "circle_fit - G4:"); - renorm = G4.sum(); - G4 *= 1. / renorm; - printIt(&G4, "circle_fit - G4:"); - G = G4; + G = cov_rad.inverse(); + renorm = G.sum(); + G *= 1. / renorm; weight = Weight_circle(G); } printIt(&weight, "circle_fit - weight:"); // SPACE TRANSFORMATION -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - SPACE TRANSFORMATION\n"); #endif // center -#if RFIT_DEBUG +#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(3, n); + Matrix3xNd p3D; p3D.block(0, 0, 2, n) = hits2D.colwise() - h_; printIt(&p3D, "circle_fit - p3D: a)"); - Vector2Nd mc(2 * n); // centered hits, used in error computation + Vector2Nd mc; // centered hits, used in error computation mc << p3D.row(0).transpose(), p3D.row(1).transpose(); printIt(&mc, "circle_fit - mc(centered hits):"); @@ -774,25 +675,24 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, p3D.row(2) = p3D.block(0, 0, 2, n).colwise().squaredNorm(); printIt(&p3D, "circle_fit - p3D: b)"); -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - COST FUNCTION\n"); #endif // COST FUNCTION // compute - Matrix3d A = Matrix3d::Zero(); - const Vector3d r0 = p3D * weight; // center of gravity - const Matrix3xNd X = p3D.colwise() - r0; - A = X * G * X.transpose(); + 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:"); -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - MINIMIZE\n"); #endif // minimize double chi2; Vector3d v = min_eigen3D(A, chi2); -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - AFTER MIN_EIGEN\n"); #endif printIt(&v, "v BEFORE INVERSION"); @@ -800,21 +700,21 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, 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. -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - AFTER MIN_EIGEN 1\n"); #endif - Matrix cm; -#if RFIT_DEBUG + Eigen::Matrix cm; +#ifdef RFIT_DEBUG printf("circle_fit - AFTER MIN_EIGEN 2\n"); #endif cm = -v.transpose() * r0; -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - AFTER MIN_EIGEN 3\n"); #endif const double c = cm(0, 0); // const double c = -v.transpose() * r0; -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - COMPUTE CIRCLE PARAMETER\n"); #endif // COMPUTE CIRCLE PARAMETER @@ -832,57 +732,60 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, 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:"); -#if RFIT_DEBUG - printf("circle_fit - CIRCLE CHARGE: %ld\n", circle.q); +#ifdef RFIT_DEBUG + printf("circle_fit - CIRCLE CHARGE: %d\n", circle.q); #endif -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - ERROR PROPAGATION\n"); #endif // ERROR PROPAGATION if (error) { -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - ERROR PRPAGATION ACTIVATED\n"); #endif - ArrayNd Vcs_[2][2]; // cov matrix of center & scaled points -#if RFIT_DEBUG + 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 { - Matrix cm; - Matrix cm2; + Eigen::Matrix cm; + Eigen::Matrix cm2; cm = mc.transpose() * V * mc; - // cm2 = mc * mc.transpose(); const double c = cm(0, 0); - // const double c2 = cm2(0,0); - const Matrix2Nd Vcs = sqr(s) * V + sqr(sqr(s)) * 1. / (4. * q * n) * - (2. * V.squaredNorm() + 4. * c) * // mc.transpose() * V * mc) * - mc * mc.transpose(); + 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:"); - Vcs_[0][0] = Vcs.block(0, 0, n, n); + C[0][0] = Vcs.block(0, 0, n, n). template selfadjointView(); Vcs_[0][1] = Vcs.block(0, n, n, n); - Vcs_[1][1] = Vcs.block(n, 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:"); } - MatrixNd C[3][3]; // cov matrix of 3D transformed points { - 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(); - C[0][0] = Vcs_[0][0]; + 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); - C[1][1] = Vcs_[1][1]; + Vcs_[1][1] = C[1][1]; C[1][2] = 2. * (Vcs_[1][0] * t0 + Vcs_[1][1] * t1); - C[2][2] = 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); + 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]:"); @@ -891,7 +794,7 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, { for (u_int j = i; j < 3; ++j) { - Matrix tmp; + 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; @@ -900,14 +803,14 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, } 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(); + 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) + 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); @@ -924,14 +827,16 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, 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); + VectorNd t0(n); + VectorNd t1(n); if (l == k) { t0 = 2. * D_[j][l] * s_v.col(l); @@ -951,14 +856,14 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, if (i == j) { - Matrix cm; + Eigen::Matrix cm; cm = s_v.col(i).transpose() * (t0 + t1); const double c = cm(0, 0); E(a, b) = 0. + c; } else { - Matrix cm; + 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); @@ -969,7 +874,8 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, } printIt(&E, "circle_fit - E:"); - Matrix J2; // Jacobian of min_eigen() (numerically computed) + 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]; @@ -988,9 +894,8 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, Cvc.block(0, 0, 3, 3) = t0; Cvc.block(0, 3, 3, 1) = t1; Cvc.block(3, 0, 1, 3) = t1.transpose(); - Matrix cm1; - // Matrix cm2; - Matrix cm3; + Eigen::Matrix cm1; + Eigen::Matrix cm3; cm1 = (v.transpose() * C0 * v); // cm2 = (C0.cwiseProduct(t0)).sum(); cm3 = (r0.transpose() * t0 * r0); @@ -1000,15 +905,15 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, } printIt(&Cvc, "circle_fit - Cvc:"); - Matrix J3; // Jacobian (v0,v1,v2,c)->(X0,Y0,R) + 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, - 0, 0, -h * sqr(v2x2_inv) * 2. - (2. * c + v(2)) * v2x2_inv * t, -t; + 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) + 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) @@ -1018,231 +923,190 @@ __host__ __device__ inline circle_fit Circle_fit(const Matrix2xNd& hits2D, } printIt(&circle.cov, "Circle cov:"); -#if RFIT_DEBUG +#ifdef RFIT_DEBUG printf("circle_fit - exit\n"); #endif return circle; } -/*! - \brief Fit of helix parameter cotan(theta)) and Zip by projection on the - pre-fitted cylinder and line fit on its surface. - - \param hits hits coordinates. - \param hits_cov covariance matrix of the hits. - \param circle cylinder parameter, their covariance (if computed, otherwise - uninitialized) and particle charge. - \param fast_fit result of the previous fast fit in this form: - (X0,Y0,R,cotan(theta))). - \param error flag for error computation. - - \return line line_fit: - -par parameter of the line in this form: (cotan(theta)), Zip); \n - -cov covariance matrix of the fitted parameter; \n - -chi2. - - \warning correlation between R and z are neglected, this could be relevant - if geometry detector provides sloped modules in the R/z plane. - - \bug chi2 and errors could be slightly underestimated for small eta (<0.2) - when pt is small (<0.3 Gev/c). - - \todo multiple scattering treatment. - - \details Line fit is made by orthogonal distance regression where - correlation between coordinates in the transverse plane (x,y) and z are - neglected (for a barrel + endcap geometry this is a very good - approximation). - Covariance matrix of the fitted parameter is optionally computed. - Multiple scattering is not handled (yet). - A fast pre-fit is performed in order to evaluate weights and to compute - errors. -*/ -__host__ __device__ inline line_fit Line_fit(const Matrix3xNd& hits, - const Matrix3Nd& hits_cov, - const circle_fit& circle, - const Vector4d& fast_fit, - const double B, - const bool error = true) -{ - u_int n = hits.cols(); - // PROJECTION ON THE CILINDER - Matrix2xNd p2D(2, n); - MatrixNx5d Jx(n, 5); - -#if RFIT_DEBUG - printf("Line_fit - B: %g\n", B); +/*! \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. + */ - printIt(&hits, "Line_fit points: "); - printIt(&hits_cov, "Line_fit covs: "); + 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 - for (u_int i = 0; i < n; ++i) - { // x - 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(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.row(i) << d_X0, d_Y0, d_R, d_x, d_y; + // 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_; } - // Math of d_{X0,Y0,R,x,y} all verified by hand - - // y - p2D.row(1) = hits.row(2); - - // WEIGHT COMPUTATION - Matrix2Nd cov_sz = MatrixXd::Zero(2 * n, 2 * n); - VectorNd x_err2 = X_err2(hits_cov, circle, Jx, error, n); - VectorNd y_err2 = hits_cov.block(2 * n, 2 * n, n, n).diagonal(); - cov_sz.block(0, 0, n, n) = x_err2.asDiagonal(); - cov_sz.block(n, n, n, n) = y_err2.asDiagonal(); -#if RFIT_DEBUG - printIt(&cov_sz, "line_fit - cov_sz:"); -#endif - MatrixNd cov_with_ms = Scatter_cov_line(cov_sz, fast_fit, p2D.row(0), p2D.row(1), B); -#if RFIT_DEBUG - printIt(&cov_with_ms, "line_fit - cov_with_ms: "); -#endif - Matrix4d G, G4; - G4 = cov_with_ms.inverse(); -#if RFIT_DEBUG - printIt(&G4, "line_fit - cov_with_ms.inverse():"); + 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 - double renorm = G4.sum(); - G4 *= 1. / renorm; -#if RFIT_DEBUG - printIt(&G4, "line_fit - G4:"); -#endif - G = G4; - const VectorNd weight = Weight_circle(G); - - VectorNd err2_inv = cov_with_ms.diagonal(); - err2_inv = err2_inv.cwiseInverse(); -// const VectorNd err2_inv = Weight_line(x_err2, y_err2, fast_fit(3)); -// const VectorNd weight = err2_inv * 1. / err2_inv.sum(); + // Rotate Points with the shape [2, n] + Matrix2xNd p2D_rot = rot*p2D; -#if RFIT_DEBUG - printIt(&x_err2, "Line_fit - x_err2: "); - printIt(&y_err2, "Line_fit - y_err2: "); - printIt(&err2_inv, "Line_fit - err2_inv: "); - printIt(&weight, "Line_fit - weight: "); +#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 - // COST FUNCTION + // Build the A Matrix + Matrix2xNd A; + A << MatrixXd::Ones(1, n), p2D_rot.row(0); // rotated s values - // compute - // r0 represents the weighted mean of "x" and "y". - const Vector2d r0 = p2D * weight; - // This is the X vector that will be used to build the - // scatter matrix S = X^T * X - const Matrix2xNd X = p2D.colwise() - r0; - Matrix2d A = Matrix2d::Zero(); - A = X * G * X.transpose(); -// for (u_int i = 0; i < n; ++i) -// { -// A += err2_inv(i) * (X.col(i) * X.col(i).transpose()); -// } - -#if RFIT_DEBUG - printIt(&A, "Line_fit - A: "); +#ifdef RFIT_DEBUG + printIt(&A, "A Matrix:"); #endif - // minimize - double chi2; - Vector2d v = min_eigen2D(A, chi2); -#if RFIT_DEBUG - printIt(&v, "Line_fit - v: "); - printf("Line_fit chi2: %e\n", chi2); -#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(); - // n *= (chi2>0) ? 1 : -1; //TO FIX - // This hack to be able to run on GPU where the automatic assignment to a - // double from the vector multiplication is not working. - Matrix cm; - cm = -v.transpose() * r0; - const double c = cm(0, 0); + // Compute the Covariance Matrix of the fit parameters + Eigen::Matrix Cov_params = Inv_Cov.inverse(); - // COMPUTE LINE PARAMETER - line_fit line; - line.par << -v(0) / v(1), // cotan(theta)) - -c * sqrt(sqr(v(0)) + sqr(v(1))) * 1. / v(1); // Zip - line.chi2 = abs(chi2); -#if RFIT_DEBUG - printIt(&(line.par), "Line_fit - line.par: "); - printf("Line_fit - v norm: %e\n", sqrt(v(0)*v(0) + v(1)*v(1))); -#endif + // 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(); - // ERROR PROPAGATION - if (error) - { - const double v0_2 = sqr(v(0)); - const double v1_2 = sqr(v(1)); - Matrix3d C; // cov(v,c) - { - // The norm is taken from Chernov, properly adapted to the weights case. - double norm = v.transpose() * A * v; - norm /= weight.sum(); -#if RFIT_DEBUG - printf("Line_fit - norm: %e\n", norm); -#endif - const double sig2 = 1. / (A(0, 0) + A(1, 1)) * norm; - C(0, 0) = sig2 * v1_2; - C(1, 1) = sig2 * v0_2; - C(0, 1) = C(1, 0) = -sig2 * v(0) * v(1); - const VectorNd weight_2 = (weight).array().square(); - const Vector2d C0(weight_2.dot(x_err2), weight_2.dot(y_err2)); - C.block(0, 2, 2, 1) = C.block(2, 0, 1, 2).transpose() = -C.block(0, 0, 2, 2) * r0; - Matrix tmp = (r0.transpose() * C.block(0, 0, 2, 2) * r0); - C(2, 2) = v0_2 * C0(0) + v1_2 * C0(1) + C0(0) * C(0, 0) + C0(1) * C(1, 1) + tmp(0, 0); - } -#if RFIT_DEBUG - printIt(&C, "line_fit - C:"); +#ifdef RFIT_DEBUG + printIt(&sol, "Rotated solutions:"); #endif - Matrix J; // Jacobian of (v,c) -> (cotan(theta)),Zip) - { - const double t0 = 1. / v(1); - const double t1 = sqr(t0); - const double sqrt_ = sqrt(v1_2 + v0_2); - const double t2 = 1. / sqrt_; - J << -t0, v(0) * t1, 0, -c * v(0) * t0 * t2, v0_2 * c * t1 * t2, -sqrt_ * t0; - } - Matrix JT = J.transpose().eval(); -#if RFIT_DEBUG - printIt(&J, "line_fit - J:"); + // 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 - line.cov = J * C * JT; - } -#if RFIT_DEBUG - printIt(&line.cov, "Line cov:"); -#endif - return line; + return line; } /*! @@ -1253,14 +1117,11 @@ __host__ __device__ inline line_fit Line_fit(const Matrix3xNd& hits, -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 @@ -1272,31 +1133,31 @@ __host__ __device__ inline line_fit Line_fit(const Matrix3xNd& hits, |(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. */ -inline helix_fit Helix_fit(const Matrix3xNd& hits, const Matrix3Nd& hits_cov, const double B, - const bool error = true) +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 rad = (hits.block(0, 0, 2, n).colwise().norm()); + 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. - const Vector4d fast_fit = Fast_fit(hits); - + 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.block(0, 0, 2 * n, 2 * n), + hits_cov, fast_fit, rad, B, error); - line_fit line = Line_fit(hits, hits_cov, circle, fast_fit, B, error); + line_fit line = Line_fit(hits, hits_ge, circle, fast_fit, B, error); par_uvrtopak(circle, B, error); @@ -1317,4 +1178,4 @@ inline helix_fit Helix_fit(const Matrix3xNd& hits, const Matrix3Nd& hits_cov, co } // namespace Rfit -#endif // RecoPixelVertexing_PixelTrackFitting_interface_RiemannFit_h +#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 c549e05d69f55..d8177a0e9447c 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/BuildFile.xml +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/BuildFile.xml @@ -1,4 +1,7 @@ + + + diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.cc b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.cc index eadfda8cb6a26..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" @@ -59,62 +60,6 @@ void PixelTrackProducer::produce(edm::Event& ev, const edm::EventSetup& es) 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 6bc6d2815c8e7..7e0d5d73b03fc 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.h +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducer.h @@ -2,7 +2,6 @@ #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" @@ -22,7 +21,6 @@ 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; diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc index 6a3bf229cc67f..c05fa9172ffe3 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackProducerFromCUDA.cc @@ -1,4 +1,8 @@ -#include "FWCore/Framework/interface/global/EDProducer.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 "FWCore/Framework/interface/Event.h" #include "FWCore/Framework/interface/Frameworkfwd.h" #include "FWCore/Framework/interface/MakerMacros.h" @@ -6,39 +10,206 @@ #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 edm::global::EDProducer<> { +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 produce(edm::StreamID id, edm::Event& iEvent, const edm::EventSetup& iSetup) const override; + 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 enabled_; + bool enableConversion_; }; PixelTrackProducerFromCUDA::PixelTrackProducerFromCUDA(const edm::ParameterSet& iConfig): - srcToken_(consumes(iConfig.getParameter("src"))) + HeterogeneousEDProducer(iConfig), + tBeamSpot_(consumes(iConfig.getParameter("beamSpot"))), + gpuToken_(consumes(iConfig.getParameter("src"))), + enableConversion_ (iConfig.getParameter("gpuEnableConversion")) { - produces(); + 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::produce(edm::StreamID id, edm::Event& iEvent, const edm::EventSetup& iSetup) const { - iEvent.put(std::make_unique(0)); +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 index 68343253b670d..2a360b1da58a6 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cc +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cc @@ -75,8 +75,7 @@ void PixelTrackReconstructionGPU::run(TracksWithTTRHs& tracks, Rfit::helix_fit * helix_fit_resultsGPU = nullptr; const int points_in_seed = 4; - // We use 3 floats for GlobalPosition and 9 floats for GlobalError (that's what is used by the Riemann fit). - // TODO: optimise dimensions eploiting matrix symmetries + // 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)); @@ -86,15 +85,16 @@ void PixelTrackReconstructionGPU::run(TracksWithTTRHs& tracks, 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().matrix4D(); + 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()); - for (auto j = 0; j < 3; ++j) { - for (auto l = 0; l < 3; ++l) { - hits_and_covariances.push_back(errors(j, l)); - } - } + 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++; } diff --git a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu index 25b3bc300fbfb..19a91ba8c83b4 100644 --- a/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu +++ b/RecoPixelVertexing/PixelTrackFitting/plugins/PixelTrackReconstructionGPU.cu @@ -12,8 +12,8 @@ KernelFastFitAllHits(float *hits_and_covariances, int cumulative_size, float B, Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, + Rfit::Matrix3xNd<4> *hits, + Eigen::Matrix *hits_ge, Rfit::circle_fit *circle_fit, Vector4d *fast_fit, Rfit::line_fit *line_fit) @@ -36,8 +36,6 @@ KernelFastFitAllHits(float *hits_and_covariances, blockDim.x, blockIdx.x, threadIdx.x, start, cumulative_size); #endif - hits[helix_start].resize(3, hits_in_fit); - hits_cov[helix_start].resize(3 * hits_in_fit, 3 * hits_in_fit); // Prepare data structure (stack) for (unsigned int i = 0; i < hits_in_fit; ++i) { @@ -45,22 +43,20 @@ KernelFastFitAllHits(float *hits_and_covariances, hits_and_covariances[start + 1], hits_and_covariances[start + 2]; start += 3; - for (auto j = 0; j < 3; ++j) { - for (auto l = 0; l < 3; ++l) { - hits_cov[helix_start](i + j * hits_in_fit, i + l * hits_in_fit) = - hits_and_covariances[start]; - start++; - } - } + 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; } - fast_fit[helix_start] = Rfit::Fast_fit(hits[helix_start]); + 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 *hits, Rfit::Matrix3Nd *hits_cov, + Rfit::Matrix3xNd<4> *hits, Eigen::Matrix *hits_ge, Rfit::circle_fit *circle_fit, Vector4d *fast_fit, Rfit::line_fit *line_fit) { @@ -84,11 +80,14 @@ KernelCircleFitAllHits(float *hits_and_covariances, int hits_in_fit, #endif u_int n = hits[helix_start].cols(); - Rfit::VectorNd rad = (hits[helix_start].block(0, 0, 2, n).colwise().norm()); + 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[helix_start].block(0, 0, 2 * n, 2 * n), + hits_cov, fast_fit[helix_start], rad, B, true); #ifdef GPU_DEBUG @@ -105,7 +104,7 @@ KernelCircleFitAllHits(float *hits_and_covariances, int hits_in_fit, __global__ void KernelLineFitAllHits(float *hits_and_covariances, int hits_in_fit, int cumulative_size, float B, Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, Rfit::Matrix3Nd *hits_cov, + Rfit::Matrix3xNd<4> *hits, Eigen::Matrix *hits_ge, Rfit::circle_fit *circle_fit, Vector4d *fast_fit, Rfit::line_fit *line_fit) { @@ -130,7 +129,7 @@ KernelLineFitAllHits(float *hits_and_covariances, int hits_in_fit, #endif line_fit[helix_start] = - Rfit::Line_fit(hits[helix_start], hits_cov[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); @@ -166,13 +165,13 @@ void PixelTrackReconstructionGPU::launchKernelFit( int num_blocks = cumulative_size / (hits_in_fit * 12) / threads_per_block.x + 1; auto numberOfSeeds = cumulative_size / (hits_in_fit * 12); - Rfit::Matrix3xNd *hitsGPU; - cudaCheck(cudaMalloc(&hitsGPU, 48 * numberOfSeeds * sizeof(Rfit::Matrix3xNd(3, 4)))); - cudaCheck(cudaMemset(hitsGPU, 0x00, 48 * numberOfSeeds * sizeof(Rfit::Matrix3xNd(3, 4)))); + Rfit::Matrix3xNd<4> *hitsGPU; + cudaCheck(cudaMalloc(&hitsGPU, 48 * numberOfSeeds * sizeof(Rfit::Matrix3xNd<4>))); + cudaCheck(cudaMemset(hitsGPU, 0x00, 48 * numberOfSeeds * sizeof(Rfit::Matrix3xNd<4>))); - Rfit::Matrix3Nd *hits_covGPU = nullptr; - cudaCheck(cudaMalloc(&hits_covGPU, 48 * numberOfSeeds * sizeof(Rfit::Matrix3Nd(12, 12)))); - cudaCheck(cudaMemset(hits_covGPU, 0x00, 48 * numberOfSeeds * sizeof(Rfit::Matrix3Nd(12, 12)))); + 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))); @@ -188,24 +187,24 @@ void PixelTrackReconstructionGPU::launchKernelFit( KernelFastFitAllHits<<>>( hits_and_covariancesGPU, hits_in_fit, cumulative_size, B, results, - hitsGPU, hits_covGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, + 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_covGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, + 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_covGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, + hitsGPU, hits_geGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, line_fit_resultsGPU); cudaCheck(cudaGetLastError()); cudaFree(hitsGPU); - cudaFree(hits_covGPU); + cudaFree(hits_geGPU); cudaFree(fast_fit_resultsGPU); cudaFree(circle_fit_resultsGPU); cudaFree(line_fit_resultsGPU); 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 728b3fec47f39..e868ff1921965 100644 --- a/RecoPixelVertexing/PixelTrackFitting/python/PixelTracks_cff.py +++ b/RecoPixelVertexing/PixelTrackFitting/python/PixelTracks_cff.py @@ -50,7 +50,10 @@ doublets = "pixelTracksHitDoublets", 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 @@ -67,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, diff --git a/RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc b/RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc index dd1460adaf05a..45ee6266bcc31 100644 --- a/RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc +++ b/RecoPixelVertexing/PixelTrackFitting/src/PixelFitterByRiemannParaboloid.cc @@ -59,25 +59,20 @@ std::unique_ptr PixelFitterByRiemannParaboloid::run( isBarrel[i] = recHit->detUnit()->type().isBarrel(); } - Matrix riemannHits(3, nhits); + assert(nhits==4); + Rfit::Matrix3xNd<4> riemannHits; - Matrix riemannHits_cov = - MatrixXd::Zero(3 * nhits, 3 * nhits); + 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(); - const auto& errorMatrix = errors[i].matrix4D(); - - for (auto j = 0; j < 3; ++j) { - for (auto l = 0; l < 3; ++l) { - riemannHits_cov(i + j * nhits, i + l * nhits) = errorMatrix(j, l); - } - } + 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_cov, bField, useErrors_); + helix_fit fittedTrack = Rfit::Helix_fit(riemannHits, riemannHits_ge, bField, useErrors_); int iCharge = fittedTrack.q; // parameters are: diff --git a/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml b/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml index d6beb57b862b8..b4b5e3a335bcb 100644 --- a/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml +++ b/RecoPixelVertexing/PixelTrackFitting/test/BuildFile.xml @@ -1,3 +1,4 @@ + @@ -11,19 +12,48 @@ + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + diff --git a/RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc b/RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc index 77b5d1bebe6b6..adcabd7dde508 100644 --- a/RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc +++ b/RecoPixelVertexing/PixelTrackFitting/test/PixelTrackRiemannFit.cc @@ -5,8 +5,13 @@ #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; @@ -20,9 +25,10 @@ using Vector6d = Eigen::Matrix; using Vector8d = Eigen::Matrix; }; // namespace Rfit +// quadruplets... struct hits_gen { - Matrix3xNd hits; - Matrix3Nd hits_cov; + Matrix3xNd<4> hits; + Eigen::Matrix hits_ge; Vector5d true_par; }; @@ -66,30 +72,31 @@ void smearing(const Vector5d& err, const bool& isbarrel, double& x, double& y, d } } -void Hits_cov(Matrix3Nd& V, const unsigned int& i, const unsigned int& n, const Matrix3xNd& hits, +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(i, 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(i + n, i + n) = + V.col(i)[2] = (Rfit::sqr(err[1]) * Rfit::sqr(hits(0, i)) + Rfit::sqr(err[0]) * Rfit::sqr(hits(1, i))) / R2; - V(i, i + n) = V(i + n, i) = + V.col(i)[1] = (Rfit::sqr(err[0]) - Rfit::sqr(err[1])) * hits(1, i) * hits(0, i) / R2; - V(i + 2 * n, i + 2 * n) = Rfit::sqr(err[2]); + V.col(i)[5] = Rfit::sqr(err[2]); } else { - V(i, i) = Rfit::sqr(err[3]); - V(i + n, i + n) = Rfit::sqr(err[3]); - V(i + 2 * n, i + 2 * n) = Rfit::sqr(err[4]); + 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_cov = MatrixXd::Zero(3 * n, 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, @@ -123,7 +130,7 @@ hits_gen Hits_gen(const unsigned int& n, const Matrix& gen_par) { 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_cov, i, n, gen.hits, err, true); + Hits_cov(gen.hits_ge, i, n, gen.hits, err, true); } return gen; @@ -164,161 +171,265 @@ Matrix New_par(const Matrix& gen_par, const int& cha return new_par; } -void test_helix_fit() { +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_; - int iteration; - int debug2 = 0; bool return_err; const double B_field = 3.8 * c_speed / pow(10, 9) / 100; Matrix gen_par; Vector5d true_par; Vector5d err; -// while (1) { - generator.seed(1); - int debug = 0; - debug2 = 0; - 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; -// cin >> n_ >> gen_par(0) >> gen_par(1) >> gen_par(2) >> gen_par(3) >> gen_par(4) >> gen_par(5) >> -// iteration >> return_err >> debug2; - 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 - iteration = 1; - return_err = true; - debug2 = 1; - - iteration *= 10; - gen_par = New_par(gen_par, 1, B_field); - true_par = True_par(gen_par, 1, B_field); - Matrix3xNd hits; - Matrix3Nd hits_cov; - unique_ptr helix(new helix_fit[iteration]); -// helix_fit* helix = new helix_fit[iteration]; - Matrix score(41, iteration); - - for (int i = 0; i < iteration; i++) { - if (debug2 == 1 && i == (iteration - 1)) { - debug = 1; - } - 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; - helix[i] = Rfit::Helix_fit(gen.hits, gen.hits_cov, B_field, return_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; - if (debug) - cout << std::setprecision(10) - << "phi: " << helix[i].par(0) << " +/- " << sqrt(helix[i].cov(0, 0)) << " vs " - << true_par(0) << endl - << "Tip: " << helix[i].par(1) << " +/- " << sqrt(helix[i].cov(1, 1)) << " vs " - << true_par(1) << endl - << "p_t: " << helix[i].par(2) << " +/- " << sqrt(helix[i].cov(2, 2)) << " vs " - << true_par(2) << endl - << "theta:" << helix[i].par(3) << " +/- " << sqrt(helix[i].cov(3, 3)) << " vs " - << true_par(3) << endl - << "Zip: " << helix[i].par(4) << " +/- " << sqrt(helix[i].cov(4, 4)) << " vs " - << true_par(4) << endl - << "charge:" << helix[i].q << " vs 1" << endl - << "covariance matrix:" << endl - << helix[i].cov << endl - << "Initial hits:\n" << gen.hits << endl - << "Initial Covariance:\n" << gen.hits_cov << endl; - } + 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; - for (int x = 0; x < iteration; x++) { - // Compute PULLS information - score(0, x) = (helix[x].par(0) - true_par(0)) / sqrt(helix[x].cov(0, 0)); - score(1, x) = (helix[x].par(1) - true_par(1)) / sqrt(helix[x].cov(1, 1)); - score(2, x) = (helix[x].par(2) - true_par(2)) / sqrt(helix[x].cov(2, 2)); - score(3, x) = (helix[x].par(3) - true_par(3)) / sqrt(helix[x].cov(3, 3)); - score(4, x) = (helix[x].par(4) - true_par(4)) / sqrt(helix[x].cov(4, 4)); - score(5, x) = - (helix[x].par(0) - true_par(0)) * (helix[x].par(1) - true_par(1)) / (helix[x].cov(0, 1)); - score(6, x) = - (helix[x].par(0) - true_par(0)) * (helix[x].par(2) - true_par(2)) / (helix[x].cov(0, 2)); - score(7, x) = - (helix[x].par(1) - true_par(1)) * (helix[x].par(2) - true_par(2)) / (helix[x].cov(1, 2)); - score(8, x) = - (helix[x].par(3) - true_par(3)) * (helix[x].par(4) - true_par(4)) / (helix[x].cov(3, 4)); - score(9, x) = helix[x].chi2_circle; - score(25, x) = helix[x].chi2_line; - score(10, x) = sqrt(helix[x].cov(0, 0)) / helix[x].par(0) * 100; - score(13, x) = sqrt(helix[x].cov(3, 3)) / helix[x].par(3) * 100; - score(14, x) = sqrt(helix[x].cov(4, 4)) / helix[x].par(4) * 100; - score(15, x) = (helix[x].par(0) - true_par(0)) * (helix[x].par(3) - true_par(3)) / - sqrt(helix[x].cov(0, 0)) / sqrt(helix[x].cov(3, 3)); - score(16, x) = (helix[x].par(1) - true_par(1)) * (helix[x].par(3) - true_par(3)) / - sqrt(helix[x].cov(1, 1)) / sqrt(helix[x].cov(3, 3)); - score(17, x) = (helix[x].par(2) - true_par(2)) * (helix[x].par(3) - true_par(3)) / - sqrt(helix[x].cov(2, 2)) / sqrt(helix[x].cov(3, 3)); - score(18, x) = (helix[x].par(0) - true_par(0)) * (helix[x].par(4) - true_par(4)) / - sqrt(helix[x].cov(0, 0)) / sqrt(helix[x].cov(4, 4)); - score(19, x) = (helix[x].par(1) - true_par(1)) * (helix[x].par(4) - true_par(4)) / - sqrt(helix[x].cov(1, 1)) / sqrt(helix[x].cov(4, 4)); - score(20, x) = (helix[x].par(2) - true_par(2)) * (helix[x].par(4) - true_par(4)) / - sqrt(helix[x].cov(2, 2)) / sqrt(helix[x].cov(4, 4)); - score(21, x) = (helix[x].par(0) - true_par(0)) * (helix[x].par(1) - true_par(1)) / - sqrt(helix[x].cov(0, 0)) / sqrt(helix[x].cov(1, 1)); - score(22, x) = (helix[x].par(0) - true_par(0)) * (helix[x].par(2) - true_par(2)) / - sqrt(helix[x].cov(0, 0)) / sqrt(helix[x].cov(2, 2)); - score(23, x) = (helix[x].par(1) - true_par(1)) * (helix[x].par(2) - true_par(2)) / - sqrt(helix[x].cov(1, 1)) / sqrt(helix[x].cov(2, 2)); - score(24, x) = (helix[x].par(3) - true_par(3)) * (helix[x].par(4) - true_par(4)) / - sqrt(helix[x].cov(3, 3)) / sqrt(helix[x].cov(4, 4)); - } + 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; - 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(); - 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.; +// helixBrokenLine_fit[i] = BrokenLine::Helix_fit(gen.hits, gen.hits_cov, B_field); - cout << "\nPULLS:\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; -// } + 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() { - test_helix_fit(); +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 index 7b1125eebc312..a60eeda935d79 100644 --- a/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPU.cu +++ b/RecoPixelVertexing/PixelTrackFitting/test/testEigenGPU.cu @@ -3,76 +3,61 @@ #include #include + #include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" -#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" #include "test_common.h" +#include "HeterogeneousCore/CUDAUtilities/interface/cudaCheck.h" using namespace Eigen; -__global__ -void kernelFullFit(Rfit::Matrix3xNd * hits, - Rfit::Matrix3Nd * hits_cov, - double B, - bool errors, - Rfit::circle_fit * circle_fit_resultsGPU, - Rfit::line_fit * line_fit_resultsGPU) { - - printf("hits size: %d,%d\n", hits->rows(), hits->cols()); - Rfit::printIt(hits, "KernelFulFit - input hits: "); - Vector4d fast_fit = Rfit::Fast_fit(*hits); - - u_int n = hits->cols(); - Rfit::VectorNd rad = (hits->block(0, 0, 2, n).colwise().norm()); - - Rfit::Matrix2xNd hits2D_local = (hits->block(0,0,2,n)).eval(); - Rfit::Matrix2Nd hits_cov2D_local = (hits_cov->block(0, 0, 2 * n, 2 * n)).eval(); - Rfit::printIt(&hits2D_local, "kernelFullFit - hits2D_local: "); - Rfit::printIt(&hits_cov2D_local, "kernelFullFit - hits_cov2D_local: "); - /* - printf("kernelFullFit - hits address: %p\n", hits); - printf("kernelFullFit - hits_cov address: %p\n", hits_cov); - printf("kernelFullFit - hits_cov2D address: %p\n", &hits2D_local); - printf("kernelFullFit - hits_cov2D_local address: %p\n", &hits_cov2D_local); - */ - /* At some point I gave up and locally construct block on the stack, so that - the next invocation to Rfit::Circle_fit works properly. Failing to do so - implied basically an empty collection of hits and covariances. That could - have been partially fixed if values of the passed in matrices would have - been printed on screen since that, maybe, triggered internally the real - creations of the blocks. To be understood and compared against the myriad - of compilation warnings we have. - */ - (*circle_fit_resultsGPU) = - Rfit::Circle_fit(hits->block(0,0,2,n), hits_cov->block(0, 0, 2 * n, 2 * n), - fast_fit, rad, B, errors); - /* - (*circle_fit_resultsGPU) = - Rfit::Circle_fit(hits2D_local, hits_cov2D_local, - fast_fit, rad, B, errors, scattering); - */ - (*line_fit_resultsGPU) = Rfit::Line_fit(*hits, *hits_cov, *circle_fit_resultsGPU, fast_fit, errors); - - return; +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(Rfit::Matrix3xNd * hits, Vector4d * results) { - (*results) = Rfit::Fast_fit(*hits); +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(Rfit::Matrix3xNd * hits, - Rfit::Matrix3Nd * hits_cov, Vector4d * fast_fit_input, double B, +void kernelCircleFit(double * __restrict__ phits, + float * __restrict__ phits_ge, + double * __restrict__ pfast_fit_input, + double B, Rfit::circle_fit * circle_fit_resultsGPU) { - u_int n = hits->cols(); - Rfit::VectorNd rad = (hits->block(0, 0, 2, n).colwise().norm()); - -#if TEST_DEBUG - 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)); + +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)); @@ -81,91 +66,126 @@ void kernelCircleFit(Rfit::Matrix3xNd * hits, 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 - (*circle_fit_resultsGPU) = - Rfit::Circle_fit(hits->block(0,0,2,n), hits_cov->block(0, 0, 2 * n, 2 * n), - *fast_fit_input, rad, B, false); } __global__ -void kernelLineFit(Rfit::Matrix3xNd * hits, - Rfit::Matrix3Nd * hits_cov, +void kernelLineFit(double * __restrict__ phits, + float * __restrict__ phits_ge, + double B, Rfit::circle_fit * circle_fit, - Vector4d * fast_fit, + double * __restrict__ pfast_fit, Rfit::line_fit * line_fit) { - (*line_fit) = Rfit::Line_fit(*hits, *hits_cov, *circle_fit, *fast_fit, true); + 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); } -void fillHitsAndHitsCov(Rfit::Matrix3xNd & hits, Rfit::Matrix3Nd & hits_cov) { +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_cov(0,0) = 7.14652e-06; - hits_cov(1,1) = 2.15789e-06; - hits_cov(2,2) = 1.63328e-06; - hits_cov(3,3) = 6.27919e-06; - hits_cov(4,4) = 6.10348e-06; - hits_cov(5,5) = 2.08211e-06; - hits_cov(6,6) = 1.61672e-06; - hits_cov(7,7) = 6.28081e-06; - hits_cov(8,8) = 5.184e-05; - hits_cov(9,9) = 1.444e-05; - hits_cov(10,10) = 6.25e-06; - hits_cov(11,11) = 3.136e-05; - hits_cov(0,4) = hits_cov(4,0) = -5.60077e-06; - hits_cov(1,5) = hits_cov(5,1) = -1.11936e-06; - hits_cov(2,6) = hits_cov(6,2) = -6.24945e-07; - hits_cov(3,7) = hits_cov(7,3) = -5.28e-06; + 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 hits(3,4); - Rfit::Matrix3Nd hits_cov = MatrixXd::Zero(12,12); - Rfit::Matrix3xNd * hitsGPU = new Rfit::Matrix3xNd(3,4); - Rfit::Matrix3Nd * hits_covGPU = nullptr; - Vector4d * fast_fit_resultsGPU = new Vector4d(); - Vector4d * fast_fit_resultsGPUret = new Vector4d(); - Rfit::circle_fit * circle_fit_resultsGPU = new Rfit::circle_fit(); + 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_cov); + fillHitsAndHitsCov(hits, hits_ge); - // FAST_FIT_CPU - Vector4d fast_fit_results = Rfit::Fast_fit(hits); -#if TEST_DEBUG + std::cout << "sizes " << sizeof(hits) << ' ' << sizeof(hits_ge) + << ' ' << sizeof(Vector4d)<< std::endl; + std::cout << "Generated hits:\n" << hits << std::endl; -#endif + 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; - // FAST_FIT GPU - cudaMalloc((void**)&hitsGPU, sizeof(Rfit::Matrix3xNd(3,4))); - cudaMalloc((void**)&fast_fit_resultsGPU, sizeof(Vector4d)); - cudaMemcpy(hitsGPU, &hits, sizeof(Rfit::Matrix3xNd(3,4)), cudaMemcpyHostToDevice); + // 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); - kernelFastFit<<<1, 1>>>(hitsGPU, fast_fit_resultsGPU); + // FAST_FIT GPU + kernelFastFit<<>>(hitsGPU, fast_fit_resultsGPU); cudaDeviceSynchronize(); - cudaMemcpy(fast_fit_resultsGPUret, fast_fit_resultsGPU, sizeof(Vector4d), cudaMemcpyDeviceToHost); - std::cout << "Fitted values (FastFit, [X0, Y0, R, tan(theta)]): GPU\n" << *fast_fit_resultsGPUret << std::endl; - assert(isEqualFuzzy(fast_fit_results, (*fast_fit_resultsGPUret))); + 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 - u_int n = hits.cols(); - Rfit::VectorNd rad = (hits.block(0, 0, 2, n).colwise().norm()); + 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.block(0, 0, 2 * n, 2 * n), - fast_fit_results, rad, B, false); + hits_cov, + fast_fit_results, rad, B, true); std::cout << "Fitted values (CircleFit):\n" << circle_fit_results.par << std::endl; // CIRCLE_FIT GPU - cudaMalloc((void **)&hits_covGPU, sizeof(Rfit::Matrix3Nd(12,12))); - cudaMalloc((void **)&circle_fit_resultsGPU, sizeof(Rfit::circle_fit)); - cudaMemcpy(hits_covGPU, &hits_cov, sizeof(Rfit::Matrix3Nd(12,12)), cudaMemcpyHostToDevice); - kernelCircleFit<<<1,1>>>(hitsGPU, hits_covGPU, + kernelCircleFit<<>>(hitsGPU, hits_geGPU, fast_fit_resultsGPU, B, circle_fit_resultsGPU); cudaDeviceSynchronize(); @@ -175,90 +195,29 @@ void testFit() { assert(isEqualFuzzy(circle_fit_results.par, circle_fit_resultsGPUret->par)); // LINE_FIT CPU - Rfit::line_fit line_fit_results = Rfit::Line_fit(hits, hits_cov, circle_fit_results, fast_fit_results, true); + 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_resultsGPU = nullptr; Rfit::line_fit * line_fit_resultsGPUret = new Rfit::line_fit(); - cudaMalloc((void **)&line_fit_resultsGPU, sizeof(Rfit::line_fit)); - - kernelLineFit<<<1,1>>>(hitsGPU, hits_covGPU, circle_fit_resultsGPU, fast_fit_resultsGPU, line_fit_resultsGPU); + 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)); -} - -void testFitOneGo(bool errors, double epsilon=1e-6) { - constexpr double B = 0.0113921; - Rfit::Matrix3xNd hits(3,4); - Rfit::Matrix3Nd hits_cov = MatrixXd::Zero(12,12); - fillHitsAndHitsCov(hits, hits_cov); - - // FAST_FIT_CPU - Vector4d fast_fit_results = Rfit::Fast_fit(hits); - // CIRCLE_FIT CPU - u_int n = hits.cols(); - Rfit::VectorNd rad = (hits.block(0, 0, 2, n).colwise().norm()); + 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; - Rfit::circle_fit circle_fit_results = Rfit::Circle_fit(hits.block(0, 0, 2, n), - hits_cov.block(0, 0, 2 * n, 2 * n), - fast_fit_results, rad, B, errors); - // LINE_FIT CPU - Rfit::line_fit line_fit_results = Rfit::Line_fit(hits, hits_cov, circle_fit_results, - fast_fit_results, errors); - - // FIT GPU - std::cout << "GPU FIT" << std::endl; - Rfit::Matrix3xNd * hitsGPU = nullptr; // new Rfit::Matrix3xNd(3,4); - Rfit::Matrix3Nd * hits_covGPU = nullptr; - Rfit::line_fit * line_fit_resultsGPU = nullptr; - Rfit::line_fit * line_fit_resultsGPUret = new Rfit::line_fit(); - Rfit::circle_fit * circle_fit_resultsGPU = nullptr; // new Rfit::circle_fit(); - Rfit::circle_fit * circle_fit_resultsGPUret = new Rfit::circle_fit(); - - cudaCheck(cudaMalloc((void **)&hitsGPU, sizeof(Rfit::Matrix3xNd(3,4)))); - cudaCheck(cudaMalloc((void **)&hits_covGPU, sizeof(Rfit::Matrix3Nd(12,12)))); - cudaCheck(cudaMalloc((void **)&line_fit_resultsGPU, sizeof(Rfit::line_fit))); - cudaCheck(cudaMalloc((void **)&circle_fit_resultsGPU, sizeof(Rfit::circle_fit))); - cudaCheck(cudaMemcpy(hitsGPU, &hits, sizeof(Rfit::Matrix3xNd(3,4)), cudaMemcpyHostToDevice)); - cudaCheck(cudaMemcpy(hits_covGPU, &hits_cov, sizeof(Rfit::Matrix3Nd(12,12)), cudaMemcpyHostToDevice)); - - kernelFullFit<<<1, 1>>>(hitsGPU, hits_covGPU, B, errors, - circle_fit_resultsGPU, line_fit_resultsGPU); - cudaCheck(cudaDeviceSynchronize()); - - cudaCheck(cudaMemcpy(circle_fit_resultsGPUret, circle_fit_resultsGPU, sizeof(Rfit::circle_fit), cudaMemcpyDeviceToHost)); - cudaCheck(cudaMemcpy(line_fit_resultsGPUret, line_fit_resultsGPU, sizeof(Rfit::line_fit), cudaMemcpyDeviceToHost)); - - std::cout << "Fitted values (CircleFit) CPU:\n" << circle_fit_results.par << std::endl; - std::cout << "Fitted values (LineFit): CPU\n" << line_fit_results.par << std::endl; - std::cout << "Fitted values (CircleFit) GPU:\n" << circle_fit_resultsGPUret->par << std::endl; - std::cout << "Fitted values (LineFit): GPU\n" << line_fit_resultsGPUret->par << std::endl; - assert(isEqualFuzzy(circle_fit_results.par, circle_fit_resultsGPUret->par, epsilon)); - assert(isEqualFuzzy(line_fit_results.par, line_fit_resultsGPUret->par, epsilon)); - - cudaCheck(cudaFree(hitsGPU)); - cudaCheck(cudaFree(hits_covGPU)); - cudaCheck(cudaFree(line_fit_resultsGPU)); - cudaCheck(cudaFree(circle_fit_resultsGPU)); - delete line_fit_resultsGPUret; - delete circle_fit_resultsGPUret; - - cudaDeviceReset(); } int main (int argc, char * argv[]) { -// testFit(); + testFit(); std::cout << "TEST FIT, NO ERRORS" << std::endl; - testFitOneGo(false); - - std::cout << "TEST FIT, ERRORS AND SCATTER" << std::endl; - testFitOneGo(true, 1e-5); 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 index e22fb5cfbf59b..79bb128eeec8a 100644 --- a/RecoPixelVertexing/PixelTrackFitting/test/test_common.h +++ b/RecoPixelVertexing/PixelTrackFitting/test/test_common.h @@ -5,14 +5,10 @@ #include #include -#ifndef TEST_DEBUG -#define TEST_DEBUG 0 -#endif - template __host__ __device__ void printIt(C * m) { -#if TEST_DEBUG +#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) { @@ -22,8 +18,8 @@ void printIt(C * m) { #endif } -template -bool isEqualFuzzy(C a, C b, double epsilon = 1e-6) { +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)) @@ -37,6 +33,7 @@ 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; 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 77cb6c4da68a4..3c8397cf572f6 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/BuildFile.xml +++ b/RecoPixelVertexing/PixelTriplets/plugins/BuildFile.xml @@ -11,7 +11,8 @@ - + + 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 index fa86cf42688bc..141b960b993f0 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/CAHitNtupletHeterogeneousEDProducer.cc +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitNtupletHeterogeneousEDProducer.cc @@ -33,7 +33,10 @@ class CAHitNtupletHeterogeneousEDProducer heterogeneous::GPUCuda, heterogeneous::CPU>> { public: - using PixelRecHitsH = siPixelRecHitsHeterogeneousProduct::HeterogeneousPixelRecHit; + using PixelRecHitsH = siPixelRecHitsHeterogeneousProduct::HeterogeneousPixelRecHit; + using GPUProduct = pixelTuplesHeterogeneousProduct::GPUProduct; + using CPUProduct = pixelTuplesHeterogeneousProduct::CPUProduct; + using Output = pixelTuplesHeterogeneousProduct::HeterogeneousPixelTuples; CAHitNtupletHeterogeneousEDProducer(const edm::ParameterSet &iConfig); @@ -71,8 +74,6 @@ class CAHitNtupletHeterogeneousEDProducer CAHitNtupletHeterogeneousEDProducer::CAHitNtupletHeterogeneousEDProducer( const edm::ParameterSet &iConfig) : HeterogeneousEDProducer(iConfig), - regionToken_(consumes>( - iConfig.getParameter("trackingRegions"))), gpuHits_(consumesHeterogeneous(iConfig.getParameter("heterogeneousPixelRecHitSrc"))), cpuHits_(consumes(iConfig.getParameter("heterogeneousPixelRecHitSrc"))), GPUGenerator_(iConfig, consumesCollector()), @@ -80,17 +81,20 @@ CAHitNtupletHeterogeneousEDProducer::CAHitNtupletHeterogeneousEDProducer( enableConversion_(iConfig.getParameter("gpuEnableConversion")), enableTransfer_(enableConversion_ || iConfig.getParameter("gpuEnableTransfer")) { - produces(); + produces(); + if(enableConversion_) { + regionToken_ = consumes>(iConfig.getParameter("trackingRegions")); + produces(); + } } void CAHitNtupletHeterogeneousEDProducer::fillDescriptions( edm::ConfigurationDescriptions &descriptions) { edm::ParameterSetDescription desc; - desc.add("doublets", edm::InputTag(""))->setComment("Not really used, kept to keep the python parameters"); desc.add("trackingRegions", edm::InputTag("globalTrackingRegionFromBeamSpot")); desc.add("heterogeneousPixelRecHitSrc", edm::InputTag("siPixelRecHitsPreSplitting")); - desc.add("doRiemannFit", true); + desc.add("doRiemannFit", true); // mandatory! desc.add("gpuEnableTransfer", true); desc.add("gpuEnableConversion", true); @@ -109,47 +113,42 @@ void CAHitNtupletHeterogeneousEDProducer::acquireGPUCuda( const edm::HeterogeneousEvent &iEvent, const edm::EventSetup &iSetup, cuda::stream_t<> &cudaStream) { - seedingHitSets_ = std::make_unique(); - - edm::Handle> hregions; - iEvent.getByToken(regionToken_, hregions); - const auto ®ions = *hregions; - assert(regions.size()<=1); - - if (regions.empty()) { - emptyRegions = true; - return; - } - - const TrackingRegion ®ion = regions[0]; - edm::Handle gh; iEvent.getByToken(gpuHits_, gh); auto const & gHits = *gh; GPUGenerator_.buildDoublets(gHits,cudaStream.id()); - seedingHitSets_->reserve(regions.size(), localRA_.upper()); GPUGenerator_.initEvent(iEvent.event(), iSetup); LogDebug("CAHitNtupletHeterogeneousEDProducer") - << "Creating ntuplets for " << regions.size() - << " regions"; + << "Creating ntuplets on GPU"; + + GPUGenerator_.hitNtuplets(gHits, iSetup, doRiemannFit_, enableTransfer_, cudaStream.id()); + + - GPUGenerator_.hitNtuplets(region, gHits, iSetup, doRiemannFit_, enableTransfer_, cudaStream.id()); } void CAHitNtupletHeterogeneousEDProducer::produceGPUCuda( edm::HeterogeneousEvent &iEvent, const edm::EventSetup &iSetup, cuda::stream_t<> &cudaStream) { - GPUGenerator_.cleanup(cudaStream.id()); - - if (not emptyRegions and enableConversion_) { + 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; @@ -165,8 +164,13 @@ void CAHitNtupletHeterogeneousEDProducer::produceGPUCuda( index++; } localRA_.update(seedingHitSets_->size()); + iEvent.put(std::move(seedingHitSets_)); } - 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( diff --git a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc index ea905e013d335..4963bd68c712c 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorGPU.cc @@ -15,7 +15,6 @@ #include "TrackingTools/DetLayers/interface/BarrelDetLayer.h" #include "CAHitQuadrupletGeneratorGPU.h" -#include "CAHitQuadrupletGeneratorKernels.h" namespace { @@ -29,52 +28,24 @@ constexpr unsigned int CAHitQuadrupletGeneratorGPU::minLayers; CAHitQuadrupletGeneratorGPU::CAHitQuadrupletGeneratorGPU( const edm::ParameterSet &cfg, - edm::ConsumesCollector &iC) - : extraHitRPhitolerance(cfg.getParameter("extraHitRPhitolerance")), // extra window in ThirdHitPredictionFromCircle range (divide by R to get phi) - maxChi2(cfg.getParameter("maxChi2")), - fitFastCircle(cfg.getParameter("fitFastCircle")), - fitFastCircleChi2Cut(cfg.getParameter("fitFastCircleChi2Cut")), - useBendingCorrection(cfg.getParameter("useBendingCorrection")), + edm::ConsumesCollector &iC) : + kernels(cfg.getParameter("earlyFishbone"),cfg.getParameter("lateFishbone")), caThetaCut(cfg.getParameter("CAThetaCut")), caPhiCut(cfg.getParameter("CAPhiCut")), caHardPtCut(cfg.getParameter("CAHardPtCut")) { - edm::ParameterSet comparitorPSet = cfg.getParameter("SeedComparitorPSet"); - std::string comparitorName = comparitorPSet.getParameter("ComponentName"); - if (comparitorName != "none") { - theComparitor.reset(SeedComparitorFactory::get()->create(comparitorName, comparitorPSet, iC)); - } } void CAHitQuadrupletGeneratorGPU::fillDescriptions(edm::ParameterSetDescription &desc) { - desc.add("extraHitRPhitolerance", 0.1); - desc.add("fitFastCircle", false); - desc.add("fitFastCircleChi2Cut", false); - desc.add("useBendingCorrection", false); desc.add("CAThetaCut", 0.00125); desc.add("CAPhiCut", 10); desc.add("CAHardPtCut", 0); - desc.addOptional("CAOnlyOneLastHitPerLayerFilter")->setComment( - "Deprecated and has no effect. To be fully removed later when the " - "parameter is no longer used in HLT configurations."); - edm::ParameterSetDescription descMaxChi2; - descMaxChi2.add("pt1", 0.2); - descMaxChi2.add("pt2", 1.5); - descMaxChi2.add("value1", 500); - descMaxChi2.add("value2", 50); - descMaxChi2.add("enabled", true); - desc.add("maxChi2", descMaxChi2); - - edm::ParameterSetDescription descComparitor; - descComparitor.add("ComponentName", "none"); - descComparitor.setAllowAnything(); // until we have moved SeedComparitor to EDProducers too - desc.add("SeedComparitorPSet", descComparitor); + desc.add("earlyFishbone",false); + desc.add("lateFishbone",true); } void CAHitQuadrupletGeneratorGPU::initEvent(edm::Event const& ev, edm::EventSetup const& es) { - if (theComparitor) - theComparitor->init(ev, es); - bField_ = 1 / PixelRecoUtilities::fieldInInvGev(es); + fitter.setBField(1 / PixelRecoUtilities::fieldInInvGev(es)); } @@ -83,7 +54,6 @@ CAHitQuadrupletGeneratorGPU::~CAHitQuadrupletGeneratorGPU() { } void CAHitQuadrupletGeneratorGPU::hitNtuplets( - TrackingRegion const& region, HitsOnCPU const& hh, edm::EventSetup const& es, bool doRiemannFit, @@ -91,8 +61,7 @@ void CAHitQuadrupletGeneratorGPU::hitNtuplets( cudaStream_t cudaStream) { hitsOnCPU = &hh; - int index = 0; - launchKernels(region, index, hh, doRiemannFit, transferToCPU, cudaStream); + launchKernels(hh, doRiemannFit, transferToCPU, cudaStream); } void CAHitQuadrupletGeneratorGPU::fillResults( @@ -109,23 +78,22 @@ void CAHitQuadrupletGeneratorGPU::fillResults( auto const & foundQuads = fetchKernelResult(index); unsigned int numberOfFoundQuadruplets = foundQuads.size(); - const QuantityDependsPtEval maxChi2Eval = maxChi2.evaluator(es); - // re-used throughout - std::array bc_r; - std::array bc_z; - std::array bc_errZ2; 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 = false; + bool bad = pixelTuplesHeterogeneousProduct::bad == quality_[quadId]; for (unsigned int i = 0; i < 4; ++i) { auto k = foundQuads[quadId][i]; assert(kcompatible(tmpTriplet)) { - continue; - } - } - - float chi2 = std::numeric_limits::quiet_NaN(); - // TODO: Do we have any use case to not use bending correction? - if (useBendingCorrection) { - // Following PixelFitterByConformalMappingAndLine - const float simpleCot = (gps.back().z() - gps.front().z()) / - (gps.back().perp() - gps.front().perp()); - const float pt = 1.f / PixelRecoUtilities::inversePt(abscurv, es); - for (int i = 0; i < 4; ++i) { - const GlobalPoint &point = gps[i]; - const GlobalError &error = ges[i]; - bc_r[i] = sqrt(sqr(point.x() - region.origin().x()) + - sqr(point.y() - region.origin().y())); - bc_r[i] += pixelrecoutilities::LongitudinalBendingCorrection(pt, es)( - bc_r[i]); - bc_z[i] = point.z() - region.origin().z(); - bc_errZ2[i] = - (barrels[i]) ? error.czz() : error.rerr(point) * sqr(simpleCot); - } - RZLine rzLine(bc_r, bc_z, bc_errZ2, RZLine::ErrZ2_tag()); - chi2 = rzLine.chi2(); - } else { - RZLine rzLine(gps, ges, barrels); - chi2 = rzLine.chi2(); - } - if (edm::isNotFinite(chi2) || chi2 > thisMaxChi2) { - continue; - } - // TODO: Do we have any use case to not use circle fit? Maybe - // HLT where low-pT inefficiency is not a problem? - if (fitFastCircle) { - FastCircleFit c(gps, ges); - chi2 += c.chi2(); - if (edm::isNotFinite(chi2)) - continue; - if (fitFastCircleChi2Cut && chi2 > thisMaxChi2) - continue; - } + 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 -} -void CAHitQuadrupletGeneratorGPU::launchKernels(const TrackingRegion ®ion, - int regionIndex, HitsOnCPU const & hh, - bool doRiemannFit, - bool transferToCPU, - cudaStream_t cudaStream) -{ - assert(regionIndex < maxNumberOfRegions_); - assert(0==regionIndex); - - h_foundNtupletsVec_[regionIndex]->reset(); - - auto nhits = hh.nHits; - assert(nhits <= PixelGPUConstants::maxNumberOfHits); - auto blockSize = 64; - auto numberOfBlocks = (maxNumberOfDoublets_ + blockSize - 1)/blockSize; - //kernel_connect<<>>( - wrapperConnect(numberOfBlocks, blockSize, cudaStream, - d_foundNtupletsVec_[regionIndex], // needed only to be reset, ready for next kernel - hh.gpu_d, - device_theCells_, device_nCells_, - device_isOuterHitOfCell_, - region.ptMin(), - region.originRBound(), caThetaCut, caPhiCut, caHardPtCut, - maxNumberOfDoublets_, PixelGPUConstants::maxNumberOfHits); - - //kernel_find_ntuplets<<>>( - wrapperFindNtuplets(numberOfBlocks, blockSize, cudaStream, - device_theCells_, device_nCells_, - d_foundNtupletsVec_[regionIndex], - 4, maxNumberOfDoublets_); - - numberOfBlocks = (std::max(int(nhits), maxNumberOfDoublets_) + blockSize - 1)/blockSize; - //kernel_checkOverflows<<>>( - wrapperCheckOverflows(numberOfBlocks, blockSize, cudaStream, - d_foundNtupletsVec_[regionIndex], - device_theCells_, device_nCells_, - device_isOuterHitOfCell_, nhits, - maxNumberOfDoublets_); - - // kernel_print_found_ntuplets<<<1, 1, 0, cudaStream>>>(d_foundNtupletsVec_[regionIndex], 10); - // wrapperPrintFoundNtuplets(cudaStream, d_foundNtupletsVec_[regionIndex], 10); +#ifdef GPU_DEBUG + std::cout << "Q Final quads " << result[index].size() << ' ' << nbad << std::endl; +#endif - if (doRiemannFit) { - //kernelFastFitAllHits<<>>( - wrapperFastFitAllHits(numberOfBlocks, 512, cudaStream, - d_foundNtupletsVec_[regionIndex], hh.gpu_d, 4, bField_, helix_fit_resultsGPU_, - hitsGPU_, hits_covGPU_, circle_fit_resultsGPU_, fast_fit_resultsGPU_, - line_fit_resultsGPU_); - - blockSize = 256; - numberOfBlocks = (maxNumberOfQuadruplets_ + blockSize - 1) / blockSize; - - //kernelCircleFitAllHits<<>>( - wrapperCircleFitAllHits(numberOfBlocks, blockSize, cudaStream, - d_foundNtupletsVec_[regionIndex], 4, bField_, helix_fit_resultsGPU_, - hitsGPU_, hits_covGPU_, circle_fit_resultsGPU_, fast_fit_resultsGPU_, - line_fit_resultsGPU_); - - //kernelLineFitAllHits<<>>( - wrapperLineFitAllHits(numberOfBlocks, blockSize, cudaStream, - d_foundNtupletsVec_[regionIndex], bField_, helix_fit_resultsGPU_, - hitsGPU_, hits_covGPU_, circle_fit_resultsGPU_, fast_fit_resultsGPU_, - line_fit_resultsGPU_); - } +} - if (transferToCPU) { - cudaCheck(cudaMemcpyAsync(h_foundNtupletsVec_[regionIndex], d_foundNtupletsVec_[regionIndex], - sizeof(GPU::SimpleVector), - cudaMemcpyDeviceToHost, cudaStream)); - cudaCheck(cudaMemcpyAsync(h_foundNtupletsData_[regionIndex], d_foundNtupletsData_[regionIndex], - maxNumberOfQuadruplets_*sizeof(Quadruplet), - cudaMemcpyDeviceToHost, cudaStream)); - } +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() { - ////////////////////////////////////////////////////////// - // ALLOCATIONS FOR THE INTERMEDIATE RESULTS (STAYS ON WORKER) - ////////////////////////////////////////////////////////// - - cudaCheck(cudaMalloc(&device_theCells_, - maxNumberOfLayerPairs_ * 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(GPU::VecArray))); - cudaCheck(cudaMemset(device_isOuterHitOfCell_, 0, - PixelGPUConstants::maxNumberOfHits * sizeof(GPU::VecArray))); - - h_foundNtupletsVec_.resize(maxNumberOfRegions_); - h_foundNtupletsData_.resize(maxNumberOfRegions_); - d_foundNtupletsVec_.resize(maxNumberOfRegions_); - d_foundNtupletsData_.resize(maxNumberOfRegions_); - - // FIXME this could be rewritten with a single pair of cudaMallocHost / cudaMalloc - for (int i = 0; i < maxNumberOfRegions_; ++i) { - cudaCheck(cudaMallocHost(&h_foundNtupletsData_[i], sizeof(Quadruplet) * maxNumberOfQuadruplets_)); - cudaCheck(cudaMallocHost(&h_foundNtupletsVec_[i], sizeof(GPU::SimpleVector))); - GPU::make_SimpleVector(h_foundNtupletsVec_[i], maxNumberOfQuadruplets_, h_foundNtupletsData_[i]); - cudaCheck(cudaMalloc(&d_foundNtupletsData_[i], sizeof(Quadruplet) * maxNumberOfQuadruplets_)); - cudaCheck(cudaMemset(d_foundNtupletsData_[i], 0x00, sizeof(Quadruplet) * maxNumberOfQuadruplets_)); - cudaCheck(cudaMalloc(&d_foundNtupletsVec_[i], sizeof(GPU::SimpleVector))); - auto tmp_foundNtuplets = GPU::make_SimpleVector(maxNumberOfQuadruplets_, d_foundNtupletsData_[i]); - cudaCheck(cudaMemcpy(d_foundNtupletsVec_[i], & tmp_foundNtuplets, sizeof(GPU::SimpleVector), cudaMemcpyDefault)); - } + constexpr auto maxNumberOfQuadruplets_ = CAConstants::maxNumberOfQuadruplets(); - // Riemann-Fit related allocations - cudaCheck(cudaMalloc(&hitsGPU_, 48 * maxNumberOfQuadruplets_ * sizeof(Rfit::Matrix3xNd(3, 4)))); - cudaCheck(cudaMemset(hitsGPU_, 0x00, 48 * maxNumberOfQuadruplets_ * sizeof(Rfit::Matrix3xNd(3, 4)))); + // 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(&hits_covGPU_, 48 * maxNumberOfQuadruplets_ * sizeof(Rfit::Matrix3Nd(12, 12)))); - cudaCheck(cudaMemset(hits_covGPU_, 0x00, 48 * maxNumberOfQuadruplets_ * sizeof(Rfit::Matrix3Nd(12, 12)))); + cudaCheck(cudaMalloc(&gpu_d, sizeof(TuplesOnGPU))); + gpu_.me_d = gpu_d; + cudaCheck(cudaMemcpy(gpu_d, &gpu_, sizeof(TuplesOnGPU), cudaMemcpyDefault)); - cudaCheck(cudaMalloc(&fast_fit_resultsGPU_, 48 * maxNumberOfQuadruplets_ * sizeof(Eigen::Vector4d))); - cudaCheck(cudaMemset(fast_fit_resultsGPU_, 0x00, 48 * maxNumberOfQuadruplets_ * sizeof(Eigen::Vector4d))); + cudaCheck(cudaMallocHost(&tuples_, sizeof(TuplesOnGPU::Container))); + cudaCheck(cudaMallocHost(&helix_fit_results_, sizeof(Rfit::helix_fit)*maxNumberOfQuadruplets_)); + cudaCheck(cudaMallocHost(&quality_, sizeof(Quality)*maxNumberOfQuadruplets_)); - cudaCheck(cudaMalloc(&circle_fit_resultsGPU_, 48 * maxNumberOfQuadruplets_ * sizeof(Rfit::circle_fit))); - cudaCheck(cudaMemset(circle_fit_resultsGPU_, 0x00, 48 * maxNumberOfQuadruplets_ * sizeof(Rfit::circle_fit))); + kernels.allocateOnGPU(); + fitter.allocateOnGPU(gpu_.tuples_d, gpu_.helix_fit_results_d); - cudaCheck(cudaMalloc(&line_fit_resultsGPU_, maxNumberOfQuadruplets_ * sizeof(Rfit::line_fit))); - cudaCheck(cudaMemset(line_fit_resultsGPU_, 0x00, maxNumberOfQuadruplets_ * sizeof(Rfit::line_fit))); - cudaCheck(cudaMalloc(&helix_fit_resultsGPU_, sizeof(Rfit::helix_fit)*maxNumberOfQuadruplets_)); - cudaCheck(cudaMemset(helix_fit_resultsGPU_, 0x00, sizeof(Rfit::helix_fit)*maxNumberOfQuadruplets_)); } -void CAHitQuadrupletGeneratorGPU::deallocateOnGPU() +void CAHitQuadrupletGeneratorGPU::launchKernels(HitsOnCPU const & hh, + bool doRiemannFit, + bool transferToCPU, + cudaStream_t cudaStream) { - for (size_t i = 0; i < h_foundNtupletsVec_.size(); ++i) - { - cudaFreeHost(h_foundNtupletsVec_[i]); - cudaFreeHost(h_foundNtupletsData_[i]); - cudaFree(d_foundNtupletsVec_[i]); - cudaFree(d_foundNtupletsData_[i]); + + 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)); + } - cudaFree(device_theCells_); - cudaFree(device_isOuterHitOfCell_); - cudaFree(device_nCells_); - - // Free Riemann Fit stuff - cudaFree(hitsGPU_); - cudaFree(hits_covGPU_); - cudaFree(fast_fit_resultsGPU_); - cudaFree(circle_fit_resultsGPU_); - cudaFree(line_fit_resultsGPU_); - cudaFree(helix_fit_resultsGPU_); } void CAHitQuadrupletGeneratorGPU::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(GPU::VecArray), - cudaStream)); - cudaCheck(cudaMemsetAsync(device_nCells_, 0, sizeof(uint32_t), cudaStream)); + kernels.cleanup(cudaStream); } + + std::vector> -CAHitQuadrupletGeneratorGPU::fetchKernelResult(int regionIndex) +CAHitQuadrupletGeneratorGPU::fetchKernelResult(int) { - assert(0==regionIndex); - h_foundNtupletsVec_[regionIndex]->set_data(h_foundNtupletsData_[regionIndex]); - - std::vector> quadsInterface(h_foundNtupletsVec_[regionIndex]->size()); - for (int i = 0; i < h_foundNtupletsVec_[regionIndex]->size(); ++i) { - for (int j = 0; j<4; ++j) quadsInterface[i][j] = (*h_foundNtupletsVec_[regionIndex])[i].hitId[j]; + 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())<<'/'<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, @@ -73,116 +87,35 @@ class CAHitQuadrupletGeneratorGPU { private: - std::unique_ptr theComparitor; - - class QuantityDependsPtEval { - public: - - QuantityDependsPtEval(float v1, float v2, float c1, float c2) : - value1_(v1), value2_(v2), curvature1_(c1), curvature2_(c2) { - } - - float value(float curvature) const { - if (value1_ == value2_) // not enabled - return value1_; - - if (curvature1_ < curvature) - return value1_; - if (curvature2_ < curvature && curvature <= curvature1_) - return value2_ + (curvature - curvature2_) / (curvature1_ - curvature2_) * (value1_ - value2_); - return value2_; - } - - private: - const float value1_; - const float value2_; - const float curvature1_; - const float curvature2_; - }; - - // Linear interpolation (in curvature) between value1 at pt1 and - // value2 at pt2. If disabled, value2 is given (the point is to - // allow larger/smaller values of the quantity at low pt, so it - // makes more sense to have the high-pt value as the default). - - class QuantityDependsPt { - public: - - explicit QuantityDependsPt(const edm::ParameterSet& pset) : - value1_(pset.getParameter("value1")), - value2_(pset.getParameter("value2")), - pt1_(pset.getParameter("pt1")), - pt2_(pset.getParameter("pt2")), - enabled_(pset.getParameter("enabled")) { - if (enabled_ && pt1_ >= pt2_) - throw cms::Exception("Configuration") << "PixelQuadrupletGenerator::QuantityDependsPt: pt1 (" << pt1_ << ") needs to be smaller than pt2 (" << pt2_ << ")"; - if (pt1_ <= 0) - throw cms::Exception("Configuration") << "PixelQuadrupletGenerator::QuantityDependsPt: pt1 needs to be > 0; is " << pt1_; - if (pt2_ <= 0) - throw cms::Exception("Configuration") << "PixelQuadrupletGenerator::QuantityDependsPt: pt2 needs to be > 0; is " << pt2_; - } - - QuantityDependsPtEval evaluator(const edm::EventSetup& es) const { - if (enabled_) { - return QuantityDependsPtEval(value1_, value2_, - PixelRecoUtilities::curvature(1.f / pt1_, es), - PixelRecoUtilities::curvature(1.f / pt2_, es)); - } - return QuantityDependsPtEval(value2_, value2_, 0.f, 0.f); - } - - private: - const float value1_; - const float value2_; - const float pt1_; - const float pt2_; - const bool enabled_; - }; - - void launchKernels(const TrackingRegion &, int, HitsOnCPU const & hh, bool doRiemannFit, bool transferToCPU, cudaStream_t); - std::vector> fetchKernelResult(int); + void launchKernels(HitsOnCPU const & hh, bool doRiemannFit, bool transferToCPU, cudaStream_t); + - float bField_; + std::vector> fetchKernelResult(int); - const float extraHitRPhitolerance; - const QuantityDependsPt maxChi2; - const bool fitFastCircle; - const bool fitFastCircleChi2Cut; - const bool useBendingCorrection; + 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; - static constexpr int maxNumberOfQuadruplets_ = 10000; - static constexpr int maxCellsPerHit_ = 256; - static constexpr int maxNumberOfLayerPairs_ = 13; - static constexpr int maxNumberOfLayers_ = 10; - static constexpr int maxNumberOfDoublets_ = 262144; - static constexpr int maxNumberOfRegions_ = 2; - - std::vector*> h_foundNtupletsVec_; - std::vector h_foundNtupletsData_; - - std::vector*> d_foundNtupletsVec_; - std::vector d_foundNtupletsData_; - GPUCACell* device_theCells_ = nullptr; - GPU::VecArray< unsigned int, maxCellsPerHit_>* device_isOuterHitOfCell_ = nullptr; - uint32_t* device_nCells_ = nullptr; + // 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); - // Riemann Fit stuff - Rfit::Matrix3xNd *hitsGPU_ = nullptr; - Rfit::Matrix3Nd *hits_covGPU_ = nullptr; - Eigen::Vector4d *fast_fit_resultsGPU_ = nullptr; - Rfit::circle_fit *circle_fit_resultsGPU_ = nullptr; - Rfit::line_fit *line_fit_resultsGPU_ = nullptr; - Rfit::helix_fit * helix_fit_resultsGPU_ = 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 index 61c3ff0799637..85dc10ee04587 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cu +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.cu @@ -1,416 +1,333 @@ +// +// Author: Felice Pantaleo, CERN +// + +#include "CAHitQuadrupletGeneratorKernels.h" #include -#include #include -#include - -#include "HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h" -#include "HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h" #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" -#include "CAHitQuadrupletGeneratorKernels.h" #include "GPUCACell.h" #include "gpuPixelDoublets.h" +#include "gpuFishbone.h" +#include "CAConstants.h" -__global__ -void kernelFastFitAllHits( - GPU::SimpleVector * foundNtuplets, - siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * hhp, - int hits_in_fit, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit) -{ - int helix_start = (blockIdx.x * blockDim.x + threadIdx.x); - if (helix_start >= foundNtuplets->size()) { - return; - } +using namespace gpuPixelDoublets; -#ifdef GPU_DEBUG - printf("BlockDim.x: %d, BlockIdx.x: %d, threadIdx.x: %d, helix_start: %d, cumulative_size: %d\n", - blockDim.x, blockIdx.x, threadIdx.x, helix_start, foundNtuplets->size()); -#endif +using HitsOnCPU = siPixelRecHitsHeterogeneousProduct::HitsOnCPU; +using TuplesOnGPU = pixelTuplesHeterogeneousProduct::TuplesOnGPU; +using Quality = pixelTuplesHeterogeneousProduct::Quality; - hits[helix_start].resize(3, hits_in_fit); - hits_cov[helix_start].resize(3 * hits_in_fit, 3 * hits_in_fit); - - // Prepare data structure - for (unsigned int i = 0; i < hits_in_fit; ++i) { - auto hit = (*foundNtuplets)[helix_start].hitId[i]; - // printf("Hit global_x: %f\n", hhp->xg_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[helix_start].col(i) << hhp->xg_d[hit], hhp->yg_d[hit], hhp->zg_d[hit]; - - for (auto j = 0; j < 3; ++j) { - for (auto l = 0; l < 3; ++l) { - // 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, xz, yy, yz, zz] - // in (j,l) notation, we have: - // ge[] ==> [(0,0), (0,1), (0,2), (1,1), (1,2), (2,2)] - // so the index ge_idx corresponds to the matrix elements: - // | 0 1 2 | - // | 1 3 4 | - // | 2 4 5 | - auto ge_idx = j + l + (j > 0 and l > 0); - hits_cov[helix_start](i + j * hits_in_fit, i + l * hits_in_fit) = ge[ge_idx]; - } - } - } - fast_fit[helix_start] = Rfit::Fast_fit(hits[helix_start]); -} -void wrapperFastFitAllHits( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * hhp, - int hits_in_fit, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit) -{ - kernelFastFitAllHits<<>>( - foundNtuplets, - hhp, - hits_in_fit, - B, - results, - hits, - hits_cov, - circle_fit, - fast_fit, - line_fit); - cudaCheck(cudaGetLastError()); -} __global__ -void kernelCircleFitAllHits( - GPU::SimpleVector * foundNtuplets, - int hits_in_fit, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit) -{ - int helix_start = (blockIdx.x * blockDim.x + threadIdx.x); - if (helix_start >= foundNtuplets->size()) { - return; - } +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); + } -#ifdef GPU_DEBUG - printf("blockDim.x: %d, blockIdx.x: %d, threadIdx.x: %d, helix_start: %d, cumulative_size: %d\n", - blockDim.x, blockIdx.x, threadIdx.x, helix_start, foundNtuplets->size()); -#endif - auto n = hits[helix_start].cols(); + 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"); + } - circle_fit[helix_start] = - Rfit::Circle_fit(hits[helix_start].block(0, 0, 2, n), - hits_cov[helix_start].block(0, 0, 2 * n, 2 * n), - fast_fit[helix_start], rad, B, true); + 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); + } -#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 + __syncthreads(); +// if (threadIdx.x==0) printf("number of killed cells %d\n",killedCell); } -void wrapperCircleFitAllHits( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - int hits_in_fit, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit) -{ - kernelCircleFitAllHits<<>>( - foundNtuplets, - hits_in_fit, - B, - results, - hits, - hits_cov, - circle_fit, - fast_fit, - line_fit); - cudaCheck(cudaGetLastError()); -} __global__ -void kernelLineFitAllHits( - GPU::SimpleVector * foundNtuplets, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit) -{ - int helix_start = (blockIdx.x * blockDim.x + threadIdx.x); - if (helix_start >= foundNtuplets->size()) { - return; - } - -#ifdef GPU_DEBUG - printf("blockDim.x: %d, blockIdx.x: %d, threadIdx.x: %d, helix_start: %d, cumulative_size: %d\n", - blockDim.x, blockIdx.x, threadIdx.x, helix_start, foundNtuplets->size()); -#endif +void +kernel_fishboneCleaner(GPUCACell const * cells, uint32_t const * __restrict__ nCells, + pixelTuplesHeterogeneousProduct::Quality * quality + ) { - line_fit[helix_start] = Rfit::Line_fit(hits[helix_start], hits_cov[helix_start], circle_fit[helix_start], fast_fit[helix_start], B, true); + constexpr auto bad = pixelTuplesHeterogeneousProduct::bad; - par_uvrtopak(circle_fit[helix_start], B, true); - - // Grab helix_fit from the proper location in the output vector - auto & helix = results[helix_start]; - helix.par << circle_fit[helix_start].par, line_fit[helix_start].par; - - // TODO: pass properly error booleans - - helix.cov = Eigen::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; + auto cellIndex = threadIdx.x + blockIdx.x * blockDim.x; - helix.q = circle_fit[helix_start].q; - helix.chi2_circle = circle_fit[helix_start].chi2; - helix.chi2_line = line_fit[helix_start].chi2; + if (cellIndex >= (*nCells) ) return; + auto const & thisCell = cells[cellIndex]; + if (thisCell.theDoubletId>=0) return; -#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 -} + for (auto it : thisCell.theTracks) quality[it] = bad; -void wrapperLineFitAllHits( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit) -{ - kernelLineFitAllHits<<>>( - foundNtuplets, - B, - results, - hits, - hits_cov, - circle_fit, - fast_fit, - line_fit); - cudaCheck(cudaGetLastError()); } __global__ -void kernelCheckOverflows( - GPU::SimpleVector *foundNtuplets, - GPUCACell const * __restrict__ cells, - uint32_t const * __restrict__ nCells, - GPU::VecArray const * __restrict__ isOuterHitOfCell, - uint32_t nHits, - uint32_t maxNumberOfDoublets) -{ - auto idx = threadIdx.x + blockIdx.x * blockDim.x; - #ifdef GPU_DEBUG - if (0==idx) - printf("number of found cells %d\n",*nCells); - #endif - 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 (idx < nHits) { - if (isOuterHitOfCell[idx].full()) // ++tooManyOuterHitOfCell; - printf("OuterHitOfCell overflow %d\n", idx); - } -} +void +kernel_fastDuplicateRemover(GPUCACell const * cells, uint32_t const * __restrict__ nCells, + TuplesOnGPU::Container * foundNtuplets, + Rfit::helix_fit const * __restrict__ hfit, + pixelTuplesHeterogeneousProduct::Quality * quality + ) { -void wrapperCheckOverflows( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector *foundNtuplets, - GPUCACell const * cells, - uint32_t const * nCells, - GPU::VecArray const * isOuterHitOfCell, - uint32_t nHits, - uint32_t maxNumberOfDoublets) -{ - kernelCheckOverflows<<>>( - foundNtuplets, - cells, - nCells, - isOuterHitOfCell, - nHits, - maxNumberOfDoublets); - cudaCheck(cudaGetLastError()); + 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 kernelConnect( - GPU::SimpleVector * foundNtuplets, - GPUCACell::Hits const * __restrict__ hhp, - GPUCACell * cells, uint32_t const * __restrict__ nCells, - GPU::VecArray const * __restrict__ isOuterHitOfCell, - float ptmin, - float region_origin_radius, const float thetaCut, - const float phiCut, const float hardPtCut, - unsigned int maxNumberOfDoublets_, unsigned int maxNumberOfHits_) -{ +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; - constexpr float region_origin_x = 0.; - constexpr float region_origin_y = 0.; + // 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) foundNtuplets->reset(); // ready for next kernel + 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, region_origin_x, region_origin_y, - region_origin_radius, thetaCut, phiCut, hardPtCut) + cells[otherCell], ptmin, hardCurvCut) ) { cells[otherCell].theOuterNeighbors.push_back(cellIndex); } } } -void wrapperConnect( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - GPUCACell::Hits const * hhp, - GPUCACell * cells, - uint32_t const * nCells, - GPU::VecArray const * isOuterHitOfCell, - float ptmin, - float region_origin_radius, - const float thetaCut, - const float phiCut, - const float hardPtCut, - unsigned int maxNumberOfDoublets_, - unsigned int maxNumberOfHits_) +__global__ +void kernel_find_ntuplets( + GPUCACell * __restrict__ cells, uint32_t const * nCells, + TuplesOnGPU::Container * foundNtuplets, AtomicPairCounter * apc, + unsigned int minHitsPerNtuplet) { - kernelConnect<<>>( - foundNtuplets, - hhp, - cells, - nCells, - isOuterHitOfCell, - ptmin, - region_origin_radius, - thetaCut, - phiCut, - hardPtCut, - maxNumberOfDoublets_, - maxNumberOfHits_); - cudaCheck(cudaGetLastError()); -} -__global__ -void kernelFindNtuplets( - GPUCACell * const __restrict__ cells, - uint32_t const * nCells, - GPU::SimpleVector *foundNtuplets, - unsigned int minHitsPerNtuplet, - unsigned int maxNumberOfDoublets_) -{ auto cellIndex = threadIdx.x + blockIdx.x * blockDim.x; - if (cellIndex >= (*nCells)) - return; - auto & thisCell = cells[cellIndex]; - if (thisCell.theLayerPairId != 0 and thisCell.theLayerPairId != 3 and thisCell.theLayerPairId != 8) - return; // inner layer is 0 FIXME - GPU::VecArray stack; + 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, stack, minHitsPerNtuplet); + thisCell.find_ntuplets(cells, *foundNtuplets, *apc, stack, minHitsPerNtuplet); assert(stack.size()==0); - // printf("in %d found quadruplets: %d\n", cellIndex, foundNtuplets->size()); + // printf("in %d found quadruplets: %d\n", cellIndex, apc->get()); } -void wrapperFindNtuplets( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPUCACell * const cells, - uint32_t const * nCells, - GPU::SimpleVector *foundNtuplets, - unsigned int minHitsPerNtuplet, - unsigned int maxNumberOfDoublets) -{ - kernelFindNtuplets<<>>( - cells, - nCells, - foundNtuplets, - minHitsPerNtuplet, - maxNumberOfDoublets); - cudaCheck(cudaGetLastError()); + +__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 kernelPrintFoundNtuplets(GPU::SimpleVector *foundNtuplets, int maxPrint) -{ +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)[i].hitId[0], - (*foundNtuplets)[i].hitId[1], - (*foundNtuplets)[i].hitId[2], - (*foundNtuplets)[i].hitId[3] + (*(*foundNtuplets).begin(i)), + (*(*foundNtuplets).begin(i)+1), + (*(*foundNtuplets).begin(i)+2), + (*(*foundNtuplets).begin(i)+3) ); - } } -void wrapperPrintFoundNtuplets( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector *foundNtuplets, - int maxPrint) +void CAHitQuadrupletGeneratorKernels::launchKernels( // here goes algoparms.... + HitsOnCPU const & hh, + TuplesOnGPU & tuples_d, + cudaStream_t cudaStream) { - kernelPrintFoundNtuplets<<>>( - foundNtuplets, - maxPrint); + 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()); -} -void wrapperDoubletsFromHisto( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPUCACell * cells, - uint32_t * nCells, - siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * hits, - GPU::VecArray * isOuterHitOfCell) -{ - gpuPixelDoublets::getDoubletsFromHisto<<>>(cells, nCells, hits, isOuterHitOfCell); + 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 index e1775bd1e445b..97b5617a54f89 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.h +++ b/RecoPixelVertexing/PixelTriplets/plugins/CAHitQuadrupletGeneratorKernels.h @@ -1,93 +1,51 @@ #ifndef RecoPixelVertexing_PixelTriplets_plugins_CAHitQuadrupletGeneratorKernels_h #define RecoPixelVertexing_PixelTriplets_plugins_CAHitQuadrupletGeneratorKernels_h -#include -#include "HeterogeneousCore/CUDAUtilities/interface/GPUSimpleVector.h" -#include "HeterogeneousCore/CUDAUtilities/interface/GPUVecArray.h" #include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" -#include "RecoPixelVertexing/PixelTrackFitting/interface/RiemannFit.h" + +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" #include "GPUCACell.h" -void wrapperFastFitAllHits( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * hhp, - int hits_in_fit, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit); - -void wrapperCircleFitAllHits( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - int hits_in_fit, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit); - -void wrapperLineFitAllHits( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - float B, - Rfit::helix_fit *results, - Rfit::Matrix3xNd *hits, - Rfit::Matrix3Nd *hits_cov, - Rfit::circle_fit *circle_fit, - Eigen::Vector4d *fast_fit, - Rfit::line_fit *line_fit); - -void wrapperCheckOverflows( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector *foundNtuplets, - GPUCACell const * cells, - uint32_t const * nCells, - GPU::VecArray const * isOuterHitOfCell, - uint32_t nHits, - uint32_t maxNumberOfDoublets); - -void wrapperConnect( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector * foundNtuplets, - GPUCACell::Hits const * hhp, - GPUCACell * cells, - uint32_t const * nCells, - GPU::VecArray const * isOuterHitOfCell, - float ptmin, - float region_origin_radius, - const float thetaCut, - const float phiCut, - const float hardPtCut, - unsigned int maxNumberOfDoublets_, - unsigned int maxNumberOfHits); - -void wrapperFindNtuplets( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPUCACell * const cells, - uint32_t const * nCells, - GPU::SimpleVector *foundNtuplets, - unsigned int minHitsPerNtuplet, - unsigned int maxNumberOfDoublets); - -void wrapperPrintFoundNtuplets( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPU::SimpleVector *foundNtuplets, - int maxPrint); - -void wrapperDoubletsFromHisto( - int gridSize, int blockSize, cudaStream_t cudaStream, - GPUCACell * cells, - uint32_t * nCells, - siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * hits, - GPU::VecArray * isOuterHitOfCell); +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 index 43fcd88fa30de..dbd4eecbaab3c 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/GPUCACell.h +++ b/RecoPixelVertexing/PixelTriplets/plugins/GPUCACell.h @@ -10,19 +10,25 @@ #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" -struct Quadruplet { - using hindex_type = siPixelRecHitsHeterogeneousProduct::hindex_type; - hindex_type hitId[4]; -}; +#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__ @@ -39,6 +45,7 @@ class GPUCACell { 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); } @@ -50,6 +57,9 @@ class GPUCACell { __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; } @@ -69,10 +79,9 @@ class GPUCACell { __device__ bool check_alignment(Hits const & hh, - GPUCACell const & otherCell, const float ptmin, - const float region_origin_x, const float region_origin_y, - const float region_origin_radius, const float thetaCut, - const float phiCut, const float hardPtCut) const + GPUCACell const & otherCell, + const float ptmin, + const float hardCurvCut) const { auto ri = get_inner_r(hh); auto zi = get_inner_z(hh); @@ -82,11 +91,9 @@ class GPUCACell { auto r1 = otherCell.get_inner_r(hh); auto z1 = otherCell.get_inner_z(hh); - bool aligned = areAlignedRZ(r1, z1, ri, zi, ro, zo, ptmin, thetaCut); - return (aligned && - haveSimilarCurvature(hh, otherCell, ptmin, region_origin_x, - region_origin_y, region_origin_radius, phiCut, - hardPtCut)); + 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__ @@ -106,13 +113,12 @@ class GPUCACell { return tan_12_13_half_mul_distance_13_squared * pMin <= thetaCut * distance_13_squared * radius_diff; } - __device__ + + __device__ bool - haveSimilarCurvature(Hits const & hh, GPUCACell const & otherCell, - const float ptmin, const float region_origin_x, - const float region_origin_y, - const float region_origin_radius, const float phiCut, - const float hardPtCut) const { + 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); @@ -123,69 +129,12 @@ class GPUCACell { auto x3 = get_outer_x(hh); auto y3 = get_outer_y(hh); - float distance_13_squared = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3); - float tan_12_13_half_mul_distance_13_squared = - fabs(y1 * (x2 - x3) + y2 * (x3 - x1) + y3 * (x1 - x2)); - // high pt : just straight - if (tan_12_13_half_mul_distance_13_squared * ptmin <= - 1.0e-4f * distance_13_squared) { - - float distance_3_beamspot_squared = - (x3 - region_origin_x) * (x3 - region_origin_x) + - (y3 - region_origin_y) * (y3 - region_origin_y); - - float dot_bs3_13 = ((x1 - x3) * (region_origin_x - x3) + - (y1 - y3) * (region_origin_y - y3)); - float proj_bs3_on_13_squared = - dot_bs3_13 * dot_bs3_13 / distance_13_squared; - - float distance_13_beamspot_squared = - distance_3_beamspot_squared - proj_bs3_on_13_squared; - - return distance_13_beamspot_squared < - (region_origin_radius + phiCut) * (region_origin_radius + phiCut); - } - - // 87 cm/GeV = 1/(3.8T * 0.3) + CircleEq eq(x1,y1,x2,y2,x3,y3); - // take less than radius given by the hardPtCut and reject everything below - float minRadius = hardPtCut * 87.f; // FIXME move out and use real MagField + if (eq.curvature() > maxCurv) return false; - auto det = (x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2); + return std::abs(eq.dca0()) < region_origin_radius_plus_tolerance*std::abs(eq.curvature()); - 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; - - auto x_center = (bc * (y2 - y3) - cd * (y1 - y2)) * idet; - auto y_center = (cd * (x1 - x2) - bc * (x2 - x3)) * idet; - - auto radius = std::sqrt((x2 - x_center) * (x2 - x_center) + - (y2 - y_center) * (y2 - y_center)); - - if (radius < minRadius) - return false; // hard cut on pt - - auto centers_distance_squared = - (x_center - region_origin_x) * (x_center - region_origin_x) + - (y_center - region_origin_y) * (y_center - region_origin_y); - auto region_origin_radius_plus_tolerance = region_origin_radius + phiCut; - auto minimumOfIntersectionRange = - (radius - region_origin_radius_plus_tolerance) * - (radius - region_origin_radius_plus_tolerance); - - if (centers_distance_squared >= minimumOfIntersectionRange) { - auto maximumOfIntersectionRange = - (radius + region_origin_radius_plus_tolerance) * - (radius + region_origin_radius_plus_tolerance); - return centers_distance_squared <= maximumOfIntersectionRange; - } - - return false; } // trying to free the track building process from hardcoded layers, leaving @@ -195,9 +144,10 @@ class GPUCACell { __device__ inline void find_ntuplets( - GPUCACell const * __restrict__ cells, - GPU::SimpleVector *foundNtuplets, - GPU::VecArray &tmpNtuplet, + GPUCACell * __restrict__ cells, + TuplesOnGPU::Container & foundNtuplets, + AtomicPairCounter & apc, + TmpTuple & tmpNtuplet, const unsigned int minHitsPerNtuplet) const { // the building process for a track ends if: @@ -206,34 +156,35 @@ class GPUCACell { // the ntuplets is then saved if the number of hits it contains is greater // than a threshold - tmpNtuplet.push_back_unsafe(theInnerHitId); - assert(tmpNtuplet.size()<=3); + tmpNtuplet.push_back_unsafe(theDoubletId); + assert(tmpNtuplet.size()<=4); - if ((unsigned int)(tmpNtuplet.size()) >= minHitsPerNtuplet-1) { - Quadruplet tmpQuadruplet; - for (unsigned int i = 0; i < minHitsPerNtuplet-1; ++i) { - tmpQuadruplet.hitId[i] = tmpNtuplet[i]; - } - tmpQuadruplet.hitId[minHitsPerNtuplet-1] = theOuterHitId; - foundNtuplets->push_back(tmpQuadruplet); - } - else { + if(theOuterNeighbors.size()>0) { // continue for (int j = 0; j < theOuterNeighbors.size(); ++j) { auto otherCell = theOuterNeighbors[j]; - cells[otherCell].find_ntuplets(cells, foundNtuplets, tmpNtuplet, + 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() < 3); + assert(tmpNtuplet.size() < 4); } #endif // __CUDACC__ - GPU::VecArray< unsigned int, 40> theOuterNeighbors; + GPU::VecArray< uint32_t, 36> theOuterNeighbors; + GPU::VecArray< uint16_t, 42> theTracks; - int theDoubletId; - int theLayerPairId; + int32_t theDoubletId; + int32_t theLayerPairId; private: float theInnerZ; diff --git a/RecoPixelVertexing/PixelTriplets/plugins/GPUHitsAndDoublets.h b/RecoPixelVertexing/PixelTriplets/plugins/GPUHitsAndDoublets.h deleted file mode 100644 index 4f24c3331f460..0000000000000 --- a/RecoPixelVertexing/PixelTriplets/plugins/GPUHitsAndDoublets.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Author: Felice Pantaleo, CERN -// - -#ifndef RecoPixelVertexing_PixelTriplets_GPUHitsAndDoublets_h -#define RecoPixelVertexing_PixelTriplets_GPUHitsAndDoublets_h - -#include - -struct GPULayerHits -{ - unsigned int layerId; - size_t size; - float * x; - float * y; - float * z; -}; - -struct HostLayerHits -{ - unsigned int layerId; - size_t size; - std::vector x; - std::vector y; - std::vector z; -}; - -struct GPULayerDoublets -{ - size_t size; - unsigned int innerLayerId; - unsigned int outerLayerId; - unsigned int * indices; -}; - -struct HostLayerDoublets -{ - size_t size; - unsigned int innerLayerId; - unsigned int outerLayerId; - std::vector indices; -}; - -#endif 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 index d4b44f64573c6..02a175fcc2903 100644 --- a/RecoPixelVertexing/PixelTriplets/plugins/gpuPixelDoublets.h +++ b/RecoPixelVertexing/PixelTriplets/plugins/gpuPixelDoublets.h @@ -13,10 +13,11 @@ #include "RecoLocalTracker/SiPixelRecHits/plugins/siPixelRecHitsHeterogeneousProduct.h" #include "GPUCACell.h" +#include "CAConstants.h" namespace gpuPixelDoublets { - constexpr uint32_t MaxNumOfDoublets = 1024*1024*256; + constexpr uint32_t MaxNumOfDoublets = CAConstants::maxNumberOfDoublets(); // not really relevant template __device__ @@ -29,7 +30,7 @@ namespace gpuPixelDoublets { Hist const & __restrict__ hist, uint32_t const * __restrict__ offsets, siPixelRecHitsHeterogeneousProduct::HitsOnGPU const & __restrict__ hh, - GPU::VecArray< unsigned int, 256> * isOuterHitOfCell, + GPUCACell::OuterHitOfCell * isOuterHitOfCell, int16_t const * __restrict__ phicuts, float const * __restrict__ minz, float const * __restrict__ maxz, @@ -122,7 +123,8 @@ namespace gpuPixelDoublets { if (std::min(std::abs(int16_t(iphi[oi]-mep)), std::abs(int16_t(mep-iphi[oi]))) > iphicut) continue; if (z0cutoff(oi) || ptcut(oi)) continue; - auto ind = atomicInc(nCells, MaxNumOfDoublets); + 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); @@ -130,9 +132,10 @@ namespace gpuPixelDoublets { ++tot; } } +#ifdef GPU_DEBUG if (tooMany > 0) - printf("OuterHitOfCell full for %d in layer %d/%d, %d:%d %d,%d\n", i, inner, outer, kl, kh, nmin, tot); - + printf("OuterHitOfCell full for %d in layer %d/%d, %d,%d %d\n", i, inner, outer, nmin, tot, tooMany); +#endif } // loop in block... } @@ -144,7 +147,7 @@ namespace gpuPixelDoublets { void getDoubletsFromHisto(GPUCACell * cells, uint32_t * nCells, siPixelRecHitsHeterogeneousProduct::HitsOnGPU const * __restrict__ hhp, - GPU::VecArray * isOuterHitOfCell) + GPUCACell::OuterHitOfCell * isOuterHitOfCell) { constexpr int nPairs = 13; constexpr const uint8_t layerPairs[2*nPairs] = { 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 index 8497eba9f759f..c72c07ae5a721 100644 --- a/RecoPixelVertexing/PixelTriplets/python/caHitQuadrupletEDProducer_cfi.py +++ b/RecoPixelVertexing/PixelTriplets/python/caHitQuadrupletEDProducer_cfi.py @@ -2,7 +2,3 @@ from RecoPixelVertexing.PixelTriplets.caHitQuadrupletDefaultEDProducer_cfi import caHitQuadrupletDefaultEDProducer as _caHitQuadrupletDefaultEDProducer caHitQuadrupletEDProducer = _caHitQuadrupletDefaultEDProducer.clone() - -from Configuration.ProcessModifiers.gpu_cff import gpu -from RecoPixelVertexing.PixelTriplets.caHitQuadrupletHeterogeneousEDProducer_cfi import caHitQuadrupletHeterogeneousEDProducer as _caHitQuadrupletHeterogeneousEDProducer -gpu.toReplaceWith(caHitQuadrupletEDProducer, _caHitQuadrupletHeterogeneousEDProducer) diff --git a/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml b/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml index 1de3629887ec9..9f5d10ad020e9 100644 --- a/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml +++ b/RecoPixelVertexing/PixelTriplets/test/BuildFile.xml @@ -21,3 +21,4 @@ + 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/pixHits.ipynb b/RecoPixelVertexing/PixelTriplets/test/pixHits.ipynb index ee8295f85a00a..12027fe01cdd8 100644 --- a/RecoPixelVertexing/PixelTriplets/test/pixHits.ipynb +++ b/RecoPixelVertexing/PixelTriplets/test/pixHits.ipynb @@ -133,7 +133,53 @@ }, { "cell_type": "code", - "execution_count": 5, + "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": [ @@ -170,12 +216,19 @@ " 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" + " 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": 6, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ @@ -207,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -224,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -241,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -258,7 +311,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 38, "metadata": {}, "outputs": [ { @@ -278,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 39, "metadata": {}, "outputs": [ { @@ -336,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -345,7 +398,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 41, "metadata": {}, "outputs": [ { @@ -613,7 +666,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 42, "metadata": {}, "outputs": [ { @@ -714,7 +767,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 43, "metadata": {}, "outputs": [], "source": [ @@ -723,27 +776,268 @@ " '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": 16, + "execution_count": 44, "metadata": {}, "outputs": [], "source": [ - "def fishBone(q,t,p,c) :\n", - " return pd.DataFrame({ 'th' : t,\n", - " 'pz' : p,\n", - " 'curv' : c,\n", - " 'trackID' : q['trackID']\n", - " })\n" + "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": "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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" + }, + { + "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" + }, + { + "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": 17, + "execution_count": 47, "metadata": {}, "outputs": [ { @@ -758,87 +1052,137 @@ "540108\n", "483260\n", "931967\n", + "pentuplets\n", + "612902\n", + "triplets\n", + "111601\n", + "47552\n", "4230185\n", - " phi1 pt1 r1 trackID z1 phi2 pt2 r2 \\\n", - "0 -0.534839 1698 3.209876 10000005 -2.03853 -0.522814 1698 6.636066 \n", - "1 -0.534839 1698 3.209876 10000005 -2.03853 -0.522814 1698 6.636066 \n", - "2 -0.534839 1698 3.209876 10000005 -2.03853 -0.522814 1698 6.636066 \n", - "3 -0.534839 1698 3.209876 10000005 -2.03853 -0.522814 1698 6.636066 \n", - "4 -0.534839 1698 3.209876 10000005 -2.03853 -0.508068 1698 6.638935 \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", - " z2 phi3 pt3 r3 z3 phi4 pt4 r4 \\\n", - "0 -5.024552 -0.506715 1698 11.091832 -8.901971 -0.469588 1698 16.203526 \n", - "1 -5.024552 -0.506715 1698 11.091832 -8.901971 -0.464408 1698 16.201468 \n", - "2 -5.024552 -0.506715 1698 11.091832 -8.901971 -0.488653 1698 16.214857 \n", - "3 -5.024552 -0.506715 1698 11.091832 -8.901971 -0.471780 1698 16.204531 \n", - "4 -5.171747 -0.506715 1698 11.091832 -8.901971 -0.469588 1698 16.203526 \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", - " z4 \n", - "0 -13.062537 \n", - "1 -13.123804 \n", - "2 -13.366333 \n", - "3 -13.442224 \n", - "4 -13.062537 \n", - " phi1 pt1 r1 trackID z1 phi2 pt2 r2 \\\n", - "0 1.342908 791 3.215434 10000094 -6.674059 1.316544 791 6.533934 \n", - "1 0.042201 662 3.147929 10000100 -6.337495 0.071146 662 6.962423 \n", - "2 0.038396 662 3.150830 10000100 -6.388875 0.071146 662 6.962423 \n", - "3 -0.362996 487 2.798809 10000109 0.902371 -0.413166 487 7.010263 \n", - "4 -0.362996 487 2.798809 10000109 0.902371 -0.413166 487 7.010263 \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", - " z2 phi3 pt3 r3 z3 phi4 pt4 r4 \\\n", - "0 -14.353554 1.287120 791 10.669562 -23.916361 1.260540 791 14.376982 \n", - "1 -14.991426 0.107237 662 11.064787 -24.246792 0.133849 662 14.090476 \n", - "2 -14.991426 0.107237 662 11.064787 -24.246792 0.133849 662 14.090476 \n", - "3 1.127291 -0.463537 487 11.100529 1.352261 -2.994291 487 8.460827 \n", - "4 1.127291 -0.463537 487 11.100529 1.352261 -2.864020 487 5.959154 \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", - " z4 \n", - "0 -32.514194 \n", - "1 -31.091267 \n", - "2 -31.091267 \n", - "3 30.964153 \n", - "4 31.328598 \n", - " phi1 pt1 r1 trackID z1 phi2 pt2 r2 \\\n", - "0 -1.615744 740 3.405559 10000002 -9.927705 -1.644181 740 7.273387 \n", - "1 -1.615744 740 3.405559 10000002 -9.927705 -1.644181 740 7.273387 \n", - "2 1.914170 2464 2.671153 10000010 -9.608046 1.903031 2464 6.863056 \n", - "3 1.910948 2464 2.670602 10000010 -9.678793 1.903031 2464 6.863056 \n", - "4 1.922151 2464 2.672636 10000010 -9.662353 1.903031 2464 6.863056 \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", - " z2 phi3 pt3 r3 z3 phi4 pt4 r4 \\\n", - "0 -22.09573 -1.673348 740 10.764234 -33.038609 -1.692311 740 13.137235 \n", - "1 -22.09573 -1.672782 740 10.705758 -32.855053 -1.692311 740 13.137235 \n", - "2 -26.02704 1.898713 2464 8.804473 -33.619747 1.895945 2464 10.021165 \n", - "3 -26.02704 1.898713 2464 8.804473 -33.619747 1.895945 2464 10.021165 \n", - "4 -26.02704 1.898713 2464 8.804473 -33.619747 1.895945 2464 10.021165 \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", - " z4 \n", - "0 -40.457005 \n", - "1 -40.457005 \n", - "2 -38.362865 \n", - "3 -38.362865 \n", - "4 -38.362865 \n", - " phi1 pt1 r1 trackID z1 phi2 pt2 r2 \\\n", - "0 1.914170 2464 2.671153 10000010 -9.608046 1.898713 2464 8.804473 \n", - "1 1.910948 2464 2.670602 10000010 -9.678793 1.898713 2464 8.804473 \n", - "2 1.922151 2464 2.672636 10000010 -9.662353 1.898713 2464 8.804473 \n", - "3 2.955944 1148 2.702642 10000032 -10.496306 2.928084 1148 8.442572 \n", - "4 2.955944 1148 2.702642 10000032 -10.496306 2.928084 1148 8.442572 \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", - " z2 phi3 pt3 r3 z3 phi4 pt4 r4 \\\n", - "0 -33.619747 1.895945 2464 10.021165 -38.362865 1.890452 2464 12.449927 \n", - "1 -33.619747 1.895945 2464 10.021165 -38.362865 1.890452 2464 12.449927 \n", - "2 -33.619747 1.895945 2464 10.021165 -38.362865 1.890452 2464 12.449927 \n", - "3 -34.392139 2.919404 1148 10.328422 -42.279213 2.910286 1148 12.075643 \n", - "4 -34.392139 2.921753 1148 9.830442 -40.189011 2.910286 1148 12.075643 \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", - " z4 \n", - "0 -47.847328 \n", - "1 -47.847328 \n", - "2 -47.847328 \n", - "3 -49.587833 \n", - "4 -49.587833 \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" ] } ], @@ -862,18 +1206,79 @@ "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()" + "print t1123.head()\n", + "print t1230.head()\n", + "print t12123.head()" ] }, { "cell_type": "code", - "execution_count": 34, + "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": [ @@ -910,18 +1315,18 @@ " thcut2 = alignRPZ(quadc,'r',True)\n", " pzcut2 = alignRPZ(quadc,'phi',True)\n", "\n", - " curv = curvature(quadc,0.6,0.02,0.2,True)\n", - " rad = curvature(quadc,0.6,0.02,0.2,True,True)\n", - " field = rad/quadc['pt1']\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)<500],log=True, bins=100)\n", + " plt.hist(field[abs(field)<5],log=True, bins=100)\n", " plt.show()\n", - " print 'thcut,pzcut,curvcut',len(thcut)\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(curv[abs(curv)<0.4],log=True, bins=100)\n", + " plt.hist(dc[abs(dc)<0.3],log=True, bins=100)\n", " plt.show()\n", "\n", " print 'thcut2,pzcut2',len(thcut2)\n", @@ -936,18 +1341,22 @@ " pzcut = alignRZ(quadc,'phi',1.0,False)\n", " thcut2 = alignRPZ(quadc,'r',False)\n", " pzcut2 = alignRPZ(quadc,'phi',False)\n", - " curv = curvature(quadc,0.6,0.02,0.2,False)\n", - " rad = curvature(quadc,0.6,0.02,0.2,False,True)\n", - " field = rad/quadc['pt1']\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)<500],log=True, bins=100)\n", + " plt.hist(field[abs(field)<5],log=True, bins=100)\n", " plt.show()\n", - " print 'thcut,pzcut,curvcut',len(thcut)\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(curv[abs(curv)<0.4],log=True, bins=100)\n", + " plt.hist(dc[abs(dc)<0.3],log=True, bins=100)\n", " plt.show()\n", "\n", " \n", @@ -960,7 +1369,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 55, "metadata": {}, "outputs": [], "source": [ @@ -1054,7 +1463,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 58, "metadata": {}, "outputs": [ { @@ -1067,7 +1476,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1079,7 +1488,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 227975\n" + "thcut,pzcut,dcacut 227975\n" ] }, { @@ -1104,7 +1513,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1149,7 +1558,24 @@ }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADdZJREFUeJzt3X+oXPlZx/H30yypUOm1bUIt+dGk3FCMRVoYs//ZBXcxa5qmSMENVLcQNlSICP5jZIWKf6UqaEuDNeyGdAvduC5Yk27q6kbr+sdWk61SmizbjSE1N9Ym22oQW1yXPv5xRxluM/eeycyZc+eZ9wtC7px77sxz5t75zHee853vRGYiSarrDV0XIElql0EvScUZ9JJUnEEvScUZ9JJUnEEvScUZ9JJUnEEvScUZ9JJU3D1dFwCwadOm3LFjR9dlSNJMefHFF1/NzM1r7ddp0EfEfmD/4uIiFy9e7LIUSZo5EfHNJvt12rrJzLOZeXhhYaHLMiSpNHv0klRcp0EfEfsj4sTt27e7LEOSSrN1I0nF2bqRpOJs3UhScbZuJKk4WzeSVNy6ecOUNGt2HH3m/7++dmxfh5VIq7N1I0nF2bqRpOIMekkqzqCXpOKcRy9JxXkyVpKKs3UjScUZ9JJUnEEvScX5zlipocF3wkqzxJOxklScrRtJKs6gl6TiDHpJKs6gl6TiDHpJKs6gl6TiXNRMkopzHr0kFWfrRpKKM+glqTiDXpKKM+glqbhOV6+U2jK40uS1Y/s6rETqniN6SSrOEb3KG7aOvCN9zQuDXnPL9o7mRStBHxFvAv4W+O3M/GIbtyFNkqGvyhr16CPiZETcjIivr9i+NyJejogrEXF04Fu/ATw1yUIlSXen6cnYU8DewQ0RsQE4DjwI7AYORsTuiHgAuAzcnGCdkqS71Kh1k5nPR8SOFZv3AFcy8ypARJwGDgA/CryJ5fD/fkScy8wfTKxiqWW2cVTNOD36LcD1gctLwL2ZeQQgIj4KvDos5CPiMHAYYPv27WOUIUlaTWvz6DPz1GonYjPzRGb2MrO3efPmtsqQpLk3TtDfALYNXN7a39aY69FLUvvGCfoLwK6I2BkRG4GHgDOjXIHr0UtS+5pOr3wSeAF4d0QsRcShzHwdOAI8C7wEPJWZl0a5cUf0ktS+prNuDg7Zfg44d7c3nplngbO9Xu+Ru70OSdLqXAJBWsWwdXKkWeKHg0tScX44uCQV53r0klScrRtJKs7WjSQVZ+tGkoqzdSNJxdm6kaTibN1IUnEGvSQVZ9BLUnGejJWk4jpd1MzVKzVJLkAm3ZmtG0kqzqCXpOIMekkqzpOxklSc74yVpOJs3UhScQa9JBVn0EtScQa9JBVn0EtScQa9JBXnPHpJKs5FzaQJGLag2rVj+6ZcifTDOg16aZ4MPhn4BKBpMuilFrl0stYDT8ZKUnGO6DXTZnXEbBtH0+SIXpKKc0SvmTOro/hhHN2rbQa9tI44TVNtsHUjScVNfEQfET8B/BqwCTifmX806duQ5o3tHY2jUdBHxEngA8DNzHzPwPa9wCeBDcBjmXksM18CPhYRbwCeAAz6GTcsZNpuM1TrxU+K7R2NqumI/hTwaZaDG4CI2AAcBx4AloALEXEmMy9HxAeBXwE+N9lyVZGB3g1fJcyPRkGfmc9HxI4Vm/cAVzLzKkBEnAYOAJcz8wxwJiKeAT4/uXI1CwyQ9aXJ78PfWW3j9Oi3ANcHLi8B90bEfcAvAG8Ezg374Yg4DBwG2L59+xhlaFYYJu3ylZGGmfjJ2Mz8MvDlBvudAE4A9Hq9nHQdaseoYWL4rC+T+n34pD1bxgn6G8C2gctb+9sai4j9wP7FxcUxytAs8glg/TLE6xlnHv0FYFdE7IyIjcBDwJlRriAzz2bm4YWFhTHKkCStpun0yieB+4BNEbEEfDwzH4+II8CzLE+vPJmZl0a5cUf03XKantbi6L6GyOy+Pd7r9fLixYtdlzF3bJ+oDT4hTE9EvJiZvbX2c62bOWO4q22+Clh//HBwSSqu06D3ZKwktc/VKyWpuE579M66kWpzZtf60GnQZ+ZZ4Gyv13ukyzqq8wSsNN+cdVOIsx0k3YmtmxlkoGvW+Tc8Xc66kaTinHUjScXZo58Rw06oeqJV0loM+qJ8AtCssF/fPk/GrjP+0Wue+fffDk/GSlJxtm5aNs4IxfaL5tmwx44fdj46g15SCQ6MhjPoW+AfnDRZPqbG48nYKfLlpDRZoz4BzOtj0EXNOjKvf3CSps93xkpScfboh5jmiNv+o6Q2GfSS5tI8tU8N+glxVC5pvTLoBxjWkioy6MfgE4O0/vk47XjWTUTsj4gTt2/f7rIMSSrNefSS5l71E7O2bkbky0CptnEWU1uvfMOUJBXniL4BR/GSZpkjekkqzhG9JA1R5dW8I3pJKm6uR/RVnq0laTWO6CWpuFZG9BHxIWAf8Gbg8cz8yzZuR5K0tsZBHxEngQ8ANzPzPQPb9wKfBDYAj2Xmscz8AvCFiHgL8PuAQS+pvPX6pqpRRvSngE8DT/zfhojYABwHHgCWgAsRcSYzL/d3+a3+9yWpjFk7v9e4R5+ZzwPfXbF5D3AlM69m5mvAaeBALPsE8KXM/OrkypUkjWrcHv0W4PrA5SXgXuBXgfuBhYhYzMzPrPzBiDgMHAbYvn37mGU0N2vPxJI0rlZOxmbmp4BPrbHPCeAEQK/XyzbqkCSNP73yBrBt4PLW/rZGXI9ekto3btBfAHZFxM6I2Ag8BJxp+sOZeTYzDy8sLIxZhiRpmFGmVz4J3Adsiogl4OOZ+XhEHAGeZXl65cnMvDTCde4H9i8uLo5W9Yjsy0uaZ42DPjMPDtl+Djh3NzfuJ0xJUvtcAkGSivPDwSWpuE6D3pOxktQ+WzeSVJytG0kqztaNJBVn60aSirN1I0nF2bqRpOJs3UhScQa9JBVn0EtScZ6MlaTiPBkrScW18lGC64Fr0EvSMnv0klScQS9JxXkyVpKK67RHP+mPErQvL0k/zNaNJBVn0EtScWWnV0pSl1a2kq8d29dRJY7oJak8g16SijPoJak459FLUnEuaiZJxdm6kaTiDHpJKs6gl6TiDHpJKs6gl6TiDHpJKs61biRpCgbXvpn2ujeO6CWpuIkHfUS8KyIej4inJ33dkqTRNQr6iDgZETcj4usrtu+NiJcj4kpEHAXIzKuZeaiNYiVJo2s6oj8F7B3cEBEbgOPAg8Bu4GBE7J5odZKksTUK+sx8Hvjuis17gCv9EfxrwGngwITrkySNaZwe/Rbg+sDlJWBLRLwtIj4DvC8ifnPYD0fE4Yi4GBEXb926NUYZkqTVTHx6ZWZ+B/hYg/1OACcAer1eTroOSdKycUb0N4BtA5e39rc15nr0ktS+cYL+ArArInZGxEbgIeDMKFfgevSS1L6m0yufBF4A3h0RSxFxKDNfB44AzwIvAU9l5qVRbtwRvSS1r1GPPjMPDtl+Djh3tzeemWeBs71e75G7vQ5J0ur8zFhJKs7PjJWk4lzUTJKKs3UjScXZupGk4mzdSFJxBr0kFWePXpKKs0cvScXZupGk4gx6SSrOHr0kFWePXpKKs3UjScUZ9JJUnEEvScUZ9JJUnLNuJKk4Z91IUnG2biSpOINekooz6CWpOINekooz6CWpOINekoq7p8sbj4j9wP7FxcW7vo4dR5+ZXEGSNAWDuXXt2L7Wb8959JJUnK0bSSrOoJek4gx6SSrOoJek4gx6SSrOoJek4gx6SSrOoJek4iIzu66BiLgFfLPrOka0CXi16yI6NO/HD94HHn/3x//OzNy81k7rIuhnUURczMxe13V0Zd6PH7wPPP7ZOX5bN5JUnEEvScUZ9HfvRNcFdGzejx+8Dzz+GWGPXpKKc0QvScUZ9A1FxFsj4q8i4pX+/2+5wz7vjYgXIuJSRHwtIn6xi1rb0OT4+/v9RUT8R0R8cdo1tiEi9kbEyxFxJSKO3uH7b4yIP+l//+8jYsf0q2xXg/vgZyLiqxHxekR8uIsa29Tg+H89Ii73H/PnI+KdXdS5GoO+uaPA+czcBZzvX17pe8AvZ+ZPAnuBP4yIH5tijW1qcvwAvwf80tSqalFEbACOAw8Cu4GDEbF7xW6HgH/PzEXgD4BPTLfKdjW8D/4F+Cjw+elW176Gx/+PQC8zfwp4Gvjd6Va5NoO+uQPAZ/tffxb40ModMvMbmflK/+t/BW4Ca76ZYUasefwAmXke+M9pFdWyPcCVzLyama8Bp1m+HwYN3i9PAz8bETHFGtu25n2Qmdcy82vAD7oosGVNjv9vMvN7/YtfAbZOucY1GfTNvT0zv9X/+t+At6+2c0TsATYC/9x2YVMy0vEXsQW4PnB5qb/tjvtk5uvAbeBtU6luOprcB5WNevyHgC+1WtFd6PTDwdebiHgO+PE7fOvRwQuZmRExdLpSRLwD+BzwcGbOzChnUscvzaOI+AjQA97fdS0rGfQDMvP+Yd+LiG9HxDsy81v9IL85ZL83A88Aj2bmV1oqtRWTOP5ibgDbBi5v7W+70z5LEXEPsAB8ZzrlTUWT+6CyRscfEfezPCB6f2b+95Rqa8zWTXNngIf7Xz8M/PnKHSJiI/BnwBOZ+fQUa5uGNY+/oAvArojY2f/dPsTy/TBo8H75MPDXWevNKU3ug8rWPP6IeB/wx8AHM3N9DoAy038N/rHcdz0PvAI8B7y1v70HPNb/+iPA/wD/NPDvvV3XPq3j71/+O+AW8H2W+5k/13XtYx73zwPfYPlcy6P9bb/D8oMa4EeAPwWuAP8AvKvrmju4D366/7v+L5ZfzVzquuYpH/9zwLcHHvNnuq555T/fGStJxdm6kaTiDHpJKs6gl6TiDHpJKs6gl6TiDHpJKs6gl6TiDHpJKu5/AVF1mi+KDsRIAAAAAElFTkSuQmCC\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": [ "
" ] @@ -1161,7 +1587,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 227975\n" + "thcut,pzcut,dcacut 227975\n" ] }, { @@ -1186,7 +1612,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1231,7 +1657,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1243,7 +1669,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 40709\n" + "thcut,pzcut,dcacut 40709\n" ] }, { @@ -1268,7 +1694,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1313,7 +1739,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1325,7 +1751,24 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 40709\n" + "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" ] }, { @@ -1350,7 +1793,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1395,7 +1838,7 @@ }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADqVJREFUeJzt3V+MXOdZx/HvDwcHKS0pbaoq8h/sYCtir2hYJZWoqlwAtRNcl6oCGyQKsmIFYQQXSHVVLsoFIkWCi6iGalEsF1TZskIAR9kqBUTkXpgSp0pTu5bp1qTyWqF2CDJ/hDBuHi52ko5WnvXZnRmP993vR7J25p0zZ55nz/rRu89595xUFZKkdv3ApAOQJI2XhV6SGmehl6TGWeglqXEWeklqnIVekhpnoZekxlnoJalxFnpJatwdkw4A4J577qktW7ZMOgxJWlVeeuml16vqvTfb7rYo9Fu2bOH06dOTDkOSVpUk3+myna0bSWqchV6SGmehl6TGWeglqXETLfRJdiWZuXr16iTDkKSmTbTQV9WzVbX/7rvvnmQYktQ0WzeS1DgLvSQ17rb4g6lhbDn43NuPX33i0QlGIkm3J2f0ktQ4C70kNW7khT7Jw0m+kuTzSR4e9f4lScvTqdAnOZzkcpIzi8Z3JDmfZC7Jwd5wAf8F/BAwP9pwJUnL1XVGfwTY0T+QZB1wCNgJTAF7k0wBX6mqncAngd8bXaiSpJXoVOir6iTwxqLhB4G5qrpQVdeAY8Duqnqz9/q/A3eOLFJJ0ooMs7xyA3Cx7/k88FCSjwEfBt4FfG7Qm5PsB/YDbN68eYgwJElLGfk6+qp6Bnimw3YzwAzA9PR0jToOSdKCYVbdXAI29T3f2BvrzIuaSdL4DVPoXwS2J9maZD2wBzixnB14UTNJGr+uyyuPAqeA+5PMJ9lXVdeBA8DzwDngeFWdXc6HO6OXpPHr1KOvqr0DxmeB2ZV+eFU9Czw7PT392Er3IUlamjcekaTGeeMRSWqcFzWTpMbZupGkxtm6kaTG2bqRpMbZupGkxtm6kaTG2bqRpMbZupGkxtm6kaTG2bqRpMZZ6CWpcRZ6SWqcJ2MlqXGejJWkxtm6kaTGWeglqXEWeklqnIVekhpnoZekxrm8UpIa5/JKSWqcrRtJapyFXpIaZ6GXpMZZ6CWpcRZ6SWqchV6SGjeWQp/kriSnk/zcOPYvSequU6FPcjjJ5SRnFo3vSHI+yVySg30vfRI4PspAJUkr03VGfwTY0T+QZB1wCNgJTAF7k0wl+Rngm8DlEcYpSVqhO7psVFUnk2xZNPwgMFdVFwCSHAN2A+8A7mKh+P9PktmqenPxPpPsB/YDbN68eaXxS5JuolOhH2ADcLHv+TzwUFUdAEjyq8DrNyryAFU1A8wATE9P1xBxSJKWMEyhX1JVHbnZNkl2Abu2bds2rjAkac0bZtXNJWBT3/ONvbHOvKiZJI3fMIX+RWB7kq1J1gN7gBPL2YGXKZak8eu6vPIocAq4P8l8kn1VdR04ADwPnAOOV9XZ5Xy4M3pJGr+uq272DhifBWZX+uH26CVp/LzxiCQ1zmvdSFLjvGesJDXO1o0kNc7WjSQ1ztaNJDXO1o0kNc7WjSQ1zkIvSY2zRy9JjbNHL0mNs3UjSY2z0EtS4+zRS1Lj7NFLUuNs3UhS4yz0ktQ4C70kNc5CL0mNs9BLUuNcXilJjXN5pSQ1ztaNJDXOQi9JjbPQS1LjLPSS1DgLvSQ1zkIvSY0beaFP8uNJPp/k6SS/Pur9S5KWp1OhT3I4yeUkZxaN70hyPslckoMAVXWuqh4HfgH4qdGHLElajq4z+iPAjv6BJOuAQ8BOYArYm2Sq99pHgOeA2ZFFKklakU6FvqpOAm8sGn4QmKuqC1V1DTgG7O5tf6KqdgK/PGifSfYnOZ3k9JUrV1YWvSTppu4Y4r0bgIt9z+eBh5I8DHwMuJMlZvRVNQPMAExPT9cQcUiSljBMob+hqnoBeKHLtkl2Abu2bds26jAkST3DrLq5BGzqe76xN9aZFzWTpPEbptC/CGxPsjXJemAPcGI5O/AyxZI0fl2XVx4FTgH3J5lPsq+qrgMHgOeBc8Dxqjq7nA93Ri9J49epR19VeweMzzLEEkp79JI0ft54RJIa57VuJKlx3jNWkhpn60aSGmfrRpIaZ+tGkhpn60aSGmfrRpIaZ6GXpMbZo5ekxtmjl6TG2bqRpMZZ6CWpcfboJalx9uglqXG2biSpcRZ6SWqchV6SGmehl6TGWeglqXEur5Skxrm8UpIaZ+tGkhp3x6QDGKUtB597+/GrTzw6wUgk6fbhjF6SGmehl6TGWeglqXEWeklq3FhOxib5KPAo8MPAU1X15XF8jiTp5jrP6JMcTnI5yZlF4zuSnE8yl+QgQFX9dVU9BjwO/OJoQ5YkLcdyWjdHgB39A0nWAYeAncAUsDfJVN8mv9t7XZI0IZ0LfVWdBN5YNPwgMFdVF6rqGnAM2J0FnwW+VFVfG124kqTlGvZk7AbgYt/z+d7YbwI/DXw8yeM3emOS/UlOJzl95cqVIcOQJA0ylpOxVfUk8ORNtplJ8hqwa/369T85jjgkScPP6C8Bm/qeb+yNdeJFzSRp/IYt9C8C25NsTbIe2AOc6PpmL1MsSeO3nOWVR4FTwP1J5pPsq6rrwAHgeeAccLyqznbdpzN6SRq/zj36qto7YHwWmB1ZRJKkkfIOU5LUOO8wJUmNc0YvSY1zRi9JjfMyxZLUOFs3ktQ4WzeS1DhbN5LUOAu9JDVuLFev7CrJLmDXtm3bRr7vLQefe/vxq088OvL9S9JqYY9ekhpn60aSGmehl6TGWeglqXHNnozt54lZSWuZJ2MlqXG2biSpcRZ6SWqchV6SGmehl6TGeZliSWqcq24kqXG2biSpcRZ6SWrcRP8ydhL8K1lJa40zeklq3Jqb0ffrn92DM3xJbXJGL0mNG3mhT3JfkqeSPD3qfUuSlq9ToU9yOMnlJGcWje9Icj7JXJKDAFV1oar2jSNYSdLydZ3RHwF29A8kWQccAnYCU8DeJFMjjU6SNLROhb6qTgJvLBp+EJjrzeCvAceA3SOOT5I0pGFW3WwALvY9nwceSvIe4PeB9yf5VFX9wY3enGQ/sB9g8+bNQ4QxHq63l9SKkS+vrKp/Ax7vsN0MMAMwPT1do45DkrRgmFU3l4BNfc839sY68+qVkjR+w8zoXwS2J9nKQoHfA/zSSKKakMV/QCVJLei6vPIocAq4P8l8kn1VdR04ADwPnAOOV9XZ5Xy4lymWpPHrNKOvqr0DxmeB2ZFGJEkaKe8wJUmN8w5TktQ4Z/SS1Dhn9JLUOC9TLEmNs3UjSY2zdSNJjbN1I0mNs9BLUuMmenPwJLuAXdu2bZtkGDd1u12y+HaLR9LtzR69JDXO1o0kNc5CL0mNs9BLUuM8GXsb8OSqpHHyZKwkNc7WjSQ1zkIvSY2z0EtS4yz0ktQ4V90s03JXyAzavn98rejyvXMF0vL4/VIXrrqRpMbZupGkxlnoJalxFnpJapyFXpIaZ6GXpMZZ6CWpcSNfR5/kLuBPgGvAC1X1xVF/hiSpu04z+iSHk1xOcmbR+I4k55PMJTnYG/4Y8HRVPQZ8ZMTxSpKWqWvr5giwo38gyTrgELATmAL2JpkCNgIXe5t9bzRhSpJWqlOhr6qTwBuLhh8E5qrqQlVdA44Bu4F5Fop95/1LksZnmB79Br4/c4eFAv8Q8CTwuSSPAs8OenOS/cB+gM2bNw8RxuQs93o1476+jdc9ublBx8Dvl26lW/1/deQnY6vqv4Ff67DdDDADMD09XaOOQ5K0YJjWyiVgU9/zjb2xzpLsSjJz9erVIcKQJC1lmEL/IrA9ydYk64E9wInl7MCrV0rS+HVdXnkUOAXcn2Q+yb6qug4cAJ4HzgHHq+rscj7cGb0kjV+nHn1V7R0wPgvMrvTDq+pZ4Nnp6enHVroPSdLSJrr80Rm9JI2fd5iSpMY5o5ekxjmjl6TGpWryf6uU5ArwnRW+/R7g9RGGsxqstZzXWr6w9nJea/nCaHL+0ap67802ui0K/TCSnK6q6UnHcSuttZzXWr6w9nJea/nCrc3Zi45JUuMs9JLUuBYK/cykA5iAtZbzWssX1l7Oay1fuIU5r/oevSRpaS3M6CVJS1jVhX7APWubkuTVJN9I8nKS072xdyf52yTf6n39kUnHOYwb3ZN4UI5Z8GTvmL+S5IHJRb4yA/L9TJJLveP8cpJH+l77VC/f80k+PJmoh5NkU5J/SPLNJGeT/FZvvMnjvES+kznOVbUq/wHrgG8D9wHrga8DU5OOawx5vgrcs2jsD4GDvccHgc9OOs4hc/wQ8ABw5mY5Ao8AXwICfAD46qTjH1G+nwF+5wbbTvV+tu8EtvZ+5tdNOocV5Hwv8EDv8TuBf+7l1uRxXiLfiRzn1TyjH3TP2rVgN/CF3uMvAB+dYCxDqxvfk3hQjruBP68F/wi8K8m9tybS0RiQ7yC7gWNV9b9V9S/AHAs/+6tKVb1WVV/rPf5PFi5tvoFGj/MS+Q4y1uO8mgv9je5Zu9Q3crUq4MtJXurdZxfgfVX1Wu/xvwLvm0xoYzUox5aP+4Fem+JwXzuuuXyTbAHeD3yVNXCcF+ULEzjOq7nQrxUfrKoHgJ3AbyT5UP+LtfB7X9NLp9ZCjsCfAj8G/ATwGvBHkw1nPJK8A/hL4Ler6j/6X2vxON8g34kc59Vc6Ie+Z+1qUFWXel8vA3/Fwq9z333r19je18uTi3BsBuXY5HGvqu9W1feq6k3gz/j+r+3N5JvkB1koel+sqmd6w80e5xvlO6njvJoL/dD3rL3dJbkryTvfegz8LHCGhTw/0dvsE8DfTCbCsRqU4wngV3qrMj4AXO371X/VWtR//nkWjjMs5LsnyZ1JtgLbgX+61fENK0mAp4BzVfXHfS81eZwH5Tux4zzps9NDntl+hIWz2d8GPj3peMaQ330snIn/OnD2rRyB9wB/D3wL+Dvg3ZOOdcg8j7Lwa+z/sdCb3DcoRxZWYRzqHfNvANOTjn9E+f5FL59Xev/p7+3b/tO9fM8DOycd/wpz/iALbZlXgJd7/x5p9Tgvke9EjrN/GStJjVvNrRtJUgcWeklqnIVekhpnoZekxlnoJalxFnpJapyFXpIaZ6GXpMb9P3O1whJAOXBIAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1407,7 +1850,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 152561\n" + "thcut,pzcut,dcacut 152561\n" ] }, { @@ -1432,7 +1875,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1477,7 +1920,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAEIhJREFUeJzt3X2sZHddx/H3xy2LkYfysCtg22VLthA2mIBOimgIVWmytWyLSLAbScA03QCp/xgTazAh0X8KKglNa2BDm4KRltpo3bVLyoM0NaTFXQSRtgGWFejWyhaQm+BTQb7+MbN0ut1777l3Hs7c332/kk1nzpx75nOnc7/3d7/nN7+TqkKS1K6f6DuAJGm2LPSS1DgLvSQ1zkIvSY2z0EtS4yz0ktQ4C70kNc5CL0mNs9BLUuPO6jsAwLZt22rnzp19x5CkDeVzn/vct6tq+2r79Vrok+wF9u7atYujR4/2GUWSNpwk3+iyX6+tm6o6VFX7zz777D5jSFLTei30SfYmObC0tNRnDElqmiN6SWqcs24kqXG2biSpcbZuJKlxtm4kqXG2biSpcb1+YKqqDgGHBoPBVX3mkGZl5zV3/vj216+9tMck2sxs3UhS42zdSFLjnHUjSY2zdSNJjbPQS1Lj7NFLUuPs0UtS42zdSFLjLPSS1LiFuGastNH5CVgtMk/GSlLjXOtGmrLx0b20CGzdSOtkQddG4clYSWqchV6SGmehl6TGWeglqXFOr5SkxrnWjSQ1ztaNJDXOQi9JjbPQS1LjLPSS1DgLvSQ1zkIvSY2z0EtS42ZS6JM8LcnRJK+bxfElSd11KvRJbkpyMsmXTtu+J8mXkxxLcs3YQ78P3DbNoJKk9ek6or8Z2DO+IckW4AbgEmA3sC/J7iQXAw8AJ6eYU5K0Tp0uPFJV9yTZedrmC4FjVXUcIMmtwOXA04GnMSz+/53kcFX9aGqJJUlrMskVps4BHhq7fwJ4ZVVdDZDkrcC3lyvySfYD+wF27NgxQQxJ0kpmNuumqm6uqr9b4fEDVTWoqsH27dtnFUOSNr1JCv3DwHlj988dbevMZYolafYmKfRHgAuSnJ9kK3AFcHAtB3CZYkmava7TK28B7gVekuREkiur6ofA1cBdwIPAbVV1/1qe3BG9JM1e11k3+5bZfhg4vN4nr6pDwKHBYHDVeo8hSVqZlxKUpMZ5KUFJapyLmklS42zdSFLjbN1IUuNs3UhS4yz0ktQ4e/SS1Dh79JLUOFs3ktQ4C70kNc4evSQ1zh69JDXO1o0kNc5CL0mNs9BLUuM8GStJjfNkrCQ1ztaNJDXOQi9JjbPQS1LjLPSS1DgLvSQ17qw+nzzJXmDvrl27+owhdbLzmjv7jiCti9MrJalxtm4kqXEWeklqnIVekhrX68lYaTMZP5n79Wsv7TGJNhtH9JLUOAu9JDXOQi9JjZt6oU/y0iTvT3J7krdP+/iSpLXpVOiT3JTkZJIvnbZ9T5IvJzmW5BqAqnqwqt4GvAn4pelHliStRdcR/c3AnvENSbYANwCXALuBfUl2jx67DLgTODy1pJKkdek0vbKq7kmy87TNFwLHquo4QJJbgcuBB6rqIHAwyZ3AR6YXV5ov17dRCyaZR38O8NDY/RPAK5NcBLwBeCorjOiT7Af2A+zYsWOCGJKklUz9A1NVdTdwd4f9DgAHAAaDQU07hyRpaJJZNw8D543dP3e0rbMke5McWFpamiCGJGklkxT6I8AFSc5PshW4Aji4lgO4TLEkzV7X6ZW3APcCL0lyIsmVVfVD4GrgLuBB4Laqun8tT+6IXpJmr+usm33LbD/MBFMoq+oQcGgwGFy13mNIklbmpQSlHriSpebJSwlKUuNcj146jR+SUmt6HdF7MlaSZs/WjSQ1ztaN1DNPzGrWnHUjYV9ebbN1I0mN81KCktQ4C70kNc4evTYt+/LaLHot9K51Iz2RM3A0C7ZuJKlxFnpJapyFXpIaZ6GXpMa5qJkkNc5PxkpS42zdSFLjXL1SWlDOqde0WOi1qfhpWG1Gtm4kqXGO6NU8R/Ha7JxeKUmNc3qlJDXOHr0kNc5CL0mNs9BLUuOcdaNmbJYPGG2W71PTY6FXk5xSKT3OQq8NbbMU9M3yfWo2LPRSI2zpaDkzKfRJXg9cCjwTuLGqPj6L55Ekra5zoU9yE/A64GRVvWxs+x7gfcAW4INVdW1V3QHckeTZwJ8CFnppBmzpqIu1TK+8GdgzviHJFuAG4BJgN7Avye6xXf5w9LgkqSedC31V3QN897TNFwLHqup4VT0G3ApcnqF3Ax+rqn860/GS7E9yNMnRRx99dL35JUmrmPQDU+cAD43dPzHa9jvAa4E3Jnnbmb6wqg5U1aCqBtu3b58whiRpOTM5GVtV1wHXrbZfkr3A3l27ds0ihiSJyUf0DwPnjd0/d7StE1evlKTZm7TQHwEuSHJ+kq3AFcDByWNJkqZlLdMrbwEuArYlOQG8q6puTHI1cBfD6ZU3VdX9azimrRutmVMKpbXpXOirat8y2w8Dh9fz5FV1CDg0GAyuWs/XS5JW1+sSCI7opdlwOQSN67XQO6KXZm+5Vpe/ADYPLzwiSY2zdaMNwROw0vrZutFCsbcsTZ/r0UublL9UNw9bN5Kmyl8gi8fWjZ7k9H64P6zSxmbrRr1w1CfNj4VevXNGjTRbvc6jT7I3yYGlpaU+Y0hS0+zRa2E50pemw0/GSlLjLPSS1DgLvSQ1zg9MSVqW02Db4MlYSU/gSfD2OI9ec2MB2dgc3W9cFnpJm8Zm/WVloddMOYrfGPz/1DYLvVa1WUdBUiss9JIm4l8Di89Cr6nwh11aXC5qJkmNcx691sR+vcC/4DYaWzeSFp4DjMlY6CX1yiI+exb6TcYfKm0Utoemx9UrJalxjug3sUlH9464pI3BQi/Aoq3F0OV9uNw+fbUiN0I7dOqtmyQvSnJjktunfWxJ0tp1GtEnuQl4HXCyql42tn0P8D5gC/DBqrq2qo4DV1roJS3CaNe/Vru3bm4Grgc+fGpDki3ADcDFwAngSJKDVfXAtENK0lpY3J+oU+umqu4Bvnva5guBY1V1vKoeA24FLp9yPknShCbp0Z8DPDR2/wRwTpLnJnk/8Iokf7DcFyfZn+RokqOPPvroBDEkSSuZ+qybqvoO8LYO+x0ADgAMBoOadg5J0tAkhf5h4Lyx++eOtnWWZC+wd9euXRPE2NwW4WSX1IXv1f5M0ro5AlyQ5PwkW4ErgINrOUBVHaqq/WefffYEMSRJK+k6vfIW4CJgW5ITwLuq6sYkVwN3MZxeeVNV3b+WJ3dEPx/OQNBmMOv3+TT/Ipn3XzedCn1V7Vtm+2Hg8Hqf3PXoJWn2XNRMkhrX61o3tm6kzWmR24ld2iqLnP9Meh3RezJWkmbPEf0CczqaNB8bbYS+Vo7oJalxnoyVpMZZ6CWpcfboJW1Kk1zNaqOxRy9JjbN1I0mNs9BLUuPs0S+AafUBnXcv9WtRfwbt0UtS42zdSFLjLPSS1DgLvSQ1rtdCn2RvkgNLS0t9xpCkpnkyVpIaZ+tGkhpnoZekxlnoJalxFnpJapyFXpIat+HXulnUtSX61so62pIm5/RKSWqcrRtJapyFXpIaZ6GXpMZZ6CWpcRZ6SWqchV6SGmehl6TGTf0DU0meBvw58Bhwd1X95bSfQ5LUXacRfZKbkpxM8qXTtu9J8uUkx5JcM9r8BuD2qroKuGzKeSVJa9S1dXMzsGd8Q5ItwA3AJcBuYF+S3cC5wEOj3f5vOjElSevVqdBX1T3Ad0/bfCFwrKqOV9VjwK3A5cAJhsW+8/ElSbMzSY/+HB4fucOwwL8SuA64PsmlwKHlvjjJfmA/wI4dOyaI0Z8uC6pNa9E1FymTtF5TPxlbVf8J/HaH/Q4ABwAGg0FNO4ckaWiS1srDwHlj988dbessyd4kB5aWliaIIUlaySSF/ghwQZLzk2wFrgAOruUALlMsSbPXdXrlLcC9wEuSnEhyZVX9ELgauAt4ELitqu5fy5M7opek2evUo6+qfctsPwwcXu+TV9Uh4NBgMLhqvceQJK2s1+mPjuglafa8lKAkNc4PNElS42zdSFLjUtX/Z5WSPAp8A9gGfLvnOGtl5vnZiLnNPD8bMfekmV9YVdtX22khCv0pSY5W1aDvHGth5vnZiLnNPD8bMfe8Mtujl6TGWeglqXGLVugP9B1gHcw8Pxsxt5nnZyPmnkvmherRS5Kmb9FG9JKkKet7Hv1zknwiyVdH/332Mvu9J8n9SR5Mcl2SzDvrWJaumXck+fgo8wNJds436ROydMo82veZo4Xrrp9nxmWyrJo7ycuT3Dt6f3wxyW/2lPVM108ef/ypST46evyzfb4fxjKtlvl3R+/dLyb5VJIX9pHztEwrZh7b7zeSVJKFmIXTJXeSN41e7/uTfGSqAaqqt3/Ae4BrRrevAd59hn1+EfgMsGX0717gokXOPHrsbuDi0e2nAz+16JlHj78P+AhwfZ/vjTW8P14MXDC6/TPAI8Cz5pxzC/A14EXAVuCfgd2n7fMO4P2j21cAH+35te2S+ZdPvW+Bt2+EzKP9ngHcA9wHDPrMvIbX+gLg88CzR/d/epoZ+m7dXA58aHT7Q8Drz7BPAT/J8AV6KvAU4FtzSXdmq2YeXST9rKr6BEBVfb+q/mt+EZ+ky+tMkp8Hngd8fE65VrNq7qr6SlV9dXT734CTwKofIJmy5a6fPG78e7kd+NU+/zKlQ+aq+vTY+/Y+Hr8WdF+6vM4Afwy8G/ifeYZbQZfcVwE3VNV/AFTVyWkG6LvQP6+qHhnd/neGReYJqupe4NMMR2qPAHdV1YPzi/gkq2ZmOMr8XpK/TvL5JH+SZMv8Ij7JqpmT/ATwZ8DvzTPYKrq81j+W5EKGA4KvzTrYac50/eRzltunhtdyWAKeO5d0Z9Yl87grgY/NNNHqVs2c5OeA86pqkS6y3OW1fjHw4iSfSXJfkj3TDDD1a8aeLskngeef4aF3jt+pqkrypClASXYBL+Xx0cQnkry6qv5h6mEff86JMjN8XV8NvAL4JvBR4K3AjdNN+rgpZH4HcLiqTsxzoDmF3KeO8wLgL4C3VNWPpptyc0vyZmAAvKbvLCsZDVbey/BnbaM5i2H75iKGte6eJD9bVd+b1sFnqqpeu9xjSb6V5AVV9cjoB/VMf678OnBfVX1/9DUfA14FzKzQTyHzCeALVXV89DV3AL/ADAv9FDK/Cnh1kncwPKewNcn3q2rZE17TMIXcJHkmcCfwzqq6b0ZRV9Ll+smn9jmR5CzgbOA784l3Rp2u+ZzktQx/6b6mqv53TtmWs1rmZwAvA+4eDVaeDxxMcllVHZ1byifr8lqfAD5bVT8A/jXJVxgW/iPTCNB36+Yg8JbR7bcAf3uGfb4JvCbJWUmewnBU0WfrpkvmI8CzkpzqFf8K8MAcsi1n1cxV9VtVtaOqdjJs33x41kW+g1VzZ3i94r9hmPf2OWYb1+X6yePfyxuBv6/RWbeerJo5ySuADwCXTbtnvE4rZq6qparaVlU7R+/j+xhm77PIQ7f3xx0MR/Mk2cawlXN8agl6Phv9XOBTwFeBTwLPGW0fAB8cO2P9AYbF/QHgvYueeXT/YuCLwL8ANwNbFz3z2P5vZTFm3XR5f7wZ+AHwhbF/L+8h668BX2F4fuCdo21/xLDQwHBCwV8Bx4B/BF60AK/vapk/yXDiw6nX9eCiZz5t37tZgFk3HV/rMGw7PTCqGVdM8/n9ZKwkNa7v1o0kacYs9JLUOAu9JDXOQi9JjbPQS1LjLPSS1DgLvSQ1zkIvSY37f6Ie9kXWoTyuAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -1489,7 +1932,24 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 152561\n" + "delta curv\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAD2hJREFUeJzt3W2sZdVdx/HvzxmhCdUROlgbYHqHXCRiY2w8QmKjaewTCFMabQzYmKqESWswvjHpEPRNk0ZqTEybEsmkpdNqhGKb1BkYRdqK+AKVofaBh9AOFMNMqtjWjq1paLB/X9w9eHqdO3Me7z5n3e8nmcw5++xz7lrn4XfX+e+19k1VIUlq1w/03QBJ0nwZ9JLUOINekhpn0EtS4wx6SWqcQS9JjTPoJalxBr0kNc6gl6TGbe+7AQA7d+6slZWVvpshSUvlkUce+VpVnX+m/XoN+iR7gD2rq6scOXKkz6ZI0tJJ8q+j7Ndr6aaqDlXV3h07dvTZDElqmjV6SWpcr0GfZE+S/SdOnOizGZLUNEs3ktQ4SzeS1DiDXpIaZ41ekhpnjV6SGrcQK2OlRbWy794XLz9z69U9tkSanDV6SWqcNXpJalyvpZuqOgQcGgwGN/bZDmnYcLlGaoGlG0lqnEEvSY0z6CWpcQa9JDXOWTeS1DhXxkpS4yzdSFLjDHpJapxBL0mNM+glqXEGvSQ1zumVktQ4p1dKUuMs3UhS4wx6SWqcQS9JjTPoJalxBr0kNc6gl6TGGfSS1DgXTElS41wwJUmNs3QjSY0z6CWpcQa9JDXOoJekxhn0ktQ4g16SGmfQS1LjDHpJapxBL0mNM+glqXEGvSQ1bi5Bn+ScJEeSXDOPx5ckjW6koE9yR5Lnkjy6bvuVSZ5McjTJvqGb3gXcPcuGSpImM+qI/gBw5fCGJNuA24CrgMuA65NcluQNwOPAczNspyRpQttH2amqHkyysm7z5cDRqnoaIMldwLXAS4FzWAv/7yQ5XFXfm1mLJUljGSnoN3AB8OzQ9WPAFVV1E0CS3wC+tlHIJ9kL7AXYtWvXFM2QJJ3O3GbdVNWBqrrnNLfvr6pBVQ3OP//8eTVDkra8aYL+OHDR0PULu22SpAUyTdA/DFySZHeSs4DrgIPjPIB/M1aS5m/U6ZV3Ag8BlyY5luSGqnoBuAm4D3gCuLuqHhvnh/s3YyVp/kaddXP9BtsPA4cn/eFJ9gB7VldXJ30IadOs7Lv3+64/c+vVPbVEGk+vp0BwRC9J8+e5biSpcdPMo5easb4sI7Wk1xG9s24kaf6s0UtS46zRS1LjLN1IUuMs3UhS4yzdSFLjDHpJapxBL0mN82CsJDXOg7GS1DhLN5LUOINekhpn0EtS4zwYK0mN82CsJDXO0o0kNc6gl6TGGfSS1DiDXpIaZ9BLUuOcXilJjXN6pSQ1bnvfDZCW1cq+e1+8/MytV/fYEun0rNFLUuMMeklqnKUbbVnDpRepZY7oJalxBr0kNc6gl6TGGfSS1DhXxkpS43qddVNVh4BDg8Hgxj7bIU3LxVNaZJZuJKlxBr0kNc6gl6TGuTJWW4qrYbUVOaKXpMYZ9JLUOEs30ow51VKLxhG9JDXOoJekxhn0ktQ4g16SGjfzg7FJfgL4XWAn8Omq+tNZ/wxpWWw0b9+DtNpMI43ok9yR5Lkkj67bfmWSJ5McTbIPoKqeqKp3AL8KvGb2TZYkjWPUEf0B4APAR09uSLINuA14A3AMeDjJwap6PMmbgXcCfzbb5krjczWstrqRRvRV9SDwjXWbLweOVtXTVfVd4C7g2m7/g1V1FfC2WTZWkjS+aWr0FwDPDl0/BlyR5LXALwNnA4c3unOSvcBegF27dk3RDEnS6cz8YGxVPQA8MMJ++4H9AIPBoGbdDknSmmmmVx4HLhq6fmG3bWT+KUFJmr9UjTaYTrIC3FNVr+qubwe+BLyOtYB/GPi1qnps3EYMBoM6cuTIuHeTNrRMB2CdaqlJJXmkqgZn2m/U6ZV3Ag8BlyY5luSGqnoBuAm4D3gCuHuSkJckzddINfqqun6D7Yc5zQHXM0myB9izuro66UNIks6g11MgVNWhqtq7Y8eOPpshSU3zfPRSzzx/veat1xG9s24kaf5GnnUzT8660Sws00ybUTi615nMdNaNJGl5GfSS1LheD8Y6vVKjaq0sI22mXoO+qg4BhwaDwY19tkNaRM7G0axYupGkxhn0ktQ459FLUuM8BYIkNc5TIGhhOdPm/3hgVtOwRi9JjXNELy0ZR/calyN6SWqcK2O1UKzLS7PnrBtJapylG0lqnAdj1QsPKM6Gz6NGYdBr01h/ny9DXxsx6DVXhrvUP4NevfOXgTRfntRMkhrnHx7RTFgfXlwbfWPyddo6LN1IDbIcpmEGvWbOkJEWiwumJKlxBr0kNc7Sjc7IA63ScjPoNTFr8dJyMOilLcpvaluH56PXWBzFt89fAO1xwZQkf4E3ztKN/h8/9FJbDHoBhrvUMufRS1LjHNFL2pAHZttg0G8xfnClrcegX3IGt6QzMegljcTz2i8vg34Lc6aNtDUY9A2xjKM+rB8w+N5bPAb9gtkorA1xSZNyHr0kNW4uI/okbwGuBn4Y+FBV/e08fo425jcASSeNHPRJ7gCuAZ6rqlcNbb8SeB+wDfhgVd1aVZ8EPpnkXOCPgaUI+lbD0YOu6kurn6llM86I/gDwAeCjJzck2QbcBrwBOAY8nORgVT3e7fL73e2aIYNb0jhGDvqqejDJyrrNlwNHq+ppgCR3AdcmeQK4FfjrqvrsjNoqaYk5uu/PtDX6C4Bnh64fA64Afgd4PbAjyWpV3b7+jkn2AnsBdu3aNWUz+jfKm3jcN7ojd0mzMJeDsVX1fuD9Z9hnP7AfYDAY1Dza0RdHLpIWybRBfxy4aOj6hd22pTfvsPaXgVrlN9HFM+08+oeBS5LsTnIWcB1wcNQ7J9mTZP+JEyembIYkaSMjB32SO4GHgEuTHEtyQ1W9ANwE3Ac8AdxdVY+N+phVdaiq9u7YsWPcdm+qlX33vvhPkpbNOLNurt9g+2Hg8MxatAX5C0RbjaXLzdXruW6S7AH2rK6u9tmMuTLEpen5i2E6vQZ9VR0CDg0Ggxv7bIekxeMgaXY8e6WkXjlan79ez17prBtJmj9LN1Pwq6WkZdBs6WZeXwcNd6lflnrGt6Vn3RjakrYCSzeSFoaDr/nwTwlKUuOardEPs6YnaSvbEkEvSbB1B31NHYy1vidpVFsp9Hut0S/L2SslaZktfelm3FG8o36pHVtpVD4NZ91IUuOWfkQvSes50v9+TR2MlbR1zaos2+IvCQ/GSlLjrNFLUuOs0UvSCJa5pOOIXpIa54hekjbQyrobg15S01oJ62n4N2MlqXFOr5Skxlm6kbTltb7YyqCXpCkswzEAg16SNkGfo32DXpLGtAyj+GEumJKkxhn0ktQ4SzeSNAeLVN5xRC9JjXNlrCQ1zpWxktQ4SzeS1DiDXpIaZ9BLUuMMeklqnEEvSY1zwZQkbbLNPsGZI3pJapxBL0mNM+glqXEGvSQ1zqCXpMbNPOiTXJzkQ0k+PuvHliSNb6SgT3JHkueSPLpu+5VJnkxyNMk+gKp6uqpumEdjJUnjG3VEfwC4cnhDkm3AbcBVwGXA9Ukum2nrJElTGynoq+pB4BvrNl8OHO1G8N8F7gKunXH7JElTmmZl7AXAs0PXjwFXJHkZ8B7g1Ulurqo/PNWdk+wF9nZXv53kyQnbsRP42oT3XTT2ZTG10pdW+gEN9SXvnaovrxxlp5mfAqGqvg68Y4T99gP7p/15SY5U1WDax1kE9mUxtdKXVvoB9mVc08y6OQ5cNHT9wm6bJGmBTBP0DwOXJNmd5CzgOuDgbJolSZqVUadX3gk8BFya5FiSG6rqBeAm4D7gCeDuqnpsfk3d0NTlnwViXxZTK31ppR9gX8aSqpr3z5Ak9chTIEhS45Yi6JOcl+T+JF/u/j93g/3+Jsk3k9yzbvuBJF9J8rnu309vTstP2cZp+7I7yT91q5E/1h0f6cUYfXl7t8+Xk7x9aPsD3crqk6/Lj25e60+9snvd7Wd3z/HR7jlfGbrt5m77k0netJntPpVJ+5JkJcl3hl6D2ze77euN0JdfSPLZJC8keeu62075XuvDlP34n6HXZPpjn1W18P+APwL2dZf3Ae/dYL/XAXuAe9ZtPwC8te9+zKgvdwPXdZdvB965yH0BzgOe7v4/t7t8bnfbA8Cgp7ZvA54CLgbOAj4PXLZun98Gbu8uXwd8rLt8Wbf/2cDu7nG29fg6TNOXFeDRvto+YV9WgJ8CPjr8uT7de22Z+tHd9u1ZtmcpRvSsrbj9SHf5I8BbTrVTVX0a+NZmNWpCE/clSYBfBE6eMG7D+2+SUfryJuD+qvpGVf0ncD/rTqfRk1FWdg/37+PA67rX4Frgrqp6vqq+AhztHq8v0/Rl0ZyxL1X1TFV9Afjeuvsu0nttmn7M3LIE/cur6qvd5X8DXj7BY7wnyReS/EmSs2fYtnFN05eXAd+stRlPsLYa+YJZNm5Mo/TlVCuoh9v84e7r6R9scvCcqV3ft0/3nJ9g7TUY5b6baZq+AOxO8i9J/j7Jz8+7sWcwzXO7SK/LtG15SZIjSf4xydSDuYX54+BJPgX82CluumX4SlVVknGnCt3MWhCdxdpUpncB756knaOYc1821Zz78raqOp7kh4BPAL/O2tdYbZ6vAruq6utJfgb4ZJKfrKr/6rthW9wru8/GxcBnknyxqp6a9MEWJuir6vUb3Zbk35O8oqq+muQVwHNjPvbJUefzST4M/N4UTR3l582rL18HfiTJ9m5UNvfVyDPoy3HgtUPXL2StNk9VHe/+/1aSv2Dt6+5mBf0oK7tP7nMsyXZgB2uvwaKtCp+4L7VWEH4eoKoeSfIU8OPAkbm3+tSmeW43fK/1YKr3yNBn4+kkDwCvZq3mP5FlKd0cBE4eQX878Ffj3LkLoZM17rcAj57+HnM1cV+6D+XfASeP0I/9XMzYKH25D3hjknO7WTlvBO5Lsj3JToAkPwhcw+a+LqOs7B7u31uBz3SvwUHgum4my27gEuCfN6ndpzJxX5Kcn7VTjtONHi9h7SBmX6ZZcX/K99qc2nkmE/eja//Z3eWdwGuAx6dqTR9HpCc4gv0y4NPAl4FPAed12wfAB4f2+wfgP4DvsFYTe1O3/TPAF1kLkj8HXrrEfbmYtVA5CvwlcPYS9OW3uvYeBX6z23YO8AjwBeAx4H1s8swV4JeAL7E2Urql2/Zu4M3d5Zd0z/HR7jm/eOi+t3T3exK4qq/XYNq+AL/SPf+fAz4L7FmCvvxs95n4b9a+YT12uvfasvUD+Lkurz7f/X/DtG1xZawkNW5ZSjeSpAkZ9JLUOINekhpn0EtS4wx6SWqcQS9JjTPoJalxBr0kNe5/AV3XBojqQLcuAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "thcut,pzcut,dcacut 152561\n" ] }, { @@ -1514,7 +1974,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1559,7 +2019,7 @@ }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD8CAYAAAB5Pm/hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAADqhJREFUeJzt3V+MXOdZx/HvDwcHKS2BNlYV+Q92WCvCVzS1nEpUVS4o2Albl6oCGyQKsmoFYQQXSHVVLspdiwQXUQzBKJbbqrJlhQCuslUKiMi9MMVOlaZ2LNNtSJW1Qu0QZP4IYdw8XOwkHS2e9dmdGY/33e9HWu3MO2fOeV6f9aN3nvPOe1JVSJLa9UOTDkCSNF4meklqnIlekhpnopekxpnoJalxJnpJapyJXpIaZ6KXpMaZ6CWpcXdMOgCAe+65pzZv3jzpMCRpRXn++edfr6p1N9vutkj0mzdv5uzZs5MOQ5JWlCTf7bLdREs3SaaTHL569eokw5Ckpk000VfVl6tq/9133z3JMCSpaY7oJalxjuglqXFOr5Skxlm6kaTGWbqRpMZZupGkxk30C1NJpoHpqampZe9j88Fn3n78ymcfGUFUktQWSzeS1DhLN5LUOBO9JDXORC9JjXMevSQ1zouxktQ4SzeS1DgTvSQ1zkQvSY0z0UtS45x1I0mNc9aNJDXO0o0kNc5EL0mNM9FLUuNM9JLUOBO9JDVu5Ik+yUNJvpbkiSQPjXr/kqSl6ZTokxxJcjnJuQXtO5NcTDKb5GCvuYD/BH4EmBttuJKkpeo6oj8K7OxvSLIGOATsArYBe5NsA75WVbuATwJ/MLpQJUnL0SnRV9Up4I0FzTuA2ap6uaquAceB3VX1Zu/1fwPuHLTPJPuTnE1y9sqVK8sIXZLUxTA1+vXAq33P54D1ST6a5M+ALwKPD3pzVR2uqu1VtX3dunVDhCFJWswdo95hVT0NPN1l2yTTwPTU1NSow5Ak9Qwzor8EbOx7vqHX1plr3UjS+A2T6M8AW5NsSbIW2AOcXMoOXL1Sksav6/TKY8Bp4P4kc0n2VdV14ADwLHABOFFV55dycEf0kjR+nWr0VbV3QPsMMLPcg1ujl6Txcz16SWqcd5iSpMY5opekxrl6pSQ1ztKNJDXO0o0kNc7SjSQ1zkQvSY2zRi9JjbNGL0mNs3QjSY0z0UtS46zRS1LjrNFLUuMs3UhS40z0ktQ4E70kNc5EL0mNc9aNJDXOWTeS1DhLN5LUOBO9JDXORC9JjTPRS1LjTPSS1LixJPokdyU5m+QXxrF/SVJ3nRJ9kiNJLic5t6B9Z5KLSWaTHOx76ZPAiVEGKklanq4j+qPAzv6GJGuAQ8AuYBuwN8m2JB8CXgIujzBOSdIy3dFlo6o6lWTzguYdwGxVvQyQ5DiwG3gHcBfzyf+/k8xU1Zsji1iStCSdEv0A64FX+57PAQ9W1QGAJL8OvD4oySfZD+wH2LRp0xBhSJIWM0yiX1RVHb3J64eTvAZMr1279n3jikOSVrthZt1cAjb2Pd/Qa+vMtW4kafyGSfRngK1JtiRZC+wBTi5lB65eKUnj13V65THgNHB/krkk+6rqOnAAeBa4AJyoqvNLObgjekkav66zbvYOaJ8BZpZ78CTTwPTU1NRydyFJugnXo5ekxrnWjSQ1zlsJSlLjLN1IUuMc0UtS4xzRS1LjvBgrSY0z0UtS46zRS1LjrNFLUuMs3UhS40z0ktQ4a/SS1Dhr9JLUOEs3ktQ4E70kNc5EL0mNM9FLUuOcdSNJjXPWjSQ1ztKNJDXORC9JjTPRS1LjTPSS1DgTvSQ1buSJPslPJXkiyVNJfnPU+5ckLU2nRJ/kSJLLSc4taN+Z5GKS2SQHAarqQlU9CvwS8DOjD1mStBRdR/RHgZ39DUnWAIeAXcA2YG+Sbb3XPgw8A8yMLFJJ0rJ0SvRVdQp4Y0HzDmC2ql6uqmvAcWB3b/uTVbUL+NVRBitJWro7hnjveuDVvudzwINJHgI+CtzJIiP6JPuB/QCbNm0aIgxJ0mKGSfQ3VFXPAc912O4wcBhg+/btNeo4JEnzhpl1cwnY2Pd8Q6+tMxc1k6TxGybRnwG2JtmSZC2wBzg5mrAkSaPSdXrlMeA0cH+SuST7quo6cAB4FrgAnKiq80s5uKtXStL4darRV9XeAe0zOIVSkm5r3nhEkhrnjUckqXGO6CWpcY7oJalxLlMsSY2zdCNJjbN0I0mNs3QjSY2zdCNJjbN0I0mNG/kyxZO0+eAzbz9+5bOPTDASSbp9WKOXpMaZ6CWpcV6MlaTGeTFWkhpn6UaSGmeil6TGmeglqXEmeklqnLNuJKlxzrqRpMZZupGkxpnoJalxJnpJapyJXpIaZ6KXpMaNZT36JB8BHgF+FHiyqr46juMsxrXpJWle5xF9kiNJLic5t6B9Z5KLSWaTHASoqr+qqk8AjwK/PNqQJUlLsZTSzVFgZ39DkjXAIWAXsA3Ym2Rb3ya/33tdkjQhnRN9VZ0C3ljQvAOYraqXq+oacBzYnXmfA75SVd+40f6S7E9yNsnZK1euLDd+SdJNDHsxdj3wat/zuV7bbwM/C3wsyaM3emNVHa6q7VW1fd26dUOGIUkaZCwXY6vqMeCxm22XZBqYnpqaGkcYkiSGH9FfAjb2Pd/Qa+vEtW4kafyGTfRngK1JtiRZC+wBTnZ9s6tXStL4LWV65THgNHB/krkk+6rqOnAAeBa4AJyoqvNd9+mIXpLGr3ONvqr2DmifAWaWc/BbVaP3y1OSVjPXo5ekxnmHKUlqnCN6SWqcq1dKUuMs3UhS4yzdSFLjxrIEwu3MqZaSVhtLN5LUOEs3ktQ4Z91IUuNM9JLUOBO9JDXOi7GS1DgvxkpS4yzdSFLjTPSS1LhV983Yfv3fkgW/KSupTY7oJalxzrqRpMY560aSGmfpRpIaZ6KXpMaZ6CWpcSZ6SWqciV6SGjfyRJ/kviRPJnlq1PuWJC1dp0Sf5EiSy0nOLWjfmeRiktkkBwGq6uWq2jeOYG+lzQefeftHklayrksgHAUeB77wVkOSNcAh4EPAHHAmycmqemnUQU7aoGTvkgmSVoJOI/qqOgW8saB5BzDbG8FfA44Du0ccnyRpSMPU6NcDr/Y9nwPWJ3l3kieA9yb51KA3J9mf5GySs1euXBkiDEnSYka+emVV/SvwaIftDid5DZheu3bt+0Ydx3JYj5fUomFG9JeAjX3PN/TaOnOtG0kav2FG9GeArUm2MJ/g9wC/spQdJJkGpqempoYI4/bQ/2lgVBdpx7FPSatP1+mVx4DTwP1J5pLsq6rrwAHgWeACcKKqzi/l4I7oJWn8Oo3oq2rvgPYZYGa5B1/pI/pBNX1H4pJuJ65HL0mNm+g9Y1f6iH6pBo30/QQgaZwc0UtS41y9UpIaZ+lmQrpcyJWkUbB0I0mNs3QjSY0z0UtS4yaa6JNMJzl89erVSYYhSU2zRi9JjbN0I0mNM9FLUuOcR6//Z+FcfpdlkEbrVi97Yo1ekhpn6UaSGmeil6TGmeglqXEmeklqnLNuVrFbfeXfG6xIk+GsG0lqnKUbSWqciV6SGmeil6TGmeglqXEmeklq3MinVya5C/gT4BrwXFV9adTHkCR112lEn+RIkstJzi1o35nkYpLZJAd7zR8FnqqqTwAfHnG8kqQl6lq6OQrs7G9IsgY4BOwCtgF7k2wDNgCv9jb7/mjClCQtV6dEX1WngDcWNO8AZqvq5aq6BhwHdgNzzCf7zvuXJI3PMDX69fxg5A7zCf5B4DHg8SSPAF8e9OYk+4H9AJs2bRoijNvbwpt4jGI/XZYPGGb75cQk3a5cemMMF2Or6r+A3+iw3WHgMMD27dtr1HFIkuYNU1q5BGzse76h19ZZkukkh69evTpEGJKkxQyT6M8AW5NsSbIW2AOcHE1YkqRR6Tq98hhwGrg/yVySfVV1HTgAPAtcAE5U1fmlHNzVKyVp/DrV6Ktq74D2GWBmuQd3PXpJGj/Xo5ekxjnPXZIaN9FE76wbSRo/SzeS1LhUTf67SkmuAN9d5tvvAV4fYTgrgX1u32rrL9jn5fiJqlp3s41ui0Q/jCRnq2r7pOO4lexz+1Zbf8E+j5MXYyWpcSZ6SWpcC4n+8KQDmAD73L7V1l+wz2Oz4mv0kqTFtTCilyQtYkUn+gH3rG1KkleSfCvJC0nO9treleRvkny79/vHJx3nMG50T+JBfcy8x3rn/MUkD0wu8uUb0OfPJLnUO9cvJHm477VP9fp8McnPTybq5UuyMcnfJ3kpyfkkv9Nrb/Y8L9LnW3+eq2pF/gBrgO8A9wFrgW8C2yYd1xj6+Qpwz4K2PwQO9h4fBD436TiH7OMHgQeAczfrI/Aw8BUgwPuBr086/hH2+TPA791g2229v+87gS29v/s1k+7DEvt7L/BA7/E7gX/q9avZ87xIn2/5eV7JI/pB96xdDXYDn+89/jzwkQnGMrS68T2JB/VxN/CFmvcPwI8luffWRDo6A/o8yG7geFX9T1X9MzDL/N//ilFVr1XVN3qP/4P5pc3X0/B5XqTPg4ztPK/kRH+je9Yu9o+4UhXw1STP9+6zC/Ceqnqt9/hfgPdMJrSxGtTH1s/7gV6p4khfSa6pPifZDLwX+Dqr5Dwv6DPc4vO8khP9avGBqnoA2AX8VpIP9r9Y85/5mp46tRr62POnwE8CPw28BvzRZMMZvSTvAP4C+N2q+vf+11o9zzfo8y0/zys50Q99z9qVoKou9X5fBv6S+Y9y33vrY2zv9+XJRTg2g/rY7Hmvqu9V1fer6k3gz/nBx/Ym+pzkh5lPeF+qqqd7zU2f5xv1eRLneSUn+ubvWZvkriTvfOsx8HPAOeb7+fHeZh8H/noyEY7VoD6eBH6tNyvj/cDVvo/+K9qCGvQvMn+uYb7Pe5LcmWQLsBX4x1sd3zCSBHgSuFBVf9z3UrPneVCfJ3KeJ31lesir2g8zfyX7O8CnJx3PGPp3H/NX4b8JnH+rj8C7gb8Dvg38LfCuScc6ZD+PMf8R9n+Zr0vuG9RH5mdhHOqd828B2ycd/wj7/MVen17s/ae/t2/7T/f6fBHYNen4l9HfDzBflnkReKH383DL53mRPt/y8+w3YyWpcSu5dCNJ6sBEL0mNM9FLUuNM9JLUOBO9JDXORC9JjTPRS1LjTPSS1Lj/A7BN/WkUbQ+yAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1571,7 +2031,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 400905\n" + "thcut,pzcut,dcacut 400905\n" ] }, { @@ -1596,7 +2056,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1641,7 +2101,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1653,7 +2113,24 @@ "name": "stdout", "output_type": "stream", "text": [ - "thcut,pzcut,curvcut 400905\n" + "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" ] }, { @@ -1678,7 +2155,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1721,7 +2198,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 57, "metadata": {}, "outputs": [ { diff --git a/RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h b/RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h index ff3624cdafd65..691c3e9ef429c 100644 --- a/RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h +++ b/RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h @@ -16,7 +16,7 @@ namespace pixelVertexHeterogeneousProduct { float * z_d; float * zerr_d; float * chi2_d; - uint16_t * sortInd; + uint16_t * sortInd_d; int32_t * ivtx_d; // this should be indexed with the original tracks, not the reduced set (oops) }; @@ -24,18 +24,13 @@ namespace pixelVertexHeterogeneousProduct { struct VerticesOnCPU { VerticesOnCPU() = default; - explicit VerticesOnCPU(uint32_t nvtx, uint32_t ntrks) : - z(nvtx), - zerr(nvtx), - ivtx(ntrks), - nVertices(nvtx) - { } - - std::vector> z,zerr, chi2; - std::vector> sortInd; - std::vector> ivtx; + 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; }; 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/PixelVertexHeterogeneousProducer.cc b/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousProducer.cc index 0b8c31235abea..3bd1e522cb776 100644 --- a/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousProducer.cc +++ b/RecoPixelVertexing/PixelVertexFinding/src/PixelVertexHeterogeneousProducer.cc @@ -22,12 +22,22 @@ #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; @@ -46,14 +56,20 @@ class PixelVertexHeterogeneousProducer : public HeterogeneousEDProducer token_Tracks; - const edm::EDGetTokenT token_BeamSpot; + const float m_ptMin; - reco::TrackRefVector m_trks; + const bool enableConversion_; + const bool enableTransfer_; + const edm::EDGetTokenT gpuToken_; + edm::EDGetTokenT token_Tracks_; + edm::EDGetTokenT token_BeamSpot_; + gpuVertexFinder::Producer m_gpuAlgo; @@ -72,6 +88,10 @@ void PixelVertexHeterogeneousProducer::fillDescriptions(edm::ConfigurationDescri 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); @@ -80,19 +100,26 @@ void PixelVertexHeterogeneousProducer::fillDescriptions(edm::ConfigurationDescri PixelVertexHeterogeneousProducer::PixelVertexHeterogeneousProducer(const edm::ParameterSet& conf) : HeterogeneousEDProducer(conf) - ,m_ptMin (conf.getParameter("PtMin") ) // 0.5 GeV - , trackCollName ( conf.getParameter("TrackCollection") ) - , token_Tracks ( consumes(trackCollName) ) - , token_BeamSpot ( consumes(conf.getParameter("beamSpot") ) ) + , 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_ ) { - // Register my product - produces(); - + produces(); + if (enableConversion_) { + token_Tracks_ = consumes(conf.getParameter("TrackCollection")); + token_BeamSpot_ =consumes(conf.getParameter("beamSpot") ); + // Register my product + produces(); + } else { + produces(); // dummy + } } @@ -103,34 +130,15 @@ void PixelVertexHeterogeneousProducer::acquireGPUCuda( cuda::stream_t<> &cudaStream) { // First fish the pixel tracks out of the event - edm::Handle trackCollection; - e.getByToken(token_Tracks,trackCollection); - const reco::TrackCollection tracks = *(trackCollection.product()); - if (verbose_) edm::LogInfo("PixelVertexHeterogeneousProducer") << ": Found " << tracks.size() << " tracks in TrackCollection called " << trackCollName << "\n"; + edm::Handle gh; + e.getByToken(gpuToken_, gh); + auto const & gTuples = *gh; + // std::cout << "Vertex Producers: tuples from gpu " << gTuples.nTuples << std::endl; - // on gpu beamspot already subtracted at hit level... - math::XYZPoint bs(0.,0.,0.); - edm::Handle bsHandle; - e.getByToken(token_BeamSpot,bsHandle); + tuples_ = gh.product(); - if (bsHandle.isValid()) bs = math::XYZPoint(bsHandle->x0(),bsHandle->y0(), bsHandle->z0() ); + m_gpuAlgo.produce(cudaStream.id(),gTuples,m_ptMin); - // Second, make a collection of pointers to the tracks we want for the vertex finder - // fill z,ez - std::vector z,ez2,pt2; - assert(m_trks.empty()); - for (unsigned int i=0; i(); + 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); + e.getByToken(token_BeamSpot_,bsHandle); auto vertexes = std::make_unique(); float x0=0,y0=0,z0=0,dxdz=0,dydz=0; - std::vector itrk; + std::vector itrk; if(!bsHandle.isValid()) { edm::LogWarning("PixelVertexHeterogeneousProducer") << "No beamspot found. Using returning vertexes with (0,0,Z) "; } else { @@ -158,6 +176,9 @@ void PixelVertexHeterogeneousProducer::produceGPUCuda( } // 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.... @@ -171,25 +192,31 @@ void PixelVertexHeterogeneousProducer::produceGPUCuda( z +=z0; reco::Vertex::Error err; err(2,2) = 1.f/gpuProduct.zerr[i]; - // err(2,2) *= 4.; // artifically inflate error + err(2,2) *= 2.; // artifically inflate error //Copy also the tracks (no intention to be efficient....) - for (auto k=0U; k0); + if (nt==0) { std::cout << "vertex " << i << " with no tracks..." << std::endl; continue;} (*vertexes).emplace_back(reco::Vertex::Point(x,y,z), err, gpuProduct.chi2[i], nt-1, nt ); auto & v = (*vertexes).back(); - for (auto k: itrk) { - v.add(reco::TrackBaseRef(m_trks[k])); + for (auto it: itrk) { + assert(it< (*tuples_).indToEdm.size()); + auto k = (*tuples_).indToEdm[it]; + assert(ksize() << " vertexes\n"; @@ -202,10 +229,8 @@ void PixelVertexHeterogeneousProducer::produceGPUCuda( 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; @@ -242,7 +267,6 @@ void PixelVertexHeterogeneousProducer::produceGPUCuda( } e.put(std::move(vertexes)); - m_trks.clear(); } diff --git a/RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h b/RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h index 1f02b1f84134c..dfc9efe28a81d 100644 --- a/RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h +++ b/RecoPixelVertexing/PixelVertexFinding/src/gpuClusterTracks.h @@ -13,49 +13,10 @@ namespace gpuVertexFinder { - __global__ - void sortByPt2(int nt, - OnGPU * pdata - ) { - auto & __restrict__ data = *pdata; - float const * __restrict__ ptt2 = data.ptt2; - uint32_t const & nv = *data.nv; - - int32_t const * __restrict__ iv = data.iv; - float * __restrict__ ptv2 = data.ptv2; - uint16_t * __restrict__ sortInd = data.sortInd; - - if (nv<1) return; - - // can be done asynchronoisly at the end of previous event - for (int i = threadIdx.x; i < nv; 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==nv) { - if (threadIdx.x==0) sortInd[0]=0; - return; - } - __shared__ uint16_t ws[1024]; - radixSort(ptv2,sortInd,ws,nv); - - assert(ptv2[sortInd[nv-1]]>=ptv2[sortInd[nv-2]]); - assert(ptv2[sortInd[1]]>=ptv2[sortInd[0]]); - } - - // this algo does not really scale as it works in a single block... // enough for <10K tracks we have __global__ - void clusterTracks(int nt, + void clusterTracks( OnGPU * pdata, int minT, // min number of neighbours to be "core" float eps, // max absolute distance to cluster @@ -66,17 +27,17 @@ namespace gpuVertexFinder { constexpr bool verbose = false; // in principle the compiler should optmize out if false - if(verbose && 0==threadIdx.x) printf("params %d %f\n",minT,eps); + 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; - float * __restrict__ zv = data.zv; - float * __restrict__ wv = data.wv; - float * __restrict__ chi2 = data.chi2; - uint32_t & nv = *data.nv; + + uint32_t & nvFinal = *data.nvFinal; + uint32_t & nvIntermediate = *data.nvIntermediate; uint8_t * __restrict__ izt = data.izt; int32_t * __restrict__ nn = data.nn; @@ -86,10 +47,9 @@ namespace gpuVertexFinder { assert(zt); using Hist=HistoContainer; - constexpr auto wss = Hist::totbins(); __shared__ Hist hist; - __shared__ typename Hist::Counter ws[wss]; - for (auto j=threadIdx.x; j=minT) { auto old = atomicInc(&foundClusters, 0xffffffff); iv[i] = -(old + 1); - zv[old]=0; - wv[old]=0; - chi2[old]=0; } else { // noise iv[i] = -9998; } @@ -220,52 +192,10 @@ namespace gpuVertexFinder { iv[i] = - iv[i] - 1; } - // 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]; - // remove outliers ???? if (c2> cut) {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); - - nv = foundClusters; + nvIntermediate = nvFinal = foundClusters; + + if(verbose && 0==threadIdx.x) printf("found %d proto vertices\n",foundClusters); + } } 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)>>(ntrks,onGPU_d,minT,eps,errmax,chi2max); + assert(onGPU_d);assert(tracks.gpu_d); + cudaCheck(cudaMemsetAsync(onGPU.ntrks, 0, sizeof(uint32_t),stream)); + auto blockSize = 128; + auto numberOfBlocks = (CAConstants::maxTuples() + blockSize - 1) / blockSize; + loadTracks<<>>(tracks.gpu_d,onGPU_d, ptMin); + cudaCheck(cudaGetLastError()); + + clusterTracks<<<1,1024-256,0,stream>>>(onGPU_d,minT,eps,errmax,chi2max); cudaCheck(cudaGetLastError()); - sortByPt2<<<1,256,0,stream>>>(ntrks,onGPU_d); + fitVertices<<<1,1024-256,0,stream>>>(onGPU_d,50.); cudaCheck(cudaGetLastError()); - cudaCheck(cudaMemcpyAsync(&gpuProduct.nVertices, onGPU.nv, sizeof(uint32_t), - cudaMemcpyDeviceToHost, stream)); - - gpuProduct.ivtx.resize(ntrks); - cudaCheck(cudaMemcpyAsync(gpuProduct.ivtx.data(),onGPU.iv,sizeof(int32_t)*ntrks, - cudaMemcpyDeviceToHost, stream)); + 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::GPUProduct const & Producer::fillResults(cudaStream_t 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)); diff --git a/RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h b/RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h index ded6759a940bd..8f005052da95c 100644 --- a/RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h +++ b/RecoPixelVertexing/PixelVertexFinding/src/gpuVertexFinder.h @@ -3,6 +3,7 @@ #include +#include "RecoPixelVertexing/PixelTriplets/plugins/pixelTuplesHeterogeneousProduct.h" #include "RecoPixelVertexing/PixelVertexFinding/interface/pixelVertexHeterogeneousProduct.h" @@ -12,7 +13,9 @@ namespace gpuVertexFinder { 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 @@ -22,7 +25,8 @@ namespace gpuVertexFinder { float * wv; // output weight (1/error^2) on the above float * chi2; // vertices chi2 float * ptv2; // vertices pt^2 - uint32_t * nv; // the number of vertices + 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) @@ -33,10 +37,25 @@ namespace gpuVertexFinder { }; + 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 GPUProduct = pixelVertexHeterogeneousProduct::GPUProduct; + + using TuplesOnCPU = pixelTuplesHeterogeneousProduct::TuplesOnCPU; + + using OnCPU = gpuVertexFinder::OnCPU; using OnGPU = gpuVertexFinder::OnGPU; @@ -44,31 +63,28 @@ namespace gpuVertexFinder { 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 + float ichi2max, // max normalized distance to cluster + bool ienableTransfer ) : minT(iminT), eps(ieps), errmax(ierrmax), - chi2max(ichi2max) + chi2max(ichi2max), + enableTransfer(ienableTransfer) {} ~Producer() { deallocateOnGPU();} - - void produce(cudaStream_t stream, - float const * zt, - float const * ezt2, - float const * ptt2, - uint32_t ntrks - ); - - GPUProduct const & fillResults(cudaStream_t stream); + + void produce(cudaStream_t stream, TuplesOnCPU const & tuples, float ptMin); + + OnCPU const & fillResults(cudaStream_t stream); void allocateOnGPU(); void deallocateOnGPU(); private: - GPUProduct gpuProduct; + OnCPU gpuProduct; OnGPU onGPU; OnGPU * onGPU_d=nullptr; @@ -76,6 +92,7 @@ namespace gpuVertexFinder { float eps; // max absolute distance to cluster float errmax; // max error to be "seed" float chi2max; // max normalized distance to cluster + const bool enableTransfer; }; diff --git a/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml b/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml index ad1f03999fbea..dc3b98f8456a5 100644 --- a/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml +++ b/RecoPixelVertexing/PixelVertexFinding/test/BuildFile.xml @@ -21,5 +21,6 @@ + diff --git a/RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu b/RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu index a92c116702231..d1f508ca98798 100644 --- a/RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu +++ b/RecoPixelVertexing/PixelVertexFinding/test/gpuVertexFinder_t.cu @@ -4,6 +4,11 @@ #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 @@ -81,7 +86,8 @@ int main() { } 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); @@ -96,11 +102,13 @@ int main() { 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(); @@ -109,7 +117,8 @@ int main() { onGPU.chi2 = chi2_d.get(); onGPU.ptv2 = ptv2_d.get(); onGPU.sortInd = ind_d.get(); - onGPU.nv = nv_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(); @@ -131,7 +140,8 @@ int main() { 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()); @@ -143,51 +153,101 @@ int main() { if ( (i%4) == 0 ) cuda::launch(clusterTracks, { 1, 512+256 }, - ev.ztrack.size(), onGPU_d.get(),kk,eps, + onGPU_d.get(),kk,eps, 0.02f,12.0f ); if ( (i%4) == 1 ) cuda::launch(clusterTracks, { 1, 512+256 }, - ev.ztrack.size(), onGPU_d.get(),kk,eps, + onGPU_d.get(),kk,eps, 0.02f,9.0f ); if ( (i%4) == 2 ) cuda::launch(clusterTracks, { 1, 512+256 }, - ev.ztrack.size(), onGPU_d.get(),kk,eps, + onGPU_d.get(),kk,eps, 0.01f,9.0f ); if ( (i%4) == 3 ) cuda::launch(clusterTracks, { 1, 512+256 }, - ev.ztrack.size(), onGPU_d.get(),kk,0.7f*eps, + 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 }, - ev.ztrack.size(), onGPU_d.get() + onGPU_d.get() ); - uint32_t nv; - cuda::memory::copy(&nv, onGPU.nv, sizeof(uint32_t)); + cuda::memory::copy(&nv, onGPU.nvFinal, sizeof(uint32_t)); if (nv==0) { std::cout << "NO VERTICES???" << std::endl; continue; } - float zv[nv]; - float wv[nv]; - float chi2[nv]; - float ptv2[nv]; - int32_t nn[nv]; - uint16_t ind[nv]; + 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)); @@ -195,15 +255,16 @@ int main() { 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(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(chi2,chi2+nv); + std::cout << "min max chi2 " << nv << " " << *mx.first << ' ' << *mx.second << std::endl; } + { - auto mx = std::minmax_element(chi2,chi2+nv); - std::cout << "min max chi2 " << *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; @@ -212,16 +273,15 @@ int main() { } float dd[nv]; - uint32_t ii=0; - for (auto zr : zv) { + for (auto kv=0U; kv