Skip to content

Commit d3f6b7a

Browse files
authored
Merge pull request #1375 from pnnl/develop
Develop
2 parents 511e6c9 + e687b13 commit d3f6b7a

File tree

8 files changed

+362
-12
lines changed

8 files changed

+362
-12
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "ruleset-checking-tool"
3-
version = "0.2.6"
3+
version = "0.2.7"
44
description = "PNNL ruleset checking tool"
55
authors = ["Weili Xu <[email protected]>", "Charlie Holly <[email protected]>", "Juan Gonzalez <[email protected]>", "Yun Joon Jung <[email protected]>", "Jiarong Xie <[email protected]>"]
66
license = "MIT"

rct229/rule_engine/engine.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def get_available_rules():
2727
return available_rules
2828

2929

30-
def evaluate_all_rules_rpd(ruleset_project_descriptions):
30+
def evaluate_all_rules_rpd(ruleset_project_descriptions, session_id=""):
3131
# Get reference to rule functions in rules model
3232
available_rule_definitions = rulesets.__getrules__()
3333
ruleset_models = get_rmd_instance()
@@ -49,7 +49,7 @@ def evaluate_all_rules_rpd(ruleset_project_descriptions):
4949

5050
print("Processing rules...")
5151
rules_list = [rule_def[1]() for rule_def in available_rule_definitions]
52-
report = evaluate_rules(rules_list, ruleset_models)
52+
report = evaluate_rules(rules_list, ruleset_models, session_id=session_id)
5353
report["rpd_files"] = rpd_rmd_map_list
5454

5555
return report
@@ -135,7 +135,11 @@ def evaluate_rule(rule, rmrs, test=False):
135135

136136

