3333// /
3434// / Classes derived from RooResolutionModel implement
3535// / \f[
36- // / R_k(x,\bar{b},\bar{c}) = \int \mathrm{basis}_k(x', \bar{b}) \cdot \mathrm{resModel}(x-x',\bar{c}) \; \mathrm{d}x',
36+ // / R_k(x,\bar{b},\bar{c}) = \int \mathrm{basis}_k(x', \bar{b}) \cdot \mathrm{resModel}(x-x',\bar{c}) \;
37+ // / \mathrm{d}x',
3738// / \f]
3839// /
3940// / which RooAbsAnaConvPdf uses to construct the pdf for [ Phys (x) R ] :
5960// / advertise the coefficient integration capabilities and implement them respectively.
6061// / Please see RooAbsPdf for additional details. Advertised analytical integrals must be
6162// / valid for all coefficients.
63+ // /
64+ // / ### The resolution model is a configuration object, not a graph node
65+ // /
66+ // / The resolution model passed to the constructor is **not** a node of the
67+ // / computation graph of the RooAbsAnaConvPdf. It is never evaluated directly;
68+ // / it only serves as a *configuration* object that specifies which resolution
69+ // / model should be convolved with the basis functions. From it, the
70+ // / RooAbsAnaConvPdf builds its own internal \f$ \mathrm{basis}_k \otimes
71+ // / \mathrm{resModel} \f$ convolution objects (one per declared basis function),
72+ // / and it is *those* convolutions that are the actual value servers of the pdf
73+ // / and that get evaluated.
74+ // /
75+ // / Consequently, the resolution model itself is not a server of the
76+ // / RooAbsAnaConvPdf. It remains accessible via getModel() (for example for
77+ // / serialization), but it does not appear in the pdf's `servers()` list, in
78+ // / `getParameters()` / `getVariables()`, or in the printed computation graph.
79+ // /
80+ // / \note **Behavior change in ROOT 6.42:** in earlier releases the resolution
81+ // / model was kept as a (non-value, non-shape) server of the RooAbsAnaConvPdf.
82+ // / As a side effect, importing a RooAbsAnaConvPdf into a RooWorkspace also
83+ // / dragged the original resolution model into the workspace (and into HS3/JSON
84+ // / exports), even though it played no role in the computation. As of ROOT 6.42
85+ // / this is no longer the case: a resolution model that is only used as the
86+ // / configuration of a RooAbsAnaConvPdf is not imported into the workspace on
87+ // / its own anymore.
88+ // /
89+ // / Objects written with older ROOT versions are read back correctly via schema
90+ // / evolution: the resolution model is dropped as a server, so the *pdf's*
91+ // / computation graph is the same as for a freshly constructed one. Note,
92+ // / however, that if such an old file already stored the resolution model as a
93+ // / standalone RooWorkspace member (which used to happen on import), that member
94+ // / is *not* retroactively removed from the workspace on read-back -- it simply
95+ // / is no longer wired into the RooAbsAnaConvPdf. Only newly created workspaces
96+ // / are guaranteed to be free of the standalone resolution model.
6297
6398#include " RooAbsAnaConvPdf.h"
6499
@@ -93,33 +128,37 @@ RooAbsAnaConvPdf::RooAbsAnaConvPdf() :
93128// / Constructor. The supplied resolution model must be constructed with the same
94129// / convoluted variable as this physics model ('convVar')
95130
96- RooAbsAnaConvPdf::RooAbsAnaConvPdf (const char *name, const char *title,
97- const RooResolutionModel& model, RooRealVar& cVar) :
98- RooAbsPdf(name,title), _isCopy(false ),
99- _model(" !model" ," Original resolution model" ,this ,(RooResolutionModel&)model,false,false),
100- _convVar(" !convVar" ," Convolution variable" ,this ,cVar,false ,false ),
101- _convSet(" !convSet" ," Set of resModel X basisFunc convolutions" ,this ),
102- _coefNormMgr(this ,10 ),
103- _codeReg(10 )
131+ RooAbsAnaConvPdf::RooAbsAnaConvPdf (const char *name, const char *title, const RooResolutionModel &model,
132+ RooRealVar &cVar)
133+ : RooAbsPdf(name, title),
134+ _isCopy(false ),
135+ _model{static_cast <RooResolutionModel *>(model.clone (model.GetName ()))},
136+ _ownModel{true },
137+ _convVar (" !convVar" , " Convolution variable" , this , cVar, false , false ),
138+ _convSet(" !convSet" , " Set of resModel X basisFunc convolutions" , this ),
139+ _coefNormMgr(this , 10 ),
140+ _codeReg(10 )
104141{
105- _model. absArg () ->setAttribute (" NOCacheAndTrack" ) ;
142+ _model->setAttribute (" NOCacheAndTrack" );
106143}
107144
108145
109146
110147// //////////////////////////////////////////////////////////////////////////////
111148
112- RooAbsAnaConvPdf::RooAbsAnaConvPdf (const RooAbsAnaConvPdf& other, const char * name) :
113- RooAbsPdf(other,name), _isCopy(true ),
114- _model(" !model" ,this ,other._model),
115- _convVar(" !convVar" ,this ,other._convVar),
116- _convSet(" !convSet" ,this ,other._convSet),
117- _coefNormMgr(other._coefNormMgr,this ),
118- _codeReg(other._codeReg)
149+ RooAbsAnaConvPdf::RooAbsAnaConvPdf (const RooAbsAnaConvPdf &other, const char *name)
150+ : RooAbsPdf(other, name),
151+ _isCopy(true ),
152+ _model{other._model ? static_cast <RooResolutionModel *>(other._model ->clone (other._model ->GetName ())) : nullptr },
153+ _ownModel{true },
154+ _convVar (" !convVar" , this , other._convVar),
155+ _convSet(" !convSet" , this , other._convSet),
156+ _coefNormMgr(other._coefNormMgr, this ),
157+ _codeReg(other._codeReg)
119158{
120159 // Copy constructor
121- if (_model. absArg () ) {
122- _model. absArg () ->setAttribute (" NOCacheAndTrack" ) ;
160+ if (_model) {
161+ _model->setAttribute (" NOCacheAndTrack" );
123162 }
124163 other._basisList .snapshot (_basisList);
125164}
@@ -140,8 +179,53 @@ RooAbsAnaConvPdf::~RooAbsAnaConvPdf()
140179 }
141180 }
142181
182+ if (_ownModel) {
183+ delete _model;
184+ }
143185}
144186
187+ // //////////////////////////////////////////////////////////////////////////////
188+ // / Forward server redirection to the original resolution model. The resolution
189+ // / model is not a server of this pdf (it is only used to build the convolutions
190+ // / and for generation), so it is not redirected by the standard machinery. We
191+ // / keep its servers in sync here, analogous to RooResolutionModel forwarding
192+ // / the redirection to its basis function.
193+
194+ bool RooAbsAnaConvPdf::redirectServersHook (const RooAbsCollection &newServerList, bool mustReplaceAll, bool nameChange,
195+ bool isRecursive)
196+ {
197+ if (_model) {
198+ // Pass mustReplaceAll=false: the model may legitimately reference servers
199+ // that are not part of this particular redirection, and it is never
200+ // evaluated as part of the computation graph anyway.
201+ _model->redirectServers (newServerList, false , nameChange);
202+ }
203+
204+ return RooAbsPdf::redirectServersHook (newServerList, mustReplaceAll, nameChange, isRecursive);
205+ }
206+
207+ // //////////////////////////////////////////////////////////////////////////////
208+ // / Second-pass schema evolution, called by the RooWorkspace after reading.
209+ // /
210+ // / In class version <= 3, the original resolution model was held in a
211+ // / RooRealProxy and was therefore a (non-value, non-shape) server of this pdf.
212+ // / The schema evolution read rule (see LinkDef.h) already recovered the model
213+ // / pointer into _model, but the stale server link is also restored from the
214+ // / file via the RooAbsArg server list. We remove it here, once the full graph
215+ // / is live, so that an object read from an old file has the same clean server
216+ // / structure as a freshly constructed one. This is safe because there is no
217+ // / longer a proxy that could resurrect the server link on copy.
218+
219+ void RooAbsAnaConvPdf::ioStreamerPass2 ()
220+ {
221+ RooAbsPdf::ioStreamerPass2 ();
222+
223+ // Force removal (the link may have been added with a reference count > 1):
224+ // the model must be completely severed from the server list.
225+ if (_model && findServer (*_model)) {
226+ removeServer (*_model, true );
227+ }
228+ }
145229
146230// //////////////////////////////////////////////////////////////////////////////
147231// / Declare a basis function for use in this physics model. The string expression
@@ -165,11 +249,10 @@ Int_t RooAbsAnaConvPdf::declareBasis(const char* expression, const RooArgList& p
165249 }
166250
167251 // Resolution model must support declared basis
168- if (!(static_cast <RooResolutionModel*>(_model.absArg ()))->isBasisSupported (expression)) {
169- coutE (InputArguments) << " RooAbsAnaConvPdf::declareBasis(" << GetName () << " ): resolution model "
170- << _model.absArg ()->GetName ()
171- << " doesn't support basis function " << expression << std::endl ;
172- return -1 ;
252+ if (!_model->isBasisSupported (expression)) {
253+ coutE (InputArguments) << " RooAbsAnaConvPdf::declareBasis(" << GetName () << " ): resolution model "
254+ << _model->GetName () << " doesn't support basis function " << expression << std::endl;
255+ return -1 ;
173256 }
174257
175258 // Instantiate basis function
@@ -188,7 +271,7 @@ Int_t RooAbsAnaConvPdf::declareBasis(const char* expression, const RooArgList& p
188271 basisFunc->setOperMode (operMode ()) ;
189272
190273 // Instantiate resModel x basisFunc convolution
191- RooAbsReal* conv = static_cast <RooResolutionModel*>( _model. absArg ()) ->convolution (basisFunc.get (),this );
274+ RooAbsReal * conv = _model->convolution (basisFunc.get (), this );
192275 _basisList.addOwned (std::move (basisFunc));
193276 if (!conv) {
194277 coutE (InputArguments) << " RooAbsAnaConvPdf::declareBasis(" << GetName () << " ): unable to construct convolution with basis function '"
@@ -229,14 +312,15 @@ bool RooAbsAnaConvPdf::changeModel(const RooResolutionModel& newModel)
229312 _convSet.removeAll () ;
230313 _convSet.addOwned (std::move (newConvSet));
231314
232- const std::string attrib = std::string (" ORIGNAME:" ) + _model->GetName ();
233- const bool oldAttrib = newModel.getAttribute (attrib.c_str ());
234- const_cast <RooResolutionModel&>(newModel).setAttribute (attrib.c_str ());
235-
236- redirectServers (RooArgSet{newModel}, false , true );
237-
238- // reset temporary attribute for server redirection
239- const_cast <RooResolutionModel&>(newModel).setAttribute (attrib.c_str (), oldAttrib);
315+ // Replace the stored original resolution model. Since it is not a server of
316+ // this pdf, it cannot (and need not) be redirected via redirectServers(): we
317+ // simply own a fresh clone of the new model.
318+ if (_ownModel) {
319+ delete _model;
320+ }
321+ _model = static_cast <RooResolutionModel *>(newModel.clone (newModel.GetName ()));
322+ _ownModel = true ;
323+ _model->setAttribute (" NOCacheAndTrack" );
240324
241325 return false ;
242326}
@@ -255,7 +339,7 @@ RooAbsGenContext* RooAbsAnaConvPdf::genContext(const RooArgSet &vars, const RooD
255339 const RooArgSet* auxProto, bool verbose) const
256340{
257341 // Check if the resolution model specifies a special context to be used.
258- RooResolutionModel* conv = dynamic_cast <RooResolutionModel*>( _model. absArg ()) ;
342+ RooResolutionModel * conv = _model;
259343 assert (conv);
260344
261345 std::unique_ptr<RooArgSet> modelDep {_model->getObservables (&vars)};
@@ -297,9 +381,8 @@ bool RooAbsAnaConvPdf::isDirectGenSafe(const RooAbsArg& arg) const
297381{
298382
299383 // All direct generation of convolution arg if model is truth model
300- if (!TString (_convVar.absArg ()->GetName ()).CompareTo (arg.GetName ()) &&
301- dynamic_cast <RooTruthModel*>(_model.absArg ())) {
302- return true ;
384+ if (!TString (_convVar.absArg ()->GetName ()).CompareTo (arg.GetName ()) && dynamic_cast <RooTruthModel *>(_model)) {
385+ return true ;
303386 }
304387
305388 return RooAbsPdf::isDirectGenSafe (arg) ;
0 commit comments