From 26f83bdcb2d88b6743ce65c239c455e4361a089b Mon Sep 17 00:00:00 2001 From: Ben Bartling Date: Sun, 25 Aug 2024 09:32:08 -0500 Subject: [PATCH] ahu rules getting err exception handling make over. need to finish ipynb to run individual rulse then publish new version to pypi --- README.md | 3 +- .../ahu_individual_faults.ipynb | 126 ++++++++++++------ .../faults/fault_condition_eight.py | 40 +++++- .../faults/fault_condition_eleven.py | 40 +++++- .../faults/fault_condition_fifteen.py | 40 +++++- .../faults/fault_condition_five.py | 40 +++++- .../faults/fault_condition_four.py | 36 ++++- .../faults/fault_condition_fourteen.py | 40 +++++- .../faults/fault_condition_nine.py | 40 +++++- .../faults/fault_condition_one.py | 43 +++++- .../faults/fault_condition_seven.py | 40 +++++- .../faults/fault_condition_six.py | 41 +++++- .../faults/fault_condition_ten.py | 42 +++++- .../faults/fault_condition_thirteen.py | 40 +++++- .../faults/fault_condition_three.py | 34 ++++- .../faults/fault_condition_twelve.py | 43 +++++- .../faults/fault_condition_two.py | 34 ++++- setup.py | 2 +- 18 files changed, 644 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 1a655d6..b128de8 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,8 @@ These are some basic project goals to make this into an interactive FDD applicat - [ ] make `energy_efficiency` faults, IPython reports, and examples to `optimize` in reducing energy consumption. - [ ] make `metering`, faults, IPython reports, and examples to possibly model utility metering data. - [ ] create SQL example to read data from time series db and write back to SQL to then read faults in Grafana. - - [ ] other? + - [ ] other? Certainly! As ChatGPT would eagerly say! -Certainly! Here's a revised version of your contribution guidelines: ## Contribute diff --git a/examples/csv_data_source/ahu_individual_faults.ipynb b/examples/csv_data_source/ahu_individual_faults.ipynb index e0f9042..c015b11 100644 --- a/examples/csv_data_source/ahu_individual_faults.ipynb +++ b/examples/csv_data_source/ahu_individual_faults.ipynb @@ -7,8 +7,8 @@ "## CSV file import for Variable Air Volume (VAV) Air Handling Unit (AHU) Tutorial \n", "* Run all faults individually\n", "* Future versions may require BRICK model\n", - "* updated 8/24/24\n", - "* dataset is available here: https://drive.google.com/file/d/1LEZxmoi7ekF8sOmMbNa-f3MPoEF6-B_i/view?usp=drive_link\n", + "* updated 8/25/24\n", + "* dataset is available here: https://drive.google.com/file/d/1QiBR8oJHSS7a8q__ip5Gm1rA2cO9MQ2M/view?usp=drive_link\n", "\n", "1. Install py package from PyPI" ] @@ -369,9 +369,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This code snip below tries to find the outside air damper minimum position which is a required input. It is also noticible in the BAS screenshots above as a `Economizer Minimum Air Damper` for indoor air ventilation requirements. This value is set during building startup by the Testing, Adjusting, and Balancing (TAB) contractor on the BAS (and hopefully not modified by building operators) where TAB uses calibrated flow measurement tools to verify and set that the AHU provides ventilation air volume requirements that the engineer designed in non-economizer AHU operating states (OS).\n", - "\n", - "The Pandas logic attempts to locate when the AHU is operating in cold weather based on the fan speed greater than 15% and the outside air colder than 10°F where then the AHU SHOULD be operating in a min OA mode where then throught the data we should see that 20% value in the 75th percentile. Some data could be capture when the AHU is starting or stopping which is probably why the mean looks sort of funny. Further down we need that `20%` value converted to a `0.2` float for an input." + "This code snippet attempts to determine the minimum position of the outside air damper, which is a critical input for the analysis. It’s important to note that this AHU includes an Energy Recovery Ventilator (ERV), which may impact the accuracy of this determination. AHUs equipped with ERVs might not have a standard 'minimum outside air' position. Additionally, when using rule-based FDD (Fault Detection and Diagnostics) on systems with ERVs, there is an increased risk of encountering false positives, particularly when the mixing temperatures are influenced by the energy recovery wheel." ] }, { @@ -534,6 +532,15 @@ " print(df.max(numeric_only=True))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This AHU, as observed in the screenshot, features DX cooling controlled by an analog signal, likely indicating a variable-capacity scroll compressor rather than a binary staged compressor. Open-fdd faults, when calculating operating modes, require a cooling signal to identify when the AHU is in a cooling mode. The code snippet below calculates a `cooling_signal` as a Pandas series, with values ranging from 0.0 to 1.0 to represent the percentage command of the analog output.\n", + "\n", + "The calculations might seem unconventional, but they are designed to provide the rule-based logic with an appropriate 'mode' for the AHU based on the DX cooling signals. This calculation ensures that when the DX cooling is active, the AHU is correctly identified as being in a cooling mode. Additionally, some faults check whether the cooling signal is at 100% but there's insufficient drop in the AHU supply air temperature to flag a fault. This calculation ensures that the `cooling_signal` will approach 1.0 or 100% when the DX cooling is fully engaged but not achieving the setpoint." + ] + }, { "cell_type": "code", "execution_count": 10, @@ -584,10 +591,9 @@ "As seen above printing the max values, please check columns that represent float values ranging from 0 to 100.0 for the control system's analog outputs. Open-fdd expects these values to be in the range of 0 to 1.0 to represent percentages. Some control system data is provided as 0 to 100.0, while others are in the 0 to 1.0 range. If you pass a 0 to 100.0 value for the control system's analog outputs, the check_analog_pct method will flag this as a `ValueError`. This method checks the data type and range of these values, raising an error if the maximum value exceeds 1.0, ensuring that the data conforms to the expected format. Under the hood logic monitors analog outputs to know the operating state (OS) of the AHU based on heating, cooling, and economizer percent commands as well as supply fan speed reference to know when the AHU is running. ASHRAE and NIST use operating states 0, 1, 2, 3 to represent AHU in a heating mode, economizer, economizer plus mechanical cooling, and mechanical cooling only modes.\n", "\n", "```python\n", - "Sa_FanSpeed 100.00\n", - "CW_Valve 100.00\n", - "HW_Valve 100.00\n", - "Ma_Dampers 100.00\n", + "SA_FanVFD 100.00\n", + "OA_RA_Damper 100.00\n", + "SA_FanSpeed 80.25\n", "```" ] }, @@ -811,14 +817,9 @@ } ], "source": [ - "'''\n", - "SA_FanVFD 100.00\n", - "OA_RA_Damper 100.00\n", - "SA_FanSpeed 80.25\n", - "\n", - "'''\n", + "# convert only the AHU data\n", "\n", - "# this data has floats between 0.0 and 100.0 so we need to convert to 0.0 and 1.0 ranges\n", + "# the floats between 0.0 and 100.0 so we need to convert to 0.0 and 1.0 ranges\n", "percentage_columns = [\n", " [\"SA_FanVFD\"],\n", " [\"OA_RA_Damper\"],\n", @@ -1206,7 +1207,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Required columns for FaultConditionOne: SaStatic, SA_FanSpeed, Static_SP\n", + "fc1_flag = 1 if (DSP < DPSP - εDSP) and (VFDSPD >= VFDSPD_max - εVFDSPD) for N consecutive values else 0 \n", + "Fault Condition 1: Duct static too low at fan at full speed \n", + "Required inputs are the duct static pressure, setpoint, and supply fan VFD speed \n", + "Your config dictionary is mapped as: SaStatic, SA_FanSpeed, Static_SP\n", "FC1 Fault Sum: 0\n" ] } @@ -1221,7 +1225,7 @@ "print(fc1_required_columns)\n", "\n", "# Apply the fault condition to the DataFrame\n", - "df_fc1 = fc1.apply(df.copy())\n", + "df_fc1 = fc1.apply(df)\n", "\n", "# Calculate the fault sum\n", "fault_counts[\"fc1_fault_sum\"] = df_fc1[\"fc1_flag\"].sum()\n", @@ -1239,7 +1243,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Required columns for FaultConditionTwo: MA_Temp, RA_Temp, OaTemp, SA_FanSpeed\n", + "fc2_flag = 1 if (MAT + εMAT < min(RAT - εRAT, OAT - εOAT)) and (VFDSPD > 0) for N consecutive values else 0 \n", + "Fault Condition 2: Mix temperature too low; should be between outside and return air \n", + "Required inputs are the mix air temperature, return air temperature, outside air temperature, and supply fan VFD speed \n", + "Your config dictionary is mapped as: MA_Temp, RA_Temp, OaTemp, SA_FanSpeed\n", "FC2 Fault Sum: 7025\n" ] } @@ -1254,7 +1261,7 @@ "print(fc2_required_columns)\n", "\n", "# Apply the fault condition to the DataFrame\n", - "df_fc2 = fc2.apply(df.copy())\n", + "df_fc2 = fc2.apply(df)\n", "\n", "# Calculate the fault sum\n", "fault_counts[\"fc2_fault_sum\"] = df_fc2[\"fc2_flag\"].sum()\n", @@ -1272,7 +1279,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Required columns for FaultConditionThree: MA_Temp, RA_Temp, OaTemp, SA_FanSpeed\n", + "fc3_flag = 1 if (MAT - εMAT > max(RAT + εRAT, OAT + εOAT)) and (VFDSPD > 0) for N consecutive values else 0 \n", + "Fault Condition 3: Mix temperature too high; should be between outside and return air \n", + "Required inputs are the mix air temperature, return air temperature, outside air temperature, and supply fan VFD speed \n", + "Your config dictionary is mapped as: MA_Temp, RA_Temp, OaTemp, SA_FanSpeed\n", "FC3 Fault Sum: 0\n" ] } @@ -1287,7 +1297,7 @@ "print(fc3_required_columns)\n", "\n", "# Apply the fault condition to the DataFrame\n", - "df_fc3 = fc3.apply(df.copy())\n", + "df_fc3 = fc3.apply(df)\n", "\n", "# Calculate the fault sum\n", "fault_counts[\"fc3_fault_sum\"] = df_fc3[\"fc3_flag\"].sum()\n", @@ -1296,6 +1306,14 @@ "print(f\"FC3 Fault Sum: {fault_counts['fc3_fault_sum']}\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fault conditon 4\n", + "This fault condition involves resampling the data to obtain hourly sums of the calculated AHU operating states. It's important to keep this resampled dataframe separate, as merging it back into the original dataframe would be problematic due to the differences introduced by the resampling process." + ] + }, { "cell_type": "code", "execution_count": 18, @@ -1305,7 +1323,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Required columns for FaultConditionFour: OA_RA_Damper, SA_FanSpeed, cooling_signal\n", + "fc4_flag = 1 if excessive mode changes (> δOS_max) occur within an hour across heating, econ, econ+mech, mech clg, and min OA modes \n", + "Fault Condition 4: Excessive AHU operating state changes detected (hunting behavior) \n", + "Required inputs are the economizer signal, supply fan VFD speed, and optionally heating and cooling signals \n", + "Your config dictionary is mapped as: OA_RA_Damper, SA_FanSpeed, cooling_signal\n", "==================================================\n", "Warning: The program is in FC4 and resampling the data\n", "to compute AHU OS state changes per hour\n", @@ -1339,6 +1360,43 @@ "cell_type": "code", "execution_count": 19, "metadata": {}, + "outputs": [ + { + "ename": "MissingColumnError", + "evalue": "One or more required columns are missing or None: ['MA_Temp', 'HC1_DaTemp', None, 'SA_FanSpeed']", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mMissingColumnError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[19], line 3\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mopen_fdd\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mair_handling_unit\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfaults\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfault_condition_five\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m FaultConditionFive\n\u001b[1;32m----> 3\u001b[0m fc5 \u001b[38;5;241m=\u001b[39m \u001b[43mFaultConditionFive\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig_dict\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 5\u001b[0m fc5_required_columns \u001b[38;5;241m=\u001b[39m fc5\u001b[38;5;241m.\u001b[39mget_required_columns()\n\u001b[0;32m 7\u001b[0m \u001b[38;5;28mprint\u001b[39m(fc5_required_columns)\n", + "File \u001b[1;32mc:\\Users\\bbartling\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\open_fdd\\air_handling_unit\\faults\\fault_condition_five.py:54\u001b[0m, in \u001b[0;36mFaultConditionFive.__init__\u001b[1;34m(self, dict_)\u001b[0m\n\u001b[0;32m 52\u001b[0m \u001b[38;5;66;03m# Check if any of the required columns are None\u001b[39;00m\n\u001b[0;32m 53\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28many\u001b[39m(col \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01mfor\u001b[39;00m col \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrequired_columns):\n\u001b[1;32m---> 54\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m MissingColumnError(\n\u001b[0;32m 55\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mOne or more required columns are missing or None: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrequired_columns\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 56\u001b[0m )\n\u001b[0;32m 58\u001b[0m \u001b[38;5;66;03m# Ensure all required columns are strings\u001b[39;00m\n\u001b[0;32m 59\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrequired_columns \u001b[38;5;241m=\u001b[39m [\u001b[38;5;28mstr\u001b[39m(col) \u001b[38;5;28;01mfor\u001b[39;00m col \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrequired_columns]\n", + "\u001b[1;31mMissingColumnError\u001b[0m: One or more required columns are missing or None: ['MA_Temp', 'HC1_DaTemp', None, 'SA_FanSpeed']" + ] + } + ], + "source": [ + "from open_fdd.air_handling_unit.faults.fault_condition_five import FaultConditionFive\n", + "\n", + "fc5 = FaultConditionFive(config_dict)\n", + "\n", + "fc5_required_columns = fc5.get_required_columns()\n", + "\n", + "print(fc5_required_columns)\n", + "\n", + "# Apply the fault condition to the DataFrame\n", + "df_fc5 = fc5.apply(df.copy())\n", + "\n", + "# Calculate the fault sum\n", + "fault_counts[\"fc5_fault_sum\"] = df_fc5[\"fc5_flag\"].sum()\n", + "\n", + "# Print the fault sum\n", + "print(f\"FC5 Fault Sum: {fault_counts['fc5_fault_sum']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1359,7 +1417,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -1391,28 +1449,14 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, "outputs": [ - { - "ename": "ValueError", - "evalue": "zero-size array to reduction operation fmin which has no identity", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[22], line 27\u001b[0m\n\u001b[0;32m 25\u001b[0m \u001b[38;5;66;03m# Plot heatmap using seaborn\u001b[39;00m\n\u001b[0;32m 26\u001b[0m plt\u001b[38;5;241m.\u001b[39mfigure(figsize\u001b[38;5;241m=\u001b[39m(\u001b[38;5;241m14\u001b[39m, \u001b[38;5;241m8\u001b[39m))\n\u001b[1;32m---> 27\u001b[0m \u001b[43msns\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mheatmap\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdf_faults_t\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcmap\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcmap\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcbar\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlinewidths\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m0.5\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[0;32m 29\u001b[0m plt\u001b[38;5;241m.\u001b[39mtitle(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mHeatmap of Fault Conditions Over Time\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m 30\u001b[0m plt\u001b[38;5;241m.\u001b[39mxlabel(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mDate\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", - "File \u001b[1;32mc:\\Users\\bbartling\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\seaborn\\matrix.py:446\u001b[0m, in \u001b[0;36mheatmap\u001b[1;34m(data, vmin, vmax, cmap, center, robust, annot, fmt, annot_kws, linewidths, linecolor, cbar, cbar_kws, cbar_ax, square, xticklabels, yticklabels, mask, ax, **kwargs)\u001b[0m\n\u001b[0;32m 365\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Plot rectangular data as a color-encoded matrix.\u001b[39;00m\n\u001b[0;32m 366\u001b[0m \n\u001b[0;32m 367\u001b[0m \u001b[38;5;124;03mThis is an Axes-level function and will draw the heatmap into the\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 443\u001b[0m \n\u001b[0;32m 444\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m 445\u001b[0m \u001b[38;5;66;03m# Initialize the plotter object\u001b[39;00m\n\u001b[1;32m--> 446\u001b[0m plotter \u001b[38;5;241m=\u001b[39m \u001b[43m_HeatMapper\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvmin\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvmax\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcmap\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcenter\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrobust\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mannot\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfmt\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 447\u001b[0m \u001b[43m \u001b[49m\u001b[43mannot_kws\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcbar\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcbar_kws\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mxticklabels\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 448\u001b[0m \u001b[43m \u001b[49m\u001b[43myticklabels\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmask\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 450\u001b[0m \u001b[38;5;66;03m# Add the pcolormesh kwargs here\u001b[39;00m\n\u001b[0;32m 451\u001b[0m kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mlinewidths\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m linewidths\n", - "File \u001b[1;32mc:\\Users\\bbartling\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\seaborn\\matrix.py:163\u001b[0m, in \u001b[0;36m_HeatMapper.__init__\u001b[1;34m(self, data, vmin, vmax, cmap, center, robust, annot, fmt, annot_kws, cbar, cbar_kws, xticklabels, yticklabels, mask)\u001b[0m\n\u001b[0;32m 160\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mylabel \u001b[38;5;241m=\u001b[39m ylabel \u001b[38;5;28;01mif\u001b[39;00m ylabel \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 162\u001b[0m \u001b[38;5;66;03m# Determine good default values for the colormapping\u001b[39;00m\n\u001b[1;32m--> 163\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_determine_cmap_params\u001b[49m\u001b[43m(\u001b[49m\u001b[43mplot_data\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvmin\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvmax\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 164\u001b[0m \u001b[43m \u001b[49m\u001b[43mcmap\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcenter\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrobust\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 166\u001b[0m \u001b[38;5;66;03m# Sort out the annotations\u001b[39;00m\n\u001b[0;32m 167\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m annot \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mor\u001b[39;00m annot \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m:\n", - "File \u001b[1;32mc:\\Users\\bbartling\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\seaborn\\matrix.py:202\u001b[0m, in \u001b[0;36m_HeatMapper._determine_cmap_params\u001b[1;34m(self, plot_data, vmin, vmax, cmap, center, robust)\u001b[0m\n\u001b[0;32m 200\u001b[0m vmin \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mnanpercentile(calc_data, \u001b[38;5;241m2\u001b[39m)\n\u001b[0;32m 201\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m--> 202\u001b[0m vmin \u001b[38;5;241m=\u001b[39m \u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnanmin\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcalc_data\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 203\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m vmax \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m 204\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m robust:\n", - "File \u001b[1;32m~\\AppData\\Roaming\\Python\\Python312\\site-packages\\numpy\\lib\\_nanfunctions_impl.py:360\u001b[0m, in \u001b[0;36mnanmin\u001b[1;34m(a, axis, out, keepdims, initial, where)\u001b[0m\n\u001b[0;32m 355\u001b[0m kwargs[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mwhere\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m where\n\u001b[0;32m 357\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(a) \u001b[38;5;129;01mis\u001b[39;00m np\u001b[38;5;241m.\u001b[39mndarray \u001b[38;5;129;01mand\u001b[39;00m a\u001b[38;5;241m.\u001b[39mdtype \u001b[38;5;241m!=\u001b[39m np\u001b[38;5;241m.\u001b[39mobject_:\n\u001b[0;32m 358\u001b[0m \u001b[38;5;66;03m# Fast, but not safe for subclasses of ndarray, or object arrays,\u001b[39;00m\n\u001b[0;32m 359\u001b[0m \u001b[38;5;66;03m# which do not implement isnan (gh-9009), or fmin correctly (gh-8975)\u001b[39;00m\n\u001b[1;32m--> 360\u001b[0m res \u001b[38;5;241m=\u001b[39m \u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfmin\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mreduce\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mout\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 361\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(res)\u001b[38;5;241m.\u001b[39many():\n\u001b[0;32m 362\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAll-NaN slice encountered\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;167;01mRuntimeWarning\u001b[39;00m,\n\u001b[0;32m 363\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m2\u001b[39m)\n", - "\u001b[1;31mValueError\u001b[0m: zero-size array to reduction operation fmin which has no identity" - ] - }, { "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABBwAAAOZCAYAAAC9QTPgAAAAP3RFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMS5wb3N0MSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8kixA/AAAACXBIWXMAAA9hAAAPYQGoP6dpAADyfUlEQVR4nOzdeXgUVfb/8U8nIQkQkxAkJKiEnbAKgkAYFBeGgIwDgrKIAooiKKKgonwFcRllUUEUFBc2Rx1xQR0RQUTQkVVAkWFRFBAXEvaERRKS3N8f/NJDmwR7KapS4f16nn7GVPfpOudUVU+4uXXbY4wxAgAAAAAAsFCY0wkAAAAAAICyhwEHAAAAAABgOQYcAAAAAACA5RhwAAAAAAAAlmPAAQAAAAAAWI4BBwAAAAAAYDkGHAAAAAAAgOUYcAAAAAAAAJZjwAEAAAAAAFiOAQcAgKOefPJJ1apVS+Hh4WrWrJnT6fjweDx6+OGHnU7DcgMGDFCNGjV8tgVSa40aNTRgwADL88LpFXfcAAAozRhwAIBTzJ49Wx6PR2vXri32+csuu0yNGzc+ozksWLCgTP4jtziffPKJRo4cqb/85S+aNWuWnnjiiRJfO2DAAHk8nmIfCxcutCXfFStW6OGHH9ahQ4cCilu2bJm6d++upKQkRUZGKjExUVdffbXmzZt3ZhK1QLC1lhbz589Xp06dVLlyZUVHR6tevXq69957tX//fqdT81HSOf3Hx7Jly5xOFQCAgEU4nQAAwNeCBQs0bdq0s2LQ4bPPPlNYWJhmzJihyMjIP319VFSUXnnllSLbL7zwwjORXhErVqzQI488ogEDBig+Pt6vmLFjx+rRRx9V3bp1ddtttyklJUX79+/XggUL1KNHD73++uu6/vrrz2zifvj9998VEfG/XwtOV+t3332nsLDS+zeLe++9V08//bQuvPBC3X///UpISND69es1depUvfnmm1qyZInq16/vdJqSpH/+858+P7/66qtavHhxke0NGjTQyy+/rIKCAjvTAwAgJAw4AAAcs2fPHpUvX96vwQZJioiI0A033HCGs7LOO++8o0cffVTXXnut3njjDZUrV8773H333adFixbpxIkTDmb4P9HR0X6/Nioq6gxmEpp//etfevrpp9WrVy+9/vrrCg8P9z43YMAAXX755bruuuu0fv16nwGWM+3o0aOqWLFike1/PJ9XrVqlxYsXu+o8BwCgJKX3zxMA4CKvvfaaWrRoofLlyyshIUG9e/fWzz//7POa//znP7ruuutUvXp1RUVF6YILLtDw4cP1+++/e18zYMAATZs2TZLvVGtJ2rlzpzwej5566ilNmzZNtWrVUoUKFdSxY0f9/PPPMsboscce0/nnn6/y5cura9euOnDggE8OH3zwgbp06aJq1aopKipKtWvX1mOPPab8/Hyf1xXeOrJu3Tq1bdtW5cuXV82aNTV9+nS/+pGXl6fHHntMtWvXVlRUlGrUqKH/+7//U05Ojvc1Ho9Hs2bN0tGjR711zp492++e/5E//S2s7bLLLisS/2f3xz/88MO67777JEk1a9b05rxz584SY8aMGaOEhATNnDnTZ7ChUHp6uv72t795f96zZ48GDhyoqlWrKjo6WhdeeKHmzJnjE3PqefDSSy95e3zxxRfrq6++KrKP999/X40bN1Z0dLQaN26s9957r9hcT13D4c9qLW4Nh+3bt+u6665TQkKCKlSooDZt2uijjz7yec2yZcvk8Xj01ltv6fHHH9f555+v6OhoXXnllfrhhx98Xrtt2zb16NFDSUlJio6O1vnnn6/evXsrKyur2PwLPfLII6pUqZJeeukln8EGSWrVqpXuv/9+bdy4Ue+8844kaejQoYqJidGxY8eKvFefPn2UlJTkc318/PHHuuSSS1SxYkWdc8456tKlizZt2uQTN2DAAMXExOjHH3/UVVddpXPOOUd9+/Y9bd7++OM5asVngr81AQAQDGY4AEAxsrKytG/fviLbi/tr9OOPP64xY8aoZ8+euuWWW7R3714999xzuvTSS/X11197p6O//fbbOnbsmIYMGaLKlStrzZo1eu655/TLL7/o7bffliTddttt+u2334qdUl3o9ddfV25uru68804dOHBAEydOVM+ePXXFFVdo2bJluv/++/XDDz/oueee07333quZM2d6Y2fPnq2YmBiNGDFCMTEx+uyzz/TQQw8pOztbTz75pM9+Dh48qKuuuko9e/ZUnz599NZbb2nIkCGKjIzUzTfffNr+3XLLLZozZ46uvfZa3XPPPVq9erXGjRunLVu2eP/B+89//lMvvfSS1qxZ471Nom3btqd9X0lFjku5cuUUFxfnV39D0b17d33//ff617/+pcmTJ+vcc8+VJFWpUqXY12/btk1bt27VzTffrHPOOedP3//333/XZZddph9++EFDhw5VzZo19fbbb2vAgAE6dOiQ7rrrLp/Xv/HGGzp8+LBuu+02eTweTZw4Ud27d9f27du9gxuffPKJevTooYYNG2rcuHHav3+/brrpJp1//vmW1pqZmam2bdvq2LFjGjZsmCpXrqw5c+bo73//u9555x1dc801Pq8fP368wsLCdO+99yorK0sTJ05U3759tXr1aklSbm6u0tPTlZOTozvvvFNJSUn69ddfNX/+fB06dEhxcXEl9vy7777TgAEDFBsbW+xr+vXrp7Fjx2r+/Pnq3bu3evXqpWnTpumjjz7Sdddd533dsWPH9OGHH2rAgAHegYt//vOf6t+/v9LT0zVhwgQdO3ZML7zwgtq1a6evv/7aZzAgLy9P6enpateunZ566ilVqFDhtD0PRSifCYHUBABAwAwAwGvWrFlG0mkfjRo18r5+586dJjw83Dz++OM+77Nx40YTERHhs/3YsWNF9jdu3Djj8XjMTz/95N12xx13mOI+nnfs2GEkmSpVqphDhw55t48aNcpIMhdeeKE5ceKEd3ufPn1MZGSkOX78+GlzuO2220yFChV8Xte+fXsjyTz99NPebTk5OaZZs2YmMTHR5ObmFm3e//fNN98YSeaWW27x2X7vvfcaSeazzz7zbuvfv7+pWLFiie91qv79+xd7PNq3b19ibcX1t3379t6YP75/SkqKzzZJZuzYsd6fn3zySSPJ7Nix40/z/eCDD4wkM3nyZD+qM+aZZ54xksxrr73m3Zabm2vS0tJMTEyMyc7ONsb87zyoXLmyOXDgQJH9ffjhh95tzZo1M8nJyT7nyyeffGIkhVRrSkqK6d+/v/fnu+++20gy//nPf7zbDh8+bGrWrGlq1Khh8vPzjTHGLF261EgyDRo0MDk5Od7XTpkyxUgyGzduNMYY8/XXXxtJ5u233/ajc//z/vvv+9Xz2NhYc9FFFxljjCkoKDDnnXee6dGjh89r3nrrLSPJfPHFF9564uPjza233urzuoyMDBMXF+ezvfBcfeCBBwLK35iSr//C9z31uIX6mRBITQAABINbKgCgGNOmTdPixYuLPJo2berzunnz5qmgoEA9e/bUvn37vI+kpCTVrVtXS5cu9b62fPny3v8+evSo9u3bp7Zt28oYo6+//trv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KSnav3+/Fi5cqCVLluiNN96QJA0aNEgvvviiBgwYoHXr1qlGjRp65513tHz5cj3zzDN+Lz55qnHjxqlLly5q166dbr75Zh04cEDPPfecGjVq5LNAYqi1PvDAA/rXv/6lzp07a9iwYUpISNCcOXO0Y8cOvfvuuwHfWvDZZ59p6NChuu6661SvXj3l5eXpn//8p8LDw4sMFPxR37599dVXX2nKlCnavHmz+vbtq0qVKmn9+vWaOXOmKleurHfeeafIOXzRRRepTp06evDBB5WTk+NzO4V0cj2DF154QTfeeKMuuugi9e7dW1WqVNGuXbv00Ucf6S9/+YumTp0aUJ1OK4s1AQBKGdu/FwMASrHCr8X86quvin2+ffv2Pl+LWejdd9817dq1MxUrVjQVK1Y0qamp5o477jDfffed9zWbN282HTp0MDExMebcc881t956q9mwYYORZGbNmuV9XV5enrnzzjtNlSpVjMfj8X5FXuFX4D355JM++y78qsE/foVgcbUsX77ctGnTxpQvX95Uq1bNjBw50ixatMhIMkuXLi1S59q1a01aWpqJjo42KSkpZurUqX718cSJE+aRRx4xNWvWNOXKlTMXXHCBGTVqlM9XbxoT+Ndinu61/vbXGGNee+01U6tWLRMZGWmaNWtmFi1a5NfXYhpjzGOPPWbOO+88ExYW5vdXZC5ZssR07drVJCYmmoiICFOlShVz9dVXmw8++MDndZmZmeamm24y5557romMjDRNmjQpkntJ50FJ+b777rumQYMGJioqyjRs2NDMmzcv5Fr/+LWYxhjz448/mmuvvdbEx8eb6Oho06pVKzN//nyf15R0rhbWVFjr9u3bzc0332xq165toqOjTUJCgrn88svNp59+WqTmkrz//vvmr3/9q6lUqZKJiooyderUMffcc4/Zu3dviTEPPvigkWTq1KlT4muWLl1q0tPTTVxcnImOjja1a9c2AwYMMGvXrvW+JpDz+o+C+VrMUD4T/K0JAIBgeIxxwepSAABbXXbZZdq3b5/++9//Op0KAAAAXIo1HAAAAAAAgOUYcAAAAAAAAJZjwAEAAAAAAFiONRwAAAAAAIDlmOEAAAAAAAAsx4ADAAAAAACwHAMOAAAAAADAchFOJwAAAAAAgBvc5vE4st8XXbr0Ypmd4ZCVlaWsrKyg/9uK97D6/ciJnMiJnMiJ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H9WoUSNJ0r59+wJNDwAAAACAMsWf2ydOFRkZqTp16kiSWrRooa+++kpTpkxRr169lJubq0OHDvnMcsjMzFRSUpKkk98kuWbNGp/3K/yCiMLX+Cvg4Zm77rpLd911l/Lz89W4cWNdeOGFPo9AtWnTRl9++aUk6aqrrtI999yjxx9/XDfffLPatGkT8PsBAAAAAID/KSgoUE5Ojlq0aKFy5cppyZIl3ue+++477dq1S2lpaZKktLQ0bdy4UXv27PG+ZvHixYqNjVXDhg0D2m/AMxzefPNNvfXWW7rqqqsCDS3WpEmTdOTIEUnSI488oiNHjmju3LmqW7cu31ABAAAAACg1nFo0MhCjRo1S586dVb16dR0+fFhvvPGGli1bpkWLFikuLk4DBw7UiBEjlJCQoNjYWN15551KS0vz/sG/Y8eOatiwoW688UZNnDhRGRkZGj16tO64446AZllIQQw4nDo1wwq1atXy/nfFihU1ffp0y94bAAAAAICzyZ49e9SvXz/t3r1bcXFxatq0qRYtWqS//vWvkqTJkycrLCxMPXr0UE5OjtLT0/X8889748PDwzV//nwNGTJEaWlpqlixovr3769HH3004FwCHnC45557NGXKFE2dOtUVozsAAAAAAFjB49CikYGYMWPGaZ+Pjo7WtGnTNG3atBJfk5KSogULFoScS8ADDl9++aWWLl2qjz/+WI0aNVK5cuV8np83b96fvkelSpX8Hqw4cOBAoCkCAAAAAACHBTzgEB8fr2uuuSaknT7zzDMhxQMAAAAAYDc3zHAoTQIecJg1a1bIO92wYYMee+wxVaxYUV988YXatm2riIiAUwEAAAAAAKVU0MMze/fu1Zdffqkvv/xSe/fuDSj2ueee834zxeWXX85tEwAAAAAAlDEBTys4evSo7rzzTr366qsqKCiQdHIVy379+um5555ThQoV/vQ9atSooWeffVYdO3aUMUYrV65UpUqVin3tpZdeGmiKAAAAAABYji9OCEzAAw4jRozQ559/rg8//FB/+ctfJJ1cSHLYsGG655579MILL/zpezz55JMaPHiwxo0bJ4/HU+KaEB6PR/n5+ad9r5ycHOXk5PhsC/S7QQEAAAAAgLUCvqXi3Xff1YwZM9S5c2fFxsYqNjZWV111lV5++WW98847fr1Ht27dlJGRoezsbBlj9N133+ngwYNFHv7cajFu3DjFxcX5PMaNGxdoWQAAAAAAnF5YmDMPlwp4hsOxY8dUtWrVItsTExN17NixgN4rJiZGS5cuVc2aNf900cjx48dr8ODBio+P99k+atQojRgxwmdbVFSUco4eDSgXAAAAAABgnYCHStLS0jR27FgdP37cu+3333/XI488orS0tIATaN++vV/fUPHEE08UO+MhKirKO9Oi8MEtFQAAAAAAOCvgGQ5TpkxRenq6zj//fF144YWSTn7NZXR0tBYtWmR5goWMMWfsvQEAAAAA+FMsGhmQgAccGjdurG3btun111/X1q1bJUl9+vRR3759Vb58ecsTBAAAAAAA7hPwgIMkVahQQbfeeqvVuQAAAAAAUHq5eAFHJ/jdrXXr1unyyy9XdnZ2keeysrJ0+eWXa8OGDZYmBwAAAAAA3MnvAYenn35aV1xxhWJjY4s8FxcXp7/+9a968sknLU0OAAAAAIBSg6/FDIjfma9evVpdu3Yt8fmrr75aK1assCSp4lxyySWsEQEAAAAAgEv4vYbDr7/+qnPOOafE52NiYrR7925LkirOggULzth7AwAAAAAAa/k9w6FKlSr67rvvSnx+69atOvfcc/3e8YkTJzRy5EjVqVNHrVq10syZM32ez8zMVHh4uN/vBwAAAADAGeXxOPNwKb8HHDp06KDHH3+82OeMMXr88cfVoUMHv3f8+OOP69VXX9XgwYPVsWNHjRgxQrfddluR9wUAAAAAAO7j9y0Vo0ePVosWLdS6dWvdc889ql+/vqSTMxuefvppff/995o9e7bfO3799df1yiuv6G9/+5skacCAAercubNuuukm72wHj4tHcgAAAAAAZYyLF3B0gt/dql27tj799FMdPXpUvXv31kUXXaSLLrpIffr00bFjx7R48WLVqVPH7x3/+uuvaty4sffnOnXqaNmyZVqxYoVuvPFG5efnB1YJAAAAAAAoNfye4SBJLVu21H//+19988032rZtm4wxqlevnpo1axbwjpOSkvTjjz+qRo0a3m3nnXeeli5dqssvv1wDBgwI+D0BAAAAAEDpENCAQ6FmzZoFNchwqiuuuEJvvPGGrrzySp/t1apV02effabLLrsspPcHAAAAAMBS3PYfkKAGHKwwZswYbd26tdjnzjvvPH3++edavHixzVkBAAAAAAArODbgkJKSopSUlBKfr1at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hZKrW3pqjP19tbunoezTLedqKMffLefq2fAZ58R1bPfvOE5c/245/k7UCJSkTKzhUFBQoMjIyBKfj4yMVEFBgWVxixYt0hdffKH69esXea5+/fp69tlnddlllxX7nsHG2l2j3XFS8L1xS09DiT18+LCqVatWYlxycrKOHj1q2f5CqTHYXN0S58TxD/Ycd6LGPXv2qEmTJiXGNmnSRPv27bMsV7trDLa+UHJ1S08l+/tqd09D2adbztVQjr9bztWz4TPOievY7t9xnLj+3XL8nagRKJHTIx5WuP76603z5s3N+vXrizy3fv1606JFC9O3b1/L4ipXrmyWLVtWYj5Lly41lStXLva5YGPtrtHuOGOC741behpK7FVXXWU6duxo9u7dW+S5vXv3mk6dOpkuXbqUihqDzdUtcU4c/2DPcSdqvOSSS0y/fv3MiRMnijyXl5dn+vXrZy699FLLcrW7xmDrCyVXt/TUGPv7andPQ9mnW87VUI6/W87Vs+Ezzonr2O7fcZy4/t1y/J2oEShJmRhwOHDggOnUqZPxeDwmISHBpKammtTUVJOQkGDCwsJM586dzcGDBy2Lu/32201KSoqZN2+eycrK8m7Pysoy8+bNMzVq1DBDhw4tNtdgY+2u0e64UHrjlp6GErtr1y7TuHFjExERYZo3b246depkOnXqZJo3b24iIiJM06ZNza5du0pFjcHm6pY4J45/sOe4EzVu2LDBJCUlmcqVK5trrrnGDB482AwePNhcc801pnLlyiY5Odls3LjRslztrjHY+kLJ1S09daKvdvfUib66padO7JPPuNJ1Hdv9O44T179bjr8TNQIl8RhjjNOzLKyyZcsWrVq1yufrW9LS0pSammppXE5Oju6++27NnDlTeXl53uljubm5ioiI0MCBAzV58mRFRUVZGmtnjU7EBdsbt/U02NiCggItWrSo2LiOHTsqLCzM8lyDjQs2V7fEhdKbYGJDOcedqPHw4cN67bXXio29/vrrFRsbW2ycW45jsPWFkqubeirZ21e7exrKPt1yroaSp5vO1bL+GefEdRxsnN01hnL9u+X4O1EjUJwyNeBgt+zsbK1du1aZmZmSTl6ILVq08OsiDCW2rAu2N/QUZR3nOAAAANykzAw45Obm6v3339fKlSt9RuLatm2rrl27lriITbBxTrC7Rnp6ZmoMJXbNmjXF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aN++fZSenu7eF1yIH3/8kbp16+b++7rrrqPS0lI6cuQIERH9z//8DxUUFPiERyLyGMeQkBCPsbzsssuovLy8jiYmJoaKioqIiGj//v3kcrncfxMRFRYWer0taGBgIJ07d879d3BwMDVp0sTj799//72OLj4+njZv3kxERF9//TXZbDbaunWre/uWLVvoiiuusMyfGY85OTm0YsUKGjNmDD3++ONUVVXl9fX/TFRUlHuOEBGVlZVRdXU1NW3alIiI2rRpQ8ePH6+jE503ROJzR7ZHIvnjqMtcFfWnoqaKTGV7JCI6e/asx985OTn01FNPUb9+/ejLL7807POPdO3alebNm0eHDx+mN954g3766Sfq27ev138bHBxMv/32m8dzmZmZtGDBArr77rvp/fff96r788U2Y2Nj6cknn6R9+/bR+vXr6eqrr6a//e1vyj3+/vvvHhe/tdlsNGfOHLrllluoV69etG/fvouqFxgYSHfddRfl5eXRgQMH6K9//SstXbqU2rZt22g9MoxXVJ/xsIJhw4ZhwoQJhtt37NgBm81W5/nffvsN1113HVq3bo17770XISEhSEhIQN++fdGqVSs0a9bM/fOHP5KUlIS3337bsN63335reNbwqaeeQvfu3XHixAnk5OTglltuwalTpwAAZ86cwV133YV+/fop9yhar/Z3X3a7HTabDf/4xz88tv/73/82/Cpmz549kZWV5fX3ftXV1cjKykJGRobXmgUFBdi5cycSEhKwdetWj+3FxcVo0qRJHV1BQQHCw8ORlpaGRx99FNOnT8f06dPx6KOPokOHDmjSpInX6xSY8WimptPpRPPmzREYGFhn/q1btw6JiYk+4fGf//wnHA4HbrnlFsyePRvLly/H8uXLMXv2bNx6660ICgrCe++9Z5lH0Xq1uFwuTJ48GXFxccjLy0NgYGC9/6P67LPP4sorr8ScOXOQm5uL9u3be3y7YOXKlUhNTTXU15Kbm4uYmBjMmzfvgjVTUlI8foe8bds2BAUFua9dsn//foSHh/uExw4dOnh8c2v37t0ea3rjxo1er3EzadIkREZG4oEHHkCrVq2Qk5OD+Ph4zJkzB3PnzkVcXJzX3/Bfd911Hl+FLi8v97gQ7scff4ykpKQ6updffhkhISHo06cPmjdvjldeeQUxMTGYMGECcnJy0KxZMzz33HOW+TPj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6y6v21esWEHjx4+nQ4cOWaK7EDt37qROnTpRTU3NJenq03bq1IlGjRrlvp3QP//5T/rLX/5Cs2fPptGjR1NZWRm1bNmSXC6XJbqrr76aZs+eTUOGDKHvv/+e2rZtS8uWLaO7777b/TrPP/887d6920MXExNDb731FvXv39+rv/z8fMrKyqKysjKhbK699to6vYrq2rVrRxMnTqTMzEwiIvryyy/p9ttvp+zsbHruuecMsxHVEZ2/Fddbb71FN954Ix0+fJiuvPJK+vDDD2nIkCFEdP4Wjo899lid23iKztX/83/+T73ZlJeX04YNG+r0Kqoj0meuqthvmMm1Pnxlv0FElJSURI888gg99NBDXnt944036OWXX6b9+/dbovuf//mfepIhOnr0KC1btqxOr6K69PR0uvHGG+mFF14gADRz5kx6/vnn6d1336UBAwbUm42oVpdMzWhFs5GdqRmtLnPVzBzXZa6ameOyc9XpvUpUJ/u9itd/w7xXMYwRfnHCITQ0lLZv304pKSletxcVFdF1111HFRUVlujMfGgQ1TZp0oR2795NrVq1cj9XUFBAt956K82cOZOGDh3qdfGL6kJDQ2nfvn0UFxfn/vvbb791X5SupKSErrnmGjp58qSHLjw8nL766itKS0vz6m/Xrl2Unp7u9T7s1157bb3Z/P7777Rv3746vYrqwsLCqKioiBITE93Pfffdd9SnTx8aNWoUjRs3zms2ojoiopCQENq/f7871/DwcPr2228pKSmJiM5fADM1NZXOnDnjoROdq4GBgdS3b1+Kjo72qjt+/DitWrWqTq+iOiJ95qrs/QaReK667DeIzs/xnTt3Gt6je+/evdSxY0f6/fffLdEFBARQx44dvV5Qluj8xQ63b99ep1dRXbNmzWj79u109dVXu59btmwZPfjgg7R8+XLq0qWLYTaiWl0yNaMVzUZ2pma0usxVM3Ncl7lqZo7LzlWn9ypRnez3Kl7/DfNexTCGqPhahdX07NkTWVlZHvdBrqW6uhpZWVnIyMiwTOdwODBw4EDcf//9Xh+33nqr4VeNRLWxsbF1bhMInL99YZMmTTBx4kRLddHR0di1a5f77/T0dPz888/uv/fs2YOmTZvW0Q0aNAj9+vWr83sv4Py1DQYMGIDBgwfX2QYAwcHBuO+++/DMM894ffzXf/2X115FdXFxcdi4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", "text/plain": [ - "
" + "
" ] }, "metadata": {}, diff --git a/open_fdd/air_handling_unit/faults/fault_condition_eight.py b/open_fdd/air_handling_unit/faults/fault_condition_eight.py index c305fea..f3869c4 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_eight.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_eight.py @@ -26,6 +26,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default should be False self.rolling_window_size = int + self.equation_string = ( + "fc8_flag = 1 if |SAT - MAT - ΔT_fan| > √(εSAT² + εMAT²) " + "in economizer mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 8: Supply air temperature and mixed air temperature should " + "be approximately equal in economizer mode \n" + ) + self.required_column_description = ( + "Required inputs are the mixed air temperature, supply air temperature, " + "economizer signal, and cooling signal \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -36,9 +50,31 @@ def __init__(self, dict_): self.cooling_sig_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionEight: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -88,4 +124,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_eleven.py b/open_fdd/air_handling_unit/faults/fault_condition_eleven.py index e28a285..e72eab9 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_eleven.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_eleven.py @@ -25,6 +25,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default False self.rolling_window_size = int + self.equation_string = ( + "fc11_flag = 1 if OAT < (SATSP - ΔT_fan - εSAT) in " + "economizer cooling mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 11: Outside air temperature too low for 100% outdoor air cooling " + "in economizer cooling mode (Economizer performance fault) \n" + ) + self.required_column_description = ( + "Required inputs are the supply air temperature setpoint, outside air temperature, " + "cooling signal, and economizer signal \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -35,9 +49,31 @@ def __init__(self, dict_): self.economizer_sig_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionEleven: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -87,4 +123,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py b/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py index b6a72a8..f0e40bd 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py @@ -28,6 +28,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default to False self.rolling_window_size = int + self.equation_string = ( + "fc15_flag = 1 if ΔT_coil >= √(εcoil_enter² + εcoil_leave²) + ΔT_fan " + "in inactive heating coil mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 15: Temperature rise across inactive heating coil " + "detected, requiring coil leaving temperature sensor \n" + ) + self.required_column_description = ( + "Required inputs are the heating coil entering temperature, heating coil leaving temperature, " + "cooling signal, heating signal, economizer signal, and supply fan VFD speed \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -40,9 +54,31 @@ def __init__(self, dict_): self.supply_vfd_speed_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionFifteen: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -113,4 +149,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_five.py b/open_fdd/air_handling_unit/faults/fault_condition_five.py index 5e27438..a574286 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_five.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_five.py @@ -26,6 +26,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default to False self.rolling_window_size = int + self.equation_string = ( + "fc5_flag = 1 if (SAT + εSAT <= MAT - εMAT + ΔT_supply_fan) and " + "(heating signal > 0) and (VFDSPD > 0) for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 5: SAT too low; should be higher than MAT in HTG MODE, " + "potential broken heating valve or mechanical issue \n" + ) + self.required_column_description = ( + "Required inputs are the mixed air temperature, supply air temperature, " + "heating signal, and supply fan VFD speed \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -36,9 +50,31 @@ def __init__(self, dict_): self.supply_vfd_speed_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionFive: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -84,4 +120,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_four.py b/open_fdd/air_handling_unit/faults/fault_condition_four.py index 8882b81..dd7c544 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_four.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_four.py @@ -26,6 +26,17 @@ def __init__(self, dict_): self.supply_vfd_speed_col = str self.troubleshoot_mode = bool # default to False + self.equation_string = ( + "fc4_flag = 1 if excessive mode changes (> δOS_max) occur " + "within an hour across heating, econ, econ+mech, mech clg, and min OA modes \n" + ) + self.description_string = "Fault Condition 4: Excessive AHU operating state changes detected (hunting behavior) \n" + self.required_column_description = ( + "Required inputs are the economizer signal, supply fan VFD speed, " + "and optionally heating and cooling signals \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns, making heating and cooling optional @@ -40,9 +51,30 @@ def __init__(self, dict_): if self.cooling_sig_col: self.required_columns.append(self.cooling_sig_col) + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionFour: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -133,4 +165,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py b/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py index 99481fc..f9512eb 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py @@ -29,6 +29,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default to False self.rolling_window_size = int + self.equation_string = ( + "fc14_flag = 1 if ΔT_coil >= √(εcoil_enter² + εcoil_leave²) + ΔT_fan " + "in inactive cooling coil mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 14: Temperature drop across inactive cooling coil " + "detected, requiring coil leaving temperature sensor \n" + ) + self.required_column_description = ( + "Required inputs are the cooling coil entering temperature, cooling coil leaving temperature, " + "cooling signal, heating signal, economizer signal, and supply fan VFD speed \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -41,9 +55,31 @@ def __init__(self, dict_): self.supply_vfd_speed_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionFourteen: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -104,4 +140,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_nine.py b/open_fdd/air_handling_unit/faults/fault_condition_nine.py index cdb4cc2..9f65e55 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_nine.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_nine.py @@ -26,6 +26,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default should be False self.rolling_window_size = int + self.equation_string = ( + "fc9_flag = 1 if OAT > (SATSP - ΔT_fan + εSAT) " + "in free cooling mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 9: Outside air temperature too high in free cooling mode " + "without additional mechanical cooling in economizer mode \n" + ) + self.required_column_description = ( + "Required inputs are the supply air temperature setpoint, outside air temperature, " + "cooling signal, and economizer signal \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -36,9 +50,31 @@ def __init__(self, dict_): self.economizer_sig_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionNine: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -89,4 +125,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_one.py b/open_fdd/air_handling_unit/faults/fault_condition_one.py index fcde3ec..b352c13 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_one.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_one.py @@ -22,6 +22,13 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default should be False self.rolling_window_size = int + self.equation_string = "fc1_flag = 1 if (DSP < DPSP - εDSP) and (VFDSPD >= VFDSPD_max - εVFDSPD) for N consecutive values else 0 \n" + self.description_string = ( + "Fault Condition 1: Duct static too low at fan at full speed \n" + ) + self.required_column_description = "Required inputs are the duct static pressure, setpoint, and supply fan VFD speed \n" + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition manually @@ -31,9 +38,31 @@ def __init__(self, dict_): self.duct_static_setpoint_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: - """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionOne: {', '.join(self.required_columns)}" + """called from IPython to print out""" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -69,13 +98,15 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: if self.troubleshoot_mode: print("Troubleshoot mode enabled - not removing helper columns") sys.stdout.flush() - del df["static_check_"] - del df["fan_check_"] - del df["combined_check"] + + # Optionally remove temporary columns + df.drop( + columns=["static_check_", "fan_check_", "combined_check"], inplace=True + ) return df except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_seven.py b/open_fdd/air_handling_unit/faults/fault_condition_seven.py index 87501a5..351df83 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_seven.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_seven.py @@ -22,6 +22,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default to False self.rolling_window_size = int + self.equation_string = ( + "fc7_flag = 1 if SAT < (SATSP - εSAT) in full heating mode " + "and VFD speed > 0 for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 7: Supply air temperature too low in full heating mode " + "with heating valve fully open \n" + ) + self.required_column_description = ( + "Required inputs are the supply air temperature, supply air temperature setpoint, " + "heating signal, and supply fan VFD speed \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -32,9 +46,31 @@ def __init__(self, dict_): self.supply_vfd_speed_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionSeven: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -75,4 +111,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_six.py b/open_fdd/air_handling_unit/faults/fault_condition_six.py index 2daed1a..a22e950 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_six.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_six.py @@ -41,6 +41,21 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default should be False self.rolling_window_size = int + self.equation_string = ( + "fc6_flag = 1 if |OA_frac_calc - OA_min| > airflow_err_thres " + "in non-economizer modes, considering htg and mech clg OS \n" + ) + self.description_string = ( + "Fault Condition 6: Issues detected with OA fraction calculation or AHU " + "not maintaining design air flow in non-economizer conditions \n" + ) + self.required_column_description = ( + "Required inputs are the supply fan air volume, mixed air temperature, " + "outside air temperature, return air temperature, and VFD speed. " + "Optional inputs include economizer signal, heating signal, and cooling signal \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -55,9 +70,31 @@ def __init__(self, dict_): self.cooling_sig_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionSix: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -141,4 +178,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_ten.py b/open_fdd/air_handling_unit/faults/fault_condition_ten.py index 79693ab..4568e92 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_ten.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_ten.py @@ -24,6 +24,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default False, self.rolling_window_size = int + self.equation_string = ( + "fc10_flag = 1 if |OAT - MAT| > √(εOAT² + εMAT²) in " + "economizer + mech cooling mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 10: Outdoor air temperature and mixed air temperature " + "should be approximately equal in economizer plus mechanical cooling mode \n" + ) + self.required_column_description = ( + "Required inputs are the outside air temperature, mixed air temperature, " + "cooling signal, and economizer signal \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -34,9 +48,31 @@ def __init__(self, dict_): self.economizer_sig_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionTen: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -60,7 +96,7 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: df["combined_check"] = ( (df["abs_mat_minus_oat"] > df["mat_oat_sqrted"]) - # verify ahu is running in OS 3 clg mode in min OA + # verify AHU is running in OS 3 clg mode in min OA & (df[self.cooling_sig_col] > 0.01) & (df[self.economizer_sig_col] > 0.9) ) @@ -84,4 +120,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py b/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py index f26ae36..a77bd6a 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py @@ -24,6 +24,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default False self.rolling_window_size = int + self.equation_string = ( + "fc13_flag = 1 if SAT > (SATSP + εSAT) in " + "economizer + mech cooling mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 13: Supply air temperature too high in full cooling " + "in economizer plus mechanical cooling mode \n" + ) + self.required_column_description = ( + "Required inputs are the supply air temperature, supply air temperature setpoint, " + "cooling signal, and economizer signal \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -34,9 +48,31 @@ def __init__(self, dict_): self.economizer_sig_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionThirteen: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -88,4 +124,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_three.py b/open_fdd/air_handling_unit/faults/fault_condition_three.py index 08772f5..050886f 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_three.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_three.py @@ -24,6 +24,14 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default to False self.rolling_window_size = int + self.equation_string = ( + "fc3_flag = 1 if (MAT - εMAT > max(RAT + εRAT, OAT + εOAT)) and (VFDSPD > 0) " + "for N consecutive values else 0 \n" + ) + self.description_string = "Fault Condition 3: Mix temperature too high; should be between outside and return air \n" + self.required_column_description = "Required inputs are the mix air temperature, return air temperature, outside air temperature, and supply fan VFD speed \n" + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -34,9 +42,31 @@ def __init__(self, dict_): self.supply_vfd_speed_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionThree: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -80,4 +110,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_twelve.py b/open_fdd/air_handling_unit/faults/fault_condition_twelve.py index de94b21..a620230 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_twelve.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_twelve.py @@ -1,4 +1,5 @@ import pandas as pd +import numpy as np import operator from open_fdd.air_handling_unit.faults.fault_condition import ( FaultCondition, @@ -26,6 +27,20 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default False self.rolling_window_size = int + self.equation_string = ( + "fc12_flag = 1 if SAT >= MAT + εMAT in " + "economizer + mech cooling mode for N consecutive values else 0 \n" + ) + self.description_string = ( + "Fault Condition 12: Supply air temperature too high; should be less than " + "mixed air temperature in economizer plus mechanical cooling mode \n" + ) + self.required_column_description = ( + "Required inputs are the supply air temperature, mixed air temperature, " + "cooling signal, and economizer signal \n" + ) + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -36,9 +51,31 @@ def __init__(self, dict_): self.economizer_sig_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionTwelve: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -68,7 +105,7 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: & (df[self.cooling_sig_col] > 0.01) & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr), # OR (df["sat_minus_saterr_delta_supply_fan"] > df["mat_plus_materr"]) - # verify ahu is running in OS 3 clg mode in 100 OA + # verify AHU is running in OS 3 clg mode in 100 OA & (df[self.cooling_sig_col] > 0.01) & (df[self.economizer_sig_col] > 0.9), ) @@ -92,4 +129,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/open_fdd/air_handling_unit/faults/fault_condition_two.py b/open_fdd/air_handling_unit/faults/fault_condition_two.py index e817ca8..87816fa 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_two.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_two.py @@ -24,6 +24,14 @@ def __init__(self, dict_): self.troubleshoot_mode = bool # default to False self.rolling_window_size = int + self.equation_string = ( + "fc2_flag = 1 if (MAT + εMAT < min(RAT - εRAT, OAT - εOAT)) and (VFDSPD > 0) " + "for N consecutive values else 0 \n" + ) + self.description_string = "Fault Condition 2: Mix temperature too low; should be between outside and return air \n" + self.required_column_description = "Required inputs are the mix air temperature, return air temperature, outside air temperature, and supply fan VFD speed \n" + self.error_string = f"One or more required columns are missing or None \n" + self.set_attributes(dict_) # Set required columns specific to this fault condition @@ -34,9 +42,31 @@ def __init__(self, dict_): self.supply_vfd_speed_col, ] + # Check if any of the required columns are None + if any(col is None for col in self.required_columns): + raise MissingColumnError( + f"{self.error_string}" + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.required_columns}" + ) + + # Ensure all required columns are strings + self.required_columns = [str(col) for col in self.required_columns] + + self.mapped_columns = ( + f"Your config dictionary is mapped as: {', '.join(self.required_columns)}" + ) + def get_required_columns(self) -> str: """Returns a string representation of the required columns.""" - return f"Required columns for FaultConditionTwo: {', '.join(self.required_columns)}" + return ( + f"{self.equation_string}" + f"{self.description_string}" + f"{self.required_column_description}" + f"{self.mapped_columns}" + ) def apply(self, df: pd.DataFrame) -> pd.DataFrame: try: @@ -80,4 +110,4 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: except MissingColumnError as e: print(f"Error: {e.message}") sys.stdout.flush() - raise e # Re-raise the exception so it can be caught by pytest + raise e diff --git a/setup.py b/setup.py index 6248296..098c28f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ def read_long_description(file_path): setup( name="open_fdd", - version="0.1.2", + version="0.1.3", author="Ben Bartling", author_email="ben.bartling@gmail.com", description="A package for fault detection and diagnosis in HVAC systems",