Official websites use .gov
A .gov website belongs to an official government organization in the United States.

Secure .gov websites use HTTPS
A lock ( ) or https:// means you’ve safely connected to the .gov website. Share sensitive information only on official, secure websites.

Isis 3 Programmer Reference
StatCumProbDistDynCalc.cpp
1
5
6/* SPDX-License-Identifier: CC0-1.0 */
7
8#include "StatCumProbDistDynCalc.h"
9
10#include <QDataStream>
11#include <QDebug>
12#include <QList>
13#include <QStringView>
14#include <QUuid>
15#include <QXmlStreamWriter>
16#include <QXmlStreamReader>
17
18#include <float.h>
19#include <math.h>
20#include <stdio.h>
21
22#include "IException.h"
23#include "IString.h"
24#include "Project.h"
25
26#include "Pvl.h"
27#include <iostream>
28
29namespace Isis {
30
31
39 : QObject(parent) {
40 initialize();
41 setQuantiles(nodes);
42 }
43
44
45 StatCumProbDistDynCalc::StatCumProbDistDynCalc(QXmlStreamReader *xmlReader, QObject *parent) {
46 initialize();
47 readStatistics(xmlReader);
48 }
49
50 void StatCumProbDistDynCalc::readStatistics(QXmlStreamReader *xmlReader) {
51 Q_ASSERT(xmlReader->name() == "statCumProbDistDynCalc");
52 while (xmlReader->readNextStartElement()) {
53 if (xmlReader->qualifiedName() == "numberCells") {
54 try {
55 m_numberCells = toDouble(xmlReader->readElementText());
56 }
57 catch (IException &e) {
58 m_numberCells = 0.0;
59 }
60 }
61 else if (xmlReader->qualifiedName() == "numberQuantiles") {
62 try {
63 m_numberQuantiles = toDouble(xmlReader->readElementText());
64 }
65 catch (IException &e) {
67 }
68 }
69 else if (xmlReader->qualifiedName() == "numberObservations") {
70 try {
71 m_numberObservations = toDouble(xmlReader->readElementText());
72 }
73 catch (IException &e) {
75 }
76 }
77 else if (xmlReader->qualifiedName() == "distributionData") {
78 m_quantiles.clear();
79 m_observationValues.clear();
82 for (unsigned int i = 0; i < m_numberQuantiles; i++) {
83 while (xmlReader->readNextStartElement()) {
84 if (xmlReader->qualifiedName() == "quantileInfo") {
85 QStringView quantile = xmlReader->attributes().value("quantile");
86 if (!quantile.isEmpty()) {
87 m_quantiles.append(quantile.toDouble());
88 }
89 QStringView dataValue = xmlReader->attributes().value("dataValue");
90 if (!dataValue.isEmpty()) {
91 m_observationValues.append(dataValue.toDouble());
92 }
93 QStringView idealNumObsBelowQuantile = xmlReader->attributes().value("idealNumObsBelowQuantile");
94 if (!idealNumObsBelowQuantile.isEmpty()) {
95 m_idealNumObsBelowQuantile.append(idealNumObsBelowQuantile.toDouble());
96 }
97 QStringView actualNumObsBelowQuantile = xmlReader->attributes().value("actualNumObsBelowQuantile");
98
99 if (!actualNumObsBelowQuantile.isEmpty()) {
100 m_numObsBelowQuantile.append(actualNumObsBelowQuantile.toInt());
101 }
102 }
103 else {
104 xmlReader->skipCurrentElement();
105 }
106 }
107 }
108 }
109 else {
110 xmlReader->skipCurrentElement();
111 }
112 }
113 }
114
115
117 : m_numberCells(other.m_numberCells),
118 m_numberQuantiles(other.m_numberQuantiles),
119 m_numberObservations(other.m_numberObservations),
120 m_quantiles(other.m_quantiles),
121 m_observationValues(other.m_observationValues),
122 m_idealNumObsBelowQuantile(other.m_idealNumObsBelowQuantile),
123 m_numObsBelowQuantile(other.m_numObsBelowQuantile) {
124 }
125
126
127
133
134
135
136 StatCumProbDistDynCalc &StatCumProbDistDynCalc::operator=(const StatCumProbDistDynCalc &other) {
137
138 if (&other != this) {
142 m_quantiles = other.m_quantiles;
146 }
147 return *this;
148
149 }
150
151
152
166
167
168
169 void StatCumProbDistDynCalc::setQuantiles(unsigned int nodes) {
170 initialize();
171 if (nodes < 3) {
173 }
174 else {
175 m_numberQuantiles = nodes;
176 }
177
178 m_numberCells = m_numberQuantiles - 1; // there is one more border value then there are cells
179
180 double stepSize = 1.0 / (double)m_numberCells;
181 for (unsigned int i = 0; i < m_numberQuantiles; i++) {
182 if (i == 0) {
183 m_quantiles.append( 0.0 );
184 }
185 else {
186 m_quantiles.append( m_quantiles[i-1] + stepSize );
187 } // essentially the same as i/numCells without having to cast i every time...
188 m_idealNumObsBelowQuantile.append(i+1);
189 m_numObsBelowQuantile.append(i+1);
190 }
191
192 }
193
194
195
206 validate();
207 //return the largest value so far
209 }
210
211
212
223 validate();
224 //return the smallest value so far
225 return m_observationValues[0];
226 }
227
228
229
245 validate();
246 //given a cumProbability return the associate value
247
248 //if the requested cumProb is outside [0,1] return DBL_MAX
250 IString msg = "Invalid cumulative probability [" + toString(cumProb) + "] passed in to "
251 "StatCumProbDistDynCalc::value(double cumProb). Must be on the domain [0, 1].";
252 throw IException(IException::Programmer, msg, _FILEINFO_);
253 }
254
255 //if the requested quantile is 0.0 return the lowest value
256 if (cumProb == 0.0) {
257 return m_observationValues[0];
258 }
259
260 //if the requested quantile is 1.0 return the largest value
261 if (cumProb == 1.0) {
263 }
264
265 // otherwise find the the node nearest the value
266 double minDistance = fabs(m_quantiles[0] - cumProb); // distance from first quantile
267 // to the given probability
268 unsigned int index = 0;
269 for (int i = 1; i < int(m_numberQuantiles); i++) {
270 // if the given probability is closer to this quantile than the currently saved
271 // min distance, then replace
272 double dist = fabs(m_quantiles[i] - cumProb);
273// if ( dist < minDistance || qFuzzyCompare(dist, minDistance)) {
274 if ( dist < minDistance ) {
275 minDistance = fabs(m_quantiles[i] - cumProb);
276 index = i;
277 }
278 else {
279 break; // we are getting farther from the given value
280 }
281 }// TODO: must be a better way to write this???
282
283 //get the coordinates of the three closest nodes, value as a function of cumProb
284 double x[3]; // m_observationValues values
285 double y[3]; // cumlative probilities
286
287 if (index == 0) { // the given prob is closest to the first quantile
288 y[0] = m_observationValues[0];
289 y[1] = m_observationValues[1];
290 y[2] = m_observationValues[2];
291
292 x[0] = m_quantiles[0];
293 x[1] = m_quantiles[1];
294 x[2] = m_quantiles[2];
295 }
296 else if (index == m_numberCells) { // the given prob is closest to the last quantile
297 y[0] = m_observationValues[index-2];
298 y[1] = m_observationValues[index-1];
299 y[2] = m_observationValues[index ];
300
301 x[0] = m_quantiles[index-2];
302 x[1] = m_quantiles[index-1];
303 x[2] = m_quantiles[index ];
304 }
305 else {
306 y[0] = m_observationValues[index-1];
307 y[1] = m_observationValues[index ];
308 y[2] = m_observationValues[index+1];
309
310 x[0] = m_quantiles[index-1];
311 x[1] = m_quantiles[index ];
312 x[2] = m_quantiles[index+1];
313 }
314
315 if ( x[0] == x[1] || x[0] == x[2] || x[1] == x[2]) {
316 return m_observationValues[index]; // this should never happen,
317 // but just in case return the value of the nearest node
318 }
319
320 // try quadratic interpolation
321 double interp = (cumProb-x[1]) * (cumProb-x[2]) / (x[0]-x[1]) / (x[0]-x[2]) * y[0]
322 + (cumProb-x[0]) * (cumProb-x[2]) / (x[1]-x[0]) / (x[1]-x[2]) * y[1]
323 + (cumProb-x[0]) * (cumProb-x[1]) / (x[2]-x[0]) / (x[2]-x[1]) * y[2];
324
325 // check for reasonability of the quadratic interpolation
326
327 // TODO: better way to write this???
328 int i;
329 for (i = 0; i < 3; i++) {
330 if ( x[i] <= cumProb && x[i+1] >= cumProb) { // find the nodes on both sides of cumProb
331 break;
332 }
333 }
334
335 // make sure things are strictly increasing
336 if (y[i] <= interp && y[i+1] >= interp) {
337 return interp;
338 }
339 else {
340 // if the quadratic wasn't reasonable return the linear interpolation
341 return ( (x[i] - cumProb) * y[i+1] + (x[i+1] - cumProb) * y[i] ) / (x[i] - x[i+1]);
342 }
343
344 }
345
346
347
362 validate();
363 //given a value return the cumulative probility
364
365 if (value <= m_observationValues[0]) {
366 return 0.0; // if the value is less than or equal to the the current min,
367 // the best estimate is 0.0
368 }
370 return 1.0; // if the value is greater than or equal to the current max,
371 // the best estimate is 1.0
372 }
373
374 // otherwise, find the the node nearest to the given value
375 double minDistance = fabs(m_observationValues[0]-value);
376 unsigned int index = 0;
377 for (unsigned int i = 1; i < m_numberQuantiles; i++) {
378 double dist = fabs(m_observationValues[i]-value);
379 if ( dist < minDistance) {
380 minDistance = fabs(m_observationValues[i]-value);
381 index = i;
382 }
383 else {
384 break; // we are getting farther from the given value
385 }
386 }// TODO: must be a better way to write this???
387
388 //get the coordinates of the three closest nodes cumProb as a function of value
389 double x[3]; // m_observationValues values
390 double y[3]; // cumlative probilities
391
392 if (index == 0) {
393 x[0] = m_observationValues[0];
394 x[1] = m_observationValues[1];
395 x[2] = m_observationValues[2];
396
397 y[0] = m_quantiles[0];
398 y[1] = m_quantiles[1];
399 y[2] = m_quantiles[2];
400 }
401 else if (index == m_numberCells) {
402 x[0] = m_observationValues[index-2];
403 x[1] = m_observationValues[index-1];
404 x[2] = m_observationValues[index ];
405
406 y[0] = m_quantiles[index-2];
407 y[1] = m_quantiles[index-1];
408 y[2] = m_quantiles[index ];
409 }
410 else {
411 x[0] = m_observationValues[index-1];
412 x[1] = m_observationValues[index ];
413 x[2] = m_observationValues[index+1];
414
415 y[0] = m_quantiles[index-1];
416 y[1] = m_quantiles[index ];
417 y[2] = m_quantiles[index+1];
418 }
419
420 if ( x[0] == x[1] || x[0] == x[2] || x[1] == x[2]) {
421 return m_quantiles[index]; // this should never happen,
422 // but just in case return the cumProb of the nearest node
423 }
424
425 //do a parabolic interpolation to find the probability at value
426 double interp = (value-x[1]) * (value-x[2]) / (x[0]-x[1]) / (x[0]-x[2]) * y[0]
427 + (value-x[0]) * (value-x[2]) / (x[1]-x[0]) / (x[1]-x[2]) * y[1]
428 + (value-x[0]) * (value-x[1]) / (x[2]-x[0]) / (x[2]-x[1]) * y[2];
429
430 //check for reasonability of the quadratic interpolation
431 // TODO: better way to write this???
432 int i;
433 for (i = 0; i < 3; i++) {
434 if ( x[i] <= value && x[i+1] >= value) //find the nodes on both sides of cumProb
435 break;
436 }
437
438 if (y[i] <= interp && y[i+1] >= interp) //make sure things are strictly increasing
439 return interp;
440 //if the quadratic wasn't reasonable return the linear interpolation
441 else {
442 return ((x[i] - value) * y[i+1] + (x[i+1] - value) * y[i]) / (x[i] - x[i+1]);
443 }
444
445 }
446
447
448
457 double temp = 0.0;
458
460 // in this phase, the algorithm is getting initial values
461 m_observationValues.append(obs);
463
464 // if there are now m_numberQuantiles observations, then sort them from smallest to greatest
466 std::sort(m_observationValues.begin(), m_observationValues.end(), std::less<double>());
467 }
468 }
469 else { // m_numberObservations >= m_numberQuantiles
470
471 //incrementing the number of observations
474
475 // replace min or max with this observation, if appropriate
478 }
479 if (obs < m_observationValues[0]) {
480 m_observationValues[0] = obs;
481 temp = 1.0;
482 }
483
484 //estimated quantile positions are increased if obs <= estimated quantile value
485 for (int i = 1; i < (int)m_numberQuantiles-1; i++) {
486 if (obs <= m_observationValues[i]) {
487 for (; i < (int)m_numberQuantiles-1; i++) {
488 m_numObsBelowQuantile[i]++; // all m_n's above the first >= obs get incremented
489 }
490 }
491 }
492
493 for (int i = 1; i < (int)m_numberQuantiles; i++) {
495 }
496
497 //calculate corrections to node positions (m_n) and values (m_observationValues)
498 int d;
499 // note that the loop does not edit the first or last positions
500 for (int i = 1; i < (int)m_numberCells; i++) {
501 //calculation of d[i] it is either 1, -1, or 0
503 if (fabs(temp)>1 && temp > 0.0) {
504 d = 1;
505 }
506 else if (fabs(temp)>1 && temp < 0.0) {
507 d = -1;
508 }
509 else {
510 continue;
511 }
512
513 // if the node can be moved by d without stepping on another node
514 if ( ( (d == 1)
516 || ( (d == -1)
517 && (m_numObsBelowQuantile[i-1] - m_numObsBelowQuantile[i] < -1) ) ) {
518 //calculate a new quantile value for the node from the parabolic formula
519 temp = m_observationValues[i]
520 + double(d) / (m_numObsBelowQuantile[i+1] - m_numObsBelowQuantile[i-1])
527
528 // it is necessary that the values of the quantiles be strictly increasing,
529 // if the parabolic formula perserves this so be it
530 if ( m_observationValues[i-1] < temp && temp < m_observationValues[i+1]) {// use qSort???
531 m_observationValues[i] = temp;
532 }
533 else {
534 // if the parabolic formula does not maintain
535 // that the values of the quantiles be strictly increasing,
536 // then use linear interpolation
538 + double(d)
541 }
542
543 // the position of the quantile
544 // (in terms of the number of observations <= quantile) is also adjusted
545 m_numObsBelowQuantile[i] += d;
546 }
547 }
548 }
549 }
550
551
552
553 void StatCumProbDistDynCalc::save(QXmlStreamWriter &stream, const Project *project) const { // TODO: does xml stuff need project???
554
555 stream.writeStartElement("statCumProbDistDynCalc");
556 stream.writeTextElement("numberCells", toString(m_numberCells));
557 stream.writeTextElement("numberQuantiles", toString(m_numberQuantiles));
558 stream.writeTextElement("numberObservations", toString(m_numberObservations));
559
560 stream.writeStartElement("distributionData");
561 for (unsigned int i = 0; i < m_numberQuantiles; i++) {
562 stream.writeStartElement("quantileInfo");
563 // we need to write out high precision for minDistance calculations in value() and cumProb()
564 stream.writeAttribute("quantile", toString(m_quantiles[i], 17));
565 if (i < m_observationValues.size()) {
566 stream.writeAttribute("dataValue", toString(m_observationValues[i], 17));
567 }
568 stream.writeAttribute("idealNumObsBelowQuantile",
570 stream.writeAttribute("actualNumObsBelowQuantile", toString(m_numObsBelowQuantile[i]));
571 stream.writeEndElement(); // end observation
572 }
573 stream.writeEndElement(); // end observationData
574 stream.writeEndElement(); // end statCumProbDistDynCalc
575
576 }
577
578 QDataStream &StatCumProbDistDynCalc::write(QDataStream &stream) const {
579 stream << (qint32)m_numberCells
580 << (qint32)m_numberQuantiles
581 << (qint32)m_numberObservations
582 << m_quantiles
586 return stream;
587 }
588
589 QDataStream &StatCumProbDistDynCalc::read(QDataStream &stream) {
590 QString id;
591 qint32 numCells, numQuantiles, numObservations;
592 stream >> numCells
593 >> numQuantiles
594 >> numObservations
595 >> m_quantiles
599
600 m_numberCells = (unsigned int)numCells;
601 m_numberQuantiles = (unsigned int)numQuantiles;
602 m_numberObservations = (unsigned int)numObservations;
603 return stream;
604 }
605
606
607
608 QDataStream &operator<<(QDataStream &stream, const StatCumProbDistDynCalc &scpddc) {
609 return scpddc.write(stream);
610 }
611
612
613
614 QDataStream &operator>>(QDataStream &stream, StatCumProbDistDynCalc &scpddc) {
615 return scpddc.read(stream);
616 }
617
618 void StatCumProbDistDynCalc::validate() {
619 // if quantiles have not been set
620 if (m_numberQuantiles == 0) {
621 QString msg = "StatCumProbDistDynCalc will return no data until the quantiles have been set. "
622 "Number of cells = [" + toString(m_numberCells) + "].";
623 throw IException(IException::Programmer, msg, _FILEINFO_);
624 }
625
626 //if there isn't even as much data as there are quantiles to track
628 QString msg = "StatCumProbDistDynCalc will return no data until the number of observations "
629 "added [" + toString(m_numberObservations) + "] matches the number of "
630 "quantiles [" + toString(m_numberQuantiles)
631 + "] (i.e. number of nodes) selected.";
632 throw IException(IException::Programmer, msg, _FILEINFO_);
633 }
634
635 }
636}// end namespace Isis
The main project for ipce.
Definition Project.h:287
This class is used to approximate cumulative probibility distributions of a stream of observations wi...
QList< int > m_numObsBelowQuantile
The actual number of observations that are less than or equal to the value of the corresponding quant...
unsigned int m_numberObservations
The number of observations, note this is dynamically changing as observations are added.
double min()
Returns the maximum observation so far included in the dynamic calculation.
QList< double > m_idealNumObsBelowQuantile
The ideal number of observations that should be less than or equal to the value of the corresponding ...
QList< double > m_observationValues
The calculated values of the quantiles, note this is dynamically changing as observations are added.
StatCumProbDistDynCalc(unsigned int nodes=20, QObject *parent=0)
Construtor sets up the class to start recieving data.
void initialize()
Inializer, resets the class to start its dynamic calculation anew.
unsigned int m_numberCells
The number of cells or histogram bins that are being used to model the probility density function.
~StatCumProbDistDynCalc()
Destroys StatCumProbDistDynCalc object.
unsigned int m_numberQuantiles
The number of quantiles being used to model the probility density function.
QList< double > m_quantiles
The target quantiles being modeled, between 0 and 1.
double value(double cumProb)
Provides the value of the variable that has the given cumulative probility (according the current est...
void addObs(double obs)
Values for the estimated quantile positions are update as observations are added.
double cumProb(double value)
Provides the cumulative probility, that is, the proportion of the distribution that is less than or e...
double max()
Returns the maximum observation so far included in the dynamic calculation.
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16
QString toString(const LinearAlgebra::Vector &vector, int precision)
A global function to format LinearAlgebra::Vector as a QString with the given precision.
std::istream & operator>>(std::istream &is, CSVReader &csv)
Input read operator for input stream sources.
QDebug operator<<(QDebug debug, const Hillshade &hillshade)
Print this class out to a QDebug object.