TF1 Streamer (when created with C++ functor)

Hi,

If a class contains a std::vector of “type D” TF1 pointers i.e. those using a general C++ functor as their formula, does the TF1 Streamer have the capability to recreate the appropriate functors when an object of this class is written to and then read from a TFile? If not, is there a method of TF1 which allows one to re-associate an appropriate functor with the TF1 in a custom streamer method?

Does it make it more difficult that I’ve used a std::vector and not a TObjArray? I’m assuming that I’ll need to add std::vector<TF1*> to the LinkDef file as well?

Cheers,

Hugh

Hi,

[quote]Does it make it more difficult that I’ve used a std::vector and not a TObjArray?[/quote]As far as restoring your TF1 object in would not make a difference.[quote] I’m assuming that I’ll need to add std::vector<TF1*> to the LinkDef file as well?[/quote]You should generate a dictionary for std::vector<TF1*>; Note that if you vector is a member of a class for which you generate a dictionary then the dictionary for vector<TF1*> is useful but not necessary.

Cheers,
Philippe.

[quote]does the TF1 Streamer have the capability to recreate the appropriate functors when an object of this class is written to and then read from a TFile?[/quote]For now, no information about the functor is saved to the file. To restore it completely you will need to call TF1::SetFunction

Cheers,
Philippe.

According to the ROOT documentation there is no such method TF1::SetFunction, nor does CINT seem to think one exists. I’m using ROOT 5.20, and I can’t seem to find any method that sets the function of a TF1.

Help!

Cheers,

Hugh

Hi,

TF1::SetFunction is a template method, so it is not part of the dictionary.
You can see them it in the header file TF1.h


template <typename Func> 
void TF1::SetFunction( Func f )    {
   // set function from a generic C++ callable object 
   fType = 1; 
   fFunctor = ROOT::Math::ParamFunctor(f); 
} 
template <class PtrObj, typename MemFn> 
void TF1::SetFunction( PtrObj& p, MemFn memFn )   { 
   // set from a pointer to a member function
   fType = 1; 
   fFunctor = ROOT::Math::ParamFunctor(p,memFn); 
} 

I agree, it would be good to have the functionality to re-create the TF1 automatically when read from a file. However, this would requires that the
object used by TF1 has a dictionary, and this is not alway true for user defined objects.
We could plan to add this functionality in one of the next releases.

Cheers

Lorenzo

Thanks Lorenzo! I must admit I’m still a little confused. Does the lack of a dictionary mean that I can’t call the function, or just that I can’t call it from within an interactive session? I’ve tried in an interactive session with:

and

and neither works.

I’m also having trouble getting the Streamer to work. Compiling with ACLiC I get the following errors from the automatically generated Streamer in the dictionary:

[quote]/Applications/root/ROOTUtils/TFitFunction/tmp_0_pe4ZTP.cxx: In member function ‘virtual void TCompoundFormula::Streamer(TBuffer&)’:
/Applications/root/ROOTUtils/TFitFunction/tmp_0_pe4ZTP.cxx:464: error: type ‘class std::vector<TF1*, std::allocator<TF1*> >’ argument given to ‘delete’, expected pointer
/Applications/root/ROOTUtils/TFitFunction/tmp_0_pe4ZTP.cxx:465: error: no match for ‘operator=’ in '((TCompoundFormula)this)->TCompoundFormula::fComps = (((const std::allocator<TF1*>&)((const std::allocator<TF1*>)(& std::allocator<TF1>()))), (((std::vector<TF1*, std::allocator<TF1*> >)operator new(12u)), (->std::vector<_Tp, _Alloc>::vector with _Tp = TF1*, _Alloc = std::allocator<TF1*>, )))’
/usr/include/gcc/darwin/4.0/c++/bits/vector.tcc:133: note: candidates are: std::vector<_Tp, _Alloc>& std::vector<_Tp, _Alloc>::operator=(const std::vector<_Tp, _Alloc>&) [with _Tp = TF1
, _Alloc = std::allocator<TF1*>]
/Applications/root/ROOTUtils/TFitFunction/tmp_0_pe4ZTP.cxx:466: error: no match for ‘operator*’ in ‘((TCompoundFormula*)this)->TCompoundFormula::fComps’
/Applications/root/ROOTUtils/TFitFunction/tmp_0_pe4ZTP.cxx:490: error: no match for ‘operator*’ in '
((TCompoundFormula*)this)->TCompoundFormula::fComps’
[/quote]

The streamer method looks like:

[code]void TCompoundFormula::Streamer(TBuffer &R__b)
{
// Stream an object of class TCompoundFormula.

UInt_t R__s, R__c;
if (R__b.IsReading()) {
Version_t R__v = R__b.ReadVersion(&R__s, &R__c); if (R__v) { }
TF1::Streamer(R__b);
{
delete fComps;
fComps = new vector<TF1>;
vector<TF1
> &R__stl = fComps;
R__stl.clear();
TClass R__tcl1 = TBuffer::GetClass(typeid(TF1));
if (R__tcl1==0) {
Error(“fComps streamer”,“Missing the TClass object for TF1!”);
return;
}
int R__i, R__n;
R__b >> R__n;
R__stl.reserve(R__n);
for (R__i = 0; R__i < R__n; R__i++) {
TF1
R__t;
R__t = (TF1
)R__b.ReadObjectAny(R__tcl1);
R__stl.push_back(R__t);
}
}
R__b >> fCombination;
R__b >> fDummyFormula;
R__b >> fNumPars;
R__b.CheckByteCount(R__s, R__c, TCompoundFormula::IsA());
} else {
R__c = R__b.WriteVersion(TCompoundFormula::IsA(), kTRUE);
TF1::Streamer(R__b);
{
vector<TF1
> &R__stl = *fComps;
int R__n=(&R__stl) ? int(R__stl.size()) : 0;
R__b << R__n;
if(R__n) {
vector<TF1
>::iterator R__k;
for (R__k = R__stl.begin(); R__k != R__stl.end(); ++R__k) {
R__b << (*R__k);
}
}
}
R__b << fCombination;
R__b << fDummyFormula;
R__b << fNumPars;
R__b.SetByteCount(R__c, kTRUE);
}
}
[/code]

