Source code for dragonfly_energy.reopt

# coding=utf-8
"""Complete set of REopt Simulation Settings."""
from __future__ import division

import os
import json
import math

from ladybug_geometry.geometry2d.pointvector import Point2D, Vector2D
from ladybug_geometry.geometry2d.polygon import Polygon2D
from honeybee.typing import float_positive, float_in_range, int_positive, \
    valid_ep_string, valid_string
from dragonfly.projection import polygon_to_lon_lat, meters_to_long_lat_factors, \
    origin_long_lat_from_location


[docs]class REoptParameter(object): """Complete set of REopt Simulation Settings. Args: financial_parameter: A FinancialParameter object to describe the parameters of the financial analysis. If None, a set of defaults will be generated. (Default: None). wind_parameter: A WindParameter object to set the cost and max amount of wind to include in the analysis. If None, no wind will be included in the analysis. (Default: None). pv_parameter: A PVParameter object to set the cost and max amount of photovoltaic to include in the analysis. If None, no PV will be included in the analysis. (Default: None). storage_parameter: A StorageParameter object to set the cost and max amount of electricity storage to include in the analysis. If None, no storage will be included in the analysis. (Default: None). generator_parameter: A GeneratorParameter object to set the cost and max amount of generators to include in the analysis. If None, no generators will be included in the analysis. (Default: None). Properties: * financial_parameter * wind_parameter * pv_parameter * storage_parameter * generator_parameter """ def __init__(self, financial_parameter=None, wind_parameter=None, pv_parameter=None, storage_parameter=None, generator_parameter=None): """Initialize SimulationParameter.""" self.financial_parameter = financial_parameter self.wind_parameter = wind_parameter self.pv_parameter = pv_parameter self.storage_parameter = storage_parameter self.generator_parameter = generator_parameter @property def financial_parameter(self): """Get or set a FinancialParameter object for financial settings.""" return self._financial_parameter @financial_parameter.setter def financial_parameter(self, value): if value is not None: assert isinstance(value, FinancialParameter), 'Expected ' \ 'FinancialParameter. Got {}.'.format(type(value)) self._financial_parameter = value else: self._financial_parameter = FinancialParameter() @property def wind_parameter(self): """Get or set a WindParameter object for wind settings.""" return self._wind_parameter @wind_parameter.setter def wind_parameter(self, value): if value is not None: assert isinstance(value, WindParameter), 'Expected ' \ 'WindParameter. Got {}.'.format(type(value)) self._wind_parameter = value else: self._wind_parameter = WindParameter() @property def pv_parameter(self): """Get or set a PVParameter object for photovoltaic settings.""" return self._pv_parameter @pv_parameter.setter def pv_parameter(self, value): if value is not None: assert isinstance(value, PVParameter), 'Expected ' \ 'PVParameter. Got {}.'.format(type(value)) self._pv_parameter = value else: self._pv_parameter = PVParameter() @property def storage_parameter(self): """Get or set a StorageParameter object for electricity storage settings.""" return self._storage_parameter @storage_parameter.setter def storage_parameter(self, value): if value is not None: assert isinstance(value, StorageParameter), 'Expected ' \ 'StorageParameter. Got {}.'.format(type(value)) self._storage_parameter = value else: self._storage_parameter = StorageParameter() @property def generator_parameter(self): """Get or set a GeneratorParameter object for electricity storage settings.""" return self._generator_parameter @generator_parameter.setter def generator_parameter(self, value): if value is not None: assert isinstance(value, GeneratorParameter), 'Expected ' \ 'GeneratorParameter. Got {}.'.format(type(value)) self._generator_parameter = value else: self._generator_parameter = GeneratorParameter()
[docs] def to_assumptions_dict(self, base_file, urdb_label): """Get REoptParameter as a dictionary representation in the REopt Lite schema. Full documentation of the REopt Lite schema can be found at. https://developer.nrel.gov/docs/energy-optimization/reopt-v1/ Args: base_file: A JSON file in the REopt Lite schema containing a base set of assumptions that will be modified based on the properties of this object. urdb_label: Text string for the Utility Rate Database (URDB) label for the particular electrical utility rate for the optimization. The label is the last term of the URL of a utility rate detail page (eg. the label for the rate at https://openei.org/apps/IURDB/rate/view/5b0d83af5457a3f276733305 is 5b0d83af5457a3f276733305). """ # load up the base dictionary assert os.path.isfile(base_file), \ 'No base JSON file found at {}.'.format(base_file) with open(base_file, 'r') as base_f: base_dict = json.load(base_f) # apply this object's properties site_dict = base_dict['Scenario']['Site'] site_dict['ElectricTariff']['urdb_label'] = urdb_label self.financial_parameter.apply_to_dict(site_dict['Financial']) self.wind_parameter.apply_to_dict(site_dict['Wind']) self.pv_parameter.apply_to_dict(site_dict['PV']) self.storage_parameter.apply_to_dict(site_dict['Storage']) self.generator_parameter.apply_to_dict(site_dict['Generator']) return base_dict
[docs] def duplicate(self): """Get a copy of this object.""" return self.__copy__()
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __copy__(self): return REoptParameter( self.financial_parameter.duplicate(), self.wind_parameter.duplicate(), self.pv_parameter.duplicate(), self.storage_parameter.duplicate(), self.generator_parameter.duplicate()) def __repr__(self): return 'REoptParameter:'
[docs]class FinancialParameter(object): """Complete set of Financial settings for REopt. Args: analysis_years: An integer for the number of years over which cost will be optimized. (Default: 25). escalation_rate: A number between 0 and 1 for the escalation rate over the analysis. (Default: 0.023). tax_rate: A number between 0 and 1 for the rate at which the owner/host of the system is taxed. (Default: 0.26). discount_rate: A number between 0 and 1 for the discount rate for the owner/host of the system. (Default: 0.083). Properties: * analysis_years * escalation_rate * tax_rate * discount_rate """ def __init__(self, analysis_years=25, escalation_rate=0.023, tax_rate=0.26, discount_rate=0.083): """Initialize FinancialParameter.""" self.analysis_years = analysis_years self.escalation_rate = escalation_rate self.tax_rate = tax_rate self.discount_rate = discount_rate @property def analysis_years(self): """Get or set a integer for the number of years to run the analysis.""" return self._analysis_years @analysis_years.setter def analysis_years(self, value): self._analysis_years = int_positive( value, input_name='financial parameter analysis years') @property def escalation_rate(self): """Get or set a fractional number for the escalation rate.""" return self._escalation_rate @escalation_rate.setter def escalation_rate(self, value): self._escalation_rate = float_in_range( value, 0, 1, input_name='financial parameter escalation rate') @property def tax_rate(self): """Get or set a fractional number for the tax rate.""" return self._tax_rate @tax_rate.setter def tax_rate(self, value): self._tax_rate = float_in_range( value, 0, 1, input_name='financial parameter tax rate') @property def discount_rate(self): """Get or set a fractional number for the discount rate.""" return self._discount_rate @discount_rate.setter def discount_rate(self, value): self._discount_rate = float_in_range( value, 0, 1, input_name='financial parameter discount rate')
[docs] def apply_to_dict(self, base_dict): """Apply this object's properties to a 'Financial' object of REopt schema.""" base_dict['analysis_years'] = self.analysis_years base_dict['escalation_pct'] = self.escalation_rate base_dict['offtaker_tax_pct'] = self.tax_rate base_dict['offtaker_discount_pct'] = self.discount_rate
[docs] def duplicate(self): """Get a copy of this object.""" return self.__copy__()
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __copy__(self): return FinancialParameter( self.analysis_years, self.escalation_rate, self.tax_rate, self.discount_rate) def __repr__(self): return 'REopt FinancialParameter:'
class _SourceParameter(object): """Base class for all REopt energy sources. Args: max_kw: A number for the maximum installed kilowatts of the energy source. (Default: 0). dollars_per_kw: A number for the installation cost of the energy source in US dollars. (Default: 500). Properties: * max_kw * dollars_per_kw """ def __init__(self, max_kw=0, dollars_per_kw=500): self.max_kw = max_kw self.dollars_per_kw = dollars_per_kw @property def max_kw(self): """Get or set a number for the maximum installed kilowatts.""" return self._max_kw @max_kw.setter def max_kw(self, value): self._max_kw = float_positive(value, input_name='reopt max kw') @property def dollars_per_kw(self): """Get or set a number for the installation cost in US dollars.""" return self._dollars_per_kw @dollars_per_kw.setter def dollars_per_kw(self, value): self._dollars_per_kw = float_positive(value, input_name='reopt dollars per kw') def apply_to_dict(self, base_dict): """Apply this object's properties to an object of REopt schema.""" base_dict['max_kw'] = self.max_kw base_dict['installed_cost_us_dollars_per_kw'] = self.dollars_per_kw def duplicate(self): """Get a copy of this object.""" return self.__copy__() def __copy__(self): return self.__class__(self.max_kw, self.dollars_per_kw)
[docs]class WindParameter(_SourceParameter): """Wind settings for REopt. Args: max_kw: A number for the maximum installed kilowatts. (Default: 0). dollars_per_kw: A number for installation cost in US dollars. (Default: 3013). Properties: * max_kw * dollars_per_kw """ def __init__(self, max_kw=0, dollars_per_kw=3013): _SourceParameter.__init__(self, max_kw, dollars_per_kw)
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __repr__(self): return 'REopt WindParameter: {} kW'.format(self.max_kw)
[docs]class PVParameter(_SourceParameter): """Photovoltaic settings for REopt. Args: max_kw: A number for the maximum installed kilowatts. (Default: 0). dollars_per_kw: A number for installation cost in US dollars. (Default: 1600). Properties: * max_kw * dollars_per_kw * max_kw_ground * dollars_per_kw_ground """ def __init__(self, max_kw=0, dollars_per_kw=1600, max_kw_ground=0, dollars_per_kw_ground=2200): _SourceParameter.__init__(self, max_kw, dollars_per_kw) self.max_kw_ground = max_kw_ground self.dollars_per_kw_ground = dollars_per_kw_ground @property def max_kw_ground(self): """Get or set a number for the maximum installed kilowatts of Ground PV.""" return self._max_kw_ground @max_kw_ground.setter def max_kw_ground(self, value): self._max_kw_ground = float_positive(value, input_name='reopt max kw') @property def dollars_per_kw_ground(self): """Get or set a number for the installation cost of ground PV in US dollars.""" return self._dollars_per_kw_ground @dollars_per_kw_ground.setter def dollars_per_kw_ground(self, value): self._dollars_per_kw_ground = \ float_positive(value, input_name='reopt dollars per kw')
[docs] def apply_to_dict(self, base_dict): """Apply this object's properties to an object of REopt schema.""" if isinstance(base_dict, dict): base_dict['max_kw'] = self.max_kw base_dict['installed_cost_us_dollars_per_kw'] = self.dollars_per_kw else: base_dict[0]['max_kw'] = self.max_kw base_dict[0]['installed_cost_us_dollars_per_kw'] = self.dollars_per_kw base_dict[1]['max_kw'] = self.max_kw_ground base_dict[1]['installed_cost_us_dollars_per_kw'] = self.dollars_per_kw_ground
def __copy__(self): return PVParameter(self.max_kw, self.dollars_per_kw, self.max_kw_ground, self.dollars_per_kw_ground)
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __repr__(self): return 'REopt PVParameter: {} kW'.format(self.max_kw)
[docs]class StorageParameter(_SourceParameter): """Electrical storage settings for REopt. Args: max_kw: A number for the maximum installed kilowatts. (Default: 0). dollars_per_kw: A number for installation cost in US dollars. (Default: 840). Properties: * max_kw * dollars_per_kw """ def __init__(self, max_kw=0, dollars_per_kw=840): _SourceParameter.__init__(self, max_kw, dollars_per_kw)
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __repr__(self): return 'REopt StorageParameter: {} kW'.format(self.max_kw)
[docs]class GeneratorParameter(_SourceParameter): """Generator settings for REopt. Args: max_kw: A number for the maximum installed kilowatts. (Default: 0). dollars_per_kw: A number for installation cost in US dollars. (Default: 500). Properties: * max_kw * dollars_per_kw """ def __init__(self, max_kw=0, dollars_per_kw=500): _SourceParameter.__init__(self, max_kw, dollars_per_kw)
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __repr__(self): return 'REopt GeneratorParameter: {} kW'.format(self.max_kw)
[docs]class GroundMountPV(object): """Represents a ground-mounted photovoltaic system in REopt. Args: identifier: Text string for a unique Photovoltaic ID. Must contain only characters that are acceptable in REopt. This will be used to identify the object across the exported geoJSON and REopt files. geometry: A Polygon2D representing the geometry of the PV field. building_identifier: An optional identifier of a dragonfly Building with which the photovoltaic system is associated. If None, the PV system will be assumed to be a community PV field that isn't associated with a particular building meter. (Default: None). Properties: * identifier * display_name * geometry * building_identifier """ __slots__ = ('_identifier', '_display_name', '_geometry', '_building_identifier') def __init__(self, identifier, geometry, building_identifier=None): """Initialize GroundMountPV.""" self.identifier = identifier self._display_name = None assert isinstance(geometry, Polygon2D), 'Expected ladybug_geometry ' \ 'Polygon2D for GroundMountPV geometry. Got {}'.format(type(geometry)) self._geometry = geometry self.building_identifier = building_identifier
[docs] @classmethod def from_dict(cls, data): """Initialize an GroundMountPV from a dictionary. Args: data: A dictionary representation of an GroundMountPV object. """ # check the type of dictionary assert data['type'] == 'GroundMountPV', 'Expected GroundMountPV ' \ 'dictionary. Got {}.'.format(data['type']) geo = Polygon2D.from_dict(data['geometry']) pv_obj = cls(data['identifier'], geo) if 'display_name' in data and data['display_name'] is not None: pv_obj.display_name = data['display_name'] if 'building_identifier' in data and data['building_identifier'] is not None: pv_obj.building_identifier = data['building_identifier'] return pv_obj
@property def identifier(self): """Get or set the text string for unique object identifier.""" return self._identifier @identifier.setter def identifier(self, identifier): self._identifier = valid_ep_string(identifier, '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 building_identifier(self): """Get or set the text string for a Building associated with the PV field.""" return self._building_identifier @building_identifier.setter def building_identifier(self, identifier): if identifier is not None: identifier = valid_string(identifier, 'building identifier') self._building_identifier = identifier @property def geometry(self): """Get a Polygon2D representing the photovoltaic field.""" return self._geometry
[docs] def move(self, moving_vec): """Move this object along a vector. Args: moving_vec: A ladybug_geometry Vector3D with the direction and distance to move the object. """ self._geometry = self._geometry.move(Vector2D(moving_vec.x, moving_vec.y))
[docs] def rotate_xy(self, angle, origin): """Rotate this object counterclockwise in the XY plane by a certain angle. Args: angle: An angle in degrees. origin: A ladybug_geometry Point3D for the origin around which the object will be rotated. """ self._geometry = self._geometry.rotate( math.radians(angle), Point2D(origin.x, origin.y))
[docs] def reflect(self, plane): """Reflect this object across a plane. Args: plane: A ladybug_geometry Plane across which the object will be reflected. """ assert plane.n.z == 0, \ 'Plane normal must be in XY plane to use it on dragonfly object reflect.' norm = Vector2D(plane.n.x, plane.n.y) origin = Point2D(plane.o.x, plane.o.y) self._geometry = self._geometry.reflect(norm, origin)
[docs] def scale(self, factor, origin=None): """Scale this object by a factor from an origin point. Args: factor: A number representing how much the object should be scaled. origin: A ladybug_geometry Point3D representing the origin from which to scale. If None, it will be scaled from the World origin (0, 0, 0). """ ori = Point2D(origin.x, origin.y) if origin is not None else None self._geometry = self._geometry.scale(factor, ori)
[docs] def to_dict(self): """GroundMountPV dictionary representation.""" base = {'type': 'GroundMountPV'} base['identifier'] = self.identifier base['geometry'] = self.geometry.to_dict() if self._display_name is not None: base['display_name'] = self.display_name if self._building_identifier is not None: base['building_identifier'] = self.building_identifier return base
[docs] def to_geojson_dict(self, location, point): """Get GroundMountPV dictionary as it appears in an URBANopt geoJSON. Args: location: A ladybug Location object possessing longitude and latitude data. point: A ladybug_geometry Point2D for where the location object exists within the space of a scene. The coordinates of this point are expected to be in the units of this Model. (Default: (0, 0)). """ # get the conversion factors over to (longitude, latitude) origin_lon_lat = origin_long_lat_from_location(location, point) convert_facs = meters_to_long_lat_factors(origin_lon_lat) # create the GeoJSON dictionary pts = [(pt.x, pt.y) for pt in self.geometry.vertices] coords = [polygon_to_lon_lat(pts, origin_lon_lat, convert_facs)] base = { 'type': 'Feature', 'properties': { 'id': self.identifier, 'geometryType': 'Rectangle', 'name': self.display_name, 'type': 'District System', 'footprint_area': self.geometry.area, 'footprint_perimeter': self.geometry.perimeter }, 'geometry': { 'type': 'Polygon', 'coordinates': coords } } if self.building_identifier is None: base['properties']['district_system_type'] = 'Community Photovoltaic' else: base['properties']['district_system_type'] = 'Ground Mount Photovoltaic' base['properties']['associated_building_id'] = self.building_identifier return base
[docs] def duplicate(self): """Get a copy of this object.""" return self.__copy__()
def __copy__(self): new_obj = GroundMountPV(self.identifier, self.geometry, self.building_identifier) new_obj._display_name = self._display_name return new_obj
[docs] def ToString(self): return self.__repr__()
def __repr__(self): return 'GroundMountPV: {}'.format(self.display_name)