Skip to content

V26.2.0-IOFreeze: new ZoneMRTCalculation object - #5649

Open
joseph-robertson wants to merge 22 commits into
developfrom
v26.2.0-IOFreeze-ZoneMRTCalc
Open

V26.2.0-IOFreeze: new ZoneMRTCalculation object#5649
joseph-robertson wants to merge 22 commits into
developfrom
v26.2.0-IOFreeze-ZoneMRTCalc

Conversation

@joseph-robertson

@joseph-robertson joseph-robertson commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Pull request overview

Pull Request Author

  • Model API Changes / Additions
  • Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
  • Model API methods are tested (in src/model/test)
  • EnergyPlus ForwardTranslator Tests (in src/energyplus/Test)
  • If a new object or method, added a test in NREL/OpenStudio-resources: Wrap ZoneMRTCalculation OpenStudio-resources#232
  • If needed, added VersionTranslation rules for the objects (src/osversion/VersionTranslator.cpp)
  • Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
  • All new and existing tests passes
  • If methods have been deprecated, update rest of code to use the new methods

Labels:

  • If change to an IDD file, add the label IDDChange
  • If breaking existing API, add the label APIChange
  • If deemed ready, add label Pull Request - Ready for CI so that CI builds your PR

Review Checklist

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • Code Style, strip trailing whitespace, etc.
  • All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
  • Labeling is ok
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified

@joseph-robertson joseph-robertson self-assigned this Sep 1, 2026
@joseph-robertson joseph-robertson added Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge. IDDChange labels Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Results Dashboard

Summary

Metric Value
Total Tests 0
Passed 0
Failed 0
Errors 0
Skipped 0
Success Rate 0.0%
Generated 2026-09-04 18:12:07 UTC

❌ Significant Test Failures

📊 Test Run Information

Run XML File Status
- No XML files found ❌ Missing

@joseph-robertson
joseph-robertson requested a balanced review from Copilot September 1, 2026 22:20
@joseph-robertson
joseph-robertson marked this pull request as ready for review September 1, 2026 22:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Weight-sum validation, stale references, non-finite values, and partial-add failure reporting can currently produce invalid behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds Model API and EnergyPlus translation support for ZoneMRTCalculation.

Changes:

  • Adds the model object, MRT weighting-factor API, and ThermalZone integration.
  • Adds forward/reverse translators and binding support.
  • Updates IDDs, build registration, and tests.
File summaries
File Description
src/model/ZoneMRTCalculation.hpp Declares the new API.
src/model/ZoneMRTCalculation.cpp Implements weighting-factor behavior.
src/model/ZoneMRTCalculation_Impl.hpp Declares implementation details.
src/model/ThermalZone.hpp Exposes MRT calculation access.
src/model/ThermalZone.cpp Implements creation and lookup.
src/model/ThermalZone_Impl.hpp Adds implementation declaration.
src/model/test/ZoneMRTCalculation_GTest.cpp Tests the model API.
src/model/ModelHVAC.i Adds C# ThermalZone access.
src/model/ModelGeometry.i Registers binding templates.
src/model/Model.cpp Registers constructors.
src/model/ConcreteModelObjects.hpp Includes the new object.
src/model/CMakeLists.txt Adds model sources and tests.
src/energyplus/Test/ZoneMRTCalculation_GTest.cpp Tests both translators.
src/energyplus/ReverseTranslator/ReverseTranslateZoneMRTCalculation.cpp Implements reverse translation.
src/energyplus/ReverseTranslator.hpp Declares reverse translation.
src/energyplus/ReverseTranslator.cpp Dispatches reverse translation.
src/energyplus/ForwardTranslator/ForwardTranslateZoneMRTCalculation.cpp Implements forward translation.
src/energyplus/ForwardTranslator.hpp Declares forward translation.
src/energyplus/ForwardTranslator.cpp Registers forward translation.
src/energyplus/CMakeLists.txt Adds translator sources and tests.
resources/model/OpenStudio.idd Defines the OpenStudio object.
resources/energyplus/ProposedEnergy+.idd Converts EnergyPlus fields to extensible groups.
Review details

Suppressed comments (1)

