Skip to content

Commit

Permalink
Merge pull request #1257 from pnnl/RS/YJ/Rule_5-1
Browse files Browse the repository at this point in the history
RS/YJ/Rule 5-1
  • Loading branch information
weilixu authored Jan 23, 2024
2 parents b1ef782 + a82deea commit b517da8
Show file tree
Hide file tree
Showing 9 changed files with 1,246 additions and 33 deletions.
32 changes: 16 additions & 16 deletions docs/section5/Rule5-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,34 @@ None
## Rule Logic:

Get the fenestration area for each unique orientation (i.e., azimuth) and then check if the minimum and maximum areas differ by 5% or more
- Create a blank dictionary that will have all unique azimuths paired with the total vertical fenestration area: `azimuth_fen_area_dict = {}`
- Create a blank dictionary that will have all unique azimuths paired with the total vertical fenestration area: `azimuth_fen_area_dict_b = {}`
- For each building in the B_RMD: `for bldg in B_RMD.buildings:`
- For each building_segment in the bldg: `for bldg_seg in bldg.building_segments`
- For each zone in the building_segment: `for zone in bldg_seg.zones`
- For each surface in zone: `for surface in zone.surfaces:`
- Check if surface is above-grade wall: `if get_opaque_surface_type(surface) == "ABOVE-GRADE WALL":`
- Get the azimuth: `surface_azimuth = surface.azimuth`
- Check if the azimuth is not in a key in the dictionary of azimuths and fen area, if it is not then add it: `if surface_azimuth not in azimuth_fen_area_dict:`
- Add the unique azimuth to the dictionary as a key and set initial fen area to 0: `azimuth_fen_area_dict[surface_azimuth]= 0`
- Check if the azimuth is not in a key in the dictionary of azimuths and fen area, if it is not then add it: `if surface_azimuth not in azimuth_fen_area_dict_b:`
- Add the unique azimuth to the dictionary as a key and set initial fen area to 0: `azimuth_fen_area_dict_b[surface_azimuth]= 0`
- Reset the total surface fenestration area variable to 0: `total_surface_fenestration_area = 0`
- For each subsurface associated with the surface: `for sub_surface in surface.subsurfaces:`
- Check if subsurface is door: `if sub_surface.classification == "DOOR":`
- If glazed area in door is more than 50% of the total door area, add door area to total_surface_fenestration_area: `if sub_surface.glazed_area > subsurface.opaque_area: total_surface_fenestration_area += sub_surface.glazed_area + sub_surface.opaque_area`
- Else, subsurface is not door, add total area to total_surface_fenestration_area (because we checked that the surface is an above grade wall we can assume that the subsurface classification is not and could not be a skylight so no need to check this): `total_surface_fenestration_area += subsurface.glazed_area + subsurface.opaque_area`
- Add the total_surface_fenestration_area summed for the surface to the total fen area associated with the azimuth: `azimuth_fen_area_dict[surface_azimuth] += total_surface_fenestration_area`
- Add the total_surface_fenestration_area summed for the surface to the total fen area associated with the azimuth: `azimuth_fen_area_dict_b[surface_azimuth] += total_surface_fenestration_area`

- Loop through the dictionary keys and put the area in bins depending on the azimuth (bins will be in 3 degree increments). Use this logic: if azimuth >= 0 and < 3 then put area in the 0-3 bin, if azimuth >=3 and < 6 then put the area in the the 3-6 bin, etc. `for azi in azimuth_fen_area_dict.keys():`
- Loop through the dictionary keys and put the area in bins depending on the azimuth (bins will be in 3 degree increments). Use this logic: if azimuth >= 0 and < 3 then put area in the 0-3 bin, if azimuth >=3 and < 6 then put the area in the the 3-6 bin, etc. `for azi in azimuth_fen_area_dict_b.keys():`
- Lookup the bin that the azimuth falls into based on the value of the azimuth using this logic. Bin lookup table based on this logic: if azimuth >= 0 and < 3 then put the fen area in the 0-3 bin, if azimuth >=3 and < 6 then put the area in the the 3-6 bin, etc. (this assumes the RCT team will create a lookup table to make it easy to lookup the bin that the azimuth (azi) falls into): `bin = lookup(azi, lookuptable)`
- Add the area to the bin in a revised binned dictionary (bin is key, area is value in dictionary): `azimuth_fen_area__binned_dict[bin] += azimuth_fen_area_dict[azi]`
- Add the area to the bin in a revised binned dictionary (bin is key, area is value in dictionary): `azimuth_fen_area_dict_b[bin] += azimuth_fen_area_dict_b[azi]`

