ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/UserCode/MitAna/TAM/src/TAModule.cxx
Revision: 1.7
Committed: Thu Jul 16 21:02:05 2009 UTC (15 years, 9 months ago) by loizides
Content type: text/plain
Branch: MAIN
CVS Tags: Mit_032, Mit_031, Mit_025c_branch2, Mit_025c_branch1, Mit_030, Mit_029c, Mit_029b, Mit_030_pre1, Mit_029a, Mit_029, Mit_029_pre1, Mit_028a, Mit_025c_branch0, Mit_028, Mit_027a, Mit_027, Mit_026, Mit_025e, Mit_025d, Mit_025c, Mit_025b, Mit_025a, Mit_025, Mit_025pre2, Mit_024b, Mit_025pre1, Mit_024a, Mit_024, Mit_023, Mit_022a, Mit_022, Mit_020d, TMit_020d, Mit_020c, Mit_021, Mit_021pre2, Mit_021pre1, Mit_020b, Mit_020a, Mit_020, Mit_020pre1, Mit_018, Mit_017, Mit_017pre3, Mit_017pre2, Mit_017pre1, Mit_016, Mit_015b, Mit_015a, Mit_015, Mit_014e, Mit_014d, Mit_014c, Mit_014b, Mit_014a, Mit_014, Mit_014pre3, Mit_014pre2, Mit_014pre1, Mit_013d, Mit_013c, Mit_013b, Mit_013a, Mit_013, Mit_013pre1, Mit_012i, Mit_012h, Mit_012g, Mit_012f, Mit_012e, Mit_012d, Mit_012c, Mit_012b, Mit_012a, Mit_012, Mit_011a, Mit_011, Mit_010a, Mit_010, HEAD
Branch point for: Mit_025c_branch
Changes since 1.6: +5 -5 lines
Log Message:
use same include paths as rest of code. Remember to always remove this when committing to the svn version.

File Contents

