Mantid
Loading...
Searching...
No Matches
TimeSeriesProperty.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
10#include "MantidKernel/Logger.h"
12
13#include <json/value.h>
14#include <nexus/NeXusFile.hpp>
15
16#include <boost/regex.hpp>
17#include <numeric>
18
19namespace Mantid {
20using namespace Types::Core;
21namespace Kernel {
22namespace {
24Logger g_log("TimeSeriesProperty");
25} // namespace
26
31template <typename TYPE>
33 : Property(name, typeid(std::vector<TimeValueUnit<TYPE>>)), m_values(), m_size(), m_propSortedFlag(),
34 m_filterApplied() {}
35
42template <typename TYPE>
44 const std::vector<Types::Core::DateAndTime> &times,
45 const std::vector<TYPE> &values)
46 : TimeSeriesProperty(name) {
47 addValues(times, values);
48}
49
51template <typename TYPE> TimeSeriesProperty<TYPE>::~TimeSeriesProperty() = default;
52
57 return new TimeSeriesProperty<TYPE>(*this);
58}
59
64template <typename TYPE> Property *TimeSeriesProperty<TYPE>::cloneWithTimeShift(const double timeShift) const {
65 auto timeSeriesProperty = this->clone();
66 auto values = timeSeriesProperty->valuesAsVector();
67 auto times = timeSeriesProperty->timesAsVector();
68 // Shift the time
69 for (auto it = times.begin(); it != times.end(); ++it) {
70 // There is a known issue which can cause cloneWithTimeShift to be called
71 // with a large (~9e+9 s) shift. Actual shifting is capped to be ~4.6e+19
72 // seconds in DateAndTime::operator+=
73 (*it) += timeShift;
74 }
75 timeSeriesProperty->clear();
76 timeSeriesProperty->addValues(times, values);
77 return timeSeriesProperty;
78}
79
87template <typename TYPE> std::unique_ptr<TimeSeriesProperty<double>> TimeSeriesProperty<TYPE>::getDerivative() const {
88
89 if (this->m_values.size() < 2) {
90 throw std::runtime_error("Derivative is not defined for a time-series "
91 "property with less then two values");
92 }
93
94 this->sortIfNecessary();
95 auto it = this->m_values.begin();
96 int64_t t0 = it->time().totalNanoseconds();
97 TYPE v0 = it->value();
99 it++;
100 auto timeSeriesDeriv = std::make_unique<TimeSeriesProperty<double>>(this->name() + "_derivative");
101 timeSeriesDeriv->reserve(this->m_values.size() - 1);
102 for (; it != m_values.end(); it++) {
103 TYPE v1 = it->value();
104 int64_t t1 = it->time().totalNanoseconds();
105 if (t1 != t0) {
106 double deriv = 1.e+9 * (double(v1 - v0) / double(t1 - t0));
107 auto tm = static_cast<int64_t>((t1 + t0) / 2);
108 timeSeriesDeriv->addValue(Types::Core::DateAndTime(tm), deriv);
109 }
110 t0 = t1;
111 v0 = v1;
112 }
113 return timeSeriesDeriv;
116template <> std::unique_ptr<TimeSeriesProperty<double>> TimeSeriesProperty<std::string>::getDerivative() const {
117 throw std::runtime_error("Time series property derivative is not defined for strings");
119
123template <typename TYPE> size_t TimeSeriesProperty<TYPE>::getMemorySize() const {
124 // Rough estimate
125 return m_values.size() * (sizeof(TYPE) + sizeof(DateAndTime));
127
134 return operator+=(rhs);
135}
143 auto const *rhs = dynamic_cast<TimeSeriesProperty<TYPE> const *>(right);
144
145 if (rhs) {
146 if (this->operator!=(*rhs)) {
147 m_values.insert(m_values.end(), rhs->m_values.begin(), rhs->m_values.end());
148 m_propSortedFlag = TimeSeriesSortStatus::TSUNKNOWN;
149 } else {
150 // Do nothing if appending yourself to yourself. The net result would be
151 // the same anyway
153 }
154
155 // Count the REAL size.
156 m_size = static_cast<int>(m_values.size());
158 } else
159 g_log.warning() << "TimeSeriesProperty " << this->name()
160 << " could not be added to another property of the same "
161 "name but incompatible type.\n";
162
163 return *this;
164}
165
171template <typename TYPE> bool TimeSeriesProperty<TYPE>::operator==(const TimeSeriesProperty<TYPE> &right) const {
172 sortIfNecessary();
174 if (this->name() != right.name()) // should this be done?
175 {
176 return false;
177 }
179 if (this->m_size != right.m_size) {
180 return false;
182
183 if (this->realSize() != right.realSize()) {
184 return false;
185 } else {
186 const std::vector<DateAndTime> lhsTimes = this->timesAsVector();
187 const std::vector<DateAndTime> rhsTimes = right.timesAsVector();
188 if (!std::equal(lhsTimes.begin(), lhsTimes.end(), rhsTimes.begin())) {
189 return false;
190 }
192 const std::vector<TYPE> lhsValues = this->valuesAsVector();
193 const std::vector<TYPE> rhsValues = right.valuesAsVector();
194 if (!std::equal(lhsValues.begin(), lhsValues.end(), rhsValues.begin())) {
195 return false;
197 }
199 return true;
201
207template <typename TYPE> bool TimeSeriesProperty<TYPE>::operator==(const Property &right) const {
208 auto rhs_tsp = dynamic_cast<const TimeSeriesProperty<TYPE> *>(&right);
209 if (!rhs_tsp)
210 return false;
211 return this->operator==(*rhs_tsp);
213
219template <typename TYPE> bool TimeSeriesProperty<TYPE>::operator!=(const TimeSeriesProperty<TYPE> &right) const {
220 return !(*this == right);
222
228template <typename TYPE> bool TimeSeriesProperty<TYPE>::operator!=(const Property &right) const {
229 return !(*this == right);
230}
235template <typename TYPE> void TimeSeriesProperty<TYPE>::setName(const std::string &name) { m_name = name; }
259template <typename TYPE>
260void TimeSeriesProperty<TYPE>::filterByTime(const Types::Core::DateAndTime &start,
261 const Types::Core::DateAndTime &stop) {
262 // 0. Sort
263 sortIfNecessary();
265 // 1. Do nothing for single (constant) value
266 if (m_values.size() <= 1)
267 return;
268
269 typename std::vector<TimeValueUnit<TYPE>>::iterator iterhead, iterend;
271 // 2. Determine index for start and remove Note erase is [...)
272 int istart = this->findIndex(start);
273 if (istart >= 0 && static_cast<size_t>(istart) < m_values.size()) {
274 // "start time" is behind time-series's starting time
275 iterhead = m_values.begin() + istart;
276
277 // False - The filter time is on the mark. Erase [begin(), istart)
278 // True - The filter time is larger than T[istart]. Erase[begin(), istart)
279 // ...
280 // filter start(time) and move istart to filter startime
281 bool useprefiltertime = !(m_values[istart].time() == start);
282
283 // Remove the series
284 m_values.erase(m_values.begin(), iterhead);
285
286 if (useprefiltertime) {
287 m_values[0].setTime(start);
288 }
289 } else {
290 // "start time" is before/after time-series's starting time: do nothing
291 ;
293
294 // 3. Determine index for end and remove Note erase is [...)
295 int iend = this->findIndex(stop);
296 if (static_cast<size_t>(iend) < m_values.size()) {
297 if (m_values[iend].time() == stop) {
298 // Filter stop is on a log. Delete that log
299 iterend = m_values.begin() + iend;
300 } else {
301 // Filter stop is behind iend. Keep iend
302 iterend = m_values.begin() + iend + 1;
304 // Delete from [iend to mp.end)
305 m_values.erase(iterend, m_values.end());
306 }
307
308 // 4. Make size consistent
309 m_size = static_cast<int>(m_values.size());
311
317template <typename TYPE>
318void TimeSeriesProperty<TYPE>::filterByTimes(const std::vector<SplittingInterval> &splittervec) {
319 // 1. Sort
320 sortIfNecessary();
321
322 // 2. Return for single value
323 if (m_values.size() <= 1) {
324 return;
325 }
326
327 // 3. Prepare a copy
328 std::vector<TimeValueUnit<TYPE>> mp_copy;
329
330 g_log.debug() << "DB541 mp_copy Size = " << mp_copy.size() << " Original MP Size = " << m_values.size() << "\n";
331
332 // 4. Create new
333 for (const auto &splitter : splittervec) {
334 Types::Core::DateAndTime t_start = splitter.start();
335 Types::Core::DateAndTime t_stop = splitter.stop();
336
337 int tstartindex = findIndex(t_start);
338 if (tstartindex < 0) {
339 // The splitter is not well defined, and use the first
340 tstartindex = 0;
341 } else if (tstartindex >= int(m_values.size())) {
342 // The splitter is not well defined, adn use the last
343 tstartindex = int(m_values.size()) - 1;
344 }
345
346 int tstopindex = findIndex(t_stop);
347
348 if (tstopindex < 0) {
349 tstopindex = 0;
350 } else if (tstopindex >= int(m_values.size())) {
351 tstopindex = int(m_values.size()) - 1;
352 } else {
353 if (t_stop == m_values[size_t(tstopindex)].time() && size_t(tstopindex) > 0) {
354 tstopindex--;
355 }
356 }
357
358 /* Check */
359 if (tstartindex < 0 || tstopindex >= int(m_values.size())) {
360 g_log.warning() << "Memory Leak In SplitbyTime!\n";
361 }
362
363 if (tstartindex == tstopindex) {
364 TimeValueUnit<TYPE> temp(t_start, m_values[tstartindex].value());
365 mp_copy.emplace_back(temp);
366 } else {
367 mp_copy.emplace_back(t_start, m_values[tstartindex].value());
368 for (auto im = size_t(tstartindex + 1); im <= size_t(tstopindex); ++im) {
369 mp_copy.emplace_back(m_values[im].time(), m_values[im].value());
370 }
371 }
372 } // ENDFOR
373
374 g_log.debug() << "DB530 Filtered Log Size = " << mp_copy.size() << " Original Log Size = " << m_values.size()
375 << "\n";
376
377 // 5. Clear
378 m_values.clear();
379 m_values = mp_copy;
380 mp_copy.clear();
381
382 m_size = static_cast<int>(m_values.size());
383}
384
399template <typename TYPE>
400void TimeSeriesProperty<TYPE>::splitByTime(std::vector<SplittingInterval> &splitter, std::vector<Property *> outputs,
401 bool isPeriodic) const {
402 // 0. Sort if necessary
403 sortIfNecessary();
404
405 if (outputs.empty())
406 return;
407
408 std::vector<TimeSeriesProperty<TYPE> *> outputs_tsp;
409
410 size_t numOutputs = outputs.size();
411 // 1. Clear the outputs before you start
412 for (size_t i = 0; i < numOutputs; i++) {
413 auto *myOutput = dynamic_cast<TimeSeriesProperty<TYPE> *>(outputs[i]);
414 if (myOutput) {
415 outputs_tsp.emplace_back(myOutput);
416 if (this->m_values.size() == 1) {
417 // Special case for TSP with a single entry = just copy.
418 myOutput->m_values = this->m_values;
419 myOutput->m_size = 1;
420 } else {
421 myOutput->m_values.clear();
422 myOutput->m_size = 0;
423 }
424 } else {
425 outputs_tsp.emplace_back(nullptr);
426 }
427 }
428
429 // 2. Special case for TSP with a single entry = just copy.
430 if (this->m_values.size() == 1)
431 return;
432
433 // 3. We will be iterating through all the entries in the the map/vector
434 size_t i_property = 0;
435
436 // And at the same time, iterate through the splitter
437 auto itspl = splitter.begin();
438
439 size_t counter = 0;
440 g_log.debug() << "[DB] Number of time series entries = " << m_values.size()
441 << ", Number of splitters = " << splitter.size() << "\n";
442 while (itspl != splitter.end() && i_property < m_values.size()) {
443 // Get the splitting interval times and destination
444 DateAndTime start = itspl->start();
445 DateAndTime stop = itspl->stop();
446
447 int output_index = itspl->index();
448 // output workspace index is out of range. go to the next splitter
449 if (output_index < 0 || output_index >= static_cast<int>(numOutputs))
450 continue;
451
452 TimeSeriesProperty<TYPE> *myOutput = outputs_tsp[output_index];
453 // skip if the input property is of wrong type
454 if (!myOutput) {
455 ++itspl;
456 ++counter;
457 continue;
458 }
459
460 // Skip the events before the start of the time
461 while (i_property < m_values.size() && m_values[i_property].time() < start)
462 ++i_property;
463
464 if (i_property == m_values.size()) {
465 // i_property is out of the range. Then use the last entry
466 myOutput->addValue(m_values[i_property - 1].time(), m_values[i_property - 1].value());
467 break;
468 }
469
470 // The current entry is within an interval. Record them until out
471 if (m_values[i_property].time() > start && i_property > 0 && !isPeriodic) {
472 // Record the previous oneif this property is not exactly on start time
473 // and this entry is not recorded
474 size_t i_prev = i_property - 1;
475 if (myOutput->size() == 0 || m_values[i_prev].time() != myOutput->lastTime())
476 myOutput->addValue(m_values[i_prev].time(), m_values[i_prev].value());
477 }
478
479 // Loop through all the entries until out.
480 while (i_property < m_values.size() && m_values[i_property].time() < stop) {
481
482 // Copy the log out to the output
483 myOutput->addValue(m_values[i_property].time(), m_values[i_property].value());
484 ++i_property;
485 }
486
487 // Go to the next interval
488 ++itspl;
489 ++counter;
490 } // Looping through entries in the splitter vector
491
492 // Make sure all entries have the correct size recorded in m_size.
493 for (std::size_t i = 0; i < numOutputs; i++) {
494 auto *myOutput = dynamic_cast<TimeSeriesProperty<TYPE> *>(outputs[i]);
495 if (myOutput) {
496 myOutput->m_size = myOutput->realSize();
497 }
498 }
499}
500
505template <typename TYPE>
506void TimeSeriesProperty<TYPE>::splitByTimeVector(const std::vector<DateAndTime> &timeToFilterTo,
507 const std::vector<int> &inputWorkspaceIndicies,
508 const std::vector<TimeSeriesProperty *> &output) {
509
510 // check inputs
511 if (timeToFilterTo.size() != inputWorkspaceIndicies.size() + 1) {
512 throw std::runtime_error("Input time vector's size does not match(one more larger than) target "
513 "workspace index vector's size inputWorkspaceIndicies.size() \n");
514 }
515
516 // return if the output vector TimeSeriesProperties is not defined
517 if (output.empty())
518 return;
519
520 sortIfNecessary();
521
522 // work on m_values, m_size, and m_time
523 auto const currentTimes = timesAsVector();
524 auto const currentValues = valuesAsVector();
525 size_t index_splitter = 0;
526
527 // move splitter index such that the first entry of TSP is before the stop
528 // time of a splitter
529 DateAndTime firstPropTime = currentTimes[0];
530 auto firstFilterTime = std::lower_bound(timeToFilterTo.begin(), timeToFilterTo.end(), firstPropTime);
531 if (firstFilterTime == timeToFilterTo.end()) {
532 // do nothing as the first TimeSeriesProperty entry's time is before any
533 // splitters
534 return;
535 } else if (firstFilterTime != timeToFilterTo.begin()) {
536 // calculate the splitter's index (now we check the stop time)
537 index_splitter = firstFilterTime - timeToFilterTo.begin() - 1;
538 }
539
540 DateAndTime filterStartTime = timeToFilterTo[index_splitter];
541 DateAndTime filterEndTime;
542
543 // move along the entries to find the entry inside the current splitter
544 auto firstEntryInSplitter = std::lower_bound(currentTimes.begin(), currentTimes.end(), filterStartTime);
545 if (firstEntryInSplitter == currentTimes.end()) {
546 // the first splitter's start time is LATER than the last TSP entry, then
547 // there won't be any
548 // TSP entry to be split into any wsIndex splitter.
549 DateAndTime last_entry_time = this->lastTime();
550 TYPE last_entry_value = this->lastValue();
551 for (auto &i : output) {
552 i->addValue(last_entry_time, last_entry_value);
553 }
554 return;
555 }
556
557 // first splitter start time is between firstEntryInSplitter and the one
558 // before it. so the index for firstEntryInSplitter is the first TSP entry
559 // in the splitter
560 size_t timeIndex = firstEntryInSplitter - currentTimes.begin();
561 firstPropTime = *firstEntryInSplitter;
562
563 for (; index_splitter < timeToFilterTo.size() - 1; ++index_splitter) {
564 int wsIndex = inputWorkspaceIndicies[index_splitter];
565
566 filterStartTime = timeToFilterTo[index_splitter];
567 filterEndTime = timeToFilterTo[index_splitter + 1];
568
569 // get the first entry index (overlap)
570 if (timeIndex > 0)
571 --timeIndex;
572
573 // add the continuous entries to same wsIndex time series property
574 const size_t numEntries = currentTimes.size();
575
576 // Add properties to the current wsIndex.
577 if (timeIndex >= numEntries) {
578 // We have run out of TSP entries, so use the last TSP value
579 // for all remaining outputs
580 auto currentTime = currentTimes.back();
581 if (output[wsIndex]->size() == 0 || output[wsIndex]->lastTime() != currentTime) {
582 output[wsIndex]->addValue(currentTime, currentValues.back());
583 }
584 } else {
585 // Add TSP values until we run out or go past the current filter
586 // end time.
587 for (; timeIndex < numEntries; ++timeIndex) {
588 auto currentTime = currentTimes[timeIndex];
589 if (output[wsIndex]->size() == 0 || output[wsIndex]->lastTime() < currentTime) {
590 // avoid to add duplicate entry
591 output[wsIndex]->addValue(currentTime, currentValues[timeIndex]);
592 }
593 if (currentTime > filterEndTime)
594 break;
595 }
596 }
597 }
598
599 // Add a debugging check such that there won't be any time entry with zero log
600 for (size_t i = 0; i < output.size(); ++i) {
601 if (output[i]->size() == 0) {
602 std::stringstream errss;
603 errss << "entry " << m_name << " has 0 size, whose first entry is at " << this->firstTime().toSimpleString();
604 g_log.warning(errss.str());
605 }
606 }
607}
608
609// The makeFilterByValue & expandFilterToRange methods generate a bunch of
610// warnings when the template type is the wider integer types
611// (when it's being assigned back to a double such as in a call to minValue or
612// firstValue)
613// However, in reality these methods are only used for TYPE=int or double (they
614// are only called from FilterByLogValue) so suppress the warnings
615#ifdef _WIN32
616#pragma warning(push)
617#pragma warning(disable : 4244)
618#pragma warning(disable : 4804) // This one comes about for TYPE=bool - again
619 // the method is never called for this type
620#endif
621#if defined(__GNUC__) && !(defined(__INTEL_COMPILER))
622#pragma GCC diagnostic ignored "-Wconversion"
623#endif
624
638template <typename TYPE>
639void TimeSeriesProperty<TYPE>::makeFilterByValue(std::vector<SplittingInterval> &split, double min, double max,
640 double TimeTolerance, bool centre) const {
641 const bool emptyMin = (min == EMPTY_DBL());
642 const bool emptyMax = (max == EMPTY_DBL());
643
644 if (!emptyMin && !emptyMax && max < min) {
645 std::stringstream ss;
646 ss << "TimeSeriesProperty::makeFilterByValue: 'max' argument must be "
647 "greater than 'min' "
648 << "(got min=" << min << " max=" << max << ")";
649 throw std::invalid_argument(ss.str());
650 }
651
652 // If min or max were unset ("empty") in the algorithm, set to the min or max
653 // value of the log
654 if (emptyMin)
655 min = static_cast<double>(minValue());
656 if (emptyMax)
657 max = static_cast<double>(maxValue());
658
659 // Make sure the splitter starts out empty
660 split.clear();
661
662 // Do nothing if the log is empty.
663 if (m_values.empty())
664 return;
665
666 // 1. Sort
667 sortIfNecessary();
668
669 // 2. Do the rest
670 bool lastGood(false);
671 time_duration tol = DateAndTime::durationFromSeconds(TimeTolerance);
672 int numgood = 0;
673 DateAndTime t;
674 DateAndTime start, stop;
675
676 for (size_t i = 0; i < m_values.size(); ++i) {
677 const DateAndTime lastTime = t;
678 // The new entry
679 t = m_values[i].time();
680 TYPE val = m_values[i].value();
681
682 // A good value?
683 const bool isGood = ((val >= min) && (val <= max));
684 if (isGood)
685 numgood++;
686
687 if (isGood != lastGood) {
688 // We switched from bad to good or good to bad
689
690 if (isGood) {
691 // Start of a good section. Subtract tolerance from the time if
692 // boundaries are centred.
693 start = centre ? t - tol : t;
694 } else {
695 // End of the good section. Add tolerance to the LAST GOOD time if
696 // boundaries are centred.
697 // Otherwise, use the first 'bad' time.
698 stop = centre ? lastTime + tol : t;
699 split.emplace_back(start, stop, 0);
700 // Reset the number of good ones, for next time
701 numgood = 0;
702 }
703 lastGood = isGood;
704 }
705 }
706
707 if (numgood > 0) {
708 // The log ended on "good" so we need to close it using the last time we
709 // found
710 stop = t + tol;
711 split.emplace_back(start, stop, 0);
712 }
713}
714
718template <>
719void TimeSeriesProperty<std::string>::makeFilterByValue(std::vector<SplittingInterval> & /*split*/, double /*min*/,
720 double /*max*/, double /*TimeTolerance*/,
721 bool /*centre*/) const {
722 throw Exception::NotImplementedError("TimeSeriesProperty::makeFilterByValue "
723 "is not implemented for string "
724 "properties");
725}
726
737template <typename TYPE>
738void TimeSeriesProperty<TYPE>::expandFilterToRange(std::vector<SplittingInterval> &split, double min, double max,
739 const TimeInterval &range) const {
740 const bool emptyMin = (min == EMPTY_DBL());
741 const bool emptyMax = (max == EMPTY_DBL());
742
743 if (!emptyMin && !emptyMax && max < min) {
744 std::stringstream ss;
745 ss << "TimeSeriesProperty::expandFilterToRange: 'max' argument must be "
746 "greater than 'min' "
747 << "(got min=" << min << " max=" << max << ")";
748 throw std::invalid_argument(ss.str());
749 }
750
751 // If min or max were unset ("empty") in the algorithm, set to the min or max
752 // value of the log
753 if (emptyMin)
754 min = static_cast<double>(minValue());
755 if (emptyMax)
756 max = static_cast<double>(maxValue());
757
758 // Assume everything before the 1st value is constant
759 double val = static_cast<double>(firstValue());
760 if ((val >= min) && (val <= max)) {
761 TimeSplitterType extraFilter;
762 extraFilter.emplace_back(range.begin(), firstTime(), 0);
763 // Include everything from the start of the run to the first time measured
764 // (which may be a null time interval; this'll be ignored)
765 split = split | extraFilter;
766 }
767
768 // Assume everything after the LAST value is constant
769 val = static_cast<double>(lastValue());
770 if ((val >= min) && (val <= max)) {
771 TimeSplitterType extraFilter;
772 extraFilter.emplace_back(lastTime(), range.end(), 0);
773 // Include everything from the start of the run to the first time measured
774 // (which may be a null time interval; this'll be ignored)
775 split = split | extraFilter;
776 }
777}
778
782template <>
783void TimeSeriesProperty<std::string>::expandFilterToRange(std::vector<SplittingInterval> & /*split*/, double /*min*/,
784 double /*max*/, const TimeInterval & /*range*/) const {
785 throw Exception::NotImplementedError("TimeSeriesProperty::makeFilterByValue "
786 "is not implemented for string "
787 "properties");
788}
789
793template <typename TYPE> double TimeSeriesProperty<TYPE>::timeAverageValue() const {
794 double retVal = 0.0;
795 try {
796 const auto &filter = getSplittingIntervals();
797 retVal = this->averageValueInFilter(filter);
798 } catch (std::exception &) {
799 // just return nan
800 retVal = std::numeric_limits<double>::quiet_NaN();
801 }
802 return retVal;
803}
804
811template <typename TYPE>
812double TimeSeriesProperty<TYPE>::averageValueInFilter(const std::vector<SplittingInterval> &filter) const {
813 // TODO: Consider logs that aren't giving starting values.
814
815 // First of all, if the log or the filter is empty, return NaN
816 if (realSize() == 0 || filter.empty()) {
817 return std::numeric_limits<double>::quiet_NaN();
818 }
819
820 // If there's just a single value in the log, return that.
821 if (realSize() == 1) {
822 return static_cast<double>(m_values.front().value());
823 }
824
825 sortIfNecessary();
826
827 double numerator(0.0), totalTime(0.0);
828 // Loop through the filter ranges
829 for (const auto &time : filter) {
830 // Calculate the total time duration (in seconds) within by the filter
831 totalTime += time.duration();
832
833 // Get the log value and index at the start time of the filter
834 int index;
835 double value = static_cast<double>(getSingleValue(time.start(), index));
836 DateAndTime startTime = time.start();
837
838 while (index < realSize() - 1 && m_values[index + 1].time() < time.stop()) {
839 ++index;
840 numerator += DateAndTime::secondsFromDuration(m_values[index].time() - startTime) * value;
841 startTime = m_values[index].time();
842 value = static_cast<double>(m_values[index].value());
843 }
844
845 // Now close off with the end of the current filter range
846 numerator += DateAndTime::secondsFromDuration(time.stop() - startTime) * value;
847 }
848
849 // 'Normalise' by the total time
850 return numerator / totalTime;
851}
852
857 throw Exception::NotImplementedError("TimeSeriesProperty::"
858 "averageValueInFilter is not "
859 "implemented for string properties");
860}
861
862template <typename TYPE> std::pair<double, double> TimeSeriesProperty<TYPE>::timeAverageValueAndStdDev() const {
863 std::pair<double, double> retVal{0., 0.}; // mean and stddev
864 try {
865 const auto &filter = getSplittingIntervals();
866 retVal = this->averageAndStdDevInFilter(filter);
867 } catch (std::exception &) {
868 retVal.first = std::numeric_limits<double>::quiet_NaN();
869 retVal.second = std::numeric_limits<double>::quiet_NaN();
870 }
871 return retVal;
872}
873
874template <typename TYPE>
875std::pair<double, double>
876TimeSeriesProperty<TYPE>::averageAndStdDevInFilter(const std::vector<SplittingInterval> &filter) const {
877 // the mean to calculate the standard deviation about
878 // this will sort the log as necessary as well
879 const double mean = this->averageValueInFilter(filter);
880
881 // First of all, if the log or the filter is empty or is a single value,
882 // return NaN for the uncertainty
883 if (realSize() <= 1 || filter.empty()) {
884 return std::pair<double, double>{mean, std::numeric_limits<double>::quiet_NaN()};
885 }
886
887 double numerator(0.0), totalTime(0.0);
888 // Loop through the filter ranges
889 for (const auto &time : filter) {
890 // Calculate the total time duration (in seconds) within by the filter
891 totalTime += time.duration();
892
893 // Get the log value and index at the start time of the filter
894 int index;
895 double value = static_cast<double>(getSingleValue(time.start(), index));
896 double valuestddev = (value - mean) * (value - mean);
897 DateAndTime startTime = time.start();
898
899 while (index < realSize() - 1 && m_values[index + 1].time() < time.stop()) {
900 ++index;
901
902 numerator += DateAndTime::secondsFromDuration(m_values[index].time() - startTime) * valuestddev;
903 startTime = m_values[index].time();
904 value = static_cast<double>(m_values[index].value());
905 valuestddev = (value - mean) * (value - mean);
906 }
907
908 // Now close off with the end of the current filter range
909 numerator += DateAndTime::secondsFromDuration(time.stop() - startTime) * valuestddev;
910 }
911
912 // Normalise by the total time
913 return std::pair<double, double>{mean, std::sqrt(numerator / totalTime)};
914}
915
919template <>
920std::pair<double, double>
922 throw Exception::NotImplementedError("TimeSeriesProperty::"
923 "averageAndStdDevInFilter is not "
924 "implemented for string properties");
925}
926
927// Re-enable the warnings disabled before makeFilterByValue
928#ifdef _WIN32
929#pragma warning(pop)
930#endif
931#if defined(__GNUC__) && !(defined(__INTEL_COMPILER))
932#pragma GCC diagnostic warning "-Wconversion"
933#endif
934
941template <typename TYPE> std::map<DateAndTime, TYPE> TimeSeriesProperty<TYPE>::valueAsCorrectMap() const {
942 // 1. Sort if necessary
943 sortIfNecessary();
944
945 // 2. Data Strcture
946 std::map<DateAndTime, TYPE> asMap;
947
948 if (!m_values.empty()) {
949 for (size_t i = 0; i < m_values.size(); i++)
950 asMap[m_values[i].time()] = m_values[i].value();
951 }
952
953 return asMap;
954}
955
960template <typename TYPE> std::vector<TYPE> TimeSeriesProperty<TYPE>::valuesAsVector() const {
961 sortIfNecessary();
962
963 std::vector<TYPE> out;
964 out.reserve(m_values.size());
965
966 for (size_t i = 0; i < m_values.size(); i++)
967 out.emplace_back(m_values[i].value());
968
969 return out;
970}
971
978template <typename TYPE> std::multimap<DateAndTime, TYPE> TimeSeriesProperty<TYPE>::valueAsMultiMap() const {
979 std::multimap<DateAndTime, TYPE> asMultiMap;
980
981 if (!m_values.empty()) {
982 for (size_t i = 0; i < m_values.size(); i++)
983 asMultiMap.insert(std::make_pair(m_values[i].time(), m_values[i].value()));
984 }
985
986 return asMultiMap;
987}
988
993template <typename TYPE> std::vector<DateAndTime> TimeSeriesProperty<TYPE>::timesAsVector() const {
994 sortIfNecessary();
995
996 std::vector<DateAndTime> out;
997 out.reserve(m_values.size());
998
999 for (size_t i = 0; i < m_values.size(); i++) {
1000 out.emplace_back(m_values[i].time());
1001 }
1002
1003 return out;
1004}
1005
1010template <typename TYPE> std::vector<DateAndTime> TimeSeriesProperty<TYPE>::filteredTimesAsVector() const {
1011 if (m_filter.empty()) {
1012 return this->timesAsVector(); // no filtering to do
1013 }
1014 if (!m_filterApplied) {
1015 applyFilter();
1016 }
1017 sortIfNecessary();
1018
1019 std::vector<DateAndTime> out;
1020
1021 for (const auto &value : m_values) {
1022 if (isTimeFiltered(value.time())) {
1023 out.emplace_back(value.time());
1024 }
1025 }
1026
1027 return out;
1028}
1029
1034template <typename TYPE> std::vector<double> TimeSeriesProperty<TYPE>::timesAsVectorSeconds() const {
1035 // 1. Sort if necessary
1036 sortIfNecessary();
1037
1038 // 2. Output data structure
1039 std::vector<double> out;
1040 out.reserve(m_values.size());
1041
1042 Types::Core::DateAndTime start = m_values[0].time();
1043 for (size_t i = 0; i < m_values.size(); i++) {
1044 out.emplace_back(DateAndTime::secondsFromDuration(m_values[i].time() - start));
1045 }
1046
1047 return out;
1048}
1049
1055template <typename TYPE>
1056void TimeSeriesProperty<TYPE>::addValue(const Types::Core::DateAndTime &time, const TYPE &value) {
1057 TimeValueUnit<TYPE> newvalue(time, value);
1058 // Add the value to the back of the vector
1059 m_values.emplace_back(newvalue);
1060 // Increment the separate record of the property's size
1061 m_size++;
1062
1063 // Toggle the sorted flag if necessary
1064 // (i.e. if the flag says we're sorted and the added time is before the prior
1065 // last time)
1066 if (m_size == 1) {
1067 // First item, must be sorted.
1068 m_propSortedFlag = TimeSeriesSortStatus::TSSORTED;
1069 } else if (m_propSortedFlag == TimeSeriesSortStatus::TSUNKNOWN && m_values.back() < *(m_values.rbegin() + 1)) {
1070 // Previously unknown and still unknown
1071 m_propSortedFlag = TimeSeriesSortStatus::TSUNSORTED;
1072 } else if (m_propSortedFlag == TimeSeriesSortStatus::TSSORTED && m_values.back() < *(m_values.rbegin() + 1)) {
1073 // Previously sorted but last added is not in order
1074 m_propSortedFlag = TimeSeriesSortStatus::TSUNSORTED;
1075 }
1076
1077 m_filterApplied = false;
1078}
1079
1086template <typename TYPE> void TimeSeriesProperty<TYPE>::addValue(const std::string &time, const TYPE &value) {
1087 return addValue(Types::Core::DateAndTime(time), value);
1088}
1089
1096template <typename TYPE> void TimeSeriesProperty<TYPE>::addValue(const std::time_t &time, const TYPE &value) {
1097 Types::Core::DateAndTime dt;
1098 dt.set_from_time_t(time);
1099 return addValue(dt, value);
1100}
1101
1107template <typename TYPE>
1108void TimeSeriesProperty<TYPE>::addValues(const std::vector<Types::Core::DateAndTime> &times,
1109 const std::vector<TYPE> &values) {
1110 size_t length = std::min(times.size(), values.size());
1111 m_size += static_cast<int>(length);
1112 for (size_t i = 0; i < length; ++i) {
1113 m_values.emplace_back(times[i], values[i]);
1114 }
1115
1116 if (!values.empty())
1117 m_propSortedFlag = TimeSeriesSortStatus::TSUNKNOWN;
1118}
1119
1125template <typename TYPE>
1126void TimeSeriesProperty<TYPE>::replaceValues(const std::vector<Types::Core::DateAndTime> &times,
1127 const std::vector<TYPE> &values) {
1128 clear();
1129 addValues(times, values);
1130}
1131
1136template <typename TYPE> DateAndTime TimeSeriesProperty<TYPE>::lastTime() const {
1137 if (m_values.empty()) {
1138 const std::string error("lastTime(): TimeSeriesProperty '" + name() + "' is empty");
1139 g_log.debug(error);
1140 throw std::runtime_error(error);
1141 }
1142
1143 sortIfNecessary();
1144
1145 return m_values.rbegin()->time();
1146}
1147
1151template <typename TYPE> TYPE TimeSeriesProperty<TYPE>::firstValue() const {
1152 if (m_values.empty()) {
1153 const std::string error("firstValue(): TimeSeriesProperty '" + name() + "' is empty");
1154 g_log.debug(error);
1155 throw std::runtime_error(error);
1156 }
1157
1158 sortIfNecessary();
1159
1160 return m_values[0].value();
1161}
1162
1166template <typename TYPE> DateAndTime TimeSeriesProperty<TYPE>::firstTime() const {
1167 if (m_values.empty()) {
1168 const std::string error("firstTime(): TimeSeriesProperty '" + name() + "' is empty");
1169 g_log.debug(error);
1170 throw std::runtime_error(error);
1171 }
1172
1173 sortIfNecessary();
1174
1175 return m_values[0].time();
1176}
1177
1182template <typename TYPE> TYPE TimeSeriesProperty<TYPE>::lastValue() const {
1183 if (m_values.empty()) {
1184 const std::string error("lastValue(): TimeSeriesProperty '" + name() + "' is empty");
1185 g_log.debug(error);
1186 throw std::runtime_error(error);
1187 }
1188
1189 sortIfNecessary();
1190
1191 return m_values.rbegin()->value();
1192}
1193
1194template <typename TYPE> TYPE TimeSeriesProperty<TYPE>::minValue() const {
1195 return std::min_element(m_values.begin(), m_values.end(), TimeValueUnit<TYPE>::valueCmp)->value();
1196}
1197
1198template <typename TYPE> TYPE TimeSeriesProperty<TYPE>::maxValue() const {
1199 return std::max_element(m_values.begin(), m_values.end(), TimeValueUnit<TYPE>::valueCmp)->value();
1200}
1201
1202template <typename TYPE> double TimeSeriesProperty<TYPE>::mean() const {
1203 Mantid::Kernel::Statistics raw_stats =
1204 Mantid::Kernel::getStatistics(this->filteredValuesAsVector(), StatOptions::Mean);
1205 return raw_stats.mean;
1206}
1207
1210template <typename TYPE> int TimeSeriesProperty<TYPE>::size() const { return m_size; }
1211
1216template <typename TYPE> int TimeSeriesProperty<TYPE>::realSize() const { return static_cast<int>(m_values.size()); }
1217
1218/*
1219 * Get the time series property as a string of 'time value'
1220 * @return time series property as a string
1221 */
1222template <typename TYPE> std::string TimeSeriesProperty<TYPE>::value() const {
1223 sortIfNecessary();
1224
1225 std::stringstream ins;
1226 for (size_t i = 0; i < m_values.size(); i++) {
1227 try {
1228 ins << m_values[i].time().toSimpleString();
1229 ins << " " << m_values[i].value() << "\n";
1230 } catch (...) {
1231 // Some kind of error; for example, invalid year, can occur when
1232 // converting boost time.
1233 ins << "Error Error"
1234 << "\n";
1235 }
1236 }
1237
1238 return ins.str();
1239}
1240
1245template <typename TYPE> std::vector<std::string> TimeSeriesProperty<TYPE>::time_tValue() const {
1246 sortIfNecessary();
1247
1248 std::vector<std::string> values;
1249 values.reserve(m_values.size());
1250
1251 for (size_t i = 0; i < m_values.size(); i++) {
1252 std::stringstream line;
1253 line << m_values[i].time().toSimpleString() << " " << m_values[i].value();
1254 values.emplace_back(line.str());
1255 }
1256
1257 return values;
1258}
1259
1267template <typename TYPE> std::map<DateAndTime, TYPE> TimeSeriesProperty<TYPE>::valueAsMap() const {
1268 // 1. Sort if necessary
1269 sortIfNecessary();
1270
1271 // 2. Build map
1272
1273 std::map<DateAndTime, TYPE> asMap;
1274 if (m_values.empty())
1275 return asMap;
1276
1277 TYPE d = m_values[0].value();
1278 asMap[m_values[0].time()] = d;
1279
1280 for (size_t i = 1; i < m_values.size(); i++) {
1281 if (m_values[i].value() != d) {
1282 // Only put entry with different value from last entry to map
1283 asMap[m_values[i].time()] = m_values[i].value();
1284 d = m_values[i].value();
1285 }
1286 }
1287 return asMap;
1288}
1289
1295template <typename TYPE> std::string TimeSeriesProperty<TYPE>::setValue(const std::string & /*unused*/) {
1296 throw Exception::NotImplementedError("TimeSeriesProperty<TYPE>::setValue - "
1297 "Cannot extract TimeSeries from a "
1298 "std::string");
1299}
1300
1306template <typename TYPE> std::string TimeSeriesProperty<TYPE>::setValueFromJson(const Json::Value & /*unused*/) {
1307 throw Exception::NotImplementedError("TimeSeriesProperty<TYPE>::setValue - "
1308 "Cannot extract TimeSeries from a "
1309 "Json::Value");
1310}
1311
1316template <typename TYPE>
1317std::string TimeSeriesProperty<TYPE>::setDataItem(const std::shared_ptr<DataItem> & /*unused*/) {
1318 throw Exception::NotImplementedError("TimeSeriesProperty<TYPE>::setValue - "
1319 "Cannot extract TimeSeries from "
1320 "DataItem");
1321}
1322
1325template <typename TYPE> void TimeSeriesProperty<TYPE>::clear() {
1326 m_size = 0;
1327 m_values.clear();
1328
1329 m_propSortedFlag = TimeSeriesSortStatus::TSSORTED;
1330 m_filterApplied = false;
1331}
1332
1340template <typename TYPE> void TimeSeriesProperty<TYPE>::clearOutdated() {
1341 if (realSize() > 1) {
1342 auto lastValue = m_values.back();
1343 clear();
1344 m_values.emplace_back(lastValue);
1345 m_size = 1;
1346 }
1347}
1348
1349//--------------------------------------------------------------------------------------------
1358template <typename TYPE>
1359void TimeSeriesProperty<TYPE>::create(const Types::Core::DateAndTime &start_time, const std::vector<double> &time_sec,
1360 const std::vector<TYPE> &new_values) {
1361 if (time_sec.size() != new_values.size()) {
1362 std::stringstream msg;
1363 msg << "TimeSeriesProperty \"" << name() << "\" create: mismatched size "
1364 << "for the time and values vectors.";
1365 throw std::invalid_argument(msg.str());
1366 }
1367
1368 // Make the times(as seconds) into a vector of DateAndTime in one go.
1369 std::vector<DateAndTime> times;
1370 DateAndTime::createVector(start_time, time_sec, times);
1371
1372 this->create(times, new_values);
1373}
1374
1375//--------------------------------------------------------------------------------------------
1383template <typename TYPE>
1384void TimeSeriesProperty<TYPE>::create(const std::vector<Types::Core::DateAndTime> &new_times,
1385 const std::vector<TYPE> &new_values) {
1386 if (new_times.size() != new_values.size())
1387 throw std::invalid_argument("TimeSeriesProperty::create: mismatched size "
1388 "for the time and values vectors.");
1389
1390 clear();
1391 m_values.reserve(new_times.size());
1392
1393 std::size_t num = new_values.size();
1394
1395 m_propSortedFlag = TimeSeriesSortStatus::TSSORTED;
1396 for (std::size_t i = 0; i < num; i++) {
1397 TimeValueUnit<TYPE> newentry(new_times[i], new_values[i]);
1398 m_values.emplace_back(newentry);
1399 if (m_propSortedFlag == TimeSeriesSortStatus::TSSORTED && i > 0 && new_times[i - 1] > new_times[i]) {
1400 // Status gets to unsorted
1401 m_propSortedFlag = TimeSeriesSortStatus::TSUNSORTED;
1402 }
1403 }
1404
1405 // reset the size
1406 m_size = static_cast<int>(m_values.size());
1407}
1408
1413template <typename TYPE> TYPE TimeSeriesProperty<TYPE>::getSingleValue(const Types::Core::DateAndTime &t) const {
1414 if (m_values.empty()) {
1415 const std::string error("getSingleValue(): TimeSeriesProperty '" + name() + "' is empty");
1416 g_log.debug(error);
1417 throw std::runtime_error(error);
1418 }
1419
1420 // 1. Get sorted
1421 sortIfNecessary();
1422
1423 // 2.
1424 TYPE value;
1425 if (t < m_values[0].time()) {
1426 // 1. Out side of lower bound
1427 value = m_values[0].value();
1428 } else if (t >= m_values.back().time()) {
1429 // 2. Out side of upper bound
1430 value = m_values.back().value();
1431 } else {
1432 // 3. Within boundary
1433 int index = this->findIndex(t);
1434
1435 if (index < 0) {
1436 // If query time "t" is earlier than the begin time of the series
1437 index = 0;
1438 } else if (index == int(m_values.size())) {
1439 // If query time "t" is later than the end time of the series
1440 index = static_cast<int>(m_values.size()) - 1;
1441 } else if (index > int(m_values.size())) {
1442 std::stringstream errss;
1443 errss << "TimeSeriesProperty.findIndex() returns index (" << index << " ) > maximum defined value "
1444 << m_values.size();
1445 throw std::logic_error(errss.str());
1446 }
1447
1448 value = m_values[static_cast<size_t>(index)].value();
1449 }
1450
1451 return value;
1452} // END-DEF getSinglevalue()
1453
1459template <typename TYPE>
1460TYPE TimeSeriesProperty<TYPE>::getSingleValue(const Types::Core::DateAndTime &t, int &index) const {
1461 if (m_values.empty()) {
1462 const std::string error("getSingleValue(): TimeSeriesProperty '" + name() + "' is empty");
1463 g_log.debug(error);
1464 throw std::runtime_error(error);
1465 }
1466
1467 // 1. Get sorted
1468 sortIfNecessary();
1469
1470 // 2.
1471 TYPE value;
1472 if (t < m_values[0].time()) {
1473 // 1. Out side of lower bound
1474 value = m_values[0].value();
1475 index = 0;
1476 } else if (t >= m_values.back().time()) {
1477 // 2. Out side of upper bound
1478 value = m_values.back().value();
1479 index = int(m_values.size()) - 1;
1480 } else {
1481 // 3. Within boundary
1482 index = this->findIndex(t);
1483
1484 if (index < 0) {
1485 // If query time "t" is earlier than the begin time of the series
1486 index = 0;
1487 } else if (index == int(m_values.size())) {
1488 // If query time "t" is later than the end time of the series
1489 index = static_cast<int>(m_values.size()) - 1;
1490 } else if (index > int(m_values.size())) {
1491 std::stringstream errss;
1492 errss << "TimeSeriesProperty.findIndex() returns index (" << index << " ) > maximum defined value "
1493 << m_values.size();
1494 throw std::logic_error(errss.str());
1495 }
1496
1497 value = m_values[static_cast<size_t>(index)].value();
1498 }
1499
1500 return value;
1501} // END-DEF getSinglevalue()
1502
1513template <typename TYPE> TimeInterval TimeSeriesProperty<TYPE>::nthInterval(int n) const {
1514 // 0. Throw exception
1515 if (m_values.empty()) {
1516 const std::string error("nthInterval(): TimeSeriesProperty '" + name() + "' is empty");
1517 g_log.debug(error);
1518 throw std::runtime_error(error);
1519 }
1520
1521 // 1. Sort
1522 sortIfNecessary();
1523
1524 // 2. Calculate time interval
1525
1526 Kernel::TimeInterval deltaT;
1527
1528 if (m_filter.empty()) {
1529 // I. No filter
1530 if (n >= static_cast<int>(m_values.size()) ||
1531 (n == static_cast<int>(m_values.size()) - 1 && m_values.size() == 1)) {
1532 // 1. Out of bound
1533 ;
1534 } else if (n == static_cast<int>(m_values.size()) - 1) {
1535 // 2. Last one by making up an end time.
1536 time_duration d = m_values.rbegin()->time() - (m_values.rbegin() + 1)->time();
1537 DateAndTime endTime = m_values.rbegin()->time() + d;
1538 Kernel::TimeInterval dt(m_values.rbegin()->time(), endTime);
1539 deltaT = dt;
1540 } else {
1541 // 3. Regular
1542 DateAndTime startT = m_values[static_cast<std::size_t>(n)].time();
1543 DateAndTime endT = m_values[static_cast<std::size_t>(n) + 1].time();
1544 TimeInterval dt(startT, endT);
1545 deltaT = dt;
1546 }
1547 } else {
1548 // II. Filter
1549 // II.0 apply Filter
1550 this->applyFilter();
1551
1552 if (static_cast<size_t>(n) > m_filterQuickRef.back().second + 1) {
1553 // 1. n > size of the allowed region, do nothing to dt
1554 ;
1555 } else if (static_cast<size_t>(n) == m_filterQuickRef.back().second + 1) {
1556 // 2. n = size of the allowed region, duplicate the last one
1557 auto ind_t1 = static_cast<long>(m_filterQuickRef.back().first);
1558 long ind_t2 = ind_t1 - 1;
1559 Types::Core::DateAndTime t1 = (m_values.begin() + ind_t1)->time();
1560 Types::Core::DateAndTime t2 = (m_values.begin() + ind_t2)->time();
1561 time_duration d = t1 - t2;
1562 Types::Core::DateAndTime t3 = t1 + d;
1563 Kernel::TimeInterval dt(t1, t3);
1564 deltaT = dt;
1565 } else {
1566 // 3. n < size
1567 Types::Core::DateAndTime t0;
1568 Types::Core::DateAndTime tf;
1569
1570 size_t refindex = this->findNthIndexFromQuickRef(n);
1571 if (refindex + 3 >= m_filterQuickRef.size())
1572 throw std::logic_error("nthInterval: Haven't considered this case.");
1573
1574 int diff = n - static_cast<int>(m_filterQuickRef[refindex].second);
1575 if (diff < 0)
1576 throw std::logic_error("nthInterval: diff cannot be less than 0.");
1577
1578 // i) start time
1579 Types::Core::DateAndTime ftime0 = m_filter[m_filterQuickRef[refindex].first].first;
1580 size_t iStartIndex = m_filterQuickRef[refindex + 1].first + static_cast<size_t>(diff);
1581 Types::Core::DateAndTime ltime0 = m_values[iStartIndex].time();
1582 if (iStartIndex == 0 && ftime0 < ltime0) {
1583 // a) Special case that True-filter time starts before log time
1584 t0 = ltime0;
1585 } else if (diff == 0) {
1586 // b) First entry... usually start from filter time
1587 t0 = ftime0;
1588 } else {
1589 // c) Not the first entry.. usually in the middle of TRUE filter period.
1590 // use log time
1591 t0 = ltime0;
1592 }
1593
1594 // ii) end time
1595 size_t iStopIndex = iStartIndex + 1;
1596 if (iStopIndex >= m_values.size()) {
1597 // a) Last log entry is for the start
1598 Types::Core::DateAndTime ftimef = m_filter[m_filterQuickRef[refindex + 3].first].first;
1599 tf = ftimef;
1600 } else {
1601 // b) Using the earlier value of next log entry and next filter entry
1602 Types::Core::DateAndTime ltimef = m_values[iStopIndex].time();
1603 Types::Core::DateAndTime ftimef = m_filter[m_filterQuickRef[refindex + 3].first].first;
1604 if (ltimef < ftimef)
1605 tf = ltimef;
1606 else
1607 tf = ftimef;
1608 }
1609
1610 Kernel::TimeInterval dt(t0, tf);
1611 deltaT = dt;
1612 } // END-IF-ELSE Cases
1613 }
1614
1615 return deltaT;
1616}
1617
1618//-----------------------------------------------------------------------------------------------
1624template <typename TYPE> TYPE TimeSeriesProperty<TYPE>::nthValue(int n) const {
1625 TYPE value;
1626
1627 // 1. Throw error if property is empty
1628 if (m_values.empty()) {
1629 const std::string error("nthValue(): TimeSeriesProperty '" + name() + "' is empty");
1630 g_log.debug(error);
1631 throw std::runtime_error(error);
1632 }
1633
1634 // 2. Sort and apply filter
1635 sortIfNecessary();
1636
1637 if (m_filter.empty()) {
1638 // 3. Situation 1: No filter
1639 if (static_cast<size_t>(n) < m_values.size()) {
1640 TimeValueUnit<TYPE> entry = m_values[static_cast<std::size_t>(n)];
1641 value = entry.value();
1642 } else {
1643 TimeValueUnit<TYPE> entry = m_values[static_cast<std::size_t>(m_size) - 1];
1644 value = entry.value();
1645 }
1646 } else {
1647 // 4. Situation 2: There is filter
1648 this->applyFilter();
1649
1650 if (static_cast<size_t>(n) > m_filterQuickRef.back().second + 1) {
1651 // 1. n >= size of the allowed region
1652 size_t ilog = (m_filterQuickRef.rbegin() + 1)->first;
1653 value = m_values[ilog].value();
1654 } else {
1655 // 2. n < size
1656 Types::Core::DateAndTime t0;
1657 Types::Core::DateAndTime tf;
1658
1659 size_t refindex = findNthIndexFromQuickRef(n);
1660 if (refindex + 3 >= m_filterQuickRef.size()) {
1661 throw std::logic_error("Not consider out of boundary case here. ");
1662 }
1663 size_t ilog =
1664 m_filterQuickRef[refindex + 1].first + (static_cast<std::size_t>(n) - m_filterQuickRef[refindex].second);
1665 value = m_values[ilog].value();
1666 } // END-IF-ELSE Cases
1667 }
1668
1669 return value;
1670}
1671
1677template <typename TYPE> Types::Core::DateAndTime TimeSeriesProperty<TYPE>::nthTime(int n) const {
1678 sortIfNecessary();
1679
1680 if (m_values.empty()) {
1681 const std::string error("nthTime(): TimeSeriesProperty '" + name() + "' is empty");
1682 g_log.debug(error);
1683 throw std::runtime_error(error);
1684 }
1685
1686 if (n < 0 || n >= static_cast<int>(m_values.size()))
1687 n = static_cast<int>(m_values.size()) - 1;
1688
1689 return m_values[static_cast<size_t>(n)].time();
1690}
1691
1692/* Divide the property into allowed and disallowed time intervals according to
1693 \a filter.
1694 * (Repeated time-value pairs (two same time and value entries) mark the start
1695 of a gap in the values.)
1696 * If any time-value pair is repeated, it means that this entry is in disallowed
1697 region.
1698 * The gap ends and an allowed time interval starts when a single time-value is
1699 met.
1700 The disallowed region will be hidden for countSize() and nthInterval()
1701 Boundary condition
1702 ?. If filter[0].time > log[0].time, then all log before filter[0] are
1703 considered TRUE
1704 2. If filter[-1].time < log[-1].time, then all log after filter[-1] will be
1705 considered same as filter[-1]
1706
1707 @param filter :: The filter mask to apply
1708 */
1709template <typename TYPE> void TimeSeriesProperty<TYPE>::filterWith(const TimeSeriesProperty<bool> *filter) {
1710 // 1. Clear the current
1711 m_filter.clear();
1712 m_filterQuickRef.clear();
1713
1714 if (filter->size() == 0) {
1715 // if filter is empty, return
1716 return;
1717 }
1718
1719 // 2. Construct mFilter
1720 std::vector<Types::Core::DateAndTime> filtertimes = filter->timesAsVector();
1721 std::vector<bool> filtervalues = filter->valuesAsVector();
1722 assert(filtertimes.size() == filtervalues.size());
1723 const size_t nFilterTimes(filtertimes.size());
1724 m_filter.reserve(nFilterTimes + 1);
1725
1726 bool lastIsTrue = false;
1727 auto fend = filtertimes.end();
1728 auto vit = filtervalues.begin();
1729 for (auto fit = filtertimes.begin(); fit != fend; ++fit) {
1730 if (*vit && !lastIsTrue) {
1731 // Get a true in filter but last recorded value is for false
1732 m_filter.emplace_back(*fit, true);
1733 lastIsTrue = true;
1734 } else if (!(*vit) && lastIsTrue) {
1735 // Get a False in filter but last recorded value is for TRUE
1736 m_filter.emplace_back(*fit, false);
1737 lastIsTrue = false;
1738 }
1739 ++vit; // move to next value
1740 }
1741
1742 // 2b) Get a clean finish
1743 if (filtervalues.back()) {
1744 DateAndTime lastTime, nextLastT;
1745 if (m_values.back().time() > filtertimes.back()) {
1746 const size_t nvalues(m_values.size());
1747 // Last log time is later than last filter time
1748 lastTime = m_values.back().time();
1749 if (nvalues > 1 && m_values[nvalues - 2].time() > filtertimes.back())
1750 nextLastT = m_values[nvalues - 2].time();
1751 else
1752 nextLastT = filtertimes.back();
1753 } else {
1754 // Last log time is no later than last filter time
1755 lastTime = filtertimes.back();
1756 const size_t nfilterValues(filtervalues.size());
1757 // If last-but-one filter time is still later than value then previous is
1758 // this
1759 // else it is the last value time
1760 if (nfilterValues > 1 && m_values.back().time() > filtertimes[nfilterValues - 2])
1761 nextLastT = filtertimes[nfilterValues - 2];
1762 else
1763 nextLastT = m_values.back().time();
1764 }
1765
1766 time_duration dtime = lastTime - nextLastT;
1767 m_filter.emplace_back(lastTime + dtime, false);
1768 }
1769
1770 // 3. Reset flag and do filter
1771 m_filterApplied = false;
1772 applyFilter();
1773}
1774
1778template <typename TYPE> void TimeSeriesProperty<TYPE>::clearFilter() {
1779 m_filter.clear();
1780 m_filterQuickRef.clear();
1781}
1782
1786template <typename TYPE> void TimeSeriesProperty<TYPE>::countSize() const {
1787 if (m_filter.empty()) {
1788 // 1. Not filter
1789 m_size = int(m_values.size());
1790 } else {
1791 // 2. With Filter
1792 if (!m_filterApplied) {
1793 this->applyFilter();
1794 }
1795 size_t nvalues = m_filterQuickRef.empty() ? m_values.size() : m_filterQuickRef.back().second;
1796 // The filter logic can end up with the quick ref having a duplicate of the
1797 // last time and value at the end if the last filter time is past the log
1798 // time See "If it is out of upper boundary, still record it. but make the
1799 // log entry to mP.size()+1" in applyFilter
1800 // Make the log seem the full size
1801 if (nvalues == m_values.size() + 1) {
1802 --nvalues;
1803 }
1804 m_size = static_cast<int>(nvalues);
1805 }
1806}
1807
1812template <typename TYPE> bool TimeSeriesProperty<TYPE>::isTimeString(const std::string &str) {
1813 static const boost::regex re("^[0-9]{4}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}");
1814 return boost::regex_search(str.begin(), str.end(), re);
1815}
1816
1821template <typename TYPE> std::string TimeSeriesProperty<TYPE>::isValid() const { return ""; }
1822
1823/*
1824 * A TimeSeriesProperty never has a default, so return empty string
1825 * @returns Empty string as no defaults can be provided
1826 */
1827template <typename TYPE> std::string TimeSeriesProperty<TYPE>::getDefault() const {
1828 return ""; // No defaults can be provided=empty string
1829}
1830
1834template <typename TYPE> bool TimeSeriesProperty<TYPE>::isDefault() const { return false; }
1835
1844 Mantid::Kernel::Statistics raw_stats = Mantid::Kernel::getStatistics(this->filteredValuesAsVector());
1845 out.mean = raw_stats.mean;
1846 out.standard_deviation = raw_stats.standard_deviation;
1847 out.median = raw_stats.median;
1848 out.minimum = raw_stats.minimum;
1849 out.maximum = raw_stats.maximum;
1850 if (this->size() > 0) {
1851 const auto &intervals = this->getSplittingIntervals();
1852 const double duration_sec =
1853 std::accumulate(intervals.cbegin(), intervals.cend(), 0.,
1854 [](double sum, const auto &interval) { return sum + interval.duration(); });
1855 out.duration = duration_sec;
1856 const auto time_weighted = this->timeAverageValueAndStdDev();
1857 out.time_mean = time_weighted.first;
1858 out.time_standard_deviation = time_weighted.second;
1859 } else {
1860 out.time_mean = std::numeric_limits<double>::quiet_NaN();
1861 out.time_standard_deviation = std::numeric_limits<double>::quiet_NaN();
1862 out.duration = std::numeric_limits<double>::quiet_NaN();
1863 }
1864
1865 return out;
1866}
1867
1868/*
1869 * Detects whether there are duplicated entries (of time) in property
1870 * If there is any, keep one of them
1871 */
1872template <typename TYPE> void TimeSeriesProperty<TYPE>::eliminateDuplicates() {
1873 // 1. Sort if necessary
1874 sortIfNecessary();
1875
1876 // 2. Detect and Remove Duplicated
1877 size_t numremoved = 0;
1878
1879 typename std::vector<TimeValueUnit<TYPE>>::iterator vit;
1880 vit = m_values.begin() + 1;
1881 Types::Core::DateAndTime prevtime = m_values.begin()->time();
1882 while (vit != m_values.end()) {
1883 Types::Core::DateAndTime currtime = vit->time();
1884 if (prevtime == currtime) {
1885 // Print out warning
1886 g_log.debug() << "Entry @ Time = " << prevtime
1887 << "has duplicate time stamp. Remove entry with Value = " << (vit - 1)->value() << "\n";
1888
1889 // A duplicated entry!
1890 vit = m_values.erase(vit - 1);
1891
1892 numremoved++;
1893 }
1894
1895 // b) progress
1896 prevtime = currtime;
1897 ++vit;
1898 }
1899
1900 // update m_size
1901 countSize();
1902
1903 // 3. Finish
1904 g_log.warning() << "Log " << this->name() << " has " << numremoved << " entries removed due to duplicated time. "
1905 << "\n";
1906}
1907
1908/*
1909 * Print the content to string
1910 */
1911template <typename TYPE> std::string TimeSeriesProperty<TYPE>::toString() const {
1912 std::stringstream ss;
1913 for (size_t i = 0; i < m_values.size(); ++i)
1914 ss << m_values[i].time() << "\t\t" << m_values[i].value() << "\n";
1915
1916 return ss.str();
1917}
1918
1919//-------------------------------------------------------------------------
1920// Private methods
1921//-------------------------------------------------------------------------
1922
1923//----------------------------------------------------------------------------------
1924/*
1925 * Sort vector mP and set the flag. Only sorts if the values are not already
1926 * sorted.
1927 */
1928template <typename TYPE> void TimeSeriesProperty<TYPE>::sortIfNecessary() const {
1929 if (m_propSortedFlag == TimeSeriesSortStatus::TSUNKNOWN) {
1930 bool sorted = is_sorted(m_values.begin(), m_values.end());
1931 if (sorted)
1932 m_propSortedFlag = TimeSeriesSortStatus::TSSORTED;
1933 else
1934 m_propSortedFlag = TimeSeriesSortStatus::TSUNSORTED;
1935 }
1936
1937 if (m_propSortedFlag == TimeSeriesSortStatus::TSUNSORTED) {
1938 g_log.information("TimeSeriesProperty is not sorted. Sorting is operated on it. ");
1939 std::stable_sort(m_values.begin(), m_values.end());
1940 m_propSortedFlag = TimeSeriesSortStatus::TSSORTED;
1941 }
1942}
1943
1950template <typename TYPE> int TimeSeriesProperty<TYPE>::findIndex(Types::Core::DateAndTime t) const {
1951 // 0. Return with an empty container
1952 if (m_values.empty())
1953 return 0;
1954
1955 // 1. Sort
1956 sortIfNecessary();
1957
1958 // 2. Extreme value
1959 if (t <= m_values[0].time()) {
1960 return -1;
1961 } else if (t >= m_values.back().time()) {
1962 return (int(m_values.size()));
1963 }
1964
1965 // 3. Find by lower_bound()
1966 typename std::vector<TimeValueUnit<TYPE>>::const_iterator fid;
1967 TimeValueUnit<TYPE> temp(t, m_values[0].value());
1968 fid = std::lower_bound(m_values.begin(), m_values.end(), temp);
1969
1970 int newindex = int(fid - m_values.begin());
1971 if (fid->time() > t)
1972 newindex--;
1973
1974 return newindex;
1975}
1976
1983template <typename TYPE>
1984int TimeSeriesProperty<TYPE>::upperBound(Types::Core::DateAndTime t, int istart, int iend) const {
1985 // 0. Check validity
1986 if (istart < 0) {
1987 throw std::invalid_argument("Start Index cannot be less than 0");
1988 }
1989 if (iend >= static_cast<int>(m_values.size())) {
1990 throw std::invalid_argument("End Index cannot exceed the boundary");
1991 }
1992 if (istart > iend) {
1993 throw std::invalid_argument("Start index cannot be greater than end index");
1994 }
1995
1996 // 1. Return instantly if it is out of boundary
1997 if (t < (m_values.begin() + istart)->time()) {
1998 return -1;
1999 }
2000 if (t > (m_values.begin() + iend)->time()) {
2001 return static_cast<int>(m_values.size());
2002 }
2003
2004 // 2. Sort
2005 sortIfNecessary();
2006
2007 // 3. Construct the pair for comparison and do lower_bound()
2008 TimeValueUnit<TYPE> temppair(t, m_values[0].value());
2009 typename std::vector<TimeValueUnit<TYPE>>::iterator fid;
2010 fid = std::lower_bound((m_values.begin() + istart), (m_values.begin() + iend + 1), temppair);
2011 if (fid == m_values.end())
2012 throw std::runtime_error("Cannot find data");
2013
2014 // 4. Calculate return value
2015 size_t index = size_t(fid - m_values.begin());
2016
2017 return int(index);
2018}
2019
2020/*
2021 * Apply filter
2022 * Requirement: There is no 2 consecutive 'second' values that are same in
2023 *mFilter
2024 *
2025 * It only works with filter starting from TRUE AND having TRUE and FALSE
2026 *altered
2027 */
2028template <typename TYPE> void TimeSeriesProperty<TYPE>::applyFilter() const {
2029 // 1. Check and reset
2030 if (m_filterApplied)
2031 return;
2032 if (m_filter.empty())
2033 return;
2034
2035 m_filterQuickRef.clear();
2036
2037 // 2. Apply filter
2038 int icurlog = 0;
2039 for (size_t ift = 0; ift < m_filter.size(); ift++) {
2040 if (m_filter[ift].second) {
2041 // a) Filter == True: indicating the start of a quick reference region
2042 int istart = 0;
2043 if (icurlog > 0)
2044 istart = icurlog - 1;
2045
2046 if (icurlog < static_cast<int>(m_values.size()))
2047 icurlog = this->upperBound(m_filter[ift].first, istart, static_cast<int>(m_values.size()) - 1);
2048
2049 if (icurlog < 0) {
2050 // i. If it is out of lower boundary, add filter time, add 0 time
2051 if (!m_filterQuickRef.empty())
2052 throw std::logic_error("return log index < 0 only occurs with the first log entry");
2053
2054 m_filterQuickRef.emplace_back(ift, 0);
2055 m_filterQuickRef.emplace_back(0, 0);
2056
2057 icurlog = 0;
2058 } else if (icurlog >= static_cast<int>(m_values.size())) {
2059 // ii. If it is out of upper boundary, still record it. but make the
2060 // log entry to mP.size()+1
2061 size_t ip = 0;
2062 if (m_filterQuickRef.size() >= 4)
2063 ip = m_filterQuickRef.back().second;
2064 m_filterQuickRef.emplace_back(ift, ip);
2065 m_filterQuickRef.emplace_back(m_values.size() + 1, ip);
2066 } else {
2067 // iii. The returned value is in the boundary.
2068 size_t numintervals = 0;
2069 if (!m_filterQuickRef.empty()) {
2070 numintervals = m_filterQuickRef.back().second;
2071 }
2072 if (m_filter[ift].first < m_values[static_cast<std::size_t>(icurlog)].time()) {
2073 if (icurlog == 0) {
2074 throw std::logic_error("In this case, icurlog won't be zero! ");
2075 }
2076 icurlog--;
2077 }
2078 m_filterQuickRef.emplace_back(ift, numintervals);
2079 // Note: numintervals inherits from last filter
2080 m_filterQuickRef.emplace_back(icurlog, numintervals);
2081 }
2082 } // Filter value is True
2083 else if (m_filterQuickRef.size() % 4 == 2) {
2084 // b) Filter == False: indicating the end of a quick reference region
2085 int ilastlog = icurlog;
2086
2087 if (ilastlog < static_cast<int>(m_values.size())) {
2088 // B1: Last TRUE entry is still within log
2089 icurlog = this->upperBound(m_filter[ift].first, icurlog, static_cast<int>(m_values.size()) - 1);
2090
2091 if (icurlog < 0) {
2092 // i. Some false filter is before the first log entry. The previous
2093 // filter does not make sense
2094 if (m_filterQuickRef.size() != 2)
2095 throw std::logic_error("False filter is before first log entry. "
2096 "QuickRef size must be 2.");
2097 m_filterQuickRef.pop_back();
2098 m_filterQuickRef.clear();
2099 } else {
2100 // ii. Register the end of a valid log
2101 if (ilastlog < 0)
2102 throw std::logic_error("LastLog is not expected to be less than 0");
2103
2104 int delta_numintervals = icurlog - ilastlog;
2105 if (delta_numintervals < 0)
2106 throw std::logic_error("Havn't considered delta numinterval can be less than 0.");
2107
2108 size_t new_numintervals = m_filterQuickRef.back().second + static_cast<size_t>(delta_numintervals);
2109
2110 m_filterQuickRef.emplace_back(icurlog, new_numintervals);
2111 m_filterQuickRef.emplace_back(ift, new_numintervals);
2112 }
2113 } else {
2114 // B2. Last TRUE filter's time is already out side of log.
2115 size_t new_numintervals = m_filterQuickRef.back().second + 1;
2116 m_filterQuickRef.emplace_back(icurlog - 1, new_numintervals);
2117 m_filterQuickRef.emplace_back(ift, new_numintervals);
2118 }
2119 } // Filter value is FALSE
2120
2121 } // ENDFOR
2122
2123 // 5. Change flag
2124 m_filterApplied = true;
2125
2126 // 6. Re-count size
2127 countSize();
2128}
2129
2130/*
2131 * A new algorithm sto find Nth index. It is simple and leave a lot work to the
2132 *callers
2133 *
2134 * Return: the index of the quick reference vector
2135 */
2136template <typename TYPE> size_t TimeSeriesProperty<TYPE>::findNthIndexFromQuickRef(int n) const {
2137 size_t index = 0;
2138
2139 // 1. Do check
2140 if (n < 0)
2141 throw std::invalid_argument("Unable to take into account negative index. ");
2142 else if (m_filterQuickRef.empty())
2143 throw std::runtime_error("Quick reference is not established. ");
2144
2145 // 2. Return...
2146 if (static_cast<size_t>(n) >= m_filterQuickRef.back().second) {
2147 // 2A. Out side of boundary
2148 index = m_filterQuickRef.size();
2149 } else {
2150 // 2B. Inside
2151 for (size_t i = 0; i < m_filterQuickRef.size(); i += 4) {
2152 if (static_cast<size_t>(n) >= m_filterQuickRef[i].second &&
2153 static_cast<size_t>(n) < m_filterQuickRef[i + 3].second) {
2154 index = i;
2155 break;
2156 }
2157 }
2158 }
2159
2160 return index;
2161}
2162
2170template <typename TYPE> std::string TimeSeriesProperty<TYPE>::setValueFromProperty(const Property &right) {
2171 auto prop = dynamic_cast<const TimeSeriesProperty<TYPE> *>(&right);
2172 if (!prop) {
2173 return "Could not set value: properties have different type.";
2174 }
2175 m_values = prop->m_values;
2176 m_size = prop->m_size;
2177 m_propSortedFlag = prop->m_propSortedFlag;
2178 m_filter = prop->m_filter;
2179 m_filterQuickRef = prop->m_filterQuickRef;
2180 m_filterApplied = prop->m_filterApplied;
2181 return "";
2182}
2183
2184//----------------------------------------------------------------------------------------------
2186template <typename TYPE> void TimeSeriesProperty<TYPE>::saveTimeVector(::NeXus::File *file) {
2187 std::vector<DateAndTime> times = this->timesAsVector();
2188 const DateAndTime &start = times.front();
2189 std::vector<double> timeSec(times.size());
2190 for (size_t i = 0; i < times.size(); i++)
2191 timeSec[i] = static_cast<double>(times[i].totalNanoseconds() - start.totalNanoseconds()) * 1e-9;
2192 file->writeData("time", timeSec);
2193 file->openData("time");
2194 file->putAttr("start", start.toISO8601String());
2195 file->closeData();
2196}
2197
2198//----------------------------------------------------------------------------------------------
2200template <> void TimeSeriesProperty<std::string>::saveProperty(::NeXus::File *file) {
2201 std::vector<std::string> values = this->valuesAsVector();
2202 if (values.empty())
2203 return;
2204 file->makeGroup(this->name(), "NXlog", true);
2205
2206 // Find the max length of any string
2207 auto max_it = std::max_element(values.begin(), values.end(),
2208 [](const std::string &a, const std::string &b) { return a.size() < b.size(); });
2209 // Increment by 1 to have the 0 terminator
2210 size_t maxlen = max_it->size() + 1;
2211 // Copy into one array
2212 std::vector<char> strs(values.size() * maxlen);
2213 size_t index = 0;
2214 for (const auto &prop : values) {
2215 std::copy(prop.begin(), prop.end(), &strs[index]);
2216 index += maxlen;
2217 }
2218
2219 std::vector<int> dims{static_cast<int>(values.size()), static_cast<int>(maxlen)};
2220 file->makeData("value", ::NeXus::CHAR, dims, true);
2221 file->putData(strs.data());
2222 file->closeData();
2223 saveTimeVector(file);
2224 file->closeGroup();
2225}
2226
2233template <> void TimeSeriesProperty<bool>::saveProperty(::NeXus::File *file) {
2234 std::vector<bool> value = this->valuesAsVector();
2235 if (value.empty())
2236 return;
2237 std::vector<uint8_t> asUint(value.begin(), value.end());
2238 file->makeGroup(this->name(), "NXlog", true);
2239 file->writeData("value", asUint);
2240 file->putAttr("boolean", "1");
2241 saveTimeVector(file);
2242 file->closeGroup();
2243}
2244
2245template <typename TYPE> void TimeSeriesProperty<TYPE>::saveProperty(::NeXus::File *file) {
2246 auto value = this->valuesAsVector();
2247 if (value.empty())
2248 return;
2249 file->makeGroup(this->name(), "NXlog", 1);
2250 file->writeData("value", value);
2251 file->openData("value");
2252 file->putAttr("units", this->units());
2253 file->closeData();
2254 saveTimeVector(file);
2255 file->closeGroup();
2256}
2257
2262template <typename TYPE> Json::Value TimeSeriesProperty<TYPE>::valueAsJson() const { return Json::Value(value()); }
2263
2274template <typename TYPE>
2275void TimeSeriesProperty<TYPE>::histogramData(const Types::Core::DateAndTime &tMin, const Types::Core::DateAndTime &tMax,
2276 std::vector<double> &counts) const {
2277
2278 size_t nPoints = counts.size();
2279 if (nPoints == 0)
2280 return; // nothing to do
2281
2282 auto t0 = static_cast<double>(tMin.totalNanoseconds());
2283 auto t1 = static_cast<double>(tMax.totalNanoseconds());
2284 if (t0 > t1)
2285 throw std::invalid_argument("invalid arguments for histogramData; tMax<tMin");
2286
2287 double dt = (t1 - t0) / static_cast<double>(nPoints);
2288
2289 for (auto &ev : m_values) {
2290 auto time = static_cast<double>(ev.time().totalNanoseconds());
2291 if (time < t0 || time >= t1)
2292 continue;
2293 auto ind = static_cast<size_t>((time - t0) / dt);
2294 counts[ind] += static_cast<double>(ev.value());
2295 }
2296}
2297
2298template <>
2299void TimeSeriesProperty<std::string>::histogramData(const Types::Core::DateAndTime &tMin,
2300 const Types::Core::DateAndTime &tMax,
2301 std::vector<double> &counts) const {
2302 UNUSED_ARG(tMin);
2303 UNUSED_ARG(tMax);
2304 UNUSED_ARG(counts);
2305 throw std::runtime_error("histogramData is not implememnted for time series "
2306 "properties containing strings");
2307}
2308
2315template <typename TYPE> std::vector<TYPE> TimeSeriesProperty<TYPE>::filteredValuesAsVector() const {
2316 if (m_filter.empty()) {
2317 return this->valuesAsVector(); // no filtering to do
2318 }
2319 if (!m_filterApplied) {
2320 applyFilter();
2321 }
2322 sortIfNecessary();
2323
2324 std::vector<TYPE> filteredValues;
2325 for (const auto &value : m_values) {
2326 if (isTimeFiltered(value.time())) {
2327 filteredValues.emplace_back(value.value());
2328 }
2329 }
2330
2331 return filteredValues;
2332}
2333
2343template <typename TYPE> bool TimeSeriesProperty<TYPE>::isTimeFiltered(const Types::Core::DateAndTime &time) const {
2344 // Each time/value pair in the filter defines a point where the region defined
2345 // after that time is either included/excluded depending on the boolean value.
2346 // By definition of the filter construction the region before a given filter
2347 // time must have the opposite value. For times outside the filter region:
2348 // 1. time < first filter time: inverse of the first filter value
2349 // 2. time > last filter time: value of the last filter value
2350 // If time == a filter time then the value is taken to belong to that filter
2351 // region and not the previous
2352
2353 // Find first fitler time strictly greater than time
2354 auto filterEntry = std::lower_bound(m_filter.begin(), m_filter.end(), time,
2355 [](const std::pair<Types::Core::DateAndTime, bool> &filterEntry,
2356 const Types::Core::DateAndTime &t) { return filterEntry.first <= t; });
2357
2358 if (filterEntry == m_filter.begin()) {
2359 return !filterEntry->second;
2360 } else {
2361 // iterator points to filter greater than time and but we want the previous
2362 // region
2363 --filterEntry;
2364 return filterEntry->second;
2365 }
2366}
2367
2373template <typename TYPE> std::vector<SplittingInterval> TimeSeriesProperty<TYPE>::getSplittingIntervals() const {
2374 std::vector<SplittingInterval> intervals;
2375 // Case where there is no filter
2376 if (m_filter.empty()) {
2377 intervals.emplace_back(firstTime(), lastTime());
2378 return intervals;
2379 }
2380
2381 if (!m_filterApplied) {
2382 applyFilter();
2383 }
2384
2385 // (local reference to use in lambda)
2386 const auto &localFilter = m_filter;
2388 const auto findNext = [&localFilter](size_t &index, const bool val) {
2389 for (; index < localFilter.size(); ++index) {
2390 const auto &entry = localFilter[index];
2391 if (entry.second == val) {
2392 return entry.first;
2393 }
2394 }
2395 return localFilter.back().first;
2396 };
2397
2398 // Look through filter to find start/stop pairs
2399 size_t index = 0;
2400 while (index < m_filter.size()) {
2401 DateAndTime start, stop;
2402 if (index == 0) {
2403 if (m_filter[0].second) {
2404 start = m_filter[0].first;
2405 } else {
2406 start = firstTime();
2407 }
2408 } else {
2409 start = findNext(index, true);
2410 }
2411 stop = findNext(index, false);
2412 if (stop != start) { // avoid empty ranges
2413 intervals.emplace_back(start, stop);
2414 }
2415 }
2416
2417 return intervals;
2418}
2419
2421// -------------------------- Macro to instantiation concrete types
2422// --------------------------------
2423#define INSTANTIATE(TYPE) template class TimeSeriesProperty<TYPE>;
2424
2425// -------------------------- Concrete instantiation
2426// -----------------------------------------------
2427INSTANTIATE(int32_t)
2428INSTANTIATE(int64_t)
2429INSTANTIATE(uint32_t)
2430INSTANTIATE(uint64_t)
2431INSTANTIATE(float)
2432INSTANTIATE(double)
2433INSTANTIATE(std::string)
2434INSTANTIATE(bool)
2435
2436
2437
2438} // namespace Kernel
2439} // namespace Mantid
2440
2441namespace Mantid::Kernel {
2442//================================================================================================
2449double filterByStatistic(TimeSeriesProperty<double> const *const propertyToFilter,
2450 Kernel::Math::StatisticType statisticType) {
2451 using namespace Kernel::Math;
2452 double singleValue = 0;
2453 switch (statisticType) {
2454 case FirstValue:
2455 singleValue = propertyToFilter->nthValue(0);
2456 break;
2457 case LastValue:
2458 singleValue = propertyToFilter->nthValue(propertyToFilter->size() - 1);
2459 break;
2460 case Minimum:
2461 singleValue = propertyToFilter->getStatistics().minimum;
2462 break;
2463 case Maximum:
2464 singleValue = propertyToFilter->getStatistics().maximum;
2465 break;
2466 case Mean:
2467 singleValue = propertyToFilter->getStatistics().mean;
2468 break;
2469 case Median:
2470 singleValue = propertyToFilter->getStatistics().median;
2471 break;
2472 default:
2473 throw std::invalid_argument("filterByStatistic - Unknown statistic type: " +
2474 boost::lexical_cast<std::string>(propertyToFilter));
2475 };
2476 return singleValue;
2477}
2478} // namespace Mantid::Kernel
const std::vector< double > & rhs
size_t m_size
Maximum size of the store.
double maxValue
double minValue
double value
The value of the point.
Definition: FitMW.cpp:51
double error
Definition: IndexPeaks.cpp:133
std::map< DeltaEMode::Type, std::string > index
Definition: DeltaEMode.cpp:19
#define INSTANTIATE(TYPE)
Definition: Statistics.cpp:403
double right
Definition: LineProfile.cpp:81
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition: System.h:64
Marks code as not implemented yet.
Definition: Exception.h:138
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
Base class for properties.
Definition: Property.h:94
Represents a time interval.
Definition: DateAndTime.h:25
Types::Core::DateAndTime begin() const
Beginning of the interval.
Definition: DateAndTime.h:32
Types::Core::DateAndTime end() const
End of the interval.
Definition: DateAndTime.h:34
A specialised Property class for holding a series of time-value pairs.
void splitByTime(std::vector< SplittingInterval > &splitter, std::vector< Property * > outputs, bool isPeriodic) const override
Split out a time series property by time intervals.
void replaceValues(const std::vector< Types::Core::DateAndTime > &times, const std::vector< TYPE > &values)
Replaces the time series with new values time series values.
TimeSeriesProperty< TYPE > * clone() const override
"Virtual" copy constructor
std::string getDefault() const override
Returns the default value.
std::map< Types::Core::DateAndTime, TYPE > valueAsMap() const
Return the time series as a C++ map<DateAndTime, TYPE>
size_t getMemorySize() const override
Return the memory used by the property, in bytes.
Json::Value valueAsJson() const override
std::string toString() const
Stringize the property.
int size() const override
Returns the number of values at UNIQUE time intervals in the time series.
TYPE minValue() const
Returns the minimum value found in the series.
TYPE maxValue() const
Returns the maximum value found in the series.
bool isTimeFiltered(const Types::Core::DateAndTime &time) const
Find if time lies in a filtered region.
std::vector< Mantid::Kernel::SplittingInterval > getSplittingIntervals() const
If filtering by log, get the time intervals for splitting.
std::vector< TYPE > valuesAsVector() const
Return the time series's values (unfiltered) as a vector<TYPE>
Types::Core::DateAndTime firstTime() const
Returns the first time regardless of filter.
void eliminateDuplicates()
Detects whether there are duplicated entries (of time) in property & eliminates them.
std::vector< double > timesAsVectorSeconds() const
Return the series as list of times, where the time is the number of seconds since the start.
std::multimap< Types::Core::DateAndTime, TYPE > valueAsMultiMap() const
Return the time series as a correct C++ multimap<DateAndTime, TYPE>.
TimeSeriesProperty & operator+=(Property const *right) override
Add the value of another property.
TimeSeriesProperty< TYPE > & merge(Property *rhs) override
Merge the given property with this one.
std::string value() const override
Get the time series property as a string of 'time value'.
void makeFilterByValue(std::vector< SplittingInterval > &split, double min, double max, double TimeTolerance=0.0, bool centre=false) const override
Fill a TimeSplitterType that will filter the events by matching.
bool isDefault() const override
Returns if the value is at the default.
void sortIfNecessary() const
Sort the property into increasing times, if not already sorted.
size_t findNthIndexFromQuickRef(int n) const
A new algorithm to find Nth index.
double mean() const
Returns the mean value found in the series.
int findIndex(Types::Core::DateAndTime t) const
Find the index of the entry of time t in the mP vector (sorted)
TYPE firstValue() const
Returns the first value regardless of filter.
virtual bool operator!=(const TimeSeriesProperty< TYPE > &right) const
Deep comparison (not equal).
void histogramData(const Types::Core::DateAndTime &tMin, const Types::Core::DateAndTime &tMax, std::vector< double > &counts) const
generate constant time-step histogram from the property values
void expandFilterToRange(std::vector< SplittingInterval > &split, double min, double max, const TimeInterval &range) const override
Make sure an existing filter covers the full time range given.
~TimeSeriesProperty() override
Virtual destructor.
std::string setValue(const std::string &) override
Set a property from a string.
std::vector< Types::Core::DateAndTime > filteredTimesAsVector() const
Get filtered times as a vector.
void addValue(const Types::Core::DateAndTime &time, const TYPE &value)
Add a value to the map using a DateAndTime object.
void applyFilter() const
Apply a filter.
std::pair< double, double > averageAndStdDevInFilter(const std::vector< SplittingInterval > &filter) const override
Calculate the time-weighted average and standard deviation of a property in a filtered range.
int m_size
The number of values (or time intervals) in the time series.
TimeSeriesProperty(const std::string &name)
Constructor.
void countSize() const
Updates size()
void clearFilter()
Restores the property to the unsorted state.
void filterByTimes(const std::vector< SplittingInterval > &splittervec)
Filter by a range of times.
void clear() override
Deletes the series of values in the property.
Types::Core::DateAndTime lastTime() const
Returns the last time.
void setName(const std::string &name)
Set name of property.
void saveProperty(::NeXus::File *file) override
TYPE lastValue() const
Returns the last value.
void filterByTime(const Types::Core::DateAndTime &start, const Types::Core::DateAndTime &stop) override
Filter out a run by time.
std::map< Types::Core::DateAndTime, TYPE > valueAsCorrectMap() const
Return the time series as a correct C++ map<DateAndTime, TYPE>.
std::string setValueFromJson(const Json::Value &) override
Set a property from a string.
std::unique_ptr< TimeSeriesProperty< double > > getDerivative() const
Return time series property, containing time derivative of current property.
std::vector< TimeValueUnit< TYPE > > m_values
Holds the time series data.
double timeAverageValue() const override
Returns the calculated time weighted average value.
void create(const Types::Core::DateAndTime &start_time, const std::vector< double > &time_sec, const std::vector< TYPE > &new_values)
Clears and creates a TimeSeriesProperty from these parameters.
virtual bool operator==(const TimeSeriesProperty< TYPE > &right) const
Deep comparison.
void clearOutdated() override
Deletes all but the 'last entry' in the property.
TYPE nthValue(int n) const
Returns n-th value of n-th interval in an incredibly inefficient way.
std::string setDataItem(const std::shared_ptr< DataItem > &) override
Set a property from a DataItem.
std::string isValid() const override
This doesn't check anything -we assume these are always valid.
std::vector< std::string > time_tValue() const
New method to return time series value pairs as std::vector<std::string>
void splitByTimeVector(const std::vector< Types::Core::DateAndTime > &splitter_time_vec, const std::vector< int > &target_vec, const std::vector< TimeSeriesProperty * > &outputs)
New split method.
std::vector< Types::Core::DateAndTime > timesAsVector() const override
Return the time series's times as a vector<DateAndTime>
TimeSeriesPropertyStatistics getStatistics() const
Return a TimeSeriesPropertyStatistics object.
void filterWith(const TimeSeriesProperty< bool > *filter)
Divide the property into allowed and disallowed time intervals according to filter.
Types::Core::DateAndTime nthTime(int n) const
Returns n-th time. NOTE: Complexity is order(n)! regardless of filter.
TYPE getSingleValue(const Types::Core::DateAndTime &t) const
Returns the value at a particular time.
void saveTimeVector(::NeXus::File *file)
Saves the time vector has time + start attribute.
int upperBound(Types::Core::DateAndTime t, int istart, int iend) const
Find the upper_bound of time t in container.
TimeInterval nthInterval(int n) const
Returns n-th valid time interval, in a very inefficient way.
double averageValueInFilter(const std::vector< SplittingInterval > &filter) const override
Calculate the time-weighted average of a property in a filtered range.
void addValues(const std::vector< Types::Core::DateAndTime > &times, const std::vector< TYPE > &values)
Adds vectors of values to the map.
int realSize() const override
Returns the real size of the time series property map:
static bool isTimeString(const std::string &str)
Check if str has the right time format.
Property * cloneWithTimeShift(const double timeShift) const override
"Virtual" copy constructor with a time shift in seconds
std::string setValueFromProperty(const Property &right) override
Set a value from another property.
std::vector< TYPE > filteredValuesAsVector() const
Get filtered values as a vector.
std::pair< double, double > timeAverageValueAndStdDev() const
Time weighted mean and standard deviation.
Class to hold unit value (DateAndTime, T)
MatrixWorkspace_sptr MANTID_API_DLL operator+=(const MatrixWorkspace_sptr &lhs, const MatrixWorkspace_sptr &rhs)
Adds two workspaces.
std::unique_ptr< T > create(const P &parent, const IndexArg &indexArg, const HistArg &histArg)
This is the create() method that all the other create() methods call.
void split(const int A, int &S, int &V)
Split a number into the sign and positive value.
Definition: Acomp.cpp:42
StatisticType
Maps a "statistic" to a number.
Definition: Statistics.h:18
Statistics getStatistics(const std::vector< TYPE > &data, const unsigned int flags=StatOptions::AllStats)
Return a statistics object for the given data set.
Definition: Statistics.cpp:167
std::vector< SplittingInterval > TimeSplitterType
A typedef for splitting events according their pulse time.
Definition: LogManager.h:31
MANTID_KERNEL_DLL bool operator==(const Mantid::Kernel::Property &lhs, const Mantid::Kernel::Property &rhs)
Compares this to another property for equality.
Definition: Property.cpp:259
double DLLExport filterByStatistic(TimeSeriesProperty< double > const *const propertyToFilter, Kernel::Math::StatisticType statisticType)
Function filtering double TimeSeriesProperties according to the requested statistics.
Helper class which provides the Collimation Length for SANS instruments.
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition: EmptyValues.h:43
STL namespace.
Simple struct to store statistics.
Definition: Statistics.h:25
double mean
Mean value.
Definition: Statistics.h:31
double median
Median value.
Definition: Statistics.h:33
double minimum
Minimum value.
Definition: Statistics.h:27
double maximum
Maximum value.
Definition: Statistics.h:29
double standard_deviation
standard_deviation of the values
Definition: Statistics.h:35
Struct holding some useful statistics for a TimeSeriesProperty.
double time_standard_deviation
time weighted standard deviation
double standard_deviation
standard_deviation of the values