dragonfly-doe2 0.12.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import dragonfly_doe2._extend_dragonfly
@@ -0,0 +1,4 @@
1
+ from dragonfly_doe2.cli import doe2
2
+
3
+ if __name__ == '__main__':
4
+ doe2()
@@ -0,0 +1,33 @@
1
+ # coding=utf-8
2
+ from dragonfly.properties import ModelProperties, Room2DProperties
3
+ import dragonfly.writer.model as model_writer
4
+
5
+ from .properties.model import ModelDoe2Properties
6
+ from .properties.room2d import Room2DDoe2Properties
7
+ from .writer import model_to_inp
8
+
9
+
10
+ # set a hidden doe2 attribute on each core geometry Property class to None
11
+ # define methods to produce doe2 property instances on each Property instance
12
+ ModelProperties._doe2 = None
13
+ Room2DProperties._doe2 = None
14
+
15
+
16
+ def model_doe2_properties(self):
17
+ if self._doe2 is None:
18
+ self._doe2 = ModelDoe2Properties(self.host)
19
+ return self._doe2
20
+
21
+
22
+ def room2d_doe2_properties(self):
23
+ if self._doe2 is None:
24
+ self._doe2 = Room2DDoe2Properties(self.host)
25
+ return self._doe2
26
+
27
+
28
+ # add doe2 property methods to the Properties classes
29
+ ModelProperties.doe2 = property(model_doe2_properties)
30
+ Room2DProperties.doe2 = property(room2d_doe2_properties)
31
+
32
+ # add writers to the honeybee-core modules
33
+ model_writer.inp = model_to_inp
@@ -0,0 +1,18 @@
1
+ """dragonfly-doe2 commands which will be added to the dragonfly CLI."""
2
+ import click
3
+
4
+ from dragonfly.cli import main
5
+ from .translate import translate
6
+
7
+
8
+ # command group for all doe2 extension commands.
9
+ @click.group(help='dragonfly doe2 commands.')
10
+ def doe2():
11
+ pass
12
+
13
+
14
+ # add sub-commands for doe2
15
+ doe2.add_command(translate)
16
+
17
+ # add doe2 sub-commands to dragonfly CLI
18
+ main.add_command(doe2)
@@ -0,0 +1,166 @@
1
+ """dragonfly doe2 translation commands."""
2
+ import click
3
+ import sys
4
+ import logging
5
+ import json
6
+
7
+ from ladybug.commandutil import process_content_to_output
8
+ from dragonfly.model import Model
9
+ from honeybee_doe2.simulation import SimulationPar
10
+
11
+ from dragonfly_doe2.writer import model_to_inp as writer_model_to_inp
12
+
13
+ _logger = logging.getLogger(__name__)
14
+
15
+
16
+ @click.group(help='Commands for translating Dragonfly files to DOE-2.')
17
+ def translate():
18
+ pass
19
+
20
+
21
+ @translate.command('model-to-inp')
22
+ @click.argument('model-file', type=click.Path(
23
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
24
+ @click.option(
25
+ '--multiplier/--full-geometry', ' /-fg', help='Flag to note if the '
26
+ 'multipliers on each Building story will be passed along to the '
27
+ 'generated Honeybee Room objects or if full geometry objects should be '
28
+ 'written for each story in the building.', default=True, show_default=True)
29
+ @click.option(
30
+ '--plenum/--no-plenum', '-p/-np', help='Flag to indicate whether '
31
+ 'ceiling/floor plenum depths assigned to Room2Ds should generate '
32
+ 'distinct 3D Rooms in the translation.', default=True, show_default=True)
33
+ @click.option(
34
+ '--ceil-adjacency/--no-ceil-adjacency', '-a/-na', help='Flag to indicate '
35
+ 'whether adjacencies should be solved between interior stories when '
36
+ 'Room2Ds perfectly match one another in their floor plate. This ensures '
37
+ 'that Surface boundary conditions are used instead of Adiabatic ones. '
38
+ 'Note that this input has no effect when the object-per-model is Story.',
39
+ default=True, show_default=True)
40
+ @click.option(
41
+ '--sim-par-json', '-sp', help='Full path to a honeybee-doe2 SimulationPar '
42
+ 'JSON that describes all of the settings for the simulation. If unspecified, '
43
+ 'default parameters will be generated.', default=None, show_default=True,
44
+ type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True))
45
+ @click.option(
46
+ '--hvac-mapping', '-hm', help='Text to indicate how HVAC systems should be '
47
+ 'assigned to the exported model. Story will assign one HVAC system for each '
48
+ 'distinct level polygon, Model will use only one HVAC system for the whole model '
49
+ 'and AssignedHVAC will follow how the HVAC systems have been assigned to the'
50
+ 'Rooms.properties.energy.hvac. Choose from: Room, Story, Model, AssignedHVAC',
51
+ default='Story', show_default=True, type=str)
52
+ @click.option(
53
+ '--include-interior-walls/--exclude-interior-walls', ' /-xw', help='Flag to note '
54
+ 'whether interior walls should be excluded from the export.',
55
+ default=True, show_default=True)
56
+ @click.option(
57
+ '--include-interior-ceilings/--exclude-interior-ceilings', ' /-xc', help='Flag to '
58
+ 'note whether interior ceilings should be excluded from the export.',
59
+ default=True, show_default=True)
60
+ @click.option(
61
+ '--equest-version', '-eq', help='Optional text string to denote the version '
62
+ 'of eQuest for which the INP definition will be generated. If unspecified '
63
+ 'or unrecognized, the latest version of eQuest will be used.',
64
+ default='3.65', show_default=True, type=str)
65
+ @click.option(
66
+ '--output-file', '-o', help='Optional INP file path to output the INP string '
67
+ 'of the translation. By default this will be printed out to stdout.',
68
+ type=click.File('w'), default='-', show_default=True)
69
+ def model_to_inp_cli(
70
+ model_file, multiplier, plenum, ceil_adjacency, sim_par_json, hvac_mapping,
71
+ include_interior_walls, include_interior_ceilings, equest_version, output_file
72
+ ):
73
+ """Translate a Dragonfly Model file to a Radiance string.
74
+
75
+ The resulting string will include all geometry and all modifiers.
76
+
77
+ \b
78
+ Args:
79
+ model_file: Full path to a Dragonfly Model JSON or Pkl file.
80
+ """
81
+ try:
82
+ full_geometry = not multiplier
83
+ no_plenum = not plenum
84
+ no_ceil_adjacency = not ceil_adjacency
85
+ exclude_interior_walls = not include_interior_walls
86
+ exclude_interior_ceilings = not include_interior_ceilings
87
+ model_to_inp(
88
+ model_file, full_geometry, no_plenum, no_ceil_adjacency,
89
+ sim_par_json, hvac_mapping,
90
+ exclude_interior_walls, exclude_interior_ceilings,
91
+ equest_version, output_file)
92
+ except Exception as e:
93
+ _logger.exception('Model translation failed.\n{}\n'.format(e))
94
+ sys.exit(1)
95
+ else:
96
+ sys.exit(0)
97
+
98
+
99
+ def model_to_inp(
100
+ model_file, full_geometry=False, no_plenum=False, no_ceil_adjacency=False,
101
+ sim_par_json=None, hvac_mapping='Story', exclude_interior_walls=False,
102
+ exclude_interior_ceilings=False, equest_version=None, output_file=None,
103
+ multiplier=True, plenum=True, ceil_adjacency=True,
104
+ include_interior_walls=True, include_interior_ceilings=True
105
+ ):
106
+ """Translate a Model file to an INP string.
107
+
108
+ Args:
109
+ model_file: Full path to a Model JSON file (DFJSON) or a Model pkl (DFpkl) file.
110
+ full_geometry: Boolean to note if the multipliers on each Building story
111
+ will be passed along to the generated Honeybee Room objects or if
112
+ full geometry objects should be written for each story in the
113
+ building. (Default: False).
114
+ no_plenum: Boolean to indicate whether ceiling/floor plenum depths
115
+ assigned to Room2Ds should generate distinct 3D Rooms in the
116
+ translation. (Default: False).
117
+ ceil_adjacency: Boolean to indicate whether adjacencies should be solved
118
+ between interior stories when Room2Ds perfectly match one another
119
+ in their floor plate. This ensures that Surface boundary conditions
120
+ are used instead of Adiabatic ones. Note that this input has no
121
+ effect when the object-per-model is Story. (Default: False).
122
+ simulation_par: A honeybee-doe2 SimulationPar object to specify how the
123
+ DOE-2 simulation should be run. If None, default simulation
124
+ parameters will be generated, which will run the simulation for the
125
+ full year. (Default: None).
126
+ hvac_mapping: Text to indicate how HVAC systems should be assigned to the
127
+ exported model. Story will assign one HVAC system for each distinct
128
+ level polygon, Model will use only one HVAC system for the whole model
129
+ and AssignedHVAC will follow how the HVAC systems have been assigned
130
+ to the Rooms.properties.energy.hvac. Choose from the options
131
+ below. (Default: Story).
132
+
133
+ * Room
134
+ * Story
135
+ * Model
136
+ * AssignedHVAC
137
+
138
+ exclude_interior_walls: Boolean to note whether interior wall Faces
139
+ should be excluded from the resulting string. (Default: False).
140
+ exclude_interior_ceilings: Boolean to note whether interior ceiling
141
+ Faces should be excluded from the resulting string. (Default: False).
142
+ equest_version: An optional text string to denote the version of eQuest
143
+ for which the INP definition will be generated. If unspecified
144
+ or unrecognized, the latest version of eQuest will be used.
145
+ output_file: Optional INP file to output the INP string of the translation.
146
+ If None, the string will be returned from this method. (Default: None).
147
+ """
148
+ # re-serialize the Dragonfly Model
149
+ model = Model.from_file(model_file)
150
+
151
+ # load simulation parameters if specified
152
+ sim_par = None
153
+ if sim_par_json is not None:
154
+ with open(sim_par_json) as json_file:
155
+ data = json.load(json_file)
156
+ sim_par = SimulationPar.from_dict(data)
157
+
158
+ # create the strings for the model
159
+ multiplier = not full_geometry
160
+ ceil_adjacency = not no_ceil_adjacency
161
+ inp_str = writer_model_to_inp(
162
+ model, multiplier, no_plenum, ceil_adjacency, sim_par, hvac_mapping,
163
+ exclude_interior_walls, exclude_interior_ceilings, equest_version)
164
+
165
+ # write out the INP file
166
+ return process_content_to_output(inp_str, output_file)
@@ -0,0 +1 @@
1
+ """dragonfly-doe2 properties."""
@@ -0,0 +1,313 @@
1
+ # coding=utf-8
2
+ """Model DOE-2 Properties."""
3
+ from ladybug_geometry.geometry3d import Face3D
4
+ from honeybee.units import parse_distance_string
5
+
6
+ from honeybee_doe2.grouping import _grouped_floor_boundary
7
+
8
+
9
+ class ModelDoe2Properties(object):
10
+ """DOE-2 Properties for Dragonfly Model.
11
+
12
+ Args:
13
+ host: A dragonfly_core Model object that hosts these properties.
14
+
15
+ Properties:
16
+ * host
17
+ """
18
+ # dictionary mapping validation error codes to a corresponding check function
19
+ ERROR_MAP = {
20
+ '030101': 'check_room_2d_floor_plate_vertex_count',
21
+ '030102': 'check_no_room_2d_floor_plate_holes',
22
+ '030103': 'check_no_story_courtyards'
23
+ }
24
+
25
+ def __init__(self, host):
26
+ """Initialize ModelDoe2Properties."""
27
+ self._host = host
28
+
29
+ @property
30
+ def host(self):
31
+ """Get the Model object hosting these properties."""
32
+ return self._host
33
+
34
+ def check_for_extension(self, raise_exception=True, detailed=False):
35
+ """Check that the Model is valid for DOE-2 simulation.
36
+
37
+ This process includes all relevant dragonfly-core checks as well as checks
38
+ that apply only for DOE-2.
39
+
40
+ Args:
41
+ raise_exception: Boolean to note whether a ValueError should be raised
42
+ if any errors are found. If False, this method will simply
43
+ return a text string with all errors that were found. (Default: True).
44
+ detailed: Boolean for whether the returned object is a detailed list of
45
+ dicts with error info or a string with a message. (Default: False).
46
+
47
+ Returns:
48
+ A text string with all errors that were found or a list if detailed is True.
49
+ This string (or list) will be empty if no errors were found.
50
+ """
51
+ # set up defaults to ensure the method runs correctly
52
+ detailed = False if raise_exception else detailed
53
+ msgs = []
54
+ tol = self.host.tolerance
55
+ ang_tol = self.host.angle_tolerance
56
+
57
+ # perform checks for duplicate identifiers, which might mess with other checks
58
+ msgs.append(self.host.check_all_duplicate_identifiers(False, detailed))
59
+
60
+ # perform checks for key dragonfly model schema rules
61
+ msgs.append(self.host.check_degenerate_room_2ds(tol, False, detailed))
62
+ msgs.append(self.host.check_self_intersecting_room_2ds(tol, False, detailed))
63
+ msgs.append(self.host.check_plenum_depths(tol, False, detailed))
64
+ msgs.append(self.host.check_window_parameters_valid(tol, False, detailed))
65
+ msgs.append(self.host.check_no_room2d_overlaps(tol, False, detailed))
66
+ msgs.append(self.host.check_collisions_between_stories(tol, False, detailed))
67
+ msgs.append(self.host.check_roofs_above_rooms(tol, False, detailed))
68
+ msgs.append(self.host.check_room2d_floor_heights_valid(False, detailed))
69
+ msgs.append(self.host.check_missing_adjacencies(False, detailed))
70
+ msgs.append(self.host.check_all_room3d(tol, ang_tol, False, detailed))
71
+
72
+ # perform checks that are specific to DOE-2
73
+ msgs.append(self.check_room_2d_floor_plate_vertex_count(False, detailed))
74
+ msgs.append(self.check_no_room_2d_floor_plate_holes(False, detailed))
75
+ msgs.append(self.check_no_story_courtyards(tol, False, detailed))
76
+
77
+ # output a final report of errors or raise an exception
78
+ full_msgs = [msg for msg in msgs if msg]
79
+ if detailed:
80
+ return [m for msg in full_msgs for m in msg]
81
+ full_msg = '\n'.join(full_msgs)
82
+ if raise_exception and len(full_msgs) != 0:
83
+ raise ValueError(full_msg)
84
+ return full_msg
85
+
86
+ def check_generic(self, raise_exception=True, detailed=False):
87
+ """Check generic of the aspects of the Model DOE-2 properties.
88
+
89
+ This includes checks for everything except holes in floor plates and
90
+ courtyard stories.
91
+
92
+ Args:
93
+ raise_exception: Boolean to note whether a ValueError should be raised
94
+ if any errors are found. If False, this method will simply
95
+ return a text string with all errors that were found.
96
+ detailed: Boolean for whether the returned object is a detailed list of
97
+ dicts with error info or a string with a message. (Default: False).
98
+
99
+ Returns:
100
+ A text string with all errors that were found or a list if detailed is True.
101
+ This string (or list) will be empty if no errors were found.
102
+ """
103
+ # set up defaults to ensure the method runs correctly
104
+ detailed = False if raise_exception else detailed
105
+ msgs = []
106
+ # perform checks for specific doe-2 simulation rules
107
+ msgs.append(self.check_room_2d_floor_plate_vertex_count(False, detailed))
108
+ # output a final report of errors or raise an exception
109
+ full_msgs = [msg for msg in msgs if msg]
110
+ if detailed:
111
+ return [m for msg in full_msgs for m in msg]
112
+ full_msg = '\n'.join(full_msgs)
113
+ if raise_exception and len(full_msgs) != 0:
114
+ raise ValueError(full_msg)
115
+ return full_msg
116
+
117
+ def check_all(self, raise_exception=True, detailed=False):
118
+ """Check all of the aspects of the Model DOE-2 properties.
119
+
120
+ Args:
121
+ raise_exception: Boolean to note whether a ValueError should be raised
122
+ if any errors are found. If False, this method will simply
123
+ return a text string with all errors that were found.
124
+ detailed: Boolean for whether the returned object is a detailed list of
125
+ dicts with error info or a string with a message. (Default: False).
126
+
127
+ Returns:
128
+ A text string with all errors that were found or a list if detailed is True.
129
+ This string (or list) will be empty if no errors were found.
130
+ """
131
+ # set up defaults to ensure the method runs correctly
132
+ detailed = False if raise_exception else detailed
133
+ msgs = []
134
+ tol = self.host.tolerance
135
+ # perform checks for specific doe-2 simulation rules
136
+ msgs.append(self.check_room_2d_floor_plate_vertex_count(False, detailed))
137
+ msgs.append(self.check_no_room_2d_floor_plate_holes(False, detailed))
138
+ msgs.append(self.check_no_story_courtyards(tol, False, detailed))
139
+ # output a final report of errors or raise an exception
140
+ full_msgs = [msg for msg in msgs if msg]
141
+ if detailed:
142
+ return [m for msg in full_msgs for m in msg]
143
+ full_msg = '\n'.join(full_msgs)
144
+ if raise_exception and len(full_msgs) != 0:
145
+ raise ValueError(full_msg)
146
+ return full_msg
147
+
148
+ def check_room_2d_floor_plate_vertex_count(
149
+ self, raise_exception=True, detailed=False):
150
+ """Check whether any Room2Ds floor geometry exceeds the maximum vertex count.
151
+
152
+ The DOE-2 engine currently does not support such rooms and limits the
153
+ total number of vertices to 120.
154
+
155
+ Args:
156
+ raise_exception: If True, a ValueError will be raised if the Room2D
157
+ floor plate exceeds the maximum number of vertices supported by
158
+ DOE-2. (Default: True).
159
+ detailed: Boolean for whether the returned object is a detailed list of
160
+ dicts with error info or a string with a message. (Default: False).
161
+
162
+ Returns:
163
+ A string with the message or a list with a dictionary if detailed is True.
164
+ """
165
+ detailed = False if raise_exception else detailed
166
+ msgs = []
167
+ for room in self.host.room_2ds:
168
+ msg = room.properties.doe2.check_floor_plate_vertex_count(False, detailed)
169
+ if detailed:
170
+ msgs.extend(msg)
171
+ elif msg != '':
172
+ msgs.append(msg)
173
+ if detailed:
174
+ return msgs
175
+ full_msg = '\n'.join(msgs)
176
+ if raise_exception and len(msgs) != 0:
177
+ raise ValueError(full_msg)
178
+ return full_msg
179
+
180
+ def check_no_room_2d_floor_plate_holes(self, raise_exception=True, detailed=False):
181
+ """Check whether any Room2D floor geometry has holes.
182
+
183
+ EQuest currently has no way to represent such rooms so, if the issue
184
+ is not addressed, the hole will simply be removed as part of the
185
+ process of exporting to an INP file.
186
+
187
+ Args:
188
+ raise_exception: If True, a ValueError will be raised if the Room2D
189
+ floor plate has one or more holes. (Default: True).
190
+ detailed: Boolean for whether the returned object is a detailed list of
191
+ dicts with error info or a string with a message. (Default: False).
192
+
193
+ Returns:
194
+ A string with the message or a list with a dictionary if detailed is True.
195
+ """
196
+ detailed = False if raise_exception else detailed
197
+ msgs = []
198
+ for room in self.host.room_2ds:
199
+ msg = room.properties.doe2.check_no_floor_plate_holes(False, detailed)
200
+ if detailed:
201
+ msgs.extend(msg)
202
+ elif msg != '':
203
+ msgs.append(msg)
204
+ if detailed:
205
+ return msgs
206
+ full_msg = '\n'.join(msgs)
207
+ if raise_exception and len(msgs) != 0:
208
+ raise ValueError(full_msg)
209
+ return full_msg
210
+
211
+ def check_no_story_courtyards(
212
+ self, tolerance=None, raise_exception=True, detailed=False):
213
+ """Check that Story floor plates do not contain courtyards.
214
+
215
+ EQuest currently has no way to represent such courtyards so, if the issue
216
+ is not addressed, the courtyards will simply be removed as part of the
217
+ process of exporting to an INP file.
218
+
219
+ Args:
220
+ tolerance: The tolerance to be used when joining the Room2D floor
221
+ plates together into a Story floor plate. If None, the Model
222
+ tolerance will be used. (Default: None).
223
+ raise_exception: Boolean to note whether a ValueError should be raised
224
+ if the story contains a courtyard. (Default: True).
225
+ detailed: Boolean for whether the returned object is a detailed list of
226
+ dicts with error info or a string with a message. (Default: False).
227
+
228
+ Returns:
229
+ A string with the message or a list with a dictionary if detailed is True.
230
+ """
231
+ # establish the tolerance and gap width at which point it is clearly a courtyard
232
+ tolerance = self.host.tolerance if tolerance is None else tolerance
233
+ court_width = parse_distance_string('1ft', self.host.units)
234
+ # loop through the stories and identify any courtyards
235
+ story_msgs = []
236
+ for bldg in self.host.buildings:
237
+ for story in bldg.unique_stories:
238
+ floor_geos = [room.floor_geometry for room in story.room_2ds]
239
+ joined_geos = _grouped_floor_boundary(floor_geos, tolerance)
240
+ c_count = 0
241
+ for geo in joined_geos:
242
+ if geo.has_holes:
243
+ for hole in geo.holes:
244
+ try:
245
+ h_geo = Face3D(hole)
246
+ h_geo = h_geo.remove_colinear_vertices(court_width)
247
+ max_len = max(s.length for s in h_geo.boundary_segments)
248
+ tol_area = max_len * court_width
249
+ if h_geo.area > tol_area:
250
+ c_count += 1
251
+ except (AssertionError, ValueError):
252
+ pass # gap is too small to be a true courtyard
253
+ if c_count != 0:
254
+ hole_msg = 'a courtyard' if c_count == 1 \
255
+ else '{} courtyards'.format(c_count)
256
+ msg = 'The geometry of Story "{}" contains {}, which eQuest ' \
257
+ 'cannot represent.'.format(story.display_name, hole_msg)
258
+ if detailed:
259
+ msg = {
260
+ 'type': 'ValidationError',
261
+ 'code': '030103',
262
+ 'error_type': 'Story Floor Plate Contains Courtyards',
263
+ 'extension_type': 'DOE2',
264
+ 'element_type': 'Room2D',
265
+ 'element_id': [r.identifier for r in story.room_2ds],
266
+ 'element_name': [r.display_name for r in story.room_2ds],
267
+ 'message': msg
268
+ }
269
+ story_msgs.append(msg)
270
+ if detailed:
271
+ return story_msgs
272
+ if story_msgs != []:
273
+ msg = 'The following Stories have courtyards in their floor plates' \
274
+ ':\n{}'.format('\n'.join(story_msgs))
275
+ if raise_exception:
276
+ raise ValueError(msg)
277
+ return msg
278
+ return ''
279
+
280
+ def to_dict(self):
281
+ """Return Model DOE-2 properties as a dictionary."""
282
+ return {'doe2': {'type': 'ModelDoe2Properties'}}
283
+
284
+ def apply_properties_from_dict(self, data):
285
+ """Apply the DOE-2 properties of a dictionary to the host Model of this object.
286
+
287
+ Args:
288
+ data: A dictionary representation of an entire dragonfly-core Model.
289
+ Note that this dictionary must have ModelDoe2Properties in order
290
+ for this method to successfully apply the DOE-2 properties.
291
+ """
292
+ assert 'doe2' in data['properties'], \
293
+ 'Dictionary possesses no ModelDoe2Properties.'
294
+ room_doe2_dicts = []
295
+ if 'buildings' in data and data['buildings'] is not None:
296
+ for b_dict in data['buildings']:
297
+ if 'unique_stories' in b_dict and b_dict['unique_stories'] is not None:
298
+ for story_dict in b_dict['unique_stories']:
299
+ for room_dict in story_dict['room_2ds']:
300
+ try:
301
+ room_doe2_dicts.append(room_dict['properties']['doe2'])
302
+ except KeyError:
303
+ room_doe2_dicts.append(None)
304
+ if len(room_doe2_dicts) != 0:
305
+ for room, r_dict in zip(self.host.room_2ds, room_doe2_dicts):
306
+ if r_dict is not None:
307
+ room.properties.doe2.apply_properties_from_dict(r_dict)
308
+
309
+ def ToString(self):
310
+ return self.__repr__()
311
+
312
+ def __repr__(self):
313
+ return 'Model DOE2 Properties: [host: {}]'.format(self.host.display_name)