Geant4 11.1.1
Toolkit for the simulation of the passage of particles through matter
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
G4PolynomialPDF.cc
Go to the documentation of this file.
1//
2// ********************************************************************
3// * License and Disclaimer *
4// * *
5// * The Geant4 software is copyright of the Copyright Holders of *
6// * the Geant4 Collaboration. It is provided under the terms and *
7// * conditions of the Geant4 Software License, included in the file *
8// * LICENSE and available at http://cern.ch/geant4/license . These *
9// * include a list of copyright holders. *
10// * *
11// * Neither the authors of this software system, nor their employing *
12// * institutes,nor the agencies providing financial support for this *
13// * work make any representation or warranty, express or implied, *
14// * regarding this software system or assume any liability for its *
15// * use. Please see the license in the file LICENSE and URL above *
16// * for the full disclaimer and the limitation of liability. *
17// * *
18// * This code implementation is the result of the scientific and *
19// * technical work of the GEANT4 collaboration. *
20// * By using, copying, modifying or distributing the software (or *
21// * any work based on the software) you agree to acknowledge its *
22// * use in resulting scientific publications, and indicate your *
23// * acceptance of all terms of the Geant4 Software license. *
24// ********************************************************************
25//
26//
27// -------------------------------------------------------------------
28// GEANT4 Class file
29//
30//
31// File name: G4PolynomialPDF
32//
33// Author: Jason Detwiler (jasondet@gmail.com)
34//
35// Creation date: Aug 2012
36// -------------------------------------------------------------------
37
38#include "G4PolynomialPDF.hh"
39#include "Randomize.hh"
40
41using namespace std;
42
44 G4double x1, G4double x2) :
45 fX1(x1), fX2(x2), fChanged(true), fTolerance(1.e-8), fVerbose(0)
46{
47 if(coeffs != nullptr) SetCoefficients(n, coeffs);
48 else if(n > 0) SetNCoefficients(n);
49}
50
52{}
53
54void G4PolynomialPDF::SetCoefficient(size_t i, G4double value, bool doSimplify)
55{
56 while(i >= fCoefficients.size()) fCoefficients.push_back(0);
57 /* Loop checking, 30-Oct-2015, G.Folger */
58 fCoefficients[i] = value;
59 fChanged = true;
60 if(doSimplify) Simplify();
61}
62
64 const G4double* coefficients)
65{
66 SetNCoefficients(nCoeffs);
67 for(size_t i=0; i<GetNCoefficients(); ++i) {
68 SetCoefficient(i, coefficients[i], false);
69 }
70 fChanged = true;
71 Simplify();
72}
73
75{
76 while(fCoefficients.size() && fCoefficients[fCoefficients.size()-1] == 0) {
77 if(fVerbose > 0) {
78 G4cout << "G4PolynomialPDF::Simplify() WARNING: had to pop coefficient "
79 << fCoefficients.size()-1 << G4endl;
80 }
81 fCoefficients.pop_back();
82 fChanged = true;
83 }
84}
85
87{
88 if(x2 <= x1) {
89 if(fVerbose > 0) {
90 G4cout << "G4PolynomialPDF::SetDomain() WARNING: Invalid domain! "
91 << "(x1 = " << x1 << ", x2 = " << x2 << ")." << G4endl;
92 }
93 return;
94 }
95 fX1 = x1;
96 fX2 = x2;
97 fChanged = true;
98}
99
101{
102 /// Normalize PDF to 1 over domain fX1 to fX2.
103 /// Double-check that the highest-order coefficient is non-zero.
104 while(fCoefficients.size()) { /* Loop checking, 30-Oct-2015, G.Folger */
105 if(fCoefficients[fCoefficients.size()-1] == 0.0) fCoefficients.pop_back();
106 else break;
107 }
108
109 G4double x1N = fX1, x2N = fX2;
110 G4double sum = 0;
111 for(size_t i=0; i<GetNCoefficients(); ++i) {
112 sum += GetCoefficient(i)*(x2N - x1N)/G4double(i+1);
113 x1N*=fX1;
114 x2N*=fX2;
115 }
116 if(sum <= 0) {
117 if(fVerbose > 0) {
118 G4cout << "G4PolynomialPDF::Normalize() WARNING: PDF has non-positive area: "
119 << sum << G4endl;
120 Dump();
121 }
122 return;
123 }
124
125 for(size_t i=0; i<GetNCoefficients(); ++i) {
126 SetCoefficient(i, GetCoefficient(i)/sum, false);
127 }
128 Simplify();
129}
130
132{
133 /// Evaluate f(x)
134 /// ddxPower = -1: f = CDF
135 /// ddxPower = 0: f = PDF
136 /// ddxPower = 1: f = (d/dx) PDF
137 /// ddxPower = 2: f = (d2/dx2) PDF
138 if(ddxPower < -1 || ddxPower > 2) {
139 if(fVerbose > 0) {
140 G4cout << "G4PolynomialPDF::GetX() WARNING: ddxPower " << ddxPower
141 << " not implemented" << G4endl;
142 }
143 return 0.0;
144 }
145
146 double f = 0.; // return value
147 double xN = 1.; // x to the power N
148 double x1N = 1.; // endpoint x1 to the power N; only used by CDF
149 for(size_t i=0; i<=GetNCoefficients(); ++i) {
150 if(ddxPower == -1) { // CDF
151 if(i>0) f += GetCoefficient(i-1)*(xN - x1N)/i;
152 x1N *= fX1;
153 }
154 else if(ddxPower == 0 && i<GetNCoefficients()) f += GetCoefficient(i)*xN; // PDF
155 else if(ddxPower == 1) { // (d/dx) PDF
156 if(i<GetNCoefficients()-1) f += GetCoefficient(i+1)*xN*(i+1);
157 }
158 else if(ddxPower == 2) { // (d2/dx2) PDF
159 if(i<GetNCoefficients()-2) f += GetCoefficient(i+2)*xN*((i+2)*(i+1));
160 }
161 xN *= x;
162 }
163 return f;
164}
165
167{
168 // ax2 + bx + c = 0
169 // p': 2ax + b = 0 -> = 0 at min: x_extreme = -b/2a
170
171 if(x1 < fX1 || x2 > fX2 || x2 < x1) {
172 if(fVerbose > 0) {
173 G4cout << "G4PolynomialPDF::HasNegativeMinimum() WARNING: Invalid range "
174 << x1 << " - " << x2 << G4endl;
175 }
176 return false;
177 }
178
179 // If flat, then check anywhere.
180 if(GetNCoefficients() == 1) return (Evaluate(x1) < -fTolerance);
181
182 // If linear, or if quadratic with negative second derivative,
183 // just check the endpoints
184 if(GetNCoefficients() == 2 ||
185 (GetNCoefficients() == 3 && GetCoefficient(2) <= 0)) {
186 return (Evaluate(x1) < -fTolerance) || (Evaluate(x2) < -fTolerance);
187 }
188
189 // If quadratic and second dervative is positive, check at the mininum
190 if(GetNCoefficients() == 3) {
191 G4double xMin = -GetCoefficient(1)*0.5/GetCoefficient(2);
192 if(xMin < x1) xMin = x1;
193 if(xMin > x2) xMin = x2;
194 return Evaluate(xMin) < -fTolerance;
195 }
196
197 // Higher-order polynomials: consider any extremum between x1 and x2. If none
198 // are found, check the endpoints.
199 G4double extremum = GetX(0, x1, x2, 1);
200 if(Evaluate(extremum) < -fTolerance) return true;
201 else if(extremum <= x1+(x2-x1)*fTolerance ||
202 extremum >= x2-(x2-x1)*fTolerance) return false;
203 else return
204 HasNegativeMinimum(x1, extremum) || HasNegativeMinimum(extremum, x2);
205}
206
208{
209 if(fChanged) {
210 Normalize();
212 if(fVerbose > 0) {
213 G4cout << "G4PolynomialPDF::GetRandomX() WARNING: PDF has negative values, returning 0..."
214 << G4endl;
215 }
216 return 0.0;
217 }
218 fChanged = false;
219 }
221}
222
224 G4int ddxPower, G4double guess, G4bool bisect)
225{
226 /// Find a value of X between x1 and x2 at which f(x) = p.
227 /// ddxPower = -1: f = CDF
228 /// ddxPower = 0: f = PDF
229 /// ddxPower = 1: f = (d/dx) PDF
230 /// Uses the Newton-Raphson method to find the zero of f(x) - p.
231 /// If not found in range, returns the nearest boundary
232
233 // input range checking
234 if(GetNCoefficients() == 0) {
235 if(fVerbose > 0) {
236 G4cout << "G4PolynomialPDF::GetX() WARNING: no PDF defined!" << G4endl;
237 }
238 return x2;
239 }
240 if(ddxPower < -1 || ddxPower > 1) {
241 if(fVerbose > 0) {
242 G4cout << "G4PolynomialPDF::GetX() WARNING: ddxPower " << ddxPower
243 << " not implemented" << G4endl;
244 }
245 return x2;
246 }
247 if(ddxPower == -1 && (p<0 || p>1)) {
248 if(fVerbose > 0) {
249 G4cout << "G4PolynomialPDF::GetX() WARNING: p is out of range" << G4endl;
250 }
251 return fX2;
252 }
253
254 // check limits
255 if(x2 <= x1 || x1 < fX1 || x2 > fX2) {
256 if(fVerbose > 0) {
257 G4cout << "G4PolynomialPDF::GetX() WARNING: domain must have fX1 <= x1 < x2 <= fX2. "
258 << "You sent x1 = " << x1 << ", x2 = " << x2 << "." << G4endl;
259 }
260 return x2;
261 }
262
263 // Return x2 for flat lines
264 if((ddxPower == 0 && GetNCoefficients() == 1) ||
265 (ddxPower == 1 && GetNCoefficients() == 2)) return x2;
266
267 // Solve p = mx + b -> x = (p-b)/m for linear functions
268 if((ddxPower == -1 && GetNCoefficients() == 1) ||
269 (ddxPower == 0 && GetNCoefficients() == 2) ||
270 (ddxPower == 1 && GetNCoefficients() == 3)) {
271 G4double b = (ddxPower > -1) ? GetCoefficient(ddxPower) : -GetCoefficient(0)*fX1;
272 G4double slope = GetCoefficient(ddxPower+1); // the highest-order coefficient
273 if(slope == 0) { // the highest-order coefficient should never be zero if simplified
274 if(fVerbose > 0) {
275 G4cout << "G4PolynomialPDF::GetX() WARNING: Got slope = 0. "
276 << "Did you forget to Simplify()?" << G4endl;
277 }
278 return x2;
279 }
280 if(ddxPower == 1) slope *= 2.;
281 G4double value = (p-b)/slope;
282 if(value < x1) {
283 return x1;
284 }
285 else if(value > x2) {
286 return x2;
287 }
288 else {
289 return value;
290 }
291 }
292
293 // Solve quadratic equation for f-p=0 when f is quadratic
294 if((ddxPower == -1 && GetNCoefficients() == 2) ||
295 (ddxPower == 0 && GetNCoefficients() == 3) ||
296 (ddxPower == 1 && GetNCoefficients() == 4)) {
297 G4double c = -p + ((ddxPower > -1) ? GetCoefficient(ddxPower) : 0);
298 if(ddxPower == -1) c -= (GetCoefficient(0) + GetCoefficient(1)/2.*fX1)*fX1;
299 G4double b = GetCoefficient(ddxPower+1);
300 if(ddxPower == 1) b *= 2.;
301 G4double a = GetCoefficient(ddxPower+2); // the highest-order coefficient
302 if(a == 0) { // the highest-order coefficient should never be 0 if simplified
303 if(fVerbose > 0) {
304 G4cout << "G4PolynomialPDF::GetX() WARNING: Got a = 0. "
305 << "Did you forget to Simplify()?" << G4endl;
306 }
307 return x2;
308 }
309 if(ddxPower == 1) a *= 3;
310 else if(ddxPower == -1) a *= 0.5;
311 double sqrtFactor = b*b - 4.*a*c;
312 if(sqrtFactor < 0) return x2; // quadratic equation has no solution (p not in range of f)
313 sqrtFactor = sqrt(sqrtFactor)/2./fabs(a);
314 G4double valueMinus = -b/2./a - sqrtFactor;
315 if(valueMinus >= x1 && valueMinus <= x2) return valueMinus;
316 else if(valueMinus > x2) return x2;
317 G4double valuePlus = -b/2./a + sqrtFactor;
318 if(valuePlus >= x1 && valuePlus <= x2) return valuePlus;
319 else if(valuePlus < x1) return x2;
320 return (x1-valueMinus <= valuePlus-x2) ? x1 : x2;
321 }
322
323 // f is non-trivial, so use Newton-Raphson
324 // start in the middle if no good guess is provided
325 if(guess < x1 || guess > x2) guess = (x2+x1)*0.5;
326 G4double lastChange = 1;
327 size_t iterations = 0;
328 while(fabs(lastChange) > fTolerance) { /* Loop checking, 02.11.2015, A.Ribon */
329 // calculate f and f' simultaneously
330 G4double f = -p;
331 G4double dfdx = 0;
332 G4double xN = 1;
333 G4double x1N = 1; // only used by CDF
334 for(size_t i=0; i<=GetNCoefficients(); ++i) {
335 if(ddxPower == -1) { // CDF
336 if(i>0) f += GetCoefficient(i-1)*(xN - x1N)/G4double(i);
337 if(i<GetNCoefficients()) dfdx += GetCoefficient(i)*xN;
338 x1N *= fX1;
339 }
340 else if(ddxPower == 0) { // PDF
341 if(i<GetNCoefficients()) f += GetCoefficient(i)*xN;
342 if(i+1<GetNCoefficients()) dfdx += GetCoefficient(i+1)*xN*G4double(i+1);
343 }
344 else { // ddxPower == 1: (d/dx) PDF
345 if(i+1<GetNCoefficients()) f += GetCoefficient(i+1)*xN*G4double(i+1);
346 if(i+2<GetNCoefficients()) dfdx += GetCoefficient(i+2)*xN*G4double(i+2);
347 }
348 xN *= guess;
349 }
350 if(f == 0) return guess;
351 if(dfdx == 0) {
352 if(fVerbose > 0) {
353 G4cout << "G4PolynomialPDF::GetX() WARNING: got f != 0 but slope = 0 for ddxPower = "
354 << ddxPower << G4endl;
355 }
356 return x2;
357 }
358 lastChange = - f/dfdx;
359
360 if(guess + lastChange < x1) {
361 lastChange = x1 - guess;
362 } else if(guess + lastChange > x2) {
363 lastChange = x2 - guess;
364 }
365
366 guess += lastChange;
367 lastChange /= (fX2-fX1);
368
369 ++iterations;
370 if(iterations > 50) {
371 if(p!=0) {
372 if(fVerbose > 0) {
373 G4cout << "G4PolynomialPDF::GetX() WARNING: got stuck searching for " << p
374 << " between " << x1 << " and " << x2 << " with ddxPower = "
375 << ddxPower
376 << ". Last guess was " << guess << "." << G4endl;
377 }
378 }
379 if(ddxPower==-1 && bisect) {
380 if(fVerbose > 0) {
381 G4cout << "G4PolynomialPDF::GetX() WARNING: Biseting and trying again..."
382 << G4endl;
383 }
384 return Bisect(p, x1, x2);
385 }
386 else return guess;
387 }
388 }
389 return guess;
390}
391
393 // Bisect to get 1% precision, then use Newton-Raphson
394 G4double z = (x2 + x1)/2.0; // [x1 z x2]
395 if((x2 - x1)/(fX2 - fX1) < 0.01) return GetX(p, fX1, fX2, -1, z, false);
396 G4double fz = Evaluate(z, -1) - p;
397 if(fz < 0) return Bisect(p, z, x2); // [z x2]
398 return Bisect(p, x1, z); // [x1 z]
399}
400
402{
403 G4cout << "G4PolynomialPDF::Dump() - PDF(x) = ";
404 for(size_t i=0; i<GetNCoefficients(); i++) {
405 if(i>0) G4cout << " + ";
407 if(i>0) G4cout << "*x";
408 if(i>1) G4cout << "^" << i;
409 }
410 G4cout << G4endl;
411 G4cout << "G4PolynomialPDF::Dump() - Interval: " << fX1 << " <= x < "
412 << fX2 << G4endl;
413}
414
415
double G4double
Definition: G4Types.hh:83
bool G4bool
Definition: G4Types.hh:86
int G4int
Definition: G4Types.hh:85
#define G4endl
Definition: G4ios.hh:57
G4GLOB_DLL std::ostream G4cout
#define G4UniformRand()
Definition: Randomize.hh:52
void SetNCoefficients(size_t n)
size_t GetNCoefficients() const
G4double Evaluate(G4double x, G4int ddxPower=0)
void SetCoefficient(size_t i, G4double value, bool doSimplify)
G4double GetCoefficient(size_t i) const
std::vector< G4double > fCoefficients
G4PolynomialPDF(size_t n=0, const double *coeffs=nullptr, G4double x1=0, G4double x2=1)
G4bool HasNegativeMinimum(G4double x1, G4double x2)
G4double GetX(G4double p, G4double x1, G4double x2, G4int ddxPower=0, G4double guess=1.e99, G4bool bisect=true)
void SetDomain(G4double x1, G4double x2)
G4double EvalInverseCDF(G4double p)
void SetCoefficients(const std::vector< G4double > &v)
G4double Bisect(G4double p, G4double x1, G4double x2)
G4double GetRandomX()