Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions smart_control/simulator/dbo_pump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""Models a pump for the simulation."""

import enum
from typing import Optional
import uuid

import gin

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)])


@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
97 changes: 97 additions & 0 deletions smart_control/simulator/dbo_pump_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Tests for Dbo compliant pump."""

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()
3 changes: 3 additions & 0 deletions smart_control/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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

Expand Down
Loading