ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
WeibullDistribution.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #ifdef _MSC_VER
9 // To get rid of the really annoying warnings about unsafe methods
10 #pragma warning(disable : 4996)
11 #endif
12 
13 #include <WeibullDistribution.h>
14 
15 // local
16 #include <CVMath.h>
17 #include <GenericCloud.h>
18 #include <ScalarField.h>
19 #include <ScalarFieldTools.h>
20 
21 using namespace cloudViewer;
22 
23 // GAMMA function
24 static double Gamma_cc(double x) {
25  static const double g[25] = {1.0,
26  0.5772156649015329,
27  -0.6558780715202538,
28  -0.420026350340952e-1,
29  0.1665386113822915,
30  -0.421977345555443e-1,
31  -0.9621971527877e-2,
32  0.7218943246663e-2,
33  -0.11651675918591e-2,
34  -0.2152416741149e-3,
35  0.1280502823882e-3,
36  -0.201348547807e-4,
37  -0.12504934821e-5,
38  0.1133027232e-5,
39  -0.2056338417e-6,
40  0.6116095e-8,
41  0.50020075e-8,
42  -0.11812746e-8,
43  0.1043427e-9,
44  0.77823e-11,
45  -0.36968e-11,
46  0.51e-12,
47  -0.206e-13,
48  -0.54e-14,
49  0.14e-14};
50 
51  if (x > 171.0) {
53  }
54 
55  if (x == static_cast<int>(x)) {
56  if (x > 0.0) // use factorial
57  {
58  double ga = 1.0;
59  for (int i = 2; i < x; i++) ga *= i;
60  return ga;
61  } else {
63  }
64  }
65 
66  double z = 0.0, r = 0.0;
67  if (std::abs(x) > 1.0) {
68  z = std::abs(x);
69  int m = static_cast<int>(z);
70  r = 1.0;
71  for (int k = 1; k <= m; k++) r *= (z - k);
72  z -= m;
73  } else {
74  z = x;
75  }
76 
77  double gr = g[24];
78  for (int k = 23; k >= 0; k--) gr = gr * z + g[k];
79  double ga = 1.0 / (gr * z);
80  if (std::abs(x) > 1.0) {
81  ga *= r;
82  if (x < 0.0) {
83  ga = -M_PI / (x * ga * sin(M_PI * x));
84  }
85  }
86  return ga;
87 }
88 
90 
92  ScalarType b,
93  ScalarType valueShift) {
94  setParameters(a, b, valueShift);
95 }
96 
97 bool WeibullDistribution::getParameters(ScalarType& a, ScalarType& b) const {
98  a = m_a;
99  b = m_b;
100 
101  return isValid();
102 }
103 
105  ScalarType& sigma2) const {
106  mu = m_mu;
107  sigma2 = m_sigma2;
108 
109  return isValid();
110 }
111 
113  ScalarType b,
114  ScalarType valueShift) {
115  m_valueShift = valueShift;
116  m_a = a;
117  m_b = b;
118 
119  // for the Chi2 test
120  chi2ClassesPositions.resize(0);
121 
122  if (m_a > 0.0 && m_b >= 0.0) {
123  // mean and standard deviation
124  m_mu = static_cast<ScalarType>(Gamma_cc(1.0 + 1.0 / m_a) * m_b);
125  m_sigma2 = static_cast<ScalarType>(
126  Gamma_cc(1.0 + 2.0 / m_a) * (m_b * m_b) - (m_mu * m_mu));
127 
128  setValid(true);
129  } else {
130  m_mu = m_sigma2 = 0.0;
131  setValid(false);
132  }
133 
134  return isValid();
135 };
136 
138  setValid(false);
139 
140  size_t n = values.size();
141  if (n == 0) return false;
142 
143  // we look for the maximum value of the SF so as to avoid overflow
144  ScalarType minValue = 0;
145  ScalarType maxValue = 0;
146  bool firstValue = true;
147  for (ScalarType s : values) {
148  if (!ScalarField::ValidValue(s)) continue;
149 
150  if (firstValue) {
151  minValue = maxValue = s;
152  firstValue = false;
153  } else {
154  if (s < minValue)
155  minValue = s;
156  else if (s > maxValue)
157  maxValue = s;
158  }
159  }
160 
161  if (firstValue) {
162  // sf is only composed of NAN values?!
163  return false;
164  }
165 
166  double valueRange = maxValue - minValue;
167  if (valueRange < std::numeric_limits<ScalarType>::epsilon()) {
168  return false;
169  }
170 
171  double a = FindGRoot(values, minValue, valueRange);
172  if (a < 0.0) return false;
173 
174  // we can compute b
175  double b = 0;
176  unsigned counter = 0;
177  for (size_t i = 0; i < n; ++i) {
178  ScalarType v = values[i];
179  if (ScalarField::ValidValue(v)) // we ignore NaN values
180  {
181  if (v >= minValue) {
182  b += pow((static_cast<double>(v) - minValue) / valueRange, a);
183  ++counter;
184  }
185  }
186  }
187  if (counter == 0) return false;
188 
189  return setParameters(
190  static_cast<ScalarType>(a),
191  static_cast<ScalarType>(valueRange * pow(b / counter, 1.0 / a)),
192  minValue);
193 }
194 
195 double WeibullDistribution::computeP(ScalarType _x) const {
196  double x = static_cast<double>(_x - m_valueShift) / m_b;
197  if (x < 0) return 0;
198 
199  double xp = pow(x, m_a - 1.0);
200  return (static_cast<double>(m_a) / m_b) * xp * exp(-xp * x);
201 }
202 
203 double WeibullDistribution::computePfromZero(ScalarType x) const {
204  return (x <= m_valueShift
205  ? 0.0
206  : 1.0 - exp(-pow(
207  static_cast<double>(x - m_valueShift) / m_b,
208  static_cast<double>(m_a))));
209 }
210 
211 double WeibullDistribution::computeP(ScalarType x1, ScalarType x2) const {
212  if (x1 < m_valueShift) x1 = m_valueShift;
213  if (x2 < m_valueShift) return 0;
214  // pi = computeP(minV+(ScalarType(k)+0.5)*step)*step;
215  //...instead we take the sampling into account and then integrate
216  return exp(-pow(static_cast<double>(x1 - m_valueShift) / m_b,
217  static_cast<double>(m_a))) -
218  exp(-pow(static_cast<double>(x2 - m_valueShift) / m_b,
219  static_cast<double>(m_a)));
220 }
221 
223  double r,
224  ScalarType valueShift,
225  double valueRange) {
226  size_t n = values.size();
227 
228  // a & n should be strictly positive!
229  if (r <= 0.0 || n == 0)
230  return 1.0; // a positive value means that ComputeG failed
231 
232  double p = 0;
233  double q = 0;
234  double s = 0;
235  unsigned counter = 0;
236  unsigned zeroValues = 0;
237 
238  for (unsigned i = 0; i < n; ++i) {
239  ScalarType v = values[i];
240  if (ScalarField::ValidValue(v)) // we ignore NaN values
241  {
242  double v0 = static_cast<double>(v) - valueShift;
243  if (GreaterThanEpsilon(v0)) {
244  double ln_v = log(v0);
245  double v_a = pow(v0 / valueRange, r);
246 
247  s += ln_v;
248  q += v_a;
249  p += v_a * ln_v;
250 
251  ++counter;
252  } else {
253  ++zeroValues;
254  }
255  }
256  }
257 
258  if (zeroValues) {
259  const double ln_v = log(ZERO_TOLERANCE_D) * zeroValues;
260  const double v_a =
261  pow(ZERO_TOLERANCE_D / valueRange, static_cast<double>(r));
262  s += ln_v;
263  q += v_a * zeroValues;
264  p += ln_v * v_a;
265  counter += zeroValues;
266  }
267 
268  if (counter == 0) {
269  return 1.0; // a positive value will make ComputeG fail
270  }
271 
272  return (p / q - s / counter) * r - 1.0;
273 }
274 
276  ScalarType valueShift,
277  double valueRange) {
278  double r = -1.0;
279  double aMin = 1.0;
280  double aMax = 1.0;
281  double v = ComputeG(values, aMin, valueShift, valueRange);
282  double vMin = v;
283  double vMax = v;
284 
285  // find min value for binary search so that ComputeG(aMin) < 0
286  while ((vMin > 0) && GreaterThanEpsilon(aMin)) {
287  aMin /= 10;
288  vMin = ComputeG(values, aMin, valueShift, valueRange);
289  }
290 
291  if (LessThanEpsilon(std::abs(vMin))) {
292  return aMin;
293  } else if (vMin > 0) {
294  return r; // r = -1 (i.e. problem)
295  }
296 
297  // find max value for binary search so that ComputeG(aMax) > 0
298  while (vMax < 0 && aMax < 1.0e3) {
299  aMax *= 2; // tends to become huge quickly as we compute x^a!!!!
300  vMax = ComputeG(values, aMax, valueShift, valueRange);
301  }
302 
303  if (LessThanEpsilon(std::abs(vMax))) {
304  return aMax;
305  } else if (vMax < 0) {
306  return r; // r = -1 (i.e. problem)
307  }
308 
309  // binary search to find r so that std::abs(ComputeG(r)) < ZERO_TOLERANCE
310  while (GreaterThanEpsilon(std::abs(v) *
311  100)) // DGM: *100 ?! (can't remember why ;)
312  {
313  r = (aMin + aMax) / 2;
314  double old_v = v;
315  v = ComputeG(values, r, valueShift, valueRange);
316 
317  if (LessThanEpsilon(std::abs(old_v - v))) return r;
318 
319  if (v < 0)
320  aMin = r;
321  else
322  aMax = r;
323  }
324 
325  return r;
326 }
327 
329  unsigned numberOfClasses,
330  int* inputHisto) {
331  assert(cloud);
332 
333  // we must refine the real number of elements
334  unsigned numberOfElements =
336 
337  if (numberOfElements == 0) return -1.0;
338 
339  if (numberOfClasses < 1 ||
340  numberOfClasses * numberOfClasses > numberOfElements)
341  return -1.0;
342  else if (numberOfClasses == 1)
343  return 0.0;
344 
345  if (!setChi2ClassesPositions(numberOfClasses)) return -1.0;
346 
347  assert(chi2ClassesPositions.size() + 1 == numberOfClasses);
348 
349  int* histo = inputHisto;
350  if (!histo) {
351  histo = new int[numberOfClasses];
352  if (!histo) return -1.0; // not enough memory
353  }
354  memset(histo, 0, numberOfClasses * sizeof(int));
355 
356  // compute the histogram
357  unsigned n = cloud->size();
358  for (unsigned i = 0; i < n; ++i) {
359  ScalarType V = cloud->getPointScalarValue(i);
360  if (ScalarField::ValidValue(V)) {
361  unsigned j = 0;
362  for (; j < numberOfClasses - 1; ++j)
363  if (V < chi2ClassesPositions[j]) break;
364 
365  ++histo[j];
366  }
367  }
368 
369  // Chi2 distance
370  double dk = 0;
371  {
372  double nPi = static_cast<double>(numberOfElements) / numberOfClasses;
373  for (unsigned i = 0; i < numberOfClasses; ++i) {
374  double tempValue = static_cast<double>(histo[i]) - nPi;
375  dk += tempValue * tempValue;
376  }
377  dk /= nPi;
378  }
379 
380  if (histo && !inputHisto) delete[] histo;
381  histo = nullptr;
382 
383  return dk;
384 }
385 
386 bool WeibullDistribution::setChi2ClassesPositions(unsigned numberOfClasses) {
387  chi2ClassesPositions.resize(0);
388 
389  if (!isValid() || numberOfClasses < 2) return false;
390 
391  try {
392  chi2ClassesPositions.resize(numberOfClasses - 1);
393  } catch (const std::bad_alloc&) {
394  // not engouh memory
395  return false;
396  }
397 
398  // we create "numberOfClasses" equiprobable classes (for all of themn
399  // nPi>=sqrt(n) if n>=4)
400  double areaPerClass = 1.0 / numberOfClasses;
401  double currentArea = areaPerClass;
402  double invA = 1.0 / m_a;
403 
404  for (unsigned i = 1; i < numberOfClasses; ++i) {
405  chi2ClassesPositions[i - 1] =
406  m_b *
407  static_cast<ScalarType>(pow(-log(1.0 - currentArea), invA));
408  currentArea += areaPerClass;
409  }
410 
411  return true;
412 }
413 
415  if (vs != m_valueShift) setValid(false);
416 
417  m_valueShift = vs;
418 }
419 
421  double mode = m_valueShift;
422  if (m_a > 1.0) {
423  mode += m_b * pow((m_a - 1.0) / m_a, 1.0 / m_a);
424  }
425  return mode;
426 }
427 
429  if (!isValid() || std::abs(m_a) < std::numeric_limits<double>::epsilon() ||
430  m_sigma2 < std::numeric_limits<double>::epsilon()) {
431  return std::numeric_limits<double>::quiet_NaN();
432  }
433  return (Gamma_cc(1.0 + 3.0 / m_a) * (m_b * m_b * m_b) -
434  3.0 * m_mu * m_sigma2 - (m_mu * m_mu * m_mu)) /
435  (m_sigma2 * sqrt(m_sigma2));
436 }
constexpr double M_PI
Pi.
Definition: CVConst.h:19
constexpr double ZERO_TOLERANCE_D
Definition: CVConst.h:49
virtual unsigned size() const =0
Returns the number of points.
virtual ScalarType getPointScalarValue(unsigned pointIndex) const =0
Returns the ith point associated scalar value.
virtual bool isValid() const
Indicates if the distribution parameters are valid.
void setValid(bool state)
Sets distribution current validity.
std::vector< ScalarType > ScalarContainer
Scalar values container.
static unsigned countScalarFieldValidValues(const GenericCloud *theCloud)
Count the number of valid values in a scalar field.
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
void setValueShift(ScalarType vs)
Sets the distribution value shift.
bool getOtherParameters(ScalarType &mu, ScalarType &sigma2) const
Returns the normal distribution equivalent parameters.
bool computeParameters(const ScalarContainer &values) override
Computes the distribution parameters from a set of values.
virtual bool setChi2ClassesPositions(unsigned numberOfClasses)
Compute each Chi2 class limits.
double computeP(ScalarType x) const override
Computes the probability of x.
bool setParameters(ScalarType a, ScalarType b, ScalarType valueShift=0)
Sets the distribution parameters.
bool getParameters(ScalarType &a, ScalarType &b) const
Returns the distribution parameters.
static double ComputeG(const ScalarContainer &values, double a, ScalarType valueShift, double valueRange)
internal function for parameters evaluation from sample points
ScalarType m_a
Weibull distribution parameter a (k)
ScalarType m_sigma2
Normal distribution equivalent parameter: variance.
double computeSkewness() const
Returns the distribution 'skewness'.
double computeChi2Dist(const GenericCloud *cloud, unsigned numberOfClasses, int *histo=nullptr) override
Computes the Chi2 distance (related to the Chi2 Test)
WeibullDistribution()
WeibullDistribution constructor.
ScalarType m_mu
Normal distribution equivalent parameter: mean.
ScalarType m_valueShift
Weibull distribution parameter 'value shift'.
static double FindGRoot(const ScalarContainer &values, ScalarType valueShift, double valueRange)
internal function for parameters evaluation from sample points
ScalarType m_b
Weibull distribution parameter b (lambda)
std::vector< ScalarType > chi2ClassesPositions
Chi2 classes limits.
double computePfromZero(ScalarType x) const override
Computes the cumulative probability between 0 and x.
double computeMode() const
Returns the distribution 'mode'.
static double Gamma_cc(double x)
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
int max(int a, int b)
Definition: cutil_math.h:48
Generic file read and write utility for python interface.
bool GreaterThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:37
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23