]> git.uio.no Git - u/mrichter/AliRoot.git/blame_incremental - STEER/AliRunDigitizer.cxx
Write file instead of closing it
[u/mrichter/AliRoot.git] / STEER / AliRunDigitizer.cxx
... / ...
CommitLineData
1/**************************************************************************
2* Copyright(c) 1998-1999, ALICE Experiment at CERN, All rights reserved. *
3* *
4* Author: The ALICE Off-line Project. *
5* Contributors are mentioned in the code where appropriate. *
6* *
7* Permission to use, copy, modify and distribute this software and its *
8* documentation strictly for non-commercial purposes is hereby granted *
9* without fee, provided that the above copyright notice appears in all *
10* copies and that both the copyright notice and this permission notice *
11* appear in the supporting documentation. The authors make no claims *
12* about the suitability of this software for any purpose. It is *
13* provided "as is" without express or implied warranty. *
14**************************************************************************/
15
16/*
17$Log$
18Revision 1.18 2002/07/17 08:59:39 jchudoba
19Do not delete subtasks when AliRunDigitizer is deleted. Owner should delete them itself.
20
21Revision 1.17 2002/07/16 13:47:53 jchudoba
22Add methods to get access to names of files used in merging.
23
24Revision 1.16 2002/06/07 09:18:47 jchudoba
25Changes to enable merging of ITS fast rec points. Although this class should be responsible for a creation of digits only, other solutions would be more complicated.
26
27Revision 1.15 2002/04/09 13:38:47 jchudoba
28Add const to the filename argument
29
30Revision 1.14 2002/04/04 09:28:04 jchudoba
31Change default names of TPC trees. Use update instead of recreate for the output file. Overwrite the AliRunDigitizer object in the output if it exists.
32
33Revision 1.13 2002/02/13 09:03:32 jchudoba
34Pass option to subtasks. Delete input TTrees. Use gAlice from memory if it is present (user must delete the default one created by aliroot if he/she wants to use gAlice from the input file!). Add new data member to store name of the special TPC TTrees.
35
36Revision 1.12 2001/12/10 16:40:52 jchudoba
37Import gAlice from the signal file before InitGlobal() to allow detectors to use it during initialization
38
39Revision 1.11 2001/12/03 07:10:13 jchudoba
40Default ctor cannot create new objects, create dummy default ctor which leaves object in not well defined state - to be used only by root for I/O
41
42Revision 1.10 2001/11/15 11:07:25 jchudoba
43Set to zero new pointers to TPC and TRD special trees in the default ctor. Add const to all Get functions. Remove unused constant, rename constant according coding rules.
44
45Revision 1.9 2001/11/15 09:00:11 jchudoba
46Add special treatment for TPC and TRD, they use different trees than other detectors
47
48Revision 1.8 2001/10/21 18:38:43 hristov
49Several pointers were set to zero in the default constructors to avoid memory management problems
50
51Revision 1.7 2001/10/04 15:56:07 jchudoba
52TTask inheritance
53
54Revision 1.4 2001/09/19 06:23:50 jchudoba
55Move some tasks to AliStream and AliMergeCombi classes
56
57Revision 1.3 2001/07/30 14:04:18 jchudoba
58correct bug in the initialization
59
60Revision 1.2 2001/07/28 10:44:32 hristov
61Loop variable declared once; typos corrected
62
63Revision 1.1 2001/07/27 12:59:00 jchudoba
64Manager class for merging/digitization
65
66*/
67
68////////////////////////////////////////////////////////////////////////
69//
70// AliRunDigitizer.cxx
71//
72// Manager object for merging/digitization
73//
74// Instance of this class manages the digitization and/or merging of
75// Sdigits into Digits.
76//
77// Only one instance of this class is created in the macro:
78// AliRunDigitizer * manager =
79// new AliRunDigitizer(nInputStreams,SPERB);
80// where nInputStreams is number of input streams and SPERB is
81// signals per background variable, which determines how combinations
82// of signal and background events are generated.
83// Then instances of specific detector digitizers are created:
84// AliMUONDigitizer *dMUON = new AliMUONDigitizer(manager)
85// and the I/O configured (you have to specify input files
86// and an output file). The manager connects appropriate trees from
87// the input files according a combination returned by AliMergeCombi
88// class. It creates TreeD in the output and runs once per
89// event Digitize method of all existing AliDetDigitizers
90// (without any option). AliDetDigitizers ask manager
91// for a TTree with input (manager->GetInputTreeS(Int_t i),
92// merge all inputs, digitize it, and save it in the TreeD
93// obtained by manager->GetTreeD(). Output events are stored with
94// numbers from 0, this default can be changed by
95// manager->SetFirstOutputEventNr(Int_t) method. The particle numbers
96// in the output are shifted by MASK, which is taken from manager.
97//
98// The default output is to the signal file (stream 0). This can be
99// changed with the SetOutputFile(TString fn) method.
100//
101// Single input file is permitted. Maximum kMaxStreamsToMerge can be merged.
102// Input from the memory (on-the-fly merging) is not yet
103// supported, as well as access to the input data by invoking methods
104// on the output data.
105//
106// Access to the some data is via gAlice for now (supposing the
107// same geometry in all input files), gAlice is taken from the first
108// input file on the first stream.
109//
110// Example with MUON digitizer, no merging, just digitization
111//
112// AliRunDigitizer * manager = new AliRunDigitizer(1,1);
113// manager->SetInputStream(0,"galice.root");
114// AliMUONDigitizer *dMUON = new AliMUONDigitizer(manager);
115// manager->Exec("");
116//
117// Example with MUON digitizer, merge all events from
118// galice.root (signal) file with events from bgr.root
119// (background) file. Number of merged events is
120// min(number of events in galice.root, number of events in bgr.root)
121//
122// AliRunDigitizer * manager = new AliRunDigitizer(2,1);
123// manager->SetInputStream(0,"galice.root");
124// manager->SetInputStream(1,"bgr.root");
125// AliMUONDigitizer *dMUON = new AliMUONDigitizer(manager);
126// manager->Exec("");
127//
128// Example with MUON digitizer, save digits in a new file digits.root,
129// process only 1 event
130//
131// AliRunDigitizer * manager = new AliRunDigitizer(2,1);
132// manager->SetInputStream(0,"galice.root");
133// manager->SetInputStream(1,"bgr.root");
134// manager->SetOutputFile("digits.root");
135// AliMUONDigitizer *dMUON = new AliMUONDigitizer(manager);
136// manager->SetNrOfEventsToWrite(1);
137// manager->Exec("");
138//
139////////////////////////////////////////////////////////////////////////
140
141// system includes
142
143#include <iostream.h>
144
145// ROOT includes
146
147#include "TFile.h"
148#include "TTree.h"
149#include "TList.h"
150
151// AliROOT includes
152
153#include "AliRunDigitizer.h"
154#include "AliDigitizer.h"
155#include "AliRun.h"
156#include "AliHeader.h"
157#include "TParticle.h"
158#include "AliStream.h"
159#include "AliMergeCombi.h"
160
161ClassImp(AliRunDigitizer)
162
163////////////////////////////////////////////////////////////////////////
164AliRunDigitizer::AliRunDigitizer()
165{
166// root requires default ctor, where no new objects can be created
167// do not use this ctor, it is supplied only for root needs
168
169// just set all pointers - data members to 0
170 fOutput = 0;
171 fTreeD = 0;
172 fTreeR = 0;
173 fTreeDTPC = 0;
174 fTreeDTRD = 0;
175 fInputStreams = 0;
176 for (Int_t i=0;i<kMaxStreamsToMerge;i++) {
177 fArrayTreeS[i]=fArrayTreeH[i]=fArrayTreeTPCS[i]=fArrayTreeTRDS[i]=NULL;
178 fInputFiles[i]=0;
179 }
180 fCombi = 0;
181
182}
183
184////////////////////////////////////////////////////////////////////////
185AliRunDigitizer::AliRunDigitizer(Int_t nInputStreams, Int_t sperb) : TTask("AliRunDigitizer","The manager for Merging")
186{
187// ctor which should be used to create a manager for merging/digitization
188 if (nInputStreams == 0) {
189 Error("AliRunDigitizer","Specify nr of input streams");
190 return;
191 }
192 Int_t i;
193 fNinputs = nInputStreams;
194 fOutputFileName = "";
195 fOutputDirName = ".";
196 fCombination.Set(kMaxStreamsToMerge);
197 for (i=0;i<kMaxStreamsToMerge;i++) {
198 fArrayTreeS[i]=fArrayTreeH[i]=fArrayTreeTPCS[i]=fArrayTreeTRDS[i]=NULL;
199 fCombination[i]=-1;
200 }
201 fkMASKSTEP = 10000000;
202 fkMASK[0] = 0;
203 for (i=1;i<kMaxStreamsToMerge;i++) {
204 fkMASK[i] = fkMASK[i-1] + fkMASKSTEP;
205 }
206 fInputStreams = new TClonesArray("AliStream",nInputStreams);
207 TClonesArray &lInputStreams = *fInputStreams;
208// the first Input is open RW to be output as well
209 new(lInputStreams[0]) AliStream("UPDATE");
210 for (i=1;i<nInputStreams;i++) {
211 new(lInputStreams[i]) AliStream("READ");
212 }
213 fOutput = 0;
214 fEvent = 0;
215 fNrOfEventsToWrite = -1;
216 fNrOfEventsWritten = 0;
217 fCopyTreesFromInput = -1;
218 fCombi = new AliMergeCombi(nInputStreams,sperb);
219 fDebug = 0;
220 fTreeD = 0;
221 fTreeR = 0;
222 fTreeDTPC = 0;
223 fTreeDTRD = 0;
224 fTreeDTPCBaseName = "TreeD_75x40_100x60_150x60_";
225 fTreeTPCSBaseName = "TreeS_75x40_100x60_150x60_";
226
227 for (i=0; i<kMaxStreamsToMerge; i++) fInputFiles[i]=0;
228}
229
230////////////////////////////////////////////////////////////////////////
231
232AliRunDigitizer::~AliRunDigitizer() {
233// dtor
234
235// do not delete subtasks, let the creator delete them
236 if (GetListOfTasks())
237 GetListOfTasks()->Clear("nodelete");
238
239 if (fInputStreams) {
240 delete fInputStreams;
241 fInputStreams = 0;
242 }
243 if (fCombi) {
244 delete fCombi;
245 fCombi = 0;
246 }
247
248}
249////////////////////////////////////////////////////////////////////////
250void AliRunDigitizer::AddDigitizer(AliDigitizer *digitizer)
251{
252// add digitizer to the list of active digitizers
253 this->Add(digitizer);
254}
255////////////////////////////////////////////////////////////////////////
256void AliRunDigitizer::SetInputStream(Int_t i, const char *inputFile)
257{
258 if (i > fInputStreams->GetLast()) {
259 Error("SetInputStream","Input stream number too high");
260 return;
261 }
262 static_cast<AliStream*>(fInputStreams->At(i))->AddFile(inputFile);
263}
264
265////////////////////////////////////////////////////////////////////////
266void AliRunDigitizer::Digitize(Option_t* option)
267{
268// get a new combination of inputs, connect input trees and loop
269// over all digitizers
270
271// take gAlice from the first input file. It is needed to access
272// geometry data
273// If gAlice is already in memory, use it
274 if (!gAlice) {
275 if (!static_cast<AliStream*>(fInputStreams->At(0))->ImportgAlice()) {
276 cerr<<"gAlice object not found in the first file of "
277 <<"the 1st stream"<<endl;
278 return;
279 }
280 }
281 if (!InitGlobal()) {
282 cerr<<"False from InitGlobal"<<endl;
283 return;
284 }
285 Int_t eventsCreated = 0;
286// loop until there is anything on the input in case fNrOfEventsToWrite < 0
287 while ((eventsCreated++ < fNrOfEventsToWrite) || (fNrOfEventsToWrite < 0)) {
288 if (!ConnectInputTrees()) break;
289 InitEvent();
290// loop over all registered digitizers and let them do the work
291 ExecuteTasks(option);
292 CleanTasks();
293 FinishEvent();
294 }
295 FinishGlobal();
296}
297
298////////////////////////////////////////////////////////////////////////
299Bool_t AliRunDigitizer::ConnectInputTrees()
300{
301// fill arrays fArrayTreeS, fArrayTreeH and fArrayTreeTPCS with
302// pointers to the correct events according fCombination values
303// null pointers can be in the output, AliDigitizer has to check it
304
305 TTree *tree;
306 char treeName[50];
307 Int_t serialNr;
308 Int_t eventNr[kMaxStreamsToMerge], delta[kMaxStreamsToMerge];
309 fCombi->Combination(eventNr, delta);
310 for (Int_t i=0;i<fNinputs;i++) {
311 if (delta[i] == 1) {
312 AliStream *iStream = static_cast<AliStream*>(fInputStreams->At(i));
313 if (!iStream->NextEventInStream(serialNr)) return kFALSE;
314 fInputFiles[i]=iStream->CurrentFile();
315 sprintf(treeName,"TreeS%d",serialNr);
316 tree = static_cast<TTree*>(iStream->CurrentFile()->Get(treeName));
317 if (fArrayTreeS[i]) {
318 delete fArrayTreeS[i];
319 fArrayTreeS[i] = 0;
320 }
321 fArrayTreeS[i] = tree;
322 sprintf(treeName,"TreeH%d",serialNr);
323 tree = static_cast<TTree*>(iStream->CurrentFile()->Get(treeName));
324 if (fArrayTreeH[i]) {
325 delete fArrayTreeH[i];
326 fArrayTreeH[i] = 0;
327 }
328 fArrayTreeH[i] = tree;
329 sprintf(treeName,"%s%d",fTreeTPCSBaseName,serialNr);
330 tree = static_cast<TTree*>(iStream->CurrentFile()->Get(treeName));
331 if (fArrayTreeTPCS[i]) {
332 delete fArrayTreeTPCS[i];
333 fArrayTreeTPCS[i] = 0;
334 }
335 fArrayTreeTPCS[i] = tree;
336 sprintf(treeName,"TreeS%d_TRD",serialNr);
337 tree = static_cast<TTree*>(iStream->CurrentFile()->Get(treeName));
338 if (fArrayTreeTRDS[i]) {
339 delete fArrayTreeTRDS[i];
340 fArrayTreeTRDS[i] = 0;
341 }
342 fArrayTreeTRDS[i] = tree;
343 } else if (delta[i] != 0) {
344 Error("ConnectInputTrees","Only delta 0 or 1 is implemented");
345 return kFALSE;
346 }
347 }
348 return kTRUE;
349}
350
351////////////////////////////////////////////////////////////////////////
352Bool_t AliRunDigitizer::InitGlobal()
353{
354// called once before Digitize() is called, initialize digitizers and output
355
356 TList* subTasks = this->GetListOfTasks();
357 if (subTasks) {
358 subTasks->ForEach(AliDigitizer,Init)();
359 }
360 return kTRUE;
361}
362
363////////////////////////////////////////////////////////////////////////
364
365void AliRunDigitizer::SetOutputFile(TString fn)
366// the output will be to separate file, not to the signal file
367{
368 fOutputFileName = fn;
369 (static_cast<AliStream*>(fInputStreams->At(0)))->ChangeMode("READ");
370 InitOutputGlobal();
371}
372
373////////////////////////////////////////////////////////////////////////
374Bool_t AliRunDigitizer::InitOutputGlobal()
375{
376// Creates the output file, called by InitEvent()
377
378 TString fn;
379 fn = fOutputDirName + '/' + fOutputFileName;
380 fOutput = new TFile(fn,"update");
381 if (GetDebug()>2) {
382 cerr<<"AliRunDigitizer::InitOutputGlobal(): file "<<fn.Data()<<" was opened"<<endl;
383 }
384 if (fOutput) return kTRUE;
385 Error("InitOutputGlobal","Could not create output file.");
386 return kFALSE;
387}
388
389
390////////////////////////////////////////////////////////////////////////
391void AliRunDigitizer::InitEvent()
392{
393// Creates TreeDxx in the output file, called from Digitize() once for
394// each event. xx = fEvent
395
396 if (GetDebug()>2)
397 cerr<<"AliRunDigitizer::InitEvent: fEvent = "<<fEvent<<endl;
398
399// if fOutputFileName was not given, write output to signal file
400 if (fOutputFileName == "") {
401 fOutput = (static_cast<AliStream*>(fInputStreams->At(0)))->CurrentFile();
402 }
403 fOutput->cd();
404 char treeName[30];
405 sprintf(treeName,"TreeD%d",fEvent);
406 fTreeD = static_cast<TTree*>(fOutput->Get(treeName));
407 if (!fTreeD) {
408 fTreeD = new TTree(treeName,"Digits");
409 fTreeD->Write(0,TObject::kOverwrite);
410 }
411
412// tree for ITS fast points
413 sprintf(treeName,"TreeR%d",fEvent);
414 fTreeR = static_cast<TTree*>(fOutput->Get(treeName));
415 if (!fTreeR) {
416 fTreeR = new TTree(treeName,"Reconstruction");
417 fTreeR->Write(0,TObject::kOverwrite);
418 }
419
420// special tree for TPC
421 sprintf(treeName,"%s%d",fTreeDTPCBaseName,fEvent);
422 fTreeDTPC = static_cast<TTree*>(fOutput->Get(treeName));
423 if (!fTreeDTPC) {
424 fTreeDTPC = new TTree(treeName,"TPC_Digits");
425 fTreeDTPC->Write(0,TObject::kOverwrite);
426 }
427
428// special tree for TRD
429 sprintf(treeName,"TreeD%d_TRD",fEvent);
430 fTreeDTRD = static_cast<TTree*>(fOutput->Get(treeName));
431 if (!fTreeDTRD) {
432 fTreeDTRD = new TTree(treeName,"TRD_Digits");
433 fTreeDTRD->Write(0,TObject::kOverwrite);
434 }
435
436}
437
438////////////////////////////////////////////////////////////////////////
439void AliRunDigitizer::FinishEvent()
440{
441// called at the end of loop over digitizers
442
443 Int_t i;
444 fOutput->cd();
445 if (fCopyTreesFromInput > -1) {
446 char treeName[20];
447 i = fCopyTreesFromInput;
448 sprintf(treeName,"TreeK%d",fCombination[i]);
449 fInputFiles[i]->Get(treeName)->Clone()->Write();
450 sprintf(treeName,"TreeH%d",fCombination[i]);
451 fInputFiles[i]->Get(treeName)->Clone()->Write();
452 }
453 fEvent++;
454 fNrOfEventsWritten++;
455 if (fTreeD) {
456 delete fTreeD;
457 fTreeD = 0;
458 }
459 if (fTreeR) {
460 delete fTreeR;
461 fTreeR = 0;
462 }
463 if (fTreeDTPC) {
464 delete fTreeDTPC;
465 fTreeDTPC = 0;
466 }
467 if (fTreeDTRD) {
468 delete fTreeDTRD;
469 fTreeDTRD = 0;
470 }
471}
472////////////////////////////////////////////////////////////////////////
473void AliRunDigitizer::FinishGlobal()
474{
475// called at the end of Exec
476// save unique objects to the output file
477
478 fOutput->cd();
479 this->Write(0,TObject::kOverwrite);
480 if (fCopyTreesFromInput > -1) {
481 fInputFiles[fCopyTreesFromInput]->Get("TE")->Clone()->Write();
482 gAlice->Write();
483 }
484 fOutput->Write();
485}
486
487
488////////////////////////////////////////////////////////////////////////
489Int_t AliRunDigitizer::GetNParticles(Int_t event) const
490{
491// return number of particles in all input files for a given
492// event (as numbered in the output file)
493// return -1 if some file cannot be accessed
494
495 Int_t sum = 0;
496 Int_t sumI;
497 for (Int_t i = 0; i < fNinputs; i++) {
498 sumI = GetNParticles(GetInputEventNumber(event,i), i);
499 if (sumI < 0) return -1;
500 sum += sumI;
501 }
502 return sum;
503}
504
505////////////////////////////////////////////////////////////////////////
506Int_t AliRunDigitizer::GetNParticles(Int_t event, Int_t input) const
507{
508// return number of particles in input file input for a given
509// event (as numbered in this input file)
510// return -1 if some error
511
512// Must be revised in the version with AliStream
513
514 return -1;
515
516/*
517 TFile *file = ConnectInputFile(input);
518 if (!file) {
519 Error("GetNParticles","Cannot open input file");
520 return -1;
521 }
522
523// find the header and get Nprimaries and Nsecondaries
524 TTree* tE = (TTree *)file->Get("TE") ;
525 if (!tE) {
526 Error("GetNParticles","input file does not contain TE");
527 return -1;
528 }
529 AliHeader* header;
530 header = 0;
531 tE->SetBranchAddress("Header", &header);
532 if (!tE->GetEntry(event)) {
533 Error("GetNParticles","event %d not found",event);
534 return -1;
535 }
536 if (GetDebug()>2) {
537 cerr<<"Nprimary: "<< header->GetNprimary()<<endl;
538 cerr<<"Nsecondary: "<<header->GetNsecondary()<<endl;
539 }
540 return header->GetNprimary() + header->GetNsecondary();
541*/
542}
543
544////////////////////////////////////////////////////////////////////////
545Int_t* AliRunDigitizer::GetInputEventNumbers(Int_t event) const
546{
547// return pointer to an int array with input event numbers which were
548// merged in the output event event
549
550// simplified for now, implement later
551 Int_t * a = new Int_t[kMaxStreamsToMerge];
552 for (Int_t i = 0; i < fNinputs; i++) {
553 a[i] = event;
554 }
555 return a;
556}
557////////////////////////////////////////////////////////////////////////
558Int_t AliRunDigitizer::GetInputEventNumber(Int_t event, Int_t input) const
559{
560// return an event number of an eventInput from input file input
561// which was merged to create output event event
562
563// simplified for now, implement later
564 return event;
565}
566////////////////////////////////////////////////////////////////////////
567TParticle* AliRunDigitizer::GetParticle(Int_t i, Int_t event) const
568{
569// return pointer to particle with index i (index with mask)
570
571// decode the MASK
572 Int_t input = i/fkMASKSTEP;
573 return GetParticle(i,input,GetInputEventNumber(event,input));
574}
575
576////////////////////////////////////////////////////////////////////////
577TParticle* AliRunDigitizer::GetParticle(Int_t i, Int_t input, Int_t event) const
578{
579// return pointer to particle with index i in the input file input
580// (index without mask)
581// event is the event number in the file input
582// return 0 i fit does not exist
583
584// Must be revised in the version with AliStream
585
586 return 0;
587/*
588 TFile *file = ConnectInputFile(input);
589 if (!file) {
590 Error("GetParticle","Cannot open input file");
591 return 0;
592 }
593
594// find the header and get Nprimaries and Nsecondaries
595 TTree* tE = (TTree *)file->Get("TE") ;
596 if (!tE) {
597 Error("GetParticle","input file does not contain TE");
598 return 0;
599 }
600 AliHeader* header;
601 header = 0;
602 tE->SetBranchAddress("Header", &header);
603 if (!tE->GetEntry(event)) {
604 Error("GetParticle","event %d not found",event);
605 return 0;
606 }
607
608// connect TreeK
609 char treeName[30];
610 sprintf(treeName,"TreeK%d",event);
611 TTree* tK = static_cast<TTree*>(file->Get(treeName));
612 if (!tK) {
613 Error("GetParticle","input file does not contain TreeK%d",event);
614 return 0;
615 }
616 TParticle *particleBuffer;
617 particleBuffer = 0;
618 tK->SetBranchAddress("Particles", &particleBuffer);
619
620
621// algorithmic way of getting entry index
622// (primary particles are filled after secondaries)
623 Int_t entry;
624 if (i<header->GetNprimary())
625 entry = i+header->GetNsecondary();
626 else
627 entry = i-header->GetNprimary();
628 Int_t bytesRead = tK->GetEntry(entry);
629// new ((*fParticles)[nentries]) TParticle(*fParticleBuffer);
630 if (bytesRead)
631 return particleBuffer;
632 return 0;
633*/
634}
635
636////////////////////////////////////////////////////////////////////////
637void AliRunDigitizer::ExecuteTask(Option_t* option)
638{
639// overwrite ExecuteTask to do Digitize only
640
641 if (!IsActive()) return;
642 Digitize(option);
643 fHasExecuted = kTRUE;
644 return;
645}
646////////////////////////////////////////////////////////////////////////
647TString AliRunDigitizer::GetInputFileName(const Int_t input, const Int_t order) const
648{
649// returns file name of the order-th file in the input stream input
650// returns empty string if such file does not exist
651// first input stream is 0
652// first file in the input stream is 0
653 TString fileName("");
654 if (input >= fNinputs) return fileName;
655 AliStream * stream = static_cast<AliStream*>(fInputStreams->At(input));
656 if (order > stream->GetNInputFiles()) return fileName;
657 fileName = stream->GetFileName(order);
658 return fileName;
659}
660////////////////////////////////////////////////////////////////////////