feat(qutip): return schema-validated Datastore from QutipBackend.run()#49
Open
Roll249 wants to merge 2 commits into
Open
feat(qutip): return schema-validated Datastore from QutipBackend.run()#49Roll249 wants to merge 2 commits into
Roll249 wants to merge 2 commits into
Conversation
Updates `QutipBackend.run()` to return an `oqd_dataschema.Datastore` (wrapping a `TrICalEmulatorDataGroup`) instead of a plain `dict`, addressing OpenQuantumDesign#44. Breaking change: callers that unpack result["states"], result["tspan"], etc. will need to update to result["emulation"].states.data, result["emulation"].tspan.data, etc. In exchange, the result is now: - Schema-validated (pydantic) at construction time - Directly serializable to HDF5 via model_dump_hdf5() - Reloadable with model_validate_hdf5() -- no glue code needed - Consistent with the oqd-dataschema standard used by other OQD backends Changes: - New src/oqd_trical/backend/qutip/datastore.py: TrICalEmulatorDataGroup (auto-registers with GroupRegistry) and build_emulator_datastore() helper. - QutipVM now exposes solver_name on the instance for metadata use. - QutipBackend.run() builds and returns a Datastore. - pyproject.toml: added oqd-dataschema dependency. - New tests/test_datastore.py: unit tests for helper functions, TrICalEmulatorDataGroup, and HDF5 round-trip. - tests/test_backends.py: new TestQutipBackendDatastore covering the full API (return type, shapes, attrs, HDF5 round-trip, explicit fock_cutoff). - docs/explanation/backends.md: new "Results" section documenting the Datastore API with usage example. Review fixes: - Fix states_to_array docstring to reflect actual output shapes: (n_tsteps, hilbert_dim, 1) for kets, (n_tsteps, hilbert_dim, hilbert_dim) for density matrices (QuTiP's Qobj.full() returns a column vector). - Wrap importlib.metadata.version() calls in try/except PackageNotFoundError with "0+unknown" fallback to avoid import failures in source-tree contexts. - Fix run() docstring: hilbert_space is HilbertSpace, not Dict[str, int]. - Mark TestQutipBackendDatastore with @pytest.mark.slow for CI flexibility. Upstream note: TrICalEmulatorDataGroup should eventually be contributed to oqd-dataschema as the canonical group for TrICal emulator outputs, enabling a shared definition between the QuTiP and Dynamiqs backends (see OpenQuantumDesign#44). Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Introduces schema-validated backend results via oqd-dataschema for the QuTiP emulator backend, including HDF5 round-tripping, tests, and documentation.
Changes:
- Add
TrICalEmulatorDataGroupandbuild_emulator_datastore()to wrap emulator outputs in anoqd_dataschema.Datastore. - Update
QutipBackend.run()to return the schema-validated datastore (with metadata inattrs) and add end-to-end tests/docs for usage. - Add
oqd-dataschemaas a project dependency and expose version metadata.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_datastore.py | Adds unit tests for datastore conversion helpers and HDF5 round-trip. |
| tests/test_backends.py | Adds end-to-end (slow) tests asserting QutipBackend.run() returns a Datastore and round-trips to HDF5. |
| src/oqd_trical/backend/qutip/vm.py | Records solver name and clarifies result as legacy dict view. |
| src/oqd_trical/backend/qutip/datastore.py | Implements TrICalEmulatorDataGroup and helper conversions; builds Datastore from raw backend outputs. |
| src/oqd_trical/backend/qutip/base.py | Changes backend run() to return Datastore, stashes fock_cutoff, and injects version metadata. |
| src/oqd_trical/backend/qutip/init.py | Imports datastore module for group registration side effects and re-exports new APIs. |
| src/oqd_trical/init.py | Exposes package __version__. |
| pyproject.toml | Adds oqd-dataschema dependency and uv source. |
| docs/explanation/backends.md | Documents the new Datastore result format and HDF5 round-trip usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+137
to
+168
| def run( | ||
| self, | ||
| experiment, | ||
| hilbert_space, | ||
| timestep, | ||
| *, | ||
| initial_state=None, | ||
| fock_cutoff=None, | ||
| ): | ||
| """ | ||
| Runs a [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment]. | ||
| Runs a [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment] | ||
| and returns the result as a schema-validated | ||
| [`Datastore`][oqd_dataschema.Datastore]. | ||
|
|
||
| Args: | ||
| experiment (QutipExperiment): [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment] to be executed. | ||
| hilbert_space (Dict[str, int]): Hilbert space of the system. | ||
| hilbert_space (HilbertSpace): Hilbert space of the system. | ||
| timestep (float): Timestep between tracked states of the evolution. | ||
| initial_state: Optional initial state. If ``None``, every | ||
| subsystem is initialized to its ground state. | ||
| fock_cutoff: Optional override for the Fock cutoff used to | ||
| label the run metadata. Defaults to the value passed to | ||
| the most recent :meth:`compile` call. | ||
|
|
||
| Returns: | ||
| result (Dict[str,Any]): Result of execution of [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment]. | ||
| result (Datastore): Schema-validated datastore wrapping a | ||
| single [`TrICalEmulatorDataGroup`][oqd_trical.backend.qutip.datastore.TrICalEmulatorDataGroup] | ||
| named ``"emulation"``. The group holds ``tspan``, ``states``, | ||
| ``final_state`` and (optionally) ``frame`` datasets plus the | ||
| run metadata in ``attrs``. The whole datastore can be saved | ||
| to disk with :meth:`Datastore.model_dump_hdf5` and reloaded | ||
| with :meth:`Datastore.model_validate_hdf5`. |
Comment on lines
+63
to
+76
| def states_to_array(states) -> np.ndarray: | ||
| """Stack a sequence of QuTiP ``Qobj`` states into a single complex array. | ||
|
|
||
| The result is shape ``(n_tsteps, hilbert_dim, 1)`` when all states are kets | ||
| (because ``Qobj.full()`` returns a column vector) and | ||
| ``(n_tsteps, hilbert_dim, hilbert_dim)`` when all states are density | ||
| matrices. An empty input returns a 1-D length-zero complex array, which | ||
| is the same shape that the ``Dataset`` validator accepts. | ||
| """ | ||
| if len(states) == 0: | ||
| return np.empty((0,), dtype=np.complex128) | ||
|
|
||
| arrays = [np.asarray(s.full()) for s in states] | ||
| return np.stack(arrays, axis=0) |
Comment on lines
+129
to
+132
| final_state: Complex array holding the state at the end of the | ||
| evolution; 1-D ``(hilbert_dim,)`` if the solver returned kets or | ||
| 2-D ``(hilbert_dim, hilbert_dim)`` for density matrices. | ||
| (For ket solvers, ``Qobj.full()`` returns ``(N, 1)``.) |
Comment on lines
+15
to
+17
| from importlib.metadata import PackageNotFoundError | ||
| from importlib.metadata import version as _pkg_version | ||
|
|
Comment on lines
+34
to
+37
| try: | ||
| __version__ = _pkg_version("oqd-trical") | ||
| except PackageNotFoundError: | ||
| __version__ = "0+unknown" |
Comment on lines
+99
to
+105
| return QutipBackend( | ||
| solver_options={ | ||
| "progress_bar": False, | ||
| "nsteps": 1_000_000, | ||
| "max_step": 1e-9, | ||
| } | ||
| ) |
…n#49 1. datastore.py: Coerce input to list() at top of states_to_array() so generators/iterators are accepted without TypeError on len(). 2. datastore.py: Fix final_state docstring -- shape is (hilbert_dim, 1) for ket solvers, not 1-D (hilbert_dim,). Matches actual stored output from np.asarray(final_state.full()). 3. base.py + __init__.py: Centralize __version__ in new _version.py to avoid duplication. base.py now imports from oqd_trical._version, avoiding the circular import that would occur from oqd_trical itself. 4. pyproject.toml: Register slow marker in pytest.ini_options so the @pytest.mark.slow annotation no longer produces an "unknown mark" warning in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Updates
QutipBackend.run()to return anoqd_dataschema.Datastore(wrapping aTrICalEmulatorDataGroup) instead of a plaindict, addressing #44.Breaking change: callers that unpack
result["states"],result["tspan"], etc. will need to update toresult["emulation"].states.data,result["emulation"].tspan.data, etc.In exchange, the result is now:
model_dump_hdf5()model_validate_hdf5()-- no glue code neededChanges
src/oqd_trical/backend/qutip/datastore.pysrc/oqd_trical/backend/qutip/base.pysrc/oqd_trical/backend/qutip/vm.pysrc/oqd_trical/backend/qutip/__init__.pysrc/oqd_trical/__init__.pypyproject.tomltests/test_datastore.pytests/test_backends.pydocs/explanation/backends.mdUsage example
Upstream contribution note
Per #44, the
TrICalEmulatorDataGroupshould eventually be contributed upstream tooqd-dataschemaso that it is the canonical group definition shared by both the QuTiP and Dynamiqs backends. This PR defines it locally to keep the PR self-contained; once the group is merged upstream, the local definition can be removed and imported fromoqd_dataschemainstead.Test plan
Made with Cursor