137137
def evaluate_rules(
138-
rules_list: list, rmds: RuleSetModels, unit_system=UNIT_SYSTEM.IP, test=False
138+
rules_list: list,
139+
rmds: RuleSetModels,
140+
unit_system=UNIT_SYSTEM.IP,
141+
test=False,
142+
session_id="",
139143
):
140144
"""Evaluates a list of rules against an RMDs
141145
@@ -147,6 +151,7 @@ def evaluate_rules(
147151
Object containing RPDs for ruleset evaluation
148152
test: Boolean
149153
Flag to indicate whether this run is for software testing workflow or not.
154+
session_id: string
150155
151156
Returns
152157
-------
@@ -227,7 +232,7 @@ def evaluate_rules(
227232
rule_counter += 1
228233
if rule_counter in counting_steps:
229234
print(
230-
f"Compliance evaluation progress: {round(rule_counter / total_num_rules * 100)}%"
235+
f"Project Evaluation Session ID: #{session_id}# => Compliance evaluation progress: {round(rule_counter / total_num_rules * 100)}%"
231236
)
232237
print(f"Processing Rule {rule.id}")
233238
outcome = rule.evaluate(copied_rmds)

rct229/rulesets/ashrae9012019/section1/section1rule6.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ def __init__(self):
1414
USER=False, BASELINE_0=True, PROPOSED=True
1515
),
1616
id="1-6",
17-
description="temp",
17+
description="The proposed design shall be the same as the baseline design for all data elements identified in the schema hosted at data.standards.ashrae {{https://github.com/open229/ruleset-model-description-schema/blob/main/docs229/ASHRAE229_extra.schema.json}}",
1818
ruleset_section_title="Performance Calculation",
19-
standard_section="a",
19+
standard_section="Table G3.1(1) Baseline Building Performance (a)",
2020
is_primary_rule=True,
2121
rmd_context="",
2222
)

rct229/rulesets/ashrae9012019/section1/section1rule7.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ def __init__(self):
1313
USER=True, BASELINE_0=False, PROPOSED=True
1414
),
1515
id="1-7",
16-
description="temp",
16+
description="The proposed design shall be the same as the user design for all data elements identified in the schema hosted at data.standards.ashrae {{https://github.com/open229/ruleset-model-description-schema/blob/main/docs229/ASHRAE229_extra.schema.json}}",
1717
ruleset_section_title="Performance Calculation",
18-
standard_section="a",
18+
standard_section="Table G3.1(1) Proposed Building Performance (a)",
1919
is_primary_rule=True,
2020
rmd_context="",
2121
)

rct229/utils/std_comparisons.py

+87
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import operator
2+
from pint import Quantity
3+
from decimal import Decimal, ROUND_HALF_UP
4+
5+
16
"""
27
Global tolerance for equality comparison. Default allows 0.5% variations of generated baseline/proposed from the standard specify value.
38
"""
@@ -29,3 +34,85 @@ def std_equal(
2934
True iff the val is within percent_tolerance of std_val
3035
"""
3136
return abs(std_val - val) <= (percent_tolerance / 100) * abs(std_val)
37+
38+
39+
def std_equal_with_precision(
40+
val: Quantity | float | int,
41+
std_val: Quantity | float | int,
42+
precision: Quantity | float | int,
43+
) -> bool:
44+
"""Determines whether the model value and standard value are equal with the specified precision. If any of the function inputs are a Quantity type, then all function inputs must be Quantity.
45+
46+
Parameters
47+
----------
48+
val: Quantity | float | int
49+
value extracted from model
50+
std_val : Quantity | float | int
51+
standard value from code
52+
precision: Quantity | float | int
53+
number of decimal places or significant value to round to, and intended units of the comparison
54+
55+
Returns
56+
-------
57+
bool
58+
True if the modeled value is equal to the standard value within the specified precision
59+
"""
60+
# Check if all or none of the arguments are Quantity types
61+
are_quantities = [isinstance(arg, Quantity) for arg in [val, std_val, precision]]
62+
if not (all(are_quantities) or not any(are_quantities)):
63+
raise TypeError(
64+
"Arguments must be consistent in type: all Quantity or all non-Quantity."
65+
)
66+
67+
# Determine if the values are pint Quantities and handle accordingly
68+
if (
69+
isinstance(val, Quantity)
70+
and isinstance(std_val, Quantity)
71+
and isinstance(precision, Quantity)
72+
):
73+
units = precision.units
74+
val = val.to(units)
75+
std_val = std_val.to(units)
76+
val_magnitude = Decimal(str(val.magnitude))
77+
std_val_magnitude = Decimal(str(std_val.magnitude))
78+
precision_magnitude = Decimal(str(precision.magnitude))
79+
else:
80+
val_magnitude = Decimal(str(val))
81+
std_val_magnitude = Decimal(str(std_val))
82+
precision_magnitude = Decimal(str(precision))
83+
84+
# Determine rounding precision based on whether precision is a whole number or a decimal
85+
if precision_magnitude.as_tuple().exponent < 0:
86+
# Decimal places (e.g., 0.01)
87+
precision_decimal_places = abs(precision_magnitude.as_tuple().exponent)
88+
rounding_precision = f"1E-{str(precision_decimal_places)}"
89+
else:
90+
# Whole number (e.g., 10, 100)
91+
rounding_precision = f"1E+{str(int(precision_magnitude.log10()))}"
92+
93+
# Round both values to the specified precision
94+
val_rounded = val_magnitude.quantize(
95+
Decimal(rounding_precision), rounding=ROUND_HALF_UP
96+
)
97+
std_val_rounded = std_val_magnitude.quantize(
98+
Decimal(rounding_precision), rounding=ROUND_HALF_UP
99+
)
100+
101+
# Compare the rounded values
102+
return val_rounded == std_val_rounded
103+
104+
105+
def std_conservative_outcome(
106+
val: Quantity, std_val: Quantity, conservative_operator_wrt_std: operator
107+
):
108+
"""Determines if the model value has a conservative outcome compared to the standard value.
109+
110+
Parameters
111+
----------
112+
val: Quantity
113+
value extracted from model
114+
std_val : Quantity
115+
standard value from code
116+
conservative_operator_wrt_std: operator that results in a conservative outcome compared to the standard value
117+
"""
118+
return conservative_operator_wrt_std(val, std_val)

rct229/utils/std_comparisons_test.py

+75-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import operator
2+
import pytest
23

34
from rct229.schema.config import ureg
45
from rct229.utils.compare_standard_val import (
56
compare_standard_val,
67
compare_standard_val_strict,
78
)
8-
from rct229.utils.std_comparisons import std_equal
9+
from rct229.utils.std_comparisons import (
10+
std_equal,
11+
std_equal_with_precision,
12+
std_conservative_outcome,
13+
)
914

1015
_M2 = ureg("m2")
1116

