Skip to content

Return Datastore from QutipBackend.run#10

Open
CodeMaverick2 wants to merge 2 commits into
OpenQuantumDesign:mainfrom
CodeMaverick2:feat/qutip-return-datastore
Open

Return Datastore from QutipBackend.run#10
CodeMaverick2 wants to merge 2 commits into
OpenQuantumDesign:mainfrom
CodeMaverick2:feat/qutip-return-datastore

Conversation

@CodeMaverick2

@CodeMaverick2 CodeMaverick2 commented May 22, 2026

Copy link
Copy Markdown

Summary

Closes #9.

QutipBackend.run(task) now returns an oqd_dataschema.Datastore with an emulation group (AnalogEmulatorDataGroup) instead of TaskResultAnalog.

  • Metrics stacked as (n_tsteps, n_metrics); names in metrics.attrs["metric_labels"] (JSON)
  • Full state trajectory in state; shot outcomes in measurements when the circuit includes measure()
  • README Rabi example updated (model_dump_hdf5, read metrics from datastore)

Depends on

Requires AnalogEmulatorDataGroup on oqd-dataschema main - merge oqd-dataschema #11 first (CI will fail until that lands).

Test plan

  • All 10 existing backend tests pass (Rabi, Bell, GHZ, identity, canonicalization, empty metrics)
  • 2 new tests: HDF5 round-trip (attrs + metric labels), measurements dataset with measure()
  • Manual checks: axis alignment (times / metrics / state), no measurements without measure(), compile path still works
  • oqd-dataschema suite with new group: 147 passed, 57 xfailed (pre-existing)

Note: Until dataschema is merged, install with pip install git+https://github.com/OpenQuantumDesign/oqd-dataschema@feat/analog-emulator-data-group for local/CI review.

Co-authored-by: Cursor <cursoragent@cursor.com>
@benjimaclellan

benjimaclellan commented Jun 11, 2026

Copy link
Copy Markdown
Member

Things look good, happy to accept this for the bounty; the PR does what the issue asked for and the new tests are good. Thanks for your work on it!

Some feedback to improve the changes: now that the Datastore is the output, TaskResultAnalog has basically become a redundant intermediate. Every field on it gets copied into the Datastore (times, metrics, runtime), and the one field that doesn't fit the new schema — state, which only ever held the latest state — gets shadowed by the new _state_trajectory list the VM maintains alongside it. So the VM ends up filling out two intermediate data objects in parallel, and task_result_to_datastore has to take both. That's where the drift is coming from.

You can see the cost of keeping it in _complex_list_to_array: that helper only exists because TaskResultAnalog is a Pydantic model and we had to wrap complex numbers in ComplexFloat to keep it happy, so now we unwrap them one element at a time with hasattr(value, "real") just to rebuild a numpy array on the way into the Dataset. Going straight from qutip's Qobj (already complex numpy) to a Dataset skips that whole conversion.

I think the cleaner shape is to drop TaskResultAnalog from this code path entirely. The VM holds what it collects during the run as plain attributes (or a small dataclass if you want some structure), and run() builds the Datastore from those in one go — qutip results in, Dataset out, no Pydantic detour. PR (#47) in oqd-trical is roughly that shape if you want a reference (sibling bounty for unitaryHACK).

As well, the upstream group fields are all bare Optional[Dataset] with no shape/dtype constraints — condataset would actually validate something (see what glasses666 did in dataschema #12). And having to import metric_labels_from_dataset just to find which column is "Z" is a bit clunky; might be worth storing labels in a way that doesn't need a helper.

Collect run data as plain numpy attributes on the VM and build the
Datastore directly. Metric labels use comma-separated attrs.
@CodeMaverick2

Copy link
Copy Markdown
Author

@benjimaclellan Thanks for the detailed feedback addressed in the latest push:

  • Dropped TaskResultAnalog from the VM path entirely
  • VM now collects plain attrs (times, metric_labels, metrics, state_trajectory, measurements, runtime) — no parallel intermediate
  • Qobj → complex128 numpy directly via _qobj_to_state_vector, no ComplexFloat unwrap
  • vm_to_datastore(vm, args) builds the Datastore in one step
  • Metric labels stored as comma-separated attr so no helper import needed (e.g. sim.metrics.attrs["metric_labels"].split(","))

Left dataschema as-is per your note. All 12 tests still pass locally.

@benjimaclellan benjimaclellan left a comment

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.

Thanks for the additional changes @CodeMaverick2 - looks good for now. I'll assign the Issue you to you and close out this bounty.

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.

unitaryHACK 2026: Update oqd-analog-emulator backend to return a datastore

2 participants