Interactive ROOT Tutorial - June 19, 2000

This tutorial demonstrates how to work interactively with ROOT:

  1. Fundamentals (environment, GUIs, definitions)

  2. Doing simple interactive analysis with trees and histograms

  3. Build your own trees (two examples - a trivial 'ntuple' and a more advanced tree)

  4. Put this tree into a Beta Module

Note: You may want to skip parts of this tutorial if you already have some experience with ROOT.

To execute this tutorial successfully, you will need:

In the following,

red text is used to indicate what you have to type in at the UNIX prompt.
blue text is used to show what you have to type at the ROOT prompt
red/brownish is used to indicate exercises.


A comprehensive list of links to documentation on ROOT can be found at BaBar's webpage on ROOT Documentation


Part 1. Fundamentals

Here, we will


Preliminaries


    cd ~
    mkdir rootutorial
    cp $BFROOT/www/doc/tutorials/19Jun2000_Root_Tutorial/sample.rootrc ~/rootutorial/.rootrc
    cp $BFROOT/www/doc/tutorials/19Jun2000_Root_Tutorial/rootlogon.C ~/rootutorial/.
    cd rootutorial

Exercise: Start up ROOT with the wrapper script

cernroot

After you get ROOT's prompt, start the demos toolbar with

.x demos.C

Quit ROOT

.q



    bbrroot





.rootrc (used to set up different paths ) and

rootlogon.C (containing e.g.style definitions, etc.). Note that if you start root in a vanilla BaBar workdir, the logon file is called RooLogon.C and this overrides your ~/rootlogon.C.

In a BaBar workdir, ROOT will also read RooAlias.C with some function definitions



/usr/local/bin/h2root framework.hbook



A quick tour of ROOT

Canvas: A canvas is a graphics window where e.g. your histograms wil be displayed (like the HIGZ window in PAW), You can enlarge the canvas like any window by click-and-holding e.g. a corner of the window containing the canvas.

Exercise: Start up ROOT

cernroot

Start the demos once again

.x demos.C

Execute an example

Left-Click on hsimple

You'll get a canvas. The canvas has a menu bar at the top

Left-Click on Options and select Event Status

Left-Click into the histogram frame

Watch how the cross-hair cursor changes to an arrow.

Move the pointer carefully to top of a histogram bin.

Carefully watch the display at the very bottom of the canvas.






Editor: Once you have a canvas, you can start up the editor for putting graphical components on the canvas:

Exercise: Start the ROOT graphics editor


Start the editor

Left-Click on Edit and select Editor

Draw an arrow on the canvas

Left-Click on Arrow, move the pointer into the canvas,
Left-Click-and-Hold and move the mouse
Release the mouse button



Browser: ROOT comes along with a browser that can display objects known to a ROOT session. It can be a handy tool to explore ROOT.

Exercise: Start the ROOT browser and display a histogram


Start the browser

Left-Click on Inspect and select Start Browser

Open the folder ROOT files

Double Left-Click on ROOT files in the main browser window

Open the hsimple.root file

Double Left-Click on hsimple.root

You'll see a few pictograms. Display one of the histograms.

DoubleLeft-Click on hpxpy



Pad: A Pad is a subdivision of a canvas. (Similar to a zone in PAW.) Click on surfaces in the demos toolbar.. A canvas with two subdivisions will appear - each one is a pad. You can resize the pad by left-clicking on the corners of the pads.

Context menu: Click on hsum in the demos toolbar. Move the cursor over the box with the statistics and click with the right mouse button. A menubar (labelled 'TPaveStats::stats') will appear. It gives you access to member functions of the class TPaveStats. For example, select FillAttributes. A window will pop up. Click on a color and select Apply. All objects displayed have context menus associated with them.

Exercise: Find the context menu of the x-axis (Hint: the cross-hair cursor will change to a hand symbol when the cursor is at the right place). Add a title.

Exercise: Find the context menu of the histogram shown with the small squares. (Hint: the cross-hair cursor will change to an arrow when you hit the right place Try to aim for one of the squares. Don't despair: It's not trivial.) Change the marker symbol and its size.

Tree: A tree is (roughly) the equivalent of an ntuple though far more powerful: You can store objects (e.g. vectors ) and not only numbers.

