Skip to content

Commit

Permalink
update unittest
Browse files Browse the repository at this point in the history
  • Loading branch information
yunjoonjung-PNNL committed Jun 21, 2023
1 parent aa77302 commit 52c83cf
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 51 deletions.
105 changes: 60 additions & 45 deletions src/library/trim_respond_logic.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import pandas as pd
import logging, sys
import logging
from typing import Union

import pandas as pd


def TrimRespondLogic(
df: pd.DataFrame,
setpoint_name: str,
Td: Union[float, int],
I: int,
ignored_requests: int,
SPtrim: Union[float, int],
SPres: Union[float, int],
SPmin: Union[float, int],
Expand All @@ -19,10 +19,9 @@ def TrimRespondLogic(
"""Trim and respond logic verification logic.
Args:
df (dataframe): dataframe including time-series data of setpoint, number of requests,
setpoint_name (str): Name of the setpoint in the `df` argument.
df (dataframe): dataframe that must include timestamp (`Date/Time` column), time-series data of setpoint (`setpoint` column), number of requests (`number_of_requests` column name),
Td (float or int): Time delay in minutes.
I (int): Number of ignored requests.
ignored_requests (int): Number of ignored requests.
SPtrim (float or int): Minimum setpoint.
SPres (float or int): Maximum setpoint.
SPmin (float or int):
Expand All @@ -31,100 +30,116 @@ def TrimRespondLogic(
tol (float or int): tolerance.
controller_type (str): either `direct_acting` or `reverse_acting` depending on whether output increases/decreases as the measurement increases/decreases.
Return: a dataframe including verification result in each timestep.
Return: dataframe including verification result in each timestep.
"""

# check the given arguments type
if not isinstance(I, int):
if not isinstance(Td, (float, int)):
logging.error(
f"The type of the `I` arg must be an int. It cannot be {type(I)}."
f"The type of the `Td` arg must be a float or int. It cannot be {type(Td)}."
)
return None
if not (isinstance(SPtrim, float) or isinstance(SPtrim, int)):

if not isinstance(ignored_requests, int):
logging.error(
f"The type of the `SPtrim` arg must be a float. It cannot be {type(SPtrim)}."
f"The type of the `ignored_requests` arg must be an int. It cannot be {type(ignored_requests)}."
)
return None
if not (isinstance(SPres, float) or isinstance(SPres, int)):

if not isinstance(SPtrim, (float, int)):
logging.error(
f"The type of the `SPres` arg must be a float. It cannot be {type(SPres)}."
f"The type of the `SPtrim` arg must be a float or int. It cannot be {type(SPtrim)}."
)
return None
if not isinstance(SPres, (float, int)):
logging.error(
f"The type of the `SPres` arg must be a float or int. It cannot be {type(SPres)}."
)
return None

if not (isinstance(SPmin, float) or isinstance(SPmin, int)):
if not isinstance(SPmin, (float, int)):
logging.error(
f"The type of the `SPmin` arg must be a float or int. It cannot be {type(SPmin)}."
)
return None
if not isinstance(SPmax, (float, int)):
logging.error(
f"The type of the `SPmin` arg must be a float. It cannot be {type(SPmin)}."
f"The type of the `SPmax` arg must be a float or int. It cannot be {type(SPmax)}."
)
return None
if not (isinstance(SPmax, float) or isinstance(SPmax, int)):

if not isinstance(SPres_max, (float, int)):
logging.error(
f"The type of the `SPmax` arg must be a float. It cannot be {type(SPmax)}."
f"The type of the `SPres_max` arg must be a float or int. It cannot be {type(SPres_max)}."
)
return None

if not (isinstance(SPres_max, float) or isinstance(SPres_max, int)):
if not isinstance(tol, (float, int)):
logging.error(
f"The type of the `SPres_max` arg must be a float. It cannot be {type(SPres_max)}."
f"The type of the `tol` arg must be a float or int. It cannot be {type(tol)}."
)
return None

if not (isinstance(tol, float) and isinstance(tol, int)):
# check if the `controller_type` is either `direct_acting` or `reverse_acting`
if controller_type not in ["direct_acting", "reverse_acting"]:
logging.error(
f"The type of the `tol` arg must be a float. It cannot be {type(tol)}."
f"`controller_type` arg must be either `direct_acting` or `reverse_acting`. It can't be `{controller_type}`."
)
return None

# create "result" column
result = pd.DataFrame(columns=["result"])
# create "result" dataframe
result = pd.DataFrame(columns=["result"], index=df["Date/Time"])
result.drop(result.index[0], inplace=True)

# start the T&R logic verification
actual_start_time = df.at[0, "Date/Time"] + pd.Timedelta(minutes=Td)
for idx, row in df.iterrows():
if idx > 0 and row["Date/Time"] >= df.at[0, "Date/Time"] + pd.Timedelta(
minutes=Td
):
current_time = row["Date/Time"]
if idx > 0 and current_time >= actual_start_time:
prev_row = df.loc[idx - 1]
# when the number of ignored requests is greater than or equal to the number of requests
if row["number_of_requests"] <= I:
if row["number_of_requests"] <= ignored_requests:
if controller_type == "direct_acting":
if (
row[setpoint_name] <= prev_row[setpoint_name] - SPtrim + tol
and row[setpoint_name] >= SPmin
row["setpoint"] <= prev_row["setpoint"] - SPtrim + tol
and row["setpoint"] >= SPmin
):
result.loc[idx, "result"] = True
result.loc[current_time, "result"] = True
else:
result.loc[idx, "result"] = False
result.loc[current_time, "result"] = False

elif controller_type == "reverse_acting":
if (
row[setpoint_name] <= prev_row[setpoint_name] + SPtrim - tol
and row[setpoint_name] <= SPmax
row["setpoint"] <= prev_row["setpoint"] + SPtrim - tol
and row["setpoint"] <= SPmax
):
result.loc[idx, "result"] = True
result.loc[current_time, "result"] = True
else:
result.loc[idx, "result"] = False
result.loc[current_time, "result"] = False

else:
trim_amount = (row["number_of_requests"] - I) * SPres
trim_amount = (row["number_of_requests"] - ignored_requests) * SPres
if abs(trim_amount) > abs(SPres_max):
delta = SPres_max
else:
delta = trim_amount

if controller_type == "direct_acting":
if (
row[setpoint_name] >= prev_row[setpoint_name] + delta - tol
and row[setpoint_name] <= SPmax
row["setpoint"] >= prev_row["setpoint"] + delta - tol
and row["setpoint"] <= SPmax
):
result.loc[idx, "result"] = True
result.loc[current_time, "result"] = True
else:
result.loc[idx, "result"] = False
result.loc[current_time, "result"] = False

elif controller_type == "reverse_acting":
if (
row[setpoint_name] >= prev_row[setpoint_name] - delta + tol
and row[setpoint_name] >= SPmin
row["setpoint"] >= prev_row["setpoint"] - delta + tol
and row["setpoint"] >= SPmin
):
result.loc[idx, "result"] = True
result.loc[current_time, "result"] = True
else:
result.loc[idx, "result"] = False
result.loc[current_time, "result"] = False

return result
187 changes: 181 additions & 6 deletions tests/test_trim_respond_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,193 @@
timestamps = pd.date_range(start=start_date, end=end_date, freq="2T")
data["Date/Time"] = timestamps
# fmt: off
data["static_setpoint"] = [0.50, 0.46, 0.42, 0.48,0.60, 0.75, 0.81, 0.77, 0.73, 0.69, 0.65, 0.61, 0.57, 0.53, 0.49, 0.45, 0.41, 0.37, 0.33, 0.29, 0.25, 0.21, 0.36, 0.51, 0.66, 0.81, 0.77, 0.73, 0.85, 0.81]
# The below data is from Figure 5.1.14.4 in the ASHRAE G36-2021
data["setpoint"] = [0.50, 0.46, 0.42, 0.48,0.60, 0.75, 0.81, 0.77, 0.73, 0.69, 0.65, 0.61, 0.57, 0.53, 0.49, 0.45, 0.41, 0.37, 0.33, 0.29, 0.25, 0.21, 0.36, 0.51, 0.66, 0.81, 0.77, 0.73, 0.85, 0.81]
data["number_of_requests"] = [0, 1, 2, 3, 4, 6, 3, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 0, 1, 2, 6, 6, 5, 5, 2, 2, 4, 2]
# fmt: on


class TestTrimRespond(unittest.TestCase):
def test_direct_acting(self):
result = TrimRespondLogic(
def test_check_args_type(self):
"""Test whether arguments' type is correct."""

# check `Td`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td="0",
ignored_requests=2,
SPtrim=-0.04,
SPres=0.06,
SPmin=0.15,
SPmax=1.5,
SPres_max=0.15,
tol=0.01,
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `Td` arg must be a float or int. It cannot be <class 'str'>.",
logobs.output[0],
)

# check `I`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests="2",
SPtrim=-0.04,
SPres=0.06,
SPmin=0.15,
SPmax=1.5,
SPres_max=0.15,
tol=0.01,
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `ignored_requests` arg must be an int. It cannot be <class 'str'>.",
logobs.output[0],
)
# check `SPtrim`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests=2,
SPtrim="-0.04",
SPres=0.06,
SPmin=0.15,
SPmax=1.5,
SPres_max=0.15,
tol=0.01,
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `SPtrim` arg must be a float or int. It cannot be <class 'str'>.",
logobs.output[0],
)