I tried making a very simple test class with just a pointer to vector of TF1*s as a data member (this is exactly the same context as the vector int the non-compiling class) and this compiles fine. Its streamer looks like:

[code]void Test::Streamer(TBuffer &R__b)
{
// Stream an object of class Test.

UInt_t R__s, R__c;
if (R__b.IsReading()) {
Version_t R__v = R__b.ReadVersion(&R__s, &R__c); if (R__v) { }
TObject::Streamer(R__b);
{
delete formulae;
formulae = new vector<TF1>;
vector<TF1
> &R__stl = formulae;
R__stl.clear();
TClass R__tcl1 = TBuffer::GetClass(typeid(TF1));
if (R__tcl1==0) {
Error(“formulae streamer”,“Missing the TClass object for TF1!”);
return;
}
int R__i, R__n;
R__b >> R__n;
R__stl.reserve(R__n);
for (R__i = 0; R__i < R__n; R__i++) {
TF1
R__t;
R__t = (TF1
)R__b.ReadObjectAny(R__tcl1);
R__stl.push_back(R__t);
}
}
R__b.CheckByteCount(R__s, R__c, Test::IsA());
} else {
R__c = R__b.WriteVersion(Test::IsA(), kTRUE);
TObject::Streamer(R__b);
{
vector<TF1
> &R__stl = *formulae;
int R__n=(&R__stl) ? int(R__stl.size()) : 0;
R__b << R__n;
if(R__n) {
vector<TF1
>::iterator R__k;
for (R__k = R__stl.begin(); R__k != R__stl.end(); ++R__k) {
R__b << (*R__k);
}
}
}
R__b.SetByteCount(R__c, kTRUE);
}
}
[/code]

To my untrained eye, the portions pertaining to the vectors (fComps and formulae respectively) appear identical, and the error messages seem to originate from the streaming of the vector, so I’m a little bemused as to why one should compile and the other not.

Any ideas?

Hugh

I should probably add, that not only does the streamer for the Test class compile fine. It also allows me to write a Test to a file and read it back again with all the TF1s intact (albeit without the function associations). Also, the Test class inherits directly from TObject, whereas the non-compiling (TCompoundFormula), inherits from TF1. Perhaps this causes some problems?

These are the class definitions for TCompoundFormula and Test.

[code]#include <TFormula.h>
#include <TF1.h>
#include
#include

// Class which computes the combination of several TF1s

class TCompoundFormula : public TF1 { // public TF1 so we can use the fit interface of the TH1 and TGraph classes

public :

TCompoundFormula():
TF1()
{
}

TCompoundFormula(const char * name, std::vector<TF1*> * comps, const char * combination, Double_t xMin, Double_t xMax, Int_t numPar);

~TCompoundFormula();

void SetComps(std::vector<TF1*> * comps); // function to set the component formulae
Bool_t SetCombination(const char * combination); // function to define the combination of the components
void DrawComps(const char * drawOpt = “”); // Draw the combined function and components.
std::vector<TF1*> * GetComps(){return fComps;}; // get the vector of components

private :

std::vector<TF1*> * fComps; // vector of component TFormulae
Bool_t fCombination; // Has a valid combination formula been compiled.
TFormula * fDummyFormula; // does the combination of the input components

// this is the total number of parameters for all the TF1s, not to be confused with the parameters of this
// which pertain to the combination.
Double_t fNumPars;

Double_t Combine(Double_t * x, Double_t * par); // evaluates the combination

ClassDef(TCompoundFormula, 1);
};
[/code]

[code]#include
#include <TObject.h>
#include <TF1.h>

class Test : public TObject{

public :

Test():formulae(new std::vector<TF1*>){};
~Test(){};
std::vector<TF1*> * formulae;

ClassDef(Test, 1);
};

[/code]

Okay, some progress has been made. The streamer works if I use the “new” style streamers by putting a “+” in the LinkDef file.

So then I moved on to writing a custom streamer to re-associate each of the TF1s with a functor on reading.

I replaced the “+” in the LinkDef file with a “-”, copied the automatically generated Streamer() method and modified the reading part.

Now when I try to compile with ACLiC I get this error:

What’s going wrong?

Cheers,

Hugh

Problem solved! The Streamer method is defined by the ClassDef macro, so I don’t need to define it myself in the header file!

[quote]Does the lack of a dictionary mean that I can’t call the function, or just that I can’t call it from within an interactive session? [/quote]It means that you can not call it from an interactive session, you can call only only from compiled code (including code compiled via ACLiC).

[quote]Okay, some progress has been made. The streamer works if I use the “new” style streamers by putting a “+” in the LinkDef file.[/quote]Indeed the old style is known to not support STL container well in general (and vector for pointer in particular). One of the purpose of the new style I/O is to solve this kind of problems. [I am surprised that your simple Test class worked. The streamer you copy pasted can not compile with the header you copy-pasted].

Cheers,
Philippe