Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
nealkruis committed Jul 28, 2022
0 parents commit 28b8fba
Show file tree
Hide file tree
Showing 19 changed files with 1,067 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .github/setup_and_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Setup and Test

on: push

jobs:
build:
name: Setup
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.9"]
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Setup python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Upgrade pip
run: python -m pip install --upgrade pip
- name: Install Poetry
uses: snok/[email protected]
- name: Install project
run: poetry install
- name: Test
run: poetry run doit
- name: Upload output
uses: actions/upload-artifact@v2
with:
name: output-${{ matrix.os }}-py${{ matrix.python-version }}
path: output
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.egg-info/
.doit.*
.pytest_cache/
.vscode/
__pycache__/
dist/
build/
output/
11 changes: 11 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Copyright (c) 2022 Big Ladder Software, LLC

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
chiller
=======

A python package for modeling chillers.

Purposes:

1. Comparing models
2. Generating ASHRAE 205 compliant representations
3. Calculating standard efficiency ratings (e.g., for AHRI 510/550)

Development setup
-----------------

Requirements:

1. Python 3.8+
2. [Poetry](https://python-poetry.org/)

Project setup: `poetry install`

Testing: `poetry run doit`
2 changes: 2 additions & 0 deletions chiller/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .chiller import Chiller
from .units import fr_u, to_u
206 changes: 206 additions & 0 deletions chiller/chiller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
from chiller.fluid_properties import FluidState
from .conditions import AHRI_550_590_WATER_COOLED_CONDITIONS, AHRI_550_590_WATER_COOLED_CONDENSER_OUTLET, AHRI_550_590_WATER_COOLED_EVAPORATOR_INLET, OperatingConditions
from .models.energyplus_eir import EnergyPlusEIR
from .units import fr_u
from numpy import linspace

class Chiller:
def __init__(
self,
model=EnergyPlusEIR(),
rated_net_evaporator_capacity=fr_u(100.0,"ton_ref"),
rated_cop=2.0,
cycling_degradation_coefficient=0.0,
standby_power=0.0,
number_of_compressor_speeds=None,
minimum_evaporator_leaving_temperature=fr_u(39.0,"°F"),
maximum_evaporator_leaving_temperature=fr_u(60.0,"°F"),
minimum_condenser_entering_temperature=fr_u(55.0,"°F"),
maximum_condenser_entering_temperature=fr_u(104.0,"°F"),
**kwargs):

self.kwargs = kwargs

self.model = model

self.number_of_compressor_speeds = number_of_compressor_speeds

self.model.set_system(self)

self.rated_net_evaporator_capacity = rated_net_evaporator_capacity
self.rated_cop = rated_cop
self.cycling_degradation_coefficient = cycling_degradation_coefficient
self.standby_power = standby_power

self.minimum_evaporator_leaving_temperature = minimum_evaporator_leaving_temperature
self.maximum_evaporator_leaving_temperature = maximum_evaporator_leaving_temperature
self.minimum_condenser_entering_temperature = minimum_condenser_entering_temperature
self.maximum_condenser_entering_temperature = maximum_condenser_entering_temperature

self.set_rated_evaporator_volumetric_flow_rate()
self.set_rated_condenser_volumetric_flow_rate()

def net_evaporator_capacity(self, conditions=None):
if conditions == None:
conditions = AHRI_550_590_WATER_COOLED_CONDITIONS
return self.model.net_evaporator_capacity(conditions)

def input_power(self, conditions=None):
if conditions == None:
conditions = AHRI_550_590_WATER_COOLED_CONDITIONS
return self.model.input_power(conditions)

def net_condenser_capacity(self, conditions=None):
if conditions == None:
conditions = AHRI_550_590_WATER_COOLED_CONDITIONS
return self.model.net_condenser_capacity(conditions)

def oil_cooler_heat(self, conditions=None):
if conditions == None:
conditions = AHRI_550_590_WATER_COOLED_CONDITIONS
return self.model.oil_cooler_heat(conditions)

def auxiliary_heat(self, conditions=None):
if conditions == None:
conditions = AHRI_550_590_WATER_COOLED_CONDITIONS
return self.model.auxiliary_heat(conditions)

def cop(self, conditions=None):
return self.net_evaporator_capacity(conditions)/self.input_power(conditions)

def condenser_liquid_leaving_temperature(self, conditions=None):
if conditions == None:
conditions = AHRI_550_590_WATER_COOLED_CONDITIONS
return conditions.condenser_inlet.T + self.net_condenser_capacity(conditions)/(conditions.condenser_inlet.get_cp()*conditions.condenser_inlet.m_dot)

def evaporator_liquid_entering_temperature(self, conditions=None):
if conditions == None:
conditions = AHRI_550_590_WATER_COOLED_CONDITIONS
return conditions.evaporator_outlet.T + self.net_evaporator_capacity(conditions)/(conditions.evaporator_outlet.get_cp()*conditions.evaporator_outlet.m_dot)

def space_loss_heat(self, conditions=None):
return (self.input_power(conditions) + self.net_evaporator_capacity(conditions)) - (self.net_condenser_capacity(conditions) + self.oil_cooler_heat(conditions) + self.auxiliary_heat(conditions))

def set_rated_evaporator_volumetric_flow_rate(self):
delta_T = AHRI_550_590_WATER_COOLED_EVAPORATOR_INLET.T - AHRI_550_590_WATER_COOLED_CONDITIONS.evaporator_outlet.T
m_dot = self.net_evaporator_capacity()/(AHRI_550_590_WATER_COOLED_CONDITIONS.evaporator_outlet.get_cp()*delta_T)
AHRI_550_590_WATER_COOLED_CONDITIONS.evaporator_outlet.set_m_dot(m_dot)
AHRI_550_590_WATER_COOLED_EVAPORATOR_INLET.set_m_dot(m_dot)
self.rated_evaporator_volumetric_flow_rate = AHRI_550_590_WATER_COOLED_CONDITIONS.evaporator_outlet.V_dot

def set_rated_condenser_volumetric_flow_rate(self):
delta_T = AHRI_550_590_WATER_COOLED_CONDENSER_OUTLET.T - AHRI_550_590_WATER_COOLED_CONDITIONS.condenser_inlet.T
m_dot = self.net_condenser_capacity()/(AHRI_550_590_WATER_COOLED_CONDITIONS.condenser_inlet.get_cp()*delta_T)
AHRI_550_590_WATER_COOLED_CONDITIONS.condenser_inlet.set_m_dot(m_dot)
AHRI_550_590_WATER_COOLED_CONDENSER_OUTLET.set_m_dot(m_dot)
self.rated_condenser_volumetric_flow_rate = AHRI_550_590_WATER_COOLED_CONDITIONS.condenser_inlet.V_dot

def generate_205_performance(self):
# Create conditions
evaporator_liquid_volumetric_flow_rates = [self.rated_evaporator_volumetric_flow_rate]
evaporator_liquid_leaving_temperatures = linspace(self.minimum_evaporator_leaving_temperature, self.maximum_evaporator_leaving_temperature, 4).tolist()
condenser_liquid_volumetric_flow_rates = [self.rated_condenser_volumetric_flow_rate]
condenser_liquid_entering_temperatures = linspace(self.minimum_condenser_entering_temperature, self.maximum_condenser_entering_temperature, 4).tolist()
compressor_sequence_numbers = list(range(1,self.number_of_compressor_speeds + 1))

grid_variables = {
"evaporator_liquid_volumetric_flow_rate":
evaporator_liquid_volumetric_flow_rates,
"evaporator_liquid_leaving_temperature":
evaporator_liquid_leaving_temperatures,
"condenser_liquid_volumetric_flow_rate":
condenser_liquid_volumetric_flow_rates,
"condenser_liquid_entering_temperature":
condenser_liquid_entering_temperatures,
"compressor_sequence_number":
compressor_sequence_numbers
}

input_powers = []
net_evaporator_capacities = []
net_condenser_capacities = []
evaporator_liquid_entering_temperatures = []
condenser_liquid_leaving_temperatures = []
evaporator_liquid_differential_pressures = []
condenser_liquid_differential_pressures = []
oil_cooler_heats = []
auxiliary_heats = []

for v_evap in evaporator_liquid_volumetric_flow_rates:
for t_evap in evaporator_liquid_leaving_temperatures:
for v_cond in condenser_liquid_volumetric_flow_rates:
for t_cond in condenser_liquid_entering_temperatures:
for speed in [self.number_of_compressor_speeds - n for n in compressor_sequence_numbers]:
conditions = OperatingConditions(
evaporator_outlet=FluidState(temperature=t_evap, volumetric_flow_rate=v_evap),
condenser_inlet=FluidState(temperature=t_cond, volumetric_flow_rate=v_cond),
compressor_speed=speed)

input_powers.append(self.input_power(conditions))
net_evaporator_capacities.append(self.net_evaporator_capacity(conditions))
net_condenser_capacities.append(self.net_condenser_capacity(conditions))
evaporator_liquid_entering_temperatures.append(self.evaporator_liquid_entering_temperature(conditions))
condenser_liquid_leaving_temperatures.append(self.condenser_liquid_leaving_temperature(conditions))
evaporator_liquid_differential_pressures.append(fr_u(15.,"kPa"))
condenser_liquid_differential_pressures.append(fr_u(15.,"kPa"))
oil_cooler_heats.append(self.oil_cooler_heat(conditions))
auxiliary_heats.append(self.auxiliary_heat(conditions))

performance_map_cooling = {
"grid_variables": grid_variables,
"lookup_variables": {
"input_power":
input_powers,
"net_evaporator_capacity":
net_evaporator_capacities,
"net_condenser_capacity":
net_condenser_capacities,
"evaporator_liquid_entering_temperature":
evaporator_liquid_entering_temperatures,
"condenser_liquid_leaving_temperature":
condenser_liquid_leaving_temperatures,
"evaporator_liquid_differential_pressure":
evaporator_liquid_differential_pressures,
"condenser_liquid_differential_pressure":
condenser_liquid_differential_pressures,
"oil_cooler_heat":
oil_cooler_heats,
"auxiliary_heat":
auxiliary_heats
}
}
performance = {
"evaporator_liquid_type": { # TODO: Make consistent with model
"liquid_components": [
{
"liquid_constituent": "WATER",
"concentration": 1.0,
}
],
"concentration_type": "BY_VOLUME"
},
"condenser_liquid_type": { # TODO: Make consistent with model
"liquid_components": [
{
"liquid_constituent": "WATER",
"concentration": 1.0,
}
],
"concentration_type": "BY_VOLUME"
},
"evaporator_fouling_factor": 0.0,
"condenser_fouling_factor": 0.0,
"compressor_speed_control_type": "CONTINUOUS",
"maximum_power": max(input_powers), # Do something else?
"cycling_degradation_coefficient": self.cycling_degradation_coefficient,
"performance_map_cooling": performance_map_cooling,
"performance_map_standby": {
"grid_variables": {
"environment_dry_bulb_temperature": [fr_u(20.0, "°C")],
},
"lookup_variables": {
"input_power": [self.standby_power],
},
}
}
return performance
14 changes: 14 additions & 0 deletions chiller/conditions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .fluid_properties import FluidState
from .units import fr_u

class OperatingConditions:
def __init__(self, condenser_inlet, evaporator_outlet, compressor_speed=0):
self.condenser_inlet = condenser_inlet
self.evaporator_outlet = evaporator_outlet
self.compressor_speed = compressor_speed

AHRI_550_590_WATER_COOLED_CONDITIONS = OperatingConditions(condenser_inlet=FluidState(fr_u(85.0,"°F")), evaporator_outlet=FluidState(fr_u(44.0,"°F")))

AHRI_550_590_WATER_COOLED_CONDENSER_OUTLET = FluidState(fr_u(94.3,"°F"))

AHRI_550_590_WATER_COOLED_EVAPORATOR_INLET = FluidState(fr_u(54.0,"°F"))
38 changes: 38 additions & 0 deletions chiller/fluid_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from re import S
import CoolProp.CoolProp as CP
from .units import fr_u

class FluidState:
def __init__(self, temperature, pressure=fr_u(1.0,"atm"), volumetric_flow_rate=None, fluid_name="Water"):
self.fluid_name = fluid_name
self.T = temperature
self.p = pressure
self.rho_set = False
self.cp_set = False
self.V_dot_set = False
if volumetric_flow_rate is None:
self.V_dot = None
else:
self.set_V_dot(volumetric_flow_rate)

def get_rho(self):
if not self.rho_set:
self.rho = CP.PropsSI("D", "P", self.p, "T", self.T, self.fluid_name)
self.rho_set = True
return self.rho

def get_cp(self):
if not self.cp_set:
self.cp = CP.PropsSI("C", "P", self.p, "T", self.T, self.fluid_name)
self.cp_set = True
return self.cp

def set_V_dot(self, volumetric_flow_rate):
self.V_dot = volumetric_flow_rate
self.m_dot = self.V_dot*self.get_rho()
self.V_dot_set = True

def set_m_dot(self, mass_flow_rate):
self.m_dot = mass_flow_rate
self.V_dot = self.m_dot/self.get_rho()
self.V_dot_set = True
1 change: 1 addition & 0 deletions chiller/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .energyplus_eir import *
25 changes: 25 additions & 0 deletions chiller/models/base_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class ChillerModel:
def __init__(self):
self.system = None
self.allowed_kwargs = []

def set_system(self, system):
self.system = system
for kwarg in system.kwargs:
if kwarg not in self.allowed_kwargs:
raise Exception(f"Unrecognized key word argument: {kwarg}")

def net_evaporator_capacity(self, conditions):
raise NotImplementedError()

def input_power(self, conditions):
raise NotImplementedError

def net_condenser_capacity(self, conditions):
raise NotImplementedError()

def oil_cooler_heat(self, conditions):
raise NotImplementedError()

def auxiliary_heat(self, conditions):
raise NotImplementedError()
Loading

0 comments on commit 28b8fba

Please sign in to comment.