quchip.inverse_design.fit

Dressed-observable fitting of bare chip parameters.

Calibration data arrives in the dressed frame: a spectroscopy peak is a dressed eigenvalue, a Ramsey shift is a dispersive chi, a parked-qubit detuning is a static zz. The chip model, on the other hand, is parameterized by bare quantities — device freq and anharmonicity, a coupling’s scalar strength (g, g_0, chi, depending on the coupling type — see coupling_strength). fit_a_dress() closes that gap: given targets on dressed observables, it finds bare parameters whose dressed spectrum reproduces them.

References

Dispersive regime (chi, zz, and dressed frequencies):

  • Koch et al., Phys. Rev. A 76, 042319 (2007), “Charge-insensitive qubit design derived from the Cooper pair box” — the DuffingTransmon approximation and its dispersive shifts.

  • Gambetta et al., Phys. Rev. A 74, 042318 (2006), “Qubit-photon interactions in a cavity” — the chi = g^2 / Delta qubit-resonator dispersive shift at leading order, the dispersive-regime intuition behind the chi seed search (_estimate_bare_g()). The zz seed makes no leading-order claim of its own: the seed search only requires the target to be bracketed by the observable at the endpoints of seed_strength_bounds (checked, not assumed) — it does not require the observable to be monotone in between; scipy.optimize.brentq() finds a consistent root regardless of the observable’s direction (increasing or decreasing) within the bracket.

JAX traceability

Every bare parameter here (device.freq, device.anharmonicity, a coupling’s coupling_strength) is a sweepable, differentiable quantity. A chip using a JAX-native backend supplies a traced dressed-observable residual and exact Jacobian; SciPy consumes their concrete values only at the bounded trust-region boundary. The optimizer itself is not JAX-traceable, while the output Chip remains fully traceable for every downstream operation.

Functions

fit_a_dress(chip, *[, coupling_targets, ...])

Fit bare chip parameters so dressed observables match targets.

quchip.inverse_design.fit.fit_a_dress(chip, *, coupling_targets=None, observable_targets=None, fit_parameters=None, max_hilbert_dim=10000, seed_strength_bounds=(1e-06, 0.25), max_nfev=1000)[source]

Fit bare chip parameters so dressed observables match targets.

With fit_parameters=None (the default), every device freq / anharmonicity and every coupling’s scalar strength (coupling_strength) is a free variable in a bounded non-linear least-squares problem (scipy Trust-Region Reflective). A fit_parameters mapping instead is the complete free-parameter allowlist — see fit_parameters below — so any device or coupling it does not list is frozen instead of free. The fitted chip is returned as a clone — the input chip is never mutated.

Parameters:
  • chip (Chip) – Seed chip. Seeds only set the optimizer’s starting point.

  • coupling_targets (Mapping | None) – Mapping from coupling (or its label) to a target mode: "chi", "zz", or "g". For listed couplings, the coupling’s current strength is interpreted as the target value for that mode. With fit_parameters=None, couplings not listed here are still free — they are optimized, just without a dedicated anchor; a fit_parameters mapping can freeze them regardless (a coupling target does not itself make a coupling free). A "chi" target requires the coupling to have exactly one computational endpoint; both-computational or neither-computational raises ValueError at construction.

  • observable_targets (Mapping | None) – Mapping of target observables. Keys are devices/labels or (device_a, device_b) tuples; values are {kind: value} dicts. Supported kinds: "freq", "anharmonicity" (device), "exchange", "zz" (pair). Device-level targets override the auto-targeted defaults for the same (kind, label).

  • fit_parameters (Mapping | None) – None (default): every declared device tunable (tunable_params()) and every coupling’s scalar strength is free — the pre-existing behavior. A mapping is instead the complete free-parameter allowlist: {component_or_label: name_collection}, where a device’s name_collection is a subset of its declared tunable names and a coupling’s is a subset of (coupling.coupling_strength_name,). A component (device or coupling, given as the object or its label) absent from the mapping is fully frozen — it does not default to free. An empty name_collection explicitly freezes a listed component. A bare string value (e.g. "E_J" instead of ("E_J",)) is rejected, since a string is itself a collection of characters. Selected parameters are packed in chip order and each component’s own declared parameter order, not mapping or tuple order. initial_params / final_params contain only the selected (free) parameters.

  • max_hilbert_dim (int) – Above this total Hilbert-space size the fit switches from dressing the whole chip to dressing one-hop subsystems per target (see quchip.inverse_design.subsystems).

  • seed_strength_bounds (tuple[float, float]) – (lo, hi) magnitude bounds for the bare-coupling-strength seed root solve (_estimate_bare_g()) used for chi/zz coupling targets. The target observable must be bracketed by the values at these two endpoints, or seeding raises ValueError rather than silently returning a saturated endpoint.

  • max_nfev (int) – Maximum number of residual evaluations for the SciPy Trust-Region Reflective solver.

Returns:

Fitted chip clone, loss, residual history, per-target ObservableReport tuples, packed parameters, and solver metadata.

Return type:

FitADressResult

Raises:

ValueError – A "chi" coupling target does not have exactly one computational endpoint, or a chi/zz seed’s target observable is not bracketed within seed_strength_bounds; a fit_parameters key does not resolve to a device or coupling label on chip, names a parameter the resolved component does not declare, resolves the same label twice, or is a bare string rather than a name collection; or fit_parameters selects zero free parameters overall.

Warns:

UserWarning – The number of free parameters exceeds the number of target residuals (underdetermined by count). This is a necessary, not sufficient, identifiability condition: it does not analyze the Jacobian’s rank, so a count-sufficient fit can still be practically underdetermined.

Notes

Residuals are normalized by max(|target|, 1e-9) so every anchor contributes on equal relative-error footing. A coupling’s scalar strength bounds are symmetric around zero — the sign of a capacitive-type coupling is physical and must not be constrained. The solver’s convergence tolerances (ftol/xtol/gtol = 1e-11) and its x_scale floor (1e-3, applied per parameter as max(abs(x0), 1e-3)) are fixed fitter policy, not exposed as options.

Two identifiability hazards the count check above does not catch. The free-parameter-vs-residual count is necessary but not sufficient: it cannot detect a flat Jacobian direction — a free parameter no target observable actually responds to — which stays underdetermined regardless of the count. And a custom DeviceModel whose tunable_param_names is discovered (the derived default, not an explicit declaration) is not automatically fit-ready: an unbounded parameter still needs a tunable_param_bounds() rule before the optimizer can search it.

JAX traceability. When the chip uses a JAX-native backend, the complete parameter-to-residual map and its exact Jacobian are JAX-traceable; SciPy receives their concrete values for bounded trust-region control. The optimizer itself is not differentiated. Other backends retain SciPy’s numerical Jacobian. The returned chip remains fully traceable and differentiable in either case.