Source code for archetypal.template.load

"""archetypal ZoneLoad."""

import collections
import logging as lg
import math
import sqlite3
from enum import Enum

import numpy as np
import pandas as pd
from sigfig import round
from validator_collection import checkers, validators

from archetypal import settings
from archetypal.template.schedule import UmiSchedule
from archetypal.template.umi_base import UmiBase
from archetypal.utils import log, reduce, timeit


class DimmingTypes(Enum):
    """DimmingType class."""

    Continuous = 0
    Off = 1
    Stepped = 2

    def __lt__(self, other):
        """Assert if self is lower then other."""
        return self._value_ < other._value_

    def __gt__(self, other):
        """Assert if self is greater then other."""
        return self._value_ > other._value_


[docs]class ZoneLoad(UmiBase): """Zone Loads. Important: Please note that the calculation of the equipment power density will sum up the electric equipment objects as well as the gas equipment objects. .. image:: ../images/template/zoneinfo-loads.png """ __slots__ = ( "_dimming_type", "_equipment_availability_schedule", "_lights_availability_schedule", "_occupancy_schedule", "_equipment_power_density", "_illuminance_target", "_lighting_power_density", "_people_density", "_is_equipment_on", "_is_lighting_on", "_is_people_on", "_area", "_volume", ) def __init__( self, Name, EquipmentPowerDensity=0, EquipmentAvailabilitySchedule=None, LightingPowerDensity=0, LightsAvailabilitySchedule=None, PeopleDensity=0, OccupancySchedule=None, IsEquipmentOn=True, IsLightingOn=True, IsPeopleOn=True, DimmingType=DimmingTypes.Continuous, IlluminanceTarget=500, area=1, volume=1, **kwargs, ): """Initialize a new ZoneLoad object. Args: DimmingType (int): Different types to dim the lighting to respect the IlluminanceTarget and taking into account the daylight illuminance: - Continuous = 0, the overhead lights dim continuously and linearly from (maximum electric power, maximum light output) to ( minimum electric power, minimum light output) as the daylight illuminance increases. The lights stay on at the minimum point with further increase in the daylight illuminance. - Off = 1, Lights switch off completely when the minimum dimming point is reached. - Stepped = 2, the electric power input and light output vary in discrete, equally spaced steps. EquipmentAvailabilitySchedule (UmiSchedule): The name of the schedule (Day | Week | Year) that modifies the design level parameter for electric equipment. EquipmentPowerDensity (float): Equipment Power Density in the zone (W/m²). IlluminanceTarget (float): Number of lux to be respected in the zone LightingPowerDensity (float): Lighting Power Density in the zone (W/m²). LightsAvailabilitySchedule (UmiSchedule): The name of the schedule (Day | Week | Year) that modifies the design level parameter for lighting. OccupancySchedule (UmiSchedule): The name of the schedule (Day | Week | Year) that modifies the number of people parameter for electric equipment. IsEquipmentOn (bool): If True, heat gains from Equipment are taken into account for the zone's load calculation. IsLightingOn (bool): If True, heat gains from Lights are taken into account for the zone's load calculation. IsPeopleOn (bool): If True, heat gains from People are taken into account for the zone's load calculation. PeopleDensity (float): Density of people in the zone (people/m²). area (float): The floor area assiciated to this zone load object. **kwargs: Other keywords passed to the parent constructor :class:`UmiBase`. """ super(ZoneLoad, self).__init__(Name, **kwargs) self.EquipmentPowerDensity = EquipmentPowerDensity self.EquipmentAvailabilitySchedule = EquipmentAvailabilitySchedule self.LightingPowerDensity = LightingPowerDensity self.LightsAvailabilitySchedule = LightsAvailabilitySchedule self.PeopleDensity = PeopleDensity self.OccupancySchedule = OccupancySchedule self.IsEquipmentOn = IsEquipmentOn self.IsLightingOn = IsLightingOn self.IsPeopleOn = IsPeopleOn self.DimmingType = DimmingTypes(DimmingType) self.IlluminanceTarget = IlluminanceTarget self.area = area self.volume = volume @property def DimmingType(self): """Get or set the dimming type. Hint: To set the value an int or a string is supported. Choices are (<DimmingTypes.Continuous: 0>, <DimmingTypes.Off: 1>, <DimmingTypes.Stepped: 2>) """ return self._dimming_type @DimmingType.setter def DimmingType(self, value): if checkers.is_string(value): assert DimmingTypes[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in DimmingTypes)}" ) self._dimming_type = DimmingTypes[value] elif checkers.is_numeric(value): assert DimmingTypes[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in DimmingTypes)}" ) self._dimming_type = DimmingTypes(value) elif isinstance(value, DimmingTypes): self._dimming_type = value else: raise ValueError(f"Could not set DimmingType with value '{value}'") @property def EquipmentAvailabilitySchedule(self): """Get or set the equipment availability schedule.""" return self._equipment_availability_schedule @EquipmentAvailabilitySchedule.setter def EquipmentAvailabilitySchedule(self, value): if value is not None: assert isinstance(value, UmiSchedule), ( f"Input value error for '{value}'. Value must be of type '" f"{UmiSchedule}', not {type(value)}" ) # set quantity on schedule as well value.quantity = self.EquipmentPowerDensity self._equipment_availability_schedule = value @property def EquipmentPowerDensity(self): """Get or set the equipment power density [W/m²].""" return self._equipment_power_density @EquipmentPowerDensity.setter def EquipmentPowerDensity(self, value): self._equipment_power_density = validators.float( value, minimum=0, allow_empty=True ) @property def IlluminanceTarget(self): """Get or set the illuminance target [lux].""" return self._illuminance_target @IlluminanceTarget.setter def IlluminanceTarget(self, value): self._illuminance_target = validators.float(value, minimum=0) @property def LightingPowerDensity(self): """Get or set the lighting power density [W/m²].""" return self._lighting_power_density @LightingPowerDensity.setter def LightingPowerDensity(self, value): self._lighting_power_density = validators.float( value, minimum=0, allow_empty=True ) @property def LightsAvailabilitySchedule(self) -> UmiSchedule: """Get or set the lights availability schedule.""" return self._lights_availability_schedule @LightsAvailabilitySchedule.setter def LightsAvailabilitySchedule(self, value): if value is not None: assert isinstance(value, UmiSchedule), ( f"Input value error for '{value}'. Value must be of type '" f"{UmiSchedule}', not {type(value)}" ) # set quantity on schedule as well value.quantity = self.LightingPowerDensity self._lights_availability_schedule = value @property def OccupancySchedule(self) -> UmiSchedule: """Get or set the occupancy schedule.""" return self._occupancy_schedule @OccupancySchedule.setter def OccupancySchedule(self, value): if value is not None: assert isinstance(value, UmiSchedule), ( f"Input value error for '{value}'. Value must be if type '" f"{UmiSchedule}', not {type(value)}" ) # set quantity on schedule as well value.quantity = self.PeopleDensity self._occupancy_schedule = value @property def PeopleDensity(self): """Get or set the people density [ppl/m²].""" return self._people_density @PeopleDensity.setter def PeopleDensity(self, value): self._people_density = validators.float(value, minimum=0) @property def IsEquipmentOn(self): """Get or set the use of equipment [bool].""" return self._is_equipment_on @IsEquipmentOn.setter def IsEquipmentOn(self, value): assert isinstance(value, bool), ( f"Input error with value {value}. IsEquipmentOn must " f"be a boolean, not a {type(value)}" ) self._is_equipment_on = value @property def IsLightingOn(self): """Get or set the use of lighting [bool].""" return self._is_lighting_on @IsLightingOn.setter def IsLightingOn(self, value): assert isinstance(value, bool), ( f"Input error with value {value}. IsLightingOn must " f"be a boolean, not a {type(value)}" ) self._is_lighting_on = value @property def IsPeopleOn(self): """Get or set people [bool].""" return self._is_people_on @IsPeopleOn.setter def IsPeopleOn(self, value): assert isinstance(value, bool), ( f"Input error with value {value}. IsPeopleOn must " f"be a boolean, not a {type(value)}" ) self._is_people_on = value @property def area(self): """Get or set the floor area of the zone associated to this zone load [m²].""" return self._area @area.setter def area(self, value): self._area = validators.float(value, minimum=0) @property def volume(self): """Get or set the volume of the zone associated to this zone load [m³].""" return self._volume @volume.setter def volume(self, value): self._volume = validators.float(value, minimum=0)
[docs] @classmethod def from_dict(cls, data, schedules, **kwargs): """Create a ZoneLoad from a dictionary. Args: data (dict): A python dictionary with the structure shown bellow. schedules (dict): A python dictionary of UmiSchedules with their id as keys. **kwargs: keywords passed to parent constructors. .. code-block:: python { "$id": "172", "DimmingType": 1, "EquipmentAvailabilitySchedule": { "$ref": "147" }, "EquipmentPowerDensity": 8.0, "IlluminanceTarget": 500.0, "LightingPowerDensity": 12.0, "LightsAvailabilitySchedule": { "$ref": "146" }, "OccupancySchedule": { "$ref": "145" }, "IsEquipmentOn": true, "IsLightingOn": true, "IsPeopleOn": true, "PeopleDensity": 0.055, "Category": "Office Spaces", "Comments": null, "DataSource": "MIT_SDL", "Name": "B_Off_0 loads" }, """ _id = data.pop("$id") return cls( id=_id, EquipmentAvailabilitySchedule=schedules[ data.pop("EquipmentAvailabilitySchedule")["$ref"] ], LightsAvailabilitySchedule=schedules[ data.pop("LightsAvailabilitySchedule")["$ref"] ], OccupancySchedule=schedules[data.pop("OccupancySchedule")["$ref"]], **data, **kwargs, )
@classmethod @timeit def from_zone(cls, zone, zone_ep, **kwargs): """Create a ZoneLoad object from a :class:`ZoneDefinition`. Args: zone_ep: zone (ZoneDefinition): zone to gets information from kwargs: keywords passed to the parent constructor. """ # If Zone is not part of total area, it should not have a ZoneLoad object. if not zone._is_part_of_total_floor_area: return None # Get schedule index for different loads and create ZoneLoad arguments # Verify if Equipment in zone # create database connection with sqlite3 with sqlite3.connect(str(zone_ep.theidf.sql_file)) as conn: sql_query = "select ifnull(ZoneIndex, null) from Zones where ZoneName=?" t = (zone.Name.upper(),) c = conn.cursor() c.execute(sql_query, t) (zone_index,) = c.fetchone() sql_query = "select t.* from NominalElectricEquipment t where ZoneIndex=?" nominal_elec = pd.read_sql(sql_query, conn, params=(zone_index,)) sql_query = "select t.* from NominalGasEquipment t where ZoneIndex=?" nominal_gas = pd.read_sql(sql_query, conn, params=(zone_index,)) def get_schedule(series): """Compute the schedule with quantity for nominal equipment series.""" sched = series["ScheduleIndex"] sql_query = ( "select t.ScheduleName, t.ScheduleType as M from " "Schedules t where ScheduleIndex=?" ) sched_name, sched_type = c.execute(sql_query, (int(sched),)).fetchone() level_ = float(series["DesignLevel"]) if level_ > 0: return UmiSchedule.from_epbunch( zone_ep.theidf.schedules_dict[sched_name.upper()], quantity=level_, ) schedules = [] if not nominal_elec.empty: # compute schedules series elec_scds = nominal_elec.apply(get_schedule, axis=1).to_list() elec_scds = list(filter(None, elec_scds)) schedules.extend(elec_scds) if not nominal_gas.empty: # compute schedules series gas_scds = nominal_gas.apply(get_schedule, axis=1).to_list() gas_scds = list(filter(None, gas_scds)) schedules.extend(gas_scds) if schedules: EquipmentAvailabilitySchedule = reduce( UmiSchedule.combine, schedules, quantity=True, ) EquipmentPowerDensity = ( EquipmentAvailabilitySchedule.quantity / zone.area ) else: EquipmentAvailabilitySchedule = None EquipmentPowerDensity = np.NaN # Verifies if Lights in zone sql_query = "select t.* from NominalLighting t where ZoneIndex=?" nominal_lighting = pd.read_sql(sql_query, conn, params=(zone_index,)) lighting_schedules = [] if not nominal_lighting.empty: # compute schedules series light_scds = nominal_lighting.apply(get_schedule, axis=1) lighting_schedules.extend(light_scds) if lighting_schedules: LightsAvailabilitySchedule = reduce( UmiSchedule.combine, lighting_schedules, quantity=True, ) LightingPowerDensity = LightsAvailabilitySchedule.quantity / zone.area else: LightsAvailabilitySchedule = None LightingPowerDensity = np.NaN # Verifies if People in zone def get_schedule(series): """Compute schedule with quantity for nominal equipment series.""" sched = series["NumberOfPeopleScheduleIndex"] sql_query = ( "select t.ScheduleName, t.ScheduleType as M from " "Schedules t where ScheduleIndex=?" ) sched_name, sched_type = c.execute(sql_query, (int(sched),)).fetchone() return UmiSchedule.from_epbunch( zone_ep.theidf.schedules_dict[sched_name.upper()], quantity=series["NumberOfPeople"], ) sql_query = "select t.* from NominalPeople t where ZoneIndex=?" nominal_people = pd.read_sql(sql_query, conn, params=(zone_index,)) occupancy_schedules = [] if not nominal_people.empty: # compute schedules series occ_scds = nominal_people.apply(get_schedule, axis=1) occupancy_schedules.extend(occ_scds) if occupancy_schedules: OccupancySchedule = reduce( UmiSchedule.combine, occupancy_schedules, quantity=lambda x: sum(obj.quantity for obj in x), ) PeopleDensity = OccupancySchedule.quantity / zone.area else: OccupancySchedule = None PeopleDensity = np.NaN name = zone.Name + "_ZoneLoad" z_load = cls( Name=name, DimmingType=_resolve_dimming_type(zone, zone_ep), EquipmentAvailabilitySchedule=EquipmentAvailabilitySchedule, EquipmentPowerDensity=float(EquipmentPowerDensity), IlluminanceTarget=_resolve_illuminance_target(zone, zone_ep), LightingPowerDensity=float(LightingPowerDensity), LightsAvailabilitySchedule=LightsAvailabilitySchedule, OccupancySchedule=OccupancySchedule, IsEquipmentOn=float(EquipmentPowerDensity) > 0, IsLightingOn=float(LightingPowerDensity) > 0, IsPeopleOn=float(PeopleDensity) > 0, PeopleDensity=float(PeopleDensity), Category=zone.DataSource, area=zone.area, volume=zone.volume, **kwargs, ) return z_load
[docs] def combine(self, other, weights=None): """Combine two ZoneLoad objects together. Returns a new object. Args: other (ZoneLoad): The other ZoneLoad object. weights (list-like, optional): A list-like object of len 2. If None, the `settings.zone_weight` of the objects is used. Returns: (ZoneLoad): the combined ZoneLoad object. """ # Check if other is None. Simply return self if not other: return self if not self: return other # Check if other is the same type as self if not isinstance(other, self.__class__): msg = "Cannot combine %s with %s" % ( self.__class__.__name__, other.__class__.__name__, ) raise NotImplementedError(msg) # Check if other is not the same as self if self == other: return self meta = self._get_predecessors_meta(other) if not weights: zone_weight = settings.zone_weight weights = [ getattr(self, str(zone_weight)), getattr(other, str(zone_weight)), ] log( 'using zone {} "{}" as weighting factor in "{}" ' "combine.".format( zone_weight, " & ".join(list(map(str, map(int, weights)))), self.__class__.__name__, ) ) new_attr = dict( DimmingType=max(self.DimmingType, other.DimmingType), EquipmentAvailabilitySchedule=UmiSchedule.combine( self.EquipmentAvailabilitySchedule, other.EquipmentAvailabilitySchedule, weights=[self.area, other.area], quantity=True, ), EquipmentPowerDensity=self.float_mean( other, "EquipmentPowerDensity", weights ), IlluminanceTarget=self.float_mean(other, "IlluminanceTarget", weights), LightingPowerDensity=self.float_mean( other, "LightingPowerDensity", weights ), LightsAvailabilitySchedule=UmiSchedule.combine( self.LightsAvailabilitySchedule, other.LightsAvailabilitySchedule, weights=[self.area, other.area], quantity=True, ), OccupancySchedule=UmiSchedule.combine( self.OccupancySchedule, other.OccupancySchedule, weights=[self.area, other.area], quantity=True, ), IsEquipmentOn=any([self.IsEquipmentOn, other.IsEquipmentOn]), IsLightingOn=any([self.IsLightingOn, other.IsLightingOn]), IsPeopleOn=any([self.IsPeopleOn, other.IsPeopleOn]), PeopleDensity=self.float_mean(other, "PeopleDensity", weights), ) new_obj = self.__class__( **meta, **new_attr, allow_duplicates=self.allow_duplicates ) new_obj.area = self.area + other.area new_obj.volume = self.volume + other.volume new_obj.predecessors.update(self.predecessors + other.predecessors) return new_obj
[docs] def validate(self): """Validate object and fill in missing values.""" if not self.DimmingType: self.DimmingType = DimmingTypes.Continuous if not self.EquipmentAvailabilitySchedule: self.EquipmentAvailabilitySchedule = UmiSchedule.constant_schedule() if not self.EquipmentPowerDensity: self.EquipmentPowerDensity = 0 if not self.IlluminanceTarget: self.IlluminanceTarget = 500 if not self.LightingPowerDensity: self.LightingPowerDensity = 0 if not self.LightsAvailabilitySchedule: self.LightsAvailabilitySchedule = UmiSchedule.constant_schedule() if not self.OccupancySchedule: self.OccupancySchedule = UmiSchedule.constant_schedule() if not self.IsEquipmentOn: self.IsEquipmentOn = False if not self.IsLightingOn: self.IsLightingOn = False if not self.IsPeopleOn: self.IsPeopleOn = False if not self.PeopleDensity: self.PeopleDensity = 0 return self
[docs] def mapping(self, validate=True): """Get a dict based on the object properties, useful for dict repr. Args: validate (bool): If True, try to validate object before returning the mapping. """ if validate: self.validate() return dict( DimmingType=self.DimmingType, EquipmentAvailabilitySchedule=self.EquipmentAvailabilitySchedule, EquipmentPowerDensity=self.EquipmentPowerDensity, IlluminanceTarget=self.IlluminanceTarget, LightingPowerDensity=self.LightingPowerDensity, LightsAvailabilitySchedule=self.LightsAvailabilitySchedule, OccupancySchedule=self.OccupancySchedule, IsEquipmentOn=self.IsEquipmentOn, IsLightingOn=self.IsLightingOn, IsPeopleOn=self.IsPeopleOn, PeopleDensity=self.PeopleDensity, Category=self.Category, Comments=self.Comments, DataSource=self.DataSource, Name=self.Name, )
[docs] def to_dict(self): """Return ZoneLoad dictionary representation.""" self.validate() # Validate object before trying to get json format data_dict = collections.OrderedDict() data_dict["$id"] = str(self.id) data_dict["DimmingType"] = self.DimmingType.value data_dict[ "EquipmentAvailabilitySchedule" ] = self.EquipmentAvailabilitySchedule.to_ref() data_dict["EquipmentPowerDensity"] = ( round(self.EquipmentPowerDensity, 3) if not math.isnan(self.EquipmentPowerDensity) else 0 ) data_dict["IlluminanceTarget"] = round(self.IlluminanceTarget, 3) data_dict["LightingPowerDensity"] = ( round(self.LightingPowerDensity, 3) if not math.isnan(self.LightingPowerDensity) else 0 ) data_dict[ "LightsAvailabilitySchedule" ] = self.LightsAvailabilitySchedule.to_ref() data_dict["OccupancySchedule"] = self.OccupancySchedule.to_ref() data_dict["IsEquipmentOn"] = self.IsEquipmentOn data_dict["IsLightingOn"] = self.IsLightingOn data_dict["IsPeopleOn"] = self.IsPeopleOn data_dict["PeopleDensity"] = ( round(self.PeopleDensity, 3) if not math.isnan(self.PeopleDensity) else 0 ) data_dict["Category"] = self.Category data_dict["Comments"] = validators.string(self.Comments, allow_empty=True) data_dict["DataSource"] = self.DataSource data_dict["Name"] = self.Name return data_dict
[docs] def to_epbunch(self, idf, zone_name): """Convert the zone load to epbunch given an idf model and a zone name. Args: idf (IDF): The idf model. epbunches will be added to this model. zone_name (str): The name of the zone in the idf model. .. code-block:: python People, People Perim, !- Name Perim, !- Zone or ZoneList Name B_Off_Y_Occ, !- Number of People Schedule Name People/Area, !- Number of People Calculation Method , !- Number of People 0.055, !- People per Zone Floor Area , !- Zone Floor Area per Person 0.3, !- Fraction Radiant AUTOCALCULATE, !- Sensible Heat Fraction PerimPeopleActivity, !- Activity Level Schedule Name 3.82e-08, !- Carbon Dioxide Generation Rate No, !- Enable ASHRAE 55 Comfort Warnings ZoneAveraged, !- Mean Radiant Temperature Calculation Type , !- Surface NameAngle Factor List Name PerimWorkEfficiency, !- Work Efficiency Schedule Name DynamicClothingModelASHRAE55, !- Clothing Insulation Calculation Method , !- Clothing Insulation Calculation Method Schedule Name , !- Clothing Insulation Schedule Name PerimAirVelocity, !- Air Velocity Schedule Name AdaptiveASH55; !- Thermal Comfort Model 1 Type Lights, Perim General lighting, !- Name Perim, !- Zone or ZoneList Name B_Off_Y_Lgt, !- Schedule Name Watts/Area, !- Design Level Calculation Method , !- Lighting Level 12, !- Watts per Zone Floor Area , !- Watts per Person 0, !- Return Air Fraction 0.42, !- Fraction Radiant 0.18, !- Fraction Visible 1, !- Fraction Replaceable ; !- EndUse Subcategory ElectricEquipment, Perim Equipment 1, !- Name Perim, !- Zone or ZoneList Name B_Off_Y_Plg, !- Schedule Name Watts/Area, !- Design Level Calculation Method , !- Design Level 8, !- Watts per Zone Floor Area , !- Watts per Person 0, !- Fraction Latent 0.2, !- Fraction Radiant 0, !- Fraction Lost ; !- EndUse Subcategory Returns: EpBunch: The EpBunch object added to the idf model. """ people = idf.newidfobject( "PEOPLE", Name=":".join(("People", self.Name, zone_name)), Zone_or_ZoneList_Name=zone_name, Number_of_People_Schedule_Name=self.OccupancySchedule.to_epbunch(idf).Name, Number_of_People_Calculation_Method="People/Area", People_per_Zone_Floor_Area=self.PeopleDensity, Fraction_Radiant=0.3, Sensible_Heat_Fraction="AUTOCALCULATE", Activity_Level_Schedule_Name=idf.newidfobject( "SCHEDULE:CONSTANT", Name="PeopleActivity", Hourly_Value=125.28 ).Name, Carbon_Dioxide_Generation_Rate=3.82e-08, Enable_ASHRAE_55_Comfort_Warnings="No", Mean_Radiant_Temperature_Calculation_Type="ZoneAveraged", Work_Efficiency_Schedule_Name=idf.newidfobject( "SCHEDULE:CONSTANT", Name="WorkEfficiency", Hourly_Value=0 ).Name, Clothing_Insulation_Calculation_Method="DynamicClothingModelASHRAE55", Air_Velocity_Schedule_Name=idf.newidfobject( "SCHEDULE:CONSTANT", Name="AirVelocity", Hourly_Value=0.2 ).Name, ) lights = idf.newidfobject( key="LIGHTS", Name=":".join(("Lights", self.Name, zone_name)), Zone_or_ZoneList_Name=zone_name, Schedule_Name=self.LightsAvailabilitySchedule.to_epbunch(idf).Name, Design_Level_Calculation_Method="Watts/Area", Watts_per_Zone_Floor_Area=self.LightingPowerDensity, Return_Air_Fraction=0, Fraction_Radiant=0.42, Fraction_Visible=0.18, Fraction_Replaceable=1, ) equipment = idf.newidfobject( "ELECTRICEQUIPMENT", Name=":".join(("ElectricEquipment", self.Name, zone_name)), Zone_or_ZoneList_Name=zone_name, Schedule_Name=self.EquipmentAvailabilitySchedule.to_epbunch(idf).Name, Design_Level_Calculation_Method="Watts/Area", Watts_per_Zone_Floor_Area=self.EquipmentPowerDensity, Fraction_Latent=0, Fraction_Radiant=0.2, Fraction_Lost=0, EndUse_Subcategory="ElectricEquipment", ) return people, lights, equipment
def __copy__(self): """Create a copy of self.""" return self.__class__( **self.mapping(validate=False), area=self.area, volume=self.volume ) def __add__(self, other): """Combine self and other.""" return self.combine(other) def __hash__(self): """Return the hash value of self.""" return hash( (self.__class__.__name__, getattr(self, "Name", None), self.DataSource) ) def __key__(self): """Get a tuple of attributes. Useful for hashing and comparing.""" return ( self.DimmingType, self.EquipmentAvailabilitySchedule, self.EquipmentPowerDensity, self.IlluminanceTarget, self.LightingPowerDensity, self.LightsAvailabilitySchedule, self.OccupancySchedule, self.IsEquipmentOn, self.IsLightingOn, self.IsPeopleOn, self.PeopleDensity, ) def __eq__(self, other): """Assert self is equivalent to other.""" if not isinstance(other, ZoneLoad): return NotImplemented else: return self.__key__() == other.__key__()
def _resolve_dimming_type(zone, zone_ep): """Resolve the dimming type for the Zone object. Args: zone_ep: """ # First, retrieve the list of Daylighting objects for this zone. Uses the eppy # `getreferingobjs` method. possible_ctrls = zone_ep.getreferingobjs( iddgroups=["Daylighting"], fields=["Zone_Name"] ) # Then, if there are controls if possible_ctrls: # Filter only the "Daylighting:Controls" ctrls = [ ctrl for ctrl in possible_ctrls if ctrl.key.upper() == "Daylighting:Controls".upper() ] ctrl_types = [ctrl["Lighting_Control_Type"] for ctrl in ctrls] # There should only be one control per zone. A set of controls should return 1. if len(set(ctrl_types)) == 1: dimming_type, *_ = set(ctrl_types) if dimming_type.lower() not in ["continuous", "stepped"]: raise ValueError( f"A dimming type of type '{dimming_type}' for zone '{zone.Name}' is not yet supported in UMI" ) else: log(f"Dimming type for zone '{zone.Name}' set to '{dimming_type}'") return DimmingTypes[dimming_type] # Return first element else: raise ValueError( "Could not resolve more than one dimming types for Zone {}. " "Make sure there is only one".format(zone.Name) ) else: # Else, there are no dimming controls => set to "Off". log( "No dimming type found for zone {}. Setting as Off".format(zone.Name), lg.DEBUG, ) return DimmingTypes.Off def _resolve_illuminance_target(zone, zone_ep): """Resolve the illuminance target for the Zone object. Args: zone_ep: """ # First, retrieve the list of Daylighting objects for this zone. Uses the eppy # `getreferingobjs` method. possible_ctrls = zone_ep.getreferingobjs( iddgroups=["Daylighting"], fields=["Zone_Name"] ) # Then, if there are controls if possible_ctrls: # Filter only the "Daylighting:Controls" ctrls = [ ctrl for ctrl in possible_ctrls if ctrl.key.upper() == "Daylighting:Controls".upper() ] ctrl_types = [ ctrl["Illuminance_Setpoint_at_Reference_Point_1"] for ctrl in ctrls ] # There should only be one control per zone. A set of controls should return 1. if len(set(ctrl_types)) == 1: dimming_type = next(iter(set(ctrl_types))) log(f"Illuminance target for zone '{zone.Name}' set to '{dimming_type}'") return float(dimming_type) # Return first element else: raise ValueError( "Could not resolve more than one illuminance targets for Zone {}. " "Make sure there is only one".format(zone.Name) ) else: # Else, there are no dimming controls => set to "Off". log( "No illuminance target found for zone {}. Setting to default 500 " "lux".format(zone.Name), lg.DEBUG, ) return 500