diff --git a/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_8_0_TBD.md b/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_8_0_TBD.md index a232fe46b1..63a8e31bce 100644 --- a/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_8_0_TBD.md +++ b/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_8_0_TBD.md @@ -86,6 +86,10 @@ For a list of deprecated and removed methods, please refer to [deprecated_method * A number of methods have been renamed (and the old ones deprecated) to conform to the API for other `SpaceLoadInstance` / `SpaceLoadDefinition` objects * Mostly `getWattsperUnit` is changed to `getDesignLevel` and `getWattsperZoneFloorArea` is changed to `getPowerPerFloorArea` * Refer to [deprecated_methods.csv](../../ruby/deprecated_methods.csv) for the complete list +* [#5111](https://github.com/NREL/OpenStudio/pull/5111) - ScheduleDay: new timeseries method and interpolation options + * The `ScheduleDay` has API-breaking changes related to setters and getters for its `Interpolate To Timestep` field. They now use `string` rather than `bool` to conform to the IDD type `\choice` + * The forward translator for `ScheduleDay` replaces always setting "Average" with the interpolation method stored in `Interpolate to Timestep` field + * A new `timeseries()` method is introduced for returning vectors of times and (interpolated) values for the given number of timesteps per hour established by the `Timestep` object; `getValue` references this vector for an interpolated value at a given time ## Minor changes and bug fixes diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd index d9b314c5b7..105e0d2169 100644 --- a/resources/model/OpenStudio.idd +++ b/resources/model/OpenStudio.idd @@ -4594,12 +4594,13 @@ OS:Schedule:Day, \type object-list \object-list ScheduleTypeLimitsNames A4, \field Interpolate to Timestep - \note when the interval does not match the user specified timestep a Yes choice will average between the intervals request (to - \note timestep resolution. a No choice will use the interval value at the simulation timestep without regard to if it matches - \note the boundary or not. + \note when the interval does not match the user specified timestep a Average choice will average between the intervals request (to + \note timestep resolution. A No choice will use the interval value at the simulation timestep without regard to if it matches + \note the boundary or not. A Linear choice will interpolate linearly between successive values. \type choice \default No - \key Yes + \key Average + \key Linear \key No N1, \field Hour \type integer diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateScheduleDay.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateScheduleDay.cpp index f4a15c27a4..960598b417 100644 --- a/src/energyplus/ForwardTranslator/ForwardTranslateScheduleDay.cpp +++ b/src/energyplus/ForwardTranslator/ForwardTranslateScheduleDay.cpp @@ -39,11 +39,7 @@ namespace energyplus { } } - if (modelObject.interpolatetoTimestep()) { - scheduleDay.setString(Schedule_Day_IntervalFields::InterpolatetoTimestep, "Average"); - } else { - scheduleDay.setString(Schedule_Day_IntervalFields::InterpolatetoTimestep, "No"); - } + scheduleDay.setString(Schedule_Day_IntervalFields::InterpolatetoTimestep, modelObject.interpolatetoTimestep()); std::vector values = modelObject.values(); std::vector times = modelObject.times(); diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateScheduleDayInterval.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateScheduleDayInterval.cpp index 69a76331a3..59e88bce0f 100644 --- a/src/energyplus/ReverseTranslator/ReverseTranslateScheduleDayInterval.cpp +++ b/src/energyplus/ReverseTranslator/ReverseTranslateScheduleDayInterval.cpp @@ -50,13 +50,7 @@ namespace energyplus { s = workspaceObject.getString(Schedule_Day_IntervalFields::InterpolatetoTimestep); if (s) { - if (openstudio::istringEqual(*s, "No")) { - scheduleDay.setInterpolatetoTimestep(false); - } else if (openstudio::istringEqual(*s, "Linear")) { - scheduleDay.setInterpolatetoTimestep(true); - } else if (openstudio::istringEqual(*s, "Average")) { - scheduleDay.setInterpolatetoTimestep(true); - } + scheduleDay.setInterpolatetoTimestep(*s); } //get extensible groups diff --git a/src/energyplus/Test/ScheduleRuleset_GTest.cpp b/src/energyplus/Test/ScheduleRuleset_GTest.cpp index 946fa1bbe9..e3e0ea7b61 100644 --- a/src/energyplus/Test/ScheduleRuleset_GTest.cpp +++ b/src/energyplus/Test/ScheduleRuleset_GTest.cpp @@ -698,7 +698,7 @@ TEST_F(EnergyPlusFixture, ReverseTranslator_ScheduleYearWeekDailyToRulesetSimple EXPECT_EQ(0, scheduleRule.ruleIndex()); ScheduleDay daySchedule = scheduleRule.daySchedule(); EXPECT_EQ(daySchedule.nameString(), "occupants schedule allday1 1"); - EXPECT_FALSE(daySchedule.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule.interpolatetoTimestep()); EXPECT_EQ(8u, daySchedule.values().size()); EXPECT_TRUE(scheduleRule.applySunday()); EXPECT_TRUE(scheduleRule.applyMonday()); @@ -864,7 +864,7 @@ TEST_F(EnergyPlusFixture, ReverseTranslator_ScheduleYearWeekDailyToRulesetComple EXPECT_EQ(2, scheduleRule1.ruleIndex()); ScheduleDay daySchedule1 = scheduleRule1.daySchedule(); EXPECT_EQ(daySchedule1.nameString(), "occupants schedule allday1 1"); - EXPECT_FALSE(daySchedule1.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule1.interpolatetoTimestep()); EXPECT_EQ(8u, daySchedule1.values().size()); EXPECT_TRUE(scheduleRule1.applySunday()); EXPECT_TRUE(scheduleRule1.applyMonday()); @@ -888,7 +888,7 @@ TEST_F(EnergyPlusFixture, ReverseTranslator_ScheduleYearWeekDailyToRulesetComple EXPECT_EQ(1, scheduleRule2.ruleIndex()); ScheduleDay daySchedule2 = scheduleRule2.daySchedule(); EXPECT_EQ(daySchedule2.nameString(), "occupants schedule allday1 2"); - EXPECT_FALSE(daySchedule2.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule2.interpolatetoTimestep()); EXPECT_EQ(8u, daySchedule2.values().size()); EXPECT_TRUE(scheduleRule2.applySunday()); EXPECT_TRUE(scheduleRule2.applyMonday()); @@ -912,7 +912,7 @@ TEST_F(EnergyPlusFixture, ReverseTranslator_ScheduleYearWeekDailyToRulesetComple EXPECT_EQ(0, scheduleRule3.ruleIndex()); ScheduleDay daySchedule3 = scheduleRule3.daySchedule(); EXPECT_EQ(daySchedule3.nameString(), "occupants schedule allday2 1"); - EXPECT_FALSE(daySchedule3.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule3.interpolatetoTimestep()); EXPECT_EQ(8u, daySchedule3.values().size()); EXPECT_FALSE(scheduleRule3.applySunday()); EXPECT_FALSE(scheduleRule3.applyMonday()); @@ -1220,7 +1220,7 @@ TEST_F(EnergyPlusFixture, ReverseTranslator_ScheduleYearWeekCompactToRulesetComp EXPECT_EQ(2, scheduleRule1.ruleIndex()); ScheduleDay daySchedule1 = scheduleRule1.daySchedule(); EXPECT_EQ(daySchedule1.nameString(), "occupants schedule allday1 1"); - EXPECT_FALSE(daySchedule1.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule1.interpolatetoTimestep()); EXPECT_EQ(8u, daySchedule1.values().size()); EXPECT_TRUE(scheduleRule1.applySunday()); EXPECT_TRUE(scheduleRule1.applyMonday()); @@ -1244,7 +1244,7 @@ TEST_F(EnergyPlusFixture, ReverseTranslator_ScheduleYearWeekCompactToRulesetComp EXPECT_EQ(1, scheduleRule2.ruleIndex()); ScheduleDay daySchedule2 = scheduleRule2.daySchedule(); EXPECT_EQ(daySchedule2.nameString(), "occupants schedule allday1 2"); - EXPECT_FALSE(daySchedule2.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule2.interpolatetoTimestep()); EXPECT_EQ(8u, daySchedule2.values().size()); EXPECT_TRUE(scheduleRule2.applySunday()); EXPECT_TRUE(scheduleRule2.applyMonday()); @@ -1268,7 +1268,7 @@ TEST_F(EnergyPlusFixture, ReverseTranslator_ScheduleYearWeekCompactToRulesetComp EXPECT_EQ(0, scheduleRule3.ruleIndex()); ScheduleDay daySchedule3 = scheduleRule3.daySchedule(); EXPECT_EQ(daySchedule3.nameString(), "occupants schedule allday2 1"); - EXPECT_FALSE(daySchedule3.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule3.interpolatetoTimestep()); EXPECT_EQ(8u, daySchedule3.values().size()); EXPECT_FALSE(scheduleRule3.applySunday()); EXPECT_FALSE(scheduleRule3.applyMonday()); diff --git a/src/model/ScheduleDay.cpp b/src/model/ScheduleDay.cpp index 66ddbc97a0..5efe02d0fc 100644 --- a/src/model/ScheduleDay.cpp +++ b/src/model/ScheduleDay.cpp @@ -16,6 +16,8 @@ #include "ScheduleRuleset_Impl.hpp" #include "ScheduleRule.hpp" #include "ScheduleRule_Impl.hpp" +#include "Timestep.hpp" +#include "Timestep_Impl.hpp" #include "../utilities/idf/IdfExtensibleGroup.hpp" #include @@ -25,6 +27,7 @@ #include "../utilities/core/Assert.hpp" #include "../utilities/time/Time.hpp" +#include "../utilities/data/TimeSeries.hpp" #include "../utilities/data/Vector.hpp" namespace openstudio { @@ -115,8 +118,10 @@ namespace model { return !getObject().getModelObjectTarget(OS_Schedule_DayFields::ScheduleTypeLimitsName); } - bool ScheduleDay_Impl::interpolatetoTimestep() const { - return getBooleanFieldValue(OS_Schedule_DayFields::InterpolatetoTimestep); + std::string ScheduleDay_Impl::interpolatetoTimestep() const { + boost::optional value = getString(OS_Schedule_DayFields::InterpolatetoTimestep, true); + OS_ASSERT(value); + return value.get(); } bool ScheduleDay_Impl::isInterpolatetoTimestepDefaulted() const { @@ -176,42 +181,132 @@ namespace model { return 0.0; } - std::vector values = this->values(); // these are already sorted - std::vector times = this->times(); // these are already sorted + // We'll calculate the entire day when we request a single value but that's on purpose: + // * We're talking about max 60 timesteps * 24 hours = 1440 points, + // but realistically more often than not you'll have a timestep of 6 (our default) or 4 so 96 to 144 points. So not a lot of points + // * We cache the timeSeries, and, + // * More often than not, the use case is to do it for the entire day anyways (eg: openstudio-standards to determine occupancy schedules) + TimeSeries ts = this->timeSeries(); - unsigned N = times.size(); + DateTimeVector dateTimes = ts.dateTimes(); + Vector values = ts.values(); + + const unsigned N = dateTimes.size(); OS_ASSERT(values.size() == N); if (N == 0) { return 0.0; } - openstudio::Vector x(N + 2); - openstudio::Vector y(N + 2); - - x[0] = -0.000001; - y[0] = 0.0; + Vector x(N + 2); + Vector y(N + 2); + + x[0] = 0.0; + std::string interpolatetoTimestep = this->interpolatetoTimestep(); + if (istringEqual("No", interpolatetoTimestep)) { + y[0] = values[0]; + } else if (istringEqual("Average", interpolatetoTimestep)) { + y[0] = values[0]; + } else if (istringEqual("Linear", interpolatetoTimestep)) { + y[0] = 0.0; + } for (unsigned i = 0; i < N; ++i) { - x[i + 1] = times[i].totalDays(); + openstudio::Time t = dateTimes[i].time(); + if (t.totalDays() == 0.0) { // this is 00:00:00 from the next day + t = openstudio::Time(0, 24, 0); + } + + x[i + 1] = t.totalDays(); y[i + 1] = values[i]; } - x[N + 1] = 1.000001; - y[N + 1] = 0.0; + x[N + 1] = 1.0; + y[N + 1] = values[N - 1]; - InterpMethod interpMethod; - if (this->interpolatetoTimestep()) { - interpMethod = LinearInterp; - } else { - interpMethod = HoldNextInterp; - } - - double result = interp(x, y, time.totalDays(), interpMethod, NoneExtrap); + double result = interp(x, y, time.totalDays(), HoldNextInterp, NoneExtrap); return result; } + openstudio::TimeSeries ScheduleDay_Impl::timeSeries() const { + if (!m_cachedTimeSeries) { + + int numberOfTimestepsPerHour; + if (boost::optional timestep = this->model().getOptionalUniqueModelObject()) { + numberOfTimestepsPerHour = timestep->numberOfTimestepsPerHour(); + } else { + numberOfTimestepsPerHour = 6; + } + + Date startDate(Date(MonthOfYear(MonthOfYear::Jan), 1)); // this is arbitrary + int minutes = 60 / numberOfTimestepsPerHour; + DateTime startDateTime(startDate, Time(0, 0, 0)); + + DateTimeVector tsDateTimes; + for (size_t hour = 0; hour < 24; ++hour) { + for (size_t minute = minutes; minute <= 60; minute += minutes) { + if (minute == 60) { + openstudio::Time t(0, hour + 1, 0); + tsDateTimes.push_back(startDateTime + t); + } else { + openstudio::Time t(0, hour, minute); + tsDateTimes.push_back(startDateTime + t); + } + } + } + + std::vector values = this->values(); // these are already sorted + std::vector times = this->times(); // these are already sorted + + const unsigned N = times.size(); + OS_ASSERT(values.size() == N); + + TimeSeries result; + if (N == 0) { + return result; + } + + Vector x(N + 2); + Vector y(N + 2); + + x[0] = -0.000001; + y[0] = 0.0; + + for (unsigned i = 0; i < N; ++i) { + x[i + 1] = times[i].totalSeconds(); + y[i + 1] = values[i]; + } + + x[N + 1] = 86400.000001; + y[N + 1] = 0.0; + + std::string interpolatetoTimestep = this->interpolatetoTimestep(); + Vector tsValues(tsDateTimes.size()); + for (unsigned j = 0; j < tsDateTimes.size(); ++j) { + openstudio::Time t = tsDateTimes[j].time(); + if (t.totalDays() == 0.0) { // this is 00:00:00 from the next day + t = openstudio::Time(0, 24, 0); + } + + if (istringEqual("No", interpolatetoTimestep)) { + tsValues[j] = interp(x, y, t.totalSeconds(), HoldNextInterp, NoneExtrap); + } else if (istringEqual("Average", interpolatetoTimestep)) { + double minutes = 60.0 / numberOfTimestepsPerHour; + double ti = minutes * 60.0; // total seconds of the timestep interval + tsValues[j] = interp(x, y, t.totalSeconds(), AverageInterp, NoneExtrap, ti); + } else if (istringEqual("Linear", interpolatetoTimestep)) { + tsValues[j] = interp(x, y, t.totalSeconds(), LinearInterp, NoneExtrap); + } + } + + result = TimeSeries(tsDateTimes, tsValues, ""); + m_cachedTimeSeries = result; + } + + return m_cachedTimeSeries.get(); + } + bool ScheduleDay_Impl::setScheduleTypeLimits(const ScheduleTypeLimits& scheduleTypeLimits) { if (scheduleTypeLimits.model() != model()) { return false; @@ -229,9 +324,8 @@ namespace model { return false; } - bool ScheduleDay_Impl::setInterpolatetoTimestep(bool interpolatetoTimestep) { - return setBooleanFieldValue(OS_Schedule_DayFields::InterpolatetoTimestep, interpolatetoTimestep); - ; + bool ScheduleDay_Impl::setInterpolatetoTimestep(const std::string& interpolatetoTimestep) { + return setString(OS_Schedule_DayFields::InterpolatetoTimestep, interpolatetoTimestep); } void ScheduleDay_Impl::resetInterpolatetoTimestep() { @@ -369,6 +463,11 @@ namespace model { void ScheduleDay_Impl::clearCachedVariables() { m_cachedTimes.reset(); m_cachedValues.reset(); + m_cachedTimeSeries.reset(); + } + + void ScheduleDay_Impl::clearCachedTimeSeries() { + m_cachedTimeSeries.reset(); } } // namespace detail @@ -394,7 +493,7 @@ namespace model { return getImpl()->isScheduleTypeLimitsDefaulted(); } - bool ScheduleDay::interpolatetoTimestep() const { + std::string ScheduleDay::interpolatetoTimestep() const { return getImpl()->interpolatetoTimestep(); } @@ -414,7 +513,11 @@ namespace model { return getImpl()->getValue(time); } - bool ScheduleDay::setInterpolatetoTimestep(bool interpolatetoTimestep) { + openstudio::TimeSeries ScheduleDay::timeSeries() const { + return getImpl()->timeSeries(); + } + + bool ScheduleDay::setInterpolatetoTimestep(const std::string& interpolatetoTimestep) { return getImpl()->setInterpolatetoTimestep(interpolatetoTimestep); } diff --git a/src/model/ScheduleDay.hpp b/src/model/ScheduleDay.hpp index ce64172a47..70feeba5e1 100644 --- a/src/model/ScheduleDay.hpp +++ b/src/model/ScheduleDay.hpp @@ -54,7 +54,7 @@ namespace model { * inherits one from a Schedule. */ bool isScheduleTypeLimitsDefaulted() const; - bool interpolatetoTimestep() const; + std::string interpolatetoTimestep() const; bool isInterpolatetoTimestepDefaulted() const; @@ -66,14 +66,18 @@ namespace model { /// Returns a vector of values in the same order and with the same number of elements as times. std::vector values() const; - /// Returns the value in effect at the given time. If time is less than 0 days or greater than 1 day, 0 is returned. + /// Returns the value in effect at the given time. + /// If time is less than 0 days or greater than 1 day, 0 is returned. double getValue(const openstudio::Time& time) const; + /// Returns the timeseries corresponding to simulation timestep and chosen interpolation method. + openstudio::TimeSeries timeSeries() const; + //@} /** @name Setters */ //@{ - bool setInterpolatetoTimestep(bool interpolatetoTimestep); + bool setInterpolatetoTimestep(const std::string& interpolatetoTimestep); void resetInterpolatetoTimestep(); diff --git a/src/model/ScheduleDay_Impl.hpp b/src/model/ScheduleDay_Impl.hpp index 564fec1a27..ff704c4ac0 100644 --- a/src/model/ScheduleDay_Impl.hpp +++ b/src/model/ScheduleDay_Impl.hpp @@ -10,15 +10,20 @@ #include "ScheduleBase_Impl.hpp" #include "../utilities/time/Time.hpp" +#include "../utilities/data/TimeSeries.hpp" namespace openstudio { +class TimeSeries; + namespace model { class ScheduleTypeLimits; namespace detail { + class Timestep_Impl; + /** ScheduleDay_Impl is a ResourceObject_Impl that is the implementation class for ScheduleDay.*/ class MODEL_API ScheduleDay_Impl : public ScheduleBase_Impl { @@ -57,7 +62,7 @@ namespace model { bool isScheduleTypeLimitsDefaulted() const; - bool interpolatetoTimestep() const; + std::string interpolatetoTimestep() const; bool isInterpolatetoTimestepDefaulted() const; @@ -69,9 +74,13 @@ namespace model { /// Returns a vector of values in the same order and with the same number of elements as times. virtual std::vector values() const override; - /// Returns the value in effect at the given time. If time is less than 0 days or greater than 1 day, 0 is returned. + /// Returns the value in effect at the given time. + /// If time is less than 0 days or greater than 1 day, 0 is returned. double getValue(const openstudio::Time& time) const; + /// Returns the timeseries corresponding to simulation timestep and chosen interpolation method. + openstudio::TimeSeries timeSeries() const; + //@} /** @name Setters */ //@{ @@ -80,7 +89,7 @@ namespace model { virtual bool resetScheduleTypeLimits() override; - bool setInterpolatetoTimestep(bool interpolatetoTimestep); + bool setInterpolatetoTimestep(const std::string& interpolatetoTimestep); void resetInterpolatetoTimestep(); @@ -104,6 +113,9 @@ namespace model { virtual bool okToResetScheduleTypeLimits() const override; + friend class Timestep_Impl; + void clearCachedTimeSeries(); + //private slots: private: void clearCachedVariables(); @@ -113,6 +125,7 @@ namespace model { mutable boost::optional> m_cachedTimes; mutable boost::optional> m_cachedValues; + mutable boost::optional m_cachedTimeSeries; }; } // namespace detail diff --git a/src/model/Timestep.cpp b/src/model/Timestep.cpp index 90b803e335..77bfd6b81f 100644 --- a/src/model/Timestep.cpp +++ b/src/model/Timestep.cpp @@ -9,6 +9,8 @@ #include "SimulationControl_Impl.hpp" #include "Model.hpp" #include "Model_Impl.hpp" +#include "ScheduleDay.hpp" +#include "ScheduleDay_Impl.hpp" #include "../utilities/core/Assert.hpp" #include #include @@ -66,11 +68,19 @@ namespace model { } bool Timestep_Impl::setNumberOfTimestepsPerHour(int numberOfTimestepsPerHour) { + if (numberOfTimestepsPerHour != this->numberOfTimestepsPerHour()) { + for (auto& sch_day : model().getConcreteModelObjects()) { + sch_day.getImpl()->clearCachedTimeSeries(); + } + } bool result = setInt(OS_TimestepFields::NumberofTimestepsperHour, numberOfTimestepsPerHour); return result; } void Timestep_Impl::resetNumberOfTimestepsPerHour() { + for (auto& sch_day : model().getConcreteModelObjects()) { + sch_day.getImpl()->clearCachedTimeSeries(); + } bool result = setString(OS_TimestepFields::NumberofTimestepsperHour, ""); OS_ASSERT(result); } diff --git a/src/model/test/ModelMerger_GTest.cpp b/src/model/test/ModelMerger_GTest.cpp index 294d90af21..7854e09028 100644 --- a/src/model/test/ModelMerger_GTest.cpp +++ b/src/model/test/ModelMerger_GTest.cpp @@ -914,10 +914,10 @@ TEST_F(ModelFixture, ModelMerger_Issue_5153) { }; std::vector floorprint2_2{ - {5, 5, 0}, + {5, 5, 0}, {10, 5, 0}, {10, 0, 0}, - {5, 0, 0}, + {5, 0, 0}, }; // set up model 2 @@ -935,14 +935,13 @@ TEST_F(ModelFixture, ModelMerger_Issue_5153) { space1_2->matchSurfaces(*space2_2); - auto testModel = [](const Model& model) { - + auto testModel = [](const Model& model) { unsigned numOutdoorSurfaces = 0; unsigned numGroundSurfaces = 0; unsigned numAdjacentSurfaces = 0; unsigned numOutdoorSubSurfaces = 0; unsigned numAdjacentSubSurfaces = 0; - + for (const auto& surface : model.getConcreteModelObjects()) { if (surface.outsideBoundaryCondition() == "Outdoors") { ++numOutdoorSurfaces; diff --git a/src/model/test/ScheduleDay_GTest.cpp b/src/model/test/ScheduleDay_GTest.cpp index be5370b918..c102655304 100644 --- a/src/model/test/ScheduleDay_GTest.cpp +++ b/src/model/test/ScheduleDay_GTest.cpp @@ -10,9 +10,12 @@ #include "../ScheduleDay_Impl.hpp" #include "../ScheduleTypeLimits.hpp" #include "../ScheduleTypeLimits_Impl.hpp" +#include "../Timestep.hpp" +#include "../Timestep_Impl.hpp" #include "../../utilities/time/Date.hpp" #include "../../utilities/time/Time.hpp" +#include "../../utilities/data/TimeSeries.hpp" using namespace openstudio::model; using namespace openstudio; @@ -50,6 +53,7 @@ TEST_F(ModelFixture, Schedule_Day) { EXPECT_EQ(0.0, daySchedule.getValue(Time(0, -1, 0))); EXPECT_EQ(1.0, daySchedule.getValue(Time(0, 0, 0))); + EXPECT_EQ(1.0, daySchedule.getValue(Time(0, 0, 1))); EXPECT_EQ(1.0, daySchedule.getValue(Time(0, 6, 0))); EXPECT_EQ(1.0, daySchedule.getValue(Time(0, 8, 0))); EXPECT_EQ(1.0, daySchedule.getValue(Time(0, 9, 0))); @@ -174,7 +178,7 @@ TEST_F(ModelFixture, Schedule_Day_Interp) { double tol = 1e-5; ScheduleDay daySchedule(model); - EXPECT_FALSE(daySchedule.interpolatetoTimestep()); + EXPECT_EQ("No", daySchedule.interpolatetoTimestep()); EXPECT_TRUE(daySchedule.isInterpolatetoTimestepDefaulted()); // schedule is 1 until 12:00 @@ -188,17 +192,31 @@ TEST_F(ModelFixture, Schedule_Day_Interp) { EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 24, 0)), tol); EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 25, 0)), tol); - daySchedule.setInterpolatetoTimestep(true); - EXPECT_TRUE(daySchedule.interpolatetoTimestep()); + EXPECT_TRUE(daySchedule.setInterpolatetoTimestep("Linear")); + EXPECT_EQ("Linear", daySchedule.interpolatetoTimestep()); EXPECT_FALSE(daySchedule.isInterpolatetoTimestepDefaulted()); EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, -1, 0)), tol); EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 0, 0)), tol); + EXPECT_NEAR(0.013890, daySchedule.getValue(Time(0, 0, 1)), tol); EXPECT_NEAR(0.5, daySchedule.getValue(Time(0, 6, 0)), tol); EXPECT_NEAR(1.0, daySchedule.getValue(Time(0, 12, 0)), tol); EXPECT_NEAR(0.5, daySchedule.getValue(Time(0, 18, 0)), tol); EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 24, 0)), tol); EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 25, 0)), tol); + + daySchedule.setInterpolatetoTimestep("Average"); + EXPECT_EQ("Average", daySchedule.interpolatetoTimestep()); + EXPECT_FALSE(daySchedule.isInterpolatetoTimestepDefaulted()); + + EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, -1, 0)), tol); + EXPECT_NEAR(1.0, daySchedule.getValue(Time(0, 0, 0)), tol); + EXPECT_NEAR(1.0, daySchedule.getValue(Time(0, 0, 1)), tol); + EXPECT_NEAR(1.0, daySchedule.getValue(Time(0, 6, 0)), tol); + EXPECT_NEAR(1.0, daySchedule.getValue(Time(0, 12, 0)), tol); + EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 18, 0)), tol); + EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 24, 0)), tol); + EXPECT_NEAR(0.0, daySchedule.getValue(Time(0, 25, 0)), tol); } TEST_F(ModelFixture, Schedule_Day_Remove) { @@ -320,3 +338,280 @@ TEST_F(ModelFixture, Schedule_Day_addValue_NaN_Infinity) { EXPECT_FALSE(sch_day.addValue(t, std::numeric_limits::infinity())); EXPECT_FALSE(sch_day.addValue(t, -std::numeric_limits::infinity())); } + +TEST_F(ModelFixture, Schedule_Day_timeSeries) { + Model model; + + double tol = 1e-5; + + ScheduleDay sch_day(model); + + Time t1("08:05:00"); + sch_day.addValue(t1, 0.1); + Time t2("21:45:00"); + sch_day.addValue(t2, 1.0); + + Time t0800("08:00:00"); + EXPECT_NEAR(8.0 / 24.0, t0800.totalDays(), tol); + Time t0805("08:05:00"); + EXPECT_NEAR((8.0 + (5.0 / 60.0)) / 24.0, t0805.totalDays(), tol); + Time t0810("08:10:00"); + EXPECT_NEAR((8.0 + (10.0 / 60.0)) / 24.0, t0810.totalDays(), tol); + Time t0818("08:18:00"); + EXPECT_NEAR((8.0 + (18.0 / 60.0)) / 24.0, t0818.totalDays(), tol); + Time t0820("08:20:00"); + EXPECT_NEAR((8.0 + (20.0 / 60.0)) / 24.0, t0820.totalDays(), tol); + Time t0900("09:00:00"); + EXPECT_NEAR(9.0 / 24.0, t0900.totalDays(), tol); + + { // Interpolate to Timestep = No + EXPECT_TRUE(sch_day.setInterpolatetoTimestep("No")); + EXPECT_EQ("No", sch_day.interpolatetoTimestep()); + + auto timestep = model.getUniqueModelObject(); + + openstudio::TimeSeries timeSeries; + double value; + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(1)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 1, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 1, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(4)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 4, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 4, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(6)); + timeSeries = sch_day.timeSeries(); + ASSERT_EQ(24 * 6, timeSeries.dateTimes().size()); + ASSERT_EQ(24 * 6, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(10)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 10, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 10, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(12)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 12, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 12, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.1, value, tol); // at this timestep, we do return the 0.1 + value = sch_day.getValue(t0810); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + } + + { // Interpolate to Timestep = Average + EXPECT_TRUE(sch_day.setInterpolatetoTimestep("Average")); + EXPECT_EQ("Average", sch_day.interpolatetoTimestep()); + + auto timestep = model.getUniqueModelObject(); + + openstudio::TimeSeries timeSeries; + double value; + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(1)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 1, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 1, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.925, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(0.925, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(0.925, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(0.925, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(0.925, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(4)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 4, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 4, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.7, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(0.7, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(6)); + timeSeries = sch_day.timeSeries(); + ASSERT_EQ(24 * 6, timeSeries.dateTimes().size()); + ASSERT_EQ(24 * 6, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.55, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(0.55, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(10)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 10, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 10, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.1, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.25, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(1.0, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(1.0, value, tol); + } + + { // Interpolate to Timestep = Linear + EXPECT_TRUE(sch_day.setInterpolatetoTimestep("Linear")); + EXPECT_EQ("Linear", sch_day.interpolatetoTimestep()); + + auto timestep = model.getUniqueModelObject(); + + openstudio::TimeSeries timeSeries; + double value; + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(1)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 1, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 1, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.098969, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.160365, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(0.160365, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(0.160365, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(0.160365, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(0.160365, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(4)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 4, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 4, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.098969, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.110975, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(0.110975, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(0.127439, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(0.127439, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(0.160365, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(6)); + timeSeries = sch_day.timeSeries(); + ASSERT_EQ(24 * 6, timeSeries.dateTimes().size()); + ASSERT_EQ(24 * 6, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.098969, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.105487, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(0.105487, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(0.116463, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(0.116463, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(0.160365, value, tol); + + EXPECT_TRUE(timestep.setNumberOfTimestepsPerHour(10)); + timeSeries = sch_day.timeSeries(); + EXPECT_EQ(24 * 10, timeSeries.dateTimes().size()); + EXPECT_EQ(24 * 10, timeSeries.values().size()); + value = sch_day.getValue(t0800); + EXPECT_NEAR(0.098969, value, tol); + value = sch_day.getValue(t0805); + EXPECT_NEAR(0.101097, value, tol); + value = sch_day.getValue(t0810); + EXPECT_NEAR(0.107682, value, tol); + value = sch_day.getValue(t0818); + EXPECT_NEAR(0.114268, value, tol); + value = sch_day.getValue(t0820); + EXPECT_NEAR(0.120853, value, tol); + value = sch_day.getValue(t0900); + EXPECT_NEAR(0.160365, value, tol); + } +} diff --git a/src/osversion/VersionTranslator.cpp b/src/osversion/VersionTranslator.cpp index ad7436325f..b4c7b138e1 100644 --- a/src/osversion/VersionTranslator.cpp +++ b/src/osversion/VersionTranslator.cpp @@ -9160,6 +9160,32 @@ namespace osversion { ss << newObject; m_refactored.emplace_back(std::move(object), std::move(newObject)); + } else if (iddname == "OS:Schedule:Day") { + + // 1 Field has been modified from 3.7.0 to 3.8.0: + // ---------------------------------------------- + // * Interpolate to Timestep * 3 - Changed from bool to string choice + + auto iddObject = idd_3_8_0.getObject(iddname); + IdfObject newObject(iddObject.get()); + + for (size_t i = 0; i < object.numFields(); ++i) { + if ((value = object.getString(i))) { + if (i == 3) { + if (istringEqual(value.get(), "Yes")) { + newObject.setString(3, "Average"); + } else { + newObject.setString(3, "No"); + } + } else { + newObject.setString(i, value.get()); + } + } + } + + ss << newObject; + m_refactored.emplace_back(std::move(object), std::move(newObject)); + // No-op } else { ss << object; diff --git a/src/osversion/test/3_8_0/test_vt_ScheduleDay.osm b/src/osversion/test/3_8_0/test_vt_ScheduleDay.osm new file mode 100644 index 0000000000..c265fde6cd --- /dev/null +++ b/src/osversion/test/3_8_0/test_vt_ScheduleDay.osm @@ -0,0 +1,14 @@ + +OS:Version, + {9d4d2ac6-81f7-466e-be47-92edf03bbde0}, !- Handle + 3.7.0; !- Version Identifier + +OS:Schedule:Day, + {b39f42af-eb9f-4079-ad11-5a527a726900}, !- Handle + Schedule Day 1, !- Name + , !- Schedule Type Limits Name + Yes, !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + diff --git a/src/osversion/test/3_8_0/test_vt_ScheduleDay.rb b/src/osversion/test/3_8_0/test_vt_ScheduleDay.rb new file mode 100644 index 0000000000..9af267e319 --- /dev/null +++ b/src/osversion/test/3_8_0/test_vt_ScheduleDay.rb @@ -0,0 +1,10 @@ +#require '/usr/local/openstudio-3.6.1/Ruby/openstudio' + +include OpenStudio::Model + +m = Model.new + +sch_day = ScheduleDay.new(m) +sch_day.setInterpolatetoTimestep(true) + +m.save('test_vt_ScheduleDay.osm', true) diff --git a/src/osversion/test/VersionTranslator_GTest.cpp b/src/osversion/test/VersionTranslator_GTest.cpp index 6d12d3c443..d7d8184249 100644 --- a/src/osversion/test/VersionTranslator_GTest.cpp +++ b/src/osversion/test/VersionTranslator_GTest.cpp @@ -4186,3 +4186,24 @@ TEST_F(OSVersionFixture, update_3_7_0_to_3_8_0_PeopleDefinition) { EXPECT_TRUE(def.isEmpty(9)); // Enable ASHRAE 55 Comfort Warnings EXPECT_EQ("EnclosureAveraged", def.getString(10).get()); // Mean Radiant Temperature Calculation Type } + +TEST_F(OSVersionFixture, update_3_7_0_to_3_8_0_ScheduleDay) { + openstudio::path path = resourcesPath() / toPath("osversion/3_8_0/test_vt_ScheduleDay.osm"); + osversion::VersionTranslator vt; + boost::optional model = vt.loadModel(path); + ASSERT_TRUE(model) << "Failed to load " << path; + + openstudio::path outPath = resourcesPath() / toPath("osversion/3_8_0/test_vt_ScheduleDay_updated.osm"); + model->save(outPath, true); + + std::vector sch_days = model->getObjectsByType("OS:Schedule:Day"); + ASSERT_EQ(1u, sch_days.size()); + WorkspaceObject sch_day = sch_days[0]; + + EXPECT_EQ("Schedule Day 1", sch_day.getString(1).get()); // Name + EXPECT_TRUE(sch_day.isEmpty(2)); // Schedule Type Limits Name + EXPECT_EQ("Average", sch_day.getString(3).get()); // Interpolate to Timestep + EXPECT_EQ(24, sch_day.getInt(4).get()); // Hour 1 + EXPECT_EQ(0, sch_day.getInt(5).get()); // Minute 1 + EXPECT_EQ(0, sch_day.getDouble(6).get()); // Value Until Time 1 +} diff --git a/src/utilities/data/Matrix.cpp b/src/utilities/data/Matrix.cpp index 41d6293894..30d1a80e62 100644 --- a/src/utilities/data/Matrix.cpp +++ b/src/utilities/data/Matrix.cpp @@ -38,7 +38,8 @@ bool operator!=(const Matrix& lhs, const Matrix& rhs) { /// linear interpolation of the function v = f(x, y) at point xi, yi /// assumes that x and y are strictly increasing -double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, double yi, InterpMethod interpMethod, ExtrapMethod extrapMethod) { +double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, double yi, InterpMethod interpMethod, ExtrapMethod extrapMethod, + double ti) { double result = 0.0; size_t M = x.size(); @@ -48,7 +49,7 @@ double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, doub return result; } - InterpInfo xInfo = interpInfo(x, xi); + InterpInfo xInfo = interpInfo(x, xi, ti); if (xInfo.extrapolated) { switch (extrapMethod) { @@ -68,6 +69,10 @@ double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, doub // linear interpolation // no-op break; + case AverageInterp: + // average interpolation + // no-op + break; case NearestInterp: // pick closest point if (xInfo.wa > xInfo.wb) { @@ -91,7 +96,7 @@ double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, doub } } - InterpInfo yInfo = interpInfo(y, yi); + InterpInfo yInfo = interpInfo(y, yi, ti); if (yInfo.extrapolated) { switch (extrapMethod) { @@ -111,6 +116,10 @@ double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, doub // linear interpolation // no-op break; + case AverageInterp: + // average interpolation + // no-op + break; case NearestInterp: // pick closest point if (yInfo.wa > yInfo.wb) { @@ -143,7 +152,8 @@ double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, doub /// linear interpolation of the function v = f(x, y) at points xi, yi /// assumes that x and y are strictly increasing -Vector interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& xi, double yi, InterpMethod interpMethod, ExtrapMethod extrapMethod) { +Vector interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& xi, double yi, InterpMethod interpMethod, ExtrapMethod extrapMethod, + double ti) { size_t M = x.size(); Vector result(M); @@ -153,7 +163,7 @@ Vector interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& x } for (unsigned i = 0; i < M; ++i) { - result(i) = interp(x, y, v, xi(i), yi, interpMethod, extrapMethod); + result(i) = interp(x, y, v, xi(i), yi, interpMethod, extrapMethod, ti); } return result; @@ -161,7 +171,8 @@ Vector interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& x /// linear interpolation of the function v = f(x, y) at points xi, yi /// assumes that x and y are strictly increasing -Vector interp(const Vector& x, const Vector& y, const Matrix& v, double xi, const Vector& yi, InterpMethod interpMethod, ExtrapMethod extrapMethod) { +Vector interp(const Vector& x, const Vector& y, const Matrix& v, double xi, const Vector& yi, InterpMethod interpMethod, ExtrapMethod extrapMethod, + double ti) { size_t N = y.size(); Vector result(N); @@ -171,7 +182,7 @@ Vector interp(const Vector& x, const Vector& y, const Matrix& v, double xi, cons } for (unsigned j = 0; j < N; ++j) { - result(j) = interp(x, y, v, xi, yi(j), interpMethod, extrapMethod); + result(j) = interp(x, y, v, xi, yi(j), interpMethod, extrapMethod, ti); } return result; @@ -180,7 +191,7 @@ Vector interp(const Vector& x, const Vector& y, const Matrix& v, double xi, cons /// linear interpolation of the function v = f(x, y) at points xi, yi /// assumes that x and y are strictly increasing Matrix interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& xi, const Vector& yi, InterpMethod interpMethod, - ExtrapMethod extrapMethod) { + ExtrapMethod extrapMethod, double ti) { size_t M = x.size(); size_t N = y.size(); @@ -192,7 +203,7 @@ Matrix interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& x for (unsigned i = 0; i < M; ++i) { for (unsigned j = 0; j < N; ++j) { - result(i, j) = interp(x, y, v, xi(i), yi(j), interpMethod, extrapMethod); + result(i, j) = interp(x, y, v, xi(i), yi(j), interpMethod, extrapMethod, ti); } } diff --git a/src/utilities/data/Matrix.hpp b/src/utilities/data/Matrix.hpp index 136224348b..ecf6a9b050 100644 --- a/src/utilities/data/Matrix.hpp +++ b/src/utilities/data/Matrix.hpp @@ -32,23 +32,31 @@ UTILITIES_API bool operator!=(const Matrix& lhs, const Matrix& rhs); /// linear interpolation of the function v = f(x, y) at point xi, yi /// assumes that x and y are strictly increasing +/// ti is the total seconds of the timestep interval; it is used for +/// AverageInterp and must be positive UTILITIES_API double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, double yi, InterpMethod interpMethod = LinearInterp, - ExtrapMethod extrapMethod = NoneExtrap); + ExtrapMethod extrapMethod = NoneExtrap, double ti = -9999.0); /// linear interpolation of the function v = f(x, y) at points xi, yi /// assumes that x and y are strictly increasing +/// ti is the total seconds of the timestep interval; it is used for +/// AverageInterp and must be positive UTILITIES_API Vector interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& xi, double yi, InterpMethod interpMethod = LinearInterp, - ExtrapMethod extrapMethod = NoneExtrap); + ExtrapMethod extrapMethod = NoneExtrap, double ti = -9999.0); /// linear interpolation of the function v = f(x, y) at points xi, yi /// assumes that x and y are strictly increasing +/// ti is the total seconds of the timestep interval; it is used for +/// AverageInterp and must be positive UTILITIES_API Vector interp(const Vector& x, const Vector& y, const Matrix& v, double xi, const Vector& yi, InterpMethod interpMethod = LinearInterp, - ExtrapMethod extrapMethod = NoneExtrap); + ExtrapMethod extrapMethod = NoneExtrap, double ti = -9999.0); /// linear interpolation of the function v = f(x, y) at points xi, yi /// assumes that x and y are strictly increasing +/// ti is the total seconds of the timestep interval; it is used for +/// AverageInterp and must be positive UTILITIES_API Matrix interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& xi, const Vector& yi, - InterpMethod interpMethod = LinearInterp, ExtrapMethod extrapMethod = NoneExtrap); + InterpMethod interpMethod = LinearInterp, ExtrapMethod extrapMethod = NoneExtrap, double ti = -9999.0); /// matrix product UTILITIES_API Matrix prod(const Matrix& lop, const Matrix& rop); diff --git a/src/utilities/data/Matrix.i b/src/utilities/data/Matrix.i index 504e55f6fd..eb63e04063 100644 --- a/src/utilities/data/Matrix.i +++ b/src/utilities/data/Matrix.i @@ -138,11 +138,11 @@ public: /// linear interpolation of the function v = f(x, y) at point xi, yi /// assumes that x and y are strictly increasing -double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, double yi, InterpMethod interpMethod = linearInterp, ExtrapMethod extrapMethod = noneExtrap); +double interp(const Vector& x, const Vector& y, const Matrix& v, double xi, double yi, InterpMethod interpMethod = linearInterp, ExtrapMethod extrapMethod = noneExtrap, double ti = 0.0); /// linear interpolation of the function v = f(x, y) at points xi, yi /// assumes that x and y are strictly increasing -Matrix interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& xi, const Vector& yi, InterpMethod interpMethod = linearInterp, ExtrapMethod extrapMethod = noneExtrap); +Matrix interp(const Vector& x, const Vector& y, const Matrix& v, const Vector& xi, const Vector& yi, InterpMethod interpMethod = linearInterp, ExtrapMethod extrapMethod = noneExtrap, double ti = 0.0); /// outer product Matrix outerProd(const Vector& lhs, const Vector& rhs); diff --git a/src/utilities/data/Vector.cpp b/src/utilities/data/Vector.cpp index d96f302fe9..007c327111 100644 --- a/src/utilities/data/Vector.cpp +++ b/src/utilities/data/Vector.cpp @@ -5,6 +5,8 @@ #include "Vector.hpp" +#include "../core/Assert.hpp" + #include // this should all be moved to a utilities/core/Random.h @@ -54,7 +56,7 @@ bool operator!=(const Vector& lhs, const Vector& rhs) { /// linear interpolation of the function y = f(x) at point xi /// assumes that x is strictly increasing -InterpInfo interpInfo(const Vector& x, double xi) { +InterpInfo interpInfo(const Vector& x, double xi, double ti) { size_t N = x.size(); InterpInfo result; @@ -64,24 +66,28 @@ InterpInfo interpInfo(const Vector& x, double xi) { result.ib = 0; result.wa = 1.0; result.wb = 0.0; + result.ti = 1.0; result.extrapolated = false; } else if (xi < x(0)) { result.ia = 0; result.ib = 0; result.wa = 1.0; result.wb = 0.0; + result.ti = 1.0; result.extrapolated = true; } else if (x(N - 1) == xi) { result.ia = N - 1; result.ib = N - 1; result.wa = 0.0; result.wb = 1.0; + result.ti = 1.0; result.extrapolated = false; } else if (xi > x(N - 1)) { result.ia = N - 1; result.ib = N - 1; result.wa = 0.0; result.wb = 1.0; + result.ti = 1.0; result.extrapolated = true; } else { @@ -92,8 +98,20 @@ InterpInfo interpInfo(const Vector& x, double xi) { result.ia = (unsigned)(it - begin - 1); result.ib = (unsigned)(it - begin); - result.wa = (x(result.ib) - xi) / (x(result.ib) - x(result.ia)); - result.wb = (xi - x(result.ia)) / (x(result.ib) - x(result.ia)); + if (ti < 0.0) { + result.wa = (x(result.ib) - xi) / (x(result.ib) - x(result.ia)); + result.wb = (xi - x(result.ia)) / (x(result.ib) - x(result.ia)); + } else { + result.wb = xi - x(result.ia); + result.wa = ti - result.wb; + if ((result.wb > 0.0) && (result.wb < ti)) { + result.ti = ti; + } else { + result.wa = 0.0; + result.wb = 1.0; + result.ti = 1.0; + } + } } return result; @@ -101,7 +119,7 @@ InterpInfo interpInfo(const Vector& x, double xi) { /// linear interpolation of the function y = f(x) at point xi /// assumes that x is strictly increasing -double interp(const Vector& x, const Vector& y, double xi, InterpMethod interpMethod, ExtrapMethod extrapMethod) { +double interp(const Vector& x, const Vector& y, double xi, InterpMethod interpMethod, ExtrapMethod extrapMethod, double ti) { size_t N = x.size(); @@ -111,7 +129,11 @@ double interp(const Vector& x, const Vector& y, double xi, InterpMethod interpMe return result; } - InterpInfo info = interpInfo(x, xi); + if (interpMethod == AverageInterp && ti <= 0.0) { + LOG_FREE_AND_THROW("openstudio.Vector", "Value of ti must be positive when interpolating using the AverageInterp method."); + } + + InterpInfo info = interpInfo(x, xi, ti); if (info.extrapolated) { switch (extrapMethod) { @@ -130,6 +152,10 @@ double interp(const Vector& x, const Vector& y, double xi, InterpMethod interpMe // linear interpolation result = info.wa * y(info.ia) + info.wb * y(info.ib); break; + case AverageInterp: + // average interpolation + result = (info.wa * y(info.ia) + info.wb * y(info.ib)) / info.ti; + break; case NearestInterp: // pick closest point result = (info.wa > info.wb ? y(info.ia) : y(info.ib)); @@ -150,7 +176,7 @@ double interp(const Vector& x, const Vector& y, double xi, InterpMethod interpMe /// linear interpolation of the function y = f(x) at points xi /// assumes that x is strictly increasing -Vector interp(const Vector& x, const Vector& y, const Vector& xi, InterpMethod interpMethod, ExtrapMethod extrapMethod) { +Vector interp(const Vector& x, const Vector& y, const Vector& xi, InterpMethod interpMethod, ExtrapMethod extrapMethod, double ti) { size_t N = x.size(); @@ -161,7 +187,7 @@ Vector interp(const Vector& x, const Vector& y, const Vector& xi, InterpMethod i } for (unsigned i = 0; i < N; ++i) { - result(i) = interp(x, y, xi(i), interpMethod, extrapMethod); + result(i) = interp(x, y, xi(i), interpMethod, extrapMethod, ti); } return result; diff --git a/src/utilities/data/Vector.hpp b/src/utilities/data/Vector.hpp index 7c61a925a8..f1b1ebd7e0 100644 --- a/src/utilities/data/Vector.hpp +++ b/src/utilities/data/Vector.hpp @@ -59,7 +59,8 @@ enum InterpMethod LinearInterp, NearestInterp, HoldLastInterp, - HoldNextInterp + HoldNextInterp, + AverageInterp }; /** Enum to specify the extrapolation method. */ @@ -75,21 +76,25 @@ struct UTILITIES_API InterpInfo bool extrapolated; // was point out of range unsigned ia, ib; // indices of two nearest points double wa, wb; // weights of two nearest points + double ti; // length of interval }; /** Linear interpolation of the function y = f(x) at point xi. Assumes that x is strictly - * increasing. */ -UTILITIES_API InterpInfo interpInfo(const Vector& x, double xi); + * increasing. ti is the total seconds of the timestep interval; it is used for + * AverageInterp and must be positive. */ +UTILITIES_API InterpInfo interpInfo(const Vector& x, double xi, double ti); /** Linear interpolation of the function y = f(x) at point xi. Assumes that x is strictly - * increasing */ + * increasing. ti is the total seconds of the timestep interval; it is used for + * AverageInterp and must be positive. */ UTILITIES_API double interp(const Vector& x, const Vector& y, double xi, InterpMethod interpMethod = LinearInterp, - ExtrapMethod extrapMethod = NoneExtrap); + ExtrapMethod extrapMethod = NoneExtrap, double ti = -9999.0); /** Linear interpolation of the function y = f(x) at points xi. Assumes that x is strictly - * increasing. */ + * increasing. ti is the total seconds of the timestep interval; it is used for + * AverageInterp and must be positive. */ UTILITIES_API Vector interp(const Vector& x, const Vector& y, const Vector& xi, InterpMethod interpMethod = LinearInterp, - ExtrapMethod extrapMethod = NoneExtrap); + ExtrapMethod extrapMethod = NoneExtrap, double ti = -9999.0); //@} /** @name Common Methods and Vector Operations */ diff --git a/src/utilities/data/Vector.i b/src/utilities/data/Vector.i index ce8606dcee..3e45d9e0d7 100644 --- a/src/utilities/data/Vector.i +++ b/src/utilities/data/Vector.i @@ -167,23 +167,24 @@ struct UTILITIES_API InterpInfo{ bool extrapolated; // was point out of range unsigned ia, ib; // indices of two nearest points double wa, wb; // weights of two nearest points + double ti; // length of interval }; /** Linear interpolation of the function y = f(x) at point xi. Assumes that x is strictly * increasing. */ -UTILITIES_API InterpInfo interpInfo(const Vector& x, double xi); +UTILITIES_API InterpInfo interpInfo(const Vector& x, double xi, double ti); /** Linear interpolation of the function y = f(x) at point xi. Assumes that x is strictly * increasing */ UTILITIES_API double interp(const Vector& x, const Vector& y, double xi, InterpMethod interpMethod = LinearInterp, - ExtrapMethod extrapMethod = NoneExtrap); + ExtrapMethod extrapMethod = NoneExtrap, double ti = 0.0); /** Linear interpolation of the function y = f(x) at points xi. Assumes that x is strictly * increasing. */ UTILITIES_API Vector interp(const Vector& x, const Vector& y, const Vector& xi, InterpMethod interpMethod = LinearInterp, - ExtrapMethod extrapMethod = NoneExtrap); + ExtrapMethod extrapMethod = NoneExtrap, double ti = 0.0); //@} /** @name Common Methods and Vector Operations */