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