]> git.uio.no Git - u/mrichter/AliRoot.git/blob - HLT/TPCLib/tracking-ca/AliHLTTPCCAGlobalMergerComponent.cxx
24c005f3d27cf2eaf2950fc3ef476a89a2f0a6ac
[u/mrichter/AliRoot.git] / HLT / TPCLib / tracking-ca / AliHLTTPCCAGlobalMergerComponent.cxx
1 // **************************************************************************
2 // This file is property of and copyright by the ALICE HLT Project          *
3 // ALICE Experiment at CERN, All rights reserved.                           *
4 //                                                                          *
5 // Primary Authors: Sergey Gorbunov <sergey.gorbunov@kip.uni-heidelberg.de> *
6 //                  Ivan Kisel <kisel@kip.uni-heidelberg.de>                *
7 //                  Matthias Kretz <kretz@kde.org>                          *
8 //                  for The ALICE HLT Project.                              *
9 //                                                                          *
10 // Permission to use, copy, modify and distribute this software and its     *
11 // documentation strictly for non-commercial purposes is hereby granted     *
12 // without fee, provided that the above copyright notice appears in all     *
13 // copies and that both the copyright notice and this permission notice     *
14 // appear in the supporting documentation. The authors make no claims       *
15 // about the suitability of this software for any purpose. It is            *
16 // provided "as is" without express or implied warranty.                    *
17 //                                                                          *
18 //***************************************************************************
19
20
21 /** @file   AliHLTTPCCAGlobalMergerComponent.cxx
22     @author Matthias Kretz
23     @date
24     @brief  HLT TPC CA global merger component.
25 */
26
27 #if __GNUC__>= 3
28 using namespace std;
29 #endif
30
31 #include "AliHLTTPCCAGlobalMergerComponent.h"
32 #include "AliHLTTPCCASliceOutput.h"
33
34 #include "AliHLTTPCCADef.h"
35 #include "AliHLTTPCCAMerger.h"
36 #include "AliHLTTPCCAMergerOutput.h"
37 #include "AliHLTTPCCATrackConvertor.h"
38
39 #include "AliHLTTPCGMMerger.h"
40 #include "AliHLTTPCGMMergedTrack.h"
41
42 #include "AliHLTTPCDefinitions.h"
43 #include "AliHLTTPCTransform.h"
44 #include "AliHLTTPCTrackSegmentData.h"
45 #include "AliHLTTPCTrack.h"
46 #include "AliHLTTPCTrackArray.h"
47 #include "AliHLTTPCTrackletDataFormat.h"
48
49 #include "AliCDBEntry.h"
50 #include "AliCDBManager.h"
51 #include "TObjString.h"
52 #include "TObjArray.h"
53 #include "AliHLTExternalTrackParam.h"
54
55 #include <climits>
56 #include <cstdlib>
57 #include <cerrno>
58
59
60 // ROOT macro for the implementation of ROOT specific class methods
61 ClassImp( AliHLTTPCCAGlobalMergerComponent )
62
63
64 AliHLTTPCCAGlobalMergerComponent::AliHLTTPCCAGlobalMergerComponent()
65 : AliHLTProcessor(), fVersion(1), fGlobalMergerVersion0( 0 ), fGlobalMerger(0), fSolenoidBz( 0 ), fClusterErrorCorrectionY(0), fClusterErrorCorrectionZ(0), fBenchmark("GlobalMerger")
66 {
67   // see header file for class documentation
68 }
69
70 AliHLTTPCCAGlobalMergerComponent::AliHLTTPCCAGlobalMergerComponent( const AliHLTTPCCAGlobalMergerComponent & ):AliHLTProcessor(), fVersion(1), fGlobalMergerVersion0( 0 ), fGlobalMerger(0), fSolenoidBz( 0 ), fClusterErrorCorrectionY(0), fClusterErrorCorrectionZ(0), fBenchmark("GlobalMerger")
71 {
72 // dummy
73 }
74
75 AliHLTTPCCAGlobalMergerComponent &AliHLTTPCCAGlobalMergerComponent::operator=( const AliHLTTPCCAGlobalMergerComponent & )
76 {
77   // dummy
78   return *this;
79 }
80
81 // Public functions to implement AliHLTComponent's interface.
82 // These functions are required for the registration process
83
84 const char *AliHLTTPCCAGlobalMergerComponent::GetComponentID()
85 {
86   // see header file for class documentation
87   return "TPCCAGlobalMerger";
88 }
89
90 void AliHLTTPCCAGlobalMergerComponent::GetInputDataTypes( AliHLTComponentDataTypeList &list )
91 {
92   // see header file for class documentation
93   list.clear();
94   list.push_back( AliHLTTPCCADefinitions::fgkTrackletsDataType );
95   //list.push_back( AliHLTTPCDefinitions::fgkTrackSegmentsDataType );
96   //list.push_back( AliHLTTPCDefinitions::fgkVertexDataType );
97 }
98
99 AliHLTComponentDataType AliHLTTPCCAGlobalMergerComponent::GetOutputDataType()
100 {
101   // see header file for class documentation
102   //return AliHLTTPCDefinitions::fgkTracksDataType; // old
103   return kAliHLTDataTypeTrack|kAliHLTDataOriginTPC;
104 }
105
106 void AliHLTTPCCAGlobalMergerComponent::GetOutputDataSize( unsigned long &constBase, double &inputMultiplier )
107 {
108   // see header file for class documentation
109   // XXX TODO: Find more realistic values.
110   constBase = 0;
111   inputMultiplier = 1.0;
112 }
113
114 AliHLTComponent *AliHLTTPCCAGlobalMergerComponent::Spawn()
115 {
116   // see header file for class documentation
117   return new AliHLTTPCCAGlobalMergerComponent;
118 }
119
120
121 void AliHLTTPCCAGlobalMergerComponent::SetDefaultConfiguration()
122 {
123   // Set default configuration for the CA merger component
124   // Some parameters can be later overwritten from the OCDB
125
126   fVersion = 1;
127   fSolenoidBz = -5.00668;
128   fClusterErrorCorrectionY = 0;
129   fClusterErrorCorrectionZ = 1.1;
130   fBenchmark.Reset();
131   fBenchmark.SetTimer(0,"total");
132   fBenchmark.SetTimer(1,"reco");    
133 }
134
135 int AliHLTTPCCAGlobalMergerComponent::ReadConfigurationString(  const char* arguments )
136 {
137   // Set configuration parameters for the CA merger component from the string
138
139   int iResult = 0;
140   if ( !arguments ) return iResult;
141
142   TString allArgs = arguments;
143   TString argument;
144   int bMissingParam = 0;
145
146   TObjArray* pTokens = allArgs.Tokenize( " " );
147
148   int nArgs =  pTokens ? pTokens->GetEntries() : 0;
149
150   for ( int i = 0; i < nArgs; i++ ) {
151     argument = ( ( TObjString* )pTokens->At( i ) )->GetString();
152     if ( argument.IsNull() ) continue;
153
154     if ( argument.CompareTo( "-version" ) == 0 ) {
155       if ( ( bMissingParam = ( ++i >= pTokens->GetEntries() ) ) ) break;
156       fVersion  = ( ( TObjString* )pTokens->At( i ) )->GetString().Atoi();
157       HLTInfo( "Merger version set to: %d", fVersion );
158       continue;
159     }
160
161     if ( argument.CompareTo( "-solenoidBz" ) == 0 ) {
162       if ( ( bMissingParam = ( ++i >= pTokens->GetEntries() ) ) ) break;
163       HLTWarning("argument -solenoidBz is deprecated, magnetic field set up globally (%f)", GetBz());
164       continue;
165     }
166
167     if ( argument.CompareTo( "-errorCorrectionY" ) == 0 ) {
168       if ( ( bMissingParam = ( ++i >= pTokens->GetEntries() ) ) ) break;
169       fClusterErrorCorrectionY = ( ( TObjString* )pTokens->At( i ) )->GetString().Atof();
170       HLTInfo( "Cluster Y error correction factor set to: %f", fClusterErrorCorrectionY );
171       continue;
172     }
173
174    if ( argument.CompareTo( "-errorCorrectionZ" ) == 0 ) {
175       if ( ( bMissingParam = ( ++i >= pTokens->GetEntries() ) ) ) break;
176       fClusterErrorCorrectionZ = ( ( TObjString* )pTokens->At( i ) )->GetString().Atof();
177       HLTInfo( "Cluster Z error correction factor set to: %f", fClusterErrorCorrectionZ );
178       continue;
179     }
180
181     HLTError( "Unknown option \"%s\"", argument.Data() );
182     iResult = -EINVAL;
183   }
184   delete pTokens;
185
186   if ( bMissingParam ) {
187     HLTError( "Specifier missed for parameter \"%s\"", argument.Data() );
188     iResult = -EINVAL;
189   }
190
191   return iResult;
192 }
193
194
195 int AliHLTTPCCAGlobalMergerComponent::ReadCDBEntry( const char* cdbEntry, const char* chainId )
196 {
197   // see header file for class documentation
198
199   const char* defaultNotify = "";
200
201   if ( !cdbEntry ) {
202     cdbEntry = "HLT/ConfigTPC/TPCCAGlobalMerger";
203     defaultNotify = " (default)";
204     chainId = 0;
205   }
206
207   HLTInfo( "configure from entry \"%s\"%s, chain id %s", cdbEntry, defaultNotify, ( chainId != NULL && chainId[0] != 0 ) ? chainId : "<none>" );
208   AliCDBEntry *pEntry = AliCDBManager::Instance()->Get( cdbEntry );//,GetRunNo());
209
210   if ( !pEntry ) {
211     HLTError( "cannot fetch object \"%s\" from CDB", cdbEntry );
212     return -EINVAL;
213   }
214
215   TObjString* pString = dynamic_cast<TObjString*>( pEntry->GetObject() );
216
217   if ( !pString ) {
218     HLTError( "configuration object \"%s\" has wrong type, required TObjString", cdbEntry );
219     return -EINVAL;
220   }
221
222   HLTInfo( "received configuration object string: \"%s\"", pString->GetString().Data() );
223
224   return  ReadConfigurationString( pString->GetString().Data() );
225 }
226
227
228
229 int AliHLTTPCCAGlobalMergerComponent::Configure( const char* cdbEntry, const char* chainId, const char *commandLine )
230 {
231   // Configure the component
232   // There are few levels of configuration,
233   // parameters which are set on one step can be overwritten on the next step
234
235   //* read hard-coded values
236
237   SetDefaultConfiguration();
238
239   //* read the default CDB entry
240
241   int iResult1 = ReadCDBEntry( NULL, chainId );
242
243   //* read magnetic field
244
245   fSolenoidBz = GetBz();
246
247   //* read the actual CDB entry if required
248
249   int iResult2 = ( cdbEntry ) ? ReadCDBEntry( cdbEntry, chainId ) : 0;
250
251   //* read extra parameters from input (if they are)
252
253   int iResult3 = 0;
254
255   if ( commandLine && commandLine[0] != '\0' ) {
256     HLTInfo( "received configuration string from HLT framework: \"%s\"", commandLine );
257     iResult3 = ReadConfigurationString( commandLine );
258   }
259
260
261   // Initialize the merger
262
263   AliHLTTPCCAParam param;
264
265   {
266     // get gemetry
267     int iSec = 0;
268     float inRmin = 83.65;
269     float outRmax = 247.7;
270     float plusZmin = 0.0529937;
271     float plusZmax = 249.778;
272     float minusZmin = -249.645;
273     float minusZmax = -0.0799937;
274     float dalpha = 0.349066;
275     float alpha = 0.174533 + dalpha * iSec;
276     bool zPlus = 1;//( iSec < 18 );
277     float zMin =  plusZmin; //zPlus ? plusZmin : minusZmin;
278     float zMax =  plusZmax; //zPlus ? plusZmax : minusZmax;
279     int nRows = AliHLTTPCTransform::GetNRows();
280     float padPitch = 0.4;
281     float sigmaZ = 0.228808;
282     float *rowX = new float [nRows];
283     for ( int irow = 0; irow < nRows; irow++ ) {
284       rowX[irow] = AliHLTTPCTransform::Row2X( irow );
285     }
286
287     param.Initialize( iSec, nRows, rowX, alpha, dalpha,
288                       inRmin, outRmax, zMin, zMax, padPitch, sigmaZ, fSolenoidBz );
289
290     if( fClusterErrorCorrectionY>1.e-4 ) param.SetClusterError2CorrectionY( fClusterErrorCorrectionY*fClusterErrorCorrectionY );
291     if( fClusterErrorCorrectionZ>1.e-4 ) param.SetClusterError2CorrectionZ( fClusterErrorCorrectionZ*fClusterErrorCorrectionZ );
292     param.Update();
293
294     delete[] rowX;
295   }
296
297
298   if( fVersion==0 ) fGlobalMergerVersion0->SetSliceParam( param );
299   else fGlobalMerger->SetSliceParam( param );
300
301   return iResult1 ? iResult1 : ( iResult2 ? iResult2 : iResult3 );
302 }
303
304
305
306
307 int AliHLTTPCCAGlobalMergerComponent::DoInit( int argc, const char** argv )
308 {
309   // see header file for class documentation
310
311   if ( fGlobalMergerVersion0 || fGlobalMerger ) {
312     return EINPROGRESS;
313   }
314
315   fGlobalMergerVersion0 = new AliHLTTPCCAMerger();
316   fGlobalMerger         = new AliHLTTPCGMMerger();
317
318   TString arguments = "";
319   for ( int i = 0; i < argc; i++ ) {
320     if ( !arguments.IsNull() ) arguments += " ";
321     arguments += argv[i];
322   }
323
324   return Configure( NULL, NULL, arguments.Data()  );
325 }
326
327 int AliHLTTPCCAGlobalMergerComponent::Reconfigure( const char* cdbEntry, const char* chainId )
328 {
329   // Reconfigure the component from OCDB
330
331   return Configure( cdbEntry, chainId, NULL );
332 }
333
334
335
336 int AliHLTTPCCAGlobalMergerComponent::DoDeinit()
337 {
338   // see header file for class documentation
339   delete fGlobalMergerVersion0;
340   fGlobalMergerVersion0 = 0;
341   delete fGlobalMerger;
342   fGlobalMerger = 0;
343
344   return 0;
345 }
346
347 int AliHLTTPCCAGlobalMergerComponent::DoEvent( const AliHLTComponentEventData &evtData,
348     const AliHLTComponentBlockData *blocks, AliHLTComponentTriggerData &/*trigData*/,
349     AliHLTUInt8_t *outputPtr, AliHLTUInt32_t &size, AliHLTComponentBlockDataList &outputBlocks )
350 {
351   // see header file for class documentation
352   int iResult = 0;
353   unsigned int maxBufferSize = size;
354
355   size = 0;
356
357   if ( !outputPtr ) {
358     return -ENOSPC;
359   }
360   if ( !IsDataEvent() ) {
361     return 0;
362   }
363   fBenchmark.StartNewEvent();
364   fBenchmark.Start(0);
365
366   if( fVersion==0 )  fGlobalMergerVersion0->Clear();
367   else fGlobalMerger->Clear();
368
369   const AliHLTComponentBlockData *const blocksEnd = blocks + evtData.fBlockCnt;
370   for ( const AliHLTComponentBlockData *block = blocks; block < blocksEnd; ++block ) {
371     if ( block->fDataType != AliHLTTPCCADefinitions::fgkTrackletsDataType ) {
372       continue;
373     }
374
375     fBenchmark.AddInput(block->fSize);
376
377     int slice = AliHLTTPCDefinitions::GetMinSliceNr( *block );
378     if ( slice < 0 || slice >= AliHLTTPCTransform::GetNSlice() ) {
379       HLTError( "invalid slice number %d extracted from specification 0x%08lx,  skipping block of type %s",
380                 slice, block->fSpecification, DataType2Text( block->fDataType ).c_str() );
381       // just remember the error, if there are other valid blocks ignore the error, return code otherwise
382       iResult = -EBADF;
383       continue;
384     }
385
386     if ( slice != AliHLTTPCDefinitions::GetMaxSliceNr( *block ) ) {
387       // the code was not written for/ never used with multiple slices in one data block/ specification
388       HLTWarning( "specification 0x%08lx indicates multiple slices in data block %s: never used before, please audit the code",
389                   block->fSpecification, DataType2Text( block->fDataType ).c_str() );
390     }
391     AliHLTTPCCASliceOutput *sliceOut =  reinterpret_cast<AliHLTTPCCASliceOutput *>( block->fPtr );
392     //sliceOut->SetPointers();
393     if( fVersion==0 ) fGlobalMergerVersion0->SetSliceData( slice, sliceOut );
394     else fGlobalMerger->SetSliceData( slice, sliceOut );
395
396         /*char filename[256];
397         sprintf(filename, "debug%d.out", slice);
398         FILE* fp = fopen(filename, "w+b");
399         if (fp == NULL) printf("Error!!!\n");
400         fwrite(sliceOut, 1, sliceOut->EstimateSize(sliceOut->NTracks(), sliceOut->NTrackClusters()), fp);
401         fclose(fp);*/
402   }
403   fBenchmark.Start(1);
404   if( fVersion==0 ) fGlobalMergerVersion0->Reconstruct();
405   else fGlobalMerger->Reconstruct();
406   fBenchmark.Stop(1);
407
408   // Fill output 
409
410   if( fVersion==0 ){
411
412     const AliHLTTPCCAMergerOutput *mergerOutput = fGlobalMergerVersion0->Output();
413
414     unsigned int mySize = 0;
415     {
416       AliHLTTracksData* outPtr = ( AliHLTTracksData* )( outputPtr );
417       
418       AliHLTExternalTrackParam* currOutTrack = outPtr->fTracklets;
419       
420       mySize =   ( ( AliHLTUInt8_t * )currOutTrack ) -  ( ( AliHLTUInt8_t * )outputPtr );
421       
422       outPtr->fCount = 0;
423       
424       int nTracks = mergerOutput->NTracks();
425       
426       for ( int itr = 0; itr < nTracks; itr++ ) {
427         
428         // convert AliHLTTPCCAMergedTrack to AliHLTTrack
429         
430         const AliHLTTPCCAMergedTrack &track = mergerOutput->Track( itr );
431         
432         unsigned int dSize = sizeof( AliHLTExternalTrackParam ) + track.NClusters() * sizeof( unsigned int );
433         
434         if ( mySize + dSize > maxBufferSize ) {
435           HLTWarning( "Output buffer size exceed (buffer size %d, current size %d), %d tracks are not stored", maxBufferSize, mySize, nTracks - itr + 1 );
436           iResult = -ENOSPC;
437           break;
438         }
439         
440         // first convert to AliExternalTrackParam
441         
442         AliExternalTrackParam tp, tpEnd;
443         AliHLTTPCCATrackConvertor::GetExtParam( track.InnerParam(), tp,  track.InnerAlpha() );
444         AliHLTTPCCATrackConvertor::GetExtParam( track.OuterParam(), tpEnd, track.OuterAlpha() );
445         
446         // normalize the angle to +-Pi
447         
448         currOutTrack->fAlpha = tp.GetAlpha() - CAMath::Nint(tp.GetAlpha()/CAMath::TwoPi())*CAMath::TwoPi();      
449         currOutTrack->fX = tp.GetX();
450         currOutTrack->fY = tp.GetY();
451         currOutTrack->fZ = tp.GetZ();      
452         {
453           float sinA = TMath::Sin( track.OuterAlpha() - track.InnerAlpha());
454           float cosA = TMath::Cos( track.OuterAlpha() - track.InnerAlpha());
455           currOutTrack->fLastX = tpEnd.GetX()*cosA - tpEnd.GetY()*sinA;
456           currOutTrack->fLastY = tpEnd.GetX()*sinA + tpEnd.GetY()*cosA;
457           currOutTrack->fLastZ = tpEnd.GetZ();
458         }
459         currOutTrack->fq1Pt = tp.GetSigned1Pt();
460         currOutTrack->fSinPsi = tp.GetSnp();
461         currOutTrack->fTgl = tp.GetTgl();
462         for( int i=0; i<15; i++ ) currOutTrack->fC[i] = tp.GetCovariance()[i];
463         currOutTrack->fTrackID = itr;
464         currOutTrack->fFlags = 0;
465         currOutTrack->fNPoints = track.NClusters();    
466         for ( int i = 0; i < track.NClusters(); i++ ) currOutTrack->fPointIDs[i] = mergerOutput->ClusterId( track.FirstClusterRef() + i );
467         
468         currOutTrack = ( AliHLTExternalTrackParam* )( (( Byte_t * )currOutTrack) + dSize );
469         mySize += dSize;
470         outPtr->fCount++;
471       }    
472
473       AliHLTComponentBlockData resultData;
474       FillBlockData( resultData );
475       resultData.fOffset = 0;
476       resultData.fSize = mySize;
477       resultData.fDataType = kAliHLTDataTypeTrack|kAliHLTDataOriginTPC;
478       resultData.fSpecification = AliHLTTPCDefinitions::EncodeDataSpecification( 0, 35, 0, 5 );
479       outputBlocks.push_back( resultData );
480       fBenchmark.AddOutput(resultData.fSize);
481       
482       size = resultData.fSize;
483     }
484     
485     HLTInfo( "CAGlobalMerger:: output %d tracks", mergerOutput->NTracks() );
486     fGlobalMergerVersion0->Clear();
487
488   } else { // new merger 
489
490     unsigned int mySize = 0;
491     {
492       AliHLTTracksData* outPtr = ( AliHLTTracksData* )( outputPtr );
493       AliHLTExternalTrackParam* currOutTrack = outPtr->fTracklets;
494       mySize =   ( ( AliHLTUInt8_t * )currOutTrack ) -  ( ( AliHLTUInt8_t * )outputPtr );
495       outPtr->fCount = 0;   
496       int nTracks = fGlobalMerger->NOutputTracks();
497
498       for ( int itr = 0; itr < nTracks; itr++ ) {
499
500         // convert AliHLTTPCGMMergedTrack to AliHLTTrack
501         
502         const AliHLTTPCGMMergedTrack &track = fGlobalMerger->OutputTracks()[ itr ];
503         if( !track.OK() ) continue;
504         unsigned int dSize = sizeof( AliHLTExternalTrackParam ) + track.NClusters() * sizeof( unsigned int );
505         
506         if ( mySize + dSize > maxBufferSize ) {
507           HLTWarning( "Output buffer size exceed (buffer size %d, current size %d), %d tracks are not stored", maxBufferSize, mySize, nTracks - itr + 1 );
508           iResult = -ENOSPC;
509           break;
510         }
511
512         // first convert to AliExternalTrackParam
513
514         AliExternalTrackParam tp;
515         track.GetParam().GetExtParam( tp,  track.GetAlpha() );
516       
517         // normalize the angle to +-Pi
518               
519         currOutTrack->fAlpha = tp.GetAlpha() - CAMath::Nint(tp.GetAlpha()/CAMath::TwoPi())*CAMath::TwoPi();      
520         currOutTrack->fX = tp.GetX();
521         currOutTrack->fY = tp.GetY();
522         currOutTrack->fZ = tp.GetZ();      
523         currOutTrack->fLastX = track.LastX();
524         currOutTrack->fLastY = track.LastY();
525         currOutTrack->fLastZ = track.LastZ();
526       
527         currOutTrack->fq1Pt = tp.GetSigned1Pt();
528         currOutTrack->fSinPsi = tp.GetSnp();
529         currOutTrack->fTgl = tp.GetTgl();
530         for( int i=0; i<15; i++ ) currOutTrack->fC[i] = tp.GetCovariance()[i];
531         currOutTrack->fTrackID = itr;
532         currOutTrack->fFlags = 0;
533         currOutTrack->fNPoints = track.NClusters();    
534         for ( int i = 0; i < track.NClusters(); i++ ) currOutTrack->fPointIDs[i] = fGlobalMerger->OutputClusterIds()[track.FirstClusterRef() + i ];
535         
536         currOutTrack = ( AliHLTExternalTrackParam* )( (( Byte_t * )currOutTrack) + dSize );
537         mySize += dSize;
538         outPtr->fCount++;
539       }
540   
541       AliHLTComponentBlockData resultData;
542       FillBlockData( resultData );
543       resultData.fOffset = 0;
544       resultData.fSize = mySize;
545       resultData.fDataType = kAliHLTDataTypeTrack|kAliHLTDataOriginTPC;
546       resultData.fSpecification = AliHLTTPCDefinitions::EncodeDataSpecification( 0, 35, 0, 5 );
547       outputBlocks.push_back( resultData );
548       fBenchmark.AddOutput(resultData.fSize);
549       
550       size = resultData.fSize;
551     }
552
553     HLTInfo( "CAGlobalMerger:: output %d tracks", fGlobalMerger->NOutputTracks() );
554
555     fGlobalMerger->Clear();
556   }
557
558   fBenchmark.Stop(0);
559   HLTInfo( fBenchmark.GetStatistics() );
560   return iResult;
561 }
562