Skip to content

Fix YAML-loaded ParameterSet lengths breaking mixed-unit arithmetic (#219)#220

Merged
gpeairs merged 2 commits into
aws-cqc:mainfrom
ad-cqc:ad-cqc/fix-219
May 15, 2026
Merged

Fix YAML-loaded ParameterSet lengths breaking mixed-unit arithmetic (#219)#220
gpeairs merged 2 commits into
aws-cqc:mainfrom
ad-cqc:ad-cqc/fix-219

Conversation

@ad-cqc

@ad-cqc ad-cqc commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Closes YAML-loaded ParameterSet lengths break mixed-unit arithmetic #219.
  • Unitful.uparse (used inside ParameterSetYAMLExt) returns FreeUnits quantities whose promotion target is m. The rest of DeviceLayout uses ContextUnits from DeviceLayout.PreferredUnits (target nm, or μm under PreferMicrons). Mixing the two through + — e.g. inside layer_z in technologies.jl — triggers the upstream Unitful mixed-context promotion bug (JuliaPhysics/Unitful.jl#845) and fails with MethodError: no method matching (::FreeUnits{(m,), 𝐋, nothing})().
  • Add a DeviceLayout.uparse(str) that resolves length symbols against PreferredUnits first (so parsed lengths carry the package's ContextUnits from the start) and falls back to Unitful for non-length symbols (s, Hz, fH, …).
  • Use DeviceLayout.uparse inside ParameterSetYAMLExt._parse_units! instead of post-parse re-contexting.

After this PR YAML-loaded lengths enter the system already wrapped in ContextUnits matching PreferredUnits.UPREFERRED, so subsequent mixed-context arithmetic (level_z, layer_z, …) avoids the buggy promotion path entirely.

Why a new uparse instead of post-parse re-contexting

The earlier iteration of this PR re-wrapped each parsed Quantity in a ContextUnits aimed at UPREFERRED. That worked but only for length leaves the helper recognized; compound expressions (1/μm, products, etc.) needed extra special-casing. Routing through Unitful.uparse(str; unit_context=PreferredUnits) lets the parser itself produce ContextUnits for any expression involving length symbols, and falls back to Unitful for everything else — no post-processing needed.

Test plan

  • Pkg.test()test/test_parameter_set.jl passes 188/188 (+2 new assertions).
  • Existing YAML round-trip tests still pass (serialized form unchanged: string(ContextUnits unit) == "μm").
  • New regression cases:
    • DeviceLayout.uparse("150μm") |> unit isa ContextUnits
    • Compound 1/μm round-trips through the YAML loader as a Quantity.
    • Reproducer of the original level_z failure path: sum(t_chips) + t_gap - 1nm followed by another + 1nm no longer throws.

Notes

  • Non-length quantities (frequencies, times, kinetic-inductance fH, …) pass through unchanged via the try/catch fallback.
  • New public surface: 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): PreferNoUnits re-exports Unitful.μm (bare FreeUnits), so DeviceLayout.uparse returns identical values to Unitful.uparse — no behavior change in that mode.

@ad-cqc ad-cqc requested a review from gpeairs May 12, 2026 17:50
@gpeairs

gpeairs commented May 12, 2026

Copy link
Copy Markdown
Member

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.

@ad-cqc

ad-cqc commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

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.

@ad-cqc

ad-cqc commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

I reworked PR by introducing PreferredUnits aware uparse, so any length parameters would be associated with correct internal context.

ad-cqc added 2 commits May 13, 2026 09:40
…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

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@gpeairs gpeairs merged commit 6936388 into aws-cqc:main May 15, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

YAML-loaded ParameterSet lengths break mixed-unit arithmetic

2 participants