Skip to content

Added ParameterSet for data-driven design parameterization#204

Open
ad-cqc wants to merge 4 commits intoaws-cqc:mainfrom
ad-cqc:ad/param-set
Open

Added ParameterSet for data-driven design parameterization#204
ad-cqc wants to merge 4 commits intoaws-cqc:mainfrom
ad-cqc:ad/param-set

Conversation

@ad-cqc
Copy link
Copy Markdown
Contributor

@ad-cqc ad-cqc commented Apr 15, 2026

Introduce ParameterSet, a mutable nested dictionary wrapper that provides dot-access syntax for reading and writing design parameters. Supports global and components namespaces, programmatic construction, and optional YAML serialization via a package extension.

Key changes:

  • Add ParameterSet type with resolve and leaf_params utilities
  • Add ParameterSetYAMLExt weak dep extension for YAML load/save
  • Integrate parameter_set into SchematicDrivenLayout and @component
  • Add comprehensive API reference documentation
  • Register YAML as optional dependency in Project.toml

Introduce `ParameterSet`, a mutable nested dictionary wrapper that
provides dot-access syntax for reading and writing design parameters.
Supports `global` and `components` namespaces, programmatic construction,
and optional YAML serialization via a package extension.

Key changes:
- Add `ParameterSet` type with `resolve` and `leaf_params` utilities
- Add `ParameterSetYAMLExt` weak dep extension for YAML load/save
- Integrate `parameter_set` into SchematicDrivenLayout and `@component`
- Add comprehensive API reference documentation
- Register YAML as optional dependency in Project.toml
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Replace Pair syntax with dot-access for setting initial parameters,
rewrite composite component section to show cleaner pattern where
subcomponent parameters live in the ParameterSet rather than the
composite struct, remove fallback dual-path (if/else isnothing) code,
and update YAML examples to match the simplified structure.
@ad-cqc ad-cqc requested a review from gpeairs April 15, 2026 21:08
@ad-cqc ad-cqc self-assigned this Apr 15, 2026
ad-cqc added 2 commits April 15, 2026 14:52
Replace silent auto-vivification of missing keys with a MissingNamespace
sentinel that supports chained dot-writes but throws a descriptive
ParameterKeyError when used as a value. Add tree-style pretty printing,
a validate() function to detect unaccessed parameters, and comprehensive
tests for the new error handling and display behavior. Update docs to
use explicit parameter set paths for shared parameter forwarding.
Support parsing unit-suffixed values (e.g., "150μm") in YAML files
loaded into ParameterSet. Values with recognized Unitful suffixes are
automatically converted to proper quantities. Update documentation
examples to use unitful parameters and add comprehensive tests for
YAML round-tripping with units.
Copy link
Copy Markdown
Member

@gpeairs gpeairs left a comment

Choose a reason for hiding this comment

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

Thanks, this is much needed. My major question is about precedence of the ParameterSet versus _build_subcomponent overrides -- right now it's ambiguous and up to the designer to follow the feature's intent. I have some ideas below about enforcing one way or the other, but I'm still undecided on what's best.

Comment thread src/parameter_set.jl

function Base.getproperty(d::MissingNamespace, s::Symbol)
s in (:parent, :key, :accessed) && return getfield(d, s)
return MissingNamespace(d, String(s), getfield(d, :accessed))
Copy link
Copy Markdown
Member

@gpeairs gpeairs Apr 16, 2026

Choose a reason for hiding this comment

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

Can you explain the problem the MissingNamespace type solves? Why not throw an error right here? Edit: Ok, I think I understand the auto-vivifying on write. I'd worry about errors after reads that don't show an informative ParameterKeyError.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The original idea is declarative definition of parameters. Parameter is allowed to be set at any namespace level event if it doesn't exist. But, reading from non-existing namespace should be prohibited. For this I use assignment operator on a parameter set namespace, e.g. ps.a.b = 10 where ps is a parameter space object. So,

  • ps.a.b = 10 creates {'a': {'b': 10}}
  • ps.a.b returns 10 as it was defined by an above definition
  • ps.a.c is an error because c was never defined

