-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update to StFcsWaveformFitMaker to use new algorithm #401
Conversation
} | ||
if(mMeasureTime){ | ||
mTime=new TH1F("FitTime","FitTime; msec",200,0,6); | ||
mTime=new TH1F("FitTime","FitTime;msec",1000,0,1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using this line to comment on a common problem which this PR seems to introduce in quite a few other places.
The mTime histogram can be allocated multiple times during a job when the framework calls InitRun(), however, the corresponding object is deleted only once in the destructor, hence causing a memory leak. Depending on the logic you are trying to implement here, you may need to allocate histograms only once in the Init() method. It may be also easier to rely on the automatic object lifetime than trying to manage the memory manually like you do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought InitRun was only called once during a job, which is why I put the histogram creations there. I agree that I only need these histograms created once per job. Where should I move the histogram allocations so that they only get created once? Hopefully I can move these before this pull request is made so that I don't need to make a new one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move to Init(). In principle, InitRun can be called multiple times per job if you are reading file(s) from multiple runs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it needs to be done only once, is there a reason not to initialize the histograms in the constructor? Also, if there is no good reason to allocate them with the new
operator, you can avoid the many delete
statements in the destructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB: During StMaker::Init(), any newly instantiated histograms are added to the maker's "hist" subdirectory (in TDataset terms) which allows them to be found using the TDataset methods and enables inclusion in the BFC's hist.root output files.
// Add the Maker histograms in the Maker histograms list
// and remove it from the ROOT system directory
gROOT->cd();
TIter nextHist(gDirectory->GetList());
Int_t ready = !objLast;
while((objHist=nextHist())) {// loop over gDirectory
if (!ready && objHist!=objLast) continue;
ready = 1999;
if (objHist==objLast) continue;
if (!objHist->InheritsFrom("TH1")) continue;
// Move the histogram from the ROOT list into the "maker's" list
((TH1*)objHist)->SetDirectory(0);
maker->AddHist((TH1*)objHist);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving them to the constructor means that these histograms will always be created, whereas now they are only created when a certain test level is set using setTest(). I thought this would be best since in the production these histograms don't need to be created as they are mostly used to quantify how well the algorithm is working. If I move them to the constructor then I would have to add an argument for the test level and file name where those histograms will be saved. I think doing that will only make this class more complicated so I left them in Init(). That way whoever calls this class can turn testing on or off before it hits the chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StFcsWaveformFitMaker is already included in the fcs
chain option. According to this page https://www.star.bnl.gov/devcgi/dbProdOptionRetrv.pl it was used once in
pp2022 StiCA BEmcChkStat epdhit -hitfilt
for "Preliminary chain for run 2022 fastoffline production". From this I conclude that changing the algorithm should not have significant consequences
{ | ||
graph->SetPoint(ipeak, Ana.GetPeak(ipeak).mPeakX, Ana.GetPeak(ipeak).MidPoint() ); | ||
} | ||
PeakAna* NewAna = new PeakAna(Ana,graph); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can cause a memory leak. Why not return the object? EDIT: I mean "return by value"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure whether you are referring to graph or NewAna. I don't think graph will give a memory leak since the next line calls NewAna->ForceInternal()
. This means that the newly created graph object is "owned" by NewAna and it will delete that newly created graph when it is destroyed or SetData() is called. There is no way to change mInternalSignal to false after that so NewAna will never think that it doesn't own graph. If you are referring to NewAna then I agree it will if someone forgets to delete it. I don't want to return the graph object itself because if someone does PeakAna* newana = new PeakAna(OldAna,ConvertPeaksToAna(OldAna))
then the way this constructor works it will think the graph object is external to it and it won't delete it and that will be a memory leak. Which means that the caller will have to remember to always call newana->ForceInternal()
in this situation. Here I called it automatically to prevent such a situation so that's why I don't return the graph object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I meant you could return the NewAna object by value rather than by pointer, not the graph object. I am a bit lost on whether PeakAna owns the graph object or not and under what conditions... Hopefully, this complex (for me) logic is correct and there are no memory leaks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about returning by value but my understanding is that when you return by value it returns a copy of the object not the object created in the function itself. This is not something I want for a large object like PeakAna. Also, if you try to get around this by returning a reference instead then the returned reference is to the object created in the function which has now been invalidated and hence does not exist. This is why I return the pointer to the new object. Is my understanding incorrect and is there a safe way to do this without copying the object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is safe to assume that your compiler implements the return value optimization. Moreover, copy elision is guaranteed when an object is returned directly (as of C++17)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it going to be addressed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
virtual int Init(); | ||
virtual int InitRun(int runNumber); | ||
virtual int Make(); | ||
virtual int Finish(); | ||
virtual void Clear(Option_t* option = ""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you should really use here is the override
keyword rather than virtual
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried using override and compiling with cons and it complains that symbol override is not defined. Can you provide a working example?
Akio, Daniel, we should try to conclude on this PR. As codeowners, could you please review and approve this PR? |
Xilin looked at invariant mass plot and finds no difference in the pi0 peak position and shape using the StFcsWaveformFitMaker in this commit and current StFcsWaveformFitMaker. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now well tested and ready to go.
if(mH1_Baseline!=0) { delete mH1_Baseline; } | ||
if(mF1_SignalFit!=0) { delete mF1_SignalFit; } | ||
if(mF1_BaselineFit!=0) { delete mF1_BaselineFit; } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if(mH1_Baseline!=0) { delete mH1_Baseline; } | |
if(mF1_SignalFit!=0) { delete mF1_SignalFit; } | |
if(mF1_BaselineFit!=0) { delete mF1_BaselineFit; } | |
delete mH1_Baseline; | |
delete mF1_SignalFit; | |
delete mF1_BaselineFit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
//if( peak>0 && radc<=last) peak ^= 1<<rightshift ; | ||
//std::cout << " - SumDep0Mod::Case2|peak:"<<peak << "|radc:"<<radc<<"|last:"<<last << std::endl; | ||
//if( peak!=0 || radc<last) peak = -1 ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please clean up the code and remove the commented code in all modified files? These distract focus from the important lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed what I could. Please let me know if there is more that I missed. Long comments describing what the algorithms do and how they work I left in since I don't know where else to put them.
{ | ||
graph->SetPoint(ipeak, Ana.GetPeak(ipeak).mPeakX, Ana.GetPeak(ipeak).MidPoint() ); | ||
} | ||
PeakAna* NewAna = new PeakAna(Ana,graph); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it going to be addressed?
std::cout << "|P::Graph:"<<mG_Data; | ||
std::cout << "|RangeX:["<<mXRangeMin<<","<<mXRangeMax<<"]"; | ||
std::cout << "|RangeY:["<<mYRangeMin<<","<<mYRangeMax<<"]"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it going to be addressed?
|
||
PeakAna* PeakAna::MeanFilter( Int_t sizeavgs, bool copy ) | ||
{ | ||
if( this->GetData()==0 ){return this;} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use this->
a lot for class members. In most of our code it is usually omitted. I suggest you do the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed what I found. I only left the ones relating to virtual methods.
if( mH1_NPeaksAkio!=0 ){ mH1_NPeaksAkio->Write(); } | ||
if( mH1_NPeaksFilteredAkio!=0 ){ mH1_NPeaksFilteredAkio->Write(); } | ||
if( mH1_PeakTiming!=0 ){mH1_PeakTiming->Write();} | ||
|
||
if( mH2_NPeakvsPeakXdiff!=0 ) {mH2_NPeakvsPeakXdiff->Write();} | ||
if( mH2_NPeakvsPeakYratio!=0) {mH2_NPeakvsPeakYratio->Write();} | ||
if( mH1_VOverlap!=0 ) {mH1_VOverlap->Write();} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tried to make a single call to rootFile->Write();
instead of doing this for individual objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tried it before for other classes and it doesn't always work. It probably has to do with the order in which you create TFile and histograms so I explicitly write them in now. If it is really a big problem I can try to see if rootFile->Write() will work in this instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please try again. ROOT has this notion of a current directory so, when you open a new file it switches to that current directory and it becomes a parent for all TObject-s created after that. The common trick with ROOT is to save the current directory and restore it later when you are done creating your histograms. Since you do it in a Maker this can interfere with other opened files, although I recall you have a switch which disables histogram filling by default. Since no tests were provided for all of this functionality we can only hope that it all works as intended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried with just rootFile->Write() and it didn't work. I understand the issue but I don't know what else to do to avoid this. You are correct that if no test level is called then no output needs to be saved. The test levels are for single file tests and should never be called in production. They are intended to help understand and improve the algorithm rather than for generating generic QA histograms for StFcsWaveformFitMaker.
if( mH1_NPeaksAkio!=0 ){ mH1_NPeaksAkio->Write(); } | ||
if( mH1_NPeaksFilteredAkio!=0 ){ mH1_NPeaksFilteredAkio->Write(); } | ||
if( mH1_PeakTiming!=0 ){mH1_PeakTiming->Write();} | ||
|
||
if( mH2_NPeakvsPeakXdiff!=0 ) {mH2_NPeakvsPeakXdiff->Write();} | ||
if( mH2_NPeakvsPeakYratio!=0) {mH2_NPeakvsPeakYratio->Write();} | ||
if( mH1_VOverlap!=0 ) {mH1_VOverlap->Write();} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please try again. ROOT has this notion of a current directory so, when you open a new file it switches to that current directory and it becomes a parent for all TObject-s created after that. The common trick with ROOT is to save the current directory and restore it later when you are done creating your histograms. Since you do it in a Maker this can interfere with other opened files, although I recall you have a switch which disables histogram filling by default. Since no tests were provided for all of this functionality we can only hope that it all works as intended.
std::cout << "|P::Graph:"<<mG_Data; | ||
std::cout << "|RangeX:["<<mXRangeMin<<","<<mXRangeMax<<"]"; | ||
std::cout << "|RangeY:["<<mYRangeMin<<","<<mYRangeMax<<"]"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not aware of any exclusion to the rule requiring the use of LOG_ stream objects. The advantage is that we can turn off or limit logging to a predefined level. I hope in production only errors and maybe warnings are logged to avoid unnecessary writes to, for example, LOG_DEBUG and LOG_INFO streams
if( mH1_PeakTimingGaus!=0 ) {delete mH1_PeakTimingGaus;} | ||
if( mH1_PeakTimingPuls!=0 ) {delete mH1_PeakTimingPuls;} | ||
if( mH2_PeakTimingCompare!=0 ) {delete mH2_PeakTimingCompare;} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we already discussed this: there is no need to check for the pointer to be non null before deleting the object. Isn't nicer to have less code than more code?
if( mH1_PeakTimingGaus!=0 ) {delete mH1_PeakTimingGaus;} | |
if( mH1_PeakTimingPuls!=0 ) {delete mH1_PeakTimingPuls;} | |
if( mH2_PeakTimingCompare!=0 ) {delete mH2_PeakTimingCompare;} | |
delete mH1_PeakTimingGaus; | |
delete mH1_PeakTimingPuls; | |
delete mH2_PeakTimingCompare; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Sorry I had forgotten about this file :)
This is an update to the StFcsWaveformFitMaker algorithm to improve the peak finding method and energy determination from the raw signal data.