Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RS/CH/get_zcc #269

Merged
merged 30 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
837cbe4
Removed mock_get_zone_conditioning_category_dict()
Feb 2, 2022
d080500
Set Black version to 21.10b0
Feb 2, 2022
936ff7f
Update get_zone_conditioning_category_dict()
Feb 14, 2022
8c73a55
Allow pint_sum() to specify an initializer
Feb 19, 2022
2728391
Updated pint_sum() to handle empty lists
Feb 19, 2022
90e09d5
Added GET_HVAC_ZONE_LIST_W_AREA_DICT__REQUIRED_FIELDS
Feb 19, 2022
9141900
Applied assert_required_fields to get_hvac_zone_list_w_area_dict()
Feb 19, 2022
7eb7644
Added floor area assertion
Feb 19, 2022
71b1f54
Added assertions.py
Feb 21, 2022
7370c36
Handl empty lists with pint_sum()
Feb 21, 2022
25756a8
Bug fixes for get_zone_conditioning_category_dict()
Feb 21, 2022
7f3b554
Test code for get_zone_conditioning_category_dict()
Feb 21, 2022
e9603f8
Comment in validate file
Feb 21, 2022
53b6824
Comments in RDS for get_zone_conditioning_category
Feb 21, 2022
73f6c28
Set correct Black version for github action
Feb 21, 2022
84a109a
Fixing my own Black version
Feb 21, 2022
518e98e
Merge branch 'develop' into RS/CH/get_zcc
charliepnnl Feb 21, 2022
9c2798c
Pipfile change
Feb 21, 2022
18a8acc
Ran Black
Feb 21, 2022
155a75e
Update get_zone_conditioning_category.md
xingcx Mar 5, 2022
4cea4ef
Remove dataclass import
Mar 14, 2022
245763c
Fix get_zcc surface UA calculation
Mar 14, 2022
ba0eca7
Update pytest for get_zcc
Mar 14, 2022
6990d5d
Created getattr_ utility function
Mar 14, 2022
f91fbee
Added a pytest for getattr_()
Mar 14, 2022
b0e9696
getattr_ requires at least one key
Mar 14, 2022
fa40325
Merge branch 'develop' into RS/CH/get_zcc
charliepnnl Mar 14, 2022
ae0ce01
update tests
Mar 15, 2022
12b78bc
format
Mar 15, 2022
c9b2bba
Format
Mar 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ python_version = "3.7"
[dev-packages]
pylint = "*"
pytest = "*"
black = "21.10b0"
isort = "*"
isort = "5.10.1"
icecream = "*"
black = "~=21.10b0"

[packages]
rct229 = {editable = true, path = "."}
Expand Down
591 changes: 321 additions & 270 deletions Pipfile.lock

Large diffs are not rendered by default.

53 changes: 28 additions & 25 deletions docs/ruleset_functions/get_zone_conditioning_category.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,23 @@ Constants:

Logic:

- Get building climate zone: `climate_zone = RMR.weather.climate_zone`
- Get dictionary for the list of zones and their total floor area served by each HVAC system in RMR: `hvac_zone_list_w_area_dict = get_hvac_zone_list_w_area(RMR)`

- Get heated space criteria: `system_min_heating_output = data_lookup(table_3_2,climate_zone)`
- For each HVAC system id in dictionary: `for hvac_sys_id in hvac_zone_list_w_area_dict.keys():` (Note XC, this only gets HVAC systems serving zones. Orphan HVAC systems are not looped)

- Get dictionary for the list of zones and their total floor area served by each HVAC system in RMR: `hvac_zone_list_w_area_dict = get_hvac_zone_list_w_area(RMR)`
- Get total central sensible cooling capacity for HVAC system:
**[CH: The hvac system has at most one cooling_system, one heating_system, and one preheat_system. Also, I think we need to handle any of these systems missing.]**
`total_central_sensible_cool_capacity = SUM(cooling_system.sensible_cool_capacity for cooling_system in GET_COMPONENT_BY_ID(hvac_sys_id).cooling_system)`

