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/YJ/Rule 5-1 #1257

Merged
merged 23 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6c65d90
initial commit
yunjoonjung-PNNL Dec 26, 2023
6eec23d
Finished dev
yunjoonjung-PNNL Jan 8, 2024
7ce53b5
Merge branch 'develop' of https://github.com/pnnl/ruleset-checking-to…
yunjoonjung-PNNL Jan 8, 2024
3d1bfe1
Fixed type key checking
yunjoonjung-PNNL Jan 8, 2024
e4fb89e
Addressed PR comments and Fixed rultest_engine
yunjoonjung-PNNL Jan 10, 2024
b1621ee
Addressed PR comments
yunjoonjung-PNNL Jan 12, 2024
7f34b4c
Added missing bool()
yunjoonjung-PNNL Jan 12, 2024
3165584
Updated the files to be able to deal with optional BASELINE 90, 180, …
yunjoonjung-PNNL Jan 12, 2024
d626675
Addressed Weili's comments
yunjoonjung-PNNL Jan 12, 2024
ff54428
Addressed comments
yunjoonjung-PNNL Jan 15, 2024
fe33932
Formatted section22 rules
yunjoonjung-PNNL Jan 15, 2024
291bc68
Merge branch 'develop' of https://github.com/pnnl/ruleset-checking-to…
yunjoonjung-PNNL Jan 15, 2024
4518369
Merge branch 'develop' of https://github.com/pnnl/ruleset-checking-to…
yunjoonjung-PNNL Jan 19, 2024
ff6f383
revert to the original for engine and rule_base changes
yunjoonjung-PNNL Jan 19, 2024
3ddf87b
black rule_base
yunjoonjung-PNNL Jan 19, 2024
a519cad
Formatted 22-29 rule
yunjoonjung-PNNL Jan 19, 2024
cf4fb68
Formatted 22-35,37 and 38
yunjoonjung-PNNL Jan 19, 2024
471ad4c
Merge branch 'develop' of https://github.com/pnnl/ruleset-checking-to…
yunjoonjung-PNNL Jan 19, 2024
8095f18
Added rmrs_used_optional
yunjoonjung-PNNL Jan 19, 2024
7e819c4
Added Jackson's TCD
yunjoonjung-PNNL Jan 19, 2024
7cd1396
Corrected 5-31 TCD
yunjoonjung-PNNL Jan 19, 2024
a6450a8
recreate 5-31
JacksonJ-KC Jan 20, 2024
a82deea
Merge pull request #1264 from pnnl/RT/JDJ/5_31
yunjoonjung-PNNL Jan 22, 2024
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
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
2 changes: 1 addition & 1 deletion rct229/rule_engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def evaluate_rules(
rmds_used = get_rmd_instance()
for rule in rules_list:
for rule_model in rmds.get_ruleset_model_types():
if rule.rmrs_used[rule_model]:
if rule.rmrs_used[rule_model] and rmds[rule_model] is not None:
rmds_used[rule_model] = True

for rule_model in rmds.get_ruleset_model_types():
Expand Down
8 changes: 5 additions & 3 deletions rct229/rule_engine/rule_base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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.rulesets.ashrae9012019 import BASELINE_90, BASELINE_180, BASELINE_270
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 Expand Up @@ -286,7 +286,9 @@ def get_context(self, rmds, data=None):
if self.rmrs_used[ruleset_model] and (
rmds[ruleset_model] is None or not rmds[ruleset_model]
):
missing_contexts.append(ruleset_model)
# BASELINE 90, 180, and 270 are optional
if ruleset_model not in [BASELINE_90, BASELINE_180, BASELINE_270]:
missing_contexts.append(ruleset_model)

if len(missing_contexts) > 0:
retval = "MISSING_" + "_".join(missing_contexts)
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