@@ -150,3 +155,72 @@ def test__compare_standard_val_strict_gt__false_with_units():
150155
std_val=1.0101 * _M2,
151156
operator=operator.gt,
152157
)
158+
159+
160+
def test__std_equal_with_precision__false_types_vary():
161+
with pytest.raises(TypeError):
162+
std_equal_with_precision(1.05 * _M2, 1.1, 0.1)
163+
164+
165+
def test__std_equal_with_precision__true_with_units():
166+
assert std_equal_with_precision(1.05 * _M2, 1.1 * _M2, 0.1 * _M2)
167+
168+
169+
def test__std_equal_with_precision__true_without_units():
170+
assert std_equal_with_precision(1.05, 1.1, 0.1)
171+
172+
173+
def test__std_equal_with_precision__false_with_units():
174+
assert not std_equal_with_precision(1.15 * _M2, 1.1 * _M2, 0.1 * _M2)
175+
176+
177+
def test__std_equal_with_precision__false_without_units():
178+
assert not std_equal_with_precision(1.15, 1.1, 0.1)
179+
180+
181+
def test__std_equal_with_precision__10_true_with_units():
182+
assert std_equal_with_precision(145 * _M2, 150 * _M2, 10 * _M2)
183+
184+
185+
def test__std_equal_with_precision__10_true_without_units():
186+
assert std_equal_with_precision(145, 150, 10)
187+
188+
189+
def test__std_equal_with_precision__10_false_with_units():
190+
assert not std_equal_with_precision(155 * _M2, 150 * _M2, 10 * _M2)
191+
192+
193+
def test__std_equal_with_precision__10_false_without_units():
194+
assert not std_equal_with_precision(155, 150, 10)
195+
196+
197+
def test__std_conservative_outcome__true_with_units_gt():
198+
assert std_conservative_outcome(1.1 * _M2, 1.05 * _M2, operator.gt)
199+
200+
201+
def test__std_conservative_outcome__true_with_units_lt():
202+
assert std_conservative_outcome(1.05 * _M2, 1.1 * _M2, operator.lt)
203+
204+
205+
def test__std_conservative_outcome__false_with_units_gt():
206+
assert not std_conservative_outcome(1.09999 * _M2, 1.1 * _M2, operator.gt)
207+
208+
209+
def test__std_conservative_outcome__false_with_units_lt():
210+
assert not std_conservative_outcome(1.05001 * _M2, 1.05 * _M2, operator.lt)
211+
212+
213+
def test__std_conservative_outcome__true_without_units_gt():
214+
assert std_conservative_outcome(1.1, 1.05, operator.gt)
215+
216+
217+
def test__std_conservative_outcome__true_without_units_lt():
218+
assert std_conservative_outcome(1.05, 1.1, operator.lt)
219+
220+
221+
def test__std_conservative_outcome__false_without_units_gt():
222+
assert not std_conservative_outcome(1.09999, 1.1, operator.gt)
223+
224+
225+
def test__std_conservative_outcome__false_without_units_lt():
226+
assert not std_conservative_outcome(1.05001, 1.05, operator.lt)

rct229/web_application.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ def run_software_test(ruleset, section=None, saving_dir="./"):
121121
return report_dir
122122

123123

124-
def run_project_evaluation(rpds, ruleset, reports=["RAW_OUTPUT"], saving_dir="./"):
124+
def run_project_evaluation(
125+
rpds, ruleset, reports=["RAW_OUTPUT"], saving_dir="./", session_id=""
126+
):
125127
"""
126128
127129
Parameters
@@ -130,6 +132,7 @@ def run_project_evaluation(rpds, ruleset, reports=["RAW_OUTPUT"], saving_dir="./
130132
ruleset: str ruleset key
131133
reports: list[str] list of strings and each string is the enum value of a report
132134
saving_dir: directory to save report.
135+
session_id: a string representing a calculation session
133136
134137
Returns
135138
-------
@@ -161,7 +164,7 @@ def run_project_evaluation(rpds, ruleset, reports=["RAW_OUTPUT"], saving_dir="./
161164

162165
print("Test implementation of rule engine for ASHRAE Std 229 RCT.")
163166
print("")
164-
report = evaluate_all_rules_rpd(rpds)
167+
report = evaluate_all_rules_rpd(rpds, session_id)
165168

166169
print(f"Saving reports to: {saving_dir}......")
167170
report_path_list = []

0 commit comments

Comments
 (0)