void FloodPlot::timeseriesData(TimeSeries tsData) { if (tsData.values().empty()){ return; } m_startDateTime = tsData.firstReportDateTime(); m_endDateTime = tsData.firstReportDateTime() + Time(tsData.daysFromFirstReport(tsData.daysFromFirstReport().size()-1)); m_duration = (m_endDateTime-m_startDateTime).totalDays(); m_xAxisMin = 0.0; m_xAxisMax = m_duration; if (m_plot2DTimeAxis == NULL) { m_plot2DTimeAxis = new Plot2DTimeAxis(m_startDateTime, m_duration); m_qwtPlot->setAxisTitle(QwtPlot::xBottom, " Simulation Time"); m_qwtPlot->setAxisScale(QwtPlot::xBottom, 0, m_duration); m_qwtPlot->setAxisScaleDraw(QwtPlot::xBottom, m_plot2DTimeAxis); m_qwtPlot->setAxisLabelRotation(QwtPlot::xBottom, -90.0); m_qwtPlot->setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom); } else { m_plot2DTimeAxis->startDateTime(m_startDateTime); m_plot2DTimeAxis->duration(m_duration); } TimeSeriesFloodPlotData::Ptr data = TimeSeriesFloodPlotData::create(tsData); floodPlotData(data); }
TimeSeriesFloodPlotData::TimeSeriesFloodPlotData(TimeSeries timeSeries, QwtDoubleInterval colorMapRange) : m_timeSeries(timeSeries), m_minValue(minimum(timeSeries.values())), m_maxValue(maximum(timeSeries.values())), m_minX(timeSeries.firstReportDateTime().date().dayOfYear()), m_maxX(ceil(timeSeries.daysFromFirstReport()[timeSeries.daysFromFirstReport().size()-1]+timeSeries.firstReportDateTime().date().dayOfYear()+timeSeries.firstReportDateTime().time().totalDays())), // end day m_minY(0), // start hour m_maxY(24), // end hour m_startFractionalDay(timeSeries.firstReportDateTime().date().dayOfYear()+timeSeries.firstReportDateTime().time().totalDays()), m_colorMapRange(colorMapRange) { // data range setBoundingRect(QwtDoubleRect(m_minX, m_minY, m_maxX-m_minX, m_maxY-m_minY)); }
TimeSeriesLinePlotData::TimeSeriesLinePlotData(TimeSeries timeSeries, double fracDaysOffset) : m_timeSeries(timeSeries), m_minX(timeSeries.firstReportDateTime().date().dayOfYear()+timeSeries.firstReportDateTime().time().totalDays()), m_maxX(timeSeries.daysFromFirstReport()[timeSeries.daysFromFirstReport().size()-1]+timeSeries.firstReportDateTime().date().dayOfYear()+timeSeries.firstReportDateTime().time().totalDays()), // end day m_minY(minimum(timeSeries.values())), m_maxY(maximum(timeSeries.values())), m_size(timeSeries.values().size()) { m_boundingRect = QwtDoubleRect(m_minX, m_minY, (m_maxX - m_minX), (m_maxY - m_minY)); m_minValue = m_minY; m_maxValue = m_maxY; m_units = timeSeries.units(); m_fracDaysOffset = fracDaysOffset; // note updating in xValue does not affect scaled axis m_x = m_timeSeries.daysFromFirstReport(); m_y = m_timeSeries.values(); }
void LinePlot::timeseriesData(TimeSeries tsData, const std::string& name, QColor color) { if (tsData.values().empty()){ return; } double offset=0.0; if (m_plot2DTimeAxis == NULL) { m_startDateTime = tsData.firstReportDateTime(); m_endDateTime = tsData.firstReportDateTime() + Time(tsData.daysFromFirstReport(tsData.daysFromFirstReport().size()-1)); m_duration = (m_endDateTime-m_startDateTime).totalDays(); m_plot2DTimeAxis = new Plot2DTimeAxis(m_startDateTime, m_duration); m_qwtPlot->setAxisTitle(QwtPlot::xBottom, " Simulation Time"); m_qwtPlot->setAxisScaleDraw(QwtPlot::xBottom, m_plot2DTimeAxis); m_xAxisMin = 0.0; m_xAxisMax = m_duration; m_qwtPlot->setAxisScale(QwtPlot::xBottom, 0, m_duration); m_qwtPlot->setAxisLabelRotation(QwtPlot::xBottom, -90.0); m_qwtPlot->setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom); } else { if (tsData.firstReportDateTime() < m_startDateTime) { m_xAxisMin = (tsData.firstReportDateTime() - m_startDateTime).totalDays(); offset = m_xAxisMin; } else { offset = (tsData.firstReportDateTime() - m_startDateTime).totalDays(); } if ((tsData.firstReportDateTime() + Time(tsData.daysFromFirstReport(tsData.daysFromFirstReport().size()-1))) > m_endDateTime) { m_xAxisMax += ((tsData.firstReportDateTime() + Time(tsData.daysFromFirstReport(tsData.daysFromFirstReport().size()-1))) - m_endDateTime).totalDays(); } } TimeSeriesLinePlotData::Ptr data = TimeSeriesLinePlotData::create(tsData, offset); linePlotData(data, name, color, offset); }
TEST_F(DataFixture,TimeSeries_AddSubtractSameTimePeriod) { std::string units = "W"; Date startDate(Date(MonthOfYear(MonthOfYear::Feb),21)); DateTime startDateTime(startDate, Time(0,1,0,0)); // interval Time interval = Time(0,1,0,0); Vector intervalValues(3); intervalValues(0) = 0; intervalValues(1) = 1; intervalValues(2) = 2; TimeSeries intervalTimeSeries(startDateTime, interval, intervalValues, units); ASSERT_TRUE(!intervalTimeSeries.values().empty()); // detailed DateTimeVector dateTimes; dateTimes.push_back(startDateTime + Time(0,0,0,0)); dateTimes.push_back(startDateTime + Time(0,0,30,0)); dateTimes.push_back(startDateTime + Time(0,1,0,0)); dateTimes.push_back(startDateTime + Time(0,1,30,0)); dateTimes.push_back(startDateTime + Time(0,2,0,0)); Vector detailedValues(5); detailedValues(0) = 0.0; // 1:00 detailedValues(1) = 0.5; // 1:30 detailedValues(2) = 1.0; // 2:00 detailedValues(3) = 1.5; // 2:30 detailedValues(4) = 2.0; // 3:00 TimeSeries detailedTimeSeries(dateTimes, detailedValues, units); ASSERT_TRUE(!detailedTimeSeries.values().empty()); // sum and difference TimeSeries sum = intervalTimeSeries + detailedTimeSeries; TimeSeries diff1 = intervalTimeSeries - detailedTimeSeries; TimeSeries diff2 = detailedTimeSeries - intervalTimeSeries; ASSERT_TRUE(!sum.values().empty()); ASSERT_TRUE(!diff1.values().empty()); ASSERT_TRUE(!diff2.values().empty()); // EXPECT_EQ((unsigned)5, sum.dateTimes().size()); // EXPECT_EQ((unsigned)5, diff1.dateTimes().size()); // EXPECT_EQ((unsigned)5, diff2.dateTimes().size()); EXPECT_EQ((unsigned)5, sum.daysFromFirstReport().size()); EXPECT_EQ((unsigned)5, diff1.daysFromFirstReport().size()); EXPECT_EQ((unsigned)5, diff2.daysFromFirstReport().size()); // EXPECT_EQ(startDateTime, sum.dateTimes().front()); // EXPECT_EQ(startDateTime, diff1.dateTimes().front()); // EXPECT_EQ(startDateTime, diff2.dateTimes().front()); EXPECT_EQ(startDateTime, sum.firstReportDateTime()); EXPECT_EQ(startDateTime, diff1.firstReportDateTime()); EXPECT_EQ(startDateTime, diff2.firstReportDateTime()); DateTime endDateTime = startDateTime + Time(0,2,0,0); // EXPECT_EQ(endDateTime, sum.dateTimes().back()); // EXPECT_EQ(endDateTime, diff1.dateTimes().back()); // EXPECT_EQ(endDateTime, diff2.dateTimes().back()); EXPECT_EQ(endDateTime, sum.firstReportDateTime() + Time(sum.daysFromFirstReport(sum.daysFromFirstReport().size()-1))); EXPECT_EQ(endDateTime, diff1.firstReportDateTime() + Time(diff1.daysFromFirstReport(diff1.daysFromFirstReport().size()-1))); EXPECT_EQ(endDateTime, diff2.firstReportDateTime() + Time(diff2.daysFromFirstReport(diff2.daysFromFirstReport().size()-1))); // 1:00 EXPECT_EQ(0, sum.value(Time(0,0,0,0))); EXPECT_EQ(0, diff1.value(Time(0,0,0,0))); EXPECT_EQ(0, diff2.value(Time(0,0,0,0))); // 1:30 EXPECT_EQ(1.5, sum.value(Time(0,0,30,0))); EXPECT_EQ(0.5, diff1.value(Time(0,0,30,0))); EXPECT_EQ(-0.5, diff2.value(Time(0,0,30,0))); // 2:00 EXPECT_EQ(2, sum.value(Time(0,1,0,0))); EXPECT_EQ(0.0, diff1.value(Time(0,1,0,0))); EXPECT_EQ(0.0, diff2.value(Time(0,1,0,0))); // 2:30 EXPECT_EQ(3.5, sum.value(Time(0,1,30,0))); EXPECT_EQ(0.5, diff1.value(Time(0,1,30,0))); EXPECT_EQ(-0.5, diff2.value(Time(0,1,30,0))); // Test helper function for summing a vector. TimeSeriesVector sumAndDiffs; sumAndDiffs.push_back(sum); sumAndDiffs.push_back(diff1); sumAndDiffs.push_back(diff2); TimeSeries ans = openstudio::sum(sumAndDiffs); EXPECT_FALSE(ans.values().empty()); // 1:00 EXPECT_DOUBLE_EQ(0, ans.value(Time(0,0,0,0))); // 1:30 EXPECT_DOUBLE_EQ(1.5, ans.value(Time(0,0,30,0))); // 2:00 EXPECT_DOUBLE_EQ(2.0, ans.value(Time(0,1,0,0))); // 2:30 EXPECT_DOUBLE_EQ(3.5, ans.value(Time(0,1,30,0))); // Test multiplication and division with a scalar sumAndDiffs.push_back(sum/2.0); sumAndDiffs.push_back(3.0*diff1); ans = openstudio::sum(sumAndDiffs); EXPECT_FALSE(ans.values().empty()); // 1:00 EXPECT_DOUBLE_EQ(0, ans.value(Time(0,0,0,0))); // 1:30 EXPECT_DOUBLE_EQ(3.75, ans.value(Time(0,0,30,0))); // 2:00 EXPECT_DOUBLE_EQ(3.0, ans.value(Time(0,1,0,0))); // 2:30 EXPECT_DOUBLE_EQ(6.75, ans.value(Time(0,1,30,0))); }
boost::optional<IdfObject> ForwardTranslator::translateScheduleVariableInterval( ScheduleVariableInterval & modelObject ) { IdfObject idfObject( openstudio::IddObjectType::Schedule_Compact ); m_idfObjects.push_back(idfObject); idfObject.setName(modelObject.name().get()); boost::optional<ScheduleTypeLimits> scheduleTypeLimits = modelObject.scheduleTypeLimits(); if (scheduleTypeLimits){ boost::optional<IdfObject> idfScheduleTypeLimits = translateAndMapModelObject(*scheduleTypeLimits); if (idfScheduleTypeLimits){ idfObject.setString(Schedule_CompactFields::ScheduleTypeLimitsName, idfScheduleTypeLimits->name().get()); } } TimeSeries timeseries = modelObject.timeSeries(); // Check that the time series has at least one point if(timeseries.values().size() == 0) { LOG(Error,"Time series in schedule '" << modelObject.name().get() << "' has no values, schedule will not be translated"); return boost::optional<IdfObject>(); } DateTime firstReportDateTime = timeseries.firstReportDateTime(); Vector daysFromFirst = timeseries.daysFromFirstReport(); std::vector<long> secondsFromFirst = timeseries.secondsFromFirstReport(); Vector values = timeseries.values(); // We aren't using this - should we? std::string interpolateField = "Interpolate:No"; if (modelObject.interpolatetoTimestep()){ interpolateField = "Interpolate:Yes"; } // New version assumes that the interval is less than one day. // The original version did not, so it was a bit more complicated. // The last date data was written Date lastDate = firstReportDateTime.date(); Time dayDelta = Time(1.0); // The day number of the date that data was last written relative to the first date //double lastDay = 0.0; int lastDay = 0; // Adjust the floating point day delta to be relative to the beginning of the first day and // shift the start of the loop if needed int secondShift = firstReportDateTime.time().totalSeconds(); unsigned int start = 0; if(secondShift == 0) { start = 1; } else { for(unsigned int i=0;i<secondsFromFirst.size();i++) { secondsFromFirst[i] += secondShift; } } // Start the input into the schedule object unsigned fieldIndex = Schedule_CompactFields::ScheduleTypeLimitsName + 1; //idfObject.setString(fieldIndex, interpolateField); //++fieldIndex; fieldIndex = startNewDay(idfObject,fieldIndex,lastDate); for(unsigned int i=start; i < values.size()-1; i++){ // Loop over the time series values and write out values to the // schedule. This version is based on the seconds from the start // of the time series, so should not be vulnerable to round-off. // It was translated from the day version, so there could be // issues associated with that. // // We still have a potential aliasing problem unless the API has // enforced that the times in the time series are all distinct when // rounded to the minute. Is that happening? int secondsFromStartOfDay = secondsFromFirst[i] % 86400; int today = (secondsFromFirst[i]-secondsFromStartOfDay)/86400; // Check to see if we are at the end of a day. if(secondsFromStartOfDay==0 || secondsFromStartOfDay==86400) { // This value is an end of day value, so end the day and set up the next // Note that 00:00:00 counts as the end of the previous day - we only write // out the 24:00:00 value and not both. fieldIndex = addUntil(idfObject,fieldIndex,24,0,values[i]); lastDate += dayDelta; fieldIndex = startNewDay(idfObject,fieldIndex,lastDate); } else { // This still could be on a different day if(today != lastDay) { // We're on a new day, need a 24:00:00 value and set up the next day fieldIndex = addUntil(idfObject,fieldIndex,24,0,values[i]); lastDate += dayDelta; fieldIndex = startNewDay(idfObject,fieldIndex,lastDate); } if(values[i] == values[i+1]){ // Bail on values that match the next value continue; } // Write out the current entry Time time(0,0,0,secondsFromStartOfDay); int hours = time.hours(); int minutes = time.minutes() + floor((time.seconds()/60.0) + 0.5); // This is a little dangerous, but all of the problematic 24:00 // times that might need to cause a day++ should be caught above. if(minutes==60){ hours += 1; minutes = 0; } fieldIndex = addUntil(idfObject,fieldIndex,hours,minutes,values[i]); } lastDay = today; } // Handle the last point a little differently to make sure that the schedule ends exactly on the end of a day unsigned int i = values.size()-1; // We'll skip a sanity check here, but it might be a good idea to add one at some point fieldIndex = addUntil(idfObject,fieldIndex,24,0,values[i]); return idfObject; }
boost::optional<IdfObject> ForwardTranslator::translateScheduleFixedInterval( ScheduleFixedInterval & modelObject ) { IdfObject idfObject( openstudio::IddObjectType::Schedule_Compact ); m_idfObjects.push_back(idfObject); idfObject.setName(modelObject.name().get()); boost::optional<ScheduleTypeLimits> scheduleTypeLimits = modelObject.scheduleTypeLimits(); if (scheduleTypeLimits){ boost::optional<IdfObject> idfScheduleTypeLimits = translateAndMapModelObject(*scheduleTypeLimits); if (idfScheduleTypeLimits){ idfObject.setString(Schedule_CompactFields::ScheduleTypeLimitsName, idfScheduleTypeLimits->name().get()); } } TimeSeries timeseries = modelObject.timeSeries(); // Check that the time series has at least one point if(timeseries.values().size() == 0) { LOG(Error,"Time series in schedule '" << modelObject.name().get() << "' has no values, schedule will not be translated"); return boost::optional<IdfObject>(); } DateTime firstReportDateTime = timeseries.firstReportDateTime(); Vector daysFromFirst = timeseries.daysFromFirstReport(); Vector values = timeseries.values(); // We aren't using this - should we? std::string interpolateField; if (modelObject.interpolatetoTimestep()){ interpolateField = "Interpolate:Yes"; }else{ interpolateField = "Interpolate:No"; } // New version assumes that the interval is less than one day. // The original version did not, so it was a bit more complicated. // 5.787x10^-6 days is a little less than half a second double eps = 5.787e-6; double intervalDays = modelObject.intervalLength(); // The last date data was written Date lastDate = firstReportDateTime.date(); Time dayDelta = Time(1.0); // The day number of the date that data was last written relative to the first date double lastDay = 0.0; // Adjust the floating point day delta to be relative to the beginning of the first day and // shift the start of the loop if needed double timeShift = firstReportDateTime.time().totalDays(); unsigned int start = 0; if(timeShift == 0.0) { start = 1; } else { for(unsigned int i=0;i<daysFromFirst.size();i++) { daysFromFirst[i] += timeShift; } } // Start the input into the schedule object unsigned fieldIndex = Schedule_CompactFields::ScheduleTypeLimitsName + 1; fieldIndex = startNewDay(idfObject,fieldIndex,lastDate); for(unsigned int i=start; i < values.size()-1; i++) { // We could loop over the entire array and use the fact that the // last entry in the daysFromFirstReport vector should be a round // number to avoid logic. However, this whole thing is very, very // sensitive to round off issues. We still have a HUGE aliasing // problem unless the API has enforced that the times in the // time series are all distinct when rounded to the minute. Is that // happening? double today = floor(daysFromFirst[i]); double hms = daysFromFirst[i]-today; // Here, we need to make sure that we aren't nearly the end of a day if(fabs(1.0-hms) < eps) { today += 1; hms = 0.0; } if(hms < eps) { // This value is an end of day value, so end the day and set up the next fieldIndex = addUntil(idfObject,fieldIndex,24,0,values[i]); lastDate += dayDelta; fieldIndex = startNewDay(idfObject,fieldIndex,lastDate); } else { if(today != lastDay) { // We're on a new day, need a 24:00:00 value and set up the next day fieldIndex = addUntil(idfObject,fieldIndex,24,0,values[i]); lastDate += dayDelta; fieldIndex = startNewDay(idfObject,fieldIndex,lastDate); } if(values[i] == values[i+1]) { // Bail on values that match the next value continue; } // Write out the current entry Time time(hms); int hours = time.hours(); int minutes = time.minutes() + floor((time.seconds()/60.0) + 0.5); // This is a little dangerous, but all of the problematic 24:00 // times that might need to cause a day++ should be caught above. if(minutes==60) { hours += 1; minutes = 0; } fieldIndex = addUntil(idfObject,fieldIndex,hours,minutes,values[i]); } lastDay = today; } // Handle the last point a little differently to make sure that the schedule ends exactly on the end of a day unsigned int i = values.size()-1; // We'll skip a sanity check here, but it might be a good idea to add one at some point fieldIndex = addUntil(idfObject,fieldIndex,24,0,values[i]); return idfObject; }
TEST_F(EnergyPlusFixture, ForwardTranslator_ScheduleFixedInterval_Hourly_Shifted) { // Create the values vector Vector values = linspace(1, 8760, 8760); // Create a time series that starts at 12/31 23:00 TimeSeries timeseries(DateTime(Date(MonthOfYear::Jan, 1)), Time(0, 1, 0), values, ""); Model model; // Create a schedule and make sure it worked boost::optional<ScheduleInterval> scheduleInterval = ScheduleInterval::fromTimeSeries(timeseries, model); ASSERT_TRUE(scheduleInterval); EXPECT_TRUE(scheduleInterval->optionalCast<ScheduleFixedInterval>()); // Verify that the schedule gives us back the time series TimeSeries ts = scheduleInterval->timeSeries(); // Oops, it doesn't. Maybe it shouldn't give back the exact time series, but it can't do this. // Without this check, the schedule manages to pass everything else and the test succeeds. EXPECT_EQ(DateTime(Date(MonthOfYear::Jan, 1), Time(0,0,0)), ts.firstReportDateTime()); // Forward translate the schedule ForwardTranslator ft; Workspace workspace = ft.translateModel(model); std::vector<WorkspaceObject> objects = workspace.getObjectsByType(IddObjectType::Schedule_Compact); ASSERT_EQ(1u, objects.size()); boost::regex throughRegex("^Through:\\s*(.*)/\\s*(.*)\\s*"); boost::regex untilRegex("^Until:\\s*(.*):(.*)\\s*"); // Write out the schedule - keep this around for now //workspace.save(toPath("./ForwardTranslator_ScheduleFixedInterval_Hourly_Shifted.idf"), true); // Check the contents of the output unsigned N = objects[0].numFields(); boost::optional<Date> lastDateThrough; bool until24Found = false; bool nextValueShouldBeLast = false; unsigned numUntils = 0; int currentHour = 0; for (unsigned i = 0; i < N; ++i) { boost::optional<std::string> field = objects[0].getString(i, true, false); ASSERT_TRUE(field); if (nextValueShouldBeLast) { double value = boost::lexical_cast<double>(*field); EXPECT_EQ(8760.0, value); nextValueShouldBeLast = false; } boost::smatch throughMatches; if (boost::regex_search(*field, throughMatches, throughRegex)) { currentHour = 1; std::string monthText(throughMatches[1].first, throughMatches[1].second); std::string dayText(throughMatches[2].first, throughMatches[2].second); int month = boost::lexical_cast<int>(monthText); int day = boost::lexical_cast<int>(dayText); Date date(MonthOfYear(month), day); if (lastDateThrough) { // check that this date is greater than last date EXPECT_TRUE(date > *lastDateThrough) << date << " <= " << *lastDateThrough; // DLM: this schedule should not wrap around to 1/1, it should end on 12/31 at 24:00 // check that last date was closed at 24:00 EXPECT_TRUE(until24Found); } lastDateThrough = date; until24Found = false; } boost::smatch untilMatches; if (boost::regex_search(*field, untilMatches, untilRegex)) { numUntils += 1; std::string hrText(untilMatches[1].first, untilMatches[1].second); std::string minText(untilMatches[2].first, untilMatches[2].second); int hr = boost::lexical_cast<int>(hrText); int min = boost::lexical_cast<int>(minText); EXPECT_EQ(currentHour, hr); ++currentHour; EXPECT_EQ(0, min); if ((hr == 24) && (min == 0)) { until24Found = true; } // should NOT see Until: 00:00, EXPECT_FALSE((hr == 0) && (min == 0)); if ((lastDateThrough == Date(MonthOfYear(12), 31)) && until24Found) { nextValueShouldBeLast = true; } } } bool lastUntil24Found = until24Found; // check last date was closed EXPECT_TRUE(lastUntil24Found); // check that there were 8760 untils EXPECT_EQ(8760, numUntils); }