]> git.uio.no Git - u/mrichter/AliRoot.git/blob - HLT/trigger/AliHLTMuonSpectroTriggerComponent.cxx
putting back the lost statistics message at EOR
[u/mrichter/AliRoot.git] / HLT / trigger / AliHLTMuonSpectroTriggerComponent.cxx
1 // $Id: $
2 /**************************************************************************
3  * This file is property of and copyright by the ALICE HLT Project        *
4  * All rights reserved.                                                   *
5  *                                                                        *
6  * Primary Authors:                                                       *
7  *   Artur Szostak <artursz@iafrica.com>                                  *
8  *                                                                        *
9  * Permission to use, copy, modify and distribute this software and its   *
10  * documentation strictly for non-commercial purposes is hereby granted   *
11  * without fee, provided that the above copyright notice appears in all   *
12  * copies and that both the copyright notice and this permission notice   *
13  * appear in the supporting documentation. The authors make no claims     *
14  * about the suitability of this software for any purpose. It is          *
15  * provided "as is" without express or implied warranty.                  *
16  **************************************************************************/
17
18 /// @file   AliHLTMuonSpectroTriggerComponent.cxx
19 /// @author Artur Szostak <artursz@iafrica.com>
20 /// @date   9 Nov 2009
21 /// @brief  Implementation of the muon spectrometer trigger component.
22 ///
23 /// The AliHLTMuonSpectroTriggerComponent component is used to generate the HLT
24 /// trigger for the muon spectrometer.
25
26 #include "AliHLTMuonSpectroTriggerComponent.h"
27 #include "AliHLTTriggerDecision.h"
28 #include "AliHLTMuonSpectroScalars.h"
29 #include "AliHLTMUONDataBlockReader.h"
30 #include "AliHLTMUONConstants.h"
31 #include "AliHLTMUONUtils.h"
32 #include "AliMUONTriggerDDLDecoderEventHandler.h"
33
34 ClassImp(AliHLTMuonSpectroTriggerComponent);
35
36
37 AliHLTMuonSpectroTriggerComponent::AliHLTMuonSpectroTriggerComponent() :
38         AliHLTTrigger(),
39         fBufferSizeConst(1024*16),
40         fBufferSizeMultiplier(1),
41         fMakeStats(false),
42         fTriggerDDLs(false),
43         fTriggerHits(false),
44         fTriggerTrigRecs(false),
45         fTriggerTracks(true),
46         fTriggerDimuons(true)
47 {
48         // Default constructor.
49 }
50
51
52 AliHLTMuonSpectroTriggerComponent::~AliHLTMuonSpectroTriggerComponent()
53 {
54         // Default destructor.
55 }
56
57
58 void AliHLTMuonSpectroTriggerComponent::GetInputDataTypes(AliHLTComponentDataTypeList& list) const
59 {
60         // Returns the list of input types expected.
61         list.push_back(AliHLTMUONConstants::DDLRawDataType());  
62         list.push_back(AliHLTMUONConstants::TriggerRecordsBlockDataType());
63         list.push_back(AliHLTMUONConstants::RecHitsBlockDataType());
64         list.push_back(AliHLTMUONConstants::MansoTracksBlockDataType());
65         list.push_back(AliHLTMUONConstants::TracksBlockDataType());
66         list.push_back(AliHLTMUONConstants::SinglesDecisionBlockDataType());
67         list.push_back(AliHLTMUONConstants::PairsDecisionBlockDataType());
68 }
69
70
71 void AliHLTMuonSpectroTriggerComponent::GetOutputDataTypes(AliHLTComponentDataTypeList& list) const
72 {
73         // Return the output data types generated.
74         
75         list.push_back(kAliHLTDataTypeTriggerDecision);
76         list.push_back(kAliHLTDataTypeEventStatistics|kAliHLTDataOriginHLT);
77 }
78
79
80 void AliHLTMuonSpectroTriggerComponent::GetOutputDataSize(unsigned long& constBase, double& inputMultiplier)
81 {
82         // Returns the output data size estimate.
83         
84         constBase = fBufferSizeConst;
85         inputMultiplier = fBufferSizeMultiplier;
86 }
87
88
89 AliHLTComponent* AliHLTMuonSpectroTriggerComponent::Spawn()
90 {
91         // Creates and returns a new instance.
92         
93         return new AliHLTMuonSpectroTriggerComponent;
94 }
95
96
97 Int_t AliHLTMuonSpectroTriggerComponent::DoInit(int argc, const char** argv)
98 {
99         // Initialise the component.
100         
101         fMakeStats = false;
102         fTriggerDDLs = false;
103         fTriggerHits = false;
104         fTriggerTrigRecs = false;
105         fTriggerTracks = false;
106         fTriggerDimuons = false;
107  
108         for (int i = 0; i < argc; i++)
109         {
110                 if (strcmp(argv[i], "-makestats") == 0)
111                 {
112                         fMakeStats = true;
113                         continue;
114                 }
115                 if (strcmp(argv[i], "-triggerddls") == 0)
116                 {
117                         fTriggerDDLs = true;
118                         continue;
119                 }
120                 if (strcmp(argv[i], "-triggerhits") == 0)
121                 {
122                         fTriggerHits = true;
123                         continue;
124                 }
125                 if (strcmp(argv[i], "-triggertrigrecs") == 0)
126                 {
127                         fTriggerTrigRecs = true;
128                         continue;
129                 }
130                 if (strcmp(argv[i], "-triggertracks") == 0)
131                 {
132                         fTriggerTracks = true;
133                         continue;
134                 }
135                 if (strcmp(argv[i], "-triggerdimuons") == 0)
136                 {
137                         fTriggerDimuons = true;
138                         continue;
139                 }
140                 if (strcmp(argv[i], "-triggerany") == 0)
141                 {
142                         fTriggerHits = true;
143                         fTriggerTrigRecs = true;
144                         fTriggerTracks = true;
145                         fTriggerDimuons = true;
146                         continue;
147                 }
148                 
149                 HLTError("Unknown option '%s'.", argv[i]);
150                 return -EINVAL;
151         } // for loop
152         
153         // If nothing was turned on to trigger then turn everything on by default.
154         if (fTriggerHits == false and fTriggerTrigRecs == false
155             and fTriggerTracks == false and fTriggerDimuons == false
156            )
157         {
158                 fTriggerTracks = true;
159                 fTriggerDimuons = true;
160         }
161         
162         return 0;
163 }
164
165
166 Int_t AliHLTMuonSpectroTriggerComponent::DoDeinit()
167 {
168         // Cleans up the component.
169         
170         return 0;
171 }
172
173
174 int AliHLTMuonSpectroTriggerComponent::DoTrigger()
175 {
176         // Applies the trigger for the HLT.
177         
178         int result = 0;
179
180         bool gotddls = false;
181         bool gothits = false;
182         bool gottrigrecs = false;
183         bool gottracks = false;
184         bool gotsingles = false;
185         bool gotpairs = false;
186         UInt_t nL0 = 0;
187         UInt_t nhits = 0;
188         UInt_t nhitsMTR = 0;
189         UInt_t nhitsMCH = 0;
190         UInt_t nhitsCh[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
191         UInt_t ntrigrecs = 0;
192         UInt_t nL0plus = 0;
193         UInt_t nL0minus = 0;
194         UInt_t ntracksT = 0;  // from track blocks.
195         UInt_t ntracksSD = 0; // from singles decision blocks
196         UInt_t nplus = 0;
197         UInt_t nminus = 0;
198         UInt_t nlowpt = 0;
199         UInt_t nhighpt = 0;
200         Double_t minpt = -1;
201         Double_t maxpt = -1;
202         UInt_t nlikeany = 0;
203         UInt_t nlikelow = 0;
204         UInt_t nlikehigh = 0;
205         UInt_t nunlikeany = 0;
206         UInt_t nunlikelow = 0;
207         UInt_t nunlikehigh = 0;
208         UInt_t nlowmass = 0;
209         UInt_t nhighmass = 0;
210         Double_t minmass = -1;
211         Double_t maxmass = -1;
212         
213         AliHLTComponentDataType blockType = AliHLTMUONConstants::DDLRawDataType();
214         for (const AliHLTComponentBlockData* block = GetFirstInputBlock(blockType);
215              block != NULL;
216              block = GetNextInputBlock()
217             )
218         {
219                 gotddls = true;
220                 nL0 = 1;
221         }
222         
223         blockType = AliHLTMUONConstants::TriggerRecordsBlockDataType();
224         for (const AliHLTComponentBlockData* block = GetFirstInputBlock(blockType);
225              block != NULL;
226              block = GetNextInputBlock()
227             )
228         {
229                 AliHLTMUONTriggerRecordsBlockReader trigRecsBlock(block->fPtr, block->fSize);
230                 if (not IsBlockOk(trigRecsBlock, blockType))
231                 {
232                         HLTWarning("Skipping problematic block '%s'", DataType2Text(blockType).c_str());
233                         continue;
234                 }
235                 gothits = true;
236                 
237                 ntrigrecs += trigRecsBlock.Nentries();
238                 for (AliHLTUInt32_t i = 0; i < trigRecsBlock.Nentries(); ++i)
239                 {
240                         AliHLTMUONParticleSign sign;
241                         bool hitset[4];
242                         AliHLTMUONUtils::UnpackTriggerRecordFlags(trigRecsBlock[i].fFlags, sign, hitset);
243                         switch (sign)
244                         {
245                         case kSignPlus: ++nL0plus; break;
246                         case kSignMinus: ++nL0minus; break;
247                         default: break;
248                         }
249                         for (int j = 0; j < 4; ++j)
250                         {
251                                 if (hitset[j])
252                                 {
253                                         ++nhitsCh[j+10];
254                                         ++nhits;
255                                         ++nhitsMTR;
256                                 }
257                         }
258                 }
259         }
260         
261         blockType = AliHLTMUONConstants::RecHitsBlockDataType();
262         for (const AliHLTComponentBlockData* block = GetFirstInputBlock(blockType);
263              block != NULL;
264              block = GetNextInputBlock()
265             )
266         {
267                 AliHLTMUONRecHitsBlockReader hitsBlock(block->fPtr, block->fSize);
268                 if (not IsBlockOk(hitsBlock, blockType))
269                 {
270                         HLTWarning("Skipping problematic block '%s'", DataType2Text(blockType).c_str());
271                         continue;
272                 }
273                 gottrigrecs = true;
274                 
275                 nhits += hitsBlock.Nentries();
276                 nhitsMCH += hitsBlock.Nentries();
277                 for (AliHLTUInt32_t i = 0; i < hitsBlock.Nentries(); ++i)
278                 {
279                         AliHLTUInt8_t chamber;
280                         AliHLTUInt16_t detElemId;
281                         AliHLTMUONUtils::UnpackRecHitFlags(hitsBlock[i].fFlags, chamber, detElemId);
282                         if (chamber < 10)
283                         {
284                                 ++nhitsCh[chamber];
285                         }
286                         else
287                         {
288                                 HLTWarning("Received a reconstructed hit which indicates"
289                                         " an invalid chamber number of %d. The expected"
290                                         " range is [0..9]. The data block is probably corrupt.",
291                                         int(chamber)
292                                 );
293                         }
294                 }
295         }
296         
297         blockType = AliHLTMUONConstants::MansoTracksBlockDataType();
298         for (const AliHLTComponentBlockData* block = GetFirstInputBlock(blockType);
299              block != NULL;
300              block = GetNextInputBlock()
301             )
302         {
303                 AliHLTMUONMansoTracksBlockReader tracksBlock(block->fPtr, block->fSize);
304                 if (not IsBlockOk(tracksBlock, blockType))
305                 {
306                         HLTWarning("Skipping problematic block '%s'", DataType2Text(blockType).c_str());
307                         continue;
308                 }
309                 gottracks = true;
310                 
311                 ntracksT += tracksBlock.Nentries();
312                 for (AliHLTUInt32_t i = 0; i < tracksBlock.Nentries(); ++i)
313                 {
314                         AliHLTMUONParticleSign sign;
315                         bool hitset[4];
316                         AliHLTMUONUtils::UnpackMansoTrackFlags(tracksBlock[i].fFlags, sign, hitset);
317                         switch (sign)
318                         {
319                         case kSignPlus: ++nplus; break;
320                         case kSignMinus: ++nminus; break;
321                         default: break;
322                         }
323                 }
324         }
325         
326         blockType = AliHLTMUONConstants::TracksBlockDataType();
327         for (const AliHLTComponentBlockData* block = GetFirstInputBlock(blockType);
328              block != NULL;
329              block = GetNextInputBlock()
330             )
331         {
332                 AliHLTMUONTracksBlockReader tracksBlock(block->fPtr, block->fSize);
333                 if (not IsBlockOk(tracksBlock, blockType))
334                 {
335                         HLTWarning("Skipping problematic block '%s'", DataType2Text(blockType).c_str());
336                         continue;
337                 }
338                 gottracks = true;
339                 
340                 ntracksT += tracksBlock.Nentries();
341                 for (AliHLTUInt32_t i = 0; i < tracksBlock.Nentries(); ++i)
342                 {
343                         AliHLTMUONParticleSign sign;
344                         bool hitset[16];
345                         AliHLTMUONUtils::UnpackTrackFlags(tracksBlock[i].fFlags, sign, hitset);
346                         switch (sign)
347                         {
348                         case kSignPlus: ++nplus; break;
349                         case kSignMinus: ++nminus; break;
350                         default: break;
351                         }
352                 }
353         }
354         
355         blockType = AliHLTMUONConstants::SinglesDecisionBlockDataType();
356         for (const AliHLTComponentBlockData* block = GetFirstInputBlock(blockType);
357              block != NULL;
358              block = GetNextInputBlock()
359             )
360         {
361                 AliHLTMUONSinglesDecisionBlockReader singlesBlock(block->fPtr, block->fSize);
362                 if (not IsBlockOk(singlesBlock, blockType))
363                 {
364                         HLTWarning("Skipping problematic block '%s'", DataType2Text(blockType).c_str());
365                         continue;
366                 }
367                 gotsingles = true;
368                 
369                 ntracksSD += singlesBlock.Nentries();
370                 nlowpt += singlesBlock.BlockHeader().fNlowPt;
371                 nhighpt += singlesBlock.BlockHeader().fNhighPt;
372                 for (AliHLTUInt32_t i = 0; i < singlesBlock.Nentries(); ++i)
373                 {
374                         if (singlesBlock[i].fPt < minpt or minpt == -1) minpt = singlesBlock[i].fPt;
375                         if (singlesBlock[i].fPt > maxpt or maxpt == -1) maxpt = singlesBlock[i].fPt;
376                 }
377         }
378         
379         blockType = AliHLTMUONConstants::PairsDecisionBlockDataType();
380         for (const AliHLTComponentBlockData* block = GetFirstInputBlock(blockType);
381              block != NULL;
382              block = GetNextInputBlock()
383             )
384         {
385                 AliHLTMUONPairsDecisionBlockReader pairsBlock(block->fPtr, block->fSize);
386                 if (not IsBlockOk(pairsBlock, blockType))
387                 {
388                         HLTWarning("Skipping problematic block '%s'", DataType2Text(blockType).c_str());
389                         continue;
390                 }
391                 gotpairs = true;
392                 
393                 nunlikeany += pairsBlock.BlockHeader().fNunlikeAnyPt;
394                 nlikeany += pairsBlock.BlockHeader().fNlikeAnyPt;
395                 // Dont use the other scalars from the pair decisions block header because
396                 // they are much more restrictive. They count pairs where both tracks pass
397                 // the low or high pT cut. But we want to relax this to just one track needs
398                 // to pass the pT cut.
399                 for (AliHLTUInt32_t i = 0; i < pairsBlock.Nentries(); ++i)
400                 {
401                         if (pairsBlock[i].fInvMass < minmass or minmass == -1) minmass = pairsBlock[i].fInvMass;
402                         if (pairsBlock[i].fInvMass > maxmass or maxmass == -1) maxmass = pairsBlock[i].fInvMass;
403                         bool highMass, lowMass, unlike;
404                         AliHLTUInt8_t highPtCount, lowPtCount;
405                         AliHLTMUONUtils::UnpackPairDecisionBits(
406                                 pairsBlock[i].fTriggerBits, highMass, lowMass, unlike,
407                                 highPtCount, lowPtCount
408                         );
409                         if (unlike)
410                         {
411                                 if (lowPtCount >= 1) ++nunlikelow;
412                                 if (highPtCount >= 1) ++nunlikehigh;
413                                 if (lowMass) ++nlowmass;
414                                 if (highMass) ++nhighmass;
415                         }
416                         else
417                         {
418                                 if (lowPtCount >= 1) ++nlikelow;
419                                 if (highPtCount >= 1) ++nlikehigh;
420                         }
421                 }
422         }
423         
424         // Select the largest value for nTracks since we might only get this information
425         // from singles decision blocks.
426         UInt_t ntracks = ntracksSD > ntracksT ? ntracksSD : ntracksT;
427         
428         bool triggeredOnDDLs = fTriggerDDLs and nL0 > 0;
429         bool triggeredOnHits = fTriggerHits and nhitsMCH > 0;
430         bool triggeredOnTrigRecs = fTriggerTrigRecs and ntrigrecs > 0;
431         bool triggeredOnTracks = fTriggerTracks and ntracks > 0;
432         bool triggeredOnDimuons = fTriggerDimuons and (nunlikeany > 0
433                 or nunlikelow > 0 or nunlikehigh > 0 or nlowmass > 0 or nhighmass > 0);
434         
435         if (triggeredOnDimuons)
436         {
437                 SetDescription("Dimuon in muon spectrometer");
438                 SetTriggerDomain(AliHLTTriggerDomain("*******:MUON"));
439         }
440         else if (triggeredOnTracks)
441         {
442                 SetDescription("Tracks in muon spectrometer");
443                 SetTriggerDomain(AliHLTTriggerDomain("*******:MUON"));
444         }
445         else if (triggeredOnTrigRecs)
446         {
447                 SetDescription("Muon trigger chambers triggered");
448                 if (triggeredOnHits)
449                 {
450                         SetReadoutList(AliHLTReadoutList(AliHLTReadoutList::kMUONTRG | AliHLTReadoutList::kMUONTRK));
451                         SetTriggerDomain(AliHLTTriggerDomain("TRIGRECS:MUON,RECHITS :MUON"));
452                 }
453                 else
454                 {
455                         SetReadoutList(AliHLTReadoutList(AliHLTReadoutList::kMUONTRG));
456                         SetTriggerDomain(AliHLTTriggerDomain("TRIGRECS:MUON"));
457                 }
458         }
459         else if (triggeredOnHits)
460         {
461                 SetDescription("Hits in muon tracking chambers");
462                 SetReadoutList(AliHLTReadoutList(AliHLTReadoutList::kMUONTRK));
463                 SetTriggerDomain(AliHLTTriggerDomain("RECHITS :MUON"));
464         }
465         else if (triggeredOnDDLs)
466         {
467                 SetDescription("DDL in muon tracking chambers");
468                 SetReadoutList(AliHLTReadoutList(AliHLTReadoutList::kMUONTRG | AliHLTReadoutList::kMUONTRK));
469                 SetTriggerDomain(AliHLTTriggerDomain("DDL_RAW :MUON"));
470         }
471         else
472         {
473                 SetDescription("Not triggered");
474                 SetTriggerDomain(AliHLTTriggerDomain());
475         }
476         
477         if (triggeredOnDimuons or triggeredOnTracks or triggeredOnTrigRecs or triggeredOnHits or triggeredOnDDLs)
478         {
479                 result = TriggerEvent();
480                 if (result == -ENOSPC) goto increaseBuffer;
481                 if (result != 0) return result;
482         }
483         
484         if (fMakeStats)
485         {
486                 AliHLTMuonSpectroScalars scalars;
487                 if (gotddls) scalars.Add("NL0", "Number of L0 triggered event", nL0);
488                 if (gothits and gottrigrecs) scalars.Add("NHits", "Total number of hits", nhits);
489                 if (gottrigrecs) scalars.Add("NHitsMTR", "Number of hits in trigger chambers", nhitsMTR);
490                 if (gothits)
491                 {
492                         scalars.Add("NHitsMCH", "Number of hits in tracking chambers", nhitsMCH);
493                         for (int i = 0; i < 10; i++)
494                         {
495                                 scalars.Add(Form("NHitsCh%d", i+1), Form("Number of hits in chamber %d", i+1), nhitsCh[i]);
496                         }
497                 }
498                 if (gottrigrecs)
499                 {
500                         for (int i = 10; i < 14; i++)
501                         {
502                                 scalars.Add(Form("NHitsCh%d", i+1), Form("Number of hits in chamber %d", i+1), nhitsCh[i]);
503                         }
504                         scalars.Add("NTrigRecs", "Total number of trigger records", ntrigrecs);
505                         scalars.Add("NL0+", "Number of positive sign tracks in L0", nL0plus);
506                         scalars.Add("NL0-", "Number of negative sign tracks in L0", nL0minus);
507                 }
508                 if (gottracks or gotsingles) scalars.Add("NTracks", "Total number of tracks", ntracks);
509                 if (gottracks)
510                 {
511                         scalars.Add("N+", "Number of positive sign tracks", nplus);
512                         scalars.Add("N-", "Number of negative sign tracks", nminus);
513                 }
514                 if (gotsingles)
515                 {
516                         scalars.Add("NLowPt", "Number of low pT tracks", nlowpt);
517                         scalars.Add("NHighPt", "Number of high pT tracks", nhighpt);
518                         scalars.Add("MinPt", "Minimum pT found (GeV/c)", minpt);
519                         scalars.Add("MaxPt", "Maximum pT found (GeV/c)", maxpt);
520                 }
521                 if (gotpairs)
522                 {
523                         scalars.Add("NLikeAny", "Number of like sign track pairs", nlikeany);
524                         scalars.Add("NLikeLow", "Number of like sign pairs with at least 1 low pT track.", nlikelow);
525                         scalars.Add("NLikeHigh", "Number of like sign pairs with at least 1 high pT track.", nlikehigh);
526                         scalars.Add("NUnlikeAny", "Number of unlike sign track pairs", nunlikeany);
527                         scalars.Add("NUnlikeLow", "Number of unlike sign pairs with at least 1 low pT track.", nunlikelow);
528                         scalars.Add("NUnlikeHigh", "Number of unlike sign pairs with at least 1 high pT track.", nunlikehigh);
529                         scalars.Add("NLowMass", "Number of low mass dimuons", nlowmass);
530                         scalars.Add("NHighMass", "Number of high mass dimuons", nhighmass);
531                         scalars.Add("MinMass", "Minimum invariant mass found for dimuon (GeV/c^2)", minmass);
532                         scalars.Add("MaxMass", "Maximum invariant mass found for dimuon (GeV/c^2)", maxmass);
533                 }
534                 
535                 result = PushBack(&scalars, kAliHLTDataTypeEventStatistics|kAliHLTDataOriginHLT);
536                 if (result == -ENOSPC) goto increaseBuffer;
537         }
538         return result;
539         
540 increaseBuffer:
541         // Increase the estimated buffer space required since the PushBack
542         // or TriggerEvent methods indicate that they ran out of buffer space.
543         fBufferSizeConst += 1024*1024;
544         fBufferSizeMultiplier *= 2.;
545         return -ENOSPC;
546 }
547
548
549 template <typename BlockReader>
550 bool AliHLTMuonSpectroTriggerComponent::IsBlockOk(
551                 const BlockReader& reader, const AliHLTComponentDataType& type
552         ) const
553 {
554         // Method for checking the block structure.
555         
556         if (not reader.BufferSizeOk())
557         {
558                 string name = DataType2Text(type).c_str();
559                 size_t headerSize = sizeof(typename BlockReader::HeaderType);
560                 if (reader.BufferSize() < headerSize)
561                 {
562                         HLTError("Received a '%s' data block with a size of %d bytes,"
563                                 " which is smaller than the minimum valid size of %d bytes."
564                                 " The block must be corrupt.",
565                                 name.c_str(), reader.BufferSize(), headerSize
566                         );
567                 }
568                 
569                 size_t expectedWidth = sizeof(typename BlockReader::ElementType);
570                 if (reader.CommonBlockHeader().fRecordWidth != expectedWidth)
571                 {
572                         HLTError("Received a '%s' data block with a record"
573                                 " width of %d bytes, but the expected value is %d bytes."
574                                 " The block might be corrupt.",
575                                 name.c_str(),
576                                 reader.CommonBlockHeader().fRecordWidth,
577                                 expectedWidth
578                         );
579                 }
580                 
581                 HLTError("Received a '%s' data block with a size of %d bytes,"
582                         " but the block header claims the block should be %d bytes."
583                         " The block might be corrupt.",
584                         name.c_str(), reader.BufferSize(), reader.BytesUsed()
585                 );
586                 return false;
587         }
588         return true;
589 }