@@ -84,6 +84,8 @@ struct zdc2stagecalibration {
8484 Configurable<std::string> confQRecenteringStructured71Path{" confQRecenteringStructured71Path" , " " , " CCDB path to the 71-feature structured Q-vector recentering coefficients" };
8585 Configurable<bool > useQWidthEqualization{" useQWidthEqualization" , false , " equalize Qx/Qy widths after Structured71 recentering" };
8686 Configurable<std::string> confQWidthPath{" confQWidthPath" , " " , " CCDB path to post-recentering Q-width map" };
87+ Configurable<bool > useQTwist{" useQTwist" , false , " apply same-side Qx-Qy twist correction after width equalization" };
88+ Configurable<std::string> confQTwistPath{" confQTwistPath" , " " , " CCDB path to the run-wise Q-twist map" };
8789 Configurable<int > cfgSpatialCalibBins{
8890 " cfgSpatialCalibBins" , 30 ,
8991 " number of bins per cross coordinate for spatial calibration" };
@@ -103,6 +105,7 @@ struct zdc2stagecalibration {
103105 TH3D * spatialprofile = nullptr ;
104106 TH3D * qRecenteringProfile = nullptr ;
105107 TH2D * qWidthProfile = nullptr ;
108+ TH2D * qTwistProfile = nullptr ;
106109
107110 static constexpr int kQRecenteringNFeatures = 21 ;
108111 static constexpr int kQRecenteringNMatrixMoments = kQRecenteringNFeatures * (kQRecenteringNFeatures + 1 ) / 2 ;
@@ -137,6 +140,9 @@ struct zdc2stagecalibration {
137140 if (useQWidthEqualization.value && (calibrationStage.value != 2 || !useQRecentering.value || qRecenteringMethod.value != 4 || confQWidthPath.value .empty ())) {
138141 LOGF (fatal, " Width equalization requires stage=2, useQRecentering=true, method=4 and confQWidthPath" );
139142 }
143+ if (useQTwist.value && (!useQWidthEqualization.value || confQTwistPath.value .empty ())) {
144+ LOGF (fatal, " Twist correction requires width equalization and confQTwistPath" );
145+ }
140146 const int nTimeBins = static_cast <int >(std::ceil (cfgMaxRunHours.value * 60 .f / cfgTimeSliceMinutes.value ));
141147 AxisSpec timeAxis = {nTimeBins, 0.0 , cfgMaxRunHours.value , " time from SOR (h)" };
142148 AxisSpec towerAxis = {4 , 0.0 , 4.0 , " tower" };
@@ -158,6 +164,7 @@ struct zdc2stagecalibration {
158164 AxisSpec vertexXYAxis = {100 , -0.1 , 0.1 , " vertex x/y (cm)" };
159165 AxisSpec vertexZAxis = {100 , -10.0 , 10.0 , " vertex z (cm)" };
160166 AxisSpec qComponentAxis = {4 , 0.0 , 4.0 , " Q component" };
167+ AxisSpec qTwistMomentAxis = {10 , 0.0 , 10.0 , " Q moments (A: 0-4, C: 5-9)" };
161168 AxisSpec qCorrelationComponentAxis = {4 , 0.0 , 4.0 , " A-C correlation component" };
162169 AxisSpec qValueAxis = {200 , -4.0 , 4.0 , " Q" };
163170
@@ -261,6 +268,10 @@ struct zdc2stagecalibration {
261268 histos.add (" ResolutionQA/pCosPsiAPsiCVsCentrality" , " ZDC A-C spectator-plane correlation;centrality (%);<#cos(#Psi_{A}-#Psi_{C})>" , kTProfile , {resolutionCentralityAxis});
262269 histos.add (" ResolutionQA/pCosPsiAPsiCVsCentralityVz" , " ZDC A-C correlation;centrality (%);v_{z} (cm); <#cos(#Psi_{A}-#Psi_{C})>" , kTProfile2D , {resolutionCentralityAxis, resolutionVzAxis});
263270 histos.add (" QWidthQA/pQAfterWidthEqVsCentrality" , " Q after width equalization;centrality (%);Q component;<Q>" , kTProfile2D , {centralityAxis, qComponentAxis});
271+ histos.add (" QTwistQA/pMomentsBeforeVsCentrality" , " Q moments before twist;centrality (%);moment;<value>" , kTProfile2D , {centralityAxis, qTwistMomentAxis});
272+ histos.add (" QTwistQA/pMomentsAfterVsCentrality" , " Q moments after twist;centrality (%);moment;<value>" , kTProfile2D , {centralityAxis, qTwistMomentAxis});
273+ histos.add (" PhiQA/hPhiAfterTwistVsCentralityZNA" , " ZNA #phi after twist;centrality (%);#phi" , kTH2F , {centralityAxis, phiAxis});
274+ histos.add (" PhiQA/hPhiAfterTwistVsCentralityZNC" , " ZNC #phi after twist;centrality (%);#phi" , kTH2F , {centralityAxis, phiAxis});
264275 histos.add (" PhiQA/hPhiAfterWidthEqVsCentralityZNA" , " ZNA #phi after width equalization;centrality (%);#phi" , kTH2F , {centralityAxis, phiAxis});
265276 histos.add (" PhiQA/hPhiAfterWidthEqVsCentralityZNC" , " ZNC #phi after width equalization;centrality (%);#phi" , kTH2F , {centralityAxis, phiAxis});
266277 histos.add (" QRecentering2DQA/pQxAAfterVsCentralityTime" , " QxA after recentering;centrality (%);time from SOR (h);<Q_{x}^{A}>" , kTProfile2D , {centralityAxis, timeAxis});
@@ -321,6 +332,7 @@ struct zdc2stagecalibration {
321332 spatialprofile = nullptr ;
322333 qRecenteringProfile = nullptr ;
323334 qWidthProfile = nullptr ;
335+ qTwistProfile = nullptr ;
324336 if (calibrationStage.value == 2 && useGainCallib.value && !confGainPath.value .empty ()) {
325337 gainprofile = ccdb->getForTimeStamp <TH2D >(confGainPath.value , timestamp);
326338 if (!gainprofile) {
@@ -362,6 +374,18 @@ struct zdc2stagecalibration {
362374 }
363375 }
364376 }
377+ if (calibrationStage.value == 2 && useQTwist.value ) {
378+ qTwistProfile = ccdb->getForTimeStamp <TH2D >(confQTwistPath.value , timestamp);
379+ if (!qTwistProfile || qTwistProfile->GetNbinsX () != 80 || qTwistProfile->GetNbinsY () != 6 ) {
380+ LOGF (fatal, " Missing/invalid ZDC twist map for run %d" , runNumber);
381+ }
382+ constexpr std::array<const char *, 6 > labels = {" MxxA" , " MxyA" , " MyyA" , " MxxC" , " MxyC" , " MyyC" };
383+ for (int i = 0 ; i < 6 ; ++i) {
384+ if (std::string (qTwistProfile->GetYaxis ()->GetBinLabel (i + 1 )) != labels[i]) {
385+ LOGF (fatal, " Incorrect ZDC twist map component ordering for run %d" , runNumber);
386+ }
387+ }
388+ }
365389 }
366390
367391 float getTimeFromSOR (uint64_t globalBC) const
@@ -1083,11 +1107,59 @@ struct zdc2stagecalibration {
10831107 }
10841108 }
10851109
1110+ const double phiWidthA = std::atan2 (qValues[1 ], qValues[0 ]);
1111+ const double phiWidthC = std::atan2 (qValues[3 ], qValues[2 ]);
1112+ if (useQWidthEqualization.value ) {
1113+ histos.fill (HIST (" PhiQA/hPhiAfterWidthEqVsCentralityZNA" ), centrality, phiWidthA);
1114+ histos.fill (HIST (" PhiQA/hPhiAfterWidthEqVsCentralityZNC" ), centrality, phiWidthC);
1115+ }
1116+ const auto makeTwistMoments = [](const std::array<double , 4 >& q) {
1117+ return std::array<double , 10 >{q[0 ], q[1 ], q[0 ] * q[0 ], q[1 ] * q[1 ], q[0 ] * q[1 ],
1118+ q[2 ], q[3 ], q[2 ] * q[2 ], q[3 ] * q[3 ], q[2 ] * q[3 ]};
1119+ };
1120+ const auto momentsBeforeTwist = makeTwistMoments (qValues);
1121+ if (useQWidthEqualization.value ) {
1122+ for (int i = 0 ; i < 10 ; ++i) {
1123+ histos.fill (HIST (" QTwistQA/pMomentsBeforeVsCentrality" ), centrality, i + 0.5 , momentsBeforeTwist[i]);
1124+ }
1125+ }
1126+ if (useQTwist.value ) {
1127+ const int centBin = qTwistProfile->GetXaxis ()->FindFixBin (centrality);
1128+ if (centBin < 1 || centBin > qTwistProfile->GetNbinsX ()) {
1129+ fillTable (false , -999 .f , -999 .f , -999 .f , -999 .f );
1130+ return ;
1131+ }
1132+ for (int side = 0 ; side < 2 ; ++side) {
1133+ const int offset = 3 * side;
1134+ const double mxx = qTwistProfile->GetBinContent (centBin, offset + 1 );
1135+ const double mxy = qTwistProfile->GetBinContent (centBin, offset + 2 );
1136+ const double myy = qTwistProfile->GetBinContent (centBin, offset + 3 );
1137+ if (!std::isfinite (mxx) || !std::isfinite (mxy) || !std::isfinite (myy) || mxx <= 0.0 || myy <= 0.0 || mxx * myy <= mxy * mxy) {
1138+ fillTable (false , -999 .f , -999 .f , -999 .f , -999 .f );
1139+ return ;
1140+ }
1141+ const int ix = 2 * side;
1142+ const double qx = qValues[ix];
1143+ const double qy = qValues[ix + 1 ];
1144+ qValues[ix] = mxx * qx + mxy * qy;
1145+ qValues[ix + 1 ] = mxy * qx + myy * qy;
1146+ }
1147+ for (int i = 0 ; i < 4 ; ++i) {
1148+ if (!std::isfinite (qValues[i]) || !std::isfinite (static_cast <float >(qValues[i]))) {
1149+ fillTable (false , -999 .f , -999 .f , -999 .f , -999 .f );
1150+ return ;
1151+ }
1152+ }
1153+ const auto momentsAfterTwist = makeTwistMoments (qValues);
1154+ for (int i = 0 ; i < 10 ; ++i) {
1155+ histos.fill (HIST (" QTwistQA/pMomentsAfterVsCentrality" ), centrality, i + 0.5 , momentsAfterTwist[i]);
1156+ }
1157+ }
10861158 const double phiFinalA = std::atan2 (qValues[1 ], qValues[0 ]);
10871159 const double phiFinalC = std::atan2 (qValues[3 ], qValues[2 ]);
1088- if (useQWidthEqualization .value ) {
1089- histos.fill (HIST (" PhiQA/hPhiAfterWidthEqVsCentralityZNA " ), centrality, phiFinalA);
1090- histos.fill (HIST (" PhiQA/hPhiAfterWidthEqVsCentralityZNC " ), centrality, phiFinalC);
1160+ if (useQTwist .value ) {
1161+ histos.fill (HIST (" PhiQA/hPhiAfterTwistVsCentralityZNA " ), centrality, phiFinalA);
1162+ histos.fill (HIST (" PhiQA/hPhiAfterTwistVsCentralityZNC " ), centrality, phiFinalC);
10911163 }
10921164 // ZDC resolution after all enabled corrections
10931165 const double qMagA = std::hypot (qValues[0 ], qValues[1 ]);
0 commit comments