JEPL/jepl/jepl_circuits.py
2025-12-29 22:39:09 -04:00

452 lines
14 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,material='SCH80'):
# Calculate fill requirements based on Table 8
valid_material = ['RMC', # Rigid Metal Conduit
'FMC', # Flexible Metal Conduit
'RPVC', # Rigid PVC
'EB1', # Type EB1
'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
#'DR135', # HDPE DR13.5
#'DR155' # HDPE DR15.5
]
if material not in valid_material:
return print(material + " is not a valid material. I should be 'al' or 'cu'.")
import numpy as np
x = np.array(valid_material)
db_result_index = np.where(x == material)[0][0]
if num_cc == 1:
percent_fill = 0.53
elif num_cc == 2:
percent_fill = 0.31
else:
percent_fill = 0.4
# Wire Size and diameter
wire_size = [
# ['tradesize',area mm^2]
['14',2.08],
['12',3.31],
['10',5.26],
['8',8.37],
['6',13.3],
['4',21.2],
['3',26.7],
['2',33.6],
['1',42.4],
['1/0',53.5],
['2/0',67.4],
['3/0',85],
['4/0',107],
['250',127],
['300',152],
['350',177],
['400',203],
['500',253],
['600',304],
['700',355],
['800',405],
['900',456],
['1000',507],
['1250',633],
['1500',760],
['1750',887],
['2000',1010]
]
# Calculate the area of current carrying conductors
x = np.array(wire_size)
row = np.where(x == cc_con)[0][0]
current_carrying_conductor_area = wire_size[row][1]
cc_area = current_carrying_conductor_area * num_cc
# Bond Area
row = np.where(x == bond)[0][0]
bond_area = wire_size[row][1]
# Total conductor area
area_conductors = cc_area + bond_area
#print(area_conductors)
min_trade_area = area_conductors / percent_fill # The minimum area of the conduit
#print(min_trade_area)
parameter = ' WHERE ' + material + ' > ' + str(min_trade_area)
try:
with sqlite3.connect("jepl-cec21.db") as con:
cur = con.cursor()
cur.execute('SELECT "Trade Size" from "Table9"'+ parameter )
table = cur.fetchone()
conduit = table
except sqlite3.OperationalError as e:
print(e)
result_raw = conduit[0]
result_name = str(conduit[0]) + 'mm ' + material
return result_raw,result_name
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