From b7b96ec812d310b059fb5a9bf90657598e5df36a Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Wed, 29 Jan 2025 15:27:33 -0800 Subject: [PATCH] fix(building): Ensure plenum constructions survive serialization --- dragonfly_energy/properties/building.py | 30 ++++++++++++++- dragonfly_energy/properties/model.py | 13 ++++++- tests/model_extend_test.py | 49 +++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/dragonfly_energy/properties/building.py b/dragonfly_energy/properties/building.py index 989ffb0d..162770e7 100644 --- a/dragonfly_energy/properties/building.py +++ b/dragonfly_energy/properties/building.py @@ -452,6 +452,14 @@ def from_dict(cls, data, host): if 'construction_set' in data and data['construction_set'] is not None: new_prop.construction_set = \ ConstructionSet.from_dict(data['construction_set']) + if 'ceiling_plenum_construction' in data and \ + data['ceiling_plenum_construction'] is not None: + new_prop.ceiling_plenum_construction = \ + OpaqueConstruction.from_dict(data['ceiling_plenum_construction']) + if 'floor_plenum_construction' in data and \ + data['floor_plenum_construction'] is not None: + new_prop.ceiling_plenum_construction = \ + OpaqueConstruction.from_dict(data['floor_plenum_construction']) if 'des_cooling_load' in data and data['des_cooling_load'] is not None: new_prop.des_cooling_load = \ HourlyContinuousCollection.from_dict(data['des_cooling_load']) @@ -463,7 +471,7 @@ def from_dict(cls, data, host): HourlyContinuousCollection.from_dict(data['des_hot_water_load']) return new_prop - def apply_properties_from_dict(self, abridged_data, construction_sets): + def apply_properties_from_dict(self, abridged_data, construction_sets, constructions): """Apply properties from a BuildingEnergyPropertiesAbridged dictionary. Args: @@ -471,10 +479,20 @@ def apply_properties_from_dict(self, abridged_data, construction_sets): coming from a Model). construction_sets: A dictionary of ConstructionSets with identifiers of the sets as keys, which will be used to re-assign construction_sets. + constructions: A dictionary with construction identifiers as keys + and honeybee construction objects as values. """ if 'construction_set' in abridged_data and \ abridged_data['construction_set'] is not None: self.construction_set = construction_sets[abridged_data['construction_set']] + if 'ceiling_plenum_construction' in abridged_data and \ + abridged_data['ceiling_plenum_construction'] is not None: + self.ceiling_plenum_construction = \ + constructions[abridged_data['ceiling_plenum_construction']] + if 'floor_plenum_construction' in abridged_data and \ + abridged_data['floor_plenum_construction'] is not None: + self.floor_plenum_construction = \ + constructions[abridged_data['floor_plenum_construction']] if 'des_cooling_load' in abridged_data and \ abridged_data['des_cooling_load'] is not None: self.des_cooling_load = \ @@ -540,6 +558,14 @@ def to_dict(self, abridged=False): base['energy']['construction_set'] = \ self._construction_set.identifier if abridged else \ self._construction_set.to_dict() + if self._ceiling_plenum_construction is not None: + base['energy']['ceiling_plenum_construction'] = \ + self._ceiling_plenum_construction.identifier if abridged else \ + self._ceiling_plenum_construction.to_dict() + if self._floor_plenum_construction is not None: + base['energy']['floor_plenum_construction'] = \ + self._floor_plenum_construction.identifier if abridged else \ + self._floor_plenum_construction.to_dict() if self._des_cooling_load is not None: base['energy']['des_cooling_load'] = self._des_cooling_load.to_dict() if self._des_heating_load is not None: @@ -596,6 +622,8 @@ def duplicate(self, new_host=None): """ _host = new_host or self._host new_prop = BuildingEnergyProperties(_host, self._construction_set) + new_prop._ceiling_plenum_construction = self._ceiling_plenum_construction + new_prop._floor_plenum_construction = self._floor_plenum_construction new_prop._des_cooling_load = self._des_cooling_load new_prop._des_heating_load = self._des_heating_load new_prop._des_hot_water_load = self._des_hot_water_load diff --git a/dragonfly_energy/properties/model.py b/dragonfly_energy/properties/model.py index ed24f7b2..32822bdb 100644 --- a/dragonfly_energy/properties/model.py +++ b/dragonfly_energy/properties/model.py @@ -93,7 +93,8 @@ def constructions(self): def face_constructions(self): """Get a list of all unique constructions assigned to Faces, Apertures and Doors. - These objects only exist under the Building.room_3ds property + These objects only exist under the Building.room_3ds property and in + the Building's ceiling_plenum_construction and floor_plenum_construction. """ constructions = [] for bldg in self.host.buildings: @@ -103,6 +104,14 @@ def face_constructions(self): self._check_and_add_obj_construction(ap, constructions) for dr in face.doors: self._check_and_add_obj_construction(dr, constructions) + constr = bldg.properties.energy._ceiling_plenum_construction + if constr is not None: + if not self._instance_in_array(constr, constructions): + constructions.append(constr) + constr = bldg.properties.energy._floor_plenum_construction + if constr is not None: + if not self._instance_in_array(constr, constructions): + constructions.append(constr) return list(set(constructions)) @property @@ -450,7 +459,7 @@ def apply_properties_from_dict(self, data): for bldg, b_dict in zip(self.host.buildings, building_e_dicts): if b_dict is not None: bldg.properties.energy.apply_properties_from_dict( - b_dict, construction_sets) + b_dict, construction_sets, constructions) if bldg.has_room_3ds and b_dict is not None and 'room_3ds' in b_dict and \ b_dict['room_3ds'] is not None: room_e_dicts, face_e_dicts, shd_e_dicts, ap_e_dicts, dr_e_dicts = \ diff --git a/tests/model_extend_test.py b/tests/model_extend_test.py index 51c4b55b..16584e4c 100644 --- a/tests/model_extend_test.py +++ b/tests/model_extend_test.py @@ -232,6 +232,55 @@ def test_to_from_dict(): assert new_model.context_shades[0].properties.energy.transmittance_schedule == tree_trans +def test_to_from_dict_plenum_construction(): + """Test the Model to_dict and from_dict method with plenum constructions.""" + # simple 10 x 10 rooms + pts1 = (Point3D(0, 0, 0), Point3D(10, 0, 0), Point3D(10, 10, 0), Point3D(0, 10, 0)) + pts2 = (Point3D(10, 0, 0), Point3D(20, 0, 0), Point3D(20, 10, 0), Point3D(10, 10, 0)) + + # Two rooms with different floor heights + room2d_full = Room2D( + 'R1-full', floor_geometry=Face3D(pts1), floor_to_ceiling_height=4, + is_ground_contact=True, is_top_exposed=True) + room2d_plenum = Room2D( + 'R2-plenum', floor_geometry=Face3D(pts2), floor_to_ceiling_height=4, + is_ground_contact=True, is_top_exposed=True) + room2d_plenum.ceiling_plenum_depth = 0.5 + room2d_plenum.floor_plenum_depth = 0.5 + + story = Story('S1', [room2d_full, room2d_plenum]) + story.solve_room_2d_adjacency(0.01) + story.set_outdoor_window_parameters(SimpleWindowRatio(0.4)) + story.multiplier = 4 + building = Building('Office_Building_1234', [story]) + + concrete = EnergyMaterial('Concrete', 0.15, 2.31, 2322, 832, 'MediumRough', + 0.95, 0.75, 0.8) + gypsum = EnergyMaterial('Gypsum', 0.0127, 0.16, 784.9, 830, 'MediumRough', + 0.93, 0.6, 0.65) + ceil_constr = OpaqueConstruction('Gypsum Ceiling', [gypsum]) + floor_constr = OpaqueConstruction('Concrete Floor', [concrete]) + building.properties.energy.ceiling_plenum_construction = ceil_constr + building.properties.energy.floor_plenum_construction = floor_constr + + plenum_model = building.to_honeybee(tolerance=0.01) + plenum_rooms = plenum_model.rooms + assert len(plenum_rooms) == 4 + plenum = plenum_rooms[0] + #print(plenum[-1].properties.energy.construction) + assert plenum[-1].properties.energy.construction == floor_constr + plenum = plenum_rooms[-1] + assert plenum[0].properties.energy.construction == ceil_constr + + model = Model('NewDevelopment', [building]) + model_dict = model.to_dict() + new_model = Model.from_dict(model_dict) + assert model_dict == new_model.to_dict() + new_building = new_model.buildings[0] + assert new_building.properties.energy.ceiling_plenum_construction == ceil_constr + assert new_building.properties.energy.floor_plenum_construction == floor_constr + + def test_to_honeybee(): """Test the Model to_honeybee method.""" pts_1 = (Point3D(0, 0, 3), Point3D(10, 0, 3), Point3D(10, 10, 3), Point3D(0, 10, 3))