Source code for dragonfly_energy.opendss.powerline

# coding=utf-8
"""PowerLine in OpenDSS."""
from __future__ import division

from honeybee._lockable import lockable
from honeybee.typing import float_in_range, float_positive, int_positive, valid_ep_string

from .wire import Wire


[docs] @lockable class PowerLine(object): """Represents the properties of a power line in OpenDSS. Args: identifier: Text string for a unique wire property ID. Must contain only characters that are acceptable in OpenDSS. This will be used to identify the object across the exported geoJSON and OpenDSS files. wires: An array of Wire objects for the wires contained within the power line. heights: An array of numbers that align with the wires and denote the height of the wire above the ground in meters. Negative values indicate wires below ground. relative_xs: An array of numbers that align with the wires and denote the X offset relative to the wire line geometry in meters. For convenience, one of the conductors in a given wire is usually assigned the 0 position. phases: An array of text values that align with the wires and denote the phases of the wire. Must be one of the following values (A, B, C, N, S1, S2). phase_count: An optional integer for the number of phases carried along the power line. If None, it wil be inferred from the phases with only phases A, B, C considered distinct from one another. (Default: None). nominal_voltage: An optional number for the nominal voltage along the power line. This is not required for OpenDSS simulation since this value can be inferred from surrounding transformers and substations but it it sometimes useful to assign a value directly to the power line object. (Default: None). Properties: * identifier * display_name * wires * heights * relative_xs * phases * phase_count * nominal_voltage * wire_count * wire_ids """ __slots__ = ( '_locked', '_display_name', '_identifier', '_wire_count', '_wires', '_heights', '_relative_xs', '_phases', '_phase_count', '_nominal_voltage' ) VALID_PHASES = ('A', 'B', 'C', 'N', 'S1', 'S2') def __init__(self, identifier, wires, heights, relative_xs, phases, phase_count=None, nominal_voltage=None): """Initialize PowerLine""" self._locked = False # unlocked by default self._display_name = None self.identifier = identifier self._wire_count = len(wires) assert self._wire_count > 0, 'PowerLine must possess at least one wire.' self.wires = wires self.heights = heights self.relative_xs = relative_xs self.phases = phases self.phase_count = phase_count self.nominal_voltage = nominal_voltage
[docs] @classmethod def from_dict(cls, data): """Create a PowerLine object from a dictionary. Args: data: A dictionary representation of a PowerLine object in the format below. .. code-block:: python { 'type': 'PowerLine', 'identifier': '3P_OH_AL_ACSR_477kcmil_Hawk_12_47_0', # unique identifier 'wires': [{}], # a list of wire definitions for the wires in the line 'heights': [16], # height of the wire above the ground in meters 'relative_x': [0], # number for the x offset from the wire line in meters 'phases': ['A'] # text for the phases of the wire } """ wires = [Wire.from_dict(wd) for wd in data['wires']] pc = data['phase_count'] if 'phase_count' in data else None vol = data['nominal_voltage'] if 'nominal_voltage' in data else None p_line = cls(data['identifier'], wires, data['heights'], data['relative_xs'], data['phases'], pc, vol) if 'display_name' in data and data['display_name'] is not None: p_line.display_name = data['display_name'] return p_line
[docs] @classmethod def from_dict_abridged(cls, data, wires): """Create a PowerLine object from a dictionary. Args: data: A dictionary representation of a PowerLine object in the format below. wires: A dictionary with identifiers of wires as keys and Python wire objects as values. .. code-block:: python { 'type': 'PowerLine', 'identifier': '3P_OH_AL_ACSR_477kcmil_Hawk_12_47_0', # unique identifier 'wires': [''], # a list of wire identifiers for the wires in the line 'heights': [16], # height of the wire above the ground in meters 'relative_x': [0], # number for the x offset from the wire line in meters 'phases': ['A'] # text for the phases of the wire } """ try: wires = [wires[wd] for wd in data['wires']] except KeyError as e: raise ValueError('Failed to find {} in wires.'.format(e)) pc = data['phase_count'] if 'phase_count' in data else None vol = data['nominal_voltage'] if 'nominal_voltage' in data else None p_line = cls(data['identifier'], wires, data['heights'], data['relative_xs'], data['phases'], pc, vol) if 'display_name' in data and data['display_name'] is not None: p_line.display_name = data['display_name'] return p_line
[docs] @classmethod def from_electrical_database_dict(cls, data, wires): """Create a PowerLine from an dictionary as it appears in a database.json. Args: data: A dictionary representation of a PowerLine object in the format below. wires: A dictionary with identifiers of wires as keys and Python wire objects as values. .. code-block:: python { 'Name': '3P_OH_AL_ACSR_336kcmil_Merlin_12_47_0', # unique identifier "Line geometry": [ { "wire": "OH ACSR 336kcmil", "phase": "A", "x (m)": 0.0, "height (m)": 10 }, { "wire": "OH ACSR 336kcmil", "phase": "B", "x (m)": 0.304, "height (m)": 10 } ] } """ try: wire_ids = [w_i['wire'] for w_i in data['Line geometry']] wire_objs = [wires[w_id] for w_id in wire_ids] except KeyError as e: raise ValueError('Failed to find {} in wires.'.format(e)) heights = [w_i['height (m)'] for w_i in data['Line geometry']] rel_xs = [w_i['x (m)'] for w_i in data['Line geometry']] phases = [w_i['phase'] for w_i in data['Line geometry']] pc = data['Nphases'] if 'Nphases' in data else None vol = data['Voltage(kV)'] if 'Voltage(kV)' in data else None return cls(data['Name'], wire_objs, heights, rel_xs, phases, pc, vol)
@property def identifier(self): """Get or set a text string for the unique object identifier.""" return self._identifier @identifier.setter def identifier(self, value): self._identifier = valid_ep_string(value, 'power line identifier') @property def display_name(self): """Get or set a string for the object name without any character restrictions. If not set, this will be equal to the identifier. """ if self._display_name is None: return self._identifier return self._display_name @display_name.setter def display_name(self, value): try: self._display_name = str(value) except UnicodeEncodeError: # Python 2 machine lacking the character set self._display_name = value # keep it as unicode @property def wires(self): """Get or set an array of Wire objects for the phases of the wires.""" return self._wires @wires.setter def wires(self, value): for w in value: assert isinstance(w, Wire), 'Expected Wire object. Got {}.'.format(type(w)) self._wires = tuple(value) assert len(self._wires) == self._wire_count, \ 'Number of wires cannot be changed from {} to {}. Initialize a new ' \ 'PowerLine object to change the number of wires.'.format( len(self._wires), self._wire_count) @property def heights(self): """Get or set an array of numbers for the heights above the ground in meters.""" return self._heights @heights.setter def heights(self, value): self._heights = tuple( float_in_range(h, input_name='power line height') for h in value) assert len(self._heights) == self._wire_count, \ 'Number of heights [{}] does not match the number of wires [{}].'.format( len(self._heights), self._wire_count) @property def relative_xs(self): """Get or set a array of numbers for the X offset relative to the wire in meters. """ return self._relative_xs @relative_xs.setter def relative_xs(self, value): self._relative_xs = tuple( float_in_range(x, input_name='power line relative_x') for x in value) assert len(self._relative_xs) == self._wire_count, \ 'Number of heights [{}] does not match the number of wires [{}].'.format( len(self._relative_xs), self._wire_count) @property def phases(self): """Get or set an array of text for the phases of the wires.""" return self._phases @phases.setter def phases(self, value): for p in value: assert p in self.VALID_PHASES, 'Phase "{}" is not acceptable. ' \ 'Choose from the following:\n{}'.format(p, '\n'.join(self.VALID_PHASES)) self._phases = tuple(value) assert len(self._phases) == self._wire_count, \ 'Number of heights [{}] does not match the number of wires [{}].'.format( len(self._phases), self._wire_count) @property def phase_count(self): """Get or set an integer for the number of phases carried along the line.""" if self._phase_count is not None: return self._phase_count all_phases = [p for p in self._phases if p in ('A', 'B', 'C')] return len(all_phases) if len(all_phases) != 0 else 1 @phase_count.setter def phase_count(self, value): if value is not None: value = int_positive(value, 'power line phase count') self._phase_count = value @property def nominal_voltage(self): """Get or set a number for nominal voltage of the power line in kiloVolts.""" return self._nominal_voltage @nominal_voltage.setter def nominal_voltage(self, value): if value is not None: value = float_positive(value, 'nominal voltage') self._nominal_voltage = value @property def wire_count(self): """Get an integer for the number of wires in the power line.""" return self._wire_count @property def wire_ids(self): """Get a list of wire identifiers in the power line.""" return [wire.identifier for wire in self._wires]
[docs] def to_dict(self, abridged=False): """Get PowerLine dictionary. Args: abridged: Boolean to note whether the full dictionary describing the object should be returned (False) or just an abridged version (True), which only specifies the identifiers of wires. (Default: False). """ base = {'type': 'PowerLine'} if not abridged else {'type': 'PowerLineAbridged'} base['identifier'] = self.identifier base['wires'] = self.wire_ids if abridged else [w.to_dict() for w in self.wires] base['heights'] = self.heights base['relative_xs'] = self.relative_xs base['phases'] = self.phases if self._phase_count is not None: base['phase_count'] = self._phase_count if self._nominal_voltage is not None: base['nominal_voltage'] = self._nominal_voltage if self._display_name is not None: base['display_name'] = self._display_name return base
[docs] def to_electrical_database_dict(self): """Get Wire as it should appear in the URBANopt database.json.""" base = {'Name': self.identifier} line_geo = [] all_props = zip(self.wires, self.heights, self.relative_xs, self.phases) for wire, hgt, r_x, pha in all_props: w_dict = { 'wire': wire.identifier, 'phase': pha, 'x (m)': r_x, 'height (m)': hgt } line_geo.append(w_dict) base['Line geometry'] = line_geo if self._phase_count is not None: base['Nphases'] = self._phase_count if self._nominal_voltage is not None: base['Voltage(kV)'] = self._nominal_voltage return base
[docs] def duplicate(self): """Get a copy of this object.""" return self.__copy__()
def __copy__(self): new_obj = PowerLine( self.identifier, self.wires, self.heights, self.relative_xs, self.phases, self._phase_count, self._nominal_voltage) new_obj._display_name = self._display_name return new_obj def __key(self): """A tuple based on the object properties, useful for hashing.""" return (self.identifier,) + tuple(hash(w) for w in self.wires) + \ self.heights + self.relative_xs + self.phases + \ (self._phase_count, self._nominal_voltage) def __hash__(self): return hash(self.__key()) def __eq__(self, other): return isinstance(other, PowerLine) and self.__key() == other.__key() def __ne__(self, other): return not self.__eq__(other) def __len__(self): return len(self._wires) def __getitem__(self, key): return self._wires[key] def __iter__(self): return iter(self._wires)
[docs] def ToString(self): """Overwrite .NET ToString method.""" return self.__repr__()
def __repr__(self): """Represent wire.""" return 'PowerLine: {}'.format(self.identifier)