Changed LICENSE. Now referenced to web site and file on project root directory
[anna.git] / source / statistics / Accumulator.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite                           //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
7
8
9 //------------------------------------------------------------------------------
10 //--------------------------------------------------------------------- #include
11 //------------------------------------------------------------------------------
12
13
14 // Local
15 #include <anna/statistics/Accumulator.hpp>
16 #include <anna/statistics/Engine.hpp>
17
18 #include <anna/time/Date.hpp>
19
20 // Standard
21 #include <math.h>
22 #include <sys/time.h>
23 #include <limits.h>
24
25 #include <anna/xml/xml.hpp>
26 #include <anna/core/tracing/Logger.hpp>
27 #include <anna/core/tracing/TraceMethod.hpp>
28
29
30
31 using namespace anna::statistics;
32 using namespace anna::time;
33
34
35 //******************************************************************************
36 //------------------------------------------------------------------------------
37 //--------------------------------------------------- Accumulator::Accumulator()
38 //------------------------------------------------------------------------------
39
40 // Default Constructor
41 Accumulator::Accumulator() {
42   //reset (); no sense
43 }
44
45
46 Accumulator::~Accumulator() {
47 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "Destructor", ANNA_FILE_LOCATION));
48 }
49
50
51
52 // Private functions:
53
54
55 //------------------------------------------------------------------------------
56 //---------------------------------------------------- Accumulator::initialize()
57 //------------------------------------------------------------------------------
58 void Accumulator::initialize(const int & conceptId) throw() {
59   _concept_data_t conceptData;
60   conceptData.reset();
61   a_concept_data_map[conceptId] = conceptData;
62 }
63
64
65 //------------------------------------------------------------------------------
66 //---------------------------------------------------- Accumulator::getConcept()
67 //------------------------------------------------------------------------------
68 _concept_data_t * Accumulator::getConcept(const int & conceptId) const throw(anna::RuntimeException) {
69   _concept_data_map_iter it = a_concept_data_map.find(conceptId);
70
71   if(it == a_concept_data_map.end()) {  // not found
72     // Check if concept id is registered:
73     Engine& engine = Engine::instantiate();
74     std::string conceptDescription, conceptUnitDescription;
75     bool integerNature;
76
77     if(!engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNature)) {
78       std::string msg = "Can't find at engine nothing about concept id = ";
79       msg += anna::functions::asString(conceptId);
80       throw anna::RuntimeException(msg, ANNA_FILE_LOCATION);
81     }
82
83     Accumulator * nc_this = const_cast<Accumulator *>(this);
84     nc_this->initialize(conceptId);
85     // (la otra posibilidad era hacer mutable el mapa e inicializar poniendo el contenido de 'initialize()')
86     it = a_concept_data_map.find(conceptId);
87   }
88
89   return ((_concept_data_t *) & ((*it).second));
90 }
91
92
93 //------------------------------------------------------------------------------
94 //--------------------------------------------------- Accumulator::floatFormat()
95 //------------------------------------------------------------------------------
96 std::string Accumulator::floatFormat(const int & numberOfDecimals) const throw() {
97   std::string result = "%.";
98   result += anna::functions::asString(numberOfDecimals);
99   result += "f";
100   return (result);
101 }
102
103
104
105
106
107 //------------------------------------------------------------------------------
108 //------------------------------------------ Accumulator::getStandardDeviation()
109 //------------------------------------------------------------------------------
110 double Accumulator::getStandardDeviation(const _concept_data_t * conceptData) const throw(anna::RuntimeException) {
111   // SD = sqrt (1/N SUM (xi^2) - X^2) = sqrt (SquareSum / N - Average^2)
112   if(conceptData->Size == 0)
113     throw anna::RuntimeException("Divide by zero: sample size = 0 for Standard Deviation !!", ANNA_FILE_LOCATION);
114
115   return (::sqrt((conceptData->SquareSum / conceptData->Size) - (::pow(conceptData->Average, (double)2))));
116 }
117
118
119 //------------------------------------------------------------------------------
120 //------------------------------------ Accumulator::getBesselStandardDeviation()
121 //------------------------------------------------------------------------------
122 double Accumulator::getBesselStandardDeviation(const _concept_data_t * conceptData) const throw(anna::RuntimeException) {
123   // BSD = sqrt (1/(N-1) SUM (xi^2) - N/(N-1) X^2) = sqrt (SquareSum / (N-1) - N/(N-1) Average^2)
124   if(conceptData->Size == 1)
125     throw anna::RuntimeException("Divide by zero: sample size = 1 for bessel's Standard Deviation !!", ANNA_FILE_LOCATION);
126
127   double N = conceptData->Size;
128   return (::sqrt((conceptData->SquareSum / (N - 1)) - (N / (N - 1)) * (::pow(conceptData->Average, (double)2))));
129 }
130
131
132
133 // Public functions:
134
135
136 //------------------------------------------------------------------------------
137 //------------------------------------------------------- Accumulator::process()
138 //------------------------------------------------------------------------------
139 void Accumulator::process(const int & conceptId, const double & value) throw(anna::RuntimeException) {
140 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "process", ANNA_FILE_LOCATION));
141   Engine& engine = Engine::instantiate();
142
143   if(!engine.enabled()) return;
144
145   _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
146   anna::Millisecond current = anna::Millisecond::getTime();
147   // Optional sample file dump:
148   Engine::instantiate().logSample(conceptId, current, value);
149
150   // Statistics calculations
151   if(ptr_auxConceptData->Size == ULLONG_MAX)  // Statistics is better during processing until reset
152     ptr_auxConceptData->reset();
153
154   ptr_auxConceptData->Size ++;
155
156   // If first:
157   if(ptr_auxConceptData->Size == 1) {
158     ptr_auxConceptData->MinimumProcessed = value;
159     ptr_auxConceptData->MaximumProcessed = value;
160     ptr_auxConceptData->MinimumEventTimestampMs = current;
161     ptr_auxConceptData->MaximumEventTimestampMs = current;
162   } else {
163     if(value < ptr_auxConceptData->MinimumProcessed) {
164       ptr_auxConceptData->MinimumProcessed = value;
165       ptr_auxConceptData->MinimumEventTimestampMs = current;
166     }
167
168     if(value > ptr_auxConceptData->MaximumProcessed) {
169       ptr_auxConceptData->MaximumProcessed = value;
170       ptr_auxConceptData->MaximumEventTimestampMs = current;
171     }
172   }
173
174   ptr_auxConceptData->Sum += value;
175   ptr_auxConceptData->SquareSum += ::pow(value, (double)2); // (*) standard deviation formula simplification
176   ptr_auxConceptData->Average = ptr_auxConceptData->Sum / ptr_auxConceptData->Size;
177   //ptr_auxConceptData->SquareSumSampleDeviation += ::pow(value - ptr_auxConceptData->Average, (double)2);
178   // Don't use former method to get standard deviation because is not correct: in each processing, average
179   //  will be change (is not fixed). There is a simplification for standard deviation, based on square sum (*):
180   // Take X as average for x1, x2, x3, etc., xN
181   // SUM (xi - X)^2    = SUM (xi^2 + X^2 - 2xiX) = SUM (xi^2) + NX^2 - 2X SUM (xi) =
182   //                   = SUM (xi`2) + NX^2 - 2X(NX) = SUM (xi^2) - NX^2
183   //
184   // Then, SD = sqrt (1/N SUM (xi^2) - X^2) = sqrt (SquareSum / N - Average^2)
185   // And corrected SD (Bessel's standard deviation) is:
186   // BSD = sqrt (1/(N-1) SUM (xi^2) - N/(N-1) X^2) = sqrt (SquareSum / (N-1) - N/(N-1) Average^2)
187   // All these values don't need to be calculated now
188   // Worked with pointer
189   //a_concept_data_map[conceptId] = *ptr_auxConceptData;
190 }
191
192
193 //------------------------------------------------------------------------------
194 //------------------------------------------------------- Accumulator::operator=
195 //------------------------------------------------------------------------------
196 const Accumulator & Accumulator::operator = (const Accumulator & accumulator) {
197 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "operator=", ANNA_FILE_LOCATION));
198   // Avoid self copy: i.e., Accumulator a,b; a=&b; a=b; b=a;
199   if(this == &accumulator) return (*this);
200
201   a_concept_data_map = accumulator.a_concept_data_map;
202   return (*this);
203 }
204
205
206
207 //------------------------------------------------------------------------------
208 //--------------------------------------------------------- Accumulator::reset()
209 //------------------------------------------------------------------------------
210 void Accumulator::reset(void) throw() {
211 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "reset", ANNA_FILE_LOCATION));
212   _concept_data_map_iter it;
213   _concept_data_map_iter it_min(a_concept_data_map.begin());
214   _concept_data_map_iter it_max(a_concept_data_map.end());
215
216   for(it = it_min; it != it_max; it++) {
217     //reset ((*it).first);
218     _concept_data_t * ptr_auxConceptData = (_concept_data_t*) & ((*it).second);
219     ptr_auxConceptData->reset();
220   }
221 }
222
223
224 //------------------------------------------------------------------------------
225 //--------------------------------------------------------- Accumulator::reset()
226 //------------------------------------------------------------------------------
227 void Accumulator::reset(const int & conceptId) throw(anna::RuntimeException) {
228 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "reset", ANNA_FILE_LOCATION));
229   _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
230   //if (ptr_auxConceptData == NULL) return; // Not possible: getConcept initializes it if not found.
231   ptr_auxConceptData->reset();
232 }
233
234
235 // Gets:
236
237 //------------------------------------------------------------------------------
238 //---------------------------------------------------- Accumulator::sampleSize()
239 //------------------------------------------------------------------------------
240 unsigned long long int Accumulator::sampleSize(const int & conceptId) const throw(anna::RuntimeException) {
241 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "sampleSize", ANNA_FILE_LOCATION));
242   _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
243   //if (ptr_auxConceptData == NULL) return 0; // Not possible: getConcept initializes it if not found.
244   return (ptr_auxConceptData->Size);
245 }
246
247
248 //------------------------------------------------------------------------------
249 //------------------------------------------------------ Accumulator::getValue()
250 //------------------------------------------------------------------------------
251 double Accumulator::getValue(const int & conceptId, const Operation::Type & operation) const throw(anna::RuntimeException) {
252 // LOGMETHOD (TraceMethod tttm ("anna::statistics::Accumulator", "getValue", ANNA_FILE_LOCATION));
253   const _concept_data_t *ptr_auxConceptData = getConcept(conceptId);
254
255   switch(operation) {
256     //case Operation::Sum:
257     // return (ptr_auxConceptData->Sum);
258   case Operation::Average:
259     return (ptr_auxConceptData->Average);
260   case Operation::StandardDeviation:
261     return (getStandardDeviation(ptr_auxConceptData));
262   case Operation::BesselStandardDeviation:
263     return (getBesselStandardDeviation(ptr_auxConceptData));
264   case Operation::Minimum:
265     return (ptr_auxConceptData->MinimumProcessed);
266   case Operation::Maximum:
267     return (ptr_auxConceptData->MaximumProcessed);
268   default:
269     throw anna::RuntimeException("Unrecognized operation", ANNA_FILE_LOCATION);
270   }
271
272   //if (ptr_auxConceptData == NULL) return ((double)0); // Not possible: getConcept initializes it if not found.
273 }
274
275
276 //------------------------------------------------------------------------------
277 //------------------------------------------------------ Accumulator::asString()
278 //------------------------------------------------------------------------------
279 std::string Accumulator::asString(const int & numberOfDecimals) const throw() {
280   std::string trace;
281   _concept_data_map_iter iter;
282   _concept_data_map_iter iter_min(a_concept_data_map.begin());
283   _concept_data_map_iter iter_max(a_concept_data_map.end());
284   time_t unixTimestamp;
285   time::Date time_now; // set current time on local TZ
286   time::Date time_aux; // for events
287   Engine& engine = Engine::instantiate();
288   std::string fFormat = floatFormat(numberOfDecimals);
289   trace =  "\n=====================";
290   trace +=  "\nStatistic Information";
291   trace +=  "\n=====================";
292   trace += "\nCurrent Time: ";
293   trace += time_now.asString();
294
295   if(a_concept_data_map.size() == 0) {
296     trace += "\nNo concept data found (empty map)";
297     return trace;
298   }
299
300   trace += "\nNumber of elements (measured concepts) = "; trace += anna::functions::asString(a_concept_data_map.size()); trace += "\n";
301   int conceptId;
302   std::string conceptDescription;
303   std::string conceptUnitDescription;
304   bool integerNatureSample;
305
306   for(iter = iter_min; iter != iter_max; iter++) {
307     conceptId = (*iter).first;
308     const _concept_data_t * ptrConceptData = &((*iter).second);
309     engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNatureSample);  // never launch exception
310     trace += "\n   Concept:              [";
311     trace += anna::functions::asString(conceptId);
312     trace += "] ";
313     trace += conceptDescription; trace += " ("; trace += conceptUnitDescription; trace += ")";
314
315     if(ptrConceptData->Size != 0) {
316       trace += "\n   Sample Size:                 "; trace += anna::functions::asString((anna::U64)(ptrConceptData->Size));
317       trace += "\n   Average:                     "; trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->Average);
318       trace += "\n   Standard Deviation:          "; trace += anna::functions::asString(fFormat.c_str(), getStandardDeviation(ptrConceptData));
319
320       if(ptrConceptData->Size != 1) {
321         trace += "\n   Bessel's Standard Deviation: "; trace += anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData));
322       }
323
324       trace += "\n   Minimum:                     ";
325
326       if(integerNatureSample)
327         trace += anna::functions::asString((int)(ptrConceptData->MinimumProcessed));
328       else
329         trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed);
330
331       // Minimum Timestamp:
332       unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
333       time_aux.storeUnix(unixTimestamp);
334       trace += " ("; trace += time_aux.asString(); trace += ")";
335       trace += "\n   Maximum:                     ";
336
337       if(integerNatureSample)
338         trace += anna::functions::asString((int)(ptrConceptData->MaximumProcessed));
339       else
340         trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed);
341
342       // Maximum Timestamp:
343       unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
344       time_aux.storeUnix(unixTimestamp);
345       trace += " ("; trace += time_aux.asString(); trace += ")";
346       // Last Reset Event:
347       unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
348       time_aux.storeUnix(unixTimestamp);
349       trace += "\n   Last Reset Timestamp:        "; trace += time_aux.asString();
350
351       // Processing Rate: it is not updated at 'process' to avoid 'time_now' use on every call
352       if(time_now != time_aux) {
353         trace += "\n   Processing rate:             ";
354         int lapse = time_now.getUnixTimestamp() - time_aux.getUnixTimestamp();
355         trace += anna::functions::asString(fFormat.c_str(), ptrConceptData->Size / ((double)lapse));
356         trace += " vps (values per second)";
357       }
358     } else {
359       trace += "\n   Not enough data to get statistics";
360     }
361   }
362
363   trace += "\n";
364   return (trace);
365 }
366
367
368 //------------------------------------------------------------------------------
369 //--------------------------------------------------------- Accumulator::asXML()
370 //------------------------------------------------------------------------------
371 anna::xml::Node* Accumulator::asXML(anna::xml::Node* parent, const int & numberOfDecimals) const throw() {
372   anna::xml::Node* result = parent->createChild("anna.statistics.Accumulator");
373   _concept_data_map_iter iter;
374   _concept_data_map_iter iter_min(a_concept_data_map.begin());
375   _concept_data_map_iter iter_max(a_concept_data_map.end());
376   time_t unixTimestamp;
377   time::Date time_now; // set current time on local TZ
378   time::Date time_aux; // for events
379   Engine& engine = Engine::instantiate();
380   std::string fFormat = floatFormat(numberOfDecimals);
381   //result->createAttribute("CurrentTime", time_now.asString());
382   anna::xml::Node* currentTime = result->createChild("CurrentTime");
383   time_now.asXML(currentTime);
384
385   if(a_concept_data_map.size() == 0) {
386     result->createAttribute("ConceptDataMapSize", "0: No concept data found (empty map)");
387     return result;
388   }
389
390   result->createAttribute("ConceptDataMapSize", anna::functions::asString(a_concept_data_map.size()));
391   int conceptId;
392   std::string conceptDescription;
393   std::string conceptUnitDescription;
394   bool integerNatureSample;
395   std::string cad_aux;
396
397   for(iter = iter_min; iter != iter_max; iter++) {
398     conceptId = (*iter).first;
399     const _concept_data_t * ptrConceptData = &((*iter).second);
400     engine.getConcept(conceptId, conceptDescription, conceptUnitDescription, integerNatureSample);  // never launch exception
401     anna::xml::Node* concept = result->createChild("Concept");
402     concept->createAttribute("Id", conceptId);
403     concept->createAttribute("Description", conceptDescription);
404     concept->createAttribute("Unit", conceptUnitDescription);
405     anna::xml::Node* data = concept->createChild("Data");
406
407     if(ptrConceptData->Size != 0) {
408       data->createAttribute("Size", anna::functions::asString((anna::U64)ptrConceptData->Size));
409       data->createAttribute("Average", anna::functions::asString(fFormat.c_str(), ptrConceptData->Average));
410       data->createAttribute("StandardDeviation", anna::functions::asString(fFormat.c_str(), getStandardDeviation(ptrConceptData)));
411
412       if(ptrConceptData->Size != 1)
413         data->createAttribute("BesselStandardDeviation", anna::functions::asString(fFormat.c_str(), getBesselStandardDeviation(ptrConceptData)));
414
415       // Minimum:
416       anna::xml::Node* minimum = data->createChild("Minimum");
417
418       if(integerNatureSample)
419         minimum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MinimumProcessed)));
420       else
421         minimum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MinimumProcessed));
422
423       unixTimestamp = (ptrConceptData->MinimumEventTimestampMs / 1000); // not neccessary such precision
424       time_aux.storeUnix(unixTimestamp);
425       time_aux.asXML(minimum);
426       // Maximum:
427       anna::xml::Node* maximum = data->createChild("Maximum");
428
429       if(integerNatureSample)
430         maximum->createAttribute("Value", anna::functions::asString((int)(ptrConceptData->MaximumProcessed)));
431       else
432         maximum->createAttribute("Value", anna::functions::asString(fFormat.c_str(), ptrConceptData->MaximumProcessed));
433
434       unixTimestamp = (ptrConceptData->MaximumEventTimestampMs / 1000); // not neccessary such precision
435       time_aux.storeUnix(unixTimestamp);
436       time_aux.asXML(maximum);
437       // Last Reset Event:
438       anna::xml::Node* lastResetEvent = data->createChild("LastResetTimestamp");
439       unixTimestamp = (ptrConceptData->LastResetEventTimestampMs / 1000); // not neccessary such precision
440       time_aux.storeUnix(unixTimestamp);
441       time_aux.asXML(lastResetEvent);
442
443       // Processing Rate: it is not updated at 'process' to avoid 'time_now' use on every call
444       if(time_now != time_aux) {
445         int lapse = time_now.getUnixTimestamp() - time_aux.getUnixTimestamp();
446         cad_aux = anna::functions::asString(fFormat.c_str(), ptrConceptData->Size / ((double)lapse)); cad_aux += " vps (values per second)";
447         data->createAttribute("ProcessingRate", cad_aux);
448       }
449     } else {
450       concept->createAttribute("Data", "Not enough data to get statistics");
451     }
452   }
453
454   return result;
455 }