Wrap ComfortViewFactorAngles - #5650
joseph-robertson wants to merge 36 commits into
Conversation
|
Um hang on, why is |
There was a problem hiding this comment.
🟡 Changes recommended
Version translation loses existing targets, and the wrapper omits valid target types while permitting inconsistent MRT state.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds model and EnergyPlus translation support for ComfortViewFactorAngles and related People MRT calculation modes.
Changes:
- Adds the new model object and bindings.
- Implements forward/reverse translation and tests.
- Moves the MRT target field to
PeopleDefinitionwith version translation.
File summaries
| File | Description |
|---|---|
src/osversion/VersionTranslator.cpp |
Migrates People fields to 3.12. |
src/osversion/test/VersionTranslator_GTest.cpp |
Tests People migration. |
src/osversion/test/3_12_0/test_vt_People.rb |
Generates migration fixture. |
src/osversion/test/3_12_0/test_vt_People.osm |
Provides 3.11 fixture data. |
src/model/test/People_GTest.cpp |
Tests new PeopleDefinition API. |
src/model/test/ComfortViewFactorAngles_GTest.cpp |
Tests angle-factor behavior. |
src/model/PeopleDefinition.hpp |
Exposes MRT target API. |
src/model/PeopleDefinition.cpp |
Implements MRT target handling. |
src/model/PeopleDefinition_Impl.hpp |
Declares internal target methods. |
src/model/People_Impl.hpp |
Removes obsolete TODO. |
src/model/ModelGeometry.i |
Adds bindings. |
src/model/Model.cpp |
Registers model constructors. |
src/model/ConcreteModelObjects.hpp |
Includes new model types. |
src/model/ComfortViewFactorAngles.hpp |
Defines the public model API. |
src/model/ComfortViewFactorAngles.cpp |
Implements angle-factor logic. |
src/model/ComfortViewFactorAngles_Impl.hpp |
Defines internal implementation API. |
src/model/CMakeLists.txt |
Builds model sources and tests. |
src/energyplus/Test/People_GTest.cpp |
Tests People translation modes. |
src/energyplus/Test/ComfortViewFactorAngles_GTest.cpp |
Tests object translation. |
src/energyplus/ReverseTranslator/ReverseTranslatePeople.cpp |
Imports MRT targets. |
src/energyplus/ReverseTranslator/ReverseTranslateComfortViewFactorAngles.cpp |
Imports angle-factor lists. |
src/energyplus/ReverseTranslator.hpp |
Declares reverse translation. |
src/energyplus/ReverseTranslator.cpp |
Dispatches reverse translation. |
src/energyplus/ForwardTranslator/ForwardTranslatePeople.cpp |
Exports People MRT settings. |
src/energyplus/ForwardTranslator/ForwardTranslateComfortViewFactorAngles.cpp |
Exports angle-factor lists. |
src/energyplus/ForwardTranslator.hpp |
Declares forward translation. |
src/energyplus/ForwardTranslator.cpp |
Dispatches forward translation. |
src/energyplus/CMakeLists.txt |
Builds translator sources and tests. |
resources/model/OpenStudio.idd |
Adds and relocates model fields. |
resources/energyplus/ProposedEnergy+.idd |
Updates the EnergyPlus schema. |
Review details
Suppressed comments (1)
src/model/PeopleDefinition.cpp:286
- This rejects valid direct targets supported by EnergyPlus.
Surface Name/Angle Factor List NameusesAllHeatTranAngFacNames, which includes fenestration and internal-mass objects as well as base surfaces and angle-factor lists (seeresources/energyplus/ProposedEnergy+.idd:7191-7193,8022-8023,11971-11973). Consequently reverse translation silently loses a validSubSurfaceorInternalMasstarget. Support the full target set rather than onlySurfaceandComfortViewFactorAngles.
if (modelObject.optionalCast<Surface>()) {
mrtType = "SurfaceWeighted";
} else if (modelObject.optionalCast<ComfortViewFactorAngles>()) {
mrtType = "AngleFactor";
} else {
LOG(Error, "Surface Name/Angle Factor List Name must reference a Surface or ComfortViewFactorAngles object.");
- Files reviewed: 30/30 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.
| } else if (i > 6) { | ||
| newObject.setString(i - 1, value.get()); |
There was a problem hiding this comment.
VT shouldn't happen by keeping it on people. Also, this mitigation is uneeded, the field was never connected to anything so we don't care if something "wrote" something manually here
| \reference AllHeatTranAngFacNames | ||
| A3, \field Surface 1 Name | ||
| \type object-list | ||
| \object-list SurfaceNames |
| if (result && istringEqual(mrtType, "EnclosureAveraged")) { | ||
| resetSurfaceNameAngleFactorListName(); |
Why? I think the specific surfaces are instance specific rather than definition-level, don't you? (Note: I didn't review the changes yet as I'm on a mobile device) As far as the IDD inconsistencies, that should be fixed upstream on EnergyPlus with a quick PR |
Require a name, declare the minimum field count, and document the enclosure requirement for referenced surfaces.
Validate surface space and thermal-zone assignments before adding angle factors, reject cross-zone lists, and align extensible-group update handling with ZoneMRTCalculation.
Revalidate every referenced surface's thermal zone before generating the EnergyPlus object and cover stale surface assignments in the forward-translator tests.
Reject non-finite angle factors and clear the MRT target when resetting the calculation type, with model regression coverage.
Move unambiguous 3.11 People MRT targets to PeopleDefinition during version translation, report shared-definition conflicts, and cover the migrated surface target.
Accept Surface, SubSurface, and InternalMass targets from AllHeatTranSurfNames, preserve same-zone validation, and cover model and EnergyPlus translation behavior.
0b23e76 to
f940f2e
Compare
🧪 Test Results DashboardSummary
❌ Significant Test Failures📊 Test Run Information
|
| A6, \field Surface Name/Angle Factor List Name | ||
| \type object-list | ||
| \object-list AllHeatTranAngFacNames |
There was a problem hiding this comment.
Ok, I confirmed what I said/asked before: why move this to People:Definition?!
This is an instance-specific thing. In fact, when I wrapped People:Instance in E+, that's where I put the field too.
There was a problem hiding this comment.
I was moving it to People:Definition so it would be alongside Mean Radiant Temperature Calculation Type. Does that not matter?
|
|
||
| bool isMeanRadiantTemperatureCalculationTypeDefaulted() const; | ||
|
|
||
| boost::optional<ModelObject> surfaceNameAngleFactorListName() const; |
There was a problem hiding this comment.
Ok, I'm going to review assuming that this gets moved back to People, not PeopleDefinition.
The only type it will accept / return a ComfortViewFactorAngles, so I don't think there's a reason to use the generic ModelObject here.
Edit: Ok, I read the I/O reference guide: this can also be just a Surface for "SurfaceWeighted". my bad.
| } else if (i > 6) { | ||
| newObject.setString(i - 1, value.get()); |
There was a problem hiding this comment.
VT shouldn't happen by keeping it on people. Also, this mitigation is uneeded, the field was never connected to anything so we don't care if something "wrote" something manually here
| boost::optional<AngleFactor> getAngleFactor(unsigned groupIndex) const; | ||
|
|
||
| bool addAngleFactor(const AngleFactor& angleFactor); | ||
| bool addAngleFactor(const ModelObject& surface, double angleFactor); |
There was a problem hiding this comment.
technically I think it can only be a Surface or a SubSurface. So PlanarSurface would work... or two overloads?
There was a problem hiding this comment.
Ok, InternalMass is possible too. I guess ModelObject is fine then
| AngleFactor::AngleFactor(const ModelObject& surface, double angleFactor) : m_surface(surface), m_angleFactor(angleFactor) { | ||
| if (!((m_angleFactor >= 0.0) && (m_angleFactor <= 1.0))) { | ||
| LOG_AND_THROW("Angle Factor must be between 0 and 1."); | ||
| } | ||
| } |
There was a problem hiding this comment.
If you end up using the generic ModelObject, then you must validate it's the right type.
Otherwise use three overloads (Surface, SubSurface, InternalMass)
There was a problem hiding this comment.
You end up delaying calling isHeatTransferSurface when ComfortViewFactorAngles::addAngleFactor(const AngleFactor& angleFactor) is called, but I think you should throw in the AngleFactor::AngleFactorctor instead
| if (auto planarSurface = surface.optionalCast<PlanarSurface>()) { | ||
| return planarSurface->space(); | ||
| } |
There was a problem hiding this comment.
(Note: without validation, a ShadingSurface would qualify)
| bool surfaceSet = group.setPointer(OS_ComfortViewFactorAnglesExtensibleFields::SurfaceName, surface.handle(), false); | ||
| if (!surfaceSet) { | ||
| LOG(Error, "Unable to add AngleFactor which has an incompatible Surface object to " << briefDescription()); | ||
| OS_ASSERT(false); | ||
| } |
There was a problem hiding this comment.
If we validate the AngleFactor, this is unecessary and could just be an OS_ASSERT
There was a problem hiding this comment.
| unsigned int ComfortViewFactorAngles_Impl::numberofAngleFactors() const { | ||
| return numExtensibleGroups(); | ||
| } |
There was a problem hiding this comment.
I know we use that in other classes, I'm just thinking about what happens if I delete a Surface after it's been added here... I have an invalid extensible group, and numExtensibleGroups is "wrong"
There was a problem hiding this comment.
Yeah, I think for ZoneMRCalculation we handled this -- updated People remove to call removeZoneMRTCalculationReferences. I'll add something similar for Surface, SubSurface, and InternalMass. Your call whether we want to do similar updates for ZonePropertyUserViewFactorsBySurfaceName.
| %extend openstudio::model::AngleFactor { | ||
| // Use the overloaded operator<< for string representation | ||
| std::string __str__() { | ||
| std::ostringstream os; | ||
| os << *$self; | ||
| return os.str(); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Nice, and ModelGeometry.i is the right file to add it to (needs Surface)
| void PeopleDefinition_Impl::resetSurfaceNameAngleFactorListName() { | ||
| bool result = setString(OS_People_DefinitionFields::SurfaceName_AngleFactorListName, ""); | ||
| OS_ASSERT(result); |
There was a problem hiding this comment.
This is moot if not consolidating MRT Calculation Type and Surface Name/Angle Facor List on a single object.
But anyways: given the enforcing/switching the mrt calculation type you do, here you would need to reset to EnclosureAveraged. and the setMeanRadiantTemperature should be removed from the public interface.
There was a problem hiding this comment.
(The enforcing/switching is probably the reason you thought about moving the AngleFactorList to the PeopleDefinition object... If we really think we should move something, I think it'd be the MRT Calculation Type moving to the Instance though)
There was a problem hiding this comment.
While on the topic, the MRT Calculation Type and Surface Name/Angle Facor List are ONLY read by E+ if using thermal comfort models: Fanger, Pierce, KSU, CoolingEffectASH55 or AnkleDraftASH55
There was a problem hiding this comment.
OK, so basically the two options are (?):
- List on People and CalcType on PeopleDefinition; resetList shouldn't change CalcType, or
- both List/CalcType on People; resetList should change CalcType
| std::string mrtType = definition.meanRadiantTemperatureCalculationType(); | ||
| if (auto target = definition.surfaceNameAngleFactorListName()) { | ||
| if (target->optionalCast<Surface>()) { | ||
| if (auto idfTarget = translateAndMapModelObject(*target)) { | ||
| mrtType = "SurfaceWeighted"; | ||
| idfObject.setString(PeopleFields::SurfaceName_AngleFactorListName, idfTarget->nameString()); | ||
| } else { | ||
| mrtType = "EnclosureAveraged"; | ||
| } | ||
| } else if (target->optionalCast<ComfortViewFactorAngles>()) { | ||
| if (auto idfTarget = translateAndMapModelObject(*target)) { | ||
| mrtType = "AngleFactor"; | ||
| idfObject.setString(PeopleFields::SurfaceName_AngleFactorListName, idfTarget->nameString()); | ||
| } else { | ||
| mrtType = "EnclosureAveraged"; | ||
| } | ||
| } else { | ||
| mrtType = "EnclosureAveraged"; | ||
| } | ||
| } else if (!istringEqual(mrtType, "EnclosureAveraged")) { | ||
| mrtType = "EnclosureAveraged"; |
There was a problem hiding this comment.
If this is enforced at model time, there's no need to recheck for it.
If we do check/recheck for it in FT, we should Warn for inconsistencies.
|
Does this mean we should square away this PR, and then move on to #5651? |
|
Pull request overview
EnclosureAveragedPull Request Author
src/model/test)src/energyplus/Test)src/osversion/VersionTranslator.cpp)Labels:
IDDChangeAPIChangePull Request - Ready for CIso that CI builds your PRReview Checklist
This will not be exhaustively relevant to every PR.