/* ======================================================================== *\ ! ! * ! * This file is part of MARS, the MAGIC Analysis and Reconstruction ! * Software. It is distributed to you in the hope that it can be a useful ! * and timesaving tool in analysing Data of imaging Cerenkov telescopes. ! * It is distributed WITHOUT ANY WARRANTY. ! * ! * Permission to use, copy, modify and distribute this software and its ! * documentation for any purpose is hereby granted without fee, ! * provided that the above copyright notice appear in all copies and ! * that both that copyright notice and this permission notice appear ! * in supporting documentation. It is provided "as is" without express ! * or implied warranty. ! * ! ! ! Author(s): Thomas Bretz, 9/2004 ! ! Copyright: MAGIC Software Development, 2000-2004 ! ! \* ======================================================================== */ ///////////////////////////////////////////////////////////////////////////// // // MJOptimize // // Class for otimizing the parameters of the supercuts // // Minimization Control // ==================== // // To choose the minimization algorithm use: // void SetOptimizer(Optimizer_t o); // // Allowed options are: // enum Optimizer_t // { // kMigrad, // Minimize by the method of Migrad // kSimplex, // Minimize by the method of Simplex // kMinimize, // Migrad + Simplex (if Migrad fails) // kMinos, // Minos error determination // kImprove, // Local minimum search // kSeek, // Minimize by the method of Monte Carlo // kNone // Skip optimization // }; // // For more details on the methods see TMinuit. // // // You can change the behaviour of the minimization using // // void SetNumMaxCalls(UInt_t num=0); // void SetTolerance(Float_t tol=0); // // While NumMaxCalls is the first, Tolerance the second arguement. // For more details start root and type // // gMinuit->mnhelp("command") // // while command can be // * MIGRAD // * SIMPLEX // * MINIMIZE // * MINOS // * IMPROVE // * SEEK // // The default (num==0 and tol==0) should always give you the // corresponding defaults used in Minuit. // // // FIXME: Implement changing cut in hadronness... // FIXME: Show MHSignificance on MStatusDisplay during filling... // FIXME: Choose step-size percentage as static data membewr // FIXME: Choose minimization method // ///////////////////////////////////////////////////////////////////////////// #include "MJOptimize.h" #include #include #include #include #include #include #include "MHMatrix.h" // environment #include "MLog.h" #include "MLogManip.h" #include "MDirIter.h" #include "MStatusDisplay.h" // eventloop #include "MParList.h" #include "MTaskList.h" #include "MEvtLoop.h" // parameters #include "MParameters.h" // tasks #include "MReadTree.h" #include "MMatrixLoop.h" #include "MFillH.h" // filters #include "MFDataPhrase.h" #include "MFilterList.h" using namespace std; //------------------------------------------------------------------------ // // fcn calculates the function to be minimized (using TMinuit::Migrad) // void MJOptimize::fcn(Int_t &npar, Double_t *gin, Double_t &f, Double_t *par, Int_t iflag) { MJOptimize *optim = (MJOptimize*)gMinuit->GetObjectFit(); // WORKAROUND --- FOR WHAT? if (gMinuit->fEpsi<1e-2) { *optim->fLog << warn << "WARNING - For unknown reasons: fEspi<1e-100... resetting to 0.01." << endl; gMinuit->fEpsi = 0.01; } TMinuit *minuit = gMinuit; f = optim->Fcn(TArrayD(TMath::Min(gMinuit->fMaxpar, optim->fParameters.GetSize()), par), minuit); gMinuit = minuit; } Double_t MJOptimize::Fcn(const TArrayD &par, TMinuit *minuit) { if (fEvtLoop->GetDisplay()!=fDisplay) return 0; /* switch(iflag) { case 1: // first call case 2: // calc derivative break; case 3: // last call MStatusDisplay *d = new MStatusDisplay; fEvtLoop->SetDisplay(d); break; } */ MParList *plist = fEvtLoop->GetParList(); MParameterD *eval = (MParameterD*)plist->FindObject(fNameMinimizationValue, "MParameterD"); MParContainer *pars = (MParContainer*)plist->FindObject("MParameters", "MParContainer"); MRead *read = (MRead*)plist->FindObject("MTaskList")->FindObject("MRead"); if (read) read->Rewind(); if (fDebug>=0) { *fLog << inf << "New Set: "; for (Int_t i=0; iSetVariables(par); eval->SetVal(0); const Bool_t isnull = gLog.IsNullOutput(); if (fDebug<3) gLog.SetNullOutput(kTRUE); TStopwatch clock; clock.Start(); fEvtLoop->Eventloop(fNumEvents, MEvtLoop::kNoStatistics); clock.Stop(); if (fDebug<3) gLog.SetNullOutput(isnull); const Double_t f = eval->GetVal(); if (fDebug>=0) *fLog << inf << "Result F=" << f << endl; if (fDebug>=1 && minuit) { Double_t fmin, fedm, errdef; Int_t n1, n2, istat; minuit->mnstat(fmin, fedm, errdef, n1, n2, istat); *fLog << inf << underline << "Minimization Status so far:" << endl; *fLog << " Calls: " << minuit->fNfcn << " (max=" << gMinuit->fMaxIterations << ")" << endl; *fLog << " Parameters: fixed=" << gMinuit->fNpfix << ", free=" << gMinuit->fNpar << endl; *fLog << " Func min: " << fmin << " (Epsi=" << gMinuit->fEpsi << ", Apsi=" << gMinuit->fApsi << ")" << endl; *fLog << " Found edm: " << fedm << endl; *fLog << " ErrDef: " << errdef << endl; *fLog << " Status: "; switch (istat) { case 0: *fLog << "n/a" << endl; break; case 1: *fLog << "approximation only, not accurate" << endl; break; case 2: *fLog << "full matrix, but forced positive-definite" << endl; break; case 3: *fLog << "full accurate covariance matrix" << endl; break; default: *fLog << "undefined" << endl; break; } } if (fDebug>=1) { clock.Print(); fEvtLoop->GetTaskList()->PrintStatistics(); } return f; } MJOptimize::MJOptimize() : fDebug(-1), fNumEvents(0), fType(kSimplex), fNumMaxCalls(0), fTolerance(0), fTestTrain(0), fNameMinimizationValue("MinimizationValue") { fRules.SetOwner(); fFilter.SetOwner(); fNamesOn.SetOwner(); fNamesOff.SetOwner(); } //------------------------------------------------------------------------ // // Add sequences from list to reader // Bool_t MJOptimize::AddSequences(MRead &read, TList &list) const { MDirIter files; TIter Next(&list); MSequence *seq=0; while ((seq=(MSequence*)Next())) { if (!seq->IsValid()) return kFALSE; if (seq->SetupDatRuns(files)<=0) return kFALSE; } return read.AddFiles(files)>0; } //------------------------------------------------------------------------ // // Add on-sequences: // - fname: sequence file name (with path) // - dir: directory were image files are stored // void MJOptimize::AddSequenceOn(const char *fname, const char *dir) { fNamesOn.Add(new MSequence(fname, dir)); } //------------------------------------------------------------------------ // // Add off-sequences: // - fname: sequence file name (with path) // - dir: directory were image files are stored // void MJOptimize::AddSequenceOff(const char *fname, const char *dir) { fNamesOff.Add(new MSequence(fname, dir)); } //------------------------------------------------------------------------ // // Empty list of on- and off-sequences // void MJOptimize::ResetSequences() { fNamesOn.Delete(); fNamesOff.Delete(); } //------------------------------------------------------------------------ // // Add a parameter used in your filters (see AddFilter) The parameter // index is returned, // // Int_t idx = AddParameter("log10(MHillas.fSize)"); // // The indices area starting with 0 always. // Int_t MJOptimize::AddParameter(const char *rule) { fRules.Add(new TNamed(rule, "")); return fRules.GetSize()-1; } //------------------------------------------------------------------------ // // Add a filter which can be applied in the optimization (for deatils // see correspodning Run function) You can use the indices you got by // AddParameter, eg // // AddFilter("M[0] < 3.2"); // // if used in optimization you can do // // AddFilter("M[0] < [0]"); // // for more details, see SetParameter and FixParameter // void MJOptimize::AddFilter(const char *rule) { fFilter.Add(new MFDataPhrase(rule)); } //------------------------------------------------------------------------ // // Add a cut which is used to fill the matrix, eg "MMcEvt.fOartId<1.5" // (The rule is applied, nit inverted: The matrix is filled with // the events fullfilling the condition) // void MJOptimize::AddPreCut(const char *rule) { MFilter *f = new MFDataPhrase(rule); f->SetBit(kCanDelete); AddPreCut(f); } //------------------------------------------------------------------------ // // Add a cut which is used to fill the matrix. If kCanDelete is set // MJOptimize takes the ownership. // void MJOptimize::AddPreCut(MFilter *f) { fPreCuts.Add(f); } //------------------------------------------------------------------------ // // Set the fParameters Array accoring to par. // void MJOptimize::SetParameters(const TArrayD &par) { fParameters = par; } //------------------------------------------------------------------------ // // Set the number of events processed by the eventloop. (Be carfull, // if you are doing on-off analysis and you only process the first // 1000 events which are on-events only the optimization may not work) // void MJOptimize::SetNumEvents(UInt_t n) { fNumEvents=n; } //------------------------------------------------------------------------ // // Set a debug level, which tells the optimization how much information // is displayed about and in the running eventloop. // void MJOptimize::SetDebug(UInt_t n) { fDebug=n; } //------------------------------------------------------------------------ // // Set a optimization algorithm to be used. For more information see // TMinuit. // // Available Algorithms are: // kMigrad, // Minimize by the method of Migrad // kSimplex, // Minimize by the method of Simplex // kSeek // Minimize by the method of Monte Carlo // void MJOptimize::SetOptimizer(Optimizer_t o) { fType = o; } //------------------------------------------------------------------------ // // If a status didplay is set, search for tab "Optimizer". // If not found, create it. // In the tab search for TMultiGraph "Parameters". // If not found create it. // If empty create TGraphs. // Check number of graphs vs. number of parameters. // return TList with graphs. // TList *MJOptimize::GetPlots() const { if (!fDisplay) return NULL; TCanvas *c=fDisplay->GetCanvas("Optimizer"); if (!c) c = &fDisplay->AddTab("Optimizer"); TMultiGraph *mg = dynamic_cast(c->FindObject("Parameters")); if (!mg) mg = new TMultiGraph("Parameters", "Parameters of optimization"); TList *l = mg->GetListOfGraphs(); if (!l) { const Int_t n = fParameters.GetSize(); for (int i=0; iSetLineColor(kBlue); mg->Add(g, ""); AddPoint(mg->GetListOfGraphs(), i, i==n?1:fParameters[i]); } mg->SetBit(kCanDelete); mg->Draw("al*"); l = mg->GetListOfGraphs(); } return l->GetSize() == fParameters.GetSize()+1 ? l : NULL; } //------------------------------------------------------------------------ // // Add a point with y=val as last point in idx-th Tgraph of list l. // void MJOptimize::AddPoint(TList *l, Int_t idx, Float_t val) const { if (!l) return; TGraph *gr = (TGraph*)l->At(idx); gr->SetPoint(gr->GetN(), gr->GetN(), val); } Int_t MJOptimize::Minuit(TMinuit &minuit, const char *cmd) const { Int_t err; Double_t tmp[2] = { fNumMaxCalls, fTolerance }; minuit.mnexcm(cmd, tmp, 2, err); switch (err) { case 0: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm excuted normally." << endl; break; case 1: *fLog << warn << GetDescriptor() << " TMinuit::mnexcm command is blank... ignored." << endl; break; case 2: *fLog << warn << GetDescriptor() << " TMinuit::mnexcm command-line syntax error... ignored." << endl; break; case 3: *fLog << warn << GetDescriptor() << " TMinuit::mnexcm unknown command... ignored." << endl; break; case 4: *fLog << warn << GetDescriptor() << " TMinuit::mnexcm - Abnormal termination (eg Migrad not converged)" << endl; break; /* case 5: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - Parameters requested." << endl; break; case 6: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - SET INPUT returned." << endl; break; case 7: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - SET TITLE returned." << endl; break; case 8: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - SET COVAR returned." << endl; break; case 9: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - reserved." << endl; break; case 10: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - END returned." << endl; break; case 11: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - EXIT or STOP returned." << endl; break; case 12: *fLog << inf << GetDescriptor() << " TMinuit::mnexcm - RETURN returned." << endl; break;*/ } return err; } Bool_t MJOptimize::Optimize(MEvtLoop &evtloop) { if (fParameters.GetSize()==0) { *fLog << err << GetDescriptor() << "::Optimize: ERROR - Sorry, no parameters defined." << endl; return kFALSE; } if (fType==kNone) return kTRUE; gMinuit = new TMinuit(fParameters.GetSize()); gMinuit->SetFCN(fcn); gMinuit->SetObjectFit(this); gMinuit->SetPrintLevel(-1); // Don't print when DefineParameter // // Set starting values and step sizes for parameters // for (Int_t i=0; iDefineParameter(i, name, vinit, step, limlo, limup); if (rc) { *fLog << err << dbginf << "Error in defining parameter #" << i << endl; return kFALSE; } if (step==0) gMinuit->FixParameter(i); } gMinuit->SetPrintLevel(1); // Switch on pritning again gMinuit->mnprin(1,0); // Print all parameters fEvtLoop = &evtloop; TList *g=GetPlots(); // Now ready for minimization step: TStopwatch clock; clock.Start(); switch (fType) { case kSimplex: Simplex(*gMinuit); break; case kMigrad: Migrad(*gMinuit); break; case kMinimize: Minimize(*gMinuit); break; case kMinos: Minos(*gMinuit); break; case kImprove: Improve(*gMinuit); break; case kSeek: Seek(*gMinuit); break; case kNone: // Should never happen return kFALSE; } clock.Stop(); clock.Print(); if (evtloop.GetDisplay()!=fDisplay) { *fLog << inf << "Optimization aborted by user." << endl; fDisplay = 0; return kFALSE; } *fLog << inf << "Resulting Chisq: " << gMinuit->fAmin << endl; // // Update values of fA, fB: // for (Int_t i=0; iGetParameter(i,x1,x2); fParameters[i] = x1; cout << i << ": " << fParameters[i] << endl; AddPoint(g, i, x1); } AddPoint(g, fParameters.GetSize(), gMinuit->fAmin); delete gMinuit; return kTRUE; } //------------------------------------------------------------------------ // // Optimize allows to use the optimizing by an eventloop based on // some requirements. // // 1) The tasklist to be executed must have the name MTaskList and // be an entry in the parameterlist. // // 2) The reading task (MReadMarsFile, MMatrixLoop) must have the name // "MRead". If it derives from MRead Rewind() must be implemented, // otherwise it must start reading from scratch each time its // PreProcess is called. // // 3) The parameters to be optimized must be accessed through (currently) // a single parameter container (MParContainer) called MParameters. // The parameters are set through SetVariables. // // 4) The result of a single function call for minimization (eg. chisquare) // must be available after the eventloop in a container of type // MParameterD with the name "MinimizationResult". // // 5) The parameters to start with must have been set using // MJOptimize::SetParameter or MJOptimize::SetParameters and // MJOptimize::FixParameter // // The behaviour of the optimization can be changed using: // void SetNumEvents(UInt_t n); // void SetDebug(UInt_t n); // void SetOptimizer(Optimizer_t o); // // After optimization the resulting parameters are set and another eventloop // with a MStatusDisplay is set is called. The resulting parameters can be // accessed using: GetParameters() // // To be fixed: // - MStatusDisplay should show status while optimization is running // - write result into MStatusDisplay // - save result // Bool_t MJOptimize::Optimize(MParList &parlist) { // Checks to make sure, that fcn doesn't crash if (!parlist.FindCreateObj("MParameterD", fNameMinimizationValue)) return kFALSE; if (!parlist.FindObject("MParameters", "MParContainer")) { *fLog << err << "MParameters [MParContainer] not found... abort." << endl; return kFALSE; } MTaskList *tlist = (MTaskList*)parlist.FindObject("MTaskList"); if (!tlist) { *fLog << err << "MTaskList not found... abort." << endl; return kFALSE; } tlist->SetAccelerator(MTask::kAccDontReset|MTask::kAccDontTime); MMatrixLoop *loop = dynamic_cast(parlist.FindTask("MRead")); TString txt("Starting "); switch (fType) { case kMigrad: txt += "Migrad"; break; case kMinimize: txt += "Minimize"; break; case kMinos: txt += "Minos"; break; case kImprove: txt += "Improve"; break; case kSimplex: txt += "Simplex"; break; case kSeek: txt += "Seek"; break; case kNone: txt += "no"; break; } txt += " optimization"; fLog->Separator(txt); // Setup eventloop MEvtLoop evtloop; evtloop.SetParList(&parlist); evtloop.SetDisplay(fDisplay); // set display for evtloop and all childs parlist.SetDisplay(0); // reset display for all contents of parlist and tasklist evtloop.SetPrivateDisplay(); // prevent display from being cascaded again in PreProcess *fLog << inf << "Number of Parameters: " << fParameters.GetSize() << endl; // In case the reader is the matrix loop and testrain is enabled // switch on even mode... if (loop && TMath::Abs(fTestTrain)>0) loop->SetOperationMode(fTestTrain>0?MMatrixLoop::kEven:MMatrixLoop::kOdd); if (!Optimize(evtloop)) return kFALSE; gMinuit = 0; fEvtLoop->SetDisplay(fDisplay); if (!Fcn(fParameters)) return kFALSE; // In case the reader is the matrix loop and testrain is enabled // switch on odd mode... if (!loop || fTestTrain==0) return kTRUE; loop->SetOperationMode(fTestTrain<0?MMatrixLoop::kEven:MMatrixLoop::kOdd); // Done already in Fcn // list.SetVariables(fParameters); return Fcn(fParameters); } void MJOptimize::AddRulesToMatrix(MHMatrix &m) const { TIter Next1(&fRules); TObject *o1=0; while ((o1=Next1())) m.AddColumn(o1->GetName()); } //------------------------------------------------------------------------ // // Fill matrix m by using read. Use rules as a filter if userules. // Bool_t MJOptimize::FillMatrix(MReadTree &read, MParList &parlist, Bool_t userules) { MHMatrix *m = (MHMatrix*)parlist.FindObject("M", "MHMatrix"); if (!m) { *fLog << err << "MJOptimize::FillMatrix - ERROR: M [MHMatrix] not found in parlist... abort." << endl; return kFALSE; } m->Print("cols"); //MParList parlist; // MGeomCamMagic cam; // parlist.AddToList(&cam); MTaskList tlist; parlist.Replace(&tlist); MFillH fillh(m); tlist.AddToList(&read); MFilterList list; if (!list.AddToList(fPreCuts)) *fLog << err << "ERROR - Calling MFilterList::AddToList for fPreCuts failed!" << endl; if (userules) SetupFilters(list); list.SetName("PreCuts"); // reset Name set by SetupFilters list.SetInverted(kFALSE); // reset inversion set by SetupFilters fillh.SetFilter(&list); tlist.AddToList(&list); tlist.AddToList(&fillh); tlist.SetAccelerator(MTask::kAccDontReset|MTask::kAccDontTime); MEvtLoop fillloop; fillloop.SetParList(&parlist); fillloop.SetDisplay(fDisplay); if (!fillloop.Eventloop(fNumEvents)) { *fLog << err << "Filling matrix failed..." << endl; return kFALSE; } *fLog << inf << "Read events from file '" << read.GetFileName() << "'" << endl; if (fillloop.GetDisplay()!=fDisplay) { fDisplay = 0; *fLog << inf << "Optimization aborted by user." << endl; return kFALSE; } m->Print("size"); return kTRUE; } //------------------------------------------------------------------------ // // Adds all filters to MFilterList // void MJOptimize::SetupFilters(MFilterList &list, MFilter *filter) const { list.SetName("MParameters"); list.SetInverted(); if (filter) { if (fFilter.GetSize()>0) { *fLog << inf; *fLog << "INFORMATION - You are using an external filter and internal filters." << endl; *fLog << " Please make sure that all parameters '[i]' are starting" << endl; *fLog << " behind the number of parameters of the external filter." << endl; } list.AddToList(filter); } if (!list.AddToList(fFilter)) *fLog << err << "ERROR - Calling MFilterList::AddToList fFilter failed!" << endl; *fLog << inf << "Filter: "; list.Print(); *fLog << endl; }