feat(qprog): unified measurement API via evaluate() + CircuitPreprocessor#95
Conversation
…ssor Add QuantumProgram.evaluate() as the single entry point for measuring a program under a named CircuitPreprocessor, replacing the PipelineSet/transformations primitives and consolidating observable grouping and adaptive shot allocation in MeasurementStage. Move Trotter synthesis onto TrotterizationResult, give PCE an entangling default ansatz, add a suppress_performance_warnings knob, and refresh the user guide and API reference to match.
Greptile SummaryThis PR unifies quantum program measurement behind a single
Confidence Score: 5/5The refactoring correctly maps all old pipeline paths to the new evaluate() + CircuitPreprocessor architecture; sign conventions in Trotter synthesis are faithfully preserved from the original _build_qiskit_circuit; PCE default-ansatz fix prevents a real crash. All three inline comments are observational or suggest hardening — none describe a present defect in the changed logic. The new evaluate() collapse, the variance side-effect pattern, and the warning stacklevel are all functional but could be made more robust. Full test suite, 18 tutorials, and pyrefly clean run support merging. divi/qprog/quantum_program.py (evaluate() key-collapse) and divi/qprog/variational_quantum_algorithm.py (_evaluate_cost_param_sets variance double-call) are worth a second look before the next release.
|
| Filename | Overview |
|---|---|
| divi/qprog/quantum_program.py | New evaluate() entry point, _build_preprocessor_pipeline() with per-instance pipeline cache, _spec_stage() / _initial_spec() hooks replacing _build_pipelines(). total_circuit_count / total_run_time docstrings refreshed but implementations stay. |
| divi/qprog/variational_quantum_algorithm.py | Removes _build_pipelines / _expectation_pipeline; adds cost_preprocessor() (public, overridable), _preprocessors(), _post_spec_batch(). optimizer is now required; _argmin_finite guards against NaN/inf best-loss selection. meta_circuit_factories replaced by cost_circuit. |
| divi/pipeline/_preprocessor.py | New file. Defines CircuitPreprocessor dataclass and factory functions cost_preprocessor() / sample_preprocessor(). |
| divi/pipeline/_result_keys_operations.py | New file consolidating result-key helpers from transformations.py and _result_keys.py; adds average_by_param_set, group_by_branch_and_param_set, typed error variants in reduce_mean/reduce_merge_histograms. |
| divi/pipeline/stages/_measurement_stage.py | Relaxes _backend_expval single-observable restriction; adds UserWarning when shot_distribution is set on an analytically-exact backend; imports moved from deleted transformations.py. |
| divi/hamiltonians/_trotterization.py | Adds TrotterizationResult.synthesize_evolution, moving circuit-building logic out of TimeEvolution._build_qiskit_circuit; sign conventions mirror the original code exactly. |
| divi/qprog/algorithms/_pce.py | Replaces _build_pipelines override with cached_property _pce_cost_preprocessor + cost_preprocessor() override; defaults to GenericLayerAnsatz to avoid crash on no-electron programs; _create_meta_circuit_factories → _create_cost_circuit. |
| divi/qprog/_metrics.py | Refactors metric evaluation to use evaluate() + CircuitPreprocessor; pullback metric now averages branch-level metrics (E_b[G_b]) for multi-branch cost cohorts (QDrift); Fubini-Study per-block logic ported to branch-aware path. |
| divi/qprog/_solution_sampling_mixin.py | Removes _build_pipelines cooperative override and init_subclass MRO guard; replaces with _preprocessors() / _sample_preprocessor(); sample_solution resolves None params via _resolve_sample_params hook. |
| divi/pipeline/stages/_preprocess_stage.py | New stage that applies a CircuitPreprocessor's transform to every MetaCircuit in the post-spec batch. |
| divi/qprog/_program_state.py | New private module housing ProgramState, OptimizerConfig, and SubclassState extracted from variational_quantum_algorithm.py; re-exported at the old module level. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Optimizer
participant VQA as VariationalQuantumAlgorithm
participant QP as QuantumProgram
participant Pipeline as CircuitPipeline
participant Spec as SpecStage
participant Preprocess as PreprocessStage
participant Measure as MeasurementStage
Optimizer->>VQA: cost_fn(params)
VQA->>VQA: _evaluate_cost_param_sets(param_sets)
VQA->>QP: evaluate(params, cost_preprocessor())
QP->>QP: _build_preprocessor_pipeline(preprocessor)
QP->>Pipeline: assemble [spec → preprocess → qem → measure]
QP->>Pipeline: "run(initial_spec=cost_circuit, env)"
Pipeline->>Spec: expand(cost_circuit)
Spec-->>Pipeline: "{ham_key: MetaCircuit}"
Pipeline->>Preprocess: expand(batch)
Note over Preprocess: identity transform for cost
Preprocess-->>Pipeline: "{ham_key: MetaCircuit}"
Pipeline->>Measure: expand(batch)
Measure-->>Pipeline: "{param_set_key: expval}"
Pipeline-->>QP: PipelineResult
QP->>QP: "collapse to {param_set_idx: value}"
QP-->>VQA: dict[int, float]
VQA-->>Optimizer: losses
Note over Optimizer,VQA: For sample_solution()
Optimizer->>VQA: sample_solution(params)
VQA->>QP: evaluate(params, sample_preprocessor())
QP->>Pipeline: "run(initial_spec=cost_circuit, env)"
Pipeline->>Spec: expand(cost_circuit)
Spec-->>Pipeline: "{ham_key: MetaCircuit w/ observable}"
Pipeline->>Preprocess: expand(batch)
Note over Preprocess: _clear_observable → measured_wires set
Preprocess-->>Pipeline: "{ham_key: MetaCircuit w/o observable}"
Pipeline->>Measure: expand(batch)
Measure-->>Pipeline: "{param_set_key: probs}"
Pipeline-->>QP: PipelineResult
QP-->>VQA: dict[int, dict[str,float]]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Optimizer
participant VQA as VariationalQuantumAlgorithm
participant QP as QuantumProgram
participant Pipeline as CircuitPipeline
participant Spec as SpecStage
participant Preprocess as PreprocessStage
participant Measure as MeasurementStage
Optimizer->>VQA: cost_fn(params)
VQA->>VQA: _evaluate_cost_param_sets(param_sets)
VQA->>QP: evaluate(params, cost_preprocessor())
QP->>QP: _build_preprocessor_pipeline(preprocessor)
QP->>Pipeline: assemble [spec → preprocess → qem → measure]
QP->>Pipeline: "run(initial_spec=cost_circuit, env)"
Pipeline->>Spec: expand(cost_circuit)
Spec-->>Pipeline: "{ham_key: MetaCircuit}"
Pipeline->>Preprocess: expand(batch)
Note over Preprocess: identity transform for cost
Preprocess-->>Pipeline: "{ham_key: MetaCircuit}"
Pipeline->>Measure: expand(batch)
Measure-->>Pipeline: "{param_set_key: expval}"
Pipeline-->>QP: PipelineResult
QP->>QP: "collapse to {param_set_idx: value}"
QP-->>VQA: dict[int, float]
VQA-->>Optimizer: losses
Note over Optimizer,VQA: For sample_solution()
Optimizer->>VQA: sample_solution(params)
VQA->>QP: evaluate(params, sample_preprocessor())
QP->>Pipeline: "run(initial_spec=cost_circuit, env)"
Pipeline->>Spec: expand(cost_circuit)
Spec-->>Pipeline: "{ham_key: MetaCircuit w/ observable}"
Pipeline->>Preprocess: expand(batch)
Note over Preprocess: _clear_observable → measured_wires set
Preprocess-->>Pipeline: "{ham_key: MetaCircuit w/o observable}"
Pipeline->>Measure: expand(batch)
Measure-->>Pipeline: "{param_set_key: probs}"
Pipeline-->>QP: PipelineResult
QP-->>VQA: dict[int, dict[str,float]]
Reviews (2): Last reviewed commit: "feat(qprog): unified measurement API via..." | Re-trigger Greptile
|
@greptile-apps — responding to your review: two of these findings aren't being changed. Recording the rationale here since there are no inline threads for them. Greptile flag — Greptile flag — |
Summary
Unifies how quantum programs are measured behind a single public entry point.
QuantumProgram.evaluate(params, preprocessor, ...)now drives every measurement routine, with namedCircuitPreprocessors selecting cost / sample / metric behavior instead of callers assembling pipelines themselves. This replaces the oldPipelineSet/transformationsprimitives.What changed
evaluate()+CircuitPreprocessor— one measurement entry point;cost_preprocessor()is now public and overridable (PCE overrides it with its counts-based variant)._result_keys_operations.py(mergingtransformations.py+_result_keys.py), with the reduce toolkit andextract_param_set_idxpublicized.MeasurementStage— consolidated observable grouping and adaptive shot allocation; the analytic_backend_expvalpath is selected only when a backend supports expval and all circuits in the batch share the same observable(s).TrotterizationResult.synthesize_evolution.suppress_performance_warningsknob threaded fromQuantumProgramdown to its pipelines.StageInfo/DryRunReportfields documented, plus a batch of fixes surfaced by docs-only alpha/beta testing.Testing
pyrefly check: 0 errors.tutorials/_ci_runner.py(local-maestro).Notes for review
optimizeris now required onVariationalQuantumAlgorithm(the implicitMonteCarloOptimizer()default was removed) — flagged in review; no deprecation window added (open to discussion if the implicit default shipped in a release).PipelineSet/divi.pipeline.transformationsare removed in favor ofevaluate()/CircuitPreprocessor/_result_keys_operations.🤖 Generated with Claude Code
https://claude.ai/code/session_0186dAXah28i9CVMDsX9NvuC