483 lines
16 KiB
Python
483 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
|
|
# Get the new inner diameter
|
|
result_inner_dia = conduit_table[material].iloc[closest_index]
|
|
result_inner_area = math.pi * (result_inner_dia/2) ** 2
|
|
resulting_percent_fill = area_conductors / result_inner_area
|
|
|
|
|
|
return result_raw,result_name,result_inner_dia,resulting_percent_fill
|
|
|
|
|
|
|
|
|
|
|
|
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
|