JEPL/jepl/jepl_circuits.py

480 lines
16 KiB
Python

'''
JMK Engineering Inc. Python Library for design and such.
by: Jeff MacKinnon
email: jeff@jmkengineering.com
Circuit Design Functions
'''
import pandas as pd
import numpy as np
import math
# Need to add the various table files here.
from .jepl_tables import *
def voltage_drop(voltage, current, conductor_size, length, num_phase = 3, material ='cu', num_runs = 1, code = 'CEC', power_factor = 'dc', raceway = True,insul_temp = 75):
'''
This function will return the drop in voltage and in percent of the supply.
For CEC this function uses Table D3 and the function VD = K * F * i * L / 1000
K -> is a table lookup based on the conductor material, size raceway and powerfactor
F -> is the system factor, 2 for single phase and sqrt(3) for three phase
'''
material = material.upper()
code = code.upper()
power_factor = power_factor.upper()
#conductor_size = str(conductor_size)
valid_insul_temp = [60,75,90]
valid_code = ['CEC',
]
valid_material = ['CU',
'AL',
]
valid_power_factor = ['DC',
'1',
'0.9',
'0.8'
]
if insul_temp not in valid_insul_temp:
return print(temp + " is not valid. The valid temps are "+ str(valid_insul_temp))
if code not in valid_code:
return print(code + " is not a valid code. The valid codes are "+ str(valid_code))
if material not in valid_material:
return print(material + " is not a valid material. I should be 'al' or 'cu'.")
if power_factor not in valid_power_factor:
return print(valid_power_factor + " is not a valid load_type.")
#
# Find K
#
# Select the correct table
if (code == 'CEC') :
D3headers = cectableD3[0]
D3rows = cectableD3[1:]
df = pd.DataFrame(D3rows, columns=D3headers)
else:
return ('The variables were\'t right, but I\'m a loss to why.')
# Select the correct column
if (material == 'CU') & (power_factor == 'DC'):
column = 'cu_dc'
elif material == 'CU' & power_factor == '1':
column = 'cu_100pf'
elif material == 'CU' & power_factor == '0.9' & raceway == False:
column = 'cu_cable_90pf'
elif material == 'CU' & power_factor == '0.8' & raceway == False:
column = 'cu_cable_80pf'
elif material == 'CU' & power_factor == '0.9' & raceway == True:
column = 'cu_raceway_90pf'
elif material == 'CU' & power_factor == '0.8' & raceway == True:
column = 'cu_raceway_80pf'
elif material == 'AL' & power_factor == 'DC':
column = 'al_dc'
elif material == 'AL' & power_factor == '1':
column = 'al_100pf'
elif material == 'AL' & power_factor == '0.9' & raceway == False:
column = 'al_cable_90pf'
elif material == 'AL' & power_factor == '0.8' & raceway == False:
column = 'al_cable_80pf'
elif material == 'AL' & power_factor == '0.9' & raceway == True:
column = 'al_raceway_90pf'
elif material == 'AL' & power_factor == '0.8' & raceway == True:
column = 'al_raceway_80pf'
else:
return ('Can\'t calculate K factor')
# Determine the ampacity of the max conductor size
K_loc = df.loc[df['size'] == conductor_size,column]
K = K_loc.item()
#print(K)
#
# Find F
#
if num_phase == 3:
F = math.sqrt(3)
else:
F = 2
#print(F)
#print(current)
#print(length)
voltage_drop = K * F * current * length / 1000
percent_voltage_drop = (voltage_drop / voltage)
return [voltage_drop, percent_voltage_drop]
def current_for_lookup(current,max_current):
'''
This is a helper function for conductor_size. It is used to calculate the number of parallel runs needed,
and the conductor current for those runs.
'''
num_parallel = math.ceil(current / max_current)
con_current = current / num_parallel
return (con_current,num_parallel)
def conductor_size(current, temp = 75, material = 'cu', code = 'CEC', raceway = True, ambient = 30, max = 500, load_type = None):
'''
The default temp column will be the 75C column as most terminals are rated for this.
The default code is CEC as that is where I am.
I still need to incorporate ambient temperature deratings, but that will be a future improvement
'''
material = material.upper()
code = code.upper()
max = str(max)
valid_temp = [60,75,90]
valid_temp_str = [str(x) for x in valid_temp]
valid_code = ['CEC',
]
valid_material = ['CU',
'AL',
]
valid_load_type = ['normal','xfmr','xfmrp','xfmrs','motor',None]
#check to make sure that the values are valid
if temp not in valid_temp:
return print(temp + " is not valid. The valid temps are "+ str(valid_temp_str))
if code not in valid_code:
return print(code + " is not a valid code. The valid codes are "+ str(valid_code))
if material not in valid_material:
return print(material + " is not a valid material. I should be 'al' or 'cu'.")
if load_type not in valid_load_type:
return print(load_type + " is not a valid load_type.")
if temp == 90:
column = '90C'
elif temp == 75:
column = '75C'
else:
column = '60C'
'''
Per CEC rules 26-256 and 28-106 Transformer and Motor conductors should be sized 125% of the rated current.
'''
list_125 = ['xfmr','xfmrp','xfmrs', 'motor']
if load_type in list_125:
current = 1.25 * current
# Seclect the proper table
if (code == 'CEC') & (material == 'CU') & (raceway == False):
df = pd.DataFrame(cectable1, columns=['size', '60C', '75C', '90C'])
elif (code == 'CEC') & (material == 'CU') & (raceway == True):
df = pd.DataFrame(cectable2, columns=['size', '60C', '75C', '90C'])
elif (code == 'CEC') & (material == 'AL') & (raceway == False):
df = pd.DataFrame(cectable3, columns=['size', '60C', '75C', '90C'])
elif (code == 'CEC') & (material == 'AL') & (raceway == True):
df = pd.DataFrame(cectable4, columns=['size', '60C', '75C', '90C'])
elif (code =='NEC') & (material =='CU'):
return (' I haven\'t created this table yet')
elif (code =='NEC') & (material =='AL'):
return (' I haven\'t created this table yet')
else:
return ('The variables were\'t right, but I\'m a loss to why.')
'''
Using the correct table, calculate the number of parallel runs needed with the maximum conductor size,
and the resulting wire size.
'''
# Determine the ampacity of the max conductor size
max_df = df.loc[df['size'] == max,column]
max_current = max_df.item()
#print(max_current)
# The current that we will need to
current_lookup = current_for_lookup(current,max_current)
current = current_lookup[0]
num_parallel = current_lookup[1]
# Calculate the absolute difference for all values and find the index of the minimum difference
sectioned_df = df.loc[df[column] >= current,column]
closest_index = sectioned_df.idxmin()
# Retrieve the nearest value using the found index
conductor_size = df['size'].iloc[closest_index]
return [conductor_size,num_parallel]
def conductor_ampacity(conductor, temp = 75, material = 'cu', code = 'CEC', raceway = True, ambient = 30):
'''
Calculates the ampacity of a conductor size and material using code tables.
'''
material = material.upper()
code = code.upper()
valid_temp = [60,75,90]
valid_temp_str = [str(x) for x in valid_temp]
valid_code = ['CEC',
]
valid_material = ['CU',
'AL',
]
if temp == 90:
column = '90C'
elif temp == 75:
column = '75C'
else:
column = '60C'
if (code == 'CEC') & (material == 'CU') & (raceway == False):
df = pd.DataFrame(cectable1, columns=['size', '60C', '75C', '90C'])
elif (code == 'CEC') & (material == 'CU') & (raceway == True):
df = pd.DataFrame(cectable2, columns=['size', '60C', '75C', '90C'])
elif (code == 'CEC') & (material == 'AL') & (raceway == False):
df = pd.DataFrame(cectable3, columns=['size', '60C', '75C', '90C'])
elif (code == 'CEC') & (material == 'AL') & (raceway == True):
df = pd.DataFrame(cectable4, columns=['size', '60C', '75C', '90C'])
elif (code =='NEC') & (material =='CU'):
return (' I haven\'t created this table yet')
elif (code =='NEC') & (material =='AL'):
return (' I haven\'t created this table yet')
else:
return ('The variables were\'t right, but I\'m a loss to why.')
# Determine the ampacity of the conductor size
result_df = df.loc[df['size'] == conductor,column]
conductor_ampacity = result_df.item()
#print(conductor_ampacity)
return conductor_ampacity
'''
This function calculates the bonding wire or bus based on the circuit ampacity.
'''
material = material.upper()
code = code.upper()
valid_code = ['CEC',
]
valid_material = ['CU',
'AL',
]
# Select the correct table
if code == 'CEC':
table16headers = cectable16[0]
table16rows = cectable16[1:]
df = pd.DataFrame(table16rows, columns=table16headers)
else:
return ('The variables were\'t right, but I\'m a loss to why.')
# Select the correct column
if material == 'CU' and bus == False:
column = 'copper wire'
elif material == 'CU' and bus == True:
column = 'copper bus'
elif material == 'AL' and bus == False:
column = 'aluminum wire'
elif material == 'AL' and bus == True:
column = 'caluminum bus'
else:
return ('Can\'t calculate K factor')
# Calculate the absolute difference for all values and find the index of the minimum difference
sectioned_df = df.loc[df['current'] >= conductor_ampacity,'current']
closest_index = sectioned_df.idxmin()
# Retrieve the nearest value using the found index
bond_size = df[column].iloc[closest_index]
return bond_size
## This doesn't work yet, but its getting
def conduit_size(num_cc,cc_con,bond,insulation="RW90", voltage = 1000, jacketed = False,material='SCH80', code = 'CEC'):
'''
Calculated the necessary conduit size for the circuit using code tables.
num_cc = The number of current carrying conductors, including neutral.
cc_con = The current carrying conductor size
bond = The bond size
insulation = The insulation class of the conductors.
voltage = The voltage rating of the insulation, typically 600 or 1000 [V].
jacketed = Whether or not the conductor is jacketed, typically this is false.
material = The material of the conduit. The default is SCH80 as it is the "worse case" in most configurations as it has a high difference from trade size to inner diameter.
code = CEC or NEC, although NEC is lagging in the development front at the moment, so stick with CEC.
The calculation is completed in the following steps:
1. Check to make sure the variables are valid.
2. Determine the percent fill limitation based on the number of current carrying conductors.
3. Look up the outer diameter of the conductors and bond. Calculate the total area.
4. Look up the trade size conduit, of the correct material, that meets the requirements.
5. Return the trade size, inner diameter and "result name".
'''
valid_insulation = ['R90',
'RW90',
'RPV90',
'RW75'
]
valid_voltage = [600,1000]
valid_material = ['RMC', # Rigid Metal Conduit
'FMC', # Flexible Metal Conduit
'RPVC', # Rigid PVC
'DB2', # Type DB2
'LTMC', # Liquid Tight Metal Conduit
'LTNMC', # Liquid Tight non-metallic conduit
'EMT', # electrical metallic tubing
'ENT', # electrical non-metallic tubing
'SCH40', # HDPE Schedule 40
'SCH80', # HDPE Schedule 80
'DR9', # HDPE DR9
'DR11', # HDPE DR11
'DR13', # HDPE DR13.5
'DR15' # HDPE DR15.5
]
valid_code = ['CEC',
# 'NEC' - NEC isn't completed yet.
]
if insulation not in valid_insulation:
return print(insulation + " is not valid.")
if voltage not in valid_voltage:
return print(voltage + " is not valid.")
if material not in valid_material:
return print(material + " is not a valid material. It should be in " + valid_material)
if code not in valid_code:
return print(code + " is not valid.")
cc_con = str(cc_con)
bond = str(bond)
#
# Determine the maximum conduit fill
#
import numpy as np
if num_cc == 1:
percent_fill = 0.53
elif num_cc == 2:
percent_fill = 0.31
else:
percent_fill = 0.4
#
# Find the conductor area
#
# This will be modified/added to when all the Table 6's are added. Currently there are only A-C,
if jacketed == True:
table6 = cec24table6C
elif voltage == 600:
table6 = cec24table6A
elif voltage == 1000:
table6 = cec24table6B
else:
return print("error")
table6headers = table6[0]
table6rows = table6[1:]
conductor_diameter = pd.DataFrame(table6rows,columns= table6headers)
#print(conductor_diameter)
cc_dia = conductor_diameter.loc[(conductor_diameter['size'] == cc_con) ]['diameter'].iloc[0]
print(cc_dia)
bond_dia = conductor_diameter.loc[(conductor_diameter['size'] == bond) ]['diameter'].iloc[0]
print(bond_dia)
cc_area = math.pi * (cc_dia/2) ** 2
print(cc_area)
bond_area = math.pi * (bond_dia/2) ** 2
print(bond_area)
# Total conductor area
area_conductors = num_cc * cc_area + bond_area
print(area_conductors)
min_trade_area = area_conductors / percent_fill # The minimum area of the conduit
print(min_trade_area)
trade_inner_dia = math.sqrt(min_trade_area/math.pi) * 2
print(trade_inner_dia)
#
# Select the conduit trade size
#
conduit_table = pd.DataFrame(cec24table9AB[1:], columns=cec24table9AB[0])
#print(conduit_table)
# Calculate the absolute difference for all values and find the index of the minimum difference
sectioned_df = conduit_table.loc[conduit_table[material] >= trade_inner_dia,material]
closest_index = sectioned_df.idxmin()
# Retrieve the nearest value using the found index
result_raw = conduit_table['size'].iloc[closest_index]
result_name = result_raw + 'mm ' + material
result_inner_dia = conduit_table[material].iloc[closest_index]
return result_raw,result_name,result_inner_dia
def cable_schedule_naming(conductor_size,conductors,runs = 1,bond='BOND'):
'''
Converts the conductor size from the above functions to something that can be added to a database/schedule.
'''
if conductor_size == '1/0' or conductor_size == '2/0' or conductor_size == '3/0' or conductor_size == '4/0':
unit = "AWG"
elif int(conductor_size) > 24:
unit = 'kcmil'
else:
unit = 'AWG'
if bond == 'BOND':
bondtext = bond
elif int(bond) > 24:
bondtext = '#' + str(bond) + 'kcmil'
else:
bondtext = '#' + str(bond) + 'AWG'
if runs > 1:
cable_text = str(runs) + "x " + str(conductors) + "C #" + str(conductor_size) + unit + " + " + bondtext
else:
cable_text = str(conductors) + "C #" + str(conductor_size) + unit + " + " + bondtext
return cable_text