- For each HVAC system id in dictionary: `for hvac_sys_id in hvac_zone_list_w_area_dict.keys():` (Note XC, this only gets HVAC systems serving zones. Orphan HVAC systems are not looped)
- Get total central heating capacity for HVAC system: `total_central_heat_capacity = SUM(heating_system.heat_capacity for heating_system in GET_COMPONENT_BY_ID(hvac_sys_id).heating_system) + SUM(preheat_system.heat_capacity for preheat_system in GET_COMPONENT_BY_ID(hvac_sys_id).preheat_system)`

- Get total central sensible cooling capacity for HVAC system: `total_central_sensible_cool_capacity = SUM(cooling_system.sensible_cool_capacity for cooling_system in GET_COMPONENT_BY_ID(hvac_sys_id).cooling_system)`
- Calculate and save total central sensible cooling output per floor area for HVAC system to dictionary: `hvac_cool_capacity_dict[hvac_sys_id] = total_central_sensible_cool_capacity / hvac_zone_list_w_area_dict[hvac_sys_id]["TOTAL_AREA"]`

- Get total central heating capacity for HVAC system: `total_central_heat_capacity = SUM(heating_system.heat_capacity for heating_system in GET_COMPONENT_BY_ID(hvac_sys_id).heating_system) + SUM(preheat_system.heat_capacity for preheat_system in GET_COMPONENT_BY_ID(hvac_sys_id).preheat_system)`
- Calculate and save total central heating output per floor area for HVAC system to dictionary: `hvac_heat_capacity_dict[hvac_sys_id] = total_central_heat_capacity / hvac_zone_list_w_area_dict[hvac_sys_id]["TOTAL_AREA"]`

- Calculate and save total central sensible cooling output per floor area for HVAC system to dictionary: `hvac_cool_capacity_dict[hvac_sys_id] = total_central_sensible_cool_capacity / hvac_zone_list_w_area_dict[hvac_sys_id]["TOTAL_AREA"]`
- Get building climate zone: `climate_zone = RMR.weather.climate_zone`

- Calculate and save total central heating output per floor area for HVAC system to dictionary: `hvac_heat_capacity_dict[hvac_sys_id] = total_central_heat_capacity / hvac_zone_list_w_area_dict[hvac_sys_id]["TOTAL_AREA"]`
- Get heated space criteria: `system_min_heating_output = data_lookup(table_3_2,climate_zone)`

- For each zone in RMR: `for zone in RMR...zones:`

Copy link
Collaborator

Choose a reason for hiding this comment

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

I will need to include zonal cooling output in the calculation below. The schema has been updated to reflect that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is updated.

Expand All @@ -47,7 +49,9 @@ Logic:

- Add central heating capacity per floor area to zone capacity dictionary: `zone_capacity_dict[zone.id]["HEATING"] += hvac_heat_capacity_dict[hvac_sys.id]`

- Check if terminal has heating capacity, add to zone capacity dictionary: `if terminal.heat_capacity: zone_capacity_dict[zone.id]["HEATING"] += terminal.heat_capacity / zone_area` (Note XC, it seems terminal does not have cooling capacity. Might need cooling capacity at the terminal level for equipment like active beams.)
- Check if terminal has heating capacity, add to zone capacity dictionary: `if terminal.heating_capacity: zone_capacity_dict[zone.id]["HEATING"] += terminal.heat_capacity / zone_area`

- Check if terminal has cooling capacity, add to zone capacity dictionary: `if terminal.cooling_capacity: zone_capacity_dict[zone.id]["SENSIBLE_COOLING"] += terminal.cooling_capacity / zone_area`

- Check if zone meets the criteria for directly conditioned (heated or cooled) zone, save zone as directly conditioned: `if ( zone_capacity_dict[zone.id]["SENSIBLE_COOLING"] >= CAPACITY_THRESHOLD ) OR ( zone_capacity_dict[zone.id]["HEATING"] >= system_min_heating_output ): directly_conditioned_zones.append(zone.id)`

