From 2e8ca93748c8a570f47618389e724f8052ce35ef Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 29 Dec 2025 22:39:09 -0400 Subject: [PATCH] updating for voltage drop and bonding --- jepl/Tables/CEC21Tables.py | 23 +++ jepl/Tables/CEC24Tables.py | 150 +++++++++++++++++++ jepl/jepl_circuits.py | 291 +++++++++++++++---------------------- jepl/jepl_tables.py | 43 ++++++ tools.py | 97 ++++++++++++- 5 files changed, 426 insertions(+), 178 deletions(-) create mode 100644 jepl/Tables/CEC24Tables.py create mode 100644 jepl/jepl_tables.py diff --git a/jepl/Tables/CEC21Tables.py b/jepl/Tables/CEC21Tables.py index 170e3ac..ef04249 100644 --- a/jepl/Tables/CEC21Tables.py +++ b/jepl/Tables/CEC21Tables.py @@ -120,4 +120,27 @@ cec21table4 = [ ['1500', 435, 520, 585], ['1750', 455, 545, 615], ['2000', 470, 560, 630] + ] + +cec21table16 = [ + ['current','copper wire','copper bus','aluminum wire','aluminum bus'], + [20,'14',2,'12',3.5], + [30,'12',3.5,'10',5.5], + [60,'10',5.5,'8',8.5], + [100,'8',8.5,'6',10.5], + [200,'6',10.5,'4',21], + [300,'4',21,'2',26.5], + [400,'3',26.5,'1',33.5], + [500,'2',33.5,'1/0',42.5], + [600,'1',42.5,'2/0',53.5], + [800,'1/0',53.5,'3/0',67.5], + [1000,'2/0',67.5,'4/0',84], + [1200,'3/0',84,'250',127], + [1600,'4/0',107,'350',177.5], + [2000,'250',127.5,'400',203], + [2500,'350',177.5,'500',253.5], + [3000,'400',203,'600',355], + [4000,'500',253.5,'800',405.5], + [5000,'700',355,'1000',507], + [6000,'800',405.5,'1250',633.5], ] \ No newline at end of file diff --git a/jepl/Tables/CEC24Tables.py b/jepl/Tables/CEC24Tables.py new file mode 100644 index 0000000..02a12b7 --- /dev/null +++ b/jepl/Tables/CEC24Tables.py @@ -0,0 +1,150 @@ + +# CEC 2024, Tables + +cec24table1 = [ + #['size', '60', '75', '90'], + ['14', 25, 30, 35], + ['12', 30, 35, 40], + ['10', 40, 50, 55], + ['8', 60, 70,80], + ['6', 80, 95, 105], + ['4', 105, 125, 140], + ['3', 120, 145, 165], + ['2', 140, 170, 190], + ['1', 165, 195, 220], + ['1/0', 195, 230, 260], + ['2/0', 220, 265, 300], + ['3/0', 260, 310, 350], + ['4/0', 300, 360, 405], + ['250', 340, 405, 455], + ['300', 370, 445, 500], + ['350', 425, 505, 570], + ['400', 455, 545, 615], + ['500', 520, 620, 700], + ['600', 580, 690, 780], + ['700', 630, 755, 850], + ['750', 655, 785, 885], + ['800', 680, 815, 920], + ['1000', 785, 870, 980], + ['1250', 890, 1065, 1200], + ['1500', 985, 1175, 1325], + ['1750', 1070, 1280, 1445], + ['2000', 1160, 1385, 1560] + ] +cec24table2 = [ + #['size', '60', '75', '90'], + ['14', 15, 20, 25], + ['12', 20, 25, 30], + ['10', 30, 35, 40], + ['8', 40, 50, 55], + ['6', 40, 50, 55], + ['4', 70, 85, 95], + ['3', 85, 100, 115], + ['2', 95, 115, 130], + ['1', 110, 130, 145], + ['1/0', 125, 150, 170], + ['2/0', 145, 175, 195], + ['3/0', 165, 200, 225], + ['4/0', 195, 230, 260], + ['250', 215, 255, 290], + ['300', 240, 285, 320], + ['350', 260, 310, 350], + ['400', 280, 335, 380], + ['500', 320, 380, 430], + ['600', 350, 420, 475], + ['700', 385, 460, 520], + ['750', 400, 475, 535], + ['800', 410, 490, 555], + ['900', 435, 520, 585], + ['1000', 455, 545, 615], + ['1250', 495, 590, 615], + ['1500', 525, 625, 705], + ['1750', 545, 650, 735], + ['2000', 555, 665, 750] + ] +cec24table3 = [ + #['size', '60', '75', '90'], + ['12', 25, 30, 35], + ['10', 35, 40, 45], + ['8', 45, 55, 60], + ['4', 65, 75, 85], + ['3', 95, 115, 130], + ['2', 115, 135, 150], + ['1', 134, 155, 175], + ['1/0', 150, 180, 205], + ['2/0', 175, 210, 235], + ['3/0', 200, 240, 270], + ['4/0', 235, 280, 315], + ['250', 265, 315, 355], + ['300', 295, 350, 395], + ['350', 330, 395, 445], + ['400', 355, 425, 480], + ['500', 405, 485, 545], + ['600', 455, 545, 615], + ['700', 500, 595, 670], + ['750', 520, 620, 700], + ['800', 540, 645, 725], + ['900', 585, 700, 790], + ['1000', 630, 750, 845], + ['1250', 715, 855, 965], + ['1500', 795, 950, 1070], + ['1750', 880, 1050, 1185], + ['2000', 965, 1150, 1295] + ] +cec24table4 = [ + #['size', '60', '75', '90'], + ['12', 15, 20, 25], + ['10', 25, 30, 35], + ['8', 35, 40, 45], + ['6', 40, 50, 55], + ['4', 55, 65, 75], + ['3', 65, 75, 85], + ['2', 75, 90, 100], + ['1', 85, 100, 115], + ['1/0', 100, 120, 165], + ['2/0', 115, 165, 150], + ['3/0', 160, 155, 175], + ['4/0', 150, 180, 205], + ['250', 170, 205, 230], + ['300', 195, 230, 260], + ['350', 210, 250, 280], + ['400', 225, 270, 305], + ['500', 260, 310, 350], + ['600', 285, 340, 385], + ['700', 315, 375, 425], + ['750', 320, 385, 435], + ['800', 330, 395, 445], + ['900', 355, 425, 480], + ['1000', 375, 445, 500], + ['1250', 405, 485, 545], + ['1500', 435, 520, 585], + ['1750', 455, 545, 615], + ['2000', 470, 560, 630] + ] + +# Appendix Tables + +cec24tableD3 = [ + ['size','cu_dc', 'cu_100pf', 'cu_cable_90pf', 'cu_cable_80pf', 'cu_raceway_90pf', 'cu_raceway_80pf', 'al_dc', 'al_100pf', 'al_cable_90pf', 'al_cable_80pf', 'al_raceway_90pf', 'al_raceway_80pf'], + ['14', 10.2, 10.2, 9.92, 9.67, 10, 9.67, None, None, None, None, None, None], + ['12', 6.38, 6.38, 6.25, 6.1, 6.26, 6.11, 10.5, 10.5, 10.3, 10, 10.3, 9.99], + ['10', 4.03, 4.03, 3.96, 3.87, 3.96, 3.87, 6.58, 6.58, 6.44, 6.28, 6.45, 6.29], + ['8', 2.54, 2.54, 2.5, 2.45, 2.51, 2.45, 4.14, 4.14, 4.07, 3.97, 4.07, 3.98], + ['6', 1.59, 1.59, 1.58, 1.55, 1.58, 1.55, 2.62, 2.62, 2.58, 2.52, 2.58, 2.53], + ['4', 1.01, 1.01, 1.01, 0.987, 1.01, 1, 1.65, 1.65, 1.63, 1.6, 1.64, 1.61], + ['3', 0.792, 0.792, 0.797, 0.787, 0.801, 0.792, 1.3, 1.31, 1.3, 1.27, 1.3, 1.28], + ['2', 0.626, 0.627, 0.636, 0.629, 0.639, 0.635, 1.04, 1.04, 1.04, 1.02, 1.04, 1.03], + ['1', 0.5, 0.5, 0.512, 0.509, 0.516, 0.515, 0.82, 0.82, 0.823, 0.812, 0.827, 0.818], + ['1/0', 0.395, 0.396, 0.41, 0.409, 0.414, 0.415, 0.651, 0.652, 0.659, 0.652, 0.663, 0.657], + ['2/0', 0.314, 0.316, 0.331, 0.332, 0.335, 0.338, 0.516, 0.517, 0.526, 0.522, 0.53, 0.528], + ['3/0', 0.249, 0.251, 0.267, 0.27, 0.271, 0.275, 0.408, 0.409, 0.42, 0.419, 0.424, 0.425], + ['4/0', 0.197, 0.2, 0.217, 0.221, 0.221, 0.226, 0.326, 0.327, 0.341, 0.341, 0.345, 0.347], + ['250', 0.167, 0.171, 0.188, 0.193, 0.192, 0.198, 0.275, 0.277, 0.291, 0.293, 0.295, 0.299], + ['300', 0.14, 0.144, 0.162, 0.167, 0.166, 0.172, 0.229, 0.231, 0.247, 0.249, 0.25, 0.255], + ['350', 0.12, 0.125, 0.143, 0.148, 0.147, 0.154, 0.196, 0.199, 0.215, 0.218, 0.219, 0.224], + ['400', 0.105, 0.111, 0.129, 0.135, 0.133, 0.14, 0.172, 0.175, 0.191, 0.195, 0.195, 0.201], + ['500', 0.0836, 0.0912, 0.11, 0.116, 0.114, 0.121, 0.138, 0.141, 0.158, 0.163, 0.162, 0.168], + ['600', 0.0697, 0.0785, 0.0969, 0.104, 0.101, 0.109, 0.115, 0.119, 0.136, 0.142, 0.14, 0.147], + ['750', 0.0558, 0.0668, 0.085, 0.0915, 0.0889, 0.097, 0.0916, 0.0968, 0.115, 0.121, 0.119, 0.126], + ['1000', 0.0417, 0.0558, 0.0739, 0.0805, 0.0778, 0.086, 0.0686, 0.0758, 0.0933, 0.0994, 0.0973, 0.105] +] \ No newline at end of file diff --git a/jepl/jepl_circuits.py b/jepl/jepl_circuits.py index 42b69f1..667d3c3 100644 --- a/jepl/jepl_circuits.py +++ b/jepl/jepl_circuits.py @@ -10,174 +10,109 @@ Circuit Design Functions import pandas as pd import numpy as np import math -import sqlite3 -import bisect # Need to add the various table files here. -from .Tables.CEC21Tables import * +from .jepl_tables import * -def vd(current,length,resistance,runs=1): - - ''' - Calculates the voltage drop across the conductor length. - - If there are parallel runs we assume that they are approximately the same length. - Therefore from the general equation of: - - 1/Req = 1/R1 + 1/R2 ... and all R1-Rn are the same. - Req = R/n or in the equation below r = resistance / runs. - ''' - - r = (length*resistance/1000) / runs - vd=round(2*current*r,8) - return vd - -def percentvd(vd,nominal): - - percent = (vd/nominal)*100 - return percent - - -def voltage_drop(nominal_voltage, current, conductor_size, length,material ='cu', num_runs = 1, code = 'CEC'): +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. - nominal_voltage = int - current = float - conductor_size = string - - Voltage drop equation is: - - Vdrop = 2 * I * L * R/1000 - - I = load current - L = circuit length in meters - R = Resistance in ohms/km + 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 ''' - # Check to see if the db we need exists + 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' + ] - import os - import sys - if os.path.isfile('jepl-cable.db') == False: - return (print("Run init. \nCopy jeplinit.py to the same folder as this file and add \n%run jeplinit.py jepl/folder/location/\nto the notebook. Make sure there is a trailing slash.")) + 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.") - if (material == 'al'): - - try: - with sqlite3.connect("jepl-cable.db") as con: - cur = con.cursor() - cur.execute('SELECT "AC Resistance" FROM "SW-Spec 25051" WHERE "Conductor Number" = 3 AND "Conductor Size"=?', (conductor_size,)) - resistance = cur.fetchone()[0] - - #print(resistance) - - except sqlite3.OperationalError as e: - print(e) - - elif (material == 'cu'): - try: - with sqlite3.connect("jepl-cable.db") as con: - cur = con.cursor() - cur.execute('SELECT "AC Resistance" FROM "SW-Spec 25055" WHERE "Conductor Number" = 3 AND "Conductor Size"=?', (conductor_size,)) - resistance = cur.fetchone()[0] - - except sqlite3.OperationalError as e: - print(e) + # + # Find K + # + # Select the correct table + if (code == 'CEC') : + D3headers = cectableD3[0] + D3rows = cectableD3[1:] + df = pd.DataFrame(D3rows, columns=D3headers) else: - return (print("error, choose material as cu or al")) + return ('The variables were\'t right, but I\'m a loss to why.') - voltage = vd(current,length,resistance,num_runs) - percent = percentvd(voltage,nominal_voltage) - - return [voltage, percent] -# -# voltage_drop_conductors is wrong -# -# -def voltage_drop_conductors(voltage,current,distance,v_drop_percent = 0.03,runs = 1,material='cu'): - ''' - Calculates the minimum conductor size to accomodate for voltage drop based on: - - voltage -> system nominal voltage - current -> the peak/design load for the circuit - distance -> meters - v_drop_percent -> The design voltage drop (default 0.03 or 3%) - runs -> number of parallel runs (default 1) - material -> the conductor material, either 'al' or 'cu'. (default 'cu') - - First we calculate the necessary resistivity: - - - - resistivity = ohms/km - but the distance is 2x (there and back) - resistivity = ohms/[(2 * distance)/1000] {ohms/km} - ohms = v/I or (v_drop/voltage)/current - - v_drop = v_drop_percent * voltage - - resistivity = (v_drop/current)/[(2 * distance)/1000] - - This works for 1 run, but for parallel runs Rtot = R/n where n is the number of runs. - in this equation we are looking for R, not Rtot, so we multiply the top by the number of runs. - - therefore: - resistivity = [(v_drop/current)*runs]/[(2 * distance)/1000] - - ''' - - # Determine the resistivity needed in ohms/km - - v_drop = v_drop_percent * voltage - - resistivity = ((v_drop/current)*runs)/((2 * distance)/1000) - - if resistivity < 0.1214: - print("add parallel runs") - - import os - import sys - if os.path.isfile('jepl-cable.db') == False: - return (print("Run init. \nCopy jeplinit.py to the same folder as this file and add \n%run jeplinit.py jepl/folder/location/\nto the notebook. Make sure there is a trailing slash.")) - - # Lookup the conductor size that meets this resistivity. - - if (material == 'al'): - - try: - with sqlite3.connect("jepl-cable.db") as con: - cur = con.cursor() - cur.execute('SELECT "Conductor Size" FROM "SW-Spec 25055" WHERE "Conductor Number" = 3 AND "AC Resistance"= ? ', (conductor_ampacity,)) - bond_result = cur.fetchone() - bond_size = bond_result[db_index] - - except sqlite3.OperationalError as e: - print(e) + # 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 ('The variables were\'t right, but I\'m a loss to why.') + 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 diff --git a/jepl/jepl_tables.py b/jepl/jepl_tables.py new file mode 100644 index 0000000..dd408cb --- /dev/null +++ b/jepl/jepl_tables.py @@ -0,0 +1,43 @@ +''' +JMK Engineering Inc. Python Library for design and such. +by: Jeff MacKinnon + +email: jeff@jmkengineering.com +''' + +# +# Import all the table files. This is for both NEC and CEC and all of the years that we may need. +# + +from .Tables.CEC21Tables import * +from .Tables.CEC24Tables import * + + +# +# Set the default tables for CEC and NEC. +# + +''' +This will also allow you to change the table on a case-by-case basis. +Ideally we will be able to select the code year as an option in the future. + +''' + + +cectable1 = cec21table1 +cectable2 = cec21table2 +cectable3 = cec21table3 +cectable4 = cec21table4 +cectable16 = cec21table16 + +# Appendix Tables + +cectableD3 = cec24tableD3 + + +#print(cec21table1) + +def Test(): + print( "JMK Python Module works! !") + + diff --git a/tools.py b/tools.py index 7f3c099..b7c39e6 100644 --- a/tools.py +++ b/tools.py @@ -98,7 +98,75 @@ def condamp(args): result = jmk.conductor_ampacity(args.conductor, temp = temp, material = material, code = code, raceway = raceway) print(result) +def bonding(args): + if args.material == None: + material = 'cu' + else: + material = args.material + if args.code == None: + code = 'CEC' + else: + code = args.code + if args.bus == None: + bus = False + else: + bus = args.bus + + result = jmk.bonding_conductor(args.circuit_ampacity, bus = bus, material = material, code = code) + print(result) + + +def voltagedrop(args): + + voltage = args.voltage + current = args.current + size = args.conductor_size + length = args.length + if args.phases == None: + num_phase = 3 + elif args.phases == '1': + num_phase = 1 + elif args.phases == '3': + num_phase = 3 + else: + print('--phase must be 1 or 2.') + if args.material == None: + material = 'cu' + else: + material = args.material + if args.runs == None: + num_runs = 1 + else: + num_runs = args.runs + if args.runs == None: + num_runs = 1 + else: + num_runs = args.runs + if args.code == None: + code = 'CEC' + else: + code = args.code + if args.powerfactor == None: + power_factor = 'dc' + else: + power_factor = args.powerfactor + if args.raceway == None: + raceway = True + elif args.raceway == 'n': + raceway = False + else: + raceway = True + + + + if args.temp == None: + insul_temp = 75 + else: + insul_temp = args.temp + + result = jmk.voltage_drop(voltage, current, size, length, num_phase = num_phase, material = material, num_runs = num_runs, code = code, power_factor = power_factor, raceway = raceway, insul_temp = insul_temp) + print(result) # Solar @@ -156,7 +224,7 @@ try: condsize_parser.set_defaults(func=condsize) condamp_parser = subparsers.add_parser("condamp", help="Determine the ampacity of a conductor.") - condamp_parser.add_argument("conductor", type=str, help="TThe conductor size in AWG/kcmil") + condamp_parser.add_argument("conductor", type=str, help="The conductor size in AWG/kcmil") condamp_parser.add_argument("-t", "--temp", type=int, help="The temperature rating for the insulation, if none included is 60C. default: 60" ) condamp_parser.add_argument("-m", "--material", type=str, help="This should be cu or al, for copper or aluminum. If none defined cu will be used.") condamp_parser.add_argument("-c", "--code", type=str, help="Currently this will either be CEC or NEC. default CEC") @@ -164,6 +232,33 @@ try: condamp_parser.add_argument("-a", "--ambient", type=float, help="Ambient temperature in celsious. default: 30") condamp_parser.set_defaults(func=condamp) + bonding_parser = subparsers.add_parser("bonding", help="Bonding conductor for the circuit, wire or bus.") + bonding_parser.add_argument("circuit_ampacity", type=float, help="Circuit ampacity.") + bonding_parser.add_argument("-m", "--material", type=str, help="This should be cu or al, for copper or aluminum. If none defined cu will be used.") + bonding_parser.add_argument("-c", "--code", type=str, help="Currently this will either be CEC or NEC. default CEC") + bonding_parser.add_argument("-b", "--bus", type=bool, help="Bus or wire bond, bus =True, wire = False.") + bonding_parser.set_defaults(func=bonding) + + + + voltagedrop_parser = subparsers.add_parser("voltagedrop", help="Determine the voltage drop of a circuit.") + voltagedrop_parser.add_argument("voltage", type=float, help="The circuit voltage.") + voltagedrop_parser.add_argument("current", type=float, help="The circuit current") + voltagedrop_parser.add_argument("conductor_size", type=str, help="The conductor size.") + voltagedrop_parser.add_argument("length", type=float, help="The circuit length, in metres.") + voltagedrop_parser.add_argument("-t", "--temp", type=int, help="The temperature rating for the insulation, if none included is 60C. default: 75" ) + voltagedrop_parser.add_argument("-m", "--material", type=str, help="This should be cu or al, for copper or aluminum. If none defined cu will be used.") + voltagedrop_parser.add_argument("-c", "--code", type=str, help="Currently this will either be CEC or NEC. default CEC") + voltagedrop_parser.add_argument("-r", "--raceway", type=str, help="If the conductors are in a raceway use y for yes and n for no.") + voltagedrop_parser.add_argument("-pf","--powerfactor", type=str, help="Power Factor. Valid options DC, 1, 0.9, 0.8. default dc") + voltagedrop_parser.add_argument("--phases", type=str, help="Number of phases, 1 or 3, default = 3.") + voltagedrop_parser.add_argument("--runs", type=int, help="Number of circuits in parallel, default 1.") + voltagedrop_parser.set_defaults(func=voltagedrop) + + + + + #