Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shade): Write shades without POLYGON when they are RECTANGLE #100

Merged
merged 4 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r standards-requirements.txt
pip install -r dev-requirements.txt
- name: run tests
run: python -m pytest tests/
Expand Down
20 changes: 2 additions & 18 deletions honeybee_doe2/cli/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,13 @@ def translate():
'--include-interior-ceilings/--exclude-interior-ceilings', ' /-xc', help='Flag to '
'note whether interior ceilings should be excluded from the export.',
default=True, show_default=True)
@click.option(
'--verbose-properties/--switch-statements', ' /-ss', help='Flag to note whether '
'program types should be written with switch statements so that they can easily '
'be edited in eQuest or a verbose definition of loads should be written for '
'each Room/Space.', default=True, show_default=True)
@click.option(
'--name', '-n', help='Deprecated option to set the name of the output file.',
default=None, show_default=True)
@click.option(
'--folder', '-f', help='Deprecated option to set the path to target folder.',
type=click.Path(file_okay=False, resolve_path=True, dir_okay=True), default=None)
@click.option(
'--output-file', '-o', help='Optional INP file path to output the INP string '
'of the translation. By default this will be printed out to stdout.',
type=click.File('w'), default='-', show_default=True)
def model_to_inp_file(
model_file, sim_par_json, hvac_mapping, include_interior_walls,
include_interior_ceilings, verbose_properties, name, folder, output_file
include_interior_ceilings, output_file
):
"""Translate a Model (HBJSON) file to an INP file.

Expand All @@ -82,12 +71,7 @@ def model_to_inp_file(
inp_str = model.to.inp(model, sim_par, hvac_mapping, x_int_w, x_int_c)

# write out the INP file
if folder is not None and name is not None:
if not name.lower().endswith('.inp'):
name = name + '.inp'
write_to_file_by_name(folder, name, inp_str, True)
else:
output_file.write(inp_str)
output_file.write(inp_str)
except Exception as e:
_logger.exception(f'Model translation failed:\n{e}')
sys.exit(1)
Expand Down
275 changes: 227 additions & 48 deletions honeybee_doe2/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,259 @@
from __future__ import division

from ladybug.datatype.area import Area
from ladybug.datatype.power import Power
from ladybug.datatype.energyflux import EnergyFlux
from ladybug.datatype.volumeflowrate import VolumeFlowRate
from ladybug.datatype.volumeflowrateintensity import VolumeFlowRateIntensity
from honeybee.typing import clean_doe2_string

from .config import RES_CHARS
# TODO: Add methods to map to SOURCE-TYPE HOT-WATER and PROCESS
# TODO: Add methods to translate daylight sensors
# TODO: Add methods to map honeybee_energy process loads to SOURCE-TYPE PROCESS
# TODO: Implement the keys that Trevor wants:
# FLOW/AREA, ASSIGNED-FLOW, MIN-FLOW-RATIO, MIN-FLOW/AREA, HMAX-FLOW-RATIO


def people_to_inp(room):
"""Translate the People definition of a Room into INP (Keywords, Values)."""
people = room.properties.energy.people
def people_to_inp(people):
"""Translate a People definition into INP (Keywords, Values).

Args:
people: A honeybee-energy People definition. None is allowed.

Returns:
A tuple with two elements.

- keywords: A tuple of text strings for keywords related to defining
people for a Space.

- values: A tuple of text strings that aligns with the keywords and
denotes the value for each keyword.
"""
if people is None:
return (), ()
ppl_den = Area().to_unit([people.area_per_person], 'ft2', 'm2')[0]
ppl_total = round(room.floor_area / ppl_den, 3)
ppl_sch = clean_doe2_string(people.occupancy_schedule.display_name, RES_CHARS)
ppl_den = round(ppl_den, 3)
ppl_sch = clean_doe2_string(people.occupancy_schedule.identifier, RES_CHARS)
ppl_sch = '"{}"'.format(ppl_sch)
ppl_kwd = ('NUMBER-OF-PEOPLE', 'PEOPLE-SCHEDULE')
ppl_val = (ppl_total, ppl_sch)
return ppl_kwd, ppl_val
keywords = ('AREA/PERSON', 'PEOPLE-SCHEDULE')
values = (ppl_den, ppl_sch)
return keywords, values


def lighting_to_inp(lighting):
"""Translate a Lighting definition into INP (Keywords, Values).