Expand All @@ -64,12 +68,12 @@ Logic:
- For each surface in zone: `for surface in zone.surfaces:`

- Check if surface is interior, get adjacent zone: `if surface.adjacent_to == "INTERIOR": adjacent_zone = match_data_element(RMR, zones, surface.adjacent_zone_id)`
**[CH: There is no `surface.fenestration_subsurfaces` field. There is a `surface.subsurfaces` field instead. Subsurface has `opaque_area` and `glazed_area`.]**
Copy link
Collaborator

Choose a reason for hiding this comment

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

Surface.fenestration_subsurfaces is no longer in the schema. I'll correct the calculation below.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is corrected.

- If adjacent zone is directly conditioned (heated or cooled), add the product of the U-factor and surface area to the directly conditioned type: `if adjacent_zone in directly_conditioned_zone: directly_conditioned_product_sum += sum( ( subsurface.glazed_area + subsurface.opaque_area ) * subsurface.u_factor for subsurface in surface.subsurfaces ) + ( surface.area - sum( ( subsurface.glazed_area + subsurface.opaque_area ) for subsurface in surface.subsurfaces ) * surface.construction.u_factor`

- If adjacent zone is directly conditioned (heated or cooled), add the product of the U-factor and surface area to the directly conditioned type: `if adjacent_zone in directly_conditioned_zone: directly_conditioned_product_sum += sum( ( fenestration.glazed_area + fenestration.opaque_area ) * fenestration.u_factor for fenestration in surface.fenestration_subsurfaces ) + ( surface.area - sum( ( fenestration.glazed_area + fenestration.opaque_area ) for fenestration in surface.fenestration_subsurfaces ) * surface.construction.u_factor`
- Else, add the product of the U-factor and surface area to the other type: `else: other_product_sum += sum( ( subsurface.glazed_area + subsurface.opaque_area ) * subsurface.u_factor for subsurface in surface.subsurfaces ) + ( surface.area - sum( ( subsurface.glazed_area + subsurface.opaque_area ) for subsurface in surface.subsurfaces ) * surface.construction.u_factor`

- Else, add the product of the U-factor and surface area to the other type: `else: other_product_sum += sum( ( fenestration.glazed_area + fenestration.opaque_area ) * fenestration.u_factor for fenestration in surface.fenestration_subsurfaces ) + ( surface.area - sum( ( fenestration.glazed_area + fenestration.opaque_area ) for fenestration in surface.fenestration_subsurfaces ) * surface.construction.u_factor`

- Else check if surface is exterior, add the product of the U-factor and surface area to the other type: `else if surface.adjacent_to == "EXTERIOR": other_product_sum += sum( ( fenestration.glazed_area + fenestration.opaque_area ) * fenestration.u_factor for fenestration in surface.fenestration_subsurfaces ) + ( surface.area - sum( ( fenestration.glazed_area + fenestration.opaque_area ) for fenestration in surface.fenestration_subsurfaces ) * surface.construction.u_factor`
- Else check if surface is exterior, add the product of the U-factor and surface area to the other type: `else if surface.adjacent_to == "EXTERIOR": other_product_sum += sum( ( subsurface.glazed_area + subsurface.opaque_area ) * subsurface.u_factor for subsurface in surface.subsurfaces ) + ( surface.area - sum( ( subsurface.glazed_area + subsurface.opaque_area ) for subsurface in surface.subsurfaces ) * surface.construction.u_factor`

- Determine if zone is indirectly conditioned: `if directly_conditioned_product_sum > other_product_sum: indirectly_conditioned_zones.append(zone)`

Expand All @@ -88,29 +92,28 @@ Logic:
- For each space in zone: `for space in zone.spaces:`

- Check if lighting space type is residential, space is classified as residential: `if space.lighting_space_type in ["DORMITORY_LIVING_QUARTERS" , "FIRE_STATION_SLEEPING_QUARTERS", "GUEST_ROOM", "DWELLING_UNIT", "HEALTHCARE_FACILITY_NURSERY", "HEALTHCARE_FACILITY_PATIENT_ROOM"]: space_residential_flag = TRUE`
**[CH: I suggest replacing `space_residential_flag` with `zone_has_residential_spaces` and replacing `space_nonresidential_flag` with `zone_has_residential_spaces`.]**
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll update these.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is updated.

