diff --git a/Framework/Kernel/inc/MantidKernel/ITimeSeriesProperty.h b/Framework/Kernel/inc/MantidKernel/ITimeSeriesProperty.h index 5f1477ffd4a12952f7ff8c559cf69767fb693dcf..d6e1f06dd67bd010754cc46ca1b82257f075f6ac 100644 --- a/Framework/Kernel/inc/MantidKernel/ITimeSeriesProperty.h +++ b/Framework/Kernel/inc/MantidKernel/ITimeSeriesProperty.h @@ -53,10 +53,16 @@ public: /// Calculate the time-weighted average of a property in a filtered range virtual double averageValueInFilter(const std::vector<SplittingInterval> &filter) const = 0; + /// Calculate the time-weighted average and standard deviation of a property + /// in a filtered range + virtual std::pair<double, double> averageAndStdDevInFilter( + const std::vector<SplittingInterval> &filter) const = 0; /// Return the time series's times as a vector<DateAndTime> virtual std::vector<Types::Core::DateAndTime> timesAsVector() const = 0; /// Returns the calculated time weighted average value virtual double timeAverageValue() const = 0; + /// Returns the calculated time weighted average value and standard deviation + virtual std::pair<double, double> timeAverageValueAndStdDev() const = 0; /// Returns the real size of the time series property map: virtual int realSize() const = 0; /// Deletes the series of values in the property diff --git a/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h b/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h index 3b1e4701ef5abc19f748b28b7d297588d449bc4d..1e2b4aa0de2affb3d0e7ed1ec8cd9ad77661a921 100644 --- a/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h +++ b/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h @@ -182,8 +182,13 @@ public: /// Calculate the time-weighted average of a property in a filtered range double averageValueInFilter( const std::vector<SplittingInterval> &filter) const override; + /// @copydoc Mantid::Kernel::ITimeSeriesProperty::averageAndStdDevInFilter() + std::pair<double, double> averageAndStdDevInFilter( + const std::vector<SplittingInterval> &filter) const override; /// Calculate the time-weighted average of a property double timeAverageValue() const override; + /// @copydoc Mantid::Kernel::ITimeSeriesProperty::timeAverageValueAndStdDev() + std::pair<double, double> timeAverageValueAndStdDev() const override; /// generate constant time-step histogram from the property values void histogramData(const Types::Core::DateAndTime &tMin, const Types::Core::DateAndTime &tMax, diff --git a/Framework/Kernel/src/TimeSeriesProperty.cpp b/Framework/Kernel/src/TimeSeriesProperty.cpp index f7c68f0fd9ec3c8969589687f815de83d185ae06..e0f5ff30b0ca5c797416bb4fe49a4610074f50ed 100644 --- a/Framework/Kernel/src/TimeSeriesProperty.cpp +++ b/Framework/Kernel/src/TimeSeriesProperty.cpp @@ -908,6 +908,22 @@ void TimeSeriesProperty<std::string>::expandFilterToRange( "properties"); } +/** Calculates the time-weighted average of a property. + * @return The time-weighted average value of the log. + */ +template <typename TYPE> +double TimeSeriesProperty<TYPE>::timeAverageValue() const { + double retVal = 0.0; + try { + const auto &filter = getSplittingIntervals(); + retVal = this->averageValueInFilter(filter); + } catch (std::exception &) { + // just return nan + retVal = std::numeric_limits<double>::quiet_NaN(); + } + return retVal; +} + /** Calculates the time-weighted average of a property in a filtered range. * This is written for that case of logs whose values start at the times given. * @param filter The splitter/filter restricting the range of values included @@ -959,30 +975,87 @@ double TimeSeriesProperty<TYPE>::averageValueInFilter( // 'Normalise' by the total time return numerator / totalTime; } -/** Calculates the time-weighted average of a property. - * @return The time-weighted average value of the log. + +/** Function specialization for TimeSeriesProperty<std::string> + * @throws Kernel::Exception::NotImplementedError always */ +template <> +double TimeSeriesProperty<std::string>::averageValueInFilter( + const TimeSplitterType &) const { + throw Exception::NotImplementedError("TimeSeriesProperty::" + "averageValueInFilter is not " + "implemented for string properties"); +} + template <typename TYPE> -double TimeSeriesProperty<TYPE>::timeAverageValue() const { - double retVal = 0.0; +std::pair<double, double> +TimeSeriesProperty<TYPE>::timeAverageValueAndStdDev() const { + std::pair<double, double> retVal{0., 0.}; // mean and stddev try { const auto &filter = getSplittingIntervals(); - retVal = this->averageValueInFilter(filter); + retVal = this->averageAndStdDevInFilter(filter); } catch (std::exception &) { - // just return nan - retVal = std::numeric_limits<double>::quiet_NaN(); + retVal.first = std::numeric_limits<double>::quiet_NaN(); + retVal.second = std::numeric_limits<double>::quiet_NaN(); } return retVal; } +template <typename TYPE> +std::pair<double, double> TimeSeriesProperty<TYPE>::averageAndStdDevInFilter( + const std::vector<SplittingInterval> &filter) const { + // the mean to calculate the standard deviation about + // this will sort the log as necessary as well + const double mean = this->averageValueInFilter(filter); + + // First of all, if the log or the filter is empty or is a single value, + // return NaN for the uncertainty + if (realSize() <= 1 || filter.empty()) { + return std::pair<double, double>{mean, + std::numeric_limits<double>::quiet_NaN()}; + } + + double numerator(0.0), totalTime(0.0); + // Loop through the filter ranges + for (const auto &time : filter) { + // Calculate the total time duration (in seconds) within by the filter + totalTime += time.duration(); + + // Get the log value and index at the start time of the filter + int index; + double value = getSingleValue(time.start(), index); + double valuestddev = (value - mean) * (value - mean); + DateAndTime startTime = time.start(); + + while (index < realSize() - 1 && m_values[index + 1].time() < time.stop()) { + ++index; + + numerator += + DateAndTime::secondsFromDuration(m_values[index].time() - startTime) * + valuestddev; + startTime = m_values[index].time(); + value = static_cast<double>(m_values[index].value()); + valuestddev = (value - mean) * (value - mean); + } + + // Now close off with the end of the current filter range + numerator += + DateAndTime::secondsFromDuration(time.stop() - startTime) * valuestddev; + } + + // Normalise by the total time + return std::pair<double, double>{mean, std::sqrt(numerator / totalTime)}; +} + /** Function specialization for TimeSeriesProperty<std::string> * @throws Kernel::Exception::NotImplementedError always */ template <> -double TimeSeriesProperty<std::string>::averageValueInFilter( +std::pair<double, double> +TimeSeriesProperty<std::string>::averageAndStdDevInFilter( const TimeSplitterType &) const { throw Exception::NotImplementedError("TimeSeriesProperty::" - "averageValueInFilter is not " + "averageAndStdDevInFilter is not " "implemented for string properties"); } diff --git a/Framework/Kernel/test/TimeSeriesPropertyTest.h b/Framework/Kernel/test/TimeSeriesPropertyTest.h index 4d5db2032e17d44f0a7f18fbfbc97d5300bc7776..c6c77858cbd249b507223a8ec3e9e9c2ad9de5df 100644 --- a/Framework/Kernel/test/TimeSeriesPropertyTest.h +++ b/Framework/Kernel/test/TimeSeriesPropertyTest.h @@ -613,8 +613,19 @@ public: auto dblLog = createDoubleTSP(); auto intLog = createIntegerTSP(5); - TS_ASSERT_DELTA(dblLog->timeAverageValue(), 7.6966, .0001); - TS_ASSERT_DELTA(intLog->timeAverageValue(), 2.5, .0001); + // average values + const double dblMean = dblLog->timeAverageValue(); + TS_ASSERT_DELTA(dblMean, 7.6966, .0001); + const double intMean = intLog->timeAverageValue(); + TS_ASSERT_DELTA(intMean, 2.5, .0001); + + // average is unchanged, standard deviation within tolerance + const auto dblPair = dblLog->timeAverageValueAndStdDev(); + TS_ASSERT_EQUALS(dblPair.first, dblMean); + TS_ASSERT_DELTA(dblPair.second, 1.8156, .0001); + const auto intPair = intLog->timeAverageValueAndStdDev(); + TS_ASSERT_EQUALS(intPair.first, intMean); + TS_ASSERT_DELTA(intPair.second, 1.1180, .0001); // Clean up delete dblLog; @@ -625,6 +636,8 @@ public: TimeSplitterType splitter; TS_ASSERT_THROWS(sProp->averageValueInFilter(splitter), Exception::NotImplementedError); + TS_ASSERT_THROWS(sProp->averageAndStdDevInFilter(splitter), + Exception::NotImplementedError); } //---------------------------------------------------------------------------- diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp index fc132c5a1705ca9fcc9406eb46f280e28c405e4a..9ed50890dba09b818b3904b4bc308a6b075158e2 100644 --- a/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp +++ b/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp @@ -10,6 +10,7 @@ #include <boost/python/make_function.hpp> #include <boost/python/register_ptr_to_python.hpp> #include <boost/python/return_value_policy.hpp> +#include <boost/python/tuple.hpp> using Mantid::Kernel::Property; using Mantid::Kernel::TimeSeriesProperty; @@ -36,6 +37,12 @@ void addPyTimeValue(TimeSeriesProperty<TYPE> &self, self.addValue(*dateandtime, value); } +template <typename TYPE> +tuple pyTimeAverageValueAndStdDev(const TimeSeriesProperty<TYPE> &self) { + const std::pair<double, double> value = self.timeAverageValueAndStdDev(); + return make_tuple(value.first, value.second); +} + // Call the dtype helper function template <typename TYPE> std::string dtype(TimeSeriesProperty<TYPE> &self) { return Mantid::PythonInterface::Converters::dtype(self); @@ -87,6 +94,8 @@ template <typename TYPE> std::string dtype(TimeSeriesProperty<TYPE> &self) { "returns :class:`mantid.kernel.TimeSeriesPropertyStatistics`") \ .def("timeAverageValue", &TimeSeriesProperty<TYPE>::timeAverageValue, \ arg("self")) \ + .def("timeAverageValueAndStdDev", &pyTimeAverageValueAndStdDev<TYPE>, \ + arg("self"), "Time average value and standard deviation") \ .def("dtype", &dtype<TYPE>, arg("self")); } // namespace diff --git a/MantidPlot/src/Mantid/MantidSampleLogDialog.cpp b/MantidPlot/src/Mantid/MantidSampleLogDialog.cpp index f39a3f0b4051d5aca2a94ca9e5c7d505f514ad76..4310a28a224a53f7c7208ed083366c1b8fb00eb3 100644 --- a/MantidPlot/src/Mantid/MantidSampleLogDialog.cpp +++ b/MantidPlot/src/Mantid/MantidSampleLogDialog.cpp @@ -65,8 +65,9 @@ MantidSampleLogDialog::MantidSampleLogDialog(const QString &wsname, } // -------------- Statistics on logs ------------------------ - std::string stats[NUM_STATS] = { - "Min:", "Max:", "Mean:", "Time Avg:", "Median:", "Std Dev:", "Duration:"}; + std::string stats[NUM_STATS] = {"Min:", "Max:", "Mean:", + "Median:", "Std Dev:", "Time Avg:", + "Time Std Dev:", "Duration:"}; QGroupBox *statsBox = new QGroupBox("Log Statistics"); QFormLayout *statsBoxLayout = new QFormLayout; for (size_t i = 0; i < NUM_STATS; i++) { diff --git a/MantidPlot/src/Mantid/SampleLogDialogBase.cpp b/MantidPlot/src/Mantid/SampleLogDialogBase.cpp index 06c5051001f540db926def34c826338d7637d40f..ff1be5af55faa170f108153e89d6931c9d3b8908 100644 --- a/MantidPlot/src/Mantid/SampleLogDialogBase.cpp +++ b/MantidPlot/src/Mantid/SampleLogDialogBase.cpp @@ -138,17 +138,17 @@ void SampleLogDialogBase::showLogStatisticsOfItem( dynamic_cast<TimeSeriesProperty<double> *>(logData); Mantid::Kernel::TimeSeriesProperty<int> *tspi = dynamic_cast<TimeSeriesProperty<int> *>(logData); - double timeAvg = 0.; + std::pair<double, double> timeAvgStdDev{0., 0.}; LogFilterGenerator generator(filter, m_ei->run()); const auto &logFilter = generator.generateFilter(logName); if (tspd) { ScopedFilter<double> applyFilter(tspd, std::move(logFilter)); stats = tspd->getStatistics(); - timeAvg = tspd->timeAverageValue(); + timeAvgStdDev = tspd->timeAverageValueAndStdDev(); } else if (tspi) { ScopedFilter<int> applyFilter(tspi, std::move(logFilter)); stats = tspi->getStatistics(); - timeAvg = tspi->timeAverageValue(); + timeAvgStdDev = tspi->timeAverageValueAndStdDev(); } else return; @@ -156,10 +156,11 @@ void SampleLogDialogBase::showLogStatisticsOfItem( statValues[0]->setText(QString::number(stats.minimum)); statValues[1]->setText(QString::number(stats.maximum)); statValues[2]->setText(QString::number(stats.mean)); - statValues[3]->setText(QString::number(timeAvg)); - statValues[4]->setText(QString::number(stats.median)); - statValues[5]->setText(QString::number(stats.standard_deviation)); - statValues[6]->setText(QString::number(stats.duration)); + statValues[3]->setText(QString::number(stats.median)); + statValues[4]->setText(QString::number(stats.standard_deviation)); + statValues[5]->setText(QString::number(timeAvgStdDev.first)); + statValues[6]->setText(QString::number(timeAvgStdDev.second)); + statValues[7]->setText(QString::number(stats.duration)); return; break; } diff --git a/MantidPlot/src/Mantid/SampleLogDialogBase.h b/MantidPlot/src/Mantid/SampleLogDialogBase.h index 4def7f419a815051192d276248be366706125ff1..f43255eb27beeae7d38804b4557a5995a15558c0 100644 --- a/MantidPlot/src/Mantid/SampleLogDialogBase.h +++ b/MantidPlot/src/Mantid/SampleLogDialogBase.h @@ -133,7 +133,7 @@ protected: QPushButton *buttonPlot, *buttonClose; /// Number of statistic values - static const std::size_t NUM_STATS = 7; + static const std::size_t NUM_STATS = 8; /// Testboxes with stats data QLineEdit *statValues[NUM_STATS]; @@ -170,4 +170,4 @@ private: Mantid::Kernel::TimeSeriesProperty<T> *m_prop; }; -#endif // SAMPLELOGDIALOGBASE_H_ \ No newline at end of file +#endif // SAMPLELOGDIALOGBASE_H_ diff --git a/docs/source/release/v3.14.0/ui.rst b/docs/source/release/v3.14.0/ui.rst index 2cf287d0f60caeac9dcd2b18413976ce8e08e2e8..15bb628796f555cae6105e972673b3bbba6a6276 100644 --- a/docs/source/release/v3.14.0/ui.rst +++ b/docs/source/release/v3.14.0/ui.rst @@ -9,4 +9,6 @@ UI & Usability Changes putting new features at the top of the section, followed by improvements, followed by bug fixes. +- Added time standard deviation to the sample log dialog + :ref:`Release 3.14.0 <v3.14.0>`