Args:
lighting: A honeybee-energy Lighting definition. None is allowed.

Returns:
A tuple with two elements.

- keywords: A tuple of text strings for keywords related to defining
lighting for a Space.

def lighting_to_inp(room):
"""Translate the Lighting definition of a Room into INP (Keywords, Values)."""
lighting = room.properties.energy.lighting
- values: A tuple of text strings that aligns with the keywords and
denotes the value for each keyword.
"""
if lighting is None:
return (), ()
lpd = EnergyFlux().to_unit([lighting.watts_per_area], 'W/ft2', 'W/m2')[0]
lpd = round(lpd, 3)
lgt_sch = clean_doe2_string(lighting.schedule.display_name, RES_CHARS)
lgt_sch = clean_doe2_string(lighting.schedule.identifier, RES_CHARS)
lgt_sch = '"{}"'.format(lgt_sch)
light_kwd = ('LIGHTING-W/AREA', 'LIGHTING-SCHEDULE', 'LIGHT-TO-RETURN')
light_val = (lpd, lgt_sch, lighting.return_air_fraction)
return light_kwd, light_val
keywords = ('LIGHTING-W/AREA', 'LIGHTING-SCHEDULE', 'LIGHT-TO-RETURN')
values = (lpd, lgt_sch, lighting.return_air_fraction)
return keywords, values


def equipment_to_inp(room):
"""Translate the Equipment definition(s) of a Room into INP (Keywords, Values)."""
# first evaluate what types of equipment we have
ele_equip = room.properties.energy.electric_equipment
gas_equip = room.properties.energy.gas_equipment
def equipment_to_inp(electric_equip, gas_equip=None):
"""Translate an Equipment definition(s) into INP (Keywords, Values).

Args:
electric_equip: A honeybee-energy ElectricEquipment definition. None is allowed.
gas_equip: A honeybee-energy GasEquipment definition. None is allowed.

Returns:
A tuple with two elements.

- keywords: A tuple of text strings for keywords related to defining
the equipment for a Space.

- values: A tuple of text strings that aligns with the keywords and
denotes the value for each keyword.
"""
# extract the properties from the equipment objects
if ele_equip is not None and gas_equip is not None: # write them as lists
equip_val = [[], [], [], [], []]
for equip in (ele_equip, gas_equip):
if electric_equip is not None and gas_equip is not None: # write them as lists
values = [[], [], [], [], []]
for equip in (electric_equip, gas_equip):
epd = EnergyFlux().to_unit([equip.watts_per_area], 'W/ft2', 'W/m2')[0]
epd = round(epd, 3)
equip_val[0].append(epd)
eqp_sch = clean_doe2_string(equip.schedule.display_name, RES_CHARS)
equip_val[1].append('"{}"'.format(eqp_sch))
equip_val[2].append(1 - equip.latent_fraction - equip.lost_fraction)
equip_val[3].append(equip.latent_fraction)
equip_val[4].append(equip.radiant_fraction)
equip_val = ['( {}, {} )'.format(v[0], v[1]) for v in equip_val]
else: # write them as a single item
equip = ele_equip if gas_equip is None else gas_equip
values[0].append(round(epd, 3))
eqp_sch = clean_doe2_string(equip.schedule.identifier, RES_CHARS)
values[1].append('"{}"'.format(eqp_sch))
values[2].append(round(1 - equip.latent_fraction - equip.lost_fraction, 3))
values[3].append(round(equip.latent_fraction, 3))
values[4].append(round(equip.radiant_fraction, 3))
format_values = []
for v in values:
if isinstance(v[0], str): # make sure the schedules do not go past 100 chars
format_values.append('({},\n{}{})'.format(v[0], ' ' * 31, v[1]))
else:
format_values.append('({}, {})'.format(v[0], v[1]))
values = format_values
elif electric_equip is not None or gas_equip is not None: # write as a single item
equip = electric_equip if gas_equip is None else gas_equip
epd = EnergyFlux().to_unit([equip.watts_per_area], 'W/ft2', 'W/m2')[0]
epd = round(epd, 3)
eqp_sch = clean_doe2_string(equip.schedule.display_name, RES_CHARS)
eqp_sch = clean_doe2_string(equip.schedule.identifier, RES_CHARS)
eqp_sch = '("{}")'.format(eqp_sch)
sens_fract = 1 - equip.latent_fraction - equip.lost_fraction
equip_val = (epd, eqp_sch, sens_fract, equip.latent_fraction,
equip.radiant_fraction)
values = (epd, eqp_sch, sens_fract, equip.latent_fraction,
equip.radiant_fraction)
else: # no equipment assigned
return (), ()

keywords = ('EQUIPMENT-W/AREA', 'EQUIP-SCHEDULE',
'EQUIP-SENSIBLE', 'EQUIP-LATENT', 'EQUIP-RAD-FRAC')
return keywords, values


def hot_water_to_inp(hot_water, room_floor_area):
"""Translate a ServiceHotWater definition into INP (Keywords, Values).

