Source code for honeybee_energy.load.daylight

# coding=utf-8
"""Room daylight controls, including sensor location and setpoint."""
from __future__ import division
import math

from ladybug_geometry.geometry3d.pointvector import Point3D
from honeybee.typing import float_in_range, float_positive

from ..reader import parse_idf_string
from ..writer import generate_idf_string


[docs] class DaylightingControl(object): """Room daylight controls, including sensor location and setpoint. Args: sensor_position: A ladybug_geometry Point3D for the position of the daylight sensor within the parent Room. This point should lie within the Room volume in order for the results to be meaningful. The ladybug_geometry Polyface.is_point_inside method can be used to check whether a given point is inside the room volume. illuminance_setpoint: A number for the illuminance setpoint in lux beyond which electric lights are dimmed if there is sufficient daylight. Some common setpoints are listed below. (Default: 300 lux). * 50 lux - Corridors and hallways. * 150 lux - Computer work spaces (screens provide illumination). * 300 lux - Paper work spaces (reading from surfaces needing illumination). * 500 lux - Retail spaces or museums illuminating merchandise/artifacts. * 1000 lux - Operating rooms and workshops where light is needed for safety. control_fraction: A number between 0 and 1 that represents the fraction of the Room lights that are dimmed when the illuminance at the sensor position is at the specified illuminance. 1 indicates that all lights are dim-able while 0 indicates that no lights are dim-able. Deeper rooms should have lower control fractions to account for the face that the lights in the back of the space do not dim in response to suitable daylight at the front of the room. (Default: 1). min_power_input: A number between 0 and 1 for the the lowest power the lighting system can dim down to, expressed as a fraction of maximum input power. (Default: 0.3). min_light_output: A number between 0 and 1 the lowest lighting output the lighting system can dim down to, expressed as a fraction of maximum light output. (Default: 0.2). off_at_minimum: Boolean to note whether lights should switch off completely when they get to the minimum power input. (Default: False). Properties: * sensor_position * illuminance_setpoint * control_fraction * min_power_input * min_light_output * off_at_minimum * parent * has_parent * is_sensor_inside_parent """ __slots__ = ('_sensor_position', '_illuminance_setpoint', '_control_fraction', '_min_power_input', '_min_light_output', '_off_at_minimum', '_parent') def __init__(self, sensor_position, illuminance_setpoint=300, control_fraction=1, min_power_input=0.3, min_light_output=0.2, off_at_minimum=False): self.sensor_position = sensor_position self.illuminance_setpoint = illuminance_setpoint self.control_fraction = control_fraction self.min_power_input = min_power_input self.min_light_output = min_light_output self.off_at_minimum = off_at_minimum self._parent = None # _parent will be set when the object is added to a Room @property def sensor_position(self): """Get or set a Point3D for the sensor position for the daylight sensor.""" return self._sensor_position @sensor_position.setter def sensor_position(self, value): assert isinstance(value, Point3D), 'Expected Point3D for DaylightingControl ' \ 'sensor_position. Got {}.'.format(type(value)) self._sensor_position = value @property def illuminance_setpoint(self): """Get or set a number for the illuminance setpoint in lux.""" return self._illuminance_setpoint @illuminance_setpoint.setter def illuminance_setpoint(self, value): self._illuminance_setpoint = float_positive(value, 'illuminance setpoint') @property def control_fraction(self): """Get or set the fraction of the Room lights that are dimmed.""" return self._control_fraction @control_fraction.setter def control_fraction(self, value): if value is not None: self._control_fraction = float_in_range( value, 0.0, 1.0, 'daylighting control fraction') else: self._control_fraction = 1 @property def min_power_input(self): """Get or set the lowest power the lighting system can dim down to.""" return self._min_power_input @min_power_input.setter def min_power_input(self, value): if value is not None: self._min_power_input = float_in_range( value, 0.0, 1.0, 'daylighting control min_power_input') else: self._min_power_input = 0.3 @property def min_light_output(self): """Get or set the lowest lighting output the lighting system can dim down to.""" return self._min_light_output @min_light_output.setter def min_light_output(self, value): if value is not None: self._min_light_output = float_in_range( value, 0.0, 1.0, 'daylighting control min_light_output') else: self._min_light_output = 0.2 @property def off_at_minimum(self): """Get or set a boolean to indicate whether the lights switch off completely.""" return self._off_at_minimum @off_at_minimum.setter def off_at_minimum(self, value): self._off_at_minimum = bool(value) @property def parent(self): """Get the parent Room if assigned. None if not assigned.""" return self._parent @property def has_parent(self): """Get a boolean noting whether this object has a parent Room.""" return self._parent is not None @property def is_sensor_inside_parent(self): """Get a boolean for whether the sensor position is inside the parent Room. This will always be True if no parent is assigned. """ if self.parent is not None: return self.parent.geometry.is_point_inside(self.sensor_position)
[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 sensor. """ self.sensor_position = self.sensor_position.move(moving_vec)
[docs] def rotate(self, angle, axis, origin): """Rotate this object by a certain angle around an axis and origin. Args: angle: An angle for rotation in degrees. axis: Rotation axis as a Vector3D. origin: A ladybug_geometry Point3D for the origin around which the object will be rotated. """ rad_angle = math.radians(angle) self.sensor_position = self.sensor_position.rotate(axis, rad_angle, origin)
[docs] def rotate_xy(self, angle, origin): """Rotate this object counterclockwise in the world 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. """ rad_angle = math.radians(angle) self.sensor_position = self.sensor_position.rotate_xy(rad_angle, origin)
[docs] def reflect(self, plane): """Reflect this object across a plane. Args: plane: A ladybug_geometry Plane across which the object will be reflected. """ self.sensor_position = self.sensor_position.reflect(plane.n, plane.o)
[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). """ self.sensor_position = self.sensor_position.scale(factor, origin)
[docs] @classmethod def from_idf(cls, idf_string, idf_point_string): """Create a DaylightingControl object from an EnergyPlus IDF text string. Args: idf_string: A text string fully describing an EnergyPlus Daylighting:Controls definition. idf_point_string: A text string fully describing an EnergyPlus Daylighting:ReferencePoint definition. Returns: A DaylightingControl object loaded from the idf_string. """ # check the inputs ep_strs = parse_idf_string(idf_string, 'Daylighting:Controls,') ep_strs_pt = parse_idf_string(idf_point_string, 'Daylighting:ReferencePoint,') assert ep_strs[1] == ep_strs_pt[1], 'Zone names do not match between ' \ 'IDF daylight controls and reference point' # extract the properties from the string off_at_min = True if ep_strs[4].lower() == 'continuousoff' else False min_power = ep_strs[5] if ep_strs[5] != '' else 0.3 min_output = ep_strs[6] if ep_strs[6] != '' else 0.2 cntrl_fract = 1 setpoint = 500 try: cntrl_fract = ep_strs[14] if ep_strs[14] != '' else 1 setpoint = ep_strs[15] if ep_strs[15] != '' else 500 except IndexError: pass # shorter daylight controls definition lacking certain fields # extract the coordinates of the control point ptx, pty = ep_strs_pt[2], ep_strs_pt[3] try: ptz = ep_strs_pt[4] if ep_strs_pt[4] != '' else 0.8 except IndexError: pass # shorter definition lacking certain fields # return the daylighting object return cls(Point3D(ptx, pty, ptz), setpoint, cntrl_fract, min_power, min_output, off_at_min)
[docs] @classmethod def from_dict(cls, data): """Create a DaylightingControl object from a dictionary. Args: data: A DaylightingControl dictionary in following the format below. .. code-block:: python { "type": 'DaylightingControl', "sensor_position": [5, 5, 0.8] # array of xyz coordinates for the sensor "illuminance_setpoint": 300, # number for illuminance setpoint in lux "control_fraction": 0.5, # fraction of the lights that are dim-able "min_power_input": 0.3, # minimum fraction of lighting power "min_light_output": 0.2, # minimum fraction of lighting output "off_at_minimum": True # boolean for whether the lights switch off } """ assert data['type'] == 'DaylightingControl', \ 'Expected DaylightingControl dictionary. Got {}.'.format(data['type']) sensor = Point3D.from_array(data['sensor_position']) setpoint = data['illuminance_setpoint'] \ if 'illuminance_setpoint' in data else 300 cntrl_fract = data['control_fraction'] if 'control_fraction' in data else 1 min_pow = data['min_power_input'] if 'min_power_input' in data else 0.3 min_out = data['min_light_output'] if 'min_light_output' in data else 0.2 off_min = data['off_at_minimum'] if 'off_at_minimum' in data else False return cls(sensor, setpoint, cntrl_fract, min_pow, min_out, off_min)
[docs] def to_idf(self): """IDF string representation of DaylightingControl object. Returns: A tuple with two values. - idf_control -- IDF string for the Daylighting:Controls object. - idf_point -- IDF string for the Daylighting:ReferencePoint object. .. code-block:: shell Daylighting:Controls, West Zone_DaylCtrl, !- Name West Zone, !- Zone or Space Name SplitFlux, !- Daylighting Method , !- Availability Schedule Name Continuous, !- Lighting Control Type 0.3, !- Minimum Input Power Fraction for Continuous or ContinuousOff Dimming Control 0.2, !- Minimum Light Output Fraction for Continuous or ContinuousOff Dimming Control , !- Number of Stepped Control Steps 1.0, !- Probability Lighting will be Reset When Needed in Manual Stepped Control West Zone_DaylRefPt1, !- Glare Calculation Daylighting Reference Point Name 180.0, !- Glare Calculation Azimuth Angle of View Direction Clockwise from Zone y-Axis {deg} 20.0, !- Maximum Allowable Discomfort Glare Index , !- DElight Gridding Resolution {m2} West Zone_DaylRefPt1, !- Daylighting Reference Point 1 Name 1.0, !- Fraction of Lights Controlled by Reference Point 1 500.; !- Illuminance Setpoint at Reference Point 1 {lux} Daylighting:ReferencePoint, West Zone_DaylRefPt1, !- Name West Zone, !- Zone or Space Name 3.048, !- X-Coordinate of Reference Point {m} 3.048, !- Y-Coordinate of Reference Point {m} 0.9; !- Z-Coordinate of Reference Point {m} """ # create the identifiers zone_id = self.parent.identifier if self.has_parent else 'Unknown_Room' controls_name = '{}_Daylighting'.format(zone_id) point_name = '{}_Sensor'.format(zone_id) # create the IDF string for the Daylighting:Controls cntrl_type = 'ContinuousOff' if self.off_at_minimum else 'Continuous' cntrl_values = \ (controls_name, zone_id, 'SplitFlux', '', cntrl_type, self.min_power_input, self.min_light_output, '', '', '', '', '', '', point_name, self.control_fraction, self.illuminance_setpoint) cntrl_comments = \ ('name', 'zone name', 'daylight method', 'availability schedule', 'control type', 'min power input', 'min lighting input', 'control step count', 'reset probability', 'glare point', 'glare azimuth', 'max glare index', 'DElight grid res', 'reference point', 'control fraction', 'illuminance setpoint') idf_control = generate_idf_string( 'Daylighting:Controls', cntrl_values, cntrl_comments) # create the IDF string for the Daylighting:ReferencePoint sensor = self.sensor_position pt_values = (point_name, zone_id, sensor.x, sensor.y, sensor.z) pt_comments = ('name', 'zone name', 'X', 'Y', 'Z') idf_point = generate_idf_string( 'Daylighting:ReferencePoint', pt_values, pt_comments) return idf_control, idf_point
[docs] def to_dict(self, abridged=False): """DaylightingControl dictionary representation. 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 schedules. Default: False. """ return { 'type': 'DaylightingControl', 'sensor_position': self.sensor_position.to_array(), 'illuminance_setpoint': self.illuminance_setpoint, 'control_fraction': self.control_fraction, 'min_power_input': self.min_power_input, 'min_light_output': self.min_light_output, 'off_at_minimum': self.off_at_minimum }
[docs] def duplicate(self): """Get a copy of this object.""" return self.__copy__()
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __key(self): """A tuple based on the object properties, useful for hashing.""" return (hash(self.sensor_position), self.illuminance_setpoint, self.control_fraction, self.min_power_input, self.min_light_output, self.off_at_minimum) def __hash__(self): return hash(self.__key()) def __eq__(self, other): return isinstance(other, DaylightingControl) and self.__key() == other.__key() def __ne__(self, other): return not self.__eq__(other) def __copy__(self): return DaylightingControl( self.sensor_position, self.illuminance_setpoint, self.control_fraction, self.min_power_input, self.min_light_output, self.off_at_minimum) def __repr__(self): pt = self.sensor_position return 'DaylightingControl: [sensor: {}] [{} lux]'.format( '(%.2f, %.2f, %.2f)' % (pt.x, pt.y, pt.z), self.illuminance_setpoint)