Check if the area differs by 5 percent or more.
- Get the max fen area: `max_fen_area = azimuth_fen_area__binned_dict[max(azimuth_fen_area__binned_dict, key=azimuth_fen_area__binned_dict.get)]`
- Get the min fen area: `min_fen_area = azimuth_fen_area__binned_dict[min(azimuth_fen_area__binned_dict, key=azimuth_fen_area__binned_dict.get)]`
- Get the max fen area: `max_fen_area = azimuth_fen_area_dict_b[max(azimuth_fen_area_dict_b, key=azimuth_fen_area_dict_b.get)]`
- Get the min fen area: `min_fen_area = azimuth_fen_area_dict_b[min(azimuth_fen_area_dict_b, key=azimuth_fen_area_dict_b.get)]`
- Calculate the % difference, take the maximum calculated: `percent_difference = max(abs(max_fen_area- min_fen_area)/max_fen_area,abs(min_fen_area- max_fen_area)/min_fen_area)`
- Check if the % difference is 5% or more, if it is then set rotation_expected boolean to TRUE: `if percent_difference >= 5%: rotation_expected = TRUE`
- Else, set rotation_expected to FALSE: `else: rotation_expected = FALSE`
- Check if the % difference is 5% or more, if it is then set rotation_expected_b boolean to TRUE: `if percent_difference >= 5%: rotation_expected_b = TRUE`
- Else, set rotation_expected_b to FALSE: `else: rotation_expected_b = FALSE`

- Set counter variable to 0 (counts the number of output instances): `counter =0`
- Set no_of_output_instance variable to 0 (counts the number of output instances): `no_of_output_instance =0`
Determine which RMDs have been created/provided
- Create variable for list of RMDs: `rmds = RulesetProjectDescription.ruleset_model_descriptions`
- Check for user RMD: `has_user = any[rmd.type == USER for rmd in rmds]`
Expand All @@ -57,19 +57,19 @@ Determine which RMDs have been created/provided
- Check for baseline 90 degree RMD: `has_baseline_90 = any[rmd.type == BASELINE_90 for rmd in rmds]`
- Check for baseline 180 degree RMD: `has_baseline_180 = any[rmd.type == BASELINE_180 for rmd in rmds]`
- Check for baseline 270 degree RMD: `has_baseline_270 = any[rmd.type == BASELINE_270 for rmd in rmds]`
- Get the number of ruleset_model_descriptions: `number_of_rmds = len(rmds)`
- Get the number of ruleset_model_descriptions: `no_of_rmds = len(rmds)`

- Check that there is an output_instance associated with each RMD and add to the counter variable for each one `For rmd in rmds:`
- Check that there is an output_instance associated with each RMD and add to the no_of_output_instance variable for each one `For rmd in rmds:`
- Check for proposed output: `if rmd.type == PROPOSED and rmd.output.Output2019ASHRAE901.output_instance != Null: has_proposed_output = TRUE`
- Check for baseline 0 degree output: `if rmd.type == BASELINE_0 and rmd.output.Output2019ASHRAE901.output_instance != Null: has_baseline_0_output = TRUE`
- Check for baseline 90 degree output: `if rmd.type == BASELINE_90 and rmd.output.Output2019ASHRAE901.output_instance != Null: has_baseline_90_output = TRUE`
- Check for baseline 180 degree output: `if rmd.type == BASELINE_180 and rmd.output.Output2019ASHRAE901.output_instance != Null: has_baseline_180_output = TRUE`
- Check for baseline 270 degree output: `if rmd.type == BASELINE_270 and rmd.output.Output2019ASHRAE901.output_instance != Null: has_baseline_270_output = TRUE`
- Check if there is an output_instance associated with the RMD: `if rmd.output.Output2019ASHRAE901.output_instance != Null: counter += 1 `
- Check if there is an output_instance associated with the RMD: `if rmd.output.Output2019ASHRAE901.output_instance != Null: no_of_output_instance += 1 `