- Else if lighting space type is specified, space is classified as non-residential: `else if space.lighting_space_type: zone_has_nonresidential_spaces = TRUE`

- Else if lighting space type is specified, space is classified as non-residential: `else if space.lighting_space_type: space_nonresidential_flag = TRUE`

- Else if lighting space type is not specified, and lighting building area type is residential, space is classified as residential: `else if segment_residential_flag: space_residential_flag = TRUE`
- Else if lighting space type is not specified, and lighting building area type is residential, space is classified as residential: `else if segment_residential_flag: zone_has_residential_spaces = TRUE`

- Else if lighting space type is not specified, and lighting building area type is non-residential, space is classified as non-residential: `else if segment_nonresidential_flag: space_nonresidential_flag = TRUE`
- Else if lighting space type is not specified, and lighting building area type is non-residential, space is classified as non-residential: `else if segment_nonresidential_flag: zone_has_nonresidential_spaces = TRUE`

- Else, neither lighting space type or lighting building area type is specified, space is classified as non-residential (i.e. where the space classification is unknown, the space shall be classified as an office space as per G3.1-1(c)): `else: space_nonresidential_flag = TRUE`
- Else, neither lighting space type or lighting building area type is specified, space is classified as non-residential (i.e. where the space classification is unknown, the space shall be classified as an office space as per G3.1-1(c)): `else: zone_has_nonresidential_spaces = TRUE`

- If zone has both residential and non-residential spaces, classify zone as conditioned mixed: `if residential_flag AND nonresidential_flag: zone_conditioning_category_dict[zone.id] = "CONDITIONED MIXED"`
- If zone has both residential and non-residential spaces, classify zone as conditioned mixed: `if zone_has_residential_spaces AND zone_has_nonresidential_spaces: zone_conditioning_category_dict[zone.id] = "CONDITIONED MIXED"`

- Else if zone has only residential spaces, classify zone as conditioned residential: `else if residential_flag: zone_conditioning_category_dict[zone.id] = "CONDITIONED RESIDENTIAL"`

- Else if zone has only non-residential spaces, classify zone as conditioned non-residential: `else: zone_conditioning_category_dict[zone.id] = "CONDITIONED NON-RESIDENTIAL"`
- Else if zone has only residential spaces, classify zone as conditioned residential: `else if zone_has_residential_spaces: zone_conditioning_category_dict[zone.id] = "CONDITIONED RESIDENTIAL"`

- Else, zone has only non-residential spaces, classify zone as conditioned non-residential: `else: zone_conditioning_category_dict[zone.id] = "CONDITIONED NON-RESIDENTIAL"`
- Else if zone is semi-heated, classify zone as semi-heated: `else if zone in semiheated_zones: zone_conditioning_category_dict[zone.id] = "SEMI-HEATED"`

- Else if zone has interior parking spaces, classify zone as unenclosed: `if( space.lighting_space_type == "PARKING_AREA_INTERIOR" for space in zone.spaces ): zone_conditioning_category_dict[zone.id] = "UNENCLOSED"`

- Else if zone is crawlspace, classify zone as unenclosed: `else if ( ( zone.volume / sum( space.floor_area for space in zone.spaces ) ) < CRAWLSPACE_HEIGHT_THRESHOLD ) AND ( ( get_opaque_surface_type(surface) in ["HEATED SLAB-ON-GRADE", "UNHEATED SLAB-ON-GRADE"] ) AND ( surface.adjacent_to == "GROUND" ) for surface in zone.surfaces ): zone_conditioning_category_dict[zone.id] = "UNENCLOSED"` (Note XC, need to check surface != Floor, RDS online not updated)
- Else if zone is crawlspace, classify zone as unenclosed: `else if ( ( zone.volume / sum( space.floor_area for space in zone.spaces ) ) < CRAWLSPACE_HEIGHT_THRESHOLD ) AND ( get_opaque_surface_type(surface) in ["HEATED SLAB-ON-GRADE", "UNHEATED SLAB-ON-GRADE"] for `**[CH: insert `any`]**` ANY surface in zone.surfaces ): zone_conditioning_category_dict[zone.id] = "UNENCLOSED"`

