Files
geant4/source/processes/hadronic/models/util/src/G4PolynomialPDF.cc
T
2017-06-30 10:49:55 +02:00

416 lines
13 KiB
C++

//
// ********************************************************************
// * License and Disclaimer *
// * *
// * The Geant4 software is copyright of the Copyright Holders of *
// * the Geant4 Collaboration. It is provided under the terms and *
// * conditions of the Geant4 Software License, included in the file *
// * LICENSE and available at http://cern.ch/geant4/license . These *
// * include a list of copyright holders. *
// * *
// * Neither the authors of this software system, nor their employing *
// * institutes,nor the agencies providing financial support for this *
// * work make any representation or warranty, express or implied, *
// * regarding this software system or assume any liability for its *
// * use. Please see the license in the file LICENSE and URL above *
// * for the full disclaimer and the limitation of liability. *
// * *
// * This code implementation is the result of the scientific and *
// * technical work of the GEANT4 collaboration. *
// * By using, copying, modifying or distributing the software (or *
// * any work based on the software) you agree to acknowledge its *
// * use in resulting scientific publications, and indicate your *
// * acceptance of all terms of the Geant4 Software license. *
// ********************************************************************
//
//
// -------------------------------------------------------------------
// GEANT4 Class file
//
//
// File name: G4PolynomialPDF
//
// Author: Jason Detwiler (jasondet@gmail.com)
//
// Creation date: Aug 2012
// -------------------------------------------------------------------
#include "G4PolynomialPDF.hh"
#include "Randomize.hh"
using namespace std;
G4PolynomialPDF::G4PolynomialPDF(size_t n, const G4double* coeffs,
G4double x1, G4double x2) :
fX1(x1), fX2(x2), fChanged(true), fTolerance(1.e-8), fVerbose(0)
{
if(coeffs != nullptr) SetCoefficients(n, coeffs);
else if(n > 0) SetNCoefficients(n);
}
G4PolynomialPDF::~G4PolynomialPDF()
{}
void G4PolynomialPDF::SetCoefficient(size_t i, G4double value, bool doSimplify)
{
while(i >= fCoefficients.size()) fCoefficients.push_back(0);
/* Loop checking, 30-Oct-2015, G.Folger */
fCoefficients[i] = value;
fChanged = true;
if(doSimplify) Simplify();
}
void G4PolynomialPDF::SetCoefficients(size_t nCoeffs,
const G4double* coefficients)
{
SetNCoefficients(nCoeffs);
for(size_t i=0; i<GetNCoefficients(); ++i) {
SetCoefficient(i, coefficients[i], false);
}
fChanged = true;
Simplify();
}
void G4PolynomialPDF::Simplify()
{
while(fCoefficients.size() && fCoefficients[fCoefficients.size()-1] == 0) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::Simplify() WARNING: had to pop coefficient "
<< fCoefficients.size()-1 << G4endl;
}
fCoefficients.pop_back();
fChanged = true;
}
}
void G4PolynomialPDF::SetDomain(G4double x1, G4double x2)
{
if(x2 <= x1) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::SetDomain() WARNING: Invalide domain! "
<< "(x1 = " << x1 << ", x2 = " << x2 << ")." << G4endl;
}
return;
}
fX1 = x1;
fX2 = x2;
fChanged = true;
}
void G4PolynomialPDF::Normalize()
{
/// Normalize PDF to 1 over domain fX1 to fX2.
/// Double-check that the highest-order coefficient is non-zero.
while(fCoefficients.size()) { /* Loop checking, 30-Oct-2015, G.Folger */
if(fCoefficients[fCoefficients.size()-1] == 0.0) fCoefficients.pop_back();
else break;
}
G4double x1N = fX1, x2N = fX2;
G4double sum = 0;
for(size_t i=0; i<GetNCoefficients(); ++i) {
sum += GetCoefficient(i)*(x2N - x1N)/G4double(i+1);
x1N*=fX1;
x2N*=fX2;
}
if(sum <= 0) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::Normalize() WARNING: PDF has non-positive area: "
<< sum << G4endl;
Dump();
}
return;
}
for(size_t i=0; i<GetNCoefficients(); ++i) {
SetCoefficient(i, GetCoefficient(i)/sum, false);
}
Simplify();
}
G4double G4PolynomialPDF::Evaluate(G4double x, G4int ddxPower)
{
/// Evaluate f(x)
/// ddxPower = -1: f = CDF
/// ddxPower = 0: f = PDF
/// ddxPower = 1: f = (d/dx) PDF
/// ddxPower = 2: f = (d2/dx2) PDF
if(ddxPower < -1 || ddxPower > 2) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: ddxPower " << ddxPower
<< " not implemented" << G4endl;
}
return 0.0;
}
double f = 0.; // return value
double xN = 1.; // x to the power N
double x1N = 1.; // endpoint x1 to the power N; only used by CDF
for(size_t i=0; i<=GetNCoefficients(); ++i) {
if(ddxPower == -1) { // CDF
if(i>0) f += GetCoefficient(i-1)*(xN - x1N)/i;
x1N *= fX1;
}
else if(ddxPower == 0 && i<GetNCoefficients()) f += GetCoefficient(i)*xN; // PDF
else if(ddxPower == 1) { // (d/dx) PDF
if(i<GetNCoefficients()-1) f += GetCoefficient(i+1)*xN*(i+1);
}
else if(ddxPower == 2) { // (d2/dx2) PDF
if(i<GetNCoefficients()-2) f += GetCoefficient(i+2)*xN*((i+2)*(i+1));
}
xN *= x;
}
return f;
}
G4bool G4PolynomialPDF::HasNegativeMinimum(G4double x1, G4double x2)
{
// ax2 + bx + c = 0
// p': 2ax + b = 0 -> = 0 at min: x_extreme = -b/2a
if(x1 < fX1 || x2 > fX2 || x2 < x1) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::HasNegativeMinimum() WARNING: Invalid range "
<< x1 << " - " << x2 << G4endl;
}
return false;
}
// If flat, then check anywhere.
if(GetNCoefficients() == 1) return (Evaluate(x1) < -fTolerance);
// If linear, or if quadratic with negative second derivative,
// just check the endpoints
if(GetNCoefficients() == 2 ||
(GetNCoefficients() == 3 && GetCoefficient(2) <= 0)) {
return (Evaluate(x1) < -fTolerance) || (Evaluate(x2) < -fTolerance);
}
// If quadratic and second dervative is positive, check at the mininum
if(GetNCoefficients() == 3) {
G4double xMin = -GetCoefficient(1)*0.5/GetCoefficient(2);
if(xMin < x1) xMin = x1;
if(xMin > x2) xMin = x2;
return Evaluate(xMin) < -fTolerance;
}
// Higher-order polynomials: consider any extremum between x1 and x2. If none
// are found, check the endpoints.
G4double extremum = GetX(0, x1, x2, 1);
if(Evaluate(extremum) < -fTolerance) return true;
else if(extremum <= x1+(x2-x1)*fTolerance ||
extremum >= x2-(x2-x1)*fTolerance) return false;
else return
HasNegativeMinimum(x1, extremum) || HasNegativeMinimum(extremum, x2);
}
G4double G4PolynomialPDF::GetRandomX()
{
if(fChanged) {
Normalize();
if(HasNegativeMinimum(fX1, fX2)) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetRandomX() WARNING: PDF has negative values, returning 0..."
<< G4endl;
}
return 0.0;
}
fChanged = false;
}
return EvalInverseCDF(G4UniformRand());
}
G4double G4PolynomialPDF::GetX(G4double p, G4double x1, G4double x2,
G4int ddxPower, G4double guess, G4bool bisect)
{
/// Find a value of X between x1 and x2 at which f(x) = p.
/// ddxPower = -1: f = CDF
/// ddxPower = 0: f = PDF
/// ddxPower = 1: f = (d/dx) PDF
/// Uses the Newton-Raphson method to find the zero of f(x) - p.
/// If not found in range, returns the nearest boundary
// input range checking
if(GetNCoefficients() == 0) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: no PDF defined!" << G4endl;
}
return x2;
}
if(ddxPower < -1 || ddxPower > 1) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: ddxPower " << ddxPower
<< " not implemented" << G4endl;
}
return x2;
}
if(ddxPower == -1 && (p<0 || p>1)) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: p is out of range" << G4endl;
}
return fX2;
}
// check limits
if(x2 <= x1 || x1 < fX1 || x2 > fX2) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: domain must have fX1 <= x1 < x2 <= fX2. "
<< "You sent x1 = " << x1 << ", x2 = " << x2 << "." << G4endl;
}
return x2;
}
// Return x2 for flat lines
if((ddxPower == 0 && GetNCoefficients() == 1) ||
(ddxPower == 1 && GetNCoefficients() == 2)) return x2;
// Solve p = mx + b -> x = (p-b)/m for linear functions
if((ddxPower == -1 && GetNCoefficients() == 1) ||
(ddxPower == 0 && GetNCoefficients() == 2) ||
(ddxPower == 1 && GetNCoefficients() == 3)) {
G4double b = (ddxPower > -1) ? GetCoefficient(ddxPower) : -GetCoefficient(0)*fX1;
G4double slope = GetCoefficient(ddxPower+1); // the highest-order coefficient
if(slope == 0) { // the highest-order coefficient should never be zero if simplified
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: Got slope = 0. "
<< "Did you forget to Simplify()?" << G4endl;
}
return x2;
}
if(ddxPower == 1) slope *= 2.;
G4double value = (p-b)/slope;
if(value < x1) {
return x1;
}
else if(value > x2) {
return x2;
}
else {
return value;
}
}
// Solve quadratic equation for f-p=0 when f is quadratic
if((ddxPower == -1 && GetNCoefficients() == 2) ||
(ddxPower == 0 && GetNCoefficients() == 3) ||
(ddxPower == 1 && GetNCoefficients() == 4)) {
G4double c = -p + ((ddxPower > -1) ? GetCoefficient(ddxPower) : 0);
if(ddxPower == -1) c -= (GetCoefficient(0) + GetCoefficient(1)/2.*fX1)*fX1;
G4double b = GetCoefficient(ddxPower+1);
if(ddxPower == 1) b *= 2.;
G4double a = GetCoefficient(ddxPower+2); // the highest-order coefficient
if(a == 0) { // the highest-order coefficient should never be 0 if simplified
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: Got a = 0. "
<< "Did you forget to Simplify()?" << G4endl;
}
return x2;
}
if(ddxPower == 1) a *= 3;
else if(ddxPower == -1) a *= 0.5;
double sqrtFactor = b*b - 4.*a*c;
if(sqrtFactor < 0) return x2; // quadratic equation has no solution (p not in range of f)
sqrtFactor = sqrt(sqrtFactor)/2./fabs(a);
G4double valueMinus = -b/2./a - sqrtFactor;
if(valueMinus >= x1 && valueMinus <= x2) return valueMinus;
else if(valueMinus > x2) return x2;
G4double valuePlus = -b/2./a + sqrtFactor;
if(valuePlus >= x1 && valuePlus <= x2) return valuePlus;
else if(valuePlus < x1) return x2;
return (x1-valueMinus <= valuePlus-x2) ? x1 : x2;
}
// f is non-trivial, so use Newton-Raphson
// start in the middle if no good guess is provided
if(guess < x1 || guess > x2) guess = (x2+x1)*0.5;
G4double lastChange = 1;
size_t iterations = 0;
while(fabs(lastChange) > fTolerance) { /* Loop checking, 02.11.2015, A.Ribon */
// calculate f and f' simultaneously
G4double f = -p;
G4double dfdx = 0;
G4double xN = 1;
G4double x1N = 1; // only used by CDF
for(size_t i=0; i<=GetNCoefficients(); ++i) {
if(ddxPower == -1) { // CDF
if(i>0) f += GetCoefficient(i-1)*(xN - x1N)/G4double(i);
if(i<GetNCoefficients()) dfdx += GetCoefficient(i)*xN;
x1N *= fX1;
}
else if(ddxPower == 0) { // PDF
if(i<GetNCoefficients()) f += GetCoefficient(i)*xN;
if(i+1<GetNCoefficients()) dfdx += GetCoefficient(i+1)*xN*G4double(i+1);
}
else { // ddxPower == 1: (d/dx) PDF
if(i+1<GetNCoefficients()) f += GetCoefficient(i+1)*xN*G4double(i+1);
if(i+2<GetNCoefficients()) dfdx += GetCoefficient(i+2)*xN*G4double(i+2);
}
xN *= guess;
}
if(f == 0) return guess;
if(dfdx == 0) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: got f != 0 but slope = 0 for ddxPower = "
<< ddxPower << G4endl;
}
return x2;
}
lastChange = - f/dfdx;
if(guess + lastChange < x1) {
lastChange = x1 - guess;
} else if(guess + lastChange > x2) {
lastChange = x2 - guess;
}
guess += lastChange;
lastChange /= (fX2-fX1);
++iterations;
if(iterations > 50) {
if(p!=0) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: got stuck searching for " << p
<< " between " << x1 << " and " << x2 << " with ddxPower = "
<< ddxPower
<< ". Last guess was " << guess << "." << G4endl;
}
}
if(ddxPower==-1 && bisect) {
if(fVerbose > 0) {
G4cout << "G4PolynomialPDF::GetX() WARNING: Bisceting and trying again..."
<< G4endl;
}
return Bisect(p, x1, x2);
}
else return guess;
}
}
return guess;
}
G4double G4PolynomialPDF::Bisect( G4double p, G4double x1, G4double x2 ) {
// Bisect to get 1% precision, then use Newton-Raphson
G4double z = (x2 + x1)/2.0; // [x1 z x2]
if((x2 - x1)/(fX2 - fX1) < 0.01) return GetX(p, fX1, fX2, -1, z, false);
G4double fz = Evaluate(z, -1) - p;
if(fz < 0) return Bisect(p, z, x2); // [z x2]
return Bisect(p, x1, z); // [x1 z]
}
void G4PolynomialPDF::Dump()
{
G4cout << "G4PolynomialPDF::Dump() - PDF(x) = ";
for(size_t i=0; i<GetNCoefficients(); i++) {
if(i>0) G4cout << " + ";
G4cout << GetCoefficient(i);
if(i>0) G4cout << "*x";
if(i>1) G4cout << "^" << i;
}
G4cout << G4endl;
G4cout << "G4PolynomialPDF::Dump() - Interval: " << fX1 << " <= x < "
<< fX2 << G4endl;
}