commit 0933ac3d2ba252a2fc4af837895d6f06e06c7b62 Author: Jeff MacKinnon Date: Tue Feb 10 17:19:57 2026 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f7ff8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv + +# uv generated files + +uv.lock +.python-version + +# MacOS things + +.DS_Store* \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..43296e5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "jmktools" +version = "0.1.0" +description = "JMK Engineering Python Toolboox" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "numpy>=2.4.2", + "pandas>=3.0.0", +] + +[project.scripts] +jmktools = "jmktools:main" + +[build-system] +requires = ["uv_build>=0.10.0,<0.11.0"] +build-backend = "uv_build" diff --git a/src/jmktools/Tables/CEC21Tables.py b/src/jmktools/Tables/CEC21Tables.py new file mode 100644 index 0000000..ef04249 --- /dev/null +++ b/src/jmktools/Tables/CEC21Tables.py @@ -0,0 +1,146 @@ + +# CEC 2021, Tables + +cec21table1 = [ + #['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] + ] +cec21table2 = [ + #['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] + ] +cec21table3 = [ + #['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] + ] +cec21table4 = [ + #['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] + ] + +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/src/jmktools/Tables/CEC24Tables.py b/src/jmktools/Tables/CEC24Tables.py new file mode 100644 index 0000000..d1171ec --- /dev/null +++ b/src/jmktools/Tables/CEC24Tables.py @@ -0,0 +1,321 @@ + +# 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] + ] + +# Table 6A Dimensions of single Class B R90XLPE, RW75XLPE, RW90XLPE and RPV90 unjacketed 600V insulated conductors for calculating conduit and tubing fill +# Only using the conductor characteristics columns, the area will be calculated in the functions. Only stranded. +cec24table6A = [ + ['size', 'diameter'], + ['14', 3.36], + ['12', 3.84], + ['10', 4.47], + ['8', 5.99], + ['6', 6.95], + ['4', 8.17], + ['3', 8.88], + ['2', 9.7], + ['1', 11.23], + ['1/0', 12.27], + ['2/0', 13.44], + ['3/0', 14.74], + ['4/0', 16.21], + ['250', 17.9], + ['300', 19.3], + ['350', 20.53], + ['400', 21.79], + ['450', 22.91], + ['500', 23.95], + ['600', 26.74], + ['700', 28.55], + ['750', 29.41], + ['800', 30.25], + ['900', 31.85], + ['1000', 33.32], + ['1250', 37.56], + ['1500', 40.68], + ['1750', 43.58], + ['2000', 46.27] +] + +# Table 6B Dimensions of single Class B R90XLPE, RW75XLPE, RW90XLPE and RPV90 unjacketed 1000V insulated conductors for calculating conduit and tubing fill +# Only using the conductor characteristics columns, the area will be calculated in the functions. Only stranded. +cec24table6B = [ + ['size', 'diameter'], + ['14', 4.12], + ['12', 4.6], + ['10', 5.23], + ['8', 5.99], # This is the same as the 600V in Table 6A + ['6', 7.71], + ['4', 8.93], + ['3', 9.64], + ['2', 10.46], + ['1', 12.49], + ['1/0', 13.53], + ['2/0', 14.7], + ['3/0', 16], + ['4/0', 17.47], + ['250', 19.17], + ['300', 20.56], + ['350', 21.79], + ['400', 23.05], + ['450', 24.17], + ['500', 25.21], + ['600', 27.24], + ['700', 29.05], + ['750', 29.91], + ['800', 30.75], + ['900', 32.35], + ['1000', 33.82], + ['1250', 38.32], + ['1500', 41.44], + ['1750', 44.34], + ['2000', 47.03] +] + +# Table 6C Dimensions of single Class B R90XLPE, RW75XLPE, R90EP, RW90XLPE, RW90EP, and RPV90 jacketed 600V insulated conductors for calculating conduit and tubing fill +# Only using the conductor characteristics columns, the area will be calculated in the functions. Only stranded. +cec24table6C = [ + ['size', 'diameter'], + ['14', 4.12], + ['12', 4.6], + ['10', 5.23], + ['8', 6.75], + ['6', 8.47], + ['4', 9.69], + ['3', 10.4], + ['2', 11.22], + ['1', 13.51], + ['1/0', 14.55], + ['2/0', 15.72], + ['3/0', 17.02], + ['4/0', 18.49], + ['250', 21.21], + ['300', 22.6], + ['350', 23.83], + ['400', 25.09], + ['450', 26.21], + ['500', 27.25], + ['600', 30.04], + ['700', 31.85], + ['750', 32.71], + ['800', 33.55], + ['900', 35.15], + ['1000', 36.62], + ['1250', 42.38], + ['1500', 45.5], + ['1750', 48.4], + ['2000', 51.09] +] + +# TODO - Add tables 6D thru 6K + +# Table 9A/B Internal diameter and cross-sectional areas of various trade conduit and tubing +# Only using the internal diameter (ID) for each of these conduits, along with the trade size. This will be used to calculate the area and fill for the various limits per Table 8 +''' +rmc = rigid metal conduit +fmc = flexible metal conduit +rpvc = rigid PVC +db2 = rigid Type EB1 PVC and rigid type DB2/ES2 PVC conduit +mlt = metallic liquid tight, flexible conduit +nmlt = non-metallic liquid tight conduit +emt = electrical metallic tubing +ent = electrical non-metallic tubing +rtrcips = rigid RTRC conduit marked IPS +rtrcid = rigid RTRC conduit marked ID +sch40 = HDPE conduit schedule 40 +sch80 = HDPE conduit schedule 80 +dr9 = HDPE DR9 conduit +dr11 = HDPE DR11 conduit +dr13 = HDPE DR13.5 conduit +dr15 = HDPE DR15.5 conduit +''' +cec24table9AB = [ + ['size','RMC', 'FMC', 'RPVC', 'DB2', 'LTMC', 'LTNMC','EMT', 'ENT', 'RTRCIPS', 'RTRCID', 'SCH40', 'SCH80', 'DR9','DR11', 'DR13', 'DR15'], + ['16', 16.05, 15.88, 14.57, None, 15.8, 15.49, 15.4, 14.58, 17.27, 11.94, 14.67, 12.75, 15.47, 16.3, 17.02, 17.43], + ['21', 21.23, 20.62, 19.77, None, 20.83, 20.45, 20.5, 19.66, 22.61, 18.29, 19.78, 17.7, 19.65, 20.73, 21.63, 22.14], + ['27', 27.0, 25.4, 25.4, None, 26.44, 25.91, 26.2, 25.37, 29.34, 24.64, 25.4, 23.06, 24.86, 26.21, 27.33, 27.97], + ['35', 35.41, 31.75, 31.75, None, 35.05, 34.54, 34.6, 33.73, 38.1, 30.99, 33.82, 31.1, 31.51, 33.31, 34.73, 35.54], + ['41', 41.25, 38.1, 38.1, None, 40.01, 40.01, 40.5, 39.57, 44.2, 37.34, 39.63, 36.63, 36.1, 38.27, 39.93, 40.85], + ['53', 52.91, 50.8, 50.8, 50.8, 51.31, 51.69, 52.1, 51.18, 56.26, 50.29, 51.18, 47.82, 45.19, 47.91, 50.18, 51.4], + ['63', 63.22, 63.5, 61.3, None, 62.99, None, 69.4, None, 69.6, 63, 61.13, 56.97, 54.63, 57.92, 60.68, 62.24], + ['78', 78.49, 76.2, 76.2, 76.2, 77.98, None, 85.2, None, 84.84, 75.69, 76.14, 71.38, 66.56, 70.6, 73.95, 75.85], + ['91', 90.68, 88.9, 88.4, 88.4, 88.9, None, 97.4, None, None, 88.39, None, None, None, None, None, None], + ['103', 102.87, 101.6, 100.1, 100.1, 101.6, None, 110, None, 109.72, 101.09, 100.26, 94.56, 85.36, 90.52, 94.83, 97.29], + ['129', 128.85, None, 125.85, 126.35, None, None, 128.9, None, 136.14, 126.24, 125.91, 119.25, 105.54, 111.93, 117.25, 120.29], + ['155', 154.76, None, 149.75, 149.75, None, None, 154.8, None, 162.05, None, 151.5, 142.86, 125.63, 133.22, 139.57, 143.19], + ['200', None, None, 199.39, None, None, None, None, None, None, None, 199.64, None, 163.58, 173.49, 181.74, 186.43], +] + +# Table 10D Dimensions of DLO cable for calculating conduit and tubing fill +cec24table10D = [ + ['size', 'diameter'], + ['14', 5.59], + ['12', 6.1], + ['10', 6.86], + ['8', 8.38], + ['6', 10.41], + ['4', 11.68], + ['3', 12.45], + ['2', 13.21], + ['1', 16.51], + ['1/0', 17.53], + ['2/0', 18.29], + ['3/0', 20.57], + ['4/0', 22.10], + ['262', 25.4], + ['313', 26.92], + ['373', 27.94], + ['444', 31.24], + ['535', 34.04], + ['646', 36.83], + ['777', 38.1], + ['929', 40.89], + ['1111', 44.45] +] + +# 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/src/jmktools/Tables/__init__.py b/src/jmktools/Tables/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/jmktools/__init__.py b/src/jmktools/__init__.py new file mode 100644 index 0000000..6d03e83 --- /dev/null +++ b/src/jmktools/__init__.py @@ -0,0 +1,6 @@ +from .circuits import * +from .general import * +from .tables import * + +def main() -> None: + print("Hello from jmktools!") diff --git a/src/jmktools/circuits.py b/src/jmktools/circuits.py new file mode 100644 index 0000000..80a303e --- /dev/null +++ b/src/jmktools/circuits.py @@ -0,0 +1,479 @@ +''' +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 .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 + # + + 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 diff --git a/src/jmktools/general.py b/src/jmktools/general.py new file mode 100644 index 0000000..38fed7a --- /dev/null +++ b/src/jmktools/general.py @@ -0,0 +1,49 @@ +''' +JMK Engineering Inc. Python Library for design and such. +by: Jeff MacKinnon + +email: jeff@jmkengineering.com + +Some General Functions and things + +''' +import pandas as pd +import numpy as np +import math +#import sqlite3 + + +def va(voltage, current,phases=3): + ''' + Calculate the Volt-Amp of a circuit + ''' + + if phases == 3: + va = (math.sqrt(3) * voltage * current) + + elif phases == 1: + va = voltage * current + else: + print("Phases needs to be 1 or 3 for now.") + + return va + +def xfmr_sc(kva, voltage, impedance, phases = 3): + + ''' + Calculate the maximum let-through current of a transformer. + + Isc = resulant short circuit + kva = kilo-voltamps + impednace in %Z + + Assume 3 phase, but calculate single phase if its selected + ''' + + if phases == 1: + Isc = (kva * 1000) / (voltage * (impedance / 100)) + + else: + Isc = (kva * 1000) / (math.sqrt(3) * voltage * (impedance / 100)) + + return Isc diff --git a/src/jmktools/tables.py b/src/jmktools/tables.py new file mode 100644 index 0000000..dd408cb --- /dev/null +++ b/src/jmktools/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! !") + +