Exercise: Open the browser if you closed it. Find the 'ntuple' (Hint: it's contained in the hsimple.root file.). Double-click on its icon. You'll see all variables (leafs) displayed. Double-click on one of them to display it.

TTreeViewer is a graphical interface to the use of trees. It is described in the Getting Started with ROOT document at FNAL.

Branches and Leafs: If you think of a tree like a directory structure, a branch is like a 'subdirectory' of the tree. The actual variable is a leaf (like a file in a subdirectory). You can have multiple leafs per branch or you can have one leaf per branch (this is the case in the 'ntuple' ntuple encountered above.)

Naming conventions: ROOT adheres to some naming conventions in its source code. Names of classes start with 'T', simple types are capitalized and have '_t' appended (e.g. Float_t). Data members start with ' f '.

Macros: The equivalent of a KUMAC. Written in (not quite standard) C++. By clicking on any of the buttons in the demos toolbar, you will execute a macro. $ROOTSYS/tutorials contains a lot of very instructive macros. There are named and unnamed macros: See below for more information.



CINT - the C++ interpreter

CINT is quite sophisticated, but not perfect. You can crash it badly. You will restart ROOT more often than PAW. You need some experience to know when you can continue with CINT after having had an error. Some people (sometimes) would not bet on results obtained with CINT. Keep that in mind when your macro gets complicated. Nothing beats the security and reassurance of a trustworthy C++ compiler.

That said, CINT is just wonderful for all the interactive display stuff usually needed for getting plots into a form suited for presentation.

CINT commands always start with a dot `.'. The most important ones include

Command

Action

.q

Quit ROOT

.x bla.C

Load and evaluate statements in the file bla.C

.L bla.C

Load macro bla.C

.! shellcommand

Execute shellcommand in shell

.?

Get list of CINT commands



CINT's extensions to C++ are the following:

In interactive usage, the semicolon at the end of a statement is not required (but it is required in macros!)

-> can be abbreviated with . (example: hist->Draw() is equivalent to hist.Draw())

The following two lines are equivalent by definition and in order to save typing

b = new TClass(...);
TClass *b = new TClass(...);

If an object is not declared upon first usage, CINT searches ROOT's global scope for an object with an identical name and sets up a pointer variable with the same name and initializes the variable to point to the object found.

As mentioned in the quick tour, macros come in two species:


named macro: helloWorld.C

Unamed macro: helloWorld.cxx

Comments

Source code

void helloWorld(int t = 3) {
cout << "hello World " << t << endl;
TFile f("hsimple.root");
f.ls();
}

{
gROOT->Reset();
cout << "hello World" << endl;
TFile f("hsimple.root");
f.ls();
}

-> Note braces at beginning
-> Reset global scope to status just before executing the previous macro.
-> Load a rootfile

execution in CINT

.x helloWorld.C(123)

.x helloWorld.cxx

Don't miss that initial point ' .' when copying

Loading and then executing entry point

.L helloWorld.C
helloWorld()



Scope

scoped according to the usual C++ rules, no f visible after macro has terminated

all statements are executed in CINT's global scope, i.e. f is visible in the ROOT session after the macro has terminated




Part 2. Working with ROOT

In this part you will learn how to

Here the emphasis will shift to the command line (or equivalently to macros), and the interactive GUI approach will usually be left as an exercise.

Start by copying an example rootfile from the tutorial area:

cp $BFROOT/www/doc/tutorials/19Jun2000_Root_Tutorial/Exercise3.root ~/rootutorial

Start up root:

cernroot

Open the file you just copied:

TFil<TAB>
TFile f("<TAB>
TFile f("Exercise3.root");

ROOT has a built-in completion. Type part of the name and ROOT will try to complete as much as unambiguously possible.

Look what's in the file:

f.ls()

ls() is one of the exceptions to capitalization

          TFile**         Exercise3.root                    
           TFile*         Exercise3.root                    
            KEY: TH1F     h1d1;1  MicroFilter;# +trk        
            KEY: TH1F     h1d2;1  MicroFilter;# -trk
            KEY: TH1F     h1d3;1  MicroFilter;# neutrals
            KEY: TH2F     h2d4;1  MicroFilter;;# +trk;# -trk/
            KEY: TH1F     h1d5;1  MicroFilter;# electron
            KEY: TH1F     h1d6;1  MicroFilter;# muon
            KEY: TH1F     h1d7;1  MicroFilter;# pion
            KEY: TH1F     h1d8;1  MicroFilter;# Ks
            KEY: TH1F     h1d9;1  MicroFilter;# JPsi
            KEY: TH1F     h1d10;1 MicroFilter;# B0
            KEY: TTree    ntp1;1  Jpsi Ks micro Analysis    
            KEY: TH1D     h1;1



Working with histograms and making plots

Draw a histogram:

h1d1->Draw();
h1d1->Print("all");

or: h1.Draw()
print the contents of the histogram

Save the output:

c1->SaveAs("bla.eps");
c1->SaveAs("bla.ps");
c1->SaveAs("bla.gif");

this assumes that your histogram is displayed in a canvas called "c1"
(which should be the case if everything went fine)

Get more pads:

TPad *npad = new TPad("npad", "", 0.6, 0.2, 0.9, 0.5);
npad->Draw();
npad->cd();
h1d1->Draw();

c1->Clear();
c1->Divide(2,2);
c1->cd(1);
h1d1->Draw();

c1->Clear()

Opening a new pad allows the drawing of insets





the equivalent of zone 2 2


Go back to one pad on the canvas

Operations with histograms

TH1F *htmp = new TH1F(*h1d1);
htmp->Add(h1d1, h1d2);
htmp->Draw();
htmp->Divide(h1d1, h1d2);
htmp->Draw("e");

Make a copy of a histogram
Replace its contents with the sum of two histograms
and display the result
Bin by bin division of histograms
NOTE: Read about TH1::Sumw2() if you need 'real' errors

Zoom and unzoom:

htmp->SetAxisRange(4., 15., "x");
gPad->Modified();

Zoom - GUI: explained in part 1
needs a refresh from the command line

htmp->SetAxisRange(0., -1., "x");

Zoom -GUI: Move to Axis context menu, select UnZoom

Logarithmic axes:

gPad->SetLogy(1);
gPad->Modified();

Not a histogram method!
GUI:Get the canvas context menu outside of frame and select SetLogy

Gridlines

gPad->SetGridx(1);
gPad->SetGridy(1);
gPad->Modified();


GUI:Get the canvas context menu outside of frame and select SetGridx

Change the drawing style (e.g. markers, color)

h1d1->Draw("p");
h1d1->SetMarkerStyle(20);
h1d1->SetMarkerSize(1.);
h1d1->SetMarkerColor(kBlue);
gPad->Modified();

You'll have to look very closely to see anything
GUI: Get histogram context menu and select SetMarkerAttributes

Or '4', the numbering is the same as in PAW
You ned the update from the command line

Enlarge axis labels, add axis titles:

h1d1->SetTitleSize(0.06, "x");
h1d1->SetTitleOffset(1., "x");
h1d1->SetXTitle("Axis title text");
gPad->SetBottomMargin(0.15);
gPad->SetLeftMargin(0.15);
h1d1->Draw();

Title size, offset and text


NOTE: (e)ps output can be missing if you cut off too much,so be sure that you have enough margins

Overlay two histograms:

h1d2->SetFillColor(kYellow);
h1d2->SetFillStyle(1001);
h1d2->Draw("hist");
h1d1->Draw("psame"); 

"Same" puts onto same pad
"p" plots with markers (points) instead of histogram line.
Several options are concatenated into one string

Make a Legend:

TLegend *pl = new TLegend(0.4,0.5,0.7,0.7);
pl->SetTextSize(0.04); 
pl->SetFillColor(0);
TLegendEntry *ple = pl->AddEntry(h1d2, "Positive tracks",  "l");
TLegendEntry *ple = pl->AddEntry(h1d1, "Negative tracks",  "p");
ple->SetMarkerSize(1.);
pl->Draw();

See documentation for more details



Add a text box:

TPaveText *pt = new TPaveText(0.2, 0.7, 0.4, 0.85, "NDC");
pt->SetTextSize(0.04);
pt->SetFillColor(0);
pt->SetTextAlign(12);
pte = pt->AddText("bla");
pt->Draw();


Switching off/on the statistics box:

gStyle->SetOptStat(0);
h1->Draw();
gStyle->SetOptStat(1111111);
h1->Draw();
gStyle->SetOptStat(1);
h1->Draw();

Check out the documentation what the different digits mean



Exercise: Write a macro that produces output similar to what is shown on the right:

open Exercise3.root
adds a few histogram
overlay them
make a new canvas for the inset
plot another histogram
Make a legend
label all axes

Exit and restart ROOT, and execute you rmacro with

.x inset.C

(assuming you named it inset.C)





Functions and Fitting

There are a few built-in functions, but in addition you can define your own functions. There are one-, two-, and three-dimensional functions (TF1, TF2, and TF3)

Instantiate and draw a function defined in terms of internal functions:

TF1 *f0 = new TF1("f0", "sin(x)/x", 0., 10.);
f0->Draw();

TF1 *f1 = new TF1("f1", "0.5*f0", 0., 10.);
f1->SetLineColor(kBlue);
f1->Draw("same");

TF1 *f2 = new TF1("f2","[0]*x*sin([1]*x)",-3.,3.);
f2->SetParameters(10., 5.);
f2->Draw();

sin() is built-in


No user-defined functions for "f0"



Add parameters

Define your own functions












TF1 *u0 =  new TF1("u0", MyFunction, 0., 10., 3);
u0->SetParameters(100., 5., 1.2);
u0->Draw();

Put the following into a file called MyFunction.C

Double_t MyFunction(Double_t *x, Double_t *par) {
Double_t arg = 0;
if (par[2]) arg = (x[0] - par[1])/par[2];
return par[0]*TMath::Exp(-0.5*arg*arg);
}

and load it with .L MyFunction.C



Fitting histograms with predefined functions, user-defined functions, and compositions of pre-defined functions. :

gStyle->SetOptFit(1111);
h1->Fit("gaus"); 
h1->Fit("gaus", "R", "", 0.9, 1.1);
h1->Fit("gaus", "R", "p", 0.9, 1.1);

Gaus is predefined and needs no further initialization. Further predefined functions include pol0 .... pol9, landau, expo
Second argument: Options for minuit
Third argument: Drawing options
Note: Range for function definition used in fit with "R"

TF1 *myG = new TF1("myG", MyFunction, 0.6, 1.4, 3);
myG->SetParameters(h1->GetMaximum(), h1->GetMean(), h1->GetRMS());
h1->Fit("myG");

User-defined function

TF1 *ff = new TF1("ff", "gaus + pol2(3)", 0.6, 1.4);
ff->SetParameters(h1->GetMaximum(), 1., 0.05, 1000., 1., -2.);
h1->Fit("ff");

pol2(3) means that the parameters for the polynomial of second order start at position 3 in the parameter array.

Accessing the information from Minuit:

double parValue, parError;
gMinuit->GetParameter(2, parValue, parError);
cout << parValue << " " <<parError << endl;




Try to do it better by taking two gaussian. Put everything into a macro to produce output in a decent format:

Exercise: Write a macro that

opens Exercise3.root
fits h1 with the sum of two Gaussians
displays the result

Quit and restart ROOT adn execute this macro with

.x plot.C

(assuming you named it plot.C)






Working with Trees

Exit from ROOT and start fresh. Load Exercise3.root.

Look what's in the tree:

ntp1->Print();

Print the branches of the tree, how many entries per branch, etc

ntp1->Scan();

Dump the variable values (for the first few variables)

ntp1->Scan("massKs:pxKs");

Select the variables to be printed

ntp1->Scan("massKs:massJpsi", "nKs ==1");

Dump the variable values and apply a cut


Draw variables of a tree:

ntp1->Draw("massKs");

Draw a variable

TH1D *hh = new TH1D("hh", "temporary histogram", 100, 0., 1.);
ntp1->Draw("massKs>>hh");
ntp1->Draw("massKs>>+hh");

Define a histogram to be filled from tree and fill it
h1 will be reset before filling
except if you use ">>+"

ntp1->Draw("pxKs:pyKs");

2d plot

ntp1->Draw("sqrt(pxKs+pyKs+pzKs):sqrt(pxKs*pxKs+pyKs*pyKs)", "", "colz");

2d plot, no cut applied, but drawing option specified


Make a function skeleton to analyze a tree:

ntp1->MakeCode();

The equivalent of uwfunc. Produces a macro ntp1.C



Exercise: After generating the function skeleton, start up your favorite editor and open ntp1.C. At the end, you'll find the commented loop over 'nentries' entries in the tree.

Jump to the end of the file,


you'll find a loop over ' i ' that is commented out:

you are looking for // for (Int_t i=0; i<nentries;i++) {

Instantiate a TH1F just before that loop:

TH1F *hmks = new TH1F("hmks", "mass", 100, 0., 1.);



Un-comment the loop:

for (Int_t i=0; i<nentries;i++) {
nbytes += ntp1->GetEntry(i);
}

In the loop, add another loop over the KS candidates
and fill the histogram in there:

for (Int_t iCand = 0; iCand < nKs; ++iCand) {
hmks->Fill(massKs[iCand]);
}

Your macro should look now like this


Exit from ROOT, restart it and execute the macro:

.x ntp1.C



Display the histogram:

hmks->Draw()

LIving in the times of OO, the equivalent of a COMIS function is not attractive to some. ROOT offers an alternative:

ntp1->MakeClass();

The OO approach to the same problem.
WARNING: it produces ntp1.hh and ntp1.C (so your previous version would be overwritten)!

Save Histograms to another rootfile: In ROOT, you are always in a directory. Before opening a file, this is Rint:. You can cd() to different directories. Saving histograms (objects in general) put them into your current directory.

Quit ROOT and start over again


Print your current directory

gDirectory->pwd()

Open a file (in read-only mode, the default)

TFile f("Exercise3.root")

Print your current directory

gDirectory->pwd()

Open an output file

TFile g("new.root", "RECREATE")

Print your current directory

gDirectory->pwd()

Find the histograms in the old file and write them to the new

(TH1*)f->Get("h1d1")->Write();

Close the new file

g.Close()





Open it with another TFile

TFile h("new.root");

Check that the histogram actually is there

h1d1->Draw()

And don't close a file if you are still using objects of it

h.Close()

(because else you loose them ... your canvas should be empty now)


This also happens when writing macros, so be warned: don't close your files too early!

Final Tip:

c1->SaveAs("display.C");

Writes a macro that should reproduce your canvas. Very useful when working with the editor on a template and then wanting to convert that to a macro.



Part 3. Build your own trees

Here you can find two examples of how to build trees - a trivial one and a more complicated one. Additional information on this topic can be found in the ROOT 102 tutorial Fermilab.

Trivial case

This comes in two incarnations, an interactive version and a compiled version, that also serves to introduce you to compiled ROOT programs.

Macro: Get the macro version and save it. It's documented, so have a look at it. After running this macro, you'll find a rootfile simpleTree.root in your directory:

.x simpleTree.C
TFile g("SimpleTree.root");
g.ls();
SimpleTree->Print();
SimpleTree->Draw("Pz");



Compiled stand-alone program: It is very easy to change this macro to a stand-alone program. You'll need the GNUmakefile,(you'll need to define an environment variable ARCH, it's described in the file) and the source, which has only some header files added and an instance of TROOT (the big common block of ROOT ...)

setenv ARCH `uname`
gmake simpleTree
./simpleTree

This will produce a rootfile with a tree SimpleTree in it. Look at it in ROOT with

TFile g("SimpleTree.root");
g.ls();
SimpleTree->Print();
SimpleTree->Draw("Pz");



Non-trivial case

The following is a version with minimal coupling to BaBar software (mainly for historical reasons). It works, it's being used in improved versions in at least three analyses within BaBar , but is by far neither the only way nor very clever - au contraire. Enough disclaimers.

1. Define what you'll put into the tree per event. Here we'll make the definition of an event class (TMyEvent) containing several candidates (TMyCand) of the same kind. You can of course expand the example to include several lists of cands. Copy the following files:

cp /afs/slac.stanford.edu/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/TMyEvent.hh ~/rootutorial
cp /afs/slac.stanford.edu/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/TMyEvent.cc ~/rootutorial
cp /afs/slac.stanford.edu/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/TMyEventLinkDef.h ~/rootutorial
cp /afs/slac.stanford.edu/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/TMyCand.hh ~/rootutorial
cp /afs/slac.stanford.edu/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/TMyCand.cc ~/rootutorial
cp /afs/slac.stanford.edu/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/TMyCandLinkDef.h ~/rootutorial
cp /afs/slac.stanford.edu/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/GNUmakefile ~/rootutorial

The ClassDef and ClassImp macros in the header and source files (respectively) are necessary to link your classes to the dictionary (generated by CINT) to get access to RTTI and other object I/O features of ROOT. The RTTI system allows you to, a.o. find out to which class an object belongs, its baseclasses, its datamembers and methods, the method signatures, etc. Read the full documentation for further details (you absolutely don't need to understand the details in order to use this!)

2. Change your .cshrc:

setenv ARCH `uname`
setenv ROOTSYS /afs/slac.stanford.edu/package/cernroot/30207
setenv ROOTVER 3.02-07
setenv ROOTPATH $ROOTSYS
addpath2 PATH $ROOTSYS/bin
setenv LD_LIBRARY_PATH $ROOTSYS/lib

Note that you need to change the line with LD_LIBRARY_PATH, if you set that variable already. You need to adjust your path for the compilation, hence it is probably best to fix a version for ROOTSYS in your .chsrc. You have to be very careful about which ROOT version you use:

At the moment, everything is set-up to use 3.02/07. This means, that you'll need to check out additional packages when incorporating this into a BaBar release (see below).

3. Compile the source code and link it into a shared library in a new shell:

cd ~/rootutorial/.
gmake myclass

4. Now you have interactive access to this class from a ROOT session:

gSystem->Load("libPhysics.so");
gSystem->Load("~/rootutorial/libMyClass.so");
TMy<TAB>

and you could e.g. instantiate objects of your event and candidate classes. An example macro filling this tree is given in writeMyEvent.C. A macro for reading the rootfile back in is provided in readMyEvent.C (with a compiled version in readMyEvent.cc)

You can write and read your custom tree with the two macros:

.x writeMyEvent.C
.x readMyEvent.C

Compile and link the executable with

gmake readMyEvent



Part 4. Incorporate your tree into a Beta job

In the previous part you found an example of a possible non-trivial tree structure. In this part,you'll find templates of the necessary files to get this into a Beta job.

1. Go to a testrelease area, and check out BetaUser

2. Copy the following files into your BetaUser directory:

cp /afs/slac/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/WorkBook* BetaUser/.
cp /afs/slac/g/babar/www/doc/tutorials/19Jun2000_Root_Tutorial/bin_BetaUser.mk BetaUser/.

The additions (all marked with // -- ROOT) comprise the following:

Adding data members TTree *_Tree, and TMyEvent *_Event (with a forward declaration at the top of the header file)



Including the relevant header files at the top of the source file.
Instantiating objects of class TTree and TMyEvent in beginJob()
Filling the TMyEvent object with candidate data and the tree with with the TMyEvent.
Writing the tree out at endJob()

override LOADLIBES += /your_home_directory/rootio/libMyClass.so

3. Set up links to your tree header files:

cd BetaUser
ln -s ~/rootutorial/TMyEvent.hh
ln -s ~/rootutorial/TMyCand.hh
cd ..

4. Consider what ROOT version you want to use. If you go with 3.02/07 (recommended), check out all required additional packages for ROOT 3.02/07. In addition, you'll need to reset ROOTVER and ROOTPATH, BaBar-specific environment variables:

setenv ROOTVER 3.02-07
setenv ROOTPATH $ROOTSYS

If you don't care about recompiling a few additional packages, you can do everything with one modern version, e.g. 3.02/07 (a recent production version well tested within BABAR).

5. Turn to the Beta/Kanga Tutorial in order to find out how to setenv your environment and compile/link so that you get a rootfile at the end of your job.

setenv ....
bsub -q bldrecoq ....

6. The macro readMyEvent.C given in Part 3 should still be able to read the resulting tree, thus

.x ~/rootutorial/readMyEvent.C

[Thanks a lot to Thomas Schietinger for his help in debugging this.]


Urs Langenegger
Last modified: 24 September 2001