]> git.uio.no Git - u/mrichter/AliRoot.git/blob - HLT/TPCLib/AliHLTTPCPad.cxx
changes by Kenneth
[u/mrichter/AliRoot.git] / HLT / TPCLib / AliHLTTPCPad.cxx
1 // @(#) $Id$
2
3 /**************************************************************************
4  * This file is property of and copyright by the ALICE HLT Project        * 
5  * ALICE Experiment at CERN, All rights reserved.                         *
6  *                                                                        *
7  * Primary Authors: Matthias Richter <Matthias.Richter@ift.uib.no>        *
8  *                  Kenneth Aamodt   <Kenneth.aamodt@ift.uib.no>          *
9  *                  for The ALICE HLT Project.                            *
10  *                                                                        *
11  * Permission to use, copy, modify and distribute this software and its   *
12  * documentation strictly for non-commercial purposes is hereby granted   *
13  * without fee, provided that the above copyright notice appears in all   *
14  * copies and that both the copyright notice and this permission notice   *
15  * appear in the supporting documentation. The authors make no claims     *
16  * about the suitability of this software for any purpose. It is          *
17  * provided "as is" without express or implied warranty.                  *
18  **************************************************************************/
19
20 /** @file   AliHLTTPCPad.cxx
21     @author Matthias Richter, Kenneth Aamodt
22     @date   
23     @brief  Container Class for TPC Pads.
24 */
25
26 // see header file for class documentation
27 // or
28 // refer to README to build package
29 // or
30 // visit http://web.ift.uib.no/~kjeks/doc/alice-hlt
31
32 #if __GNUC__>= 3
33 using namespace std;
34 #endif
35
36 #include <cerrno>
37 #include "AliHLTTPCPad.h"
38 #include "AliHLTStdIncludes.h"
39
40
41 //added by kenneth
42 #include "AliHLTTPCTransform.h"
43 #include "AliHLTTPCClusters.h"
44 #include <sys/time.h>
45 #include "TMath.h"
46 #include "TFile.h"
47 //------------------------------
48
49 /** margin for the base line be re-avaluated */
50 #define ALIHLTPAD_BASELINE_MARGIN (2*fAverage)
51
52 /** ROOT macro for the implementation of ROOT specific class methods */
53 ClassImp(AliHLTTPCPad)
54
55 AliHLTTPCPad::AliHLTTPCPad()
56   :
57   fClusterCandidates(),
58   fUsedClusterCandidates(),
59   fRowNo(-1),
60   fPadNo(-1),
61   fThreshold(0),
62   fAverage(-1),
63   fNofEvents(0),
64   fSum(0),
65   fCount(0),
66   fTotal(0),
67   fBLMax(-1),
68   fBLMaxBin(-1),
69   fBLMin(-1),
70   fBLMinBin(-1),
71   fFirstBLBin(0),
72   fNofBins(0),
73   fReadPos(0),
74   fpRawData(NULL),
75   fDataSignals(NULL),
76   fSignalPositionArray(NULL),
77   fSizeOfSignalPositionArray(0),
78   fNGoodSignalsSent(0),
79   fNSigmaThreshold(0),
80   fSignalThreshold(0)
81 {
82   // see header file for class documentation
83   // or
84   // refer to README to build package
85   // or
86   // visit http://web.ift.uib.no/~kjeks/doc/alice-hlt
87   //  HLTInfo("Entering default constructor");
88   fDataSignals= new AliHLTTPCSignal_t[AliHLTTPCTransform::GetNTimeBins()];
89   memset( fDataSignals, 0xFF, sizeof(Int_t)*(AliHLTTPCTransform::GetNTimeBins()));
90   
91   fSignalPositionArray= new Int_t[AliHLTTPCTransform::GetNTimeBins()];
92   memset( fSignalPositionArray, 0xFF, sizeof(Int_t)*(AliHLTTPCTransform::GetNTimeBins()));
93   fSizeOfSignalPositionArray=0;
94
95 }
96
97 AliHLTTPCPad::AliHLTTPCPad(Int_t dummy)
98   :
99   fClusterCandidates(),
100   fUsedClusterCandidates(),
101   fRowNo(-1),
102   fPadNo(-1),
103   fThreshold(0),
104   fAverage(-1),
105   fNofEvents(0),
106   fSum(0),
107   fCount(0),
108   fTotal(0),
109   fBLMax(-1),
110   fBLMaxBin(-1),
111   fBLMin(-1),
112   fBLMinBin(-1),
113   fFirstBLBin(0),
114   fNofBins(0),
115   fReadPos(0),
116   fpRawData(NULL),
117   fDataSignals(NULL),
118   fSignalPositionArray(NULL),
119   fSizeOfSignalPositionArray(0),
120   fNGoodSignalsSent(0),
121   fNSigmaThreshold(0),
122   fSignalThreshold(0)
123 {
124   // see header file for class documentation
125   // or
126   // refer to README to build package
127   // or
128   // visit http://web.ift.uib.no/~kjeks/doc/alice-hlt
129   dummy=0;//to get rid of warning until things are cleaned up better
130 }
131
132 AliHLTTPCPad::AliHLTTPCPad(Int_t offset, Int_t nofBins)
133   :
134   fClusterCandidates(),
135   fUsedClusterCandidates(),
136   fRowNo(-1),
137   fPadNo(-1),
138   fThreshold(0),
139   fAverage(-1),
140   fNofEvents(0),
141   fSum(0),
142   fCount(0),
143   fTotal(0),
144   fBLMax(-1),
145   fBLMaxBin(-1),
146   fBLMin(-1),
147   fBLMinBin(-1),
148   fFirstBLBin(offset),
149   fNofBins(nofBins),
150   fReadPos(0),
151   fpRawData(NULL),
152   fDataSignals(NULL),
153   fSignalPositionArray(NULL),
154   fSizeOfSignalPositionArray(0),
155   fNGoodSignalsSent(0),
156   fNSigmaThreshold(0),
157   fSignalThreshold(0)
158 {
159   // see header file for class documentation
160 }
161
162 AliHLTTPCPad::~AliHLTTPCPad()
163 {
164   // see header file for class documentation
165   if (fpRawData) {
166     HLTWarning("event data acquisition not stopped");
167     StopEvent();
168   }
169   if (fDataSignals) {
170     delete [] fDataSignals;
171     fDataSignals=NULL;
172   }
173   if (fSignalPositionArray!=NULL) {
174     delete [] fSignalPositionArray;
175     fSignalPositionArray=NULL;
176   }
177 }
178
179 Int_t AliHLTTPCPad::SetID(Int_t rowno, Int_t padno)
180 {
181   // see header file for class documentation
182   fRowNo=rowno;
183   fPadNo=padno;
184
185   return 0;
186 }
187
188 Int_t AliHLTTPCPad::StartEvent()
189 {
190   // see header file for class documentation
191   Int_t iResult=0;
192   if (fpRawData==NULL) {
193     fBLMax=-1;
194     fBLMaxBin=-1;
195     fBLMin=-1;
196     fBLMinBin=-1;
197     fSum=0;
198     fCount=0;
199     fTotal=0;
200     if (fNofBins>0) {
201       fpRawData=new AliHLTTPCSignal_t[fNofBins];
202       if (fpRawData) {
203         for (int i=0; i<fNofBins; i++) fpRawData[i]=-1;
204       } else {
205         HLTError("memory allocation failed");
206         iResult=-ENOMEM;
207       }
208     }
209   } else {
210     HLTWarning("event data acquisition already started");
211     iResult=-EALREADY;
212   }
213   return iResult;
214 }
215
216 Int_t AliHLTTPCPad::CalculateBaseLine(Int_t reqMinCount)
217 {
218   // see header file for class documentation
219   Int_t iResult=0;
220   AliHLTTPCSignal_t avBackup=fAverage;
221   //HLTDebug("reqMinCount=%d fCount=%d fTotal=%d fSum=%d fBLMax=%d fBLMin=%d", reqMinCount, fCount, fTotal, fSum, fBLMax, fBLMin);
222   if (fCount>=reqMinCount && fCount>=fTotal/2) {
223     fAverage=fCount>0?fSum/fCount:0;
224     if (fAverage>0) {
225       //HLTDebug("average for current event %d (%d - %d)", fAverage, fBLMax, fBLMin);
226       fCount=0;fSum=-1;
227       if (fBLMax>ALIHLTPAD_BASELINE_MARGIN) {
228         // calculate again
229         //HLTDebug("maximum value %d exceeds margin for base line (%d) "
230         //       "-> re-evaluate base line", fBLMax, ALIHLTPAD_BASELINE_MARGIN);
231         if (fpRawData) {
232           for (Int_t i=fFirstBLBin; i<fNofBins; i++)
233             if (fpRawData[i]>=0) AddBaseLineValue(i, fpRawData[i]);
234           if (fCount>0 && fCount>=reqMinCount && fCount>=fTotal/2) {
235             fAverage=fSum/fCount;
236             //HLTDebug("new average %d", fAverage);
237           } else {
238             //      HLTDebug("baseline re-eveluation skipped because of to few "
239             //                 "contributing bins: total=%d, contributing=%d, req=%d"
240             //                 "\ndata might be already zero suppressed"
241             //                 , fTotal, fCount, reqMinCount);
242             iResult=-ENODATA;
243           }
244           fCount=0;fSum=-1;
245         } else {
246           HLTError("missing raw data for base line calculation");
247           iResult=-ENOBUFS;
248         }
249       }
250       if (iResult>=0) {
251         // calculate average for all events
252         fAverage=((avBackup*fNofEvents)+fAverage)/(fNofEvents+1);
253         //HLTDebug("base line average for %d event(s): %d", fNofEvents+1, fAverage);
254       } else {
255         fAverage=avBackup;      
256       }
257     } else {
258       fAverage=avBackup;
259     }
260   } else {
261     //     HLTDebug("baseline calculation skipped because of to few contributing "
262     //         "bins: total=%d, contributing=%d, required=%d \ndata might be "
263     //         "already zero suppressed", fTotal, fCount, reqMinCount);
264   }
265
266   return iResult;
267 }
268
269 Int_t AliHLTTPCPad::StopEvent()
270 {
271   // see header file for class documentation
272   Int_t iResult=0;
273   if (fpRawData) {
274     AliHLTTPCSignal_t* pData=fpRawData;
275     fpRawData=NULL;
276     delete [] pData;
277     fTotal=0;
278     fNofEvents++;
279     Rewind();
280   } else if (fNofBins>0) {
281     HLTError("event data acquisition not started");
282     iResult=-EBADF;
283   }
284   return iResult;
285 }
286
287 Int_t AliHLTTPCPad::ResetHistory()
288 {
289   // see header file for class documentation
290   Int_t iResult=0;
291   fAverage=-1;
292   fNofEvents=0;
293   return iResult;
294 }
295
296 Int_t AliHLTTPCPad::SetThreshold(AliHLTTPCSignal_t thresh)
297 {
298   // see header file for class documentation
299   Int_t iResult=0;
300   fThreshold=thresh;
301   return iResult;
302 }
303
304 Int_t AliHLTTPCPad::AddBaseLineValue(Int_t bin, AliHLTTPCSignal_t value)
305 {
306   // see header file for class documentation
307   Int_t iResult=0;
308   if (bin>=fFirstBLBin) {
309     if (fAverage<0 || value<ALIHLTPAD_BASELINE_MARGIN) {
310       // add to the current sum and count
311       fSum+=value;
312       fCount++;
313       if (fBLMax<value) {
314         // keep the maximum value for later quality control of the base 
315         // line calculation
316         fBLMax=value;
317         fBLMaxBin=bin;
318       }
319       if (fBLMin<0 || fBLMin>value) {
320         // keep the minimum value for later quality control of the base 
321         // line calculation
322         fBLMin=value;
323         fBLMinBin=bin;
324       }
325     } else {
326       //       HLTDebug("ignoring value %d (bin %d) for base line calculation "
327       //               "(current average is %d)",
328       //               value, bin, fAverage);
329     }
330   }
331   return iResult;
332 }
333
334 Int_t AliHLTTPCPad::SetRawData(Int_t bin, AliHLTTPCSignal_t value)
335 {
336   // see header file for class documentation
337   //  printf("Row: %d    Pad: %d  Time: %d Charge %d", fRowNo, fPadNo, bin, value);
338   Int_t iResult=0;
339   if (fpRawData) {
340     if (bin<fNofBins) {
341       if (value>=0) {
342         if (fpRawData[bin]<0) {
343           AddBaseLineValue(bin, value);
344           fTotal++;
345         } else {
346           // ignore value for average calculation
347           HLTWarning("overriding content of bin %d (%d)", bin, fpRawData[bin]);
348         }
349         fpRawData[bin]=value;
350       } else {
351         HLTWarning("ignoring neg. raw data");
352       }
353     } else {
354       HLTWarning("bin %d out of range (%d)", bin, fNofBins);
355       iResult=-ERANGE;
356     }
357   } else if (fNofBins>0) {
358     HLTError("event cycle not started");
359     iResult=-EBADF;
360   }
361   return iResult;
362 }
363
364 Int_t AliHLTTPCPad::Next(Int_t bZeroSuppression) 
365 {
366   // see header file for class documentation
367   if (fpRawData==NULL) return 0;
368   Int_t iResult=fReadPos<fNofBins;
369   if (iResult>0 && (iResult=(++fReadPos<fNofBins))>0) {
370     if (bZeroSuppression) {
371       while ((iResult=(fReadPos<fNofBins))>0 &&
372              GetCorrectedData(fReadPos)<=0)
373         fReadPos++;
374     }
375   }
376   return iResult;
377 }
378
379 Int_t AliHLTTPCPad::Rewind(Int_t bZeroSuppression)
380 {
381   // see header file for class documentation
382   fReadPos=(bZeroSuppression>0?0:fFirstBLBin)-1;
383   return Next(bZeroSuppression);
384 }
385
386 AliHLTTPCSignal_t AliHLTTPCPad::GetRawData(Int_t bin) const
387 {
388   // see header file for class documentation
389   AliHLTTPCSignal_t data=0;
390   if (fpRawData) {
391     if (bin<fNofBins) {
392       data=fpRawData[bin];
393     } else {
394       HLTWarning("requested bin %d out of range (%d)", bin, fNofBins);
395     }
396   } else if (fNofBins>0) {
397     HLTWarning("data only available within event cycle");
398   }
399   return data;
400 }
401
402 AliHLTTPCSignal_t AliHLTTPCPad::GetCorrectedData(Int_t bin) const
403 {
404   // see header file for class documentation
405   AliHLTTPCSignal_t data=GetRawData(bin)-GetBaseLine(bin);
406   AliHLTTPCSignal_t prev=0;
407   if (bin>1) prev=GetRawData(bin-1)-GetBaseLine(bin-1);
408   AliHLTTPCSignal_t succ=0;
409   if (bin+1<GetSize()) succ=GetRawData(bin+1)-GetBaseLine(bin+1);
410   if (fThreshold>0) {
411     data-=fThreshold;
412     prev-=fThreshold;
413     succ-=fThreshold;
414   }
415  
416   // case 1:
417   // the signal is below the base-line and threshold
418   if (data<0) data=0;
419
420   //case 2:
421   // the neighboring bins are both below base-line/threshold
422   // a real signal is always more than one bin wide because of the shaper 
423   if (prev<=0 && succ<=0) data=0;
424  
425   // case 3:
426   // the bin is inside the range of ignored bins
427   if (bin<fFirstBLBin) data=0;
428   //HLTDebug("fReadPos=%d data=%d threshold=%d raw data=%d base line=%d", fReadPos, data, fThreshold, GetRawData(bin), GetBaseLine(bin));
429   return data;
430 }
431
432 AliHLTTPCSignal_t AliHLTTPCPad::GetBaseLine(Int_t /*bin*/) const //TODO: Why is bin being ignored?
433 {
434   // see header file for class documentation
435   AliHLTTPCSignal_t val=0;
436   if (fAverage>0) {
437     // we take the minumum value as the base line if it doesn't differ from
438     // the average to much
439     val=fAverage;
440 #ifdef KEEP_NOISE
441     const AliHLTTPCSignal_t kMaxDifference=15;
442     if ((fAverage-fBLMin)<=kMaxDifference) val=fBLMin;
443     else val>kMaxDifference?val-=kMaxDifference:0;
444 #endif
445   }
446   if (val<0) {
447     // here we should never get
448     val=0;
449     HLTFatal("wrong base line value");
450   }
451   return val;
452 }
453
454 AliHLTTPCSignal_t AliHLTTPCPad::GetAverage() const
455 {
456   // see header file for class documentation
457   return fAverage>0?fAverage:0;
458 }
459
460 Float_t AliHLTTPCPad::GetOccupancy() const
461 {
462   // see header file for class documentation
463   Float_t occupancy=0;
464   if (fpRawData && fNofBins>0) {
465     for (Int_t i=fFirstBLBin; i<fNofBins; i++) {
466       if (GetCorrectedData(i)>0) occupancy+=1;
467     }
468     if (fNofBins-fFirstBLBin>0)
469       occupancy/=fNofBins-fFirstBLBin;
470   }
471   return occupancy;
472 }
473
474 Float_t AliHLTTPCPad::GetAveragedOccupancy() const
475 {
476   // see header file for class documentation
477
478   // history is not yet implemented
479   return GetOccupancy();
480 }
481 void AliHLTTPCPad::PrintRawData()
482 {
483   // see header file for class documentation
484   for(Int_t bin=0;bin<AliHLTTPCTransform::GetNTimeBins();bin++){
485     if(GetDataSignal(bin)>0)
486       //This cout should be here since using logging produces output that is much more difficult to read
487         cout<<fRowNo<<"\t"<<fPadNo<<"\t"<<bin<<"\t"<<GetDataSignal(bin)<<endl;
488   }
489 }
490
491 void AliHLTTPCPad::ClearCandidates(){
492   fClusterCandidates.clear();
493   fUsedClusterCandidates.clear();
494 }
495
496 void AliHLTTPCPad::SetDataToDefault()
497 {
498   // see header file for class documentation
499   //  if(fDataSignals && fSignalPositionArray){
500     for(Int_t i =0;i<fSizeOfSignalPositionArray;i++){
501       fDataSignals[fSignalPositionArray[i]]=-1;
502     }
503     fSizeOfSignalPositionArray=0;
504     fNGoodSignalsSent = 0;
505     //  }
506 }
507
508 void AliHLTTPCPad::SetDataSignal(Int_t bin,Int_t signal)
509 {
510   // see header file for class documentation
511   fDataSignals[bin]=signal;
512   fSignalPositionArray[fSizeOfSignalPositionArray]=bin;
513   fSizeOfSignalPositionArray++;
514 }
515
516 Bool_t AliHLTTPCPad::GetNextGoodSignal(Int_t &time, Int_t &signal){
517   if(fNGoodSignalsSent<fSizeOfSignalPositionArray&&fSizeOfSignalPositionArray>0){
518     time = fSignalPositionArray[fNGoodSignalsSent];
519     signal = GetDataSignal(time);
520
521     fNGoodSignalsSent++;
522     return kTRUE;
523   }
524   return kFALSE;
525 }
526
527 Bool_t AliHLTTPCPad::GetNextGoodSignal(Int_t &time,Int_t &bunchSize,Int_t dummy){
528   dummy=0;//to get rid of warning until things are cleaned up better
529   if(fNGoodSignalsSent<fSizeOfSignalPositionArray&&fSizeOfSignalPositionArray>0){
530     time = fSignalPositionArray[fNGoodSignalsSent];
531     bunchSize=1;
532     fNGoodSignalsSent++;
533     while(fNGoodSignalsSent<fSizeOfSignalPositionArray){
534       if(fDataSignals[time+bunchSize+1]>0){
535         bunchSize++;
536         fNGoodSignalsSent++;
537       }
538       else{
539         break;
540       }
541     }
542     fNGoodSignalsSent++;
543    return kTRUE;
544   }
545   return kFALSE;
546 }
547
548 Int_t AliHLTTPCPad::GetDataSignal(Int_t bin) const
549 {
550   // see header file for class documentation
551   return fDataSignals[bin];
552 }
553
554 void AliHLTTPCPad::ZeroSuppress(Double_t nRMS, Int_t threshold, Int_t reqMinPoint, Int_t beginTime, Int_t endTime, Int_t timebinsLeft, Int_t timebinsRight, Int_t valueUnderAverage){
555   //see headerfile for documentation
556  
557   //HLTDebug("In Pad: nRMS=%d, threshold=%d, reqMinPoint=%d, beginTime=%d, endTime=%d, timebinsLeft=%d timebinsRight=%d valueUnderAverage=%d \n",nRMS,threshold,reqMinPoint,beginTime,endTime,timebinsLeft,timebinsRight,valueUnderAverage);
558
559   Bool_t useRMS= kFALSE;
560   if(nRMS>0){
561     useRMS=kTRUE;
562     if(threshold>0){
563       HLTInfo("Both RMSThreshold and SignalThreshold defined, using RMSThreshold");
564     }
565   }
566   if(threshold<1 && nRMS<=0){
567     //setting the data to -1 for this pad
568     HLTInfo("Neither of RMSThreshold and SignalThreshold set, zerosuppression aborted");
569     return;
570   }
571  
572   Int_t fThresholdUsed=threshold;
573  
574   Int_t nAdded=0;
575   Int_t sumNAdded=0;
576   fSizeOfSignalPositionArray=0;
577   if(useRMS){
578     for(Int_t i=beginTime;i<endTime+1;i++){
579       if(fDataSignals[i]>0){
580         nAdded++;
581         sumNAdded+=fDataSignals[i]*fDataSignals[i];
582       }
583     }
584   }
585   else if(threshold>0){
586     for(Int_t i=beginTime;i<endTime+1;i++){
587       if(fDataSignals[i]>0){
588         nAdded++;
589         sumNAdded+=fDataSignals[i];
590       }
591     }
592   }
593   else{
594     HLTFatal("This should never happen because this is tested earlier in the code.(nRMSThreshold<1&&signal-threshold<1)");
595   }
596   if(nAdded<reqMinPoint){
597     HLTInfo("Number of signals is less than required, zero suppression aborted");
598     return;
599   }
600  
601   if(nAdded==0){
602     HLTInfo("No signals added for this pad, zerosuppression aborted: pad %d row %d",fPadNo,fRowNo);
603     return;
604   }
605
606   Double_t averageValue=(Double_t)sumNAdded/nAdded;//true average for threshold approach, average of signals squared for rms approach
607  
608   if(useRMS){
609     //Calculate the RMS
610     if(averageValue>0){
611       fThresholdUsed =(Int_t)(TMath::Sqrt(averageValue)*nRMS);
612     }
613     else{
614       HLTFatal("average value in ZeroSuppression less than 0, investigation needed. This should never happen");
615     }
616   }
617   else{
618     fThresholdUsed = (Int_t)(averageValue + threshold); 
619   }
620
621   // Do zero suppression on the adc values within [beginTime,endTime](add the good values)
622   for(Int_t i=beginTime;i<endTime;i++){
623     if(fDataSignals[i]>fThresholdUsed){
624       Int_t firstSignalTime=i;
625       for(Int_t left=1;left<timebinsLeft;left++){//looking 5 to the left of the signal to add tail
626         if(fDataSignals[i-left]-averageValue+valueUnderAverage>0 && i-left>=beginTime){
627           firstSignalTime--;
628         }
629         else{
630           break;
631         }
632       }
633       Int_t lastSignalTime=i;
634       while(fDataSignals[lastSignalTime+1]>fThresholdUsed && lastSignalTime+1<endTime){
635         lastSignalTime++;
636       }
637       for(Int_t right=1;right<timebinsRight;right++){//looking 5 to the left of the signal to add tail
638         if(fDataSignals[i+right]-averageValue+valueUnderAverage>0&&i+right<endTime){
639           lastSignalTime++;
640         }
641         else{
642           break;
643         }       
644       }
645       
646       for(Int_t t=firstSignalTime;t<lastSignalTime;t++){
647         fDataSignals[t]=(AliHLTTPCSignal_t)(fDataSignals[t]-averageValue + valueUnderAverage);
648         fSignalPositionArray[fSizeOfSignalPositionArray]=t;
649         fSizeOfSignalPositionArray++;
650       }
651       i+=lastSignalTime;
652     }
653   }
654   //reset the rest of the data
655   Int_t counterSize=fSizeOfSignalPositionArray;
656
657   for(Int_t d=endTime;d>=beginTime;d--){
658     if(d==fSignalPositionArray[counterSize-1]&&counterSize-1>=0){
659       counterSize--;
660     }
661     else{
662       fDataSignals[d]=-1;
663     }
664   }
665   if(fDataSignals[beginTime+1]<1){
666     fDataSignals[beginTime]=0;
667   }
668 }
669
670 void AliHLTTPCPad::AddClusterCandidate(AliHLTTPCClusters candidate){
671   fClusterCandidates.push_back(candidate);
672   fUsedClusterCandidates.push_back(0);
673 }