Fix YAML-loaded ParameterSet lengths breaking mixed-unit arithmetic (#219)#220
Conversation
|
Quick pre-review note -- BSpline test failures are unrelated (caused by a dependency update). I'm relaxing the tolerances in #217. The bug is a bit surprising, since the whole point of ContextUnits is that you should be able to use them with FreeUnits and the result gets promoted based on the context. There is one bug I know of JuliaPhysics/Unitful.jl#845 but that's for two different ContextUnits (specifically same unit, different context). I'm not reproducing the error with the given snippet, so maybe you actually hit that bug downstream somewhere. Either way this looks like the correct workaround. |
|
You are right. The code where I spotted the error leads to _promote_unit call in JuliaPhysics/Unitful.jl#845 where units from different contexts getting promoted incorrectly. So the fix it the same use preferred units from the default context and promote YAML unitful values to the type with new context. |
|
I reworked PR by introducing |
…issue aws-cqc#219) `Unitful.uparse` returns `FreeUnits` quantities (promotion target `m`), which mismatches DeviceLayout's `ContextUnits` (target `nm` or `μm`) and broke mixed-unit arithmetic such as `layer_z` in `technologies.jl`. Re-context every length-dimensioned leaf inside `_parse_units!` at parse time so the rest of the package never sees a `FreeUnits` length. `PreferNoUnits` (where `UPREFERRED = NoUnits`) is left as a no-op.
Replace the post-parse `_recontext_length` workaround with a new `DeviceLayout.uparse` that resolves length symbols against `PreferredUnits` directly, so parsed lengths enter the system as `ContextUnits` from the start instead of bare `FreeUnits` that later trip the upstream Unitful mixed-context promotion bug (JuliaPhysics/Unitful.jl#845) inside `layer_z` arithmetic. Non-length symbols (`s`, `Hz`, `fH`, ...) fall back to `Unitful` via try/catch so existing usage is unchanged. Extend the regression test to cover compound expressions (`1/μm`) and the original `level_z` chained-arithmetic failure pattern.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
Unitful.uparse(used insideParameterSetYAMLExt) returnsFreeUnitsquantities whose promotion target ism. The rest of DeviceLayout usesContextUnitsfromDeviceLayout.PreferredUnits(targetnm, orμmunderPreferMicrons). Mixing the two through+— e.g. insidelayer_zintechnologies.jl— triggers the upstream Unitful mixed-context promotion bug (JuliaPhysics/Unitful.jl#845) and fails withMethodError: no method matching (::FreeUnits{(m,), 𝐋, nothing})().DeviceLayout.uparse(str)that resolves length symbols againstPreferredUnitsfirst (so parsed lengths carry the package'sContextUnitsfrom the start) and falls back toUnitfulfor non-length symbols (s,Hz,fH, …).DeviceLayout.uparseinsideParameterSetYAMLExt._parse_units!instead of post-parse re-contexting.After this PR YAML-loaded lengths enter the system already wrapped in
ContextUnitsmatchingPreferredUnits.UPREFERRED, so subsequent mixed-context arithmetic (level_z,layer_z, …) avoids the buggy promotion path entirely.Why a new
uparseinstead of post-parse re-contextingThe earlier iteration of this PR re-wrapped each parsed
Quantityin aContextUnitsaimed atUPREFERRED. That worked but only for length leaves the helper recognized; compound expressions (1/μm, products, etc.) needed extra special-casing. Routing throughUnitful.uparse(str; unit_context=PreferredUnits)lets the parser itself produceContextUnitsfor any expression involving length symbols, and falls back toUnitfulfor everything else — no post-processing needed.Test plan
Pkg.test()—test/test_parameter_set.jlpasses 188/188 (+2 new assertions).string(ContextUnits unit) == "μm").DeviceLayout.uparse("150μm") |> unit isa ContextUnits1/μmround-trips through the YAML loader as aQuantity.level_zfailure path:sum(t_chips) + t_gap - 1nmfollowed by another+ 1nmno longer throws.Notes
fH, …) pass through unchanged via thetry/catchfallback.DeviceLayout.uparse(not exported). Useful anywhere external string input needs to land in the package's promotion context — YAML, JSON, REPL helpers.PreferNoUnits(UPREFERRED == NoUnits):PreferNoUnitsre-exportsUnitful.μm(bareFreeUnits), soDeviceLayout.uparsereturns identical values toUnitful.uparse— no behavior change in that mode.