- Else if zone is attic, classify zone as unenclosed: `else if ( ( get_opaque_surface_type(surface) == "CEILING" ) AND ( surface.adjacent_to == "EXTERIOR" ) for surface in zone.surfaces ): zone_conditioning_category_dict[zone.id] = "UNENCLOSED"`
- Else if zone is attic, classify zone as unenclosed: `else if ( ( get_opaque_surface_type(surface) == "ROOF"`**[CH: ROOF?]**` ) AND ( surface.adjacent_to == "EXTERIOR" ) for `**[CH: insert `any`]**` ANY surface in zone.surfaces ): zone_conditioning_category_dict[zone.id] = "UNENCLOSED"`

- Else, classify zone as unconditioned: `else: zone_conditioning_category_dict[zone.id] = "UNCONDITIONED"`

**Returns** `return zone_conditioning_category_dict`
26 changes: 24 additions & 2 deletions rct229/ruleset_functions/get_hvac_zone_list_w_area_dict.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from rct229.schema.config import ureg
from rct229.utils.assertions import assert_, assert_required_fields
from rct229.utils.jsonpath_utils import find_all
from rct229.utils.pint_utils import pint_sum
from rct229.utils.pint_utils import ZERO, pint_sum

# Intended for export and internal use
GET_HVAC_ZONE_LIST_W_AREA_DICT__REQUIRED_FIELDS = {
"building": {
"building_segments[*].zones[*].spaces[*]": [
"floor_area",
],
"building_segments[*].zones[*].terminals[*]": [
"served_by_heating_ventilation_air_conditioning_systems"
],
}
}


def get_hvac_zone_list_w_area_dict(building):
Expand All @@ -23,13 +36,18 @@ def get_hvac_zone_list_w_area_dict(building):
}
}
"""
assert_required_fields(
GET_HVAC_ZONE_LIST_W_AREA_DICT__REQUIRED_FIELDS["building"], building
)

hvac_zone_list_w_area_dict = {}

for zone in find_all("$..zones[*]", building):
terminals = zone.get("terminals")
# Note: None and [] are falsey; zone.terminals is optional
if terminals:
zone_area = pint_sum(find_all("spaces[*].floor_area", zone))
zone_area = pint_sum(find_all("spaces[*].floor_area", zone), ZERO.AREA)
assert_(zone_area > ZERO.AREA, f"zone:{zone['id']} has zero floor area")
for terminal in terminals:
hvac_sys_id = terminal[
"served_by_heating_ventilation_air_conditioning_systems"
Expand All @@ -47,4 +65,8 @@ def get_hvac_zone_list_w_area_dict(building):
hvac_sys_entry["zone_list"].append(zone["id"])
hvac_sys_entry["total_area"] += zone_area

assert_(
hvac_sys_entry["total_area"] > ZERO.AREA,
f"terminal:{terminal['id']} serves zero floor area",
)
return hvac_zone_list_w_area_dict
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
SEMI_HEATED,
UNCONDITIONED,
UNENCLOSED,
mock_get_zone_conditioning_category_dict,
get_zone_conditioning_category_dict,
)
from rct229.utils.jsonpath_utils import find_all

Expand Down Expand Up @@ -121,7 +121,7 @@ def get_surface_conditioning_category_dict(climate_zone, building):
surface_conditioning_category_dict = {}

# Get the conditioning category for all the zones in the building
zcc_dict = mock_get_zone_conditioning_category_dict(climate_zone, building)
zcc_dict = get_zone_conditioning_category_dict(climate_zone, building)

# Loop through all the zones in the building
for zone in find_all("building_segments[*].zones[*]", building):
Expand Down
Loading