Args:
hot_water: A honeybee-energy ServiceHotWater definition. None is allowed.
room_floor_area: The host Room floor area in square feet, which will
be used to convert the hot water flow per unit floor area to an
absolute load in BTU/h.

Returns:
A tuple with two elements.

equip_kwd = ('EQUIPMENT-W/AREA', 'EQUIP-SCHEDULE',
'EQUIP-SENSIBLE', 'EQUIP-LATENT', 'EQUIP-RAD-FRAC')
return equip_kwd, equip_val
- keywords: A tuple of text strings for keywords related to defining
the hot water SOURCE load for a Space.

- values: A tuple of text strings that aligns with the keywords and
denotes the value for each keyword.
"""
if hot_water is None:
return (), ()
flow_den = hot_water.flow_per_area # L/h-m2
flr_area = Area().to_unit([room_floor_area], 'm2', 'ft2')[0] # m2
total_flow = flow_den * flr_area # L/h
delta_t = 50 # assume the water heater must heat water from 10C to 60C
c_water = 4.186 # J/g-C, the specific heat of water
shw_heat = total_flow * c_water * delta_t # J/h using Q = m * c * deltaT
shw_heat = shw_heat / 3600. # Watts
shw_power = round(Power().to_unit([shw_heat], 'Btu/h', 'W')[0], 3)
shw_sch = clean_doe2_string(hot_water.schedule.identifier, RES_CHARS)
shw_sch = '"{}"'.format(shw_sch)
keywords = ('SOURCE-TYPE', 'SOURCE-POWER', 'SOURCE-SCHEDULE',
'SOURCE-SENSIBLE', 'SOURCE-RAD-FRAC', 'SOURCE-LATENT')
values = ('HOT-WATER', shw_power, shw_sch,
hot_water.sensible_fraction, 0, hot_water.latent_fraction)
return keywords, values

def infiltration_to_inp(room):
"""Translate the Infiltration definition of a Room into INP (Keywords, Values)."""
infil = room.properties.energy.infiltration
inf_den = infil.flow_per_exterior_area

def infiltration_to_inp(infiltration):
"""Translate an Infiltration definition into INP (Keywords, Values).

Args:
infiltration: A honeybee-energy Infiltration definition. None is allowed.

Returns:
A tuple with two elements.

- keywords: A tuple of text strings for keywords related to defining
infiltration for a Space.

- values: A tuple of text strings that aligns with the keywords and
denotes the value for each keyword.
"""
if infiltration is None:
return (), ()
inf_den = infiltration.flow_per_exterior_area
inf_den = VolumeFlowRateIntensity().to_unit([inf_den], 'cfm/ft2', 'm3/s-m2')[0]
inf_den = round(inf_den, 3)
inf_sch = clean_doe2_string(infil.schedule.display_name, RES_CHARS)
inf_sch = clean_doe2_string(infiltration.schedule.identifier, RES_CHARS)
inf_sch = '"{}"'.format(inf_sch)
inf_kwd = ('INF-METHOD', 'INF-FLOW/AREA', 'INF-SCHEDULE')
inf_val = ('AIR-CHANGE', inf_den, inf_sch)
return inf_kwd, inf_val
keywords = ('INF-METHOD', 'INF-FLOW/AREA', 'INF-SCHEDULE')
values = ('AIR-CHANGE', inf_den, inf_sch)
return keywords, values


def setpoint_to_inp(setpoint):
"""Translate a Setpoint definition into INP (Keywords, Values).

Args:
setpoint: A honeybee-energy Setpoint definition. None is allowed.

Returns:
A tuple with two elements.

- keywords: A tuple of text strings for keywords related to defining
setpoints for a Zone.