src/model/ZoneMRTCalculation.cpp:107

  • Removing a referenced People object clears this pointer but leaves the extensible group. A subsequent call to addMRTWeightingFactor reaches this predicate and calls .get() on an empty optional, throwing instead of adding the new factor. Treat a missing field as a non-match (and add a removal regression test).
      auto it = std::find_if(egs.begin(), egs.end(), [&](const WorkspaceExtensibleGroup& eg) {
        return eg.getField(OS_ZoneMRTCalculationExtensibleFields::PeopleName).get() == peopleHandle;
      });
  • Files reviewed: 22/22 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +47 to +51
for (const MRTWeightingFactor& mrtWeightingFactor : mrtWeightingFactors) {
People people = mrtWeightingFactor.people();
boost::optional<IdfObject> _people = translateAndMapModelObject(people);

if (_people) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/model/ZoneMRTCalculation.cpp Outdated
Comment thread src/model/ZoneMRTCalculation.cpp Outdated
return result;
}

boost::optional<unsigned> existingIndex = mrtWeightingFactorIndex(mrtWeightingFactor);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/model/ZoneMRTCalculation.cpp
Comment thread src/model/People.cpp
Comment on lines +113 to +117
// The hooks below would remove ZoneMRTCalculation extensible groups as soon as a referenced People object
// becomes invalid by being moved to another Space, reset from its Space, assigned to a SpaceType, or losing
// its thermal comfort model types. That is more proactive than most existing extensible-reference patterns in
// the model, which generally clean references only from the referenced object's remove() path and otherwise
// rely on explicit remove APIs or read/translation-time filtering.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we'd want this -- it may be overkill.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Thermal-zone deletion can leave an invalid ZoneMRTCalculation object that later causes assertions during access or translation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

resources/model/OpenStudio.idd:6830

  • This field declares two conflicting \type values; all other OpenStudio object-list fields use only \type object-list (for example, lines 6808-6809 and 6837-6838). Remove the redundant alpha declaration so the IDD metadata has one unambiguous field type.
    src/model/ZoneMRTCalculation.cpp:85
  • These adjacent string literals concatenate as andthat, making the thrown clone error malformed. Preserve a space at the literal boundary.
  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/model/ThermalZone.cpp
