From 4f564d1e2e0df45c7c2da38cb697da57efc2290d Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Fri, 26 Apr 2024 21:14:47 -0700 Subject: [PATCH] fix(package): Fix a list few bugs identified though eQuest importing --- honeybee_doe2/construction.py | 6 ++++++ honeybee_doe2/load.py | 4 ++-- honeybee_doe2/schedule.py | 36 ++++++++++++++++++----------------- honeybee_doe2/writer.py | 15 +++++++++------ requirements.txt | 2 +- tests/construction_test.py | 12 ++++++------ tests/schedule_test.py | 34 +++++++++++++-------------------- tests/writer_test.py | 18 +++++++++--------- 8 files changed, 65 insertions(+), 62 deletions(-) diff --git a/honeybee_doe2/construction.py b/honeybee_doe2/construction.py index 4d1124a..26902d3 100644 --- a/honeybee_doe2/construction.py +++ b/honeybee_doe2/construction.py @@ -49,6 +49,12 @@ def opaque_construction_to_inp(construction): it does NOT include the constituent MATERIAL definitions and their properties. """ doe2_id = clean_doe2_string(construction.identifier, RES_CHARS) + # if the construction has no heat capacity, simply make a U-VALUE construction + if construction.area_heat_capacity == 0: + con_cond = UValue().to_unit([construction.u_factor], 'Btu/h-ft2-F', 'W/m2-K')[0] + keywords = ('TYPE', 'U-VALUE') + values = ('U-VALUE', round(con_cond, 6)) + return generate_inp_string(doe2_id, 'CONSTRUCTION', keywords, values) # create the specification of material layers layer_id = '{}_l'.format(doe2_id) layers = ['"{}"'.format(clean_doe2_string(mat, RES_CHARS)) diff --git a/honeybee_doe2/load.py b/honeybee_doe2/load.py index dea671f..c3e9f4d 100644 --- a/honeybee_doe2/load.py +++ b/honeybee_doe2/load.py @@ -58,12 +58,12 @@ def equipment_to_inp(room): 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 = '"{}"'.format(eqp_sch) + 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) - equip_kwd = ('EQUIPMENT-W/AREA', 'EQUIPMENT-SCHEDULE', + equip_kwd = ('EQUIPMENT-W/AREA', 'EQUIP-SCHEDULE', 'EQUIP-SENSIBLE', 'EQUIP-LATENT', 'EQUIP-RAD-FRAC') return equip_kwd, equip_val diff --git a/honeybee_doe2/schedule.py b/honeybee_doe2/schedule.py index 2fc2eb0..d1f2fab 100644 --- a/honeybee_doe2/schedule.py +++ b/honeybee_doe2/schedule.py @@ -6,7 +6,7 @@ from honeybee.typing import clean_doe2_string from .config import RES_CHARS -from .util import generate_inp_string +from .util import generate_inp_string, generate_inp_string_list_format def schedule_type_limit_to_inp(type_limit): @@ -33,9 +33,9 @@ def schedule_day_to_inp(day_schedule, type_limit=None): # setup a function to format list of values correctly def _format_day_values(values_to_format): if len(values_to_format) == 1: - return'({})'.format(values_to_format[0]) + return'({})'.format(round(values_to_format[0], 3)) else: - return str(tuple(values_to_format)) + return str(tuple(round(v, 3) for v in values_to_format)) # 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]] @@ -97,8 +97,10 @@ def schedule_ruleset_to_inp(schedule): # setup the DOE-2 identifier and lists for keywords and values doe2_id = clean_doe2_string(schedule.identifier, RES_CHARS) type_text = schedule_type_limit_to_inp(schedule.schedule_type_limit) - day_types = ['(MON)', '(TUE)', '(WED)', '(THU)', '(FRI)', '(SAT)', '(SUN)', - '(HOL)', '(HDD)', '(CDD)'] + day_types = [ + 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', + 'Sunday', 'Holiday', 'Winter Design Day', 'Summer Design Day' + ] def _get_week_list(schedule, rule_indices): """Get a list of the ScheduleDay identifiers applied on each day of the week.""" @@ -149,13 +151,13 @@ def _inp_week_schedule_from_rule_indices(schedule, rule_indices, week_index): # add extra days (including summer and winter design days) week_fields.extend(_get_extra_week_fields(schedule)) week_keywords, week_values = ['TYPE'], [type_text] + day_list = [] for day_type, day_sch in zip(day_types, week_fields): - week_keywords.append('DAYS') - week_values.append(day_type) - week_keywords.append('DAY-SCHEDULES') - week_values.append('"{}"'.format(day_sch)) - week_schedule = generate_inp_string( - week_sch_id, 'WEEK-SCHEDULE', week_keywords, week_values) + day_list.append('"{}", $ {}'.format(day_sch, day_type)) + week_keywords.append('DAY-SCHEDULES') + week_values.append(day_list) + week_schedule = generate_inp_string_list_format( + week_sch_id, 'WEEK-SCHEDULE-PD', week_keywords, week_values) return week_schedule, week_sch_id def _inp_week_schedule_from_week_list(schedule, week_list, week_index): @@ -167,13 +169,13 @@ def _inp_week_schedule_from_week_list(schedule, week_list, week_index): week_fields.append(week_fields.pop(0)) # DOE-2 starts week on Monday; not Sunday week_fields.extend(_get_extra_week_fields(schedule)) week_keywords, week_values = ['TYPE'], [type_text] + day_list = [] for day_type, day_sch in zip(day_types, week_fields): - week_keywords.append('DAYS') - week_values.append(day_type) - week_keywords.append('DAY-SCHEDULES') - week_values.append('"{}"'.format(day_sch)) - week_schedule = generate_inp_string( - week_sch_id, 'WEEK-SCHEDULE', week_keywords, week_values) + day_list.append('"{}", $ {}'.format(day_sch, day_type)) + week_keywords.append('DAY-SCHEDULES') + week_values.append(day_list) + week_schedule = generate_inp_string_list_format( + week_sch_id, 'WEEK-SCHEDULE-PD', week_keywords, week_values) return week_schedule, week_sch_id # prepare to create a full Schedule:Year diff --git a/honeybee_doe2/writer.py b/honeybee_doe2/writer.py index 55d73df..b010e8a 100644 --- a/honeybee_doe2/writer.py +++ b/honeybee_doe2/writer.py @@ -63,7 +63,7 @@ def face_3d_to_inp(face_3d, parent_name='HB object'): ref_plane = Plane(face_3d.normal, llc_origin, proj_x) vertices = [ref_plane.xyz_to_xy(pt) for pt in pts_3d] else: # horizontal; ensure vertices are always counterclockwise from above - azimuth = 180 + azimuth = 180.0 llc = Point2D(llc_origin.x, llc_origin.y) vertices = [Point2D(v.x - llc.x, v.y - llc.y) for v in pts_3d] if tilt > 180 - DOE2_ANGLE_TOL: @@ -104,7 +104,6 @@ def shade_mesh_to_inp(shade_mesh): """ # TODO: Sense when the shade is a rectangle and, if so, translate it without POLYGON # set up collector lists and properties for all shades - shade_type = 'FIXED-SHADE' if shade_mesh.is_detached else 'BUILDING-SHADE' base_id = clean_doe2_string(shade_mesh.identifier, GEO_CHARS) trans = energy_trans_sch_to_transmittance(shade_mesh) keywords = ('SHAPE', 'POLYGON', 'TRANSMITTANCE', @@ -120,7 +119,7 @@ def shade_mesh_to_inp(shade_mesh): values = ('POLYGON', '"{} Plg"'.format(doe2_id), trans, round(origin.x, GEO_DEC_COUNT), round(origin.y, GEO_DEC_COUNT), round(origin.z, GEO_DEC_COUNT), tilt, az) - shade_def = generate_inp_string(doe2_id, shade_type, keywords, values) + shade_def = generate_inp_string(doe2_id, 'FIXED-SHADE', keywords, values) shade_polygons.append(shade_polygon) shade_defs.append(shade_def) return shade_polygons, shade_defs @@ -141,7 +140,6 @@ def shade_to_inp(shade): """ # TODO: Sense when the shade is a rectangle and, if so, translate it without POLYGON # create the polygon string from the geometry - shade_type = 'FIXED-SHADE' if shade.is_detached else 'BUILDING-SHADE' doe2_id = clean_doe2_string(shade.identifier, GEO_CHARS) shd_geo = shade.geometry if shade.altitude > 0 else shade.geometry.flip() clean_geo = shd_geo.remove_colinear_vertices(DOE2_TOLERANCE) @@ -154,7 +152,7 @@ def shade_to_inp(shade): values = ('POLYGON', '"{} Plg"'.format(doe2_id), trans, round(origin.x, GEO_DEC_COUNT), round(origin.y, GEO_DEC_COUNT), round(origin.z, GEO_DEC_COUNT), tilt, az) - shade_def = generate_inp_string(doe2_id, shade_type, keywords, values) + shade_def = generate_inp_string(doe2_id, 'FIXED-SHADE', keywords, values) return shade_polygon, shade_def @@ -200,7 +198,9 @@ def door_to_inp(door): # create the aperture definition doe2_id = clean_doe2_string(door.identifier, GEO_CHARS) - constr_o_name = door.properties.energy.construction.identifier + dr_con = door.properties.energy.construction + constr_o_name = dr_con.identifier if isinstance(dr_con, OpaqueConstruction) \ + else dr_con.identifier + '_d' constr = clean_doe2_string(constr_o_name, RES_CHARS) keywords = ('X', 'Y', 'WIDTH', 'HEIGHT', 'CONSTRUCTION') values = (round(min_2d.x, GEO_DEC_COUNT), round(min_2d.y, GEO_DEC_COUNT), @@ -568,6 +568,9 @@ def model_to_inp( model_str.append(window_construction_to_inp(w_con)) model_str.append(header_comment_minor('Door Construction')) for dr_con in door_constructions: + if not isinstance(dr_con, OpaqueConstruction): + dr_con = dr_con.duplicate() + dr_con.identifier = dr_con.identifier + '_d' model_str.append(door_construction_to_inp(dr_con)) # loop through rooms grouped by floor level and boundary to get polygons diff --git a/requirements.txt b/requirements.txt index 0271721..cfabf46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -honeybee-energy>=1.105.48 +honeybee-energy>=1.105.50 diff --git a/tests/construction_test.py b/tests/construction_test.py index 60126ef..eb0a099 100644 --- a/tests/construction_test.py +++ b/tests/construction_test.py @@ -33,7 +33,7 @@ def test_material_nomass_to_inp(): assert inp_str == \ '"Insulation R-2" = MATERIAL\n' \ ' TYPE = RESISTANCE\n' \ - ' RESISTANCE = 11.357\n' \ + ' RESISTANCE = 11.356527\n' \ ' ..\n' @@ -89,7 +89,7 @@ def test_window_construction_to_inp(): '"NECB Window Construction" = GLASS-TYPE\n' \ ' TYPE = SHADING-COEF\n' \ ' SHADING-COEF = 0.46\n' \ - ' GLASS-CONDUCT = 0.302\n' \ + ' GLASS-CONDUCT = 0.302373\n' \ ' ..\n' inp_str = double_low_e.to_inp() @@ -97,7 +97,7 @@ def test_window_construction_to_inp(): '"Double Low-E Window" = GLASS-TYPE\n' \ ' TYPE = SHADING-COEF\n' \ ' SHADING-COEF = 0.488\n' \ - ' GLASS-CONDUCT = 0.299\n' \ + ' GLASS-CONDUCT = 0.299039\n' \ ' ..\n' inp_str = double_clear.to_inp() @@ -105,7 +105,7 @@ def test_window_construction_to_inp(): '"Double Clear Window" = GLASS-TYPE\n' \ ' TYPE = SHADING-COEF\n' \ ' SHADING-COEF = 0.791\n' \ - ' GLASS-CONDUCT = 0.479\n' \ + ' GLASS-CONDUCT = 0.479229\n' \ ' ..\n' inp_str = triple_clear.to_inp() @@ -113,7 +113,7 @@ def test_window_construction_to_inp(): '"Triple Clear Window" = GLASS-TYPE\n' \ ' TYPE = SHADING-COEF\n' \ ' SHADING-COEF = 0.688\n' \ - ' GLASS-CONDUCT = 0.309\n' \ + ' GLASS-CONDUCT = 0.309475\n' \ ' ..\n' @@ -139,7 +139,7 @@ def test_window_construction_shade_to_inp(): '"Double Low-E with Shade" = GLASS-TYPE\n' \ ' TYPE = SHADING-COEF\n' \ ' SHADING-COEF = 0.488\n' \ - ' GLASS-CONDUCT = 0.299\n' \ + ' GLASS-CONDUCT = 0.299039\n' \ ' ..\n' diff --git a/tests/schedule_test.py b/tests/schedule_test.py index a58cd53..2578a00 100644 --- a/tests/schedule_test.py +++ b/tests/schedule_test.py @@ -155,28 +155,20 @@ def test_schedule_ruleset_to_inp(): ' ..\n' assert len(inp_week_strs) == 1 assert inp_week_strs[0] == \ - '"Office Occupancy Week 1" = WEEK-SCHEDULE\n' \ + '"Office Occupancy Week 1" = WEEK-SCHEDULE-PD\n' \ ' TYPE = FRACTION\n' \ - ' DAYS = (MON)\n' \ - ' DAY-SCHEDULES = "Weekday Office Occupancy"\n' \ - ' DAYS = (TUE)\n' \ - ' DAY-SCHEDULES = "Weekday Office Occupancy"\n' \ - ' DAYS = (WED)\n' \ - ' DAY-SCHEDULES = "Weekday Office Occupancy"\n' \ - ' DAYS = (THU)\n' \ - ' DAY-SCHEDULES = "Weekday Office Occupancy"\n' \ - ' DAYS = (FRI)\n' \ - ' DAY-SCHEDULES = "Weekday Office Occupancy"\n' \ - ' DAYS = (SAT)\n' \ - ' DAY-SCHEDULES = "Saturday Office Occupancy"\n' \ - ' DAYS = (SUN)\n' \ - ' DAY-SCHEDULES = "Sunday Office Occupancy"\n' \ - ' DAYS = (HOL)\n' \ - ' DAY-SCHEDULES = "Sunday Office Occupancy"\n' \ - ' DAYS = (HDD)\n' \ - ' DAY-SCHEDULES = "Winter Office Occupancy"\n' \ - ' DAYS = (CDD)\n' \ - ' DAY-SCHEDULES = "Summer Office Occupancy"\n' \ + ' DAY-SCHEDULES = (\n' \ + ' "Weekday Office Occupancy", $ Monday,\n' \ + ' "Weekday Office Occupancy", $ Tuesday,\n' \ + ' "Weekday Office Occupancy", $ Wednesday,\n' \ + ' "Weekday Office Occupancy", $ Thursday,\n' \ + ' "Weekday Office Occupancy", $ Friday,\n' \ + ' "Saturday Office Occupancy", $ Saturday,\n' \ + ' "Sunday Office Occupancy", $ Sunday,\n' \ + ' "Sunday Office Occupancy", $ Holiday,\n' \ + ' "Winter Office Occupancy", $ Winter Design Day,\n' \ + ' "Summer Office Occupancy", $ Summer Design Day,\n' \ + ' )\n' \ ' ..\n' diff --git a/tests/writer_test.py b/tests/writer_test.py index 439a3c6..cca975b 100644 --- a/tests/writer_test.py +++ b/tests/writer_test.py @@ -30,7 +30,7 @@ def test_shade_writer(): ' V4 = (0.0, 1.0)\n' \ ' ..\n' assert shade_def == \ - '"overhang" = BUILDING-SHADE\n' \ + '"overhang" = FIXED-SHADE\n' \ ' SHAPE = POLYGON\n' \ ' POLYGON = "overhang Plg"\n' \ ' TRANSMITTANCE = 0\n' \ @@ -38,7 +38,7 @@ def test_shade_writer(): ' Y-REF = 0.0\n' \ ' Z-REF = 3.0\n' \ ' TILT = 0.0\n' \ - ' AZIMUTH = 0.0\n' \ + ' AZIMUTH = 180.0\n' \ ' ..\n' fritted_glass_trans = ScheduleRuleset.from_constant_value( @@ -46,7 +46,7 @@ def test_shade_writer(): shade.properties.energy.transmittance_schedule = fritted_glass_trans shade_polygon, shade_def = shade.to.inp(shade) assert shade_def == \ - '"overhang" = BUILDING-SHADE\n' \ + '"overhang" = FIXED-SHADE\n' \ ' SHAPE = POLYGON\n' \ ' POLYGON = "overhang Plg"\n' \ ' TRANSMITTANCE = 0.5\n' \ @@ -54,7 +54,7 @@ def test_shade_writer(): ' Y-REF = 0.0\n' \ ' Z-REF = 3.0\n' \ ' TILT = 0.0\n' \ - ' AZIMUTH = 0.0\n' \ + ' AZIMUTH = 180.0\n' \ ' ..\n' @@ -84,7 +84,7 @@ def test_shade_mesh_writer(): ' Y-REF = 0.0\n' \ ' Z-REF = 4.0\n' \ ' TILT = 0.0\n' \ - ' AZIMUTH = 0.0\n' \ + ' AZIMUTH = 180.0\n' \ ' ..\n' fritted_glass_trans = ScheduleRuleset.from_constant_value( @@ -100,7 +100,7 @@ def test_shade_mesh_writer(): ' Y-REF = 0.0\n' \ ' Z-REF = 4.0\n' \ ' TILT = 0.0\n' \ - ' AZIMUTH = 0.0\n' \ + ' AZIMUTH = 180.0\n' \ ' ..\n' @@ -245,7 +245,7 @@ def test_face_writer(): ' POLYGON = "roof face Plg"\n' \ ' CONSTRUCTION = "Thick Concrete Construction"\n' \ ' TILT = 0.0\n' \ - ' AZIMUTH = 0.0\n' \ + ' AZIMUTH = 180.0\n' \ ' X = 0.0\n' \ ' Y = 0.0\n' \ ' Z = 3.0\n' \ @@ -266,7 +266,7 @@ def test_face_writer(): ' POLYGON = "floor face Plg"\n' \ ' CONSTRUCTION = "Thick Concrete Construction"\n' \ ' TILT = 180.0\n' \ - ' AZIMUTH = 0.0\n' \ + ' AZIMUTH = 180.0\n' \ ' X = 10.0\n' \ ' Y = 0.0\n' \ ' Z = 0.0\n' \ @@ -329,7 +329,7 @@ def test_room_writer(): ' LIGHTING-SCHEDULE = "Generic Office Lighting"\n' \ ' LIGHT-TO-RETURN = 0.0\n' \ ' EQUIPMENT-W/AREA = 0.96\n' \ - ' EQUIPMENT-SCHEDULE = "Generic Office Equipment"\n' \ + ' EQUIP-SCHEDULE = ("Generic Office Equipment")\n' \ ' EQUIP-SENSIBLE = 1.0\n' \ ' EQUIP-LATENT = 0.0\n' \ ' EQUIP-RAD-FRAC = 0.5\n' \