From 13e03531b6b8d8f37ee56717c509cdf6c3bf2a69 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Thu, 7 May 2026 11:58:44 -0700 Subject: [PATCH 1/9] Updates to ScheduleFile to facilitate fromTimeSeries. --- src/epmodel/Loop/Loop.hpp | 2 +- src/epmodel/ScheduleInterval/ScheduleFile.cpp | 59 +++++++++++++++++++ src/epmodel/ScheduleInterval/ScheduleFile.hpp | 9 +++ .../ScheduleInterval/ScheduleFile_Impl.hpp | 3 + 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/epmodel/Loop/Loop.hpp b/src/epmodel/Loop/Loop.hpp index b9e9962e42..18aeb2dfb2 100644 --- a/src/epmodel/Loop/Loop.hpp +++ b/src/epmodel/Loop/Loop.hpp @@ -22,7 +22,7 @@ namespace openstudio { class AppGFuelType; class ComponentType; class FuelType; -class IddObjectType; +struct IddObjectType; class IdfObject; namespace epmodel { diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.cpp b/src/epmodel/ScheduleInterval/ScheduleFile.cpp index edadfe8f12..b45457a53d 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.cpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.cpp @@ -14,6 +14,8 @@ #include #include +#include "../utilities/data/TimeSeries.hpp" + namespace openstudio { namespace epmodel { @@ -25,6 +27,22 @@ namespace epmodel { OS_ASSERT(ok); } + ScheduleFile::ScheduleFile(const Model& model, openstudio::path& filePath, int column, int rowsToSkip) : Schedule(ScheduleFile::iddObjectType(), model) { + + if (!exists(filePath)) { + LOG_FREE(Warn, "openstudio.epmodel.Model", "Cannot find file \"" << filePath << "\""); + } else { + // make the path correct for this system + filePath = system_complete(filePath); + } + + bool ok = true; + ok &= setFileName(toString(filePath)); + ok &= setColumnNumber(column); + ok &= setRowstoSkipatTop(rowsToSkip); + OS_ASSERT(ok); + } + ScheduleFile::ScheduleFile(std::shared_ptr impl) : Schedule(std::move(impl)) {} IddObjectType ScheduleFile::iddObjectType() { @@ -39,6 +57,14 @@ namespace epmodel { return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), openstudio::Schedule_FileFields::MinutesperItem); } + std::string ScheduleFile::fileName() const { + return getImpl()->fileName(); + } + + bool ScheduleFile::setFileName(std::string fileName) { + return getImpl()->setFileName(fileName); + } + int ScheduleFile::columnNumber() const { return getImpl()->columnNumber(); } @@ -139,6 +165,27 @@ namespace epmodel { getImpl()->resetAdjustScheduleforDaylightSavings(); } + boost::optional ScheduleFile::fromTimeSeries(const openstudio::TimeSeries& timeSeries, Model& model) { + boost::optional result; + + if (timeSeries.intervalLength()) { + //const std::string name = result.nameString(); + const std::string name = "temp"; // FIXME: can we find out what the Schedule:File object's name is going to be? + openstudio::path filePath = toPath(name + ".csv"); + + CSVFile csvFile; + csvFile.addColumn(timeSeries.dateTimes()); + csvFile.addColumn(timeSeries.values()); + csvFile.saveAs(filePath); + + result = ScheduleFile(model, filePath); + } else { + // FIXME: deprecate ScheduleVariableInterval? + } + + return result; + } + } // namespace epmodel } // namespace openstudio @@ -146,6 +193,18 @@ namespace openstudio { namespace epmodel { namespace detail { + std::string ScheduleFile_Impl::fileName() const { + const auto value = getString(openstudio::Schedule_FileFields::FileName, true); + OS_ASSERT(value); + return *value; + } + + bool ScheduleFile_Impl::setFileName(std::string fileName) { + const bool result = setString(openstudio::Schedule_FileFields::FileName, fileName); + OS_ASSERT(result); + return result; + } + int ScheduleFile_Impl::columnNumber() const { const auto value = getInt(openstudio::Schedule_FileFields::ColumnNumber, true); OS_ASSERT(value); diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.hpp b/src/epmodel/ScheduleInterval/ScheduleFile.hpp index 53b5cef071..3a46745f0d 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.hpp @@ -8,6 +8,8 @@ #include "EPModelAPI.hpp" #include "Schedule/Schedule.hpp" +#include "../../utilities/core/Path.hpp" +#include "../../utilities/filetypes/CSVFile.hpp" #include @@ -15,6 +17,7 @@ #include namespace openstudio { + namespace epmodel { class Model; @@ -27,6 +30,7 @@ namespace epmodel { { public: explicit ScheduleFile(const Model& model); + explicit ScheduleFile(const Model& model, openstudio::path& filePath, int column = 1, int rowsToSkip = 0); virtual ~ScheduleFile() override = default; ScheduleFile(const ScheduleFile& other) = default; @@ -47,6 +51,9 @@ namespace epmodel { // - Field Mapping: ScheduleTypeLimitsName and FileName are intentionally excluded in this scalar-only pass // (relationship/file-path behavior is handled separately from scalar accessors). // - TODO(parity): Add relationship and path-translation parity APIs incrementally after scalar scaffold saturation. + std::string fileName() const; + bool setFileName(std::string fileName); + int columnNumber() const; bool setColumnNumber(int columnNumber); @@ -78,6 +85,8 @@ namespace epmodel { bool setAdjustScheduleforDaylightSavings(bool adjustScheduleforDaylightSavings); void resetAdjustScheduleforDaylightSavings(); + static boost::optional fromTimeSeries(const openstudio::TimeSeries& timeSeries, Model& model); + protected: using ImplType = detail::ScheduleFile_Impl; diff --git a/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp b/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp index 2d320c1625..5b6a42a2c0 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp @@ -20,6 +20,9 @@ namespace epmodel { using Schedule_Impl::Schedule_Impl; virtual ~ScheduleFile_Impl() override = default; + std::string fileName() const; + bool setFileName(std::string fileName); + int columnNumber() const; bool setColumnNumber(int columnNumber); From 840ec9886ca911a12f02fdd6146438bef65b9a3b Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Thu, 7 May 2026 15:39:23 -0700 Subject: [PATCH 2/9] Add setTimeSeries method, and tests. --- src/epmodel/ScheduleInterval/ScheduleFile.cpp | 52 +++++++++++++++---- src/epmodel/ScheduleInterval/ScheduleFile.hpp | 6 ++- .../ScheduleInterval/ScheduleFile_Impl.hpp | 2 + src/epmodel/test/ScheduleFile_GTest.cpp | 48 +++++++++++++++++ 4 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.cpp b/src/epmodel/ScheduleInterval/ScheduleFile.cpp index b45457a53d..ce0f7100a7 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.cpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.cpp @@ -37,7 +37,7 @@ namespace epmodel { } bool ok = true; - ok &= setFileName(toString(filePath)); + ok &= getImpl()->setFileName(toString(filePath)); ok &= setColumnNumber(column); ok &= setRowstoSkipatTop(rowsToSkip); OS_ASSERT(ok); @@ -57,13 +57,13 @@ namespace epmodel { return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), openstudio::Schedule_FileFields::MinutesperItem); } - std::string ScheduleFile::fileName() const { - return getImpl()->fileName(); - } + // std::string ScheduleFile::fileName() const { + // return getImpl()->fileName(); + // } - bool ScheduleFile::setFileName(std::string fileName) { - return getImpl()->setFileName(fileName); - } + // bool ScheduleFile::setFileName(std::string fileName) { + // return getImpl()->setFileName(fileName); + // } int ScheduleFile::columnNumber() const { return getImpl()->columnNumber(); @@ -168,9 +168,10 @@ namespace epmodel { boost::optional ScheduleFile::fromTimeSeries(const openstudio::TimeSeries& timeSeries, Model& model) { boost::optional result; - if (timeSeries.intervalLength()) { - //const std::string name = result.nameString(); - const std::string name = "temp"; // FIXME: can we find out what the Schedule:File object's name is going to be? + boost::optional intervalTime = timeSeries.intervalLength(); + if (intervalTime) { + result = ScheduleFile(model); + const std::string name = result->nameString(); openstudio::path filePath = toPath(name + ".csv"); CSVFile csvFile; @@ -178,9 +179,20 @@ namespace epmodel { csvFile.addColumn(timeSeries.values()); csvFile.saveAs(filePath); - result = ScheduleFile(model, filePath); + if (!exists(filePath)) { + LOG_FREE(Warn, "openstudio.epmodel.Model", "Cannot find file \"" << filePath << "\""); + } else { + // make the path correct for this system + filePath = system_complete(filePath); + } + + bool ok = true; + ok &= result->getImpl()->setFileName(toString(filePath)); + ok &= result->getImpl()->setTimeSeries(timeSeries); + OS_ASSERT(ok); } else { // FIXME: deprecate ScheduleVariableInterval? + LOG_FREE(Warn, "openstudio.epmodel.Model", "Timeseries does not have an interval length defined, but ScheduleVariableInterval is deprecated"); } return result; @@ -193,6 +205,24 @@ namespace openstudio { namespace epmodel { namespace detail { + bool ScheduleFile_Impl::setTimeSeries(const openstudio::TimeSeries& timeSeries) { + boost::optional intervalTime = timeSeries.intervalLength(); + if (!intervalTime) { + return false; + } + + // TODO: bunch of stuff based on the timeseries + // borrow from non-epmodel ScheduleFixedInterval / ScheduleFile? + // placeholders below + bool ok = true; + ok &= this->setNumberofHoursofData(8760); + ok &= this->setColumnSeparator("Comma"); + ok &= this->setInterpolatetoTimestep(true); + ok &= this->setMinutesperItem(60); + ok &= this->setAdjustScheduleforDaylightSavings(true); + return true; + } + std::string ScheduleFile_Impl::fileName() const { const auto value = getString(openstudio::Schedule_FileFields::FileName, true); OS_ASSERT(value); diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.hpp b/src/epmodel/ScheduleInterval/ScheduleFile.hpp index 3a46745f0d..c4fa07ebc8 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.hpp @@ -51,8 +51,8 @@ namespace epmodel { // - Field Mapping: ScheduleTypeLimitsName and FileName are intentionally excluded in this scalar-only pass // (relationship/file-path behavior is handled separately from scalar accessors). // - TODO(parity): Add relationship and path-translation parity APIs incrementally after scalar scaffold saturation. - std::string fileName() const; - bool setFileName(std::string fileName); + //std::string fileName() const; + //bool setFileName(std::string fileName); int columnNumber() const; bool setColumnNumber(int columnNumber); @@ -87,6 +87,8 @@ namespace epmodel { static boost::optional fromTimeSeries(const openstudio::TimeSeries& timeSeries, Model& model); + //bool setTimeSeries(const openstudio::TimeSeries& timeSeries); + protected: using ImplType = detail::ScheduleFile_Impl; diff --git a/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp b/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp index 5b6a42a2c0..722a451c7f 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp @@ -53,6 +53,8 @@ namespace epmodel { bool setAdjustScheduleforDaylightSavings(bool adjustScheduleforDaylightSavings); void resetAdjustScheduleforDaylightSavings(); + bool setTimeSeries(const openstudio::TimeSeries& timeSeries); + std::vector columnSeparatorValues() const; std::vector minutesperItemValues() const; }; diff --git a/src/epmodel/test/ScheduleFile_GTest.cpp b/src/epmodel/test/ScheduleFile_GTest.cpp index da192e9caa..c3d7ad4ad9 100644 --- a/src/epmodel/test/ScheduleFile_GTest.cpp +++ b/src/epmodel/test/ScheduleFile_GTest.cpp @@ -8,6 +8,11 @@ #include "EPModelFixture.hpp" #include "../ScheduleInterval/ScheduleFile.hpp" +#include "../../utilities/time/Date.hpp" +#include "../../utilities/time/TIme.hpp" +#include "../../utilities/time/DateTime.hpp" +#include + using namespace openstudio::epmodel; TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { @@ -67,3 +72,46 @@ TEST_F(EPModelFixture, ScheduleFile_ScalarAccessors_RoundTrip) { schedule.resetAdjustScheduleforDaylightSavings(); EXPECT_TRUE(schedule.adjustScheduleforDaylightSavings()); } + +TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthYes) { + Model model; + + Date startDate(MonthOfYear::Jan, 1); + Time intervalLength(0, 0, 60); + Vector values(8760); + for (unsigned i = 0; i < values.size(); ++i) { + values[i] = i % 24; + } + + TimeSeries timeSeries(startDate, intervalLength, values, ""); + + boost::optional schedule = ScheduleFile::fromTimeSeries(timeSeries, model); + ASSERT_TRUE(schedule); + EXPECT_EQ("", schedule->fileName()); + EXPECT_EQ(1, schedule->columnNumber()); + EXPECT_EQ(0, schedule->rowstoSkipatTop()); + ASSERT_TRUE(schedule->numberofHoursofData()); + EXPECT_EQ(8760, schedule->numberofHoursofData().get()); + EXPECT_EQ("Comma", schedule->columnSeparator()); + EXPECT_TRUE(schedule->interpolatetoTimestep()); + EXPECT_EQ(60, schedule->minutesperItem()); + EXPECT_TRUE(schedule->adjustScheduleforDaylightSavings()); +} + +TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthNo) { + Model model; + + Date startDate(MonthOfYear::Jan, 1); + Time intervalLength(0, 0, 60); + std::vector dateTimes; + Vector values(8760); + for (unsigned i = 0; i < values.size(); ++i) { + dateTimes.push_back(DateTime(startDate, intervalLength * (i + 1))); + values[i] = i % 24; + } + + TimeSeries timeSeries(dateTimes, values, ""); + + boost::optional schedule = ScheduleFile::fromTimeSeries(timeSeries, model); + ASSERT_FALSE(schedule); +} \ No newline at end of file From a14232e12a383241bf531ee460ea25e3b74b3c36 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Thu, 7 May 2026 15:39:40 -0700 Subject: [PATCH 3/9] Formatting. --- src/epmodel/ScheduleInterval/ScheduleFile.cpp | 9 +++++---- src/epmodel/ScheduleInterval/ScheduleFile.hpp | 2 +- src/epmodel/test/ScheduleFile_GTest.cpp | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.cpp b/src/epmodel/ScheduleInterval/ScheduleFile.cpp index ce0f7100a7..01d312c54c 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.cpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.cpp @@ -27,7 +27,8 @@ namespace epmodel { OS_ASSERT(ok); } - ScheduleFile::ScheduleFile(const Model& model, openstudio::path& filePath, int column, int rowsToSkip) : Schedule(ScheduleFile::iddObjectType(), model) { + ScheduleFile::ScheduleFile(const Model& model, openstudio::path& filePath, int column, int rowsToSkip) + : Schedule(ScheduleFile::iddObjectType(), model) { if (!exists(filePath)) { LOG_FREE(Warn, "openstudio.epmodel.Model", "Cannot find file \"" << filePath << "\""); @@ -60,7 +61,7 @@ namespace epmodel { // std::string ScheduleFile::fileName() const { // return getImpl()->fileName(); // } - + // bool ScheduleFile::setFileName(std::string fileName) { // return getImpl()->setFileName(fileName); // } @@ -210,7 +211,7 @@ namespace epmodel { if (!intervalTime) { return false; } - + // TODO: bunch of stuff based on the timeseries // borrow from non-epmodel ScheduleFixedInterval / ScheduleFile? // placeholders below @@ -228,7 +229,7 @@ namespace epmodel { OS_ASSERT(value); return *value; } - + bool ScheduleFile_Impl::setFileName(std::string fileName) { const bool result = setString(openstudio::Schedule_FileFields::FileName, fileName); OS_ASSERT(result); diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.hpp b/src/epmodel/ScheduleInterval/ScheduleFile.hpp index c4fa07ebc8..cc5193f04a 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.hpp @@ -53,7 +53,7 @@ namespace epmodel { // - TODO(parity): Add relationship and path-translation parity APIs incrementally after scalar scaffold saturation. //std::string fileName() const; //bool setFileName(std::string fileName); - + int columnNumber() const; bool setColumnNumber(int columnNumber); diff --git a/src/epmodel/test/ScheduleFile_GTest.cpp b/src/epmodel/test/ScheduleFile_GTest.cpp index c3d7ad4ad9..5de6207294 100644 --- a/src/epmodel/test/ScheduleFile_GTest.cpp +++ b/src/epmodel/test/ScheduleFile_GTest.cpp @@ -84,12 +84,12 @@ TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthYes) { } TimeSeries timeSeries(startDate, intervalLength, values, ""); - + boost::optional schedule = ScheduleFile::fromTimeSeries(timeSeries, model); ASSERT_TRUE(schedule); EXPECT_EQ("", schedule->fileName()); EXPECT_EQ(1, schedule->columnNumber()); - EXPECT_EQ(0, schedule->rowstoSkipatTop()); + EXPECT_EQ(0, schedule->rowstoSkipatTop()); ASSERT_TRUE(schedule->numberofHoursofData()); EXPECT_EQ(8760, schedule->numberofHoursofData().get()); EXPECT_EQ("Comma", schedule->columnSeparator()); @@ -111,7 +111,7 @@ TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthNo) { } TimeSeries timeSeries(dateTimes, values, ""); - + boost::optional schedule = ScheduleFile::fromTimeSeries(timeSeries, model); ASSERT_FALSE(schedule); } \ No newline at end of file From 2b9697c629a5363087b170d967dab270cf24abe1 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Mon, 11 May 2026 09:46:46 -0700 Subject: [PATCH 4/9] Clean up ScheduleFile files a bit. --- src/epmodel/ScheduleInterval/ScheduleFile.cpp | 28 +++++++++++-------- src/epmodel/ScheduleInterval/ScheduleFile.hpp | 5 ++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.cpp b/src/epmodel/ScheduleInterval/ScheduleFile.cpp index 01d312c54c..8b2cf6f9cf 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.cpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.cpp @@ -19,26 +19,30 @@ namespace openstudio { namespace epmodel { - ScheduleFile::ScheduleFile(const Model& model) : Schedule(ScheduleFile::iddObjectType(), model) { + // ScheduleFile(const ExternalFile& externalfile, int column = 1, int rowsToSkip = 0) : Schedule(ScheduleFile::iddObjectType(), model) {} + + ScheduleFile::ScheduleFile(const Model& model, int column, int rowsToSkip) : Schedule(ScheduleFile::iddObjectType(), model) { // Mirror preserved counterpart constructor behavior for required scalar fields. bool ok = true; - ok &= setColumnNumber(1); - ok &= setRowstoSkipatTop(0); + ok &= setColumnNumber(column); + ok &= setRowstoSkipatTop(rowsToSkip); OS_ASSERT(ok); } - ScheduleFile::ScheduleFile(const Model& model, openstudio::path& filePath, int column, int rowsToSkip) + ScheduleFile::ScheduleFile(const Model& model, const openstudio::path& filePath, int column, int rowsToSkip) : Schedule(ScheduleFile::iddObjectType(), model) { + openstudio::path p; if (!exists(filePath)) { - LOG_FREE(Warn, "openstudio.epmodel.Model", "Cannot find file \"" << filePath << "\""); + this->remove(); + // LOG_AND_THROW("Cannot find file \"" << toString(filePath) << "\" for " << this->briefDescription()); } else { // make the path correct for this system - filePath = system_complete(filePath); + p = system_complete(filePath); } bool ok = true; - ok &= getImpl()->setFileName(toString(filePath)); + ok &= getImpl()->setFileName(toString(p)); ok &= setColumnNumber(column); ok &= setRowstoSkipatTop(rowsToSkip); OS_ASSERT(ok); @@ -171,7 +175,7 @@ namespace epmodel { boost::optional intervalTime = timeSeries.intervalLength(); if (intervalTime) { - result = ScheduleFile(model); + result = ScheduleFile(model, 2); const std::string name = result->nameString(); openstudio::path filePath = toPath(name + ".csv"); @@ -180,15 +184,17 @@ namespace epmodel { csvFile.addColumn(timeSeries.values()); csvFile.saveAs(filePath); + openstudio::path p; if (!exists(filePath)) { - LOG_FREE(Warn, "openstudio.epmodel.Model", "Cannot find file \"" << filePath << "\""); + result->remove(); + // LOG_AND_THROW("Cannot find file \"" << toString(filePath) << "\" for " << briefDescription()); } else { // make the path correct for this system - filePath = system_complete(filePath); + p = system_complete(filePath); } bool ok = true; - ok &= result->getImpl()->setFileName(toString(filePath)); + ok &= result->getImpl()->setFileName(toString(p)); ok &= result->getImpl()->setTimeSeries(timeSeries); OS_ASSERT(ok); } else { diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.hpp b/src/epmodel/ScheduleInterval/ScheduleFile.hpp index cc5193f04a..12a571e236 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.hpp @@ -29,8 +29,9 @@ namespace epmodel { class EPMODEL_API ScheduleFile : public Schedule { public: - explicit ScheduleFile(const Model& model); - explicit ScheduleFile(const Model& model, openstudio::path& filePath, int column = 1, int rowsToSkip = 0); + // explicit ScheduleFile(const ExternalFile& externalfile, int column = 1, int rowsToSkip = 0); // FIXME: how do we maintain this? + explicit ScheduleFile(const Model& model, int column = 1, int rowsToSkip = 0); // new ctor + explicit ScheduleFile(const Model& model, const openstudio::path& filePath, int column = 1, int rowsToSkip = 0); // old ctor virtual ~ScheduleFile() override = default; ScheduleFile(const ScheduleFile& other) = default; From 534afff6fe3acd515a4eca0ad19b113f6b6ac884 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Mon, 11 May 2026 09:46:57 -0700 Subject: [PATCH 5/9] Add test for alt ctor. --- src/epmodel/test/ScheduleFile_GTest.cpp | 70 ++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/src/epmodel/test/ScheduleFile_GTest.cpp b/src/epmodel/test/ScheduleFile_GTest.cpp index 5de6207294..1b3bd6d38c 100644 --- a/src/epmodel/test/ScheduleFile_GTest.cpp +++ b/src/epmodel/test/ScheduleFile_GTest.cpp @@ -8,11 +8,9 @@ #include "EPModelFixture.hpp" #include "../ScheduleInterval/ScheduleFile.hpp" -#include "../../utilities/time/Date.hpp" -#include "../../utilities/time/TIme.hpp" -#include "../../utilities/time/DateTime.hpp" -#include +#include "../../utilities/data/TimeSeries.hpp" +using namespace openstudio; using namespace openstudio::epmodel; TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { @@ -24,6 +22,65 @@ TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { EXPECT_EQ(0, schedule.rowstoSkipatTop()); } +/* TEST_F(EPModelFixture, ScheduleFileAltCtor) { + Model model; + // EXPECT_EQ(0u, model.getConcreteModelObjects().size()); + EXPECT_EQ(0u, model.getConcreteModelObjects().size()); + + path p = resourcesPath() / toPath("model/schedulefile.csv"); + EXPECT_TRUE(exists(p)); + + path expectedDestDir; + std::vector absoluteFilePaths = model.workflowJSON().absoluteFilePaths(); + if (absoluteFilePaths.empty()) { + expectedDestDir = model.workflowJSON().absoluteRootDir(); + } else { + expectedDestDir = absoluteFilePaths[0]; + } + + if (exists(expectedDestDir)) { + removeDirectory(expectedDestDir); + } + ASSERT_FALSE(exists(expectedDestDir)); + + ScheduleFile schedule(model, openstudio::toString(p)); + EXPECT_EQ(1u, model.getConcreteModelObjects().size()); + // EXPECT_EQ(1u, model.getConcreteModelObjects().size()); + ExternalFile externalfile = schedule.externalFile(); + EXPECT_EQ(1u, externalfile.scheduleFiles().size()); + EXPECT_EQ(openstudio::toString(p), externalfile.fileName()); + //EXPECT_TRUE(externalfile.isColumnSeparatorDefaulted()); + EXPECT_FALSE(equivalent(expectedDestDir / externalfile.fileName(), externalfile.filePath())); + EXPECT_TRUE(exists(externalfile.filePath())); + EXPECT_EQ(p, externalfile.filePath()); + EXPECT_TRUE(schedule.isNumberofHoursofDataDefaulted()); + EXPECT_EQ(1, schedule.columnNumber()); + schedule.setColumnNumber(1); + EXPECT_EQ(1, schedule.columnNumber()); + EXPECT_EQ(0, schedule.rowstoSkipatTop()); + schedule.setRowstoSkipatTop(1); + EXPECT_EQ(1, schedule.rowstoSkipatTop()); + + EXPECT_EQ("Comma", schedule.columnSeparator()); + EXPECT_TRUE(schedule.isColumnSeparatorDefaulted()); + EXPECT_TRUE(schedule.setColumnSeparator("Tab")); + EXPECT_EQ("Tab", schedule.columnSeparator()); + + ScheduleFile schedule2(model, openstudio::toString(p)); + EXPECT_EQ(2u, model.getConcreteModelObjects().size()); + EXPECT_EQ(1u, model.getConcreteModelObjects().size()); + ExternalFile externalfile2 = schedule2.externalFile(); + EXPECT_EQ(2u, externalfile.scheduleFiles().size()); + EXPECT_EQ(externalfile.handle(), externalfile2.handle()); + EXPECT_TRUE(schedule2.isNumberofHoursofDataDefaulted()); + EXPECT_EQ(1, schedule2.columnNumber()); + schedule2.setColumnNumber(2); + EXPECT_EQ(2, schedule2.columnNumber()); + EXPECT_EQ(0, schedule2.rowstoSkipatTop()); + schedule2.setRowstoSkipatTop(1); + EXPECT_EQ(1, schedule2.rowstoSkipatTop()); +} */ + TEST_F(EPModelFixture, ScheduleFile_ScalarAccessors_RoundTrip) { Model model; ScheduleFile schedule(model); @@ -87,14 +144,15 @@ TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthYes) { boost::optional schedule = ScheduleFile::fromTimeSeries(timeSeries, model); ASSERT_TRUE(schedule); - EXPECT_EQ("", schedule->fileName()); + //EXPECT_EQ("", schedule->fileName()); EXPECT_EQ(1, schedule->columnNumber()); EXPECT_EQ(0, schedule->rowstoSkipatTop()); ASSERT_TRUE(schedule->numberofHoursofData()); EXPECT_EQ(8760, schedule->numberofHoursofData().get()); EXPECT_EQ("Comma", schedule->columnSeparator()); EXPECT_TRUE(schedule->interpolatetoTimestep()); - EXPECT_EQ(60, schedule->minutesperItem()); + ASSERT_TRUE(schedule->minutesperItem()); + EXPECT_EQ("60", schedule->minutesperItem().get()); EXPECT_TRUE(schedule->adjustScheduleforDaylightSavings()); } From 07bd196a453442d53daa94b06094fd526bacc9e3 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Wed, 15 Jul 2026 15:48:06 -0700 Subject: [PATCH 6/9] Set minutes per item for convenience in fromTimeSeries. --- src/epmodel/ScheduleInterval/ScheduleFile.cpp | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.cpp b/src/epmodel/ScheduleInterval/ScheduleFile.cpp index 8b2cf6f9cf..721caa24a7 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.cpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.cpp @@ -175,7 +175,7 @@ namespace epmodel { boost::optional intervalTime = timeSeries.intervalLength(); if (intervalTime) { - result = ScheduleFile(model, 2); + result = ScheduleFile(model, 2); // FT ScheduleFixedInterval wrote the dateTimes to file const std::string name = result->nameString(); openstudio::path filePath = toPath(name + ".csv"); @@ -198,7 +198,6 @@ namespace epmodel { ok &= result->getImpl()->setTimeSeries(timeSeries); OS_ASSERT(ok); } else { - // FIXME: deprecate ScheduleVariableInterval? LOG_FREE(Warn, "openstudio.epmodel.Model", "Timeseries does not have an interval length defined, but ScheduleVariableInterval is deprecated"); } @@ -218,15 +217,29 @@ namespace epmodel { return false; } - // TODO: bunch of stuff based on the timeseries - // borrow from non-epmodel ScheduleFixedInterval / ScheduleFile? - // placeholders below + auto intervalLengthAsInteger = [](const double value) -> int { + double integralPart = 0.0; + if (std::modf(value, &integralPart) == 0.0) { + // The intervalLength is actually an int, not a double + return static_cast(integralPart); + } + return -1; + }; + + // check the interval + const double intervalLengthDouble = intervalTime->totalMinutes(); + const int intervalLength = intervalLengthAsInteger(intervalLengthDouble); + if (intervalLength < 0) { + return false; + } + bool ok = true; - ok &= this->setNumberofHoursofData(8760); - ok &= this->setColumnSeparator("Comma"); - ok &= this->setInterpolatetoTimestep(true); - ok &= this->setMinutesperItem(60); - ok &= this->setAdjustScheduleforDaylightSavings(true); + ok &= this->setMinutesperItem(intervalLength); + // Do we actually need the following? They aren't required in the IDD. + //ok &= this->setNumberofHoursofData(8760); + //ok &= this->setColumnSeparator("Comma"); + //ok &= this->setInterpolatetoTimestep(true); + //ok &= this->setAdjustScheduleforDaylightSavings(true); return true; } From 93d6c4c75cdee0084ea724476fda06cc7af842e1 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Wed, 15 Jul 2026 15:48:50 -0700 Subject: [PATCH 7/9] Update and improve ScheduleFile::fromTimeSeries unit tests. --- src/epmodel/test/ScheduleFile_GTest.cpp | 34 ++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/epmodel/test/ScheduleFile_GTest.cpp b/src/epmodel/test/ScheduleFile_GTest.cpp index 1b3bd6d38c..96a09faaee 100644 --- a/src/epmodel/test/ScheduleFile_GTest.cpp +++ b/src/epmodel/test/ScheduleFile_GTest.cpp @@ -7,6 +7,7 @@ #include "EPModelFixture.hpp" #include "../ScheduleInterval/ScheduleFile.hpp" +#include "../ScheduleInterval/ScheduleFile_Impl.hpp" #include "../../utilities/data/TimeSeries.hpp" @@ -133,6 +134,7 @@ TEST_F(EPModelFixture, ScheduleFile_ScalarAccessors_RoundTrip) { TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthYes) { Model model; + // hourly Date startDate(MonthOfYear::Jan, 1); Time intervalLength(0, 0, 60); Vector values(8760); @@ -144,16 +146,42 @@ TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthYes) { boost::optional schedule = ScheduleFile::fromTimeSeries(timeSeries, model); ASSERT_TRUE(schedule); - //EXPECT_EQ("", schedule->fileName()); - EXPECT_EQ(1, schedule->columnNumber()); + openstudio::path filePath = schedule->getImpl()->fileName(); + ASSERT_TRUE(exists(filePath)); + EXPECT_EQ("Schedule File 1.csv", filePath.filename()); + EXPECT_EQ(2, schedule->columnNumber()); EXPECT_EQ(0, schedule->rowstoSkipatTop()); ASSERT_TRUE(schedule->numberofHoursofData()); EXPECT_EQ(8760, schedule->numberofHoursofData().get()); EXPECT_EQ("Comma", schedule->columnSeparator()); - EXPECT_TRUE(schedule->interpolatetoTimestep()); + EXPECT_FALSE(schedule->interpolatetoTimestep()); ASSERT_TRUE(schedule->minutesperItem()); EXPECT_EQ("60", schedule->minutesperItem().get()); EXPECT_TRUE(schedule->adjustScheduleforDaylightSavings()); + + // 15-minutely + Time intervalLength2(0, 0, 15); + Vector values2(4 * 8760); + for (unsigned i = 0; i < values2.size(); ++i) { + values2[i] = i % 24; + } + + TimeSeries timeSeries2(startDate, intervalLength2, values2, ""); + + boost::optional schedule2 = ScheduleFile::fromTimeSeries(timeSeries2, model); + ASSERT_TRUE(schedule2); + openstudio::path filePath2 = schedule2->getImpl()->fileName(); + ASSERT_TRUE(exists(filePath2)); + EXPECT_EQ("Schedule File 2.csv", filePath2.filename()); + EXPECT_EQ(2, schedule2->columnNumber()); + EXPECT_EQ(0, schedule2->rowstoSkipatTop()); + ASSERT_TRUE(schedule2->numberofHoursofData()); + EXPECT_EQ(8760, schedule2->numberofHoursofData().get()); + EXPECT_EQ("Comma", schedule2->columnSeparator()); + EXPECT_FALSE(schedule2->interpolatetoTimestep()); + ASSERT_TRUE(schedule2->minutesperItem()); + EXPECT_EQ("15", schedule2->minutesperItem().get()); + EXPECT_TRUE(schedule2->adjustScheduleforDaylightSavings()); } TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthNo) { From ee4dc19e2268e2740637b890ae21228c1917e7c3 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Fri, 17 Jul 2026 11:37:19 -0700 Subject: [PATCH 8/9] Add remaining deprecated methods, and update all unit tests. --- src/epmodel/ScheduleInterval/ScheduleFile.cpp | 57 ++++++- src/epmodel/ScheduleInterval/ScheduleFile.hpp | 19 ++- .../ScheduleInterval/ScheduleFile_Impl.hpp | 11 ++ src/epmodel/test/ScheduleFile_GTest.cpp | 161 ++++++++++++------ 4 files changed, 189 insertions(+), 59 deletions(-) diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.cpp b/src/epmodel/ScheduleInterval/ScheduleFile.cpp index 721caa24a7..d97e2ea770 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.cpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include "../utilities/data/TimeSeries.hpp" @@ -29,16 +31,20 @@ namespace epmodel { OS_ASSERT(ok); } - ScheduleFile::ScheduleFile(const Model& model, const openstudio::path& filePath, int column, int rowsToSkip) + ScheduleFile::ScheduleFile(const Model& model, const openstudio::path& filePath, int column, int rowsToSkip, bool translateFileWithRelativePath) : Schedule(ScheduleFile::iddObjectType(), model) { openstudio::path p; if (!exists(filePath)) { this->remove(); - // LOG_AND_THROW("Cannot find file \"" << toString(filePath) << "\" for " << this->briefDescription()); + LOG_FREE_AND_THROW("openstudio.epmodel.ScheduleFile", "Cannot find file \"" << toString(filePath) << "\" for " << briefDescription()); } else { - // make the path correct for this system - p = system_complete(filePath); + if (translateFileWithRelativePath) { + p = filePath; + } else { + // make the path correct for this system + p = system_complete(filePath); + } } bool ok = true; @@ -170,6 +176,33 @@ namespace epmodel { getImpl()->resetAdjustScheduleforDaylightSavings(); } + boost::optional ScheduleFile::csvFile() const { + return getImpl()->csvFile(); + } + + bool ScheduleFile::translateFileWithRelativePath() const { + DEPRECATED_AT_MSG(4, 0, 0, "Schedule:File is no longer 'translated'."); + return false; + } + + bool ScheduleFile::isTranslateFileWithRelativePathDefaulted() const { + DEPRECATED_AT_MSG(4, 0, 0, "Schedule:File is no longer 'translated'."); + return false; + } + + bool ScheduleFile::setTranslateFileWithRelativePath(bool translateFileWithRelativePath) { + DEPRECATED_AT_MSG(4, 0, 0, "Schedule:File is no longer 'translated'."); + return false; + } + + void ScheduleFile::resetTranslateFileWithRelativePath() { + DEPRECATED_AT_MSG(4, 0, 0, "Schedule:File is no longer 'translated'."); + } + + openstudio::path ScheduleFile::translatedFilePath() const { + return getImpl()->translatedFilePath(); + } + boost::optional ScheduleFile::fromTimeSeries(const openstudio::TimeSeries& timeSeries, Model& model) { boost::optional result; @@ -198,7 +231,7 @@ namespace epmodel { ok &= result->getImpl()->setTimeSeries(timeSeries); OS_ASSERT(ok); } else { - LOG_FREE(Warn, "openstudio.epmodel.Model", "Timeseries does not have an interval length defined, but ScheduleVariableInterval is deprecated"); + LOG_FREE(Warn, "openstudio.epmodel.ScheduleFile", "Timeseries does not have an interval length defined, but ScheduleVariableInterval is deprecated"); } return result; @@ -364,6 +397,20 @@ namespace epmodel { OS_ASSERT(setString(openstudio::Schedule_FileFields::AdjustScheduleforDaylightSavings, "")); } + boost::optional ScheduleFile_Impl::csvFile() const { + boost::optional csvFile; + csvFile = CSVFile::load(this->fileName()); + return csvFile; + } + + openstudio::path ScheduleFile_Impl::translatedFilePath() const { + openstudio::path filePath = this->fileName(); + if (!exists(filePath)) { + LOG_FREE(Warn, "openstudio.epmodel.ScheduleFile", "Cannot find file \"" << filePath << "\""); + } + return filePath; + } + std::vector ScheduleFile_Impl::columnSeparatorValues() const { return openstudio::epmodel::ScheduleFile::columnSeparatorValues(); } diff --git a/src/epmodel/ScheduleInterval/ScheduleFile.hpp b/src/epmodel/ScheduleInterval/ScheduleFile.hpp index 12a571e236..21ebf2c24e 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile.hpp @@ -10,6 +10,7 @@ #include "Schedule/Schedule.hpp" #include "../../utilities/core/Path.hpp" #include "../../utilities/filetypes/CSVFile.hpp" +#include #include @@ -31,7 +32,7 @@ namespace epmodel { public: // explicit ScheduleFile(const ExternalFile& externalfile, int column = 1, int rowsToSkip = 0); // FIXME: how do we maintain this? explicit ScheduleFile(const Model& model, int column = 1, int rowsToSkip = 0); // new ctor - explicit ScheduleFile(const Model& model, const openstudio::path& filePath, int column = 1, int rowsToSkip = 0); // old ctor + explicit ScheduleFile(const Model& model, const openstudio::path& filePath, int column = 1, int rowsToSkip = 0, bool translateFileWithRelativePath = false); // old ctor virtual ~ScheduleFile() override = default; ScheduleFile(const ScheduleFile& other) = default; @@ -52,8 +53,9 @@ namespace epmodel { // - Field Mapping: ScheduleTypeLimitsName and FileName are intentionally excluded in this scalar-only pass // (relationship/file-path behavior is handled separately from scalar accessors). // - TODO(parity): Add relationship and path-translation parity APIs incrementally after scalar scaffold saturation. - //std::string fileName() const; - //bool setFileName(std::string fileName); + + // std::string fileName() const; + // bool setFileName(std::string fileName); int columnNumber() const; bool setColumnNumber(int columnNumber); @@ -88,7 +90,13 @@ namespace epmodel { static boost::optional fromTimeSeries(const openstudio::TimeSeries& timeSeries, Model& model); - //bool setTimeSeries(const openstudio::TimeSeries& timeSeries); + // Extra setters/getters + boost::optional csvFile() const; + OS_DEPRECATED(4, 0, 0) bool translateFileWithRelativePath() const; + OS_DEPRECATED(4, 0, 0) bool isTranslateFileWithRelativePathDefaulted() const; + OS_DEPRECATED(4, 0, 0) bool setTranslateFileWithRelativePath(bool translateFileWithRelativePath); + OS_DEPRECATED(4, 0, 0) void resetTranslateFileWithRelativePath(); + openstudio::path translatedFilePath() const; protected: using ImplType = detail::ScheduleFile_Impl; @@ -98,6 +106,9 @@ namespace epmodel { friend class openstudio::detail::IdfObject_Impl; explicit ScheduleFile(std::shared_ptr impl); + + private: + REGISTER_LOGGER("openstudio.epmodel.ScheduleFile"); }; } // namespace epmodel diff --git a/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp b/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp index 722a451c7f..f71905b7d9 100644 --- a/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp +++ b/src/epmodel/ScheduleInterval/ScheduleFile_Impl.hpp @@ -7,6 +7,7 @@ #define EPMODEL_SCHEDULEFILE_IMPL_HPP #include "Schedule/Schedule_Impl.hpp" +#include "../utilities/filetypes/CSVFile.hpp" #include @@ -53,8 +54,18 @@ namespace epmodel { bool setAdjustScheduleforDaylightSavings(bool adjustScheduleforDaylightSavings); void resetAdjustScheduleforDaylightSavings(); + // openstudio::TimeSeries timeSeries() const; bool setTimeSeries(const openstudio::TimeSeries& timeSeries); + // Extra setters/getters + boost::optional csvFile() const; + // bool translateFileWithRelativePath() const; + // bool isTranslateFileWithRelativePathDefaulted() const; + // void ensureNoLeapDays(); + // bool setTranslateFileWithRelativePath(bool translateFileWithRelativePath); + // void resetTranslateFileWithRelativePath(); + openstudio::path translatedFilePath() const; + std::vector columnSeparatorValues() const; std::vector minutesperItemValues() const; }; diff --git a/src/epmodel/test/ScheduleFile_GTest.cpp b/src/epmodel/test/ScheduleFile_GTest.cpp index 96a09faaee..53edb585dd 100644 --- a/src/epmodel/test/ScheduleFile_GTest.cpp +++ b/src/epmodel/test/ScheduleFile_GTest.cpp @@ -23,7 +23,56 @@ TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { EXPECT_EQ(0, schedule.rowstoSkipatTop()); } -/* TEST_F(EPModelFixture, ScheduleFileAltCtor) { +TEST_F(EPModelFixture, ScheduleFile_ScalarAccessors_RoundTrip) { + Model model; + ScheduleFile schedule(model); + + EXPECT_TRUE(schedule.setColumnNumber(2)); + EXPECT_EQ(2, schedule.columnNumber()); + + EXPECT_TRUE(schedule.setRowstoSkipatTop(3)); + EXPECT_EQ(3, schedule.rowstoSkipatTop()); + + EXPECT_TRUE(schedule.setNumberofHoursofData(8760)); + ASSERT_TRUE(schedule.numberofHoursofData()); + EXPECT_EQ(8760, schedule.numberofHoursofData().get()); + EXPECT_FALSE(schedule.isNumberofHoursofDataDefaulted()); + + EXPECT_TRUE(schedule.setColumnSeparator("Tab")); + EXPECT_EQ("Tab", schedule.columnSeparator()); + EXPECT_FALSE(schedule.isColumnSeparatorDefaulted()); + schedule.resetColumnSeparator(); + EXPECT_TRUE(schedule.isColumnSeparatorDefaulted()); + EXPECT_EQ("Comma", schedule.columnSeparator()); + + EXPECT_FALSE(schedule.interpolatetoTimestep()); + EXPECT_TRUE(schedule.setInterpolatetoTimestep(true)); + EXPECT_TRUE(schedule.interpolatetoTimestep()); + schedule.resetInterpolatetoTimestep(); + EXPECT_FALSE(schedule.interpolatetoTimestep()); + + EXPECT_TRUE(schedule.minutesperItem()); + EXPECT_EQ("60", schedule.minutesperItem().get()); + EXPECT_TRUE(schedule.setMinutesperItem(15)); + ASSERT_TRUE(schedule.minutesperItem()); + EXPECT_EQ("15", schedule.minutesperItem().get()); + EXPECT_TRUE(schedule.setMinutesperItem("30")); + ASSERT_TRUE(schedule.minutesperItem()); + EXPECT_EQ("30", schedule.minutesperItem().get()); + EXPECT_FALSE(schedule.setMinutesperItem("BAD")); + schedule.resetMinutesperItem(); + EXPECT_TRUE(schedule.isMinutesperItemDefaulted()); + ASSERT_TRUE(schedule.minutesperItem()); + EXPECT_EQ("60", schedule.minutesperItem().get()); + + EXPECT_TRUE(schedule.adjustScheduleforDaylightSavings()); + EXPECT_TRUE(schedule.setAdjustScheduleforDaylightSavings(false)); + EXPECT_FALSE(schedule.adjustScheduleforDaylightSavings()); + schedule.resetAdjustScheduleforDaylightSavings(); + EXPECT_TRUE(schedule.adjustScheduleforDaylightSavings()); +} + +TEST_F(EPModelFixture, ScheduleFile_AltCtor) { Model model; // EXPECT_EQ(0u, model.getConcreteModelObjects().size()); EXPECT_EQ(0u, model.getConcreteModelObjects().size()); @@ -31,7 +80,7 @@ TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { path p = resourcesPath() / toPath("model/schedulefile.csv"); EXPECT_TRUE(exists(p)); - path expectedDestDir; +/* path expectedDestDir; std::vector absoluteFilePaths = model.workflowJSON().absoluteFilePaths(); if (absoluteFilePaths.empty()) { expectedDestDir = model.workflowJSON().absoluteRootDir(); @@ -42,18 +91,18 @@ TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { if (exists(expectedDestDir)) { removeDirectory(expectedDestDir); } - ASSERT_FALSE(exists(expectedDestDir)); + ASSERT_FALSE(exists(expectedDestDir)); */ ScheduleFile schedule(model, openstudio::toString(p)); EXPECT_EQ(1u, model.getConcreteModelObjects().size()); - // EXPECT_EQ(1u, model.getConcreteModelObjects().size()); +/* EXPECT_EQ(1u, model.getConcreteModelObjects().size()); ExternalFile externalfile = schedule.externalFile(); EXPECT_EQ(1u, externalfile.scheduleFiles().size()); EXPECT_EQ(openstudio::toString(p), externalfile.fileName()); - //EXPECT_TRUE(externalfile.isColumnSeparatorDefaulted()); + EXPECT_TRUE(externalfile.isColumnSeparatorDefaulted()); EXPECT_FALSE(equivalent(expectedDestDir / externalfile.fileName(), externalfile.filePath())); EXPECT_TRUE(exists(externalfile.filePath())); - EXPECT_EQ(p, externalfile.filePath()); + EXPECT_EQ(p, externalfile.filePath()); */ EXPECT_TRUE(schedule.isNumberofHoursofDataDefaulted()); EXPECT_EQ(1, schedule.columnNumber()); schedule.setColumnNumber(1); @@ -69,10 +118,10 @@ TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { ScheduleFile schedule2(model, openstudio::toString(p)); EXPECT_EQ(2u, model.getConcreteModelObjects().size()); - EXPECT_EQ(1u, model.getConcreteModelObjects().size()); +/* EXPECT_EQ(1u, model.getConcreteModelObjects().size()); ExternalFile externalfile2 = schedule2.externalFile(); EXPECT_EQ(2u, externalfile.scheduleFiles().size()); - EXPECT_EQ(externalfile.handle(), externalfile2.handle()); + EXPECT_EQ(externalfile.handle(), externalfile2.handle()); */ EXPECT_TRUE(schedule2.isNumberofHoursofDataDefaulted()); EXPECT_EQ(1, schedule2.columnNumber()); schedule2.setColumnNumber(2); @@ -80,55 +129,67 @@ TEST_F(EPModelFixture, ScheduleFile_DefaultConstructor) { EXPECT_EQ(0, schedule2.rowstoSkipatTop()); schedule2.setRowstoSkipatTop(1); EXPECT_EQ(1, schedule2.rowstoSkipatTop()); -} */ -TEST_F(EPModelFixture, ScheduleFile_ScalarAccessors_RoundTrip) { - Model model; - ScheduleFile schedule(model); + openstudio::path filePath = schedule2.getImpl()->fileName(); + ASSERT_TRUE(exists(filePath)); + EXPECT_EQ("schedulefile.csv", filePath.filename()); - EXPECT_TRUE(schedule.setColumnNumber(2)); - EXPECT_EQ(2, schedule.columnNumber()); + schedule2.remove(); + EXPECT_EQ(1u, model.getConcreteModelObjects().size()); +} - EXPECT_TRUE(schedule.setRowstoSkipatTop(3)); - EXPECT_EQ(3, schedule.rowstoSkipatTop()); +TEST_F(EPModelFixture, ScheduleFile_ExtraSettersGetters) { + Model model; - EXPECT_TRUE(schedule.setNumberofHoursofData(8760)); - ASSERT_TRUE(schedule.numberofHoursofData()); - EXPECT_EQ(8760, schedule.numberofHoursofData().get()); - EXPECT_FALSE(schedule.isNumberofHoursofDataDefaulted()); + path p = toPath("resources/model/schedulefile.csv"); + EXPECT_TRUE(exists(p)); - EXPECT_TRUE(schedule.setColumnSeparator("Tab")); - EXPECT_EQ("Tab", schedule.columnSeparator()); - EXPECT_FALSE(schedule.isColumnSeparatorDefaulted()); - schedule.resetColumnSeparator(); - EXPECT_TRUE(schedule.isColumnSeparatorDefaulted()); - EXPECT_EQ("Comma", schedule.columnSeparator()); + bool translateFileWithRelativePath = false; + ScheduleFile schedule(model, openstudio::toString(p), 1, 0, translateFileWithRelativePath); + + boost::optional csvFile = schedule.csvFile(); + ASSERT_TRUE(csvFile); + // EXPECT_EQ(3, csvFile->numColumns()); + // EXPECT_EQ(8760, csvFile->numRows()); + EXPECT_FALSE(schedule.translateFileWithRelativePath()); + EXPECT_FALSE(schedule.isTranslateFileWithRelativePathDefaulted()); + EXPECT_FALSE(schedule.setTranslateFileWithRelativePath(true)); + schedule.resetTranslateFileWithRelativePath(); + openstudio::path filePath = schedule.getImpl()->fileName(); + ASSERT_TRUE(exists(filePath)); + EXPECT_NE(p, filePath); + EXPECT_NE(p, schedule.translatedFilePath()); + + translateFileWithRelativePath = true; + ScheduleFile schedule2(model, openstudio::toString(p), 1, 0, translateFileWithRelativePath); + + boost::optional csvFile2 = schedule2.csvFile(); + ASSERT_TRUE(csvFile2); + // EXPECT_EQ(3, csvFile2->numColumns()); + // EXPECT_EQ(8760, csvFile2->numRows()); + EXPECT_FALSE(schedule2.translateFileWithRelativePath()); + EXPECT_FALSE(schedule2.isTranslateFileWithRelativePathDefaulted()); + EXPECT_FALSE(schedule2.setTranslateFileWithRelativePath(true)); + schedule2.resetTranslateFileWithRelativePath(); + openstudio::path filePath2 = schedule2.getImpl()->fileName(); + ASSERT_TRUE(exists(filePath2)); + EXPECT_EQ(p, filePath2); + EXPECT_EQ(p, schedule2.translatedFilePath()); +} - EXPECT_FALSE(schedule.interpolatetoTimestep()); - EXPECT_TRUE(schedule.setInterpolatetoTimestep(true)); - EXPECT_TRUE(schedule.interpolatetoTimestep()); - schedule.resetInterpolatetoTimestep(); - EXPECT_FALSE(schedule.interpolatetoTimestep()); +TEST_F(EPModelFixture, ScheduleFile_CheckCannotFindFile) { + Model model; - EXPECT_TRUE(schedule.minutesperItem()); - EXPECT_EQ("60", schedule.minutesperItem().get()); - EXPECT_TRUE(schedule.setMinutesperItem(15)); - ASSERT_TRUE(schedule.minutesperItem()); - EXPECT_EQ("15", schedule.minutesperItem().get()); - EXPECT_TRUE(schedule.setMinutesperItem("30")); - ASSERT_TRUE(schedule.minutesperItem()); - EXPECT_EQ("30", schedule.minutesperItem().get()); - EXPECT_FALSE(schedule.setMinutesperItem("BAD")); - schedule.resetMinutesperItem(); - EXPECT_TRUE(schedule.isMinutesperItemDefaulted()); - ASSERT_TRUE(schedule.minutesperItem()); - EXPECT_EQ("60", schedule.minutesperItem().get()); + path p = toPath("resources/model/schedulefile2.csv"); + EXPECT_FALSE(exists(p)); + EXPECT_THROW(ScheduleFile(model, openstudio::toString(p)), openstudio::Exception); - EXPECT_TRUE(schedule.adjustScheduleforDaylightSavings()); - EXPECT_TRUE(schedule.setAdjustScheduleforDaylightSavings(false)); - EXPECT_FALSE(schedule.adjustScheduleforDaylightSavings()); - schedule.resetAdjustScheduleforDaylightSavings(); - EXPECT_TRUE(schedule.adjustScheduleforDaylightSavings()); + path p2 = toPath("resources/model/schedulefile.csv"); + EXPECT_TRUE(exists(p2)); + ScheduleFile schedule(model, openstudio::toString(p2)); + EXPECT_TRUE(schedule.getImpl()->setFileName(toString(p))); + EXPECT_NO_THROW(schedule.translatedFilePath()); + EXPECT_EQ(p, schedule.translatedFilePath()); } TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthYes) { @@ -200,4 +261,4 @@ TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthNo) { boost::optional schedule = ScheduleFile::fromTimeSeries(timeSeries, model); ASSERT_FALSE(schedule); -} \ No newline at end of file +} From 6a4f74d37d1a8d144e752d78a58a661a90ce4411 Mon Sep 17 00:00:00 2001 From: Joe Robertson Date: Fri, 17 Jul 2026 11:50:35 -0700 Subject: [PATCH 9/9] Check log warning messages for bad file path. --- src/epmodel/test/ScheduleFile_GTest.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/epmodel/test/ScheduleFile_GTest.cpp b/src/epmodel/test/ScheduleFile_GTest.cpp index 53edb585dd..e7340becd6 100644 --- a/src/epmodel/test/ScheduleFile_GTest.cpp +++ b/src/epmodel/test/ScheduleFile_GTest.cpp @@ -10,6 +10,7 @@ #include "../ScheduleInterval/ScheduleFile_Impl.hpp" #include "../../utilities/data/TimeSeries.hpp" +#include "../../utilities/core/StringStreamLogSink.hpp" using namespace openstudio; using namespace openstudio::epmodel; @@ -180,16 +181,28 @@ TEST_F(EPModelFixture, ScheduleFile_ExtraSettersGetters) { TEST_F(EPModelFixture, ScheduleFile_CheckCannotFindFile) { Model model; + StringStreamLogSink sink; + sink.setLogLevel(Warn); + path p = toPath("resources/model/schedulefile2.csv"); EXPECT_FALSE(exists(p)); EXPECT_THROW(ScheduleFile(model, openstudio::toString(p)), openstudio::Exception); + EXPECT_EQ(1, sink.logMessages().size()); + EXPECT_EQ("openstudio.epmodel.ScheduleFile", sink.logMessages().front().logChannel()); + EXPECT_EQ("Cannot find file \"resources/model/schedulefile2.csv\" for Object of type 'Schedule:File' and named 'Schedule File 1'", sink.logMessages().front().logMessage()); path p2 = toPath("resources/model/schedulefile.csv"); EXPECT_TRUE(exists(p2)); ScheduleFile schedule(model, openstudio::toString(p2)); EXPECT_TRUE(schedule.getImpl()->setFileName(toString(p))); - EXPECT_NO_THROW(schedule.translatedFilePath()); + + StringStreamLogSink sink2; + sink2.setLogLevel(Warn); + EXPECT_EQ(p, schedule.translatedFilePath()); + EXPECT_EQ(1, sink2.logMessages().size()); + EXPECT_EQ("openstudio.epmodel.ScheduleFile", sink2.logMessages().front().logChannel()); + EXPECT_EQ("Cannot find file \"resources/model/schedulefile2.csv\"", sink2.logMessages().front().logMessage()); } TEST_F(EPModelFixture, ScheduleFile_fromTimeSeries_intervalLengthYes) {