'''
Genset:
This module helps size auxiliary power for a pumping system based on
electrical loads.
'''
from __future__ import print_function, division
from numpy import interp
[docs]class Genset:
'''Defines a Genset object to calculate generator loads.
:param voltage: generator output voltage (v)
:param phase: generator phase (ph)
:param capacity: generator load capaicty (kW), *default None*
:param model: generator model, *default None*
:param mfg: generator manufacturer, *default None*
:type voltage: int
:type phase: int
:type capacity: int
:type model: string
:type mfg: string
:return: Genset object
:rtype: object
:Example:
>>> from Water import Genset
>>> gen = Genset(voltage=480, phase=3, capacity=120, model='AB120', mfg='Acme')
'''
def __init__(self, voltage, phase, capacity=None, model=None, mfg=None):
self.load_dict = {'domestic':[], 'fire':[], 'resistive':[]}
self.voltage = voltage
self.phase = phase
self.capacity = capacity
self.model = model
self._power_factors = {3 : 0.89, 1 : 0.85}
self.consumption = []
@property
def fire_load(self):
'''returns sum of all fire pump motor loads'''
return sum(self.load_dict['fire'])
@property
def dom_load(self):
'''returns sum of all domestic pump motor loads'''
return sum(self.load_dict['domestic'])
@property
def res_load(self):
'''returns sum of all resistive loads'''
return sum(self.load_dict['resistive'])
@property
def total_load(self):
'''returns sum total of domestic, fire and resistive loads'''
return sum(sum(self.load_dict.values(),[]))
@property
def power_factors(self):
'''returns power factors for apparent power'''
return self._power_factors
@property
def full_load(self):
'''returns percent genset is loaded under fire flow conditions (fully loaded)'''
return int((self.total_load/self.capacity)*100)
@property
def normal_load(self):
'''returns percent genset is loaded under normal conditions (domestic and resistive only)'''
norm = self.total_load - self.fire_load
return int((norm/self.capacity)*100)
[docs] def add_motor_load(self, power, units='hp', fire=False):
'''adds motor load to self.load_dict uses kVA calculation
based on default power factors. All loads are saved in self.load_dict
in kilovolt-amps
* 3-Phase motor power factor: 0.89
* 1-Phase motor power factor: 0.85
:param power: motor power
:param units: units of power ('hp' or 'kw'), *default hp*
:param fire: set as a fire-flow load, *default False*
:type power: int/float
:type units: string
:type fire: boolean
:Example:
>>> gen.add_motor_load(power=5) # adding domestic pump motor load
>>> gen.add_motor_load(power=25, fire=True) # adding fire-pump motor load
'''
units = units.lower()
assert (units == 'hp' or units == 'kw'), "units must be either 'kw' or 'hp'"
if units == 'kw':
kVA = power/self.power_factors[self.phase]
elif units == 'hp':
kVA = (power * 745.7 * .001/self.power_factors[self.phase])
if fire:
self.load_dict['fire'].append(kVA)
else:
self.load_dict['domestic'].append(kVA)
[docs] def add_resistive_load(self, power, units='kw'):
'''adds resistive load to self.load_dict
*for example heaters, lights, controls...*
:param power: resistive load power
:param units: power units ('kw' or 'watts'), *default 'kw'*
:type power: int/float
:type units: string
:Example:
>>> gen.add_resistive_load(power=0.5)
>>> gen.add_resistive_load(power=500, units='watts')
'''
units = units.lower()
assert (units == 'watts' or units == 'kw'), "units must be either 'kw' or 'watts'"
if units == 'watts':
self.load_dict['resistive'].append(power * 0.001)
elif units == 'kw':
self.load_dict['resistive'].append(power)
[docs] def delete_load(self, load_type, index=-1):
'''deletes specific load from self.load_dict
Prints verification when removed.
:param load_type: load type keyword ('fire', 'domestic', 'resistive')
:param index: index number (use python index conventions), *default -1*
:type load_type: string
:type index: int
:Example:
>>> gen.show_loads()
{'domestic': [4.189325842696629], 'fire': [20.94662921348315], 'resistive': [0.5, 0.5]}
>>> gen.delete_load('resistive')
0.5 has been removed
>>> gen.show_loads()
{'domestic': [4.189325842696629], 'fire': [20.94662921348315], 'resistive': [0.5]}
'''
print(self.load_dict[load_type].pop(index), ' has been removed')
[docs] def add_consumption(self, consumption_list):
'''Sets consumption rate for Genset object
Consumtion rates are entered as a 4 item list.
Consumption values are in scfm and represent fuel consumed
at 25%, 50%, 75% and 100% capacity.
:param consumption_list: fuel consumption at different capacities (scfm)
:type consumption_list: list
:Example:
>>> consumption_list=[100, 200, 300 400] # scfm
'''
self.consumption = consumption_list
[docs] def size_lp_tank(self, min_days=4, fire_time=2, safety_factor=4, **kwargs):
'''Size propane tank for a minimum number of daays of continous running. Bottles
will be either 500 gal, 1000 gal or a multiple of 1000 gal bottles depending on
the application.
:param min_days: desired number of days getset should continously run *default 4*
:type min_days: int/float
:param fire_time: hours of full "fire-flow" load *default 2 hrs*
:type fire_time: int/float
:param safety_factor: safety factor for full load time *default 4*
:type safety_factor: int/float *default 4*
:return: size in gallons and quantity of propane tank needed to run min_days
:rtype: tuple
:keyword arguments:
:lp_volume: (*int/float*) - volume of propane gas at temp in cubic feet *default 36.39*
:lp_energy: (*int/float*) - energy content of propane gas in btu/gal *default 91547*
:bottle_vol: (*int/float*) - starting volume of propane bottle in gallons *default 500*
:num_bottles: (*int*) - starting number of bottles needed
:temp_factor: (*int*) - vaporization rule of thumb temperature factor *default 2*
:fill_factor: (*int*) - vaporization rule of thumb fill factor *default 60*
'''
# propane properties
lp_volume = 36.39 # cu. ft/gal
lp_energy = 91547 # btu/gal
bottle_vol = 500 # gal : start with 500 gal, program will increase if needed
num_bottles = 1 # start with 1 bottle, program will define more if needed
# vaporization rate of propane (rule of thumb)
tank_d = [37, 41] # inches
tank_l = [119, 192] # inches
temp_factor = 2
fill_factor = 60
# find normal and full load flows
perc_loads = [25, 50, 75, 100] # % gen capacity
norm_flow = interp(self.normal_load, self.consumption, perc_loads)
full_flow = interp(self.full_load, self.consumption, perc_loads)
vap = []
for d, l in zip(tank_d, tank_l):
vr = d*l*temp_factor*fill_factor
vap.append((vr, vr/lp_energy))
# define propane flows
q_fire = full_flow/lp_volume # gph
q_dom = norm_flow/lp_volume # gph
q_total = self.consumption[-1]/lp_volume # gph
total_btu = lp_energy * q_total # btu/hr
# propane volumes
v_effective = num_bottles * bottle_vol * 0.6 # gal
v_fire = q_fire * fire_time * safety_factor # gal
# time to empty
t_empty_hr = (v_effective - v_fire)/q_dom # hrs
t_empty_day = t_empty_hr / 24 # days
# IF 500 GAL IS NOT SUFFICIENT BUMP TO 1000 GAL BOTTLE
if t_empty_day < min_days and vap[0][1] < q_fire:
bottle_vol = 1000
v_effective = num_bottles * bottle_vol * 0.6
t_empty_hr = (v_effective - v_fire)/q_dom
t_empty_day = t_empty_hr / 24
# IF 1 1000 GAL BOTTLE IS NOT SUFFICIENT INCREASE UNTIL IT IS
while t_empty_day < 4 and vap[1][1] < q_fire:
num_bottles += 1
v_effective = num_bottles * bottle_vol * 0.6
t_empty_hr = (v_effective - v_fire)/q_dom
t_empty_day = t_empty_hr / 24
return bottle_vol, num_bottles
[docs] def change_power_factor(self, factor):
'''Change the default power factor for the object.
default power factors:
* 3-Phase motor power factor: 0.89
* 1-Phase motor power factor: 0.85
:param factor: power factor
:type factor: float
'''
self._power_factors[self.phase] = factor
[docs] def show_loads(self):
'''see loads in self.load_dict()
:return: loads from the genset object
:rtype: dictionary
'''
return self.load_dict
# test script
if __name__ == "__main__":
gen = Genset(480, 3, 100) # instantiate 480 volt 3 phase, 100 kw genset
gen.add_motor_load(10) # add 10 hp domestic pump motor
gen.add_motor_load(power=7.5, units='hp', fire=False) # add 7.5 hp domestic pump motor
gen.add_resistive_load(500, units='watts') # add resistive load
gen.add_motor_load(25, fire=True) # add 25 hp fire pump motor
gen.add_motor_load(30, fire=True) # add 30 hp fire pump motor
# example of printing out loads that have been entered
load_report = '''
fire-flow load = {0:.2f} kVA
domestic load = {1:.2f} kVA
resistive load = {3:.2f} kVA
total load = {2:.2f} kVA
'''.format(gen.fire_load, gen.dom_load, gen.total_load, gen.res_load)
print(load_report)
# deleting a load
gen.delete_load('fire', index=1)
gen.delete_load('resistive')
load_report = '''
fire-flow load = {0:.2f} kVA
domestic load = {1:.2f} kVA
resistive load = {3:.2f} kVA
total load = {2:.2f} kVA
'''.format(gen.fire_load, gen.dom_load, gen.total_load, gen.res_load)
print(load_report)
# add the fuel consumption rates of the genset
gen.add_consumption([100, 150, 200, 250])
print(gen.full_load, gen.normal_load)
print(gen.power_factors)
print(gen.size_lp_tank())