Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooTruthModel.cxx
Go to the documentation of this file.
1/*****************************************************************************
2 * Project: RooFit *
3 * Package: RooFitCore *
4 * @(#)root/roofitcore:$Id$
5 * Authors: *
6 * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu *
7 * DK, David Kirkby, UC Irvine, dkirkby@uci.edu *
8 * *
9 * Copyright (c) 2000-2005, Regents of the University of California *
10 * and Stanford University. All rights reserved. *
11 * *
12 * Redistribution and use in source and binary forms, *
13 * with or without modification, are permitted according to the terms *
14 * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
15 *****************************************************************************/
16
17/**
18\file RooTruthModel.cxx
19\class RooTruthModel
20\ingroup Roofitcore
21
22Implements a RooResolution model that corresponds to a delta function.
23The truth model supports <i>all</i> basis functions because it evaluates each basis function as
24as a RooFormulaVar. The 6 basis functions used in B mixing and decay and 2 basis
25functions used in D mixing have been hand coded for increased execution speed.
26**/
27
28#include <RooTruthModel.h>
29
30#include <RooAbsAnaConvPdf.h>
31#include <RooBatchCompute.h>
32#include <RooGenContext.h>
33
35
36#include <Riostream.h>
37
38#include <TError.h>
39
40#include <algorithm>
41#include <array>
42#include <cmath>
43#include <limits>
44
45namespace {
46
47enum RooTruthBasis {
48 noBasis = 0,
49 expBasisMinus = 1,
50 expBasisSum = 2,
51 expBasisPlus = 3,
52 sinBasisMinus = 11,
53 sinBasisSum = 12,
54 sinBasisPlus = 13,
55 cosBasisMinus = 21,
56 cosBasisSum = 22,
57 cosBasisPlus = 23,
58 linBasisPlus = 33,
59 quadBasisPlus = 43,
60 coshBasisMinus = 51,
61 coshBasisSum = 52,
62 coshBasisPlus = 53,
63 sinhBasisMinus = 61,
64 sinhBasisSum = 62,
65 sinhBasisPlus = 63,
66 genericBasis = 100
67};
68
69enum BasisType {
70 none = 0,
71 expBasis = 1,
72 sinBasis = 2,
73 cosBasis = 3,
74 linBasis = 4,
75 quadBasis = 5,
76 coshBasis = 6,
77 sinhBasis = 7
78};
79
80enum BasisSign { Both = 0, Plus = +1, Minus = -1 };
81
82} // namespace
83
85
86////////////////////////////////////////////////////////////////////////////////
87/// Constructor of a truth resolution model, i.e. a delta function in observable 'xIn'
88
89RooTruthModel::RooTruthModel(const char *name, const char *title, RooAbsRealLValue& xIn) :
90 RooResolutionModel(name,title,xIn)
91{
92}
93
94////////////////////////////////////////////////////////////////////////////////
95/// Return basis code for given basis definition string. Return special
96/// codes for 'known' bases for which compiled definition exists. Return
97/// generic bases code if implementation relies on TFormula interpretation
98/// of basis name
99
101{
102 std::string str = name;
103
104 // Remove whitespaces from the input string
105 str.erase(remove(str.begin(),str.end(),' '),str.end());
106
107 // Check for optimized basis functions
108 if (str == "exp(-@0/@1)") return expBasisPlus ;
109 if (str == "exp(@0/@1)") return expBasisMinus ;
110 if (str == "exp(-abs(@0)/@1)") return expBasisSum ;
111 if (str == "exp(-@0/@1)*sin(@0*@2)") return sinBasisPlus ;
112 if (str == "exp(@0/@1)*sin(@0*@2)") return sinBasisMinus ;
113 if (str == "exp(-abs(@0)/@1)*sin(@0*@2)") return sinBasisSum ;
114 if (str == "exp(-@0/@1)*cos(@0*@2)") return cosBasisPlus ;
115 if (str == "exp(@0/@1)*cos(@0*@2)") return cosBasisMinus ;
116 if (str == "exp(-abs(@0)/@1)*cos(@0*@2)") return cosBasisSum ;
117 if (str == "(@0/@1)*exp(-@0/@1)") return linBasisPlus ;
118 if (str == "(@0/@1)*(@0/@1)*exp(-@0/@1)") return quadBasisPlus ;
119 if (str == "exp(-@0/@1)*cosh(@0*@2/2)") return coshBasisPlus;
120 if (str == "exp(@0/@1)*cosh(@0*@2/2)") return coshBasisMinus;
121 if (str == "exp(-abs(@0)/@1)*cosh(@0*@2/2)") return coshBasisSum;
122 if (str == "exp(-@0/@1)*sinh(@0*@2/2)") return sinhBasisPlus;
123 if (str == "exp(@0/@1)*sinh(@0*@2/2)") return sinhBasisMinus;
124 if (str == "exp(-abs(@0)/@1)*sinh(@0*@2/2)") return sinhBasisSum;
125
126 // Truth model is delta function, i.e. convolution integral is basis
127 // function, therefore we can handle any basis function
128 return genericBasis ;
129}
130
131
132
133////////////////////////////////////////////////////////////////////////////////
134/// Changes associated bases function to 'inBasis'
135
137{
138 // Remove client-server link to old basis
139 if (_basis) {
140 if (_basisCode == genericBasis) {
141 // In the case of a generic basis, we evaluate it directly, so the
142 // basis was a direct server.
144 } else {
145 for (RooAbsArg *basisServer : _basis->servers()) {
146 removeServer(*basisServer);
147 }
148 }
149
150 if (_ownBasis) {
151 delete _basis;
152 }
153 }
154 _ownBasis = false;
155
156 _basisCode = inBasis ? basisCode(inBasis->GetTitle()) : 0;
157
158 // Change basis pointer and update client-server link
159 _basis = inBasis;
160 if (_basis) {
161 if (_basisCode == genericBasis) {
162 // Since we actually evaluate the basis function object, we need to
163 // adjust our client-server links to the basis function here
164 addServer(*_basis, true, false);
165 } else {
166 for (RooAbsArg *basisServer : _basis->servers()) {
167 addServer(*basisServer, true, false);
168 }
169 }
170 }
171}
172
173
174
175////////////////////////////////////////////////////////////////////////////////
176/// Evaluate the truth model: a delta function when used as PDF,
177/// the basis function itself, when convoluted with a basis function.
178
180{
181 // No basis: delta function
182 if (_basisCode == noBasis) {
183 if (x==0) return 1 ;
184 return 0 ;
185 }
186
187 // Generic basis: evaluate basis function object
188 if (_basisCode == genericBasis) {
189 return basis().getVal() ;
190 }
191
192 // Precompiled basis functions
193 BasisType basisType = (BasisType)( (_basisCode == 0) ? 0 : (_basisCode/10) + 1 );
194 BasisSign basisSign = (BasisSign)( _basisCode - 10*(basisType-1) - 2 ) ;
195
196 // Enforce sign compatibility
197 if ((basisSign==Minus && x>0) ||
198 (basisSign==Plus && x<0)) return 0 ;
199
200
201 double tau = (static_cast<RooAbsReal*>(basis().getParameter(1)))->getVal() ;
202 // Return desired basis function
203 switch(basisType) {
204 case expBasis: {
205 return std::exp(-std::abs((double)x)/tau) ;
206 }
207 case sinBasis: {
208 double dm = (static_cast<RooAbsReal*>(basis().getParameter(2)))->getVal() ;
209 return std::exp(-std::abs((double)x)/tau)*std::sin(x*dm) ;
210 }
211 case cosBasis: {
212 double dm = (static_cast<RooAbsReal*>(basis().getParameter(2)))->getVal() ;
213 return std::exp(-std::abs((double)x)/tau)*std::cos(x*dm) ;
214 }
215 case linBasis: {
216 double tscaled = std::abs((double)x)/tau;
217 return std::exp(-tscaled)*tscaled ;
218 }
219 case quadBasis: {
220 double tscaled = std::abs((double)x)/tau;
221 return std::exp(-tscaled)*tscaled*tscaled;
222 }
223 case sinhBasis: {
224 double dg = (static_cast<RooAbsReal*>(basis().getParameter(2)))->getVal() ;
225 return std::exp(-std::abs((double)x)/tau)*std::sinh(x*dg/2) ;
226 }
227 case coshBasis: {
228 double dg = (static_cast<RooAbsReal*>(basis().getParameter(2)))->getVal() ;
229 return std::exp(-std::abs((double)x)/tau)*std::cosh(x*dg/2) ;
230 }
231 default:
232 R__ASSERT(0) ;
233 }
234
235 return 0 ;
236}
237
238
240{
241 auto config = ctx.config(this);
242 auto xVals = ctx.at(x);
243
244 // No basis: delta function
245 if (_basisCode == noBasis) {
247 return;
248 }
249
250 // Generic basis: evaluate basis function object
251 if (_basisCode == genericBasis) {
252 RooBatchCompute::compute(config, RooBatchCompute::Identity, ctx.output(), {ctx.at(&basis())});
253 return;
254 }
255
256 // Precompiled basis functions
257 const BasisType basisType = static_cast<BasisType>((_basisCode == 0) ? 0 : (_basisCode / 10) + 1);
258
259 // Cast the int from the enum to double because we can only pass doubles to
260 // RooBatchCompute at this point.
261 const double basisSign = static_cast<double>((BasisSign)(_basisCode - 10 * (basisType - 1) - 2));
262
263 auto param1 = static_cast<RooAbsReal const *>(basis().getParameter(1));
264 auto param2 = static_cast<RooAbsReal const *>(basis().getParameter(2));
265 auto param1Vals = param1 ? ctx.at(param1) : std::span<const double>{};
266 auto param2Vals = param2 ? ctx.at(param2) : std::span<const double>{};
267
268 // Return desired basis function
269 std::array<double, 1> extraArgs{basisSign};
270 switch (basisType) {
271 case expBasis: {
272 RooBatchCompute::compute(config, RooBatchCompute::TruthModelExpBasis, ctx.output(), {xVals, param1Vals},
273 extraArgs);
274 break;
275 }
276 case sinBasis: {
278 {xVals, param1Vals, param2Vals}, extraArgs);
279 break;
280 }
281 case cosBasis: {
283 {xVals, param1Vals, param2Vals}, extraArgs);
284 break;
285 }
286 case linBasis: {
287 RooBatchCompute::compute(config, RooBatchCompute::TruthModelLinBasis, ctx.output(), {xVals, param1Vals},
288 extraArgs);
289 break;
290 }
291 case quadBasis: {
292 RooBatchCompute::compute(config, RooBatchCompute::TruthModelQuadBasis, ctx.output(), {xVals, param1Vals},
293 extraArgs);
294 break;
295 }
296 case sinhBasis: {
298 {xVals, param1Vals, param2Vals}, extraArgs);
299 break;
300 }
301 case coshBasis: {
303 {xVals, param1Vals, param2Vals}, extraArgs);
304 break;
305 }
306 default: R__ASSERT(0);
307 }
308}
309
310
311////////////////////////////////////////////////////////////////////////////////
312/// Advertise analytical integrals for compiled basis functions and when used
313/// as p.d.f without basis function.
314
315Int_t RooTruthModel::getAnalyticalIntegral(RooArgSet& allVars, RooArgSet& analVars, const char* /*rangeName*/) const
316{
317 switch(_basisCode) {
318
319 // Analytical integration capability of raw PDF
320 case noBasis:
321 if (matchArgs(allVars,analVars,convVar())) return 1 ;
322 break ;
323
324 // Analytical integration capability of convoluted PDF
325 case expBasisPlus:
326 case expBasisMinus:
327 case expBasisSum:
328 case sinBasisPlus:
329 case sinBasisMinus:
330 case sinBasisSum:
331 case cosBasisPlus:
332 case cosBasisMinus:
333 case cosBasisSum:
334 case linBasisPlus:
335 case quadBasisPlus:
336 case sinhBasisPlus:
337 case sinhBasisMinus:
338 case sinhBasisSum:
339 case coshBasisPlus:
340 case coshBasisMinus:
341 case coshBasisSum:
342 if (matchArgs(allVars,analVars,convVar())) return 1 ;
343 break ;
344 }
345
346 return 0 ;
347}
348
349
350namespace {
351
352// From asking WolframAlpha: integrate exp(-x/tau) over x.
353inline double indefiniteIntegralExpBasisPlus(double x, double tau, double /*dm*/)
354{
355 // Restrict to positive x
356 x = std::max(x, 0.0);
357 return -tau * std::exp(-x / tau);
358}
359
360// From asking WolframAlpha: integrate exp(-x/tau)* x / tau over x.
361inline double indefiniteIntegralLinBasisPlus(double x, double tau, double /*dm*/)
362{
363 // Restrict to positive x
364 x = std::max(x, 0.0);
365 return -(tau + x) * std::exp(-x / tau);
366}
367
368// From asking WolframAlpha: integrate exp(-x/tau) * (x / tau)^2 over x.
369inline double indefiniteIntegralQuadBasisPlus(double x, double tau, double /*dm*/)
370{
371 // Restrict to positive x
372 x = std::max(x, 0.0);
373 return -(std::exp(-x / tau) * (2 * tau * tau + x * x + 2 * tau * x)) / tau;
374}
375
376// A common factor that appears in the integrals of the trigonometric
377// function bases (sin and cos).
378inline double commonFactorPlus(double x, double tau, double dm)
379{
380 const double num = tau * std::exp(-x / tau);
381 const double den = dm * dm * tau * tau + 1.0;
382 return num / den;
383}
384
385// A common factor that appears in the integrals of the hyperbolic
386// trigonometric function bases (sinh and cosh).
387inline double commonFactorHyperbolicPlus(double x, double tau, double dm)
388{
389 const double num = 2 * tau * std::exp(-x / tau);
390 const double den = dm * dm * tau * tau - 4.0;
391 return num / den;
392}
393
394// From asking WolframAlpha: integrate exp(-x/tau)*sin(x*m) over x.
395inline double indefiniteIntegralSinBasisPlus(double x, double tau, double dm)
396{
397 // Restrict to positive x
398 x = std::max(x, 0.0);
399 const double fac = commonFactorPlus(x, tau, dm);
400 // Only multiply with the sine term if the coefficient is non zero,
401 // i.e. if x was not infinity. Otherwise, we are evaluating the
402 // sine of infinity, which is NAN!
403 return fac != 0.0 ? fac * (-tau * dm * std::cos(dm * x) - std::sin(dm * x)) : 0.0;
404}
405
406// From asking WolframAlpha: integrate exp(-x/tau)*cos(x*m) over x.
407inline double indefiniteIntegralCosBasisPlus(double x, double tau, double dm)
408{
409 // Restrict to positive x
410 x = std::max(x, 0.0);
411 const double fac = commonFactorPlus(x, tau, dm);
412 return fac != 0.0 ? fac * (tau * dm * std::sin(dm * x) - std::cos(dm * x)) : 0.0;
413}
414
415// From asking WolframAlpha: integrate exp(-x/tau)*sinh(x*m/2) over x.
416inline double indefiniteIntegralSinhBasisPlus(double x, double tau, double dm)
417{
418 // Restrict to positive x
419 x = std::max(x, 0.0);
420 const double fac = commonFactorHyperbolicPlus(x, tau, dm);
421 const double arg = 0.5 * dm * x;
422 return fac != 0.0 ? fac * (tau * dm * std::cosh(arg) - 2. * std::sinh(arg)) : 0.0;
423}
424
425// From asking WolframAlpha: integrate exp(-x/tau)*cosh(x*m/2) over x.
426inline double indefiniteIntegralCoshBasisPlus(double x, double tau, double dm)
427{
428 // Restrict to positive x
429 x = std::max(x, 0.0);
430 const double fac = commonFactorHyperbolicPlus(x, tau, dm);
431 const double arg = 0.5 * dm * x;
432 return fac != 0.0 ? fac * (tau * dm * std::sinh(arg) + 2. * std::cosh(arg)) : 0.0;
433}
434
435// Integrate one of the basis functions. Takes a function that represents the
436// indefinite integral, some parameters, and a flag that indicates whether the
437// basis function is symmetric or antisymmetric. This information is used to
438// evaluate the integrals for the "Minus" and "Sum" cases.
439template <class Function>
440double definiteIntegral(Function indefiniteIntegral, double xmin, double xmax, double tau, double dm,
441 BasisSign basisSign, bool isSymmetric)
442{
443 // Note: isSymmetric == false implies antisymmetric
444 if (tau == 0.0)
445 return isSymmetric ? 1.0 : 0.0;
446 double result = 0.0;
447 if (basisSign != Minus) {
448 result += indefiniteIntegral(xmax, tau, dm) - indefiniteIntegral(xmin, tau, dm);
449 }
450 if (basisSign != Plus) {
451 const double resultMinus = indefiniteIntegral(-xmax, tau, dm) - indefiniteIntegral(-xmin, tau, dm);
452 result += isSymmetric ? -resultMinus : resultMinus;
453 }
454 return result;
455}
456
457} // namespace
458
459////////////////////////////////////////////////////////////////////////////////
460/// Implement analytical integrals when used as p.d.f and for compiled
461/// basis functions.
462
463double RooTruthModel::analyticalIntegral(Int_t code, const char *rangeName) const
464{
465 // Code must be 1
466 R__ASSERT(code == 1);
467
468 // Unconvoluted PDF
469 if (_basisCode == noBasis)
470 return 1;
471
472 // Precompiled basis functions
473 BasisType basisType = (BasisType)((_basisCode == 0) ? 0 : (_basisCode / 10) + 1);
474 BasisSign basisSign = (BasisSign)(_basisCode - 10 * (basisType - 1) - 2);
475
476 const bool needsDm =
477 basisType == sinBasis || basisType == cosBasis || basisType == sinhBasis || basisType == coshBasis;
478
479 const double tau = (static_cast<RooAbsReal *>(basis().getParameter(1)))->getVal();
480 const double dm =
481 needsDm ? (static_cast<RooAbsReal *>(basis().getParameter(2)))->getVal() : std::numeric_limits<Double_t>::quiet_NaN();
482
483 const double xmin = x.min(rangeName);
484 const double xmax = x.max(rangeName);
485
486 auto integrate = [&](auto indefiniteIntegral, bool isSymmetric) {
487 return definiteIntegral(indefiniteIntegral, xmin, xmax, tau, dm, basisSign, isSymmetric);
488 };
489
490 switch (basisType) {
491 case expBasis: return integrate(indefiniteIntegralExpBasisPlus, /*isSymmetric=*/true);
492 case sinBasis: return integrate(indefiniteIntegralSinBasisPlus, /*isSymmetric=*/false);
493 case cosBasis: return integrate(indefiniteIntegralCosBasisPlus, /*isSymmetric=*/true);
494 case linBasis: return integrate(indefiniteIntegralLinBasisPlus, /*isSymmetric=*/false);
495 case quadBasis: return integrate(indefiniteIntegralQuadBasisPlus, /*isSymmetric=*/true);
496 case sinhBasis: return integrate(indefiniteIntegralSinhBasisPlus, /*isSymmetric=*/false);
497 case coshBasis: return integrate(indefiniteIntegralCoshBasisPlus, /*isSymmetric=*/true);
498 default: R__ASSERT(0);
499 }
500
501 R__ASSERT(0);
502 return 0;
503}
504
505
506////////////////////////////////////////////////////////////////////////////////
507
509(const RooAbsAnaConvPdf& convPdf, const RooArgSet &vars, const RooDataSet *prototype,
510 const RooArgSet* auxProto, bool verbose) const
511{
512 RooArgSet forceDirect(convVar()) ;
513 return new RooGenContext(convPdf, vars, prototype, auxProto, verbose, &forceDirect);
514}
515
516
517
518////////////////////////////////////////////////////////////////////////////////
519/// Advertise internal generator for observable x
520
521Int_t RooTruthModel::getGenerator(const RooArgSet& directVars, RooArgSet &generateVars, bool /*staticInitOK*/) const
522{
523 if (matchArgs(directVars,generateVars,x)) return 1 ;
524 return 0 ;
525}
526
527
528
529////////////////////////////////////////////////////////////////////////////////
530/// Implement internal generator for observable x,
531/// x=0 for all events following definition
532/// of delta function
533
535{
536 R__ASSERT(code==1) ;
537 double zero(0.) ;
538 x = zero ;
539 return;
540}
#define ClassImp(name)
Definition Rtypes.h:377
#define R__ASSERT(e)
Definition TError.h:118
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
char name[80]
Definition TGX11.cxx:110
float xmin
float xmax
Double_t(* Function)(Double_t)
Definition Functor.C:4
Base class for PDFs that represent a physics model that can be analytically convolved with a resoluti...
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:77
TIterator Use servers() and begin()
void removeServer(RooAbsArg &server, bool force=false)
Unregister another RooAbsArg as a server to us, ie, declare that we no longer depend on its value and...
void addServer(RooAbsArg &server, bool valueProp=true, bool shapeProp=false, std::size_t refCount=1)
Register another RooAbsArg as a server to us, ie, declare that we depend on it.
Abstract base class for generator contexts of RooAbsPdf objects.
Abstract base class for objects that represent a real value that may appear on the left hand side of ...
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:59
double getVal(const RooArgSet *normalisationSet=nullptr) const
Evaluate object.
Definition RooAbsReal.h:103
bool matchArgs(const RooArgSet &allDeps, RooArgSet &numDeps, const RooArgProxy &a) const
Utility function for use in getAnalyticalIntegral().
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:55
Container class to hold unbinned data.
Definition RooDataSet.h:57
std::span< const double > at(RooAbsArg const *arg, RooAbsArg const *caller=nullptr)
std::span< double > output()
RooBatchCompute::Config config(RooAbsArg const *arg) const
A RooFormulaVar is a generic implementation of a real-valued object, which takes a RooArgList of serv...
RooAbsArg * getParameter(const char *name) const
Return pointer to parameter with given name.
Implements a universal generator context for all RooAbsPdf classes that do not have or need a special...
RooResolutionModel is the base class for PDFs that represent a resolution model that can be convolute...
bool _ownBasis
Flag indicating ownership of _basis.
Int_t _basisCode
Identifier code for selected basis function.
RooAbsRealLValue & convVar() const
Return the convolution variable of the resolution model.
RooFormulaVar * _basis
Basis function convolved with this resolution model.
const RooFormulaVar & basis() const
RooTemplateProxy< RooAbsRealLValue > x
Dependent/convolution variable.
double max(const char *rname=nullptr) const
Query upper limit of range. This requires the payload to be RooAbsRealLValue or derived.
double min(const char *rname=nullptr) const
Query lower limit of range. This requires the payload to be RooAbsRealLValue or derived.
Implements a RooResolution model that corresponds to a delta function.
void doEval(RooFit::EvalContext &) const override
Base function for computing multiple values of a RooAbsReal.
void generateEvent(Int_t code) override
Implement internal generator for observable x, x=0 for all events following definition of delta funct...
double evaluate() const override
Evaluate the truth model: a delta function when used as PDF, the basis function itself,...
Int_t getAnalyticalIntegral(RooArgSet &allVars, RooArgSet &analVars, const char *rangeName=nullptr) const override
Advertise analytical integrals for compiled basis functions and when used as p.d.f without basis func...
Int_t getGenerator(const RooArgSet &directVars, RooArgSet &generateVars, bool staticInitOK=true) const override
Advertise internal generator for observable x.
RooAbsGenContext * modelGenContext(const RooAbsAnaConvPdf &convPdf, const RooArgSet &vars, const RooDataSet *prototype=nullptr, const RooArgSet *auxProto=nullptr, bool verbose=false) const override
double analyticalIntegral(Int_t code, const char *rangeName=nullptr) const override
Implement analytical integrals when used as p.d.f and for compiled basis functions.
Int_t basisCode(const char *name) const override
Return basis code for given basis definition string.
RooTruthModel()=default
void changeBasis(RooFormulaVar *basis) override
Changes associated bases function to 'inBasis'.
const char * GetTitle() const override
Returns title of object.
Definition TNamed.h:48
Double_t x[n]
Definition legend1.C:17
void compute(Config cfg, Computer comp, std::span< double > output, VarSpan vars, ArgSpan extraArgs={})