- values: A tuple of text strings that aligns with the keywords and
denotes the value for each keyword.
"""
if setpoint is None: # use some default setpoints
return ('DESIGN-HEAT-T', 'DESIGN-COOL-T'), (72, 75)
heat_setpt = round(setpoint.heating_setpoint * (9. / 5.) + 32., 2)
cool_setpt = round(setpoint.cooling_setpoint * (9. / 5.) + 32., 2)
heat_sch = clean_doe2_string(setpoint.heating_schedule.identifier, RES_CHARS)
heat_sch = '"{}"'.format(heat_sch)
cool_sch = clean_doe2_string(setpoint.cooling_schedule.identifier, RES_CHARS)
cool_sch = '"{}"'.format(cool_sch)
keywords = ('DESIGN-HEAT-T', 'DESIGN-COOL-T', 'HEAT-TEMP-SCH', 'COOL-TEMP-SCH')
values = (heat_setpt, cool_setpt, heat_sch, cool_sch)
return keywords, values


def ventilation_to_inp(ventilation):
"""Translate a Ventilation definition into INP (Keywords, Values).

Args:
ventilation: A honeybee-energy Ventilation definition. None is allowed.

Returns:
A tuple with two elements.

- keywords: A list of text strings for keywords related to defining
ventilation for a Space.

- values: A list of text strings that aligns with the keywords and
denotes the value for each keyword.
"""
keywords, values = [], []
if ventilation is None:
return keywords, values
# check the flow per person
ppl_den = ventilation.flow_per_person
if ppl_den != 0:
keywords.append('OA-FLOW/PER')
ppl_den = VolumeFlowRate().to_unit([ppl_den], 'cfm', 'm3/s')[0]
values.append(round(ppl_den, 3))
# check the flow per floor area
vent_den = ventilation.flow_per_area
if vent_den != 0:
keywords.append('OA-FLOW/AREA')
vent_den = VolumeFlowRateIntensity().to_unit([vent_den], 'cfm/ft2', 'm3/s-m2')[0]
values.append(round(vent_den, 3))
# check the air changes per hour
ach = ventilation.air_changes_per_hour
if ach != 0:
keywords.append('OA-CHANGES')
values.append(round(ach, 3))
# check the flow per zone
total_flow = ventilation.flow_per_zone
if total_flow != 0:
keywords.append('OA-FLOW/PER')
total_flow = VolumeFlowRate().to_unit([total_flow], 'cfm', 'm3/s')[0]
values.append(round(total_flow, 3))
# check the schedule
vent_sch = ventilation.schedule
if vent_sch is not None:
keywords.append('MIN-FLOW-SCH')
vent_sch = clean_doe2_string(vent_sch.identifier, RES_CHARS)
values.append('"{}"'.format(vent_sch))
return keywords, values
19 changes: 17 additions & 2 deletions honeybee_doe2/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,27 @@ def schedule_day_to_inp(day_schedule, type_limit=None):
keywords, values = ['TYPE'], [type_text]
hour_values = day_schedule.values_at_timestep(1)

# convert temperature to fahrenheit if the type if temperature
if type_text == 'TEMPERATURE':
hour_values = [round(v * (9. / 5.) + 32., 2) for v in hour_values]

# setup a function to format list of values correctly
def _format_day_values(values_to_format):
if len(values_to_format) == 1:
return'({})'.format(round(values_to_format[0], 3))
else:
elif len(values_to_format) < 5:
return str(tuple(round(v, 3) for v in values_to_format))
else: # we have to format it with multiple lines
spc = ' ' * 31
full_str = '('
for i, v in enumerate(values_to_format):
if i == len(values_to_format) - 1:
full_str += str(round(v, 3)) + ')'
elif (i + 1) % 5 == 0:
full_str += str(round(v, 3)) + ',\n' + spc
else:
full_str += str(round(v, 3)) + ', '
return full_str

# loop through the hourly values and write them in the format DOE-2 likes
prev_count, prev_hour, prev_values = 0, 1, [hour_values[0]]
Expand Down Expand Up @@ -252,5 +267,5 @@ def energy_trans_sch_to_transmittance(shade_obj):
sch_vals = trans_sch.values()
except Exception: # ScheduleFixedInterval
sch_vals = trans_sch.values
trans = sum(sch_vals) / len(sch_vals)
trans = round(sum(sch_vals) / len(sch_vals), 3)
return trans
Loading
Loading