From 95ec59fd8a10d0abd84d6071e1df6fab1994b5cf Mon Sep 17 00:00:00 2001 From: Michael Rossetti Date: Sun, 19 Oct 2025 19:31:12 +0000 Subject: [PATCH 1/5] This Pull Request contains changes from our internal repository. **Context Reference**: HEAD **Copybara Current Rev**: 821375201 **Summary of Internal Changes:** - 821375201 - 810461194 PiperOrigin-RevId: 821375201 --- smart_control/simulator/dbo_pump.py | 161 +++++++++++++++++++++++ smart_control/simulator/dbo_pump_test.py | 111 ++++++++++++++++ smart_control/utils/constants.py | 6 +- 3 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 smart_control/simulator/dbo_pump.py create mode 100644 smart_control/simulator/dbo_pump_test.py diff --git a/smart_control/simulator/dbo_pump.py b/smart_control/simulator/dbo_pump.py new file mode 100644 index 00000000..1642871d --- /dev/null +++ b/smart_control/simulator/dbo_pump.py @@ -0,0 +1,161 @@ +"""Models a pump for the simulation. + +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import enum +from typing import Optional +import uuid + +import gin +from smart_buildings.smart_control.proto import smart_control_building_pb2 +from smart_buildings.smart_control.simulator import smart_device +from smart_buildings.smart_control.utils import constants + + +RunStatus = enum.Enum('RunStatus', [('On', 1), ('Off', 2)]) + + +@gin.configurable +class Pump(smart_device.SmartDevice): + """Models a water pump. + + Attributes: + differential_pressure: Differential pressure of the pump in bars. + run_command: Command indicating if the pump is on or off + _water_pump_differential_head: Length in meters of pump head. + _water_pump_efficiency: Electrical efficiency of water pump [0,1]. + """ + + def __init__( + self, + water_pump_differential_head: float, + water_pump_efficiency: float, + device_id: Optional[str] = None, + ): + observable_fields = { + 'differential_pressure': smart_device.AttributeInfo( + 'differential_pressure', float + ), + 'run_command': smart_device.AttributeInfo('run_command', RunStatus), + } + + action_fields = { + 'differential_pressure': smart_device.AttributeInfo( + 'differential_pressure', float + ), + 'run_command': smart_device.AttributeInfo('run_command', RunStatus), + } + + if device_id is None: + device_id = f'pump_id_{uuid.uuid4()}' + + super().__init__( + observable_fields, + action_fields, + device_type=smart_control_building_pb2.DeviceInfo.DeviceType.PMP, + device_id=device_id, + ) + + self._init_water_pump_differential_head = water_pump_differential_head + self._init_water_pump_efficiency = water_pump_efficiency + self._init_run_command = RunStatus.Off + self.reset() + + def reset(self): + self._water_pump_differential_head = self._init_water_pump_differential_head + self._water_pump_efficiency = self._init_water_pump_efficiency + self._run_command = self._init_run_command + + def compute_pump_power(self, total_flow_rate_demand) -> float: + """Returns power consumed by pump in W to move water to VAVs. + + derived from: https://www.engineeringtoolbox.com/pumps-power-d_505.html + + Args: + total_flow_rate_demand: The total flow rate of water through the pump in + m3/s. + """ + return ( + total_flow_rate_demand + * constants.WATER_DENSITY + * constants.GRAVITY + * self._water_pump_differential_head + / self._water_pump_efficiency + ) + + def _convert_differential_head_to_pressure( + self, differential_head: float + ) -> float: + """Converts a differential head (m) to differential pressure (bar). + + formula derived from: + https://www.engineeringtoolbox.com/pump-head-pressure-d_663.html + pressure (pa) = fluid_density * gravity * differential_head + water density = 1000 kg/m^3, gravity = 9.81 m/s^2, + so we get p= 9810 * differential_head + now, to convert to bar, 1 bar = 100,000 Pa, so we get: + pressure (bar) = differential_head * 0.0981 + (specific gravity of water is 1 so we leave it as is) + + Args: + differential_head: The differential head of the pump in meters. + + Returns: + The differential pressure of the pump in bars. + """ + return ( + constants.GRAVITY + * constants.WATER_DENSITY + * differential_head + / constants.PASCALS_PER_BAR + ) + + def _convert_pressure_to_differential_head(self, pressure: float) -> float: + """Converts a differential pressure (bar) to differential head (m). + + We simmple reverse the conversion above: + differential_head = pressure / 0.0981 + This simplifies to differential_head = pressure * 10.1937 + + Args: + pressure: The differential pressure of the pump in bars. + + Returns: + The differential head of the pump in meters. + """ + return pressure / ( + constants.GRAVITY * constants.WATER_DENSITY / constants.PASCALS_PER_BAR + ) + + @property + def differential_pressure(self) -> float: + return self._convert_differential_head_to_pressure( + self._water_pump_differential_head + ) + + @differential_pressure.setter + def differential_pressure(self, value: float) -> None: + self._water_pump_differential_head = ( + self._convert_pressure_to_differential_head(value) + ) + + @property + def run_command(self) -> RunStatus: + return self._run_command + + @run_command.setter + def run_command(self, value: RunStatus) -> None: + self._run_command = value diff --git a/smart_control/simulator/dbo_pump_test.py b/smart_control/simulator/dbo_pump_test.py new file mode 100644 index 00000000..d994a5bf --- /dev/null +++ b/smart_control/simulator/dbo_pump_test.py @@ -0,0 +1,111 @@ +"""Tests for Dbo compliant pump. + +Copyright 2023 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from absl.testing import absltest +from absl.testing import parameterized +from smart_buildings.smart_control.simulator import dbo_pump +from smart_buildings.smart_control.utils import constants + + +class DboPumpTest(parameterized.TestCase): + + @parameterized.parameters( + (0.5, 3, 0.9), + (0.2, 7, 0.5), + (0.5, 8, 0.23), + (0.5, 9, 0.7), + ) + def test_compute_pump_power( + self, total_flow_rate, water_pump_differential_head, water_pump_efficiency + ): + pump = dbo_pump.Pump( + water_pump_differential_head=water_pump_differential_head, + water_pump_efficiency=water_pump_efficiency, + ) + + expected = ( + total_flow_rate + * constants.WATER_DENSITY + * constants.GRAVITY + * water_pump_differential_head + / water_pump_efficiency + ) + self.assertEqual(pump.compute_pump_power(total_flow_rate), expected) + + def test_run_command(self): + pump = dbo_pump.Pump( + water_pump_differential_head=3, + water_pump_efficiency=0.9, + ) + self.assertEqual(pump.run_command, dbo_pump.RunStatus.Off) + pump.run_command = dbo_pump.RunStatus.On + self.assertEqual(pump.run_command, dbo_pump.RunStatus.On) + + def test_differential_pressure(self): + water_pump_differential_head = 3 + pump = dbo_pump.Pump( + water_pump_differential_head=water_pump_differential_head, + water_pump_efficiency=0.9, + ) + expected_dp = ( + constants.GRAVITY + * constants.WATER_DENSITY + * water_pump_differential_head + / constants.PASCALS_PER_BAR + ) + self.assertAlmostEqual(pump.differential_pressure, expected_dp) + pump.differential_pressure = 30 + self.assertAlmostEqual(pump.differential_pressure, 30) + expected_head = 30 / ( + constants.GRAVITY * constants.WATER_DENSITY / constants.PASCALS_PER_BAR + ) + self.assertAlmostEqual(pump._water_pump_differential_head, expected_head) + + def test_pressure_conversion(self): + pump = dbo_pump.Pump( + water_pump_differential_head=3, + water_pump_efficiency=0.9, + ) + self.assertAlmostEqual( + pump._convert_pressure_to_differential_head( + pump._convert_differential_head_to_pressure(3) + ), + 3, + ) + self.assertAlmostEqual( + pump._convert_differential_head_to_pressure( + pump._convert_pressure_to_differential_head(30) + ), + 30, + ) + + def test_reset(self): + pump = dbo_pump.Pump( + water_pump_differential_head=3, + water_pump_efficiency=0.9, + ) + pump.run_command = dbo_pump.RunStatus.On + pump._water_pump_differential_head = 4 + pump._water_pump_efficiency = 0.1 + pump.reset() + self.assertEqual(pump.run_command, dbo_pump.RunStatus.Off) + self.assertEqual(pump._water_pump_differential_head, 3) + self.assertEqual(pump._water_pump_efficiency, 0.9) + + +if __name__ == '__main__': + absltest.main() diff --git a/smart_control/utils/constants.py b/smart_control/utils/constants.py index 4cba3423..9eed535d 100644 --- a/smart_control/utils/constants.py +++ b/smart_control/utils/constants.py @@ -1,4 +1,4 @@ -"""Single location for all constants related to the simulation and RL environment. +"""Single location for constants related to the simulation and RL environment. Copyright 2022 Google LLC @@ -16,6 +16,7 @@ """ from typing import Final + # --------- Thermal Constants --------------- AIR_HEAT_CAPACITY = 1006.0 # J/kg/K, standard atmosphere @@ -30,6 +31,9 @@ WATTS_PER_BTU_HR: float = 0.29307107 # Number of Watts in a BTU/hr HZ_PERCENT: float = 100.0 / 60.0 # Converts blower/pump Hz to Percentage Power +# --------- Pressure Constants --------------- +PASCALS_PER_BAR: float = 100000.0 # Number of Pascals in a bar. + # https://www.rapidtables.com/convert/power/hp-to-watt.html WATTS_PER_HORSEPOWER = 746.0 From 4865bfdd9127ce48f4af65574f22e546769c75bb Mon Sep 17 00:00:00 2001 From: Michael Rossetti Date: Tue, 10 Feb 2026 15:49:18 -0500 Subject: [PATCH 2/5] Remove license --- smart_control/simulator/dbo_pump.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/smart_control/simulator/dbo_pump.py b/smart_control/simulator/dbo_pump.py index 1642871d..3344cced 100644 --- a/smart_control/simulator/dbo_pump.py +++ b/smart_control/simulator/dbo_pump.py @@ -1,19 +1,4 @@ -"""Models a pump for the simulation. - -Copyright 2025 Google LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Models a pump for the simulation.""" import enum from typing import Optional From 739870e18976081a1f2a310d7be0f7a44eee9539 Mon Sep 17 00:00:00 2001 From: Michael Rossetti Date: Tue, 10 Feb 2026 15:53:13 -0500 Subject: [PATCH 3/5] Manually transform imports --- smart_control/simulator/dbo_pump.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/smart_control/simulator/dbo_pump.py b/smart_control/simulator/dbo_pump.py index 3344cced..358455d8 100644 --- a/smart_control/simulator/dbo_pump.py +++ b/smart_control/simulator/dbo_pump.py @@ -5,9 +5,10 @@ import uuid import gin -from smart_buildings.smart_control.proto import smart_control_building_pb2 -from smart_buildings.smart_control.simulator import smart_device -from smart_buildings.smart_control.utils import constants + +from smart_control.proto import smart_control_building_pb2 +from smart_control.simulator import smart_device +from smart_control.utils import constants RunStatus = enum.Enum('RunStatus', [('On', 1), ('Off', 2)]) From ea157aa01faa90abb2aa247900d1097358fea53e Mon Sep 17 00:00:00 2001 From: Michael Rossetti Date: Tue, 10 Feb 2026 15:54:56 -0500 Subject: [PATCH 4/5] Manually remove license --- smart_control/simulator/dbo_pump_test.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/smart_control/simulator/dbo_pump_test.py b/smart_control/simulator/dbo_pump_test.py index d994a5bf..b96c5bd5 100644 --- a/smart_control/simulator/dbo_pump_test.py +++ b/smart_control/simulator/dbo_pump_test.py @@ -1,19 +1,4 @@ -"""Tests for Dbo compliant pump. - -Copyright 2023 Google LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Tests for Dbo compliant pump.""" from absl.testing import absltest from absl.testing import parameterized From c68401c19b3960f3dc6e34139e04692b2aae02d8 Mon Sep 17 00:00:00 2001 From: Michael Rossetti Date: Tue, 10 Feb 2026 15:55:15 -0500 Subject: [PATCH 5/5] Update imports Added import statement for dbo_pump module. --- smart_control/simulator/dbo_pump_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/smart_control/simulator/dbo_pump_test.py b/smart_control/simulator/dbo_pump_test.py index b96c5bd5..3744deac 100644 --- a/smart_control/simulator/dbo_pump_test.py +++ b/smart_control/simulator/dbo_pump_test.py @@ -2,6 +2,7 @@ from absl.testing import absltest from absl.testing import parameterized + from smart_buildings.smart_control.simulator import dbo_pump from smart_buildings.smart_control.utils import constants