# User Rev Content
1 loizides 1.1 //
2 loizides 1.7 // $Id: TAModule.cxx,v 1.6 2009/07/16 15:06:56 loizides Exp $
3 loizides 1.1 //
4    
5 loizides 1.7 #include "MitAna/TAM/interface/TAModule.h"
6 loizides 1.1
7    
8     #ifndef ROOT_RVersion
9     #include <RVersion.h>
10     #endif
11     #ifndef ROOT_TError
12     #include "TError.h"
13     #endif
14     #ifndef ROOT_TIterator
15     #include "TIterator.h"
16     #endif
17 loizides 1.4 #ifndef ROOT_TROOT
18     #include "TROOT.h"
19     #endif
20     #ifndef ROOT_TRegexp
21     #include "TRegexp.h"
22     #endif
23 loizides 1.7 #ifndef ROOT_TAMOutput
24     #include "MitAna/TAM/interface/TAMOutput.h"
25     #endif
26 loizides 1.1
27    
28     //////////////////////////////////////////////////////////////////////////
29     // //
30     // //
31     // TAModule //
32     // //
33 loizides 1.6 // Abstract base class for processing trees. This class allows trees //
34 loizides 1.1 // to be processed in a modular fashion. All iteraction with the tree //
35     // itself is taken care of by TAMSelector. //
36     // //
37     // //
38     // Usage: //
39     // - Make a class that (publically) derives from TAModule //
40     // - The class should have member variables (pointers) to the //
41     // objects that will be read in and used from the tree. //
42     // For example, if a module will need the event info branch //
43     // from the tree, it could have a member variable such as: //
44     // TEventInfo* fEvtInfo; //
45     // Note: Such pointers should be initialized to null. //
46     // DO NOT assign them to 'new' objects as this will //
47     // result in a memory leak. The TAMSelector will //
48     // automatically make these pointers point to valid //
49     // objects after the corresponding tree branch has been //
50     // read in for the current entry. //
51     // - Override any of the following functions as needed: //
52     // - Begin() //
53     // - In Proof, this function is called by the client. //
54     // For most applications, this function will not be needed. //
55     // - SlaveBegin() //
56     // - In Proof, this function is called by each slave server. //
57     // Histograms should be created by a module here. //
58     // All branches that the module may need during Process //
59     // should be requested here using ReqBranch. //
60     // For the above example, one would call: //
61     // ReqBranch("eventInfo",fEvtInfo); //
62     // - Process() //
63     // - The TAMSelector (fSelector) will call this function //
64     // on the TAModule hierarchy for every entry in the tree. //
65     // Here one should only load branches as needed to first //
66     // perform the event selection. If and only if an event //
67     // passes the selection, one should then load the branches //
68     // needed for analysis. This is why requested branches are //
69     // NOT automatically loaded. //
70     // Continuing the example, to load the event info branch: //
71     // LoadBranch("eventInfo"); //
72     // cout << "Event Num=" << fEvtInfo->fEventNum << endl; //
73     // The module can then perform analysis and/or fill //
74     // histograms. //
75     // - SlaveTerminate() //
76     // - In Proof, this function is called by each slave server //
77     // after the Process loop. //
78     // - Terminate() //
79     // - In Proof, this function is called by the client at the //
80     // end of the analysis. //
81 loizides 1.5 // Here, a module can save results to a file or present //
82     // them graphically. //
83 loizides 1.1 // //
84     // A module may be active or inactive (controlled by TTask::SetActive). //
85     // When a module is not active, its sub modules are not executed. //
86     // //
87     // Author : Corey Reed 07/20/2004 //
88     // Author : Constantin Loizides 07/12/2006 //
89     // //
90     //////////////////////////////////////////////////////////////////////////
91    
92     ClassImp(TAModule)
93    
94    
95     #if ROOT_VERSION_CODE <=ROOT_VERSION(5,11,3)
96     #define R__ASSERT(e) \
97     if (!(e)) Fatal("", kAssertMsg, _QUOTE_(e), __LINE__, __FILE__)
98     #endif
99    
100     const Char_t TAModule::kExecBegin = 10;
101     const Char_t TAModule::kExecSlaveBegin = 20;
102     const Char_t TAModule::kExecProcess = 30;
103 loizides 1.2 const Char_t TAModule::kExecBeginRun = 40;
104     const Char_t TAModule::kExecEndRun = 50;
105     const Char_t TAModule::kExecSlaveTerminate = 60;
106     const Char_t TAModule::kExecTerminate = 70;
107 loizides 1.1
108    
109     //______________________________________________________________________________
110     TAModule::TAModule() :
111     fSelector(0),
112     fOutput(0),
113     fDefActv(IsActive()), // in principal, no need to initialze fDefActv
114     fVerbose(0),
115 loizides 1.5 fStopped(kFALSE),
116     fUseName(kFALSE)
117 loizides 1.1 {
118     // Default constructor.
119     }
120    
121    
122     //______________________________________________________________________________
123     TAModule::TAModule(const Char_t* name, const Char_t* title) :
124     TTask(name, title),
125     fSelector(0),
126     fOutput(0),
127     fDefActv(IsActive()), // in principal, no need to initialze fDefActv
128     fVerbose(0),
129 loizides 1.5 fStopped(kFALSE),
130     fUseName(kFALSE)
131 loizides 1.1 {
132     // Normal constructor:
133     // Use SetSelector on the top-most module to recursively set
134     // the selector for all mods.
135     }
136    
137    
138     //______________________________________________________________________________
139     TAModule::~TAModule()
140     {
141     // Destructor.
142     }
143    
144    
145     //______________________________________________________________________________
146     void TAModule::AbortAnalysis()
147     {
148     // Abort the analysis
149    
150     SetDefActive(kFALSE);
151     fSelector->AbortAnalysis();
152     }
153    
154    
155     //______________________________________________________________________________
156     void TAModule::AbortEvent()
157     {
158     // Abort this event
159    
160     fSelector->AbortEvent();
161     }
162    
163    
164     //______________________________________________________________________________
165     void TAModule::AbortModule()
166     {
167     // Abort the module and all sub-modules until the next call in TAMSelector.
168    
169     fSelector->AbortModule(this);
170     }
171    
172    
173     //______________________________________________________________________________
174     Bool_t TAModule::AddObjThisEvt(TObject* obj)
175     {
176     // Add this object to the list of objects stored for this event.
177     // See further description below.
178    
179     if(obj)
180     return AddObjThisEvt(obj,obj->GetName());
181     else {
182     Error("AddObjThisEvt",
183     "Can not add null object to event.");
184     return kFALSE;
185     }
186     }
187    
188    
189     //______________________________________________________________________________
190     Bool_t TAModule::AddObjThisEvt(TObject* obj, const char *name)
191     {
192     // Add this object to the list of objects stored for this event.
193     // NOTE:
194     // - The object must have a unique name.
195     // - The object must be on the heap.
196     // - The object will be owned by the selector and deleted at the
197     // end of the processing of the current event.
198     //
199     // Returns true iff the object meets the requirements and is added to
200     // the list successfully.
201    
202     if (fSelector!=0) {
203     return fSelector->AddObjThisEvt(obj,name);
204     } else {
205     Error("AddObjThisEvt",
206     "No selector exists, so there is no list of objects for "
207     "this event. Object named [%s] not added to this event.",
208     name);
209     }
210     return 0;
211     }
212    
213    
214     //______________________________________________________________________________
215     void TAModule::Browse(TBrowser* b)
216     {
217     // Browse this module's (and its sub modules) output if there is any
218     // otherwise just browse the tree of modules.
219    
220     if (fOutput!=0) fOutput->Browse(b);
221     else TTask::Browse(b);
222     }
223    
224    
225     //______________________________________________________________________________
226 loizides 1.5 Bool_t TAModule::CheckSelectors(const TAMSelector* sel,
227     const Bool_t warn/*=kTRUE*/) const
228 loizides 1.1 {
229     // Checks the tree of TAModules to be sure that each module has the
230     // specified selector.
231    
232     Bool_t check = (fSelector->IsEqual(sel));
233     if ( warn && (!check) ) {
234     Warning("CheckSelectors",
235     "Selector of module [%s] is different from specified selector.",
236     GetName());
237     }
238     TAModule* obj=0;
239     TIter nextobj(fTasks);
240     while ( (obj = dynamic_cast<TAModule*>(nextobj())) ) {
241     check &= obj->CheckSelectors(sel);
242     }
243     return check;
244     }
245    
246    
247     //______________________________________________________________________________
248     void TAModule::DeactivateAll()
249     {
250     // Recursively sets the active flag of this and all sub modules
251     // to false. Store their former activity flag in fDefActv so that
252     // they can be reset to that state by ResetAllActiveFlags().
253    
254     TTask::SetActive(kFALSE);
255    
256     TAModule* task=0;
257     TIter nextobj(fTasks);
258     while ( (task = dynamic_cast<TAModule*>(nextobj())) ) {
259     task->DeactivateAll();
260     }
261     }
262    
263    
264     //______________________________________________________________________________
265     void TAModule::Exec(Option_t* option)
266     {
267     // Executes the module corresponding to the given option.
268     // To be called only by TAMSelector.
269     // Checks the address of the private static variables
270     // to ensure that the TTask execute functions can not be
271     // called by derived classes.
272     // This function must not be overridden!
273    
274     R__ASSERT(option);
275    
276     if (option == &kExecBegin) {
277     Begin();
278     } else if (option == &kExecSlaveBegin) {
279     SlaveBegin();
280     } else if (option == &kExecProcess) {
281     Process();
282 loizides 1.2 } else if (option == &kExecBeginRun) {
283     BeginRun();
284     } else if (option == &kExecEndRun) {
285     EndRun();
286 loizides 1.1 } else if (option == &kExecSlaveTerminate) {
287     SlaveTerminate();
288     } else if (option == &kExecTerminate) {
289     Terminate();
290     } else {
291     SendError(kAbortAnalysis,
292     "Exec",
293     "Invalid option [%s] at %p. Function must only be called "
294     "by TAMSelector.",
295     option, static_cast<const void*>(option));
296     };
297     }
298    
299    
300     //______________________________________________________________________________
301     TFile* TAModule::GetCurrentFile() const
302     {
303     // Returns the current file that the tree is in.
304    
305     return (fSelector) ? (fSelector->GetCurrentFile()) : 0;
306     }
307    
308    
309     //______________________________________________________________________________
310     TObject* TAModule::FindObjThisEvt(const Char_t* name) const
311     {
312     // Looks for the object with the specified name that was added to
313     // this event. If not found, returns 0.
314    
315     if (fSelector!=0) {
316     return fSelector->FindObjThisEvt(name);
317     } else {
318     Error("FindObjThisEvt",
319     "No selector exists, so there is no list of objects for "
320     "this event. Could not find object named [%s].",
321     name);
322     }
323     return 0;
324     }
325    
326    
327     //______________________________________________________________________________
328 loizides 1.5 TObject* TAModule::FindPublicObj(const Char_t* name) const
329     {
330 loizides 1.1 // Looks for the public object with the specified name. If not found,
331 loizides 1.5 // returns 0. Note: TAModules are not public objects and will not be
332     // found by this function.
333 loizides 1.1
334     if (fSelector!=0) {
335     return fSelector->FindPublicObj(name);
336     } else {
337     Error("FindPublicObj",
338     "No selector exists, so there is no list of public objects. "
339     "Could not find object named [%s].",
340     name);
341     }
342     return 0;
343     }
344    
345    
346     //______________________________________________________________________________
347 loizides 1.4 void TAModule::ls(Option_t *option) const
348     {
349     // List the modules inside this module and its submodules if requested.
350     // Note: The following is take from TTask::ls(option) but fixed to avoid
351     // usage of null string in TRegexp.
352    
353     TROOT::IndentLevel();
354     cout <<GetName()<<"\t"<<GetTitle()<<endl;
355     TString opta = option;
356     TString opt = opta.Strip(TString::kBoth);
357     if (opt.IsNull()) return;
358    
359     TRegexp re(opt, kTRUE);
360     TROOT::IncreaseDirLevel();
361     TObject *obj;
362     TIter nextobj(fTasks);
363     while ((obj = static_cast<TObject*>(nextobj()))) {
364     TString s = obj->GetName();
365     if (s.Index(re) == kNPOS) continue;
366     obj->ls(option);
367     }
368     TROOT::DecreaseDirLevel();
369     }
370    
371    
372     //______________________________________________________________________________
373 loizides 1.1 void TAModule::LoadBranch(const Char_t* bname)
374     {
375     // Loads the current entry for the specified branch.
376     // This function should be called by specific TAModules (classes that
377     // inherit from this one) to get the current entry from the tree for
378     // the branch of the specified name.
379     // The branch must have already been requested (see
380     // ReqBranch).
381     // The actual entry is gotten from the tree by TAMSelector
382     // to ensure that the same branch is not loaded more than once
383     // for a given event.
384    
385     if (fSelector!=0) {
386     fSelector->LoadBranch(bname);
387     } else {
388     SendError(kAbortAnalysis,
389     "LoadBranch",
390     "fSelector is null in module [%s]",
391     GetName());
392     }
393     }
394    
395    
396     //______________________________________________________________________________
397     void TAModule::NewOutputList(TList* list)
398     {
399     // Make a hierarchy of TAMOutput objects corresponding to this
400     // module and all its sub modules. This must only be called once! Analysis
401     // will abort if this module already has an associated TAMOutput object.
402     // The input list is assumed to be the fOutput member of the TAMSelector
403     // and must never be null.
404    
405     R__ASSERT(list);
406     if (fOutput!=0) {
407     if (GetVerbosity()>0) {
408     SendError(kWarning,
409     "NewOutputList",
410     "New output list requested in module [%s] but one has "
411     "already been made. This should only happen if the module "
412     "has been added to more than one TAMSelector. "
413     "Set verbosity to 0 to remove this message.", GetName());
414     }
415    
416     if (fOutput->GetMod()!=this) {
417     if ((GetVerbosity()>0) && (fOutput->GetMod()!=0)) {
418     Warning("NewOutputList",
419     "Output of [%s] was associated with module at %p. "
420     "Resetting to this (%p).",
421     GetName(), (void*)fOutput->GetMod(), (void*)this);
422     }
423     fOutput->SetMod(this);
424     }
425    
426     if (list->FindObject(fOutput)==0) {
427     list->Add(fOutput);
428     }
429     } else {
430     fOutput = new TAMOutput(this);
431     fOutput->SetOwner();
432     list->Add(fOutput);
433     }
434    
435     TAModule* task=0;
436     TIter nextobj(fTasks);
437     while ( (task = dynamic_cast<TAModule*>(nextobj())) ) {
438     task->NewOutputList(fOutput);
439     }
440     }
441    
442    
443     //______________________________________________________________________________
444 loizides 1.4 Bool_t TAModule::NotifyAll()
445     {
446     // Recursively call Notify.
447 loizides 1.1
448     Bool_t ret = Notify();
449     TAModule* task=0;
450     TIter nextobj(fTasks);
451     while ( (task = dynamic_cast<TAModule*>(nextobj())) ) {
452     ret &= task->NotifyAll();
453     }
454     return ret;
455     }
456    
457    
458     //______________________________________________________________________________
459     void TAModule::Print(Option_t *option/*=""*/) const
460     {
461     // Print the modules inside this module and its submodules.
462 loizides 1.4
463     ls(option);
464 loizides 1.1 }
465    
466    
467     //______________________________________________________________________________
468     Bool_t TAModule::PublishObj(TObject* obj)
469     {
470     // Adds an object to a list of objects which is outside the module
471     // hierarchy. This can be used to pass objects (for example, calibrations)
472     // between modules. Objects in this list are available before Begin
473     // until the end of SlaveTerminate. They are not guaranteed to be available
474     // during or after Terminate.
475     // Checks (by name) if the object is already in the list. If it is, returns
476     // kFALSE and does not publish the object.
477     // NOTE: These objects are NOT owned by the list! Whatever creates these
478     // objects must take care to (1) remove the object from the list using
479     // RetractObj() and (2) delete the object.
480     // Also NOTE: will not publish TAModule objects.
481    
482     if (fSelector!=0) {
483     return fSelector->PublishObj(obj);
484     } else {
485     Error("PublishObj",
486     "No selector exists, so there is no list of public objects. "
487     "Object named [%s] not published.",
488     (obj!=0) ? obj->GetName() : "NULL");
489     }
490     return kFALSE;
491     }
492    
493    
494     //______________________________________________________________________________
495     TObject* TAModule::RemoveObjThisEvt(const Char_t* name)
496     {
497     // Finds the object with the specified name and removes it from
498     // the list of objects added to this event.
499     // Returns the object that was removed.
500    
501     if (fSelector!=0) {
502     return fSelector->RemoveObjThisEvt(name);
503     } else {
504     Error("RemoveObjThisEvt",
505     "No selector exists, so there is no list of objects for "
506     "this event. Object named [%s] not removed from this event.",
507     name);
508     }
509     return 0;
510     }
511    
512    
513     //______________________________________________________________________________
514     void TAModule::RemoveOutput(TObject* obj)
515     {
516     // Remove the object from the list of output objects of this module.
517    
518     R__ASSERT(fOutput);
519     fOutput->RemoveOutput(obj);
520     }
521    
522    
523     //______________________________________________________________________________
524     void TAModule::ResetAllActiveFlags()
525     {
526     // Recursively reset the activity flag of this and all sub modules
527     // to their fDefActv values.
528    
529     if(fStopped) // this module is permanently aborted
530     return;
531    
532     SetActive(fDefActv);
533    
534     TAModule* task=0;
535     TIter nextobj(fTasks);
536     while ( (task = dynamic_cast<TAModule*>(nextobj())) ) {
537     task->ResetAllActiveFlags();
538     }
539     }
540    
541    
542     //______________________________________________________________________________
543     TObject* TAModule::RetractObj(const Char_t* name) {
544     // Finds the public object with the specified name and removes it from
545     // the list of public objects. Returns the object that was retracted.
546     // Note: TAModules are not public objects and will not be removed by
547     // this function.
548    
549     if (fSelector!=0) {
550     return fSelector->RetractObj(name);
551     } else {
552     Error("RetractObj",
553     "No selector exists, so there is no list of public objects. "
554     "Object named [%s] not retracted.",
555     name);
556     }
557     return 0;
558     }
559    
560    
561     //______________________________________________________________________________
562     void TAModule::SendError(const EModResult errLevel,
563     const Char_t* location,
564     const Char_t* formattedMsg, ...)
565     {
566     // Sends an error using the TError facility.
567     // If errLevel is kAbortAnalysis or greater, the error is sent
568     // as type 'kBreak'.
569     // If errLevel is kWarning, the error is sent as type 'kWarning'.
570     // Otherwise it is sent as type 'kError'.
571    
572     va_list ap;
573     va_start(ap,va_(formattedMsg));
574     if (errLevel>=kAbortAnalysis) {
575 loizides 1.3 DoError(::kBreak, location, va_(formattedMsg), ap);
576 loizides 1.1 AbortAnalysis();
577     } else if (errLevel>=kStopModule) {
578 loizides 1.3 DoError(::kError, location, va_(formattedMsg), ap);
579 loizides 1.1 StopModule();
580     } else if (errLevel>=kAbortEvent) {
581 loizides 1.3 DoError(::kError, location, va_(formattedMsg), ap);
582 loizides 1.1 AbortEvent();
583     } else if (errLevel>=kAbortModule) {
584 loizides 1.3 DoError(::kError, location, va_(formattedMsg), ap);
585 loizides 1.1 AbortModule();
586     } else if (errLevel==kWarning) {
587 loizides 1.3 DoError(::kWarning, location, va_(formattedMsg), ap);
588 loizides 1.1 } else {
589     Error("SendError",
590     "Unhandled error level [%d] specified when trying to send the "
591     "following error:",static_cast<Int_t>(errLevel));
592 loizides 1.3 DoError(::kError, location, va_(formattedMsg), ap);
593 loizides 1.1 }
594     va_end(ap);
595     }
596    
597    
598     //______________________________________________________________________________
599     void TAModule::SetAllModOutput(TAMOutput* o)
600     {
601     // Recursively set the output objects for this module and its submodules
602     // see SetModOutput().
603    
604     SetModOutput(o);
605    
606     if ( fTasks->IndexOf(fTasks->Last()) == o->IndexOf(o->Last()) ) {
607     TAModule* task=0;
608     TAMOutput* taskOutput=0;
609     TIter nextobj(fTasks);
610     TIter nextout(o);
611     while ( (task = dynamic_cast<TAModule*>(nextobj())) &&
612     (taskOutput = dynamic_cast<TAMOutput*>(nextout())) ) {
613     task->SetAllModOutput(taskOutput);
614     }
615     } else {
616     SendError(kAbortAnalysis,
617     "SetAllModOutput",
618     "Can not set output for submodules of [%s]. Module contains %d "
619     "submodules, different from the Output which contains %d "
620     "suboutputs.",
621     GetName(),
622     fTasks->IndexOf(fTasks->Last())+1,
623     o->IndexOf(o->Last())+1);
624     }
625     }
626    
627    
628     //______________________________________________________________________________
629     void TAModule::SetModOutput(TAMOutput* o)
630     {
631     // Check that the output object has the same name as this module
632     // If so, set 'o' to be this module's output and set o's module to this.
633    
634     if (o!=0) {
635     TString on(o->GetName());
636     if (on.CompareTo(GetName())==0) {
637     fOutput = o;
638     o->SetMod(this);
639     } else {
640     SendError(kAbortAnalysis,
641     "SetModOutput",
642     "Output named [%s] different from module named [%s]."
643     "Can not set mod output to module.",
644     on.Data(), GetName());
645     }
646     } else {
647     SendError(kAbortAnalysis,
648     "SetModOutput",
649     "Can not set mod output to null object.");
650     }
651     }
652    
653    
654     //______________________________________________________________________________
655     void TAModule::SetSelector(TAMSelector* sel)
656     {
657     // Recursively set the selector for this module and all contained
658     // modules to 'sel'.
659    
660     fSelector = sel;
661    
662     TAModule* obj=0;
663     TIter nextobj(fTasks);
664     while ( (obj = dynamic_cast<TAModule*>(nextobj())) ) {
665     obj->SetSelector(sel);
666     }
667     }
668    
669    
670     //______________________________________________________________________________
671     void TAModule::SkipEvent()
672     {
673     // Aborts the processing of the current event by this task (and prevents
674     // its subtasks from processing the event) without sending an error
675     // message.
676     //
677     // It is designed for use by event selection type modules.
678     //
679     // If an error message is desired, use SendError with an error level of
680     // kSkipModule to both print the message and skip the event. There is no
681     // need to call both SendError(kSkipModule,...) and SkipEvent().
682    
683     AbortModule();
684     }
685    
686    
687     //______________________________________________________________________________
688     void TAModule::StopModule()
689     {
690     // Stop the module for the rest of the analysis.
691    
692     fStopped = kTRUE;
693     fSelector->AbortModule(this);
694     }
695    
696     //______________________________________________________________________________
697     const char *TAModule::Version()
698     {
699     // Return a TAM version string.
700    
701     #ifndef TAM_RELEASE
702     #define TAM_RELEASE "Unknown"
703     #endif
704    
705     return TAM_RELEASE;
706     }