Skip to content

Conversation

@brgix
Copy link
Member

@brgix brgix commented Aug 5, 2025

Around 40-odd typos (and other oddities) were identified in the original Ruby implementation of OSut, while developing this Python counterpart. In some cases, these are simple fixes in the Ruby code (no impact on the Python implementation). In other cases (like this first commit), they reveal weaknesses (to fix), such as not adequately catching bad user input. Over the next day or so, a number of such fixes are expected to be introduced, before re-releasing an upcoming pyOSut v0.6.1.

@brgix brgix self-assigned this Aug 5, 2025
@brgix brgix added the enhancement New feature or request label Aug 5, 2025

elif specs["type"] == "window":
a["glazing"]["u" ] = specs["uo"]
a["glazing"]["u" ] = u if u else uo()["window"]
Copy link
Member Author

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.

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
Copy link
Member Author

@brgix brgix Aug 5, 2025

Choose a reason for hiding this comment

The 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:

  • successfully converted to a float
  • within postulated (acceptable) range

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)
Copy link
Member Author

Choose a reason for hiding this comment

The 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).

- "max" (float): max temperature. (None if invalid inputs - see logs).
"""
mth = "osut.scheduleCompactMinMax"
mth = "osut.scheduleIntervalMinMax"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo


# Once joined, re-adjust Z-axis coordinates.
if abs(z) > CN.TOL:
if round(z, 2) != 0.00:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More appropriate.

# 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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OSut enabled ScheduleFixedInterval checks, e.g.:

  • min/max setpoint temperatures
  • is space UNCONDITIONED, etc.

Yet no unit test had been in place.

translator = openstudio.osversion.VersionTranslator()

path = openstudio.path("./tests/files/osms/out/seb2.osm")
path = openstudio.path("./tests/files/osms/out/seb_ext2.osm")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Harmonizing with Ruby test.

try:
value = float(values[i])
value = vals.append(value)
vals.append(value)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better.

self.assertTrue("str? expecting dict" in o.logs()[0]["message"])
self.assertTrue(isinstance(slab, openstudio.Point3dVector))
self.assertFalse(slab)
self.assertEqual(o.clean(), DBG)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stress-test: when requested floor plates (input) aren't dictionaries.

# if xa1b2.length() < CN.TOL2:
# if isPointAlongSegment(a1, [a2, b2]): return None
# if isPointAlongSegment(a2, [a1, b2]): return None

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now redundant checks.

except:
return oslg.mismatch("n unique points", n, int, mth, CN.DBG, v)
oslg.mismatch("n points", n, int, mth, CN.DBG)
n = 0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 3 OSut functions that take in n as argument:

  • uniques(pts, n=0):
  • collinear(pts, n=0)
  • noncollinears(pts, n=0)

... where n is an (optional) user-requested number of points to return. For instance, a user may request the first 3 points of a hexagon (6 points) to determine a plane equation.

Unfortunately, all three functions did not share the same validation checks and treatment of n - an inconsistency and source of confusion. Furthermore, DEBUG or ERROR messages shouldn't be logged when users incorrectly guess in advance what the admissible range of points may be. So these changes:

  • reduce the volume a bit
  • harmonize the treatment of n across all three methods
  • introduces stress tests

for vt in pts.vertices():
pt = openstudio.Point3d(vt.x(), vt.y(), vt.z())
v.append(pt)
return v
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No point in validating planar surface .vertices() individually.

if not isSegment(sg):
return oslg.mismatch("segment", sg, cl2, mth, CN.DBG, False)

if not isSegment(sg): return False
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowering the log volume: isPointAlongSegment calls isSegment, which in turn calls p3Dv. The latter will log an inadmissible object.

return oslg.empty("segments", mth, CN.DBG, False)
if not isinstance(p0, cl1):
return oslg.mismatch("point", p0, cl, mth, CN.DBG, False)
return oslg.mismatch("point", p0, cl1, mth, CN.DBG, False)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. No cl variable.

# Valid case #1: a single Point3d.
vtx = osut.p3Dv(p0)
self.assertTrue(isinstance(vtx, openstudio.Point3dVector))
self.assertEqual(vtx[0], p0) # same object ID
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 4 valid p3Dv use cases systematically return an openstudio.Point3dVector (and not occasionally a list).


# A bounded box's 'height', at its narrowest, is its 'width'.
return height(box)
return height(res["box"])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing key piece.

# Achtung! The function can be time consuming (multiple iterations) for
# very convoluted spaces (e.g. long corridors with multiple concavities).
self.assertAlmostEqual(osut.spaceHeight(fine), 8.53, places=2)
self.assertAlmostEqual(osut.spaceWidth(fine), 21.33, places=2)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warehouse

The value returned by spaceHeight (8.53 m) corresponds to the tallest floor-to-ceiling distance (along Z-axis).

The floor section circumscribed (and crisscrossed) with red dotted lines corresponds to the largest bounded box that fits in the (large) L-shaped floor surface. The length of the narrowest edge of this bounded box (21.33 m) is what spaceWidth returns.

self.assertAlmostEqual(osut.alignedHeight(floor), 6.88, places=2)
self.assertAlmostEqual(osut.alignedWidth(floor), 8.22, places=2)
self.assertAlmostEqual(osut.spaceHeight(openarea), 3.96, places=2)
self.assertAlmostEqual(osut.spaceWidth(openarea), 3.77, places=2)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seb_sky
  • in red: full height of space, including skylight wells
  • in green: width of bounding box of space floor(s) ... outline is very approximate!
  • in blue: width of largest bounded box that can fit within grouped floor surfaces

The latter is retained here as the most adequate measure of what could be considered a room's width, e.g. for LPD adjustment calculations.

maintainers = [ {name = "Denis Bourgeois", email = "denis@rd2.ca"} ]
dependencies = ["oslg","openstudio>=3.6.1"]
# dependencies = ["oslg","openstudio>=3.6.1"]
dependencies = ["oslg"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting aside OpenStudio as a dependency.

- name: Install dependencies
run: python -m pip install --upgrade pip setuptools wheel oslg openstudio
- name: Run unit tests
run: python -m unittest
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsuccessful in pulling more than one OpenStudio SDK package/module. Sticking to latest version for now.

@brgix brgix merged commit 240fcac into develop Aug 13, 2025
2 checks passed
@brgix brgix deleted the v061 branch August 13, 2025 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants