diff --git a/.gitignore b/.gitignore index f2f0369..23a5a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc *.ipynb_checkpoints *.pdf +*untracked_bin # Unit test / coverage reports htmlcov/ diff --git a/graphtik_calc_flow.ipynb b/graphtik_calc_flow.ipynb index eec9247..1041fbd 100644 --- a/graphtik_calc_flow.ipynb +++ b/graphtik_calc_flow.ipynb @@ -1,5 +1,21 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prototyping the calculation flow to Determine Base Passing Wire Size\n", + "\n", + "This notebook contains two approaches to using the functions already written into pySolarCalc to determine the minimum wire size that will provide the required ampacity. No further calcuations (voltage drop or conduit sizing) are included yet.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports" + ] + }, { "cell_type": "code", "execution_count": null, @@ -13,6 +29,185 @@ "import nec_tables as nec" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Recreate spreadsheet approach that has user input wire size and checks if it is large enough\n", + "\n", + "This DAG re-creates the calculation stream from the spreadsheet that we agree results in the correct wire size. This DAG takes the wire size as an input, which is the most signifcant difference from the above DAG. \n", + "\n", + "Todo:\n", + "- **move DAG into osd and write tests**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "check_wire = graphtik.compose('check_selected_wire_size',\n", + " graphtik.operation(name='ccc_derate', needs=['ccc_count', 'table_310_15_B_3_a'], provides=['ccc_derate'])(osd.lookup),\n", + " graphtik.operation(name='amb_temp_derate', needs=['amb_temp', 'wire_insulation_temp', 'table_ambient_temp'], provides=['amb_derate'])(osd.get_ambient_temp_derate),\n", + " graphtik.operation(name='get_cond_use_derate', needs=['ccc_derate', 'amb_derate'], provides=['cond_use_derate'])(operator.mul),\n", + " graphtik.operation(name='get_parallel_current', needs=['current', 'parallel_conductors'], provides=['parallel_current'])(operator.truediv),\n", + " graphtik.operation(name='get_terminal_check_current', needs=['parallel_current', 'ocpd_derate'], provides=['terminal_check_current'])(operator.truediv),\n", + " graphtik.operation(name='get_cond_use_check_current', needs=['parallel_current', 'cond_use_derate'], provides=['cond_use_check_current'])(operator.truediv),\n", + " graphtik.operation(name='get_design_current', needs=['terminal_check_current', 'cond_use_check_current'], provides=['design_current'])(max),\n", + " graphtik.operation(name='check_terminals', needs=['wire_size', 'wire_material', 'terminal_temp_rating', 'design_current', 'ampacity_table'], provides=['terminals_passing'])(osd.get_wire_ampacity),\n", + " graphtik.operation(name='get_ocpd', needs=['current', 'voltage_type', 'ocpd_derate'], provides=['ocpd'])(osd.get_ocpd),\n", + " graphtik.operation(name='get_cable_sizing_ocpd', needs=['ocpd'], provides=['cable_sizing_ocpd'])(osd.get_cable_sizing_ocpd),\n", + " graphtik.operation(name='check_ocpd', needs=['wire_size', 'wire_material', 'wire_insulation_temp', 'ocpd_check_current', 'ampacity_table', 'cond_use_derate'], provides=['derated_cable_ampacity_per_cond'])(osd.get_wire_ampacity),\n", + " graphtik.operation(name='get_derated_cable_ampacity', needs=['derated_cable_ampacity_per_cond', 'parallel_conductors'], provides=['derated_cable_ampacity'])(operator.mul),\n", + " graphtik.operation(name='check_derated_cable_ampacity', needs=['derated_cable_ampacity', 'cable_sizing_ocpd'], provides=['derated_cable_ampacity_exceeds_ocpd'])(operator.gt),\n", + " graphtik.operation(name='check_all', needs=['terminals_passing', 'derated_cable_ampacity_exceeds_ocpd'], provides=['cable_passing'])(operator.and_)\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "amp_table = '310_B_16'\n", + "base_table_temp = nec.ampacity_tables[amp_table]['base_temp']\n", + "\n", + "check_wire_results = check_wire(**{'ccc_count': 3, 'table_310_15_B_3_a': nec.ccc_count_derate,\n", + " 'amb_temp': 34, 'wire_insulation_temp': 90, 'table_ambient_temp': base_table_temp,\n", + " 'current': 230, 'parallel_conductors': 2,\n", + " 'ocpd_derate': 0.8,\n", + " 'wire_size':'3/0', 'ocpd_check_current':None, 'ampacity_table': amp_table,\n", + " 'wire_material': 'Al', 'terminal_temp_rating': 75,\n", + " 'voltage_type':'AC'})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for key, val in check_wire_results.items():\n", + " print('{}: {}'.format(key, val))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "check_wire.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Put calc in loop to get base wire size\n", + "\n", + "This section of the notebook puts the above calculation inside a loop to find the base wire size without manually iterating. For this example, this DAG gives the same results as the excel spreadsheet.\n", + "- A starting wire size is looked up using the un-modified current, no derates, and the wire insulation temperature rating. For this example this approach returns a wire size that is below the base wire size, but only by a size or two. The intent is to limit iterations without missing the base size. Testing of many scenarios is necessary to be sure this doesn't ever return a wire size larger than the base size.\n", + "- The calculation is then re-run with larger wires until the wire passes the terminal/cont. duty and the cond of use vs ocpd checks.\n", + "- This could be improved by seperating it into two DAGs because the wire size is not an input until the second half of the calculation.\n", + "\n", + "I think this approach is viable as the basis for further development as opposed to working out a calculation stream that determines the base wire size directly without the loop. The loop only needs to be run once when initializing a table and then the base wire size can be stored. Then the same DAG can be re-run whenver the user changes the cable size. (Usually for VD. Should users be allowed to input a wire size below the min for ampacity? The flexability might be nice and people could use that to find the pass/fail breakpoint themselves, which is reassuring, but this also allows users to accidently leave a cable size that is not code compliant in their table. Maybe allow it and warn strongly?)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inputs = {'ccc_count': 3, 'table_310_15_B_3_a': nec.ccc_count_derate,\n", + " 'amb_temp': 34, 'wire_insulation_temp': 90, 'table_ambient_temp': base_table_temp,\n", + " 'current': 230, 'parallel_conductors': 2,\n", + " 'ocpd_derate': 0.8,\n", + " 'wire_size':'3/0', 'ocpd_check_current':None, 'ampacity_table': amp_table,\n", + " 'wire_material': 'Al', 'terminal_temp_rating': 75,\n", + " 'voltage_type':'AC'}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "begin_wire_size = osd.lookup(inputs['current'], nec.cable_ampacity_310_16[inputs['wire_material']][inputs['wire_insulation_temp']], keys=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inputs['wire_size'] = '12'#begin_wire_size" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inputs['wire_size']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "wire_sizes = list(nec.cable_ampacity_310_16[inputs['wire_material']][inputs['wire_insulation_temp']].keys())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "begin_wire_size_ix = wire_sizes.index(begin_wire_size)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for next_wire_size in wire_sizes[begin_wire_size_ix:]:\n", + " inputs['wire_size'] = next_wire_size\n", + " check_wire_results = check_wire(**inputs)\n", + " if check_wire_results['cable_passing']:\n", + " base_cable_size = next_wire_size\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "base_cable_size" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Functions\n", + "\n", + "None of these are currently used in the DAG. All functions used in the DAG are either in osd or are built ins." + ] + }, { "cell_type": "code", "execution_count": null, @@ -25,6 +220,17 @@ " return (wire_size, nec.cable_ampacity_310_16[mat][temp][wire_size])" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def check_wire_ampacity(current, size, mat, temp, derates=1.0):\n", + " ampacity = nec.cable_ampacity_310_16[mat][temp][size]\n", + " return (ampacity * derates) >= current" + ] + }, { "cell_type": "code", "execution_count": null, @@ -41,6 +247,68 @@ " return cond_b" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def check_wire_against_ocpd(cond, ocpd):\n", + " return cond[1] >= ocpd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Check applying derates to ocpd size instead of cable\n", + "\n", + "Is there a direct way to obtain the solution without inputting a wire size and then manually or automatically bumping wire sizes to find the solution.\n", + "Would need to apply conditions of use to the next lowest ocpd size, but that might cause incorrect breakpoints in selected sizes?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "uprated_ocpd = 90 / 0.786\n", + "uprated_ocpd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_wire_size(uprated_ocpd, 'Cu', 90)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nec.cable_ampacity_310_16['Cu'][90]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calc flow matching the flow chart - OUT OF DATE\n", + "\n", + "This doesn't quite work because the ocpd check at the end requires bumping the conductor size, but the conductor size is not an input to the calculation.\n", + "Maybe, I didn't interpet the flow chart correctly?\n", + "\n", + "The names used here (and in the next DAG) need refinement before adding this to pySolarCalc.\n", + "\n", + "**Note: The values in the needs list are passed sequentially to the function for each operation.**" + ] + }, { "cell_type": "code", "execution_count": null, @@ -53,10 +321,13 @@ " graphtik.operation(name='cond_of_use_derates', needs=['ccc_derate', 'amb_derate'], provides=['total_derate'])(operator.mul),\n", " graphtik.operation(name='current_per_conductor', needs=['current', 'parallel_conductors'], provides=['parallel_current'])(operator.mul),\n", " graphtik.operation(name='current_terminal_check', needs=['parallel_current', 'ocpd_derate'], provides=['terminal_check_current'])(operator.truediv),\n", - " graphtik.operation(name='cond_use_current', needs=['total_derate', 'parallel_current'], provides=['cond_use_check_current'])(operator.mul),\n", + " graphtik.operation(name='cond_use_current', needs=['parallel_current', 'total_derate'], provides=['cond_use_check_current'])(operator.truediv),\n", " graphtik.operation(name='wire_size_terminal', needs=['terminal_check_current', 'wire_material', 'terminal_temp_rating'], provides=['terminal_min_wire'])(get_wire_size),\n", " graphtik.operation(name='wire_size_cond_use', needs=['cond_use_check_current', 'wire_material', 'wire_insulation_temp'], provides=['cond_use_min_wire'])(get_wire_size),\n", - " graphtik.operation(name='compare_wire_sizes', needs=['terminal_min_wire', 'cond_use_min_wire'], provides=['min_wire_size'])(compare_wire_sizes)\n", + " graphtik.operation(name='compare_wire_sizes', needs=['terminal_min_wire', 'cond_use_min_wire'], provides=['min_wire_size'])(compare_wire_sizes),\n", + " graphtik.operation(name='get_ocpd', needs=['current', 'voltage_type', 'ocpd_derate'], provides=['ocpd'])(osd.get_ocpd),\n", + " graphtik.operation(name='cable_sizing_ocpd', needs=['ocpd'], provides=['cable_sizing_ocpd'])(osd.get_cable_sizing_ocpd),\n", + " graphtik.operation(name='ocpd_check', needs=['min_wire_size', 'cable_sizing_ocpd'], provides=['wire_works'])(check_wire_against_ocpd)\n", " )" ] }, @@ -66,11 +337,12 @@ "metadata": {}, "outputs": [], "source": [ - "results = graph(**{'ccc_count': 4, 'table': nec.ccc_count_derate,\n", - " 'amb_temp': 32, 'wire_insulation_temp': 90,\n", - " 'current': 60, 'parallel_conductors': 1,\n", - " 'ocpd_derate': 0.8,\n", - " 'wire_material': 'Cu', 'terminal_temp_rating': 75})" + "results = graph(**{'ccc_count': 3, 'table': nec.ccc_count_derate,\n", + " 'amb_temp': 30, 'wire_insulation_temp': 90,\n", + " 'current': 60, 'parallel_conductors': 1,\n", + " 'ocpd_derate': 0.8,\n", + " 'wire_material': 'Cu', 'terminal_temp_rating': 75,\n", + " 'voltage_type':'DC'})" ] }, { diff --git a/nec_tables.py b/nec_tables.py index 51a5556..cc2ac21 100644 --- a/nec_tables.py +++ b/nec_tables.py @@ -1,5 +1,12 @@ -# Cable ampacities from NEC Table 310.16 -cable_ampacity_310_16 = {'Cu': {60: {'12': 20, +desc_310_16 = "Table 310.15(B)(16) (formerly 310.16) Allowable Ampacities of \ +Insulated Conductors Rated Up to and Including 2000 Volts, 60C Through 90C \ +(140F Through 194F), Not More Than Three Current-Carrying Conductors in \ +Raceway, Cable, or Earth (Directly Buried), Based on Ambient Temperature \ +of 30C (86F)." + +cable_ampacity_310_16 = {'desc': desc_310_16, + 'base_temp': 30, + 'Cu': {60: {'12': 20, '10': 30, '8': 40, '6': 55, @@ -162,8 +169,13 @@ '1750': 615, '2000': 630}}} -# Cable ampacities from NEC Table 310.17 -cable_ampacity_310_17 = {'Cu': {60: {'12': 30, +desc_310_17 = "Table 310.15(B)(17) (formerly Table 310.17) Allowable \ +Ampacities of Single-Insulated Conductors Rated Up to and Including 2000 Volts\ + in Free Air, Based on Ambient Temperature of 30C (86F)." + +cable_ampacity_310_17 = {'desc': desc_310_17, + 'base_temp': 30, + 'Cu': {60: {'12': 30, '10': 40, '8': 60, '6': 80, @@ -326,6 +338,9 @@ '1750': 1185, '2000': 1295}}} +ampacity_tables = {'310_B_16': cable_ampacity_310_16, + '310_B_17': cable_ampacity_310_17} + # Standard OCPD sizes from NEC Table 240.6(A) ocpd_sizes = [15, 20, 25, 30, 35, 40, 45, 50, 60, 70, diff --git a/osd.py b/osd.py index 0dea9d0..5083c7d 100644 --- a/osd.py +++ b/osd.py @@ -43,13 +43,66 @@ def lookup(lookup_value, table, keys=True): return val +def get_wire_ampacity(size, metal, wire_insulation_temp, current=None, + ampacity_table='310_B_16', derates=1.0): + """ + Lookup ampacity of wire size or check ampacity against passed current. + + By default, function looks up the ampacity for a given conductor size from + an NEC ampacity table given a wire material, insulation temperature rating, + and the ampacity table. + + Can also be used to return a boolean to check if the ampacity of the cable + exceeds a given current. + + Use the lookup function to perform the reverse - find a wire size for a + given current. + + Parameters + ---------- + size : str + Wire size, which must be an exact match of a wire size in the table. + metal : str + Type of metal used as the curent carrying conductor. Must be in the + table passed. + 'Cu' or 'Al' + wire_insulation_temp : numeric + Temperature rating of the conductor insulation. Must be in the table. + Typically, 60, 75, or 90. + current : numeric, default None + Default of None, returns ampacity of the conductor. + Pass numeric current value to check if the ampacity of the conductor + exceeds the passed current. + derates : numeric, default 1.0 + Derating to be applied to the looked up ampacity. + ampacity_table : str, default '310_B_16' + String name for ampacity tables. See keys of the ampacity_tables + dictionary in the nec_tables for available ampacity tables. + + Returns + ------- + ampacity : numeric + Default is to return the ampacity of the conductor after applying + derates, if any are passed. + bool + If a current is passed, returns True if the looked up ampacity after + applying derates, if any are passed, is greater than the passed + current. + + """ + ampacity_table = nec.ampacity_tables[ampacity_table] + ampacity = ampacity_table[metal][wire_insulation_temp][size] + if current is None: + return ampacity * derates + else: + return (ampacity * derates) >= current + + def get_ambient_temp_derate(ambient_temp, wire_insulation_temp, - table_insulation_temp=30): + table_ambient_temp=30): """ Determine the cable derate required for the ambient temperature. - Inputs are ambient temperature and wire temperature rating. - NEC Reference - Equation 310.15(B)(2) Function uses the equation to calculate the ambient temperature derate rather than Table 310.15(B)(2)(a), 30C ambient, or @@ -62,14 +115,14 @@ def get_ambient_temp_derate(ambient_temp, wire_insulation_temp, wire_insulation_temp : numeric Wire insulation temperature rating in degrees Celsius. Typically 60, 75, or 90. - table_insulation_temp : numeric, default 30 - Base insulation temperature of the the table used to look up conductor + table_ambient_temp : numeric, default 30 + Ambient temperature of the the table used to look up conductor ampacity. Table 310.15(B)(16) is the most commonly used table to lookup conductor ampacities and is based on 30C, thus the 30C default value. Returns ------- - ambient_temp_derate : float + float The derate required for the ambient temperature. """ @@ -78,7 +131,7 @@ def get_ambient_temp_derate(ambient_temp, wire_insulation_temp, 'exceed cable rating.') return(((wire_insulation_temp - ambient_temp) / - (wire_insulation_temp - table_insulation_temp)) ** 0.5) + (wire_insulation_temp - table_ambient_temp)) ** 0.5) def get_ocpd(current, voltage_type, ocpd_derate=0.80): diff --git a/test_osd.py b/test_osd.py index e94fcaa..50a0d5a 100644 --- a/test_osd.py +++ b/test_osd.py @@ -2,6 +2,13 @@ import osd import nec_tables as nec +sizes = ['12', '10', '8', '6', '4', '3', '2', '1', + '1/0', '2/0', '3/0', '4/0', + '250', '300', '350', + '400', '500', '600', '700', + '750', '800', '900', '1000', + '1250', '1500', '1750', '2000'] + class TestLookup: """Tests of lookup function against various NEC tables.""" @@ -31,6 +38,239 @@ def test_cable_ampacities(self): keys=False) == '12' +class TestGetWireSize: + """Tests of wire size other than lookup values.""" + + def test_derate(self): + """Test the derate functionality.""" + assert osd.get_wire_ampacity('1/0', 'Cu', 90, derates=0.8) == 136 + + def test_derate_current_comparison(self): + """Test the comparison against a passed current.""" + assert osd.get_wire_ampacity('1/0', 'Cu', 90, current=169.9999) is True + assert osd.get_wire_ampacity('1/0', 'Cu', 90, current=170) is True + assert osd.get_wire_ampacity('1/0', 'Cu', 90, current=170.001) is False + + def test_derate_and_current_comparison(self): + """Test the derate and current comparison.""" + assert osd.get_wire_ampacity('1/0', 'Cu', 90, current=153.01, + derates=0.9) is False + + +class TestGetWireSize310_16: + """Tests of the get wire ampacity function. + + Expected values from test looked up directly from 2017 NEC not from the + nec_tables.py file. Serves to also test values in nec_tables.py. + """ + + ampacities_cu_60 = [20, 30, 40, 55, 70, 85, 95, 110, + 125, 145, 165, 195, + 215, 240, 260, + 280, 320, 350, 385, + 400, 410, 435, 455, + 495, 525, 545, 555] + + amp_tests_310_16_cu_60 = [(size, 'Cu', 60, exp) for size, exp in + zip(sizes, ampacities_cu_60)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_16_cu_60) + def test_310_16_cu_60(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp) == expected + + ampacities_cu_75 = [25, 35, 50, 65, 85, 100, 115, 130, + 150, 175, 200, 230, + 255, 285, 310, + 335, 380, 420, 460, + 475, 490, 520, 545, + 590, 625, 650, 665] + + amp_tests_310_16_cu_75 = [(size, 'Cu', 75, exp) for size, exp in + zip(sizes, ampacities_cu_75)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_16_cu_75) + def test_310_16_cu_75(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp) == expected + + ampacities_cu_90 = [30, 40, 55, 75, 95, 115, 130, 145, + 170, 195, 225, 260, + 290, 320, 350, + 380, 430, 475, 520, + 535, 555, 585, 615, + 665, 705, 735, 750] + + amp_tests_310_16_cu_90 = [(size, 'Cu', 90, exp) for size, exp in + zip(sizes, ampacities_cu_90)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_16_cu_90) + def test_310_16_cu_90(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp) == expected + + ampacities_al_60 = [15, 25, 35, 40, 55, 65, 75, 85, + 100, 115, 130, 150, + 170, 195, 210, + 225, 260, 285, 315, + 320, 330, 355, 375, + 405, 435, 455, 470] + + amp_tests_310_16_al_60 = [(size, 'Al', 60, exp) for size, exp in + zip(sizes, ampacities_al_60)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_16_al_60) + def test_310_16_al_60(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp) == expected + + ampacities_al_75 = [20, 30, 40, 50, 65, 75, 90, 100, + 120, 135, 155, 180, + 205, 230, 250, + 270, 310, 340, 375, + 385, 395, 425, 445, + 485, 520, 545, 560] + + amp_tests_310_16_al_75 = [(size, 'Al', 75, exp) for size, exp in + zip(sizes, ampacities_al_75)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_16_al_75) + def test_310_16_al_75(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp) == expected + + ampacities_al_90 = [25, 35, 45, 55, 75, 85, 100, 115, + 135, 150, 175, 205, + 230, 260, 280, + 305, 350, 385, 425, + 435, 445, 480, 500, + 545, 585, 615, 630] + + amp_tests_310_16_al_90 = [(size, 'Al', 90, exp) for size, exp in + zip(sizes, ampacities_al_90)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_16_al_90) + def test_310_16_al_90(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp) == expected + + +class TestGetWireSize310_17: + """Tests of the get wire ampacity function. + + Expected values from test looked up directly from 2017 NEC not from the + nec_tables.py file. Serves to also test values in nec_tables.py. + """ + + ampacities_cu_60 = [30, 40, 60, 80, 105, 120, 140, 165, + 195, 225, 260, 300, + 340, 375, 420, + 455, 515, 575, 630, + 655, 680, 730, 780, + 890, 980, 1070, 1155] + + amp_tests_310_17_cu_60 = [(size, 'Cu', 60, exp) for size, exp in + zip(sizes, ampacities_cu_60)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_17_cu_60) + def test_310_17_cu_60(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp, + ampacity_table='310_B_17') == expected + + ampacities_cu_75 = [35, 50, 70, 95, 125, 145, 170, 195, + 230, 265, 310, 360, + 405, 445, 505, + 545, 620, 690, 755, + 785, 815, 870, 935, + 1065, 1175, 1280, 1385] + + amp_tests_310_17_cu_75 = [(size, 'Cu', 75, exp) for size, exp in + zip(sizes, ampacities_cu_75)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_17_cu_75) + def test_310_17_cu_75(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp, + ampacity_table='310_B_17') == expected + + ampacities_cu_90 = [40, 55, 80, 105, 140, 165, 190, 220, + 260, 300, 350, 405, + 455, 500, 570, + 615, 700, 780, 850, + 885, 920, 980, 1055, + 1200, 1325, 1445, 1560] + + amp_tests_310_17_cu_90 = [(size, 'Cu', 90, exp) for size, exp in + zip(sizes, ampacities_cu_90)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_17_cu_90) + def test_310_17_cu_90(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp, + ampacity_table='310_B_17') == expected + + ampacities_al_60 = [25, 35, 45, 60, 80, 95, 110, 130, + 150, 175, 200, 235, + 265, 290, 330, + 355, 405, 455, 500, + 515, 535, 580, 625, + 710, 795, 875, 960] + + amp_tests_310_17_al_60 = [(size, 'Al', 60, exp) for size, exp in + zip(sizes, ampacities_al_60)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_17_al_60) + def test_310_17_al_60(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp, + ampacity_table='310_B_17') == expected + + ampacities_al_75 = [30, 40, 55, 75, 100, 115, 135, 155, + 180, 210, 240, 280, + 315, 350, 395, + 425, 485, 545, 595, + 620, 645, 700, 750, + 855, 950, 1050, 1150] + + amp_tests_310_17_al_75 = [(size, 'Al', 75, exp) for size, exp in + zip(sizes, ampacities_al_75)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_17_al_75) + def test_310_17_al_75(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp, + ampacity_table='310_B_17') == expected + + ampacities_al_90 = [35, 45, 60, 85, 115, 130, 150, 175, + 205, 235, 270, 315, + 355, 395, 445, + 480, 545, 615, 670, + 700, 725, 790, 845, + 965, 1070, 1185, 1295] + + amp_tests_310_17_al_90 = [(size, 'Al', 90, exp) for size, exp in + zip(sizes, ampacities_al_90)] + + @pytest.mark.parametrize("size,metal,temp,expected", + amp_tests_310_17_al_90) + def test_310_17_al_90(self, size, metal, temp, expected): + """Test a wire sizes with defaults.""" + assert osd.get_wire_ampacity(size, metal, temp, + ampacity_table='310_B_17') == expected + + class TestGetAmbientTempDerate: """Tests of the get_ambient_temp_derate function.""" @@ -52,6 +292,12 @@ def test_warn_ambient_over_wire_rating(self): with pytest.warns(UserWarning): osd.get_ambient_temp_derate(90, 90) + def test_table_ambient(self): + """Test when an table ambient temperature is passed.""" + amb_temp_derate = osd.get_ambient_temp_derate(38, 90, + table_ambient_temp=40) + assert amb_temp_derate == pytest.approx(1.0198039) + class TestGetOcpd: """Tests of the get_ocpd function."""