But, because ps.a.b = 10 is translated as setproperty!(getproperty(ps, :a), :b, 1) where a final namespace key, :b, is a second parameter of setproperty!, we need a lazily auto-vivified wrapper around PS to facilitate namespace construction.

So, I cannot throw any missing key error in Base.getproperty(d::MissingNamespace, s::Symbol) because it generates me a first parameter of setproperty!, and by the time I'm inside setproperty! I do not care if namespace doesn't exist, it's created by the assignment operator.

ParameterKeyError is a minor patch for auxiliary functions missing namespace handling, but getproperty(d::MissingNamespace) will always return MissingNamespace object, and that's how user would be able to check existence of a namespace: ps.x is MissingNamespace.

ExampleSimpleJunction, ps, "components.transmon.junction"
)
# Forward shared parameter under the subcomponent's own name
junction = set_parameters(junction; h_ground_island=ps.components.transmon.junction_gap)
Copy link
Copy Markdown
Member

@gpeairs gpeairs Apr 16, 2026

Choose a reason for hiding this comment

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

What would you think of applying the parameter set automatically in add_node!, since it's now present in the graph? Then we wouldn't require any changes to the component definition, and we could also avoid ambiguity about precedence.

If we only do that, then the designer can still forward whatever they like in _build_subcomponents, but anything present in the ParameterSet would be overridden. So we would lose any designer-intended consistency if the PS overrides something that’s supposed to be dependent.

Alternatively, if we want to enforce consistency, we can rely on the designer following the templates pattern to allow setting arbitrary subcomponent parameters. Then to set them via the PS, we just say subcomponent addresses in the PS are just aliases for the subcomponent templates. In other words, components.transmon.island really overrides components.transmon.templates.island. PS overrides the templates, then _build_subcomponents always overrides the PS to enforce CompositeComponent invariants. I'm not committed to that path, but it seems preferable to relying on the designer to correctly apply the ParameterSet in _build_subcomponents.

In that case, we wouldn't even need the CompositeComponent to hold onto the PS after creation.

ps = parameter_set(tr._graph)

@component island = create_component(
ExampleRectangleIsland, ps, "components.transmon.island"
Copy link
Copy Markdown
Member

@gpeairs gpeairs Apr 16, 2026

Choose a reason for hiding this comment

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

Would this be ”components.$(name(tr)).island" in general?

Comment thread src/parameter_set.jl
- `data::Dict{String, Any}`: nested parameter dictionary
- `accessed::Set{String}`: tracks which parameter paths were consumed (for auditing)
"""
mutable struct ParameterSet
Copy link
Copy Markdown
Member

@gpeairs gpeairs Apr 16, 2026

Choose a reason for hiding this comment

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

Does the ParameterSet itself have to be mutable? The container contents can be updated either way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, immutability is ok

Comment thread src/parameter_set.jl

const _REQUIRED_NAMESPACES = ("global", "components")

function _ensure_required_namespaces!(data::Dict{String, Any})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it would be more idiomatic to guarantee these with an inner constructor, unless I’ve missed a reason not to.

Comment thread src/parameter_set.jl
Comment on lines +149 to +150
# Track leaf access
push!(getfield(ps, :accessed), key)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This just tracks the leaf parameter symbol -- do we need to track the whole path like in create_component? (But then inheriting ps.accessed has path prefixes no longer present.)

Comment thread src/parameter_set.jl
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this is only relevant for working with components, it should go in the src/schematics folder and be included in SchematicDrivenLayout.jl.

ExampleRectangleIsland, ps, "components.transmon.island"
)
# Forward shared parameter from parameter set to island
island = set_parameters(island; junction_gap=ps.components.transmon.junction_gap)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here it's up to the component definition which of the ParameterSet or derived parameter takes precedence. The PS won't reflect any changes applied by set_parameters. My understanding is that's intentional (PS is just input parameters, final parameters can be extracted later and compared if necessary), but just want to double check.

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.

2 participants