Comment on lines +2655 to +2658
ZoneMRTCalculation ThermalZone_Impl::getZoneMRTCalculation() const {
auto thisThermalZone = getObject<ThermalZone>();
std::vector<ZoneMRTCalculation> zoneMRTCalculations =
thisThermalZone.getModelObjectSources<ZoneMRTCalculation>(ZoneMRTCalculation::iddObjectType());

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'd have the same issue when deleting a Space.

Removing a Space bulk-removes its child People through ParentObject_Impl::remove(), but it does not call People_Impl::remove(). So cleanup would require Space_Impl::remove() to inspect all child People, then find and prune any ZoneMRTCalculation groups for each one. That reaches across more ownership boundaries and is less consistent with the existing bulk child-removal mechanism.

So: zone removal is direct source-object cleanup; space removal is indirect child/reference cleanup.

@joseph-robertson joseph-robertson Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles ThermalZone: 435380b

@jmarrec jmarrec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good. A couple of things to change in the code review

And please fill out the PR checklist, most important item of it: we need a OpenStudio-resources matching test for this new object

Comment thread resources/energyplus/ProposedEnergy+.idd
Comment on lines +6839 to +6842
\note A People object assigned directly to a Space retains its input name.
\note A People object expanded across multiple Spaces must use an expanded instance name
\note formed as "<Space Name> <People Name>"; its original input name is not accepted.
\note People instances in automatically generated remainder Spaces are not supported.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That scares me

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not totally sure what to do here. If the model has People object that references a SpaceType (instead of a Space), then the IDF's People object references a SpaceList. So I think we'd want to support this (somehow), and the resulting IDF's ZoneMRTCalculation object would look something like (?)

ZoneMRTCalculation,
[...]
  Story 1 East Perimeter Space Baseline Model People, !- People 1 Name
  0.25,                                               !- MRT Weighting Factor 1
  Story 1 West Perimeter Space Baseline Model People, !- People 2 Name
  0.75,                                               !- MRT Weighting Factor 2

@jmarrec jmarrec Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your current implementation which enforces things at model time plus FT time is fine
More importantly this is going to be a very niche object, so if you choose to use it you can also deal with the preconditions.
For eg I don't think we need to check is assigned to a spaceType and the sp has only one space

Comment on lines +50 to +55
* This constructor shouldn't be used, ThermalZone::getZoneMRTCalculation should be preferred
* as it will either instantiate a new object or return the existing one.
* A ThermalZone should not have more than one ZoneMRTCalculation. If you try to instantiate a second one for a given
* ThermalZone this constructor will throw.
*/
explicit ZoneMRTCalculation(const ThermalZone& thermalZone);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I follow.

But since you rightfully want the constructor to NOT be used, make it protected, and friend ThermalZone_Impl

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do the same for ZonePropertyUserViewFactorsBySurfaceName?


ThermalZone thermalZone() const;

std::vector<MRTWeightingFactor> mrtWeightingFactors() const;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nitpick, but I like to handle all extensible fields next to each other inside the @other section

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +59 to +61
// Direct construction is intentionally blocked once the ThermalZone already has one object.
EXPECT_THROW((ZoneMRTCalculation(thermalZone)), openstudio::Exception);
EXPECT_EQ(size + 1, model.modelObjects().size());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't compile after you make it protected

Comment on lines +198 to +202
boost::optional<ThermalZone> thermalZone = space->thermalZone();
if (!thermalZone || (thermalZone->handle() != this->thermalZone().handle())) {
LOG(Error, "Cannot add " << people.briefDescription() << " to " << briefDescription() << " because it is not assigned to the ThermalZone.");
return result;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
boost::optional<ThermalZone> thermalZone = space->thermalZone();
if (!thermalZone || (thermalZone->handle() != this->thermalZone().handle())) {
LOG(Error, "Cannot add " << people.briefDescription() << " to " << briefDescription() << " because it is not assigned to the ThermalZone.");
return result;
}
boost::optional<ThermalZone> thermalZone = space->thermalZone();
if (!thermalZone) {
LOG(Error, "Cannot add " << people.briefDescription() << " to " << briefDescription() << " because it is not assigned to any ThermalZone.");
} else if (thermalZone->handle() != this->thermalZone().handle()) {
LOG(Error, "Cannot add " << people.briefDescription() << " to " << briefDescription() << " because it is assigned to ThermalZone '" << thermalZone->nameString() << "' instead of '" << this->thermalZone().nameString() << "'.");
return result;
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +49 to +56
for (const MRTWeightingFactor& mrtWeightingFactor : mrtWeightingFactors) {
People people = mrtWeightingFactor.people();

boost::optional<Space> space = people.space();
boost::optional<ThermalZone> thermalZone;
if (space) {
thermalZone = space->thermalZone();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to throw and skip if !people.space() to begin with, saying it must be part of a space.

I know you're incidentally doing this (if people.space() is empty, so will thermalZone), but it's not as clear as it can be

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continue;
}

translatedMRTWeightingFactors.emplace_back(_people->name().get(), value);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: use nameString() everywhere

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +102 to +106
for (const auto& translatedMRTWeightingFactor : translatedMRTWeightingFactors) {
auto eg = idfObject.pushExtensibleGroup();
eg.setString(ZoneMRTCalculationExtensibleFields::PeopleName, translatedMRTWeightingFactor.first);
eg.setDouble(ZoneMRTCalculationExtensibleFields::MRTWeightingFactor, translatedMRTWeightingFactor.second);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: structured bindings for the win

Suggested change
for (const auto& translatedMRTWeightingFactor : translatedMRTWeightingFactors) {
auto eg = idfObject.pushExtensibleGroup();
eg.setString(ZoneMRTCalculationExtensibleFields::PeopleName, translatedMRTWeightingFactor.first);
eg.setDouble(ZoneMRTCalculationExtensibleFields::MRTWeightingFactor, translatedMRTWeightingFactor.second);
}
for (const auto& [name, weight] : translatedMRTWeightingFactors) {
auto eg = idfObject.pushExtensibleGroup();
eg.setString(ZoneMRTCalculationExtensibleFields::PeopleName, name);
eg.setDouble(ZoneMRTCalculationExtensibleFields::MRTWeightingFactor, weight);
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

IDDChange Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants