''' 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