- **Rule Assertion:**
- Case 1: If the fenestration area differs by 5% or more by orientation and there are 6 RMIs (for user, proposed, baseline at 0 degrees, baseline at 90 degrees, baseline at 180 degrees, and baseline at 270 degrees) and 5 output files (excludes an output for the user model) then Pass: `if rotation_expected == TRUE and has_user == TRUE and has_proposed == TRUE and has_baseline_0 == TRUE and and has_baseline_90 == TRUE and and has_baseline_180 == TRUE and and has_baseline_270 == TRUE and has_proposed_output == TRUE and has_baseline_0_output == TRUE and and has_baseline_90_output == TRUE and and has_baseline_180_output == TRUE and and has_baseline_270_output == TRUE and number_of_rmds == 6 and counter == 5: outcome = "PASS" `
- Case 2: Else if rotation is not expected then pass as long as they have the minimally required RMDs and outputs: `if rotation_expected == FALSE and has_user == TRUE and has_proposed == TRUE and has_baseline_0 == TRUE and has_proposed_output == TRUE and has_baseline_0_output == TRUE: outcome = "PASS" `
- Case 1: If the fenestration area differs by 5% or more by orientation and there are 6 RMIs (for user, proposed, baseline at 0 degrees, baseline at 90 degrees, baseline at 180 degrees, and baseline at 270 degrees) and 5 output files (excludes an output for the user model) then Pass: `if rotation_expected_b == TRUE and has_user == TRUE and has_proposed == TRUE and has_baseline_0 == TRUE and has_baseline_90 == TRUE and has_baseline_180 == TRUE and has_baseline_270 == TRUE and has_proposed_output == TRUE and has_baseline_0_output == TRUE and has_baseline_90_output == TRUE and has_baseline_180_output == TRUE and has_baseline_270_output == TRUE and no_of_rmds == 6 and no_of_output_instance == 5: outcome = "PASS" `
- Case 2: Else if rotation is not expected then pass as long as they have the minimally required RMDs and outputs: `if rotation_expected_b == FALSE and has_user == TRUE and has_proposed == TRUE and has_baseline_0 == TRUE and has_proposed_output == TRUE and has_baseline_0_output == TRUE: outcome = "PASS" `
- Case 43: Else: `Else: outcome = "FAIL" and raise_message "Fail unless Table G3.1#5a exception #2 is applicable and it can be demonstrated that the building orientation is dictated by site considerations.`


Expand Down
4 changes: 3 additions & 1 deletion rct229/rule_engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ def evaluate_rules(
rmds_used[rule_model] = True

for rule_model in rmds.get_ruleset_model_types():
if rmds_used[rule_model]:
if rmds_used[rule_model] and not (
rule.rmrs_used_optional and rule.rmrs_used_optional[rule_model]
):
rmd_validation = validate_rmr(rmds[rule_model], test)
if rmd_validation["passed"] is not True:
invalid_rmds[rule_model] = rmd_validation["error"]
Expand Down
3 changes: 1 addition & 2 deletions rct229/rule_engine/rule_base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from jsonpointer import resolve_pointer

from rct229.rule_engine.rct_outcome_label import RCTOutcomeLabel
from rct229.rule_engine.ruleset_model_factory import RuleSetModels, get_rmd_instance
from rct229.utils.assertions import MissingKeyException, RCTFailureException
from rct229.utils.json_utils import slash_prefix_guarantee
from rct229.utils.jsonpath_utils import find_all
from rct229.utils.pint_utils import calcq_to_q
from rct229.rule_engine.ruleset_model_factory import RuleSetModels, get_rmd_instance


class RuleDefinitionBase:
Expand Down
4 changes: 2 additions & 2 deletions rct229/rule_engine/ruleset_model_factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rct229.rule_engine.rulesets import RuleSet
from rct229.schema.schema_store import SchemaStore
from rct229.schema.schema_enums import SchemaEnums
from rct229.schema.schema_store import SchemaStore
from rct229.utils.assertions import assert_

ruleset_model_dict = {RuleSet.ASHRAE9012019_RULESET: "RulesetModelOptions2019ASHRAE901"}
Expand Down Expand Up @@ -66,7 +66,7 @@ def produce_ruleset_model_instance(**kwargs):
Parameters
----------
kwargs: provide key-value inputs - for example USER=Fasle, BASELINE_0=TRUE
kwargs: provide key-value inputs - for example USER=False, BASELINE_0=TRUE
Returns
-------
Expand Down
1 change: 1 addition & 0 deletions rct229/rulesets/ashrae9012019/section5/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

# TODO: Fix section5rule2 and section5rule49 - they currently cause exceptions
__all__ = [
"section5rule1",
"section5rule2",
"section5rule3",
"section5rule4",
Expand Down
Loading

0 comments on commit b517da8

Please sign in to comment.