Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/explanation/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,42 @@ Executes the compatible form of the AtomicCircuit with the backend using a tree
- Dynamiqs uses [`DynamiqsVM`][oqd_trical.backend.dynamiqs.vm.DynamiqsVM] as its tree walking interpreter.

///

## Results

The result of a backend run is a schema-validated `oqd_dataschema.Datastore`
that wraps a single
[`TrICalEmulatorDataGroup`][oqd_trical.backend.qutip.datastore.TrICalEmulatorDataGroup]
named `"emulation"`. The group contains:

- `tspan` — 1-D float array of the time points at which a state was tracked.
- `states` — complex array of shape `(n_tsteps, hilbert_dim, 1)` for state-vector
solvers (QuTiP's ``Qobj.full()`` returns a column vector for kets) or
`(n_tsteps, hilbert_dim, hilbert_dim)` for density-matrix solvers.
- `final_state` — complex array of the state at the end of the evolution;
shape `(hilbert_dim, 1)` for ket solvers, `(hilbert_dim, hilbert_dim)` for
density-matrix solvers.
- `frame` — optional complex array of the rotating frame evaluated at `t=0`.

Run metadata (solver, timestep, fock cutoff, Hilbert-space layout, frame
presence, backend identifier, oqd-trical version) is stored in the group's
`attrs` and survives an HDF5 round-trip:

```python
import pathlib
from oqd_trical.backend import QutipBackend

# ... build circuit, compile, run ...
datastore = backend.run(experiment, hilbert_space=hilbert_space, timestep=1e-8)

# Save to disk in the standard oqd-dataschema HDF5 format
datastore.model_dump_hdf5(pathlib.Path("emulation.h5"))

# Reload (note the exact same dictionary access pattern)
reloaded = type(datastore).model_validate_hdf5(pathlib.Path("emulation.h5"))
tspan = reloaded["emulation"].tspan.data
states = reloaded["emulation"].states.data
```

See `tests/test_datastore.py` and `tests/test_backends.py::TestQutipBackendDatastore`
for end-to-end usage examples.
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies = [
"qutip",
"oqd-core",
"oqd-compiler-infrastructure",
"oqd-dataschema",
"dynamiqs>=0.3.1",
]

Expand All @@ -48,6 +49,11 @@ docs = [
]
tests = ["pytest"]

[tool.pytest.ini_options]
markers = [
"slow: marks tests as slow (integration / physics solvers)",
]

[tool.setuptools.packages.find]
where = ["src"]
include = ["oqd_trical*"]
Expand All @@ -59,6 +65,7 @@ fixable = ["ALL"]
[tool.uv.sources]
oqd-compiler-infrastructure = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure" }
oqd-core = { git = "https://github.com/openquantumdesign/oqd-core" }
oqd-dataschema = { git = "https://github.com/openquantumdesign/oqd-dataschema" }

[dependency-groups]
dev = ["jupyter>=1.1.1", "pre-commit>=4.1.0", "ruff>=0.9.6"]
Expand Down
3 changes: 2 additions & 1 deletion src/oqd_trical/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
# limitations under the License.

from . import backend, light_matter, mechanical, misc
from ._version import __version__

__all__ = ["backend", "light_matter", "mechanical", "misc"]
__all__ = ["backend", "light_matter", "mechanical", "misc", "__version__"]
21 changes: 21 additions & 0 deletions src/oqd_trical/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2024-2025 Open Quantum Design

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as _pkg_version

try:
__version__ = _pkg_version("oqd-trical")
except PackageNotFoundError:
__version__ = "0+unknown"
4 changes: 4 additions & 0 deletions src/oqd_trical/backend/qutip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from . import datastore # noqa: F401 - imported for side-effects (Group registration)
from .base import QutipBackend
from .codegen import QutipCodeGeneration
from .datastore import TrICalEmulatorDataGroup, build_emulator_datastore
from .vm import QutipVM

__all__ = [
"QutipBackend",
"QutipCodeGeneration",
"QutipVM",
"TrICalEmulatorDataGroup",
"build_emulator_datastore",
]
69 changes: 59 additions & 10 deletions src/oqd_trical/backend/qutip/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Copyright 2024-2025 Open Quantum Design

#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#
# http://www.apache.org/licenses/LICENSE-2.0

#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -17,7 +17,9 @@
from oqd_core.compiler.atomic.canonicalize import canonicalize_atomic_circuit_factory
from oqd_core.interface.atomic import AtomicCircuit

from oqd_trical._version import __version__
from oqd_trical.backend.qutip.codegen import QutipCodeGeneration
from oqd_trical.backend.qutip.datastore import build_emulator_datastore
from oqd_trical.backend.qutip.vm import QutipVM
from oqd_trical.light_matter.compiler.analysis import GetHilbertSpace, HilbertSpace
from oqd_trical.light_matter.compiler.canonicalize import (
Expand Down Expand Up @@ -56,6 +58,11 @@ def __init__(
self.solver = solver
self.solver_options = solver_options

# Most recently used fock cutoff, populated by `compile` and used by
# `run` to populate the Datastore attrs. May be overridden by
# passing `fock_cutoff` to `run`.
self._fock_cutoff = None

def compile(self, circuit, fock_cutoff, *, relabel=True):
"""
Compiles a AtomicCircuit or AtomicEmulatorCircuit to a [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
Expand All @@ -66,7 +73,7 @@ def compile(self, circuit, fock_cutoff, *, relabel=True):

Returns:
experiment (QutipExperiment): Compiled [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
hilbert_space (Dict[str, int]): Hilbert space of the system.
hilbert_space (HilbertSpace): Hilbert space of the system.
"""
assert isinstance(circuit, (AtomicCircuit, AtomicEmulatorCircuit))

Expand Down Expand Up @@ -115,19 +122,43 @@ def compile(self, circuit, fock_cutoff, *, relabel=True):
compiler_p3 = Post(QutipCodeGeneration(hilbert_space=hilbert_space))
experiment = compiler_p3(intermediate)

# Stash for the upcoming `run` call.
self._fock_cutoff = fock_cutoff

return experiment, hilbert_space

def run(self, experiment, hilbert_space, timestep, *, initial_state=None):
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 +130 to +161
"""
vm = Pre(
QutipVM(
Expand All @@ -140,5 +171,23 @@ def run(self, experiment, hilbert_space, timestep, *, initial_state=None):
)

vm(experiment)

return vm.children[0].result
run_vm = vm.children[0]

cutoff = fock_cutoff if fock_cutoff is not None else self._fock_cutoff
# If the caller never invoked `compile` and did not pass a cutoff,
# fall back to an empty dict (no Fock modes labelled).
if cutoff is None:
cutoff = {}

return build_emulator_datastore(
states=run_vm.states,
tspan=run_vm.tspan,
final_state=run_vm.current_state,
frame=getattr(run_vm, "frame", None),
hilbert_space=hilbert_space,
solver=run_vm.solver_name,
timestep=timestep,
fock_cutoff=cutoff,
backend="qutip",
version=__version__,
)
Loading