-
Notifications
You must be signed in to change notification settings - Fork 0
Tweaks 'genConstruction' (tested) #7
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
Changes from 4 commits
31fe98c
0ca5976
01f0f11
f36e9a2
d4634be
a57c25c
3c630f9
02b8023
ab70a66
a33b3b0
efacdf8
b294f50
8214f2f
7a35d64
7fbf1a2
3721304
c212c30
c2129da
5c33a23
f24ad09
10087e1
512e84b
bb299a7
e4df93b
1c3d672
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,8 +107,8 @@ class _CN: | |
|
|
||
| # Default (~1980s) envelope Uo (W/m2•K), based on surface type. | ||
| _uo = dict( | ||
| shading = None, # N/A | ||
| partition = None, # N/A | ||
| shading = None, # N/A | ||
| partition = None, # N/A | ||
| wall = 0.384, # rated R14.8 hr•ft2F/Btu | ||
| roof = 0.327, # rated R17.6 hr•ft2F/Btu | ||
| floor = 0.317, # rated R17.9 hr•ft2F/Btu (exposed floor) | ||
|
|
@@ -335,9 +335,8 @@ def genConstruction(model=None, specs=dict()): | |
| ide = "OSut.CON." + specs["type"] | ||
| if specs["type"] not in uo(): | ||
| return oslg.invalid("surface type", mth, 2, CN.ERR) | ||
| if "uo" not in specs: | ||
| specs["uo"] = uo()[ specs["type"] ] | ||
|
|
||
| if "uo" not in specs: specs["uo"] = uo()[ specs["type"] ] # can be None | ||
| u = specs["uo"] | ||
|
|
||
| if u: | ||
|
|
@@ -348,6 +347,8 @@ def genConstruction(model=None, specs=dict()): | |
|
|
||
| if u < 0: | ||
| return oslg.negative(id + " Uo", mth, CN.ERR) | ||
| if round(u, 2) == 0: | ||
| return oslg.zero(id + " Uo", mth, CN.ERR) | ||
| if u > 5.678: | ||
| return oslg.invalid(id + " Uo (> 5.678)", mth, 2, CN.ERR) | ||
|
|
||
|
|
@@ -581,15 +582,15 @@ def genConstruction(model=None, specs=dict()): | |
| a["compo" ]["id" ] = "OSut." + mt + ".%03d" % int(d * 1000) | ||
|
|
||
| elif specs["type"] == "window": | ||
| a["glazing"]["u" ] = specs["uo"] | ||
| a["glazing"]["u" ] = u if u else uo()["window"] | ||
| a["glazing"]["shgc"] = 0.450 | ||
| if "shgc" in specs: a["glazing"]["shgc"] = specs["shgc"] | ||
| a["glazing"]["id" ] = "OSut.window" | ||
| a["glazing"]["id" ] += ".U%.1f" % a["glazing"]["u"] | ||
| a["glazing"]["id" ] += ".SHGC%d" % (a["glazing"]["shgc"]*100) | ||
|
|
||
| elif specs["type"] == "skylight": | ||
| a["glazing"]["u" ] = specs["uo"] | ||
| a["glazing"]["u" ] = u if u else uo()["skylight"] | ||
| a["glazing"]["shgc"] = 0.450 | ||
| if "shgc" in specs: a["glazing"]["shgc"] = specs["shgc"] | ||
| a["glazing"]["id" ] = "OSut.skylight" | ||
|
|
@@ -599,14 +600,14 @@ def genConstruction(model=None, specs=dict()): | |
| if a["glazing"]: | ||
| layers = openstudio.model.FenestrationMaterialVector() | ||
|
|
||
| u = a["glazing"]["u" ] | ||
| u0 = a["glazing"]["u" ] | ||
| shgc = a["glazing"]["shgc"] | ||
| lyr = model.getSimpleGlazingByName(a["glazing"]["id"]) | ||
|
|
||
| if lyr: | ||
| lyr = lyr.get() | ||
| else: | ||
| lyr = openstudio.model.SimpleGlazing(model, u, shgc) | ||
| lyr = openstudio.model.SimpleGlazing(model, u0, shgc) | ||
| lyr.setName(a["glazing"]["id"]) | ||
|
|
||
| layers.append(lyr) | ||
|
|
@@ -635,49 +636,54 @@ def genConstruction(model=None, specs=dict()): | |
|
|
||
| layers.append(lyr) | ||
|
|
||
| c = openstudio.model.Construction(layers) | ||
| c = openstudio.model.Construction(layers) | ||
| c.setName(ide) | ||
|
|
||
| # Adjust insulating layer thickness or conductivity to match requested Uo. | ||
| if not a["glazing"]: | ||
| ro = 1 / specs["uo"] - film()[specs["type"]] if specs["uo"] else 0 | ||
| if u and not a["glazing"]: | ||
| ro = 1 / u - flm | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For opaque construction, adjusting insulating layer thickness (to meet a requested assembly Uo factor) is skipped entirely if the requested Uo isn't:
|
||
|
|
||
| if specs["type"] == "door": # 1x layer, adjust conductivity | ||
| layer = c.getLayer(0).to_StandardOpaqueMaterial() | ||
| if ro > 0: | ||
| if specs["type"] == "door": # 1x layer, adjust conductivity | ||
| layer = c.getLayer(0).to_StandardOpaqueMaterial() | ||
|
|
||
| if not layer: | ||
| return oslg.invalid(id + " standard material?", mth, 0) | ||
| if not layer: | ||
| return oslg.invalid(id + " standard material?", mth, 0) | ||
|
|
||
| layer = layer.get() | ||
| k = layer.thickness() / ro | ||
| layer.setConductivity(k) | ||
| layer = layer.get() | ||
| k = layer.thickness() / ro | ||
| layer.setConductivity(k) | ||
|
|
||
| elif ro > 0: # multiple layers, adjust insulating layer thickness | ||
| lyr = insulatingLayer(c) | ||
| else: # multiple layers, adjust insulating layer thickness | ||
| lyr = insulatingLayer(c) | ||
|
|
||
| if not lyr["index"] or not lyr["type"] or not lyr["r"]: | ||
| return oslg.invalid(id + " construction", mth, 0) | ||
| if not lyr["index"] or not lyr["type"] or not lyr["r"]: | ||
| return oslg.invalid(id + " construction", mth, 0) | ||
|
|
||
| index = lyr["index"] | ||
| layer = c.getLayer(index).to_StandardOpaqueMaterial() | ||
| index = lyr["index"] | ||
| layer = c.getLayer(index).to_StandardOpaqueMaterial() | ||
|
|
||
| if not layer: | ||
| return oslg.invalid(id + " material %d" % index, mth, 0) | ||
| if not layer: | ||
| return oslg.invalid(id + " material %d" % index, mth, 0) | ||
|
|
||
| layer = layer.get() | ||
| k = layer.conductivity() | ||
| d = (ro - rsi(c) + lyr["r"]) * k | ||
| layer = layer.get() | ||
| k = layer.conductivity() | ||
| d = (ro - rsi(c) + lyr["r"]) * k | ||
|
|
||
| if d < 0.03: | ||
| return oslg.invalid(id + " adjusted material thickness", mth, 0) | ||
| if d < 0.03: | ||
| m = id + " adjusted material thickness" | ||
| return oslg.invalid(m, mth, 0) | ||
|
|
||
| nom = re.sub(r'[^a-zA-Z]', '', layer.nameString()) | ||
| nom = re.sub(r'OSut', '', nom) | ||
| nom = "OSut." + nom + ".%03d" % int(d * 1000) | ||
| nom = re.sub(r'[^a-zA-Z]', '', layer.nameString()) | ||
| nom = re.sub(r'OSut', '', nom) | ||
| nom = "OSut." + nom + ".%03d" % int(d * 1000) | ||
|
|
||
| if not model.getStandardOpaqueMaterialByName(nom): | ||
| layer.setName(nom) | ||
| layer.setThickness(d) | ||
| if model.getStandardOpaqueMaterialByName(nom): | ||
| omat = model.getStandardOpaqueMaterialByName(nom).get() | ||
| c.setLayer(index, omat) | ||
| else: | ||
| layer.setName(nom) | ||
| layer.setThickness(d) | ||
|
|
||
| return c | ||
|
|
||
|
|
@@ -1650,18 +1656,27 @@ def scheduleIntervalMinMax(sched=None) -> dict: | |
| - "min" (float): min temperature. (None if invalid inputs - see logs). | ||
| - "max" (float): max temperature. (None if invalid inputs - see logs). | ||
| """ | ||
| mth = "osut.scheduleCompactMinMax" | ||
| mth = "osut.scheduleIntervalMinMax" | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo |
||
| cl = openstudio.model.ScheduleInterval | ||
| vals = [] | ||
| res = dict(min=None, max=None) | ||
|
|
||
| if not isinstance(sched, cl): | ||
| return oslg.mismatch("sched", sched, cl, mth, CN.DBG, res) | ||
|
|
||
| vals = sched.timeSeries().values() | ||
| values = sched.timeSeries().values() | ||
|
|
||
| res["min"] = min(values) | ||
| res["max"] = max(values) | ||
| for i in range(len(values)): | ||
| try: | ||
| value = float(values[i]) | ||
| vals.append(value) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better. |
||
| except: | ||
| oslg.invalid("numerical at %d" % i, mth, 1, CN.ERR) | ||
|
|
||
| if not vals: return res | ||
|
|
||
| res["min"] = min(vals) | ||
| res["max"] = max(vals) | ||
|
|
||
| try: | ||
| res["min"] = float(res["min"]) | ||
|
|
@@ -2595,6 +2610,17 @@ def availabilitySchedule(model=None, avl=""): | |
|
|
||
| return schedule | ||
|
|
||
| # ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- # | ||
| # This final set of utilities targets OpenStudio geometry. Many of the | ||
| # following geometry methods rely on Boost as an OpenStudio dependency. | ||
| # As per Boost requirements, points (e.g. vertical polygon) must be 'aligned': | ||
| # - first rotated/tilted as to lay flat along XY plane (Z-axis ~= 0) | ||
| # - initial Z-axis values now become Y-axis values | ||
| # - points with the lowest X-axis values are 'aligned' along X-axis (0) | ||
| # - points with the lowest Z-axis values are 'aligned' along Y-axis (0) | ||
| # - for several Boost methods, points must be clockwise in sequence | ||
| # | ||
| # Check OSut's poly() method, which offers such Boost-related options. | ||
|
|
||
| def transforms(group=None) -> dict: | ||
| """"Returns OpenStudio site/space transformation & rotation angle. | ||
|
|
@@ -2698,7 +2724,7 @@ def p3Dv(pts=None) -> openstudio.Point3dVector: | |
| pts (list): OpenStudio 3D points. | ||
|
|
||
| Returns: | ||
| openstudio.Point3dVector: Vector of 3D points (see logs if empty). | ||
| openstudio.Point3dVector: Vector of 3D points (see 'p3Dv' logs if empty). | ||
|
|
||
| """ | ||
| mth = "osut.p3Dv" | ||
|
|
@@ -3444,13 +3470,13 @@ def lineIntersection(s1=[], s2=[]): | |
| xa1b1 = a.cross(a1b1) | ||
| xa1b2 = a.cross(a1b2) | ||
|
|
||
| if xa1b1.length() < CN.TOL2: | ||
| if isPointAlongSegment(a1, [a2, b1]): return None | ||
| if isPointAlongSegment(a2, [a1, b1]): return None | ||
|
|
||
| if xa1b2.length() < CN.TOL2: | ||
| if isPointAlongSegment(a1, [a2, b2]): return None | ||
| if isPointAlongSegment(a2, [a1, b2]): return None | ||
| # if xa1b1.length() < CN.TOL2: | ||
| # if isPointAlongSegment(a1, [a2, b1]): return None | ||
| # if isPointAlongSegment(a2, [a1, b1]): return None | ||
| # | ||
| # if xa1b2.length() < CN.TOL2: | ||
| # if isPointAlongSegment(a1, [a2, b2]): return None | ||
| # if isPointAlongSegment(a2, [a1, b2]): return None | ||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now redundant checks. |
||
| # Both segment endpoints can't be 'behind' point. | ||
| if a.dot(a1b1) < 0 and a.dot(a1b2) < 0: return None | ||
|
|
@@ -6079,7 +6105,7 @@ def genSlab(pltz=[], z=0) -> openstudio.Point3dVector: | |
| slb = vtx | ||
|
|
||
| # Once joined, re-adjust Z-axis coordinates. | ||
| if abs(z) > CN.TOL: | ||
| if round(z, 2) != 0.00: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More appropriate. |
||
| vtx = openstudio.Point3dVector() | ||
|
|
||
| for pt in slb: vtx.append(openstudio.Point3d(pt.x(), pt.y(), z)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -392,7 +392,7 @@ def test05_construction_generation(self): | |
| self.assertEqual(o.status(), 0) | ||
| del model | ||
|
|
||
| # Insulated (conditioned), parking garage roof (polyiso under 8" slab). | ||
| # Roof above conditioned parking garage (polyiso under 8" slab). | ||
| specs = dict(type="roof", uo=0.214, clad="heavy", frame="medium", finish="none") | ||
| model = openstudio.model.Model() | ||
| c = osut.genConstruction(model, specs) | ||
|
|
@@ -649,6 +649,41 @@ def test05_construction_generation(self): | |
| self.assertEqual(o.status(), 0) | ||
| del model | ||
|
|
||
| # Invalid Uo (here, skylights and windows inherit default Uo values) | ||
| specs = dict(type="skylight", uo=None) | ||
| model = openstudio.model.Model() | ||
| c = osut.genConstruction(model, specs) | ||
| self.assertEqual(o.status(), 0) | ||
| self.assertFalse(o.logs()) | ||
| self.assertTrue(c) | ||
| self.assertTrue(isinstance(c, openstudio.model.Construction)) | ||
| self.assertEqual(c.nameString(), "OSut.CON.skylight") | ||
| self.assertTrue(c.layers()) | ||
| self.assertEqual(len(c.layers()), 1) | ||
| self.assertEqual(c.layers()[0].nameString(), "OSut.skylight.U3.5.SHGC45") | ||
| r = osut.rsi(c) | ||
| self.assertAlmostEqual(r, 1/osut.uo()["skylight"], places=3) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case of fenestrated assemblies, an invalid Uo request is ignored and the glazing assembly inherits a default OSut Uo (here 3.5 W/m2.K for a skylight). |
||
| self.assertFalse(o.logs()) | ||
| self.assertEqual(o.status(), 0) | ||
| del model | ||
|
|
||
| # Invalid Uo (here, Uo-adjustments are ignored altogether) | ||
| specs = dict(type="wall", uo=None) | ||
| model = openstudio.model.Model() | ||
| c = osut.genConstruction(model, specs) | ||
| self.assertEqual(o.status(), 0) | ||
| self.assertFalse(o.logs()) | ||
| self.assertTrue(c) | ||
| self.assertTrue(isinstance(c, openstudio.model.Construction)) | ||
| self.assertEqual(c.nameString(), "OSut.CON.wall") | ||
| self.assertTrue(c.layers()) | ||
| self.assertEqual(len(c.layers()), 4) | ||
| r = osut.rsi(c) | ||
| self.assertAlmostEqual(1/r, 2.23, places=2) # not matching any defaults | ||
| self.assertFalse(o.logs()) | ||
| self.assertEqual(o.status(), 0) | ||
| del model | ||
|
|
||
| def test06_internal_mass(self): | ||
| o = osut.oslg | ||
| self.assertEqual(o.status(), 0) | ||
|
|
@@ -1696,9 +1731,27 @@ def test17_minmax_heatcool_setpoints(self): | |
| self.assertTrue(cc.setTemperatureCalculationRequestedAfterLayerNumber(1)) | ||
| self.assertTrue(floor.setConstruction(cc)) | ||
|
|
||
| # Test 'fixed interval' schedule. Annual time series - no variation. | ||
| start = model.getYearDescription().makeDate(1, 1) | ||
| inter = openstudio.Time(0, 1, 0, 0) | ||
| values = openstudio.createVector([22.78] * 8760) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OSut enabled ScheduleFixedInterval checks, e.g.:
Yet no unit test had been in place. |
||
| series = openstudio.TimeSeries(start, inter, values, "") | ||
| limits = openstudio.model.ScheduleTypeLimits(model) | ||
| limits.setName("Radiant Electric Heating Setpoint Schedule Type Limits") | ||
| self.assertTrue(limits.setNumericType("Continuous")) | ||
| self.assertTrue(limits.setUnitType("Temperature")) | ||
|
|
||
| schedule = openstudio.model.ScheduleFixedInterval(model) | ||
| schedule.setName("Radiant Electric Heating Setpoint Schedule") | ||
| self.assertTrue(schedule.setTimeSeries(series)) | ||
| self.assertTrue(schedule.setTranslatetoScheduleFile(False)) | ||
| self.assertTrue(schedule.setScheduleTypeLimits(limits)) | ||
|
|
||
| tvals = schedule.timeSeries().values() | ||
| self.assertTrue(isinstance(tvals, openstudio.Vector)) | ||
| for i in range(len(tvals)): self.assertTrue(isinstance(tvals[i], float)) | ||
|
|
||
| availability = osut.availabilitySchedule(model) | ||
| schedule = openstudio.model.ScheduleConstant(model) | ||
| self.assertTrue(schedule.setValue(22.78)) # reuse cooling setpoint | ||
|
|
||
| # Create radiant electric heating. | ||
| ht = (openstudio.model.ZoneHVACLowTemperatureRadiantElectric( | ||
|
|
@@ -3606,7 +3659,7 @@ def test26_ulc_blc(self): | |
| # [70, 0, 0] | ||
| # [70, 45, 0] | ||
| # [ 0, 45, 0] | ||
|
|
||
| def test27_polygon_attributes(self): | ||
| o = osut.oslg | ||
| self.assertEqual(o.status(), 0) | ||
|
|
@@ -5349,15 +5402,15 @@ def test35_facet_retrieval(self): | |
|
|
||
| translator = openstudio.osversion.VersionTranslator() | ||
|
|
||
| path = openstudio.path("./tests/files/osms/out/seb2.osm") | ||
| path = openstudio.path("./tests/files/osms/out/seb_ext2.osm") | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harmonizing with Ruby test. |
||
| model = translator.loadModel(path) | ||
| self.assertTrue(model) | ||
| model = model.get() | ||
| spaces = model.getSpaces() | ||
| surfs = model.getSurfaces() | ||
| subs = model.getSubSurfaces() | ||
| self.assertEqual(len(surfs), 56) | ||
| self.assertEqual(len(subs), 8) | ||
| self.assertEqual(len(surfs), 59) | ||
| self.assertEqual(len(subs), 14) | ||
|
|
||
| # The solution is similar to: | ||
| # OpenStudio::Model::Space::findSurfaces(minDegreesFromNorth, | ||
|
|
@@ -5381,15 +5434,15 @@ def test35_facet_retrieval(self): | |
| roofs1 = osut.facets(spaces, "Outdoors", "RoofCeiling", "top") | ||
| roofs2 = osut.facets(spaces, "Outdoors", "RoofCeiling", "foo") | ||
|
|
||
| self.assertEqual(len(windows), 8) | ||
| self.assertEqual(len(skylights), 0) | ||
| self.assertEqual(len(walls), 26) | ||
| self.assertEqual(len(windows), 11) | ||
| self.assertEqual(len(skylights), 3) | ||
| self.assertEqual(len(walls), 28) | ||
| self.assertFalse(northsouth) | ||
| self.assertEqual(len(northeast), 8) | ||
| self.assertEqual(len(north), 14) | ||
| self.assertEqual(len(floors1a), 4) | ||
| self.assertEqual(len(floors1b), 4) | ||
| self.assertEqual(len(roofs1), 4) | ||
| self.assertEqual(len(roofs1), 5) | ||
| self.assertFalse(roofs2) | ||
|
|
||
| # Concise variants, same output. In the SEB model, floors face "Ground". | ||
|
|
@@ -5574,7 +5627,7 @@ def test36_slab_generation(self): | |
| self.assertEqual(len(surface.vertices()), 12) | ||
| self.assertAlmostEqual(surface.grossArea(), 5 * 20 - 1, places=2) | ||
|
|
||
| # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # | ||
| # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # | ||
| # Same as previous, yet overlapping 'plate' has both negative dX & dY, | ||
| # while XY origin is set at top-right (not bottom-left) corner. | ||
| # ____ ____ | ||
|
|
@@ -5602,6 +5655,17 @@ def test36_slab_generation(self): | |
| self.assertEqual(o.status(), 0) | ||
| del model | ||
|
|
||
| # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # | ||
| # Invalid input case. | ||
| plates = ["osut"] | ||
| slab = osut.genSlab(plates, z0) | ||
| self.assertTrue(o.is_debug()) | ||
| self.assertEqual(len(o.logs()), 1) | ||
| self.assertTrue("str? expecting dict" in o.logs()[0]["message"]) | ||
| self.assertTrue(isinstance(slab, openstudio.Point3dVector)) | ||
| self.assertFalse(slab) | ||
| self.assertEqual(o.clean(), DBG) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stress-test: when requested floor plates (input) aren't dictionaries. |
||
|
|
||
| def test37_roller_shades(self): | ||
| o = osut.oslg | ||
| self.assertEqual(o.status(), 0) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A user could set a glazing assembly's
specs["uo"]to None. This is caught and reset here.