diff --git a/ApplicationCode/ReservoirDataModel/RigCurveDataTools.cpp b/ApplicationCode/ReservoirDataModel/RigCurveDataTools.cpp index e45187597c..7b26ebb343 100644 --- a/ApplicationCode/ReservoirDataModel/RigCurveDataTools.cpp +++ b/ApplicationCode/ReservoirDataModel/RigCurveDataTools.cpp @@ -19,7 +19,10 @@ #include "RigCurveDataTools.h" +#include + #include // Needed for HUGE_VAL on Linux +#include //-------------------------------------------------------------------------------------------------- @@ -85,6 +88,7 @@ std::vector> RigCurveDataTools::computePolyLineStartSt return lineStartAndStopIndices; } + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -102,3 +106,221 @@ bool RigCurveDataTools::isValidValue(double value, bool removeNegativeValues) return true; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RigCurveDataTools::isValidValue(double value) +{ + if (value == HUGE_VAL || value == -HUGE_VAL || value != value) + { + return false; + } + + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RigCurveDataInterpolationTools::RigCurveDataInterpolationTools(const std::vector& valuesA, + const std::vector& timeStepsA, + const std::vector& valuesB, + const std::vector& timeStepsB) + : m_valuesA(valuesA), + m_timeStepsA(timeStepsA), + m_valuesB(valuesB), + m_timeStepsB(timeStepsB) +{ + computeInterpolatedValues(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RigCurveDataTools::CurveIntervals RigCurveDataInterpolationTools::validIntervals() const +{ + return m_curveIntervals; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector> RigCurveDataInterpolationTools::interpolatedCurveData() const +{ + return m_interpolatedValues; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RigCurveDataInterpolationTools::computeInterpolatedValues() +{ + if (m_valuesA.size() != m_timeStepsA.size() || m_valuesB.size() != m_timeStepsB.size()) + { + return; + } + + const bool removeNegativeValues = false; + + auto validIntervalsA = RigCurveDataTools::calculateIntervalsOfValidValues(m_valuesA, removeNegativeValues); + + std::vector> validTimeStepsA; + for (const auto& interval : validIntervalsA) + { + validTimeStepsA.push_back(std::make_pair(m_timeStepsA[interval.first], m_timeStepsA[interval.second])); + } + + auto validIntervalsB = RigCurveDataTools::calculateIntervalsOfValidValues(m_valuesB, removeNegativeValues); + for (const auto& interval : validIntervalsB) + { + const QDateTime& from = m_timeStepsB[interval.first]; + const QDateTime& to = m_timeStepsB[interval.second]; + + auto intervals = intersectingValidIntervals(from, to, validTimeStepsA); + + for (const auto& i : intervals) + { + std::set validTimeSteps; + + // Add all time steps from curve A inside interval + for (const auto& d : m_timeStepsA) + { + if (i.first <= d && d <= i.second) + { + validTimeSteps.insert(d); + } + } + + // Add all time steps from curve B inside interval + for (const auto& d : m_timeStepsB) + { + if (i.first <= d && d <= i.second) + { + validTimeSteps.insert(d); + } + } + + size_t firstIndex = m_interpolatedValues.size(); + for (const auto& dt : validTimeSteps) + { + double valueA = RigCurveDataInterpolationTools::interpolatedValue(dt, m_valuesA, m_timeStepsA); + double valueB = RigCurveDataInterpolationTools::interpolatedValue(dt, m_valuesB, m_timeStepsB); + + m_interpolatedValues.push_back(std::make_tuple(dt, valueA, valueB)); + } + size_t lastIndex = m_interpolatedValues.size() - 1; + + m_curveIntervals.push_back(std::make_pair(firstIndex, lastIndex)); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector> RigCurveDataInterpolationTools::intersectingValidIntervals(const QDateTime& a, + const QDateTime& b, + const std::vector>& intervals) +{ + std::vector> validIntervals; + + for (const auto& interval : intervals) + { + const QDateTime& c = interval.first; + const QDateTime& d = interval.second; + + if (d < a) + { + continue; + } + + if (b < c) + { + // We assume the intervals are increasing, and all other intervals are larger + break; + } + + if (c <= a) + { + if (b <= d) + { + validIntervals.push_back(std::make_pair(a, b)); + } + else + { + validIntervals.push_back(std::make_pair(a, d)); + } + } + else + { + if (b <= d) + { + validIntervals.push_back(std::make_pair(c, b)); + } + else + { + validIntervals.push_back(std::make_pair(c, d)); + } + } + } + + return validIntervals; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RigCurveDataInterpolationTools::interpolatedValue(const QDateTime& dt, const std::vector& values, const std::vector& timeSteps) +{ + if (values.size() != timeSteps.size()) return HUGE_VAL; + + for (size_t firstI = 0; firstI < timeSteps.size(); firstI++) + { + size_t secondI = firstI + 1; + + if (secondI == timeSteps.size()) + { + if (timeSteps[firstI] == dt) + { + return values[firstI]; + } + } + + if (secondI < timeSteps.size() && + timeSteps[firstI] <= dt && + timeSteps[secondI] > dt) + { + const double& firstValue = values[firstI]; + const double& secondValue = values[secondI]; + + bool isFirstValid = RigCurveDataTools::isValidValue(firstValue); + bool isSecondValid = RigCurveDataTools::isValidValue(secondValue); + + if (!isFirstValid && !isSecondValid) + { + CVF_ASSERT(false); + + return HUGE_VAL; + } + + if (!isFirstValid) return secondValue; + if (!isSecondValid) return firstValue; + + double firstDiff = timeSteps[firstI].secsTo(dt); + double secondDiff = dt.secsTo(timeSteps[secondI]); + + double firstWeight = secondDiff / (firstDiff + secondDiff); + double secondWeight = firstDiff / (firstDiff + secondDiff); + + double val = (firstValue * firstWeight) + (secondValue * secondWeight); + + CVF_ASSERT(RigCurveDataTools::isValidValue(val)); + + return val; + } + } + + return HUGE_VAL; +} + diff --git a/ApplicationCode/ReservoirDataModel/RigCurveDataTools.h b/ApplicationCode/ReservoirDataModel/RigCurveDataTools.h index 616273eef6..df17f589a3 100644 --- a/ApplicationCode/ReservoirDataModel/RigCurveDataTools.h +++ b/ApplicationCode/ReservoirDataModel/RigCurveDataTools.h @@ -23,6 +23,10 @@ #include #include +#include +#include + +class QDateTime; //================================================================================================== @@ -54,7 +58,53 @@ public: } static std::vector> computePolyLineStartStopIndices(const CurveIntervals& intervals); - -private: + +public: + // Helper methods, available as public to be able to access from unit tests + static bool isValidValue(double value, bool removeNegativeValues); + static bool isValidValue(double value); +}; + + +//================================================================================================== +/// +//================================================================================================== +class RigCurveDataInterpolationTools +{ +public: + RigCurveDataInterpolationTools(const std::vector& valuesA, + const std::vector& timeStepsA, + const std::vector& valuesB, + const std::vector& timeStepsB); + + + + RigCurveDataTools::CurveIntervals validIntervals() const; + std::vector> interpolatedCurveData() const; + + + +public: + // Helper methods, available as public to be able to access from unit tests + + static std::vector> intersectingValidIntervals(const QDateTime& from, + const QDateTime& to, + const std::vector>& intervals); + + static double interpolatedValue(const QDateTime& dt, + const std::vector& values, + const std::vector& timeSteps); + +private: + void computeInterpolatedValues(); + +private: + const std::vector& m_valuesA; + const std::vector& m_timeStepsA; + const std::vector& m_valuesB; + const std::vector& m_timeStepsB; + + std::vector> m_interpolatedValues; + RigCurveDataTools::CurveIntervals m_curveIntervals; }; diff --git a/ApplicationCode/UnitTests/RimWellLogExtractionCurveImpl-Test.cpp b/ApplicationCode/UnitTests/RimWellLogExtractionCurveImpl-Test.cpp index 004e977b54..383ccb6941 100644 --- a/ApplicationCode/UnitTests/RimWellLogExtractionCurveImpl-Test.cpp +++ b/ApplicationCode/UnitTests/RimWellLogExtractionCurveImpl-Test.cpp @@ -3,6 +3,8 @@ #include "RigCurveDataTools.h" #include // Needed for HUGE_VAL on Linux +#include "QDateTime" +#include //-------------------------------------------------------------------------------------------------- @@ -49,3 +51,244 @@ TEST(RimWellLogExtractionCurveImplTest, StripOffHugeValAtEndsAndInteriorOfVector EXPECT_EQ(5, static_cast(valuesIntervals[1].first)); EXPECT_EQ(6, static_cast(valuesIntervals[1].second)); } + + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RimWellLogExtractionCurveImplTest, DateOverlap) +{ + std::vector> dayIntervalsA { {5, 7}, {9, 12}, {15, 15}, {20, 30} , {30, 31}}; + std::vector> dayIntervalsB { {3, 5}, {8, 13}, {15, 15}, {21, 22}, {25, 27}}; + + QDateTime startDate; + + std::vector> intervalsA; + for (const auto& interval : dayIntervalsA) + { + intervalsA.push_back(std::make_pair(startDate.addDays(interval.first), startDate.addDays(interval.second))); + } + + std::vector> intervalsB; + for (const auto& interval : dayIntervalsB) + { + intervalsB.push_back(std::make_pair(startDate.addDays(interval.first), startDate.addDays(interval.second))); + } + + std::vector> allDayIntervals; + + for (const auto& intervalA : intervalsA) + { + auto intersecting = RigCurveDataInterpolationTools::intersectingValidIntervals(intervalA.first, intervalA.second, intervalsB); + for (const auto& i : intersecting) + { + allDayIntervals.push_back(std::make_pair(startDate.daysTo(i.first), startDate.daysTo(i.second))); + } + } + + EXPECT_EQ(5, static_cast(allDayIntervals.size())); + + EXPECT_EQ(5, allDayIntervals[0].first); + EXPECT_EQ(5, allDayIntervals[0].second); + + EXPECT_EQ( 9, allDayIntervals[1].first); + EXPECT_EQ(12, allDayIntervals[1].second); + + EXPECT_EQ(15, allDayIntervals[2].first); + EXPECT_EQ(15, allDayIntervals[2].second); + + EXPECT_EQ(21, allDayIntervals[3].first); + EXPECT_EQ(22, allDayIntervals[3].second); + + EXPECT_EQ(25, allDayIntervals[4].first); + EXPECT_EQ(27, allDayIntervals[4].second); + +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RimWellLogExtractionCurveImplTest, TestDateInterpolation) +{ + std::vector values{ 2.0, 3.5, 5.0, 6.0}; + std::vector days{ 1, 5, 10, 15}; + + QDateTime startDate; + + std::vector timeSteps; + for (const auto& day : days) + { + timeSteps.push_back(startDate.addDays(day)); + } + + { + QDateTime dt = startDate.addDays(1); + double val = RigCurveDataInterpolationTools::interpolatedValue(dt, values, timeSteps); + + EXPECT_EQ(2.0, val); + } + + { + QDateTime dt = startDate.addDays(0); + double val = RigCurveDataInterpolationTools::interpolatedValue(dt, values, timeSteps); + + EXPECT_EQ(HUGE_VAL, val); + } + + { + QDateTime dt = startDate.addDays(20); + double val = RigCurveDataInterpolationTools::interpolatedValue(dt, values, timeSteps); + + EXPECT_EQ(HUGE_VAL, val); + } + + { + QDateTime dt = startDate.addDays(3); + double val = RigCurveDataInterpolationTools::interpolatedValue(dt, values, timeSteps); + + EXPECT_EQ(2.75, val); + } + +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RimWellLogExtractionCurveImplTest, ExtractIntervalsWithSameTimeSteps) +{ + std::vector valuesA { HUGE_VAL, 1.0, HUGE_VAL, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, HUGE_VAL }; + std::vector valuesB { 10, 20, 30, 40, 45, HUGE_VAL, HUGE_VAL, 5.0, 6.0, HUGE_VAL }; + + EXPECT_EQ(valuesA.size(), valuesB.size()); + + std::vector days(10); + std::iota(days.begin(), days.end(), 10); + + QDateTime startDate; + + std::vector timeSteps; + for (const auto& day : days) + { + timeSteps.push_back(startDate.addDays(day)); + } + + RigCurveDataInterpolationTools interpolate(valuesA, timeSteps, valuesB, timeSteps); + + auto values = interpolate.interpolatedCurveData(); + auto intervals = interpolate.validIntervals(); + + EXPECT_EQ(5, static_cast(values.size())); + EXPECT_EQ(3, static_cast(intervals.size())); + + EXPECT_EQ( 1.0, std::get<1>(values[0])); + EXPECT_EQ(20.0, std::get<2>(values[0])); + + EXPECT_EQ( 2.0, std::get<1>(values[1])); + EXPECT_EQ(40.0, std::get<2>(values[1])); + + EXPECT_EQ( 2.5, std::get<1>(values[2])); + EXPECT_EQ(45.0, std::get<2>(values[2])); + + EXPECT_EQ(5.0, std::get<1>(values[3])); + EXPECT_EQ(5.0, std::get<2>(values[3])); + + EXPECT_EQ(6.0, std::get<1>(values[4])); + EXPECT_EQ(6.0, std::get<2>(values[4])); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RimWellLogExtractionCurveImplTest, ExtractIntervalsWithSameTimeStepsOneComplete) +{ + std::vector valuesA { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 }; + std::vector valuesB { 10, 20, 30, HUGE_VAL, 50, HUGE_VAL, 70 }; + + EXPECT_EQ(valuesA.size(), valuesB.size()); + + std::vector days(7); + std::iota(days.begin(), days.end(), 10); + + QDateTime startDate; + + std::vector timeSteps; + for (const auto& day : days) + { + timeSteps.push_back(startDate.addDays(day)); + } + + RigCurveDataInterpolationTools interpolate(valuesA, timeSteps, valuesB, timeSteps); + + auto values = interpolate.interpolatedCurveData(); + auto intervals = interpolate.validIntervals(); + + EXPECT_EQ(5, static_cast(values.size())); + EXPECT_EQ(3, static_cast(intervals.size())); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RimWellLogExtractionCurveImplTest, ExtractIntervalsWithSameTimeStepsBothComplete) +{ + std::vector valuesA{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 }; + std::vector valuesB{ 10, 20, 30, 40, 50, 60, 70 }; + + EXPECT_EQ(valuesA.size(), valuesB.size()); + + std::vector days(7); + std::iota(days.begin(), days.end(), 10); + + QDateTime startDate; + + std::vector timeSteps; + for (const auto& day : days) + { + timeSteps.push_back(startDate.addDays(day)); + } + + RigCurveDataInterpolationTools interpolate(valuesA, timeSteps, valuesB, timeSteps); + + auto values = interpolate.interpolatedCurveData(); + auto intervals = interpolate.validIntervals(); + + EXPECT_EQ(7, static_cast(values.size())); + EXPECT_EQ(1, static_cast(intervals.size())); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RimWellLogExtractionCurveImplTest, OverlappintTimes) +{ + std::vector valuesA{ 1, 2, 3, 4, 5 }; + std::vector valuesB{ 10, 20, 30, 40, 50 }; + + EXPECT_EQ(valuesA.size(), valuesB.size()); + + std::vector daysA{ 0, 10, 11, 15, 20 }; + std::vector daysB{ 1, 2, 3, 5, 7 }; + + QDateTime startDate; + + std::vector timeStepsA; + for (const auto& day : daysA) + { + timeStepsA.push_back(startDate.addDays(day)); + } + + std::vector timeStepsB; + for (const auto& day : daysB) + { + timeStepsB.push_back(startDate.addDays(day)); + } + + + RigCurveDataInterpolationTools interpolate(valuesA, timeStepsA, valuesB, timeStepsB); + + auto values = interpolate.interpolatedCurveData(); + auto intervals = interpolate.validIntervals(); + + EXPECT_EQ(5, static_cast(values.size())); + EXPECT_EQ(1, static_cast(intervals.size())); +}