b0689ff18a33aedf05271e7ece19a437b0801f0f
[u/mrichter/AliRoot.git] / ITS / ITSSPDPHYSda.cxx
1 /*
2 Contact: henrik.tydesjo@cern.ch
3 Link: tydes.home.cern.ch/tydes/doc/CalibrationOverview/CalibrationAlgorithms/
4 Run Type: PHYSICS
5 DA Type: MON
6 Number of events needed: Depending on muliplicity per event
7 Input Files: spd_physics_params ,  ./calibResults/DeadReferenceTmp/* ,  ./calibResults/DeadToFXS/*
8 Output Files: ./calibResults/NoisyReference/* ,  ./calibResults/DeadReference/* ,  ./calibResults/NoisyToFXS/* ,  ./calibResults/DeadReferenceTmp/*,./calibResults/DeadToFXS/*
9 Trigger types used: PHYSICS
10 */
11
12 ////////////////////////////////////////////////////////////////////////////////
13 // This program can be compiled in two modes.                                 //
14 //                                                                            //
15 // 1. Online. With the DAQ DA framework. This is the default operating mode.  //
16 //                                                                            //
17 // 2. Offline. Without the DAQ DA framework. Define the SPD_DA_OFF            //
18 //    environment var. Call this program with the name of the executable      //
19 //    followed by the runNr and the data files to process.                    //
20 //                                                                            //
21 ////////////////////////////////////////////////////////////////////////////////
22
23 #ifndef SPD_DA_OFF
24 extern "C" {
25 #include "daqDA.h"
26 }
27 #endif
28 #include "event.h"
29 #include "monitor.h"
30 #include "AliRawReaderDate.h"
31 #include "AliITSRawStreamSPD.h"
32 #include "AliITSOnlineSPDphys.h"
33 #include "AliITSOnlineSPDphysAnalyzer.h"
34 #include "AliITSOnlineCalibrationSPDhandler.h"
35 #include "AliLog.h"
36 #include <iostream>
37 #include <fstream>
38 #include <TROOT.h>
39 #include <TPluginManager.h>
40 #include <TObjArray.h>
41 #include <TString.h>
42
43 int main(int argc, char **argv) {
44   if (argc<2) {
45     printf("Wrong number of arguments\n");
46     return -1;
47   }
48
49   // directory structure, hard coded
50   char *saveDirDead          = "./calibResults/Dead";             // may NOT delete content
51   char *saveDirDeadToFXS     = "./calibResults/DeadToFXS";        //     may delete content
52   char *saveDirDeadRef       = "./calibResults/DeadReference";    //     may delete content
53   char *saveDirDeadRefTmp    = "./calibResults/DeadReferenceTmp"; // may NOT delete content
54   char *saveDirNoisyToFXS    = "./calibResults/NoisyToFXS";       //     may delete content
55   char *saveDirNoisyRef      = "./calibResults/NoisyReference";   //     may delete content
56   char *saveDirIdsToFXS      = "./calibResults/IdsToFXS";         //     may delete content
57   char *configFilesDir       = "./configFiles";                   //     may delete content
58   // make sure the directory structure is put up correctly:
59   system("mkdir ./calibResults >& /dev/null");
60   system("mkdir ./calibResults/Dead >& /dev/null");
61   system("mkdir ./calibResults/DeadToFXS >& /dev/null");
62   system("mkdir ./calibResults/DeadReference >& /dev/null");
63   system("mkdir ./calibResults/DeadReferenceTmp >& /dev/null");
64   system("mkdir ./calibResults/NoisyToFXS >& /dev/null");
65   system("mkdir ./calibResults/NoisyReference >& /dev/null");
66   system("mkdir ./calibResults/IdsToFXS >& /dev/null");
67   system("mkdir ./configFiles >& /dev/null");
68   // parameters config file
69   TString paramsFileName = Form("%s/physics_params.txt",configFilesDir);
70
71   // This line is needed in case of a stand-alone application w/o
72   // $ROOTSYS/etc/system.rootrc file
73   gROOT->GetPluginManager()->AddHandler("TVirtualStreamerInfo",
74                                         "*",
75                                         "TStreamerInfo",
76                                         "RIO",
77                                         "TStreamerInfo()");
78
79   // turn off annoying warning messages
80   new AliLog;
81   AliLog::Instance()->SetGlobalDebugLevel(-20);
82
83
84   // ********* STEP 0: Get configuration files from db (if there are any) , then read parameters*********
85   UInt_t nrTuningParams = 0;
86   TObjArray paramNames;  paramNames.SetOwner(kTRUE);
87   TObjArray paramVals;  paramVals.SetOwner(kTRUE);
88   
89   // tuning parameters:
90   Int_t par_status = 0;
91 #ifndef SPD_DA_OFF
92   TString idp = "spd_physics_params";
93   par_status=daqDA_DB_getFile(idp.Data(),paramsFileName.Data());
94   if (par_status) {
95     printf("Failed to get config file %s: status=%d. Using default tuning parameters.\n",idp.Data(),par_status);
96   }
97 #endif
98   if (par_status==0) {
99     ifstream paramsFile;
100     paramsFile.open(paramsFileName.Data(), ifstream::in);
101     if (paramsFile.fail()) {
102       printf("No config file (%s) present. Using default tuning parameters.\n",paramsFileName.Data());
103     }
104     else {
105       while(1) {
106         Char_t paramN[50];
107         Char_t paramV[50];
108         paramsFile >> paramN;
109         if (paramsFile.eof()) break;
110         paramsFile >> paramV;
111         TString* paramNS = new TString(paramN);
112         TString* paramVS = new TString(paramV);
113         paramNames.AddAtAndExpand((TObject*)paramNS,nrTuningParams);
114         paramVals.AddAtAndExpand((TObject*)paramVS,nrTuningParams);
115         nrTuningParams++;
116         if (paramsFile.eof()) break;
117       }
118       paramsFile.close();
119     }
120   }
121   //  for (UInt_t i=0; i<nrTuningParams; i++) {
122   //    printf("Entry %d: N=%s , V=%s\n",i,((TString*)paramNames.At(i))->Data(),((TString*)paramVals.At(i))->Data());
123   //  }
124
125
126
127
128
129   // create calibration handler (needed already at step 1 in order to fill which eq,hs,chips are active
130   AliITSOnlineCalibrationSPDhandler* handler = new AliITSOnlineCalibrationSPDhandler();
131   // Read silent=dead+inactive info from previous calibrations
132   handler->SetFileLocation(saveDirDead);
133   handler->ReadSilentFromFiles();
134 //!!!  // start by deactivating all eq - should then be activated when found in raw data
135 //!!!  for (UInt_t eq=0; eq<20; eq++) {
136 //!!!    handler->ActivateEq(eq,kFALSE);
137 //!!!  }
138
139
140
141
142   // ********* STEP 1: Produce phys container files (Reference Data). ***********************************
143
144 #ifndef SPD_DA_OFF
145   if (getenv("DATE_RUN_NUMBER")==0) {
146     printf("DATE_RUN_NUMBER not properly set.\n");
147     return -1;
148   }
149   int runNr = atoi(getenv("DATE_RUN_NUMBER"));
150 #else
151   int runNr = atoi(argv[1]);
152   int startSeg = 2;
153 #endif
154
155
156   // container objects
157   AliITSOnlineSPDphys *physObj[20];
158   Bool_t bPhysInit[20];
159   for (UInt_t eqId=0; eqId<20; eqId++) {
160     physObj[eqId]=NULL;
161     bPhysInit[eqId]=kFALSE;
162   }
163
164
165   // loop over run segments in case of offline mode
166 #ifdef SPD_DA_OFF
167   for (int segNr=startSeg; segNr<argc; segNr++) {
168 #endif
169
170     int status;
171
172     /* define data source : */  
173 #ifndef SPD_DA_OFF
174     status=monitorSetDataSource( argv[1] ); // should be "^SPD" in order to get full detector online
175 #else
176     status=monitorSetDataSource( argv[segNr] );
177 #endif
178     if (status!=0) {
179       printf("monitorSetDataSource() failed : %s\n",monitorDecodeError(status));
180       return -1;
181     }
182     /* declare monitoring program */
183     status=monitorDeclareMp("ITS_SPD_PHYS");
184     if (status!=0) {
185       printf("monitorDeclareMp() failed : %s\n",monitorDecodeError(status));
186       return -1;
187     }
188     /* define wait event timeout - 1s max */
189     monitorSetNowait();
190     monitorSetNoWaitNetworkTimeout(1000);
191
192
193     UInt_t eventNr=0;
194
195     /* main loop (infinite) */
196     for(;;) {
197
198       struct eventHeaderStruct *event;
199       eventTypeType eventT;
200
201       /* check shutdown condition */
202 #ifndef SPD_DA_OFF
203       if (daqDA_checkShutdown()) {break;}
204 #endif
205
206       /* get next event (blocking call until timeout) */
207       status=monitorGetEventDynamic((void **)&event);
208       if (status==MON_ERR_EOF) {
209         printf ("End of File detected\n");
210         break; /* end of monitoring file has been reached */
211       }
212
213       if (status!=0) {
214         printf("monitorGetEventDynamic() failed : %s\n",monitorDecodeError(status));
215         break;
216       }
217
218       /* retry if got no event */
219       if (event==NULL) {
220         continue;
221       }
222
223       eventT=event->eventType;
224       if (eventT == PHYSICS_EVENT){
225
226         //      printf("eventNr %d\n",eventNr);
227
228         AliRawReader *reader = new AliRawReaderDate((void*)event);
229         AliITSRawStreamSPD *str = new AliITSRawStreamSPD(reader);
230
231         while (str->Next()) {
232
233           Int_t eqId = reader->GetDDLID();
234           // check that this hs is active in handler object
235           if (!(handler->IsActiveEq(eqId))) {
236             printf("Warning: Found Eq (%d) , previously inactive in this run!\n",eqId);
237             handler->ActivateEq(eqId);
238           }
239           if (eqId>=0 && eqId<20) {
240             if (!bPhysInit[eqId]) { // this code is duplicated for the moment... (see also below)
241               TString fileName = Form("%s/SPDphys_run_%d_eq_%d.root",saveDirNoisyRef,runNr,eqId);
242               physObj[eqId] = new AliITSOnlineSPDphys(fileName.Data());
243               physObj[eqId]->AddRunNr(runNr);
244               physObj[eqId]->SetEqNr(eqId);
245               bPhysInit[eqId]=kTRUE;
246             }
247
248             UInt_t hs = str->GetHalfStaveNr();
249             // check that this hs is active in handler object
250             if (!(handler->IsActiveHS(eqId,hs))) {
251               printf("Warning: Found HS (%d,%d) , previously inactive in this run!\n",eqId,hs);
252               handler->ActivateHS(eqId,hs);
253             }
254             UInt_t chip = str->GetChipAddr();
255             // check that this chip is active in handler object
256             if (!(handler->IsActiveChip(eqId,hs,chip))) {
257               printf("Info: Found Chip (%d,%d,%d) , inactive in previous run.\n",eqId,hs,chip);
258               handler->ActivateChip(eqId,hs,chip);
259             }
260             physObj[eqId]->IncrementHits(hs,chip,str->GetChipCol(),str->GetChipRow());
261             
262           }
263         }
264
265
266         // check which eq and hs are active for first event only
267         if (eventNr==0) {
268           for (UInt_t eqId=0; eqId<20; eqId++) {
269             // activate Eq and HSs in handler object
270             if (str->IsActiveEq(eqId)) {
271               handler->ActivateEq(eqId);
272               for (UInt_t hs=0; hs<6; hs++) {
273                 handler->ActivateHS(eqId,hs,str->IsActiveHS(eqId,hs));
274               }
275               if (!bPhysInit[eqId]) { // this code is duplicated for the moment... (see also above)
276                 TString fileName = Form("%s/SPDphys_run_%d_eq_%d.root",saveDirNoisyRef,runNr,eqId);
277                 physObj[eqId] = new AliITSOnlineSPDphys(fileName.Data());
278                 physObj[eqId]->AddRunNr(runNr);
279                 physObj[eqId]->SetEqNr(eqId);
280                 bPhysInit[eqId]=kTRUE;
281               }
282             }
283             else {
284               handler->ActivateEq(eqId,kFALSE);
285             }
286           }
287         }
288
289
290         for (UInt_t eq=0; eq<20; eq++) {
291           if (bPhysInit[eq]) {
292             physObj[eq]->IncrementNrEvents();
293           }
294         }
295
296         delete str;
297         delete reader;
298
299         eventNr++;
300
301       }
302
303       /* free resources */
304       free(event);
305
306     }
307     
308
309 #ifdef SPD_DA_OFF
310     printf("progress: %d\n",(unsigned int)( ((Float_t)(segNr-startSeg+1))/(argc-startSeg)*50 ));
311   }
312 #endif
313   
314   // clean up phys objects (also saves them)
315   for (UInt_t eq=0; eq<20; eq++) {
316     if (physObj[eq]!=NULL) delete physObj[eq];
317   }
318
319
320
321
322
323   // ********* STEP 2: Analyze phys container files. ************************************************
324
325   // clear noisyToFXS dir:
326   TString command;
327   command = Form("cd %s; rm -f *",saveDirNoisyToFXS);
328   system(command.Data());
329   // clear deadToFXS dir:
330   command = Form("cd %s; rm -f *",saveDirDeadToFXS);
331   system(command.Data());
332
333
334   UInt_t firstRunNrDead = runNr;
335
336
337   UInt_t nrEnoughStatNoisy = 0;
338   UInt_t nrEqActiveNoisy = 0;
339   Bool_t eqActiveNoisy[20];
340
341   // *** *** *** start loop over equipments (eq_id)
342   for (UInt_t eq=0; eq<20; eq++) {
343     eqActiveNoisy[eq] = kFALSE;
344
345     // create analyzer for this eq
346     TString fileName = Form("%s/SPDphys_run_%d_eq_%d.root",saveDirNoisyRef,runNr,eq);
347     AliITSOnlineSPDphysAnalyzer *noisyAnalyzer = new AliITSOnlineSPDphysAnalyzer(fileName.Data(),handler);
348
349     // check data in container
350     if (noisyAnalyzer->GetEqNr() != eq) {
351       if (noisyAnalyzer->GetEqNr() != 999) {
352         printf("Error: Mismatching EqId in Container data and filename (%d!=%d). Skipping.\n",
353                noisyAnalyzer->GetEqNr(),eq);
354       }
355       delete noisyAnalyzer;
356       continue;
357     }
358
359     nrEqActiveNoisy++;
360     eqActiveNoisy[eq] = kTRUE;
361
362     // configure analyzer with tuning parameters etc:
363     for (UInt_t i=0; i<nrTuningParams; i++) {
364       noisyAnalyzer->SetParam(((TString*)paramNames.At(i))->Data(),((TString*)paramVals.At(i))->Data());
365     }
366
367     printf("SPD phys STEP 2: Noisy search for eq %d\n",eq);  
368
369     // search for noisy pixels:
370     nrEnoughStatNoisy += noisyAnalyzer->ProcessNoisyPixels();
371
372     // copy this phys obj to temporary dead reference dir to process after noisy search
373     TString fileNameDead = Form("%s/SPDphys_dead_run_0_0_eq_%d.root",saveDirDeadRefTmp,eq);
374     AliITSOnlineSPDphys* physObj = new AliITSOnlineSPDphys(fileNameDead.Data());
375     physObj->AddPhys(noisyAnalyzer->GetOnlinePhys());
376     if (physObj->GetNrRuns()>0) {
377       UInt_t firstRunNr = physObj->GetRunNr(0);
378       if (firstRunNrDead>firstRunNr) {
379         firstRunNrDead=firstRunNr;
380       }
381     }
382     // remove noisy pixels from dead hitmap
383     for (UInt_t hs=0; hs<6; hs++) {
384       for (UInt_t chip=0; chip<10; chip++) {
385         for (UInt_t ind=0; ind<handler->GetNrNoisyC(eq,hs,chip); ind++) {
386           UInt_t col  = handler->GetNoisyColAtC(eq,hs,chip,ind);
387           UInt_t row  = handler->GetNoisyRowAtC(eq,hs,chip,ind);
388           physObj->AddHits(hs,chip,col,row,-noisyAnalyzer->GetOnlinePhys()->GetHits(hs,chip,col,row));
389         }
390       }
391     }
392
393     delete physObj;
394     delete noisyAnalyzer;
395
396 #ifndef SPD_DA_OFF
397     daqDA_progressReport((unsigned int)((eq+1)*2.5));
398 #else
399     printf("progress: %d\n",(unsigned int)(50+(eq+1)*1.25));
400 #endif
401   }
402   // *** *** *** end loop over equipments (eq_id)
403
404   printf("Noisy search finished. %d noisy pixels found. %d chips (%d) had enough statistics.\n",
405          handler->GetNrNoisy(),nrEnoughStatNoisy,nrEqActiveNoisy*60);
406   handler->SetFileLocation(saveDirNoisyToFXS);
407   handler->WriteNoisyToFiles();
408
409
410
411
412
413
414
415
416   UInt_t nrEnoughStatChips = 0;
417   UInt_t nrDeadChips = 0;
418   UInt_t nrInefficientChips = 0;
419   UInt_t nrEqActiveDead = 0;
420   Bool_t eqActiveDead[20];
421
422   // *** *** *** start loop over equipments (eq_id)
423   for (UInt_t eq=0; eq<20; eq++) {
424     eqActiveDead[eq] = kFALSE;
425
426     // setup analyzer for dead search
427     TString fileNameDead = Form("%s/SPDphys_dead_run_0_0_eq_%d.root",saveDirDeadRefTmp,eq);
428     AliITSOnlineSPDphys* physObj = new AliITSOnlineSPDphys(fileNameDead.Data());
429     AliITSOnlineSPDphysAnalyzer* deadAnalyzer = new AliITSOnlineSPDphysAnalyzer(physObj,handler);
430     // check data in container
431     if (deadAnalyzer->GetEqNr() != eq) {
432       if (deadAnalyzer->GetEqNr() != 999) {
433         printf("Error: Mismatching EqId in Dead Container data and filename (%d!=%d). Skipping.\n",
434                deadAnalyzer->GetEqNr(),eq);
435       }
436       delete deadAnalyzer;
437       //!!!      nrDeadChips+=60; // since this eq is inactive...
438       continue;
439     }
440
441     nrEqActiveDead++;
442     eqActiveDead[eq] = kTRUE;
443
444     // configure analyzer with tuning parameters etc:
445     for (UInt_t i=0; i<nrTuningParams; i++) {
446       deadAnalyzer->SetParam(((TString*)paramNames.At(i))->Data(),((TString*)paramVals.At(i))->Data());
447     }
448
449     printf("SPD phys STEP 2: Dead search for eq %d\n",eq);  
450
451     // search for dead pixels:
452     nrEnoughStatChips += deadAnalyzer->ProcessDeadPixels();
453     nrDeadChips += deadAnalyzer->GetNrDeadChips();
454     nrInefficientChips += deadAnalyzer->GetNrInefficientChips();
455
456     delete deadAnalyzer;
457
458 #ifndef SPD_DA_OFF
459     daqDA_progressReport((unsigned int)(50+(eq+1)*2.5));
460 #else
461     printf("progress: %d\n",(unsigned int)(75+(eq+1)*1.25));
462 #endif
463   }
464   // *** *** *** end loop over equipments (eq_id)
465
466   
467   printf("Dead search finished. %d dead pixels in total.\n%d chips (%d) had enough statistics. %d chips were dead. %d chips were inefficient.\n",handler->GetNrDead(),nrEnoughStatChips,nrEqActiveDead*60,nrDeadChips,nrInefficientChips);
468   handler->SetFileLocation(saveDirDead);
469   handler->WriteSilentToFilesAlways();
470   handler->SetFileLocation(saveDirDeadToFXS);
471   handler->WriteSilentToFilesAlways();
472
473
474   printf("Opening id list file\n");
475   TString idsFXSFileName = Form("%s/FXSids_run_%d.txt",saveDirIdsToFXS,runNr);
476   ofstream idsFXSfile;
477   idsFXSfile.open(idsFXSFileName.Data());
478
479
480   // send (dead) reference data for this run to FXS - only if there is no chip in category "needsMoreStat"
481   if (nrEnoughStatChips+nrDeadChips+nrInefficientChips == nrEqActiveDead*60) {
482     printf("Dead calibration is complete.\n");    // calibration is complete
483     printf("Preparing dead reference data\n");
484     // send reference data for dead pixels to FXS
485     TString tarFiles = "";
486     for (UInt_t eq=0; eq<20; eq++) {
487       if (eqActiveDead[eq]) {
488         printf("Preparing dead pixels for eq %d\n",eq);
489         // move file to ref dir
490         TString fileName = Form("%s/SPDphys_dead_run_0_0_eq_%d.root",saveDirDeadRefTmp,eq);
491         TString newFileName = Form("%s/SPDphys_dead_run_%d_%d_eq_%d.root",saveDirDeadRef,firstRunNrDead,runNr,eq);
492         TString command = Form("mv -f %s %s",fileName.Data(),newFileName.Data());
493         system(command.Data());
494
495         tarFiles.Append(Form("SPDphys_dead_run_%d_%d_eq_%d.root ",firstRunNrDead,runNr,eq));
496       }
497     }
498     TString send_command = Form("cd %s; tar -cf ref_phys_dead.tar %s",saveDirDeadRef,tarFiles.Data());
499     system(send_command.Data());
500     TString fileName = Form("%s/ref_phys_dead.tar",saveDirDeadRef);
501     TString id = "SPD_ref_phys_dead";
502 #ifndef SPD_DA_OFF
503     status = daqDA_FES_storeFile(fileName.Data(),id.Data());
504     if (status!=0) {
505       printf("Failed to export file %s , status %d\n",fileName.Data(),status);
506       return -1;
507     }
508 #endif
509     idsFXSfile << Form("%s\n",id.Data());
510   }
511
512
513   // send (noisy) reference data for this run to FXS
514   printf("Preparing noisy reference data\n");
515   TString tarFiles = "";
516   for (UInt_t eq=0; eq<20; eq++) {
517     if (eqActiveNoisy[eq]) {
518       tarFiles.Append(Form("SPDphys_run_%d_eq_%d.root ",runNr,eq));
519     }
520   }
521   TString send_command = Form("cd %s; tar -cf ref_phys.tar %s",saveDirNoisyRef,tarFiles.Data());
522   system(send_command.Data());
523   TString fileName = Form("%s/ref_phys.tar",saveDirNoisyRef);
524   TString id = "SPD_ref_phys";
525 #ifndef SPD_DA_OFF
526   status = daqDA_FES_storeFile(fileName.Data(),id.Data());
527   if (status!=0) {
528     printf("Failed to export file %s , status %d\n",fileName.Data(),status);
529     return -1;
530   }
531 #endif
532   idsFXSfile << Form("%s\n",id.Data());
533
534
535   // send dead pixels to FXS
536   printf("Preparing dead files\n");
537   // send a tared file of all the dead files
538   send_command = Form("cd %s; tar -cf dead_phys.tar *",saveDirDeadToFXS);
539   //  printf("\n\n%s\n\n",command.Data());
540   system(send_command.Data());
541   fileName = Form("%s/dead_phys.tar",saveDirDeadToFXS);
542   id = "SPD_phys_dead";
543 #ifndef SPD_DA_OFF
544   Int_t send_status = daqDA_FES_storeFile(fileName.Data(),id.Data());
545   if (send_status!=0) {
546     printf("Failed to export file %s , status %d\n",fileName.Data(),send_status);
547     return -1;
548   }
549 #endif
550   idsFXSfile << Form("%s\n",id.Data());
551
552
553   // send noisy pixels to FXS
554   if (handler->GetNrNoisy()>0) { // there must be at least one file created
555     printf("Preparing noisy files\n");
556     // send a tared file of all the noisy files
557     TString command = Form("cd %s; tar -cf noisy_phys.tar *",saveDirNoisyToFXS);
558     //    printf("\n\n%s\n\n",command.Data());
559     system(command.Data());
560     TString fileName = Form("%s/noisy_phys.tar",saveDirNoisyToFXS);
561     TString id = "SPD_phys_noisy";
562 #ifndef SPD_DA_OFF
563     status = daqDA_FES_storeFile(fileName.Data(),id.Data());
564     if (status!=0) {
565       printf("Failed to export file %s , status %d\n",fileName.Data(),status);
566       return -1;
567     }
568 #endif
569     idsFXSfile << Form("%s\n",id.Data());
570   }
571
572
573   // send ids file to FXS
574   idsFXSfile.close();
575   id = "SPD_id_list";
576 #ifndef SPD_DA_OFF
577   status = daqDA_FES_storeFile(idsFXSFileName.Data(),id.Data());
578   if (status!=0) {
579     printf("Failed to export file %s , status %d\n",idsFXSFileName.Data(),status);
580     return -1;
581   }
582 #endif
583
584
585
586
587   delete handler;
588
589
590   return 0;
591 }