# check `SPres`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests=2,
SPtrim=-0.04,
SPres="0.06",
SPmin=0.15,
SPmax=1.5,
SPres_max=0.15,
tol=0.01,
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `SPres` arg must be a float or int. It cannot be <class 'str'>.",
logobs.output[0],
)

# check `SPmin`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests=2,
SPtrim=-0.04,
SPres=0.06,
SPmin="0.15",
SPmax=1.5,
SPres_max=0.15,
tol=0.01,
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `SPmin` arg must be a float or int. It cannot be <class 'str'>.",
logobs.output[0],
)

# check `SPmax`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests=2,
SPtrim=-0.04,
SPres=0.06,
SPmin=0.15,
SPmax="1.5",
SPres_max=0.15,
tol=0.01,
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `SPmax` arg must be a float or int. It cannot be <class 'str'>.",
logobs.output[0],
)

# check `SPres_max`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests=2,
SPtrim=-0.04,
SPres=0.06,
SPmin=0.15,
SPmax=1.5,
SPres_max="0.15",
tol=0.01,
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `SPres_max` arg must be a float or int. It cannot be <class 'str'>.",
logobs.output[0],
)

# check `tol`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests=2,
SPtrim=-0.04,
SPres=0.06,
SPmin=0.15,
SPmax=1.5,
SPres_max=0.15,
tol="0.01",
controller_type="direct_acting",
)
self.assertEqual(
"ERROR:root:The type of the `tol` arg must be a float or int. It cannot be <class 'str'>.",
logobs.output[0],
)

# check `controller_type`
with self.assertLogs() as logobs:
TrimRespondLogic(
data,
Td=0,
ignored_requests=2,
SPtrim=-0.04,
SPres=0.06,
SPmin=0.15,
SPmax=1.5,
SPres_max=0.15,
tol=0.01,
controller_type="wrong_value",
)
self.assertEqual(
"ERROR:root:`controller_type` arg must be either `direct_acting` or `reverse_acting`. It can't be `wrong_value`.",
logobs.output[0],
)

def test_TR_logic_verification(self):
"""test whether the T&R logic was implemented correctly."""

tr_obj = TrimRespondLogic(
data,
setpoint_name="static_setpoint",
Td=0,
I=2,
ignored_requests=2,
SPtrim=-0.04,
SPres=0.06,
SPmin=0.15,
Expand All @@ -34,7 +209,7 @@ def test_direct_acting(self):
tol=0.01,
controller_type="direct_acting",
)
self.assertTrue(all(result))
self.assertTrue(all(tr_obj))


if __name__ == "__main__":
Expand Down

0 comments on commit 52c83cf

Please sign in to comment.