From 5f6482eed96a557bf21cff8416d0d9b8f62b54a0 Mon Sep 17 00:00:00 2001 From: etimberg Date: Sat, 13 Oct 2018 20:08:15 -0400 Subject: [PATCH 1/5] Function to convert length units + tests --- ditto/readers/synergi/length_units.py | 93 ++++++++++++++++++++ tests/readers/synergi/__init__.py | 0 tests/readers/synergi/test_unit_converter.py | 50 +++++++++++ 3 files changed, 143 insertions(+) create mode 100644 ditto/readers/synergi/length_units.py create mode 100644 tests/readers/synergi/__init__.py create mode 100644 tests/readers/synergi/test_unit_converter.py diff --git a/ditto/readers/synergi/length_units.py b/ditto/readers/synergi/length_units.py new file mode 100644 index 00000000..1a38b989 --- /dev/null +++ b/ditto/readers/synergi/length_units.py @@ -0,0 +1,93 @@ +from enum import Enum, auto + + +class SynergiValueType(Enum): + SUL = auto() + MUL = auto() + LUL = auto() + Per_LUL = auto() + + +def convert_length_unit(value, value_type, length_units): + """ + Converts a Synergi value to an SI type. + + The value is interpreted based on: + +----------+----------------+------------+ + |value_type| length_units | Unit | + +==========+================+============+ + | SUL | English2 | inch | + +----------+----------------+------------+ + | SUL | English1 | inch | + +----------+----------------+------------+ + | SUL | Metric | mm | + +----------+----------------+------------+ + | MUL | English2 | inch | + +----------+----------------+------------+ + | MUL | English1 | inch | + +----------+----------------+------------+ + | MUL | Metric | m | + +----------+----------------+------------+ + | LUL | English2 | mile | + +----------+----------------+------------+ + | LUL | English1 | kft | + +----------+----------------+------------+ + | LUL | Metric | km | + +----------+----------------+------------+ + | Per_LUL | English2 | per mile | + +----------+----------------+------------+ + | Per_LUL | English1 | per kft | + +----------+----------------+------------+ + | Per_LUL | Metric | per km | + +----------+----------------+------------+ + + The return value is based on + + SUL: metres + MUL: metres + LUL: metres + Per_LUL: per metre + """ + + if not isinstance(value_type, SynergiValueType): + raise ValueError( + 'convert_length_unit received an invalid value_type value' + ' of {}'.format(value_type) + ) + + if not isinstance(length_units, str): + raise ValueError( + 'convert_length_unit must be passed a string length_units' + ' parameter. {} was received.'.format(length_units) + ) + + if length_units not in {'English2', 'English1', 'Metric'}: + raise ValueError( + 'convert_length_unit received an invalid length unit {}'.format( + length_units + ) + ) + + CONVERSION_FACTORS = { + 'English2': { + SynergiValueType.SUL: 0.0254, + SynergiValueType.MUL: 0.3048, + SynergiValueType.LUL: 1609.34, + SynergiValueType.Per_LUL: 0.000621371, + }, + 'English1': { + SynergiValueType.SUL: 0.0254, + SynergiValueType.MUL: 0.3048, + SynergiValueType.LUL: 304.8, + SynergiValueType.Per_LUL: 3.28084 * 10 ** -3, + }, + 'Metric': { + SynergiValueType.SUL: 10 ** -3, + SynergiValueType.MUL: 1.0, + SynergiValueType.LUL: 1e3, + SynergiValueType.Per_LUL: 10 ** -3, + } + } + + factor = CONVERSION_FACTORS[length_units][value_type] + return value * factor diff --git a/tests/readers/synergi/__init__.py b/tests/readers/synergi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/readers/synergi/test_unit_converter.py b/tests/readers/synergi/test_unit_converter.py new file mode 100644 index 00000000..411e8c08 --- /dev/null +++ b/tests/readers/synergi/test_unit_converter.py @@ -0,0 +1,50 @@ +import pytest + +from ditto.readers.synergi.length_units import ( + convert_length_unit, + SynergiValueType, +) + + +@pytest.mark.parametrize( + 'value, value_type, length_unit, expected', + [ + (39.3701, SynergiValueType.SUL, 'English2', pytest.approx(1.0)), + (3.28084, SynergiValueType.MUL, 'English2', pytest.approx(1.0)), + (1.0, SynergiValueType.LUL, 'English2', pytest.approx(1609.34)), + ( + 1609.34, + SynergiValueType.Per_LUL, + 'English2', + pytest.approx(1.0, abs=1e-5) + ), + + (39.3701, SynergiValueType.SUL, 'English1', pytest.approx(1.0)), + (3.28084, SynergiValueType.MUL, 'English1', pytest.approx(1.0)), + (1.0, SynergiValueType.LUL, 'English1', pytest.approx(304.8)), + (304.8, SynergiValueType.Per_LUL, 'English1', pytest.approx(1.0)), + + (1000.0, SynergiValueType.SUL, 'Metric', 1.0), + (2.0, SynergiValueType.MUL, 'Metric', 2.0), + (1.0, SynergiValueType.LUL, 'Metric', 1000.0), + (1000.0, SynergiValueType.Per_LUL, 'Metric', 1.0), + ] +) +def test_convert_units(value, value_type, length_unit, expected): + actual = convert_length_unit(value, value_type, length_unit) + assert actual == expected + + +def test_raises_error_invalid_value_type(): + with pytest.raises(ValueError): + convert_length_unit(1.0, 'a', 'English2') + + +def test_raises_error_invalid_unit_type(): + with pytest.raises(ValueError): + convert_length_unit(1.0, SynergiValueType.SUL, 1) + + +def test_raises_error_invalid_unit_value(): + with pytest.raises(ValueError): + convert_length_unit(1.0, SynergiValueType.SUL, 'English3') From 3153e78b251b68ed8645fcb71ba63063c2edb2b1 Mon Sep 17 00:00:00 2001 From: etimberg Date: Sat, 13 Oct 2018 20:24:59 -0400 Subject: [PATCH 2/5] Convert Synergi reader to use different convert function --- ditto/readers/synergi/read.py | 137 +++++++++++++++++----------------- 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/ditto/readers/synergi/read.py b/ditto/readers/synergi/read.py index 453a181d..76142755 100644 --- a/ditto/readers/synergi/read.py +++ b/ditto/readers/synergi/read.py @@ -33,6 +33,11 @@ from ditto.models.position import Position from ditto.models.base import Unicode +from ditto.readers.synergi.length_units import ( + convert_length_unit, + SynergiValueType, +) + logger = logging.getLogger(__name__) @@ -625,7 +630,11 @@ def parse(self, model): # Assumes MUL is medium unit length and this is feets # Converts to meters then # - api_line.length = LineLength[i] * 0.3048 + api_line.length = convert_length_unit( + LineLength[i], + SynergiValueType.MUL, + LengthUnits + ) # From element # Replace spaces with "_" @@ -851,12 +860,6 @@ def parse(self, model): # The Neutral will be handled seperately if phase != "N": - - # Assumes MUL is medium unit length = ft - # Convert to meters - # - coeff = 0.3048 - # Set the position of the first wire if ( idx == 0 @@ -865,15 +868,19 @@ def parse(self, model): and "Position1_Y_MUL" in config ): # Set X - api_wire.X = ( - config["Position1_X_MUL"] * coeff - ) # DiTTo is in meters + api_wire.X = convert_length_unit( + config["Position1_X_MUL"], + SynergiValueType.MUL, + LengthUnits + ) # Set Y # Add the reference height - api_wire.Y = ( - AveHeightAboveGround_MUL[i] + config["Position1_Y_MUL"] - ) * coeff # DiTTo is in meters + api_wire.Y = convert_length_unit( + AveHeightAboveGround_MUL[i] + config["Position1_Y_MUL"], + SynergiValueType.MUL, + LengthUnits + ) # Set the position of the second wire if ( @@ -883,15 +890,19 @@ def parse(self, model): and "Position2_Y_MUL" in config ): # Set X - api_wire.X = ( - config["Position2_X_MUL"] * coeff - ) # DiTTo is in meters + api_wire.X = convert_length_unit( + config["Position2_X_MUL"], + SynergiValueType.MUL, + LengthUnits + ) # Set Y # Add the reference height - api_wire.Y = ( - AveHeightAboveGround_MUL[i] + config["Position2_Y_MUL"] - ) * coeff # DiTTo is in meters + api_wire.Y = convert_length_unit( + AveHeightAboveGround_MUL[i] + config["Position2_Y_MUL"], + SynergiValueType.MUL, + LengthUnits + ) # Set the position of the third wire if ( @@ -901,15 +912,19 @@ def parse(self, model): and "Position3_Y_MUL" in config ): # Set X - api_wire.X = ( - config["Position3_X_MUL"] * coeff - ) # DiTTo is in meters + api_wire.X = convert_length_unit( + config["Position3_X_MUL"], + SynergiValueType.MUL, + LengthUnits + ) # Set Y # Add the reference height - api_wire.Y = ( - AveHeightAboveGround_MUL[i] + config["Position3_Y_MUL"] - ) * coeff # DiTTo is in meters + api_wire.Y = convert_length_unit( + AveHeightAboveGround_MUL[i] + config["Position3_Y_MUL"], + SynergiValueType.MUL, + LengthUnits + ) # Set the characteristics of the first wire. Use PhaseConductorID # @@ -997,24 +1012,22 @@ def parse(self, model): conductor_name_raw = NeutralConductorID[i] # Set the Spacing of the neutral - # - # Assumes MUL is medium unit length = ft - # Convert to meters - # - coeff = 0.3048 - if "Neutral_X_MUL" in config and "Neutral_Y_MUL" in config: # Set X - api_wire.X = ( - config["Neutral_X_MUL"] * coeff + api_wire.X = convert_length_unit( + config["Neutral_X_MUL"], + SynergiValueType.MUL, + LengthUnits ) # DiTTo is in meters # Set Y # Add the reference height - api_wire.Y = ( - AveHeightAboveGround_MUL[i] + config["Neutral_Y_MUL"] - ) * coeff # DiTTo is in meters + api_wire.Y = convert_length_unit( + AveHeightAboveGround_MUL[i] + config["Neutral_Y_MUL"], + SynergiValueType.MUL, + LengthUnits + ) # Set the characteristics of the wire: # - GMR @@ -1030,16 +1043,19 @@ def parse(self, model): # Set the GMR of the conductor # DiTTo is in meters and GMR is assumed to be given in feets # - api_wire.gmr = ( - conductor_mapping[conductor_name_raw]["CableGMR"] * 0.3048 + api_wire.gmr = convert_length_unit( + conductor_mapping[conductor_name_raw]["CableGMR"], + SynergiValueType.MUL, + LengthUnits ) # Set the Diameter of the conductor # Diameter is assumed to be given in inches and is converted to meters here # - api_wire.diameter = ( - conductor_mapping[conductor_name_raw]["CableDiamOutside"] - * 0.0254 + api_wire.diameter = convert_length_unit( + conductor_mapping[conductor_name_raw]["CableDiamOutside"], + SynergiValueType.SUL, + LengthUnits ) # Set the Ampacity of the conductor @@ -1062,11 +1078,11 @@ def parse(self, model): # TODO: Change this once resistance is the per unit length resistance # if api_line.length is not None: - api_wire.resistance = ( + api_wire.resistance = convert_length_unit( conductor_mapping[conductor_name_raw]["CableResistance"] - * api_line.length - * 1.0 - / 1609.34 + * api_line.length, + SynergiValueType.Per_LUL, + LengthUnits ) # Add the new Wire to the line's list of wires @@ -1110,29 +1126,16 @@ def parse(self, model): # | Z0-Z+ Z0-Z+ Z0+2*Z+ | # -------------------------- - # TODO: Check that the following is correct... - # If LengthUnits is set to English2 or not defined , then assume miles - if LengthUnits == "English2" or LengthUnits is None: - coeff = 0.000621371 - # Else, if LengthUnits is set to English1, assume kft - elif LengthUnits == "English1": - coeff = 3.28084 * 10 ** -3 - # Else, if LengthUnits is set to Metric, assume km - elif LengthUnits == "Metric": - coeff = 10 ** -3 - else: - raise ValueError( - "LengthUnits <{}> is not valid.".format(LengthUnits) - ) - - # Multiply by 1/3 - coeff *= 1.0 / 3.0 + r0 = convert_length_unit(r0, SynergiValueType.Per_LUL, LengthUnits) / 3.0 + r1 = convert_length_unit(r1, SynergiValueType.Per_LUL, LengthUnits) / 3.0 + x0 = convert_length_unit(x0, SynergiValueType.Per_LUL, LengthUnits) / 3.0 + x1 = convert_length_unit(x1, SynergiValueType.Per_LUL, LengthUnits) / 3.0 # One phase case (One phase + neutral) # if NPhase == 2: impedance_matrix = [ - [coeff * complex(float(r0) + float(r1), float(x0) + float(x1))] + [complex(float(r0) + float(r1), float(x0) + float(x1))] ] # Two phase case (Two phases + neutral) @@ -1151,9 +1154,9 @@ def parse(self, model): if b2 == 0: b2 = float(x1) - b = coeff * complex(b1, b2) + b = complex(b1, b2) - a = coeff * complex( + a = complex( (2 * float(r1) + float(r0)), (2 * float(x1) + float(x0)) ) @@ -1162,7 +1165,7 @@ def parse(self, model): # Three phases case (Three phases + neutral) # if NPhase == 4: - a = coeff * complex( + a = complex( (2 * float(r1) + float(r0)), (2 * float(x1) + float(x0)) ) b1 = float(r0) - float(r1) @@ -1177,7 +1180,7 @@ def parse(self, model): if b2 == 0: b2 = float(x1) - b = coeff * complex(b1, b2) + b = complex(b1, b2) impedance_matrix = [[a, b, b], [b, a, b], [b, b, a]] From deace6e9d62334279dee262b9752afda1b17d32a Mon Sep 17 00:00:00 2001 From: etimberg Date: Sat, 13 Oct 2018 20:39:48 -0400 Subject: [PATCH 3/5] enum.auto doesn't exist on Python <3.6 --- ditto/readers/synergi/length_units.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ditto/readers/synergi/length_units.py b/ditto/readers/synergi/length_units.py index 1a38b989..b650f598 100644 --- a/ditto/readers/synergi/length_units.py +++ b/ditto/readers/synergi/length_units.py @@ -1,11 +1,11 @@ -from enum import Enum, auto +from enum import Enum class SynergiValueType(Enum): - SUL = auto() - MUL = auto() - LUL = auto() - Per_LUL = auto() + SUL = 'SUL' + MUL = 'MUL' + LUL = 'LUL' + Per_LUL = 'Per_LUL' def convert_length_unit(value, value_type, length_units): From 53d9d14ca124d0577dd9c7cf1b180805bfa541b4 Mon Sep 17 00:00:00 2001 From: etimberg Date: Tue, 16 Oct 2018 07:39:25 -0400 Subject: [PATCH 4/5] Fix improper comment --- ditto/readers/synergi/length_units.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ditto/readers/synergi/length_units.py b/ditto/readers/synergi/length_units.py index b650f598..7ecea649 100644 --- a/ditto/readers/synergi/length_units.py +++ b/ditto/readers/synergi/length_units.py @@ -22,9 +22,9 @@ def convert_length_unit(value, value_type, length_units): +----------+----------------+------------+ | SUL | Metric | mm | +----------+----------------+------------+ - | MUL | English2 | inch | + | MUL | English2 | foot | +----------+----------------+------------+ - | MUL | English1 | inch | + | MUL | English1 | foot | +----------+----------------+------------+ | MUL | Metric | m | +----------+----------------+------------+ From 4fac03fa22bdd4946d7f210c47f6c57b6d8fdb1e Mon Sep 17 00:00:00 2001 From: etimberg Date: Tue, 16 Oct 2018 07:40:53 -0400 Subject: [PATCH 5/5] Use clearer PerLuL constant --- ditto/readers/synergi/length_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ditto/readers/synergi/length_units.py b/ditto/readers/synergi/length_units.py index 7ecea649..59c81fb7 100644 --- a/ditto/readers/synergi/length_units.py +++ b/ditto/readers/synergi/length_units.py @@ -73,7 +73,7 @@ def convert_length_unit(value, value_type, length_units): SynergiValueType.SUL: 0.0254, SynergiValueType.MUL: 0.3048, SynergiValueType.LUL: 1609.34, - SynergiValueType.Per_LUL: 0.000621371, + SynergiValueType.Per_LUL: 1/1609.34, }, 'English1': { SynergiValueType.SUL: 0.0254,