qprogram-qblox 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qprogram_qblox/__init__.py +162 -0
- qprogram_qblox/mixin.py +73 -0
- qprogram_qblox/namespace.py +187 -0
- qprogram_qblox/operations.py +233 -0
- qprogram_qblox/profiles.py +271 -0
- qprogram_qblox/py.typed +0 -0
- qprogram_qblox-0.1.0.dist-info/METADATA +144 -0
- qprogram_qblox-0.1.0.dist-info/RECORD +11 -0
- qprogram_qblox-0.1.0.dist-info/WHEEL +4 -0
- qprogram_qblox-0.1.0.dist-info/entry_points.txt +2 -0
- qprogram_qblox-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Copyright 2026 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Qblox vendor extensions for QProgram.
|
|
15
|
+
|
|
16
|
+
This package provides:
|
|
17
|
+
|
|
18
|
+
1. **Runtime registration**: importing this package registers the ``qblox`` vendor namespace on
|
|
19
|
+
[`QProgram`][qprogram.QProgram], registers every Qblox operation with the ``.qp`` serializer, and
|
|
20
|
+
registers the ``qblox-default-v1`` capability profile.
|
|
21
|
+
|
|
22
|
+
2. **Typed mixin**: [`QbloxMixin`][qprogram_qblox.QbloxMixin] adds a typed ``.qblox`` property for IDE autocomplete.
|
|
23
|
+
|
|
24
|
+
3. **Pre-combined QProgram**: [`QProgram`][qprogram_qblox.QProgram] from this package has ``.qblox`` typed out of the
|
|
25
|
+
box.
|
|
26
|
+
|
|
27
|
+
The import is the activation step, and a caller reading a ``.qp`` file need not perform it: the
|
|
28
|
+
``qprogram.vendors`` entry point this package declares lets `qprogram.loads` import the module
|
|
29
|
+
on demand when it meets a ``require qblox`` header.
|
|
30
|
+
|
|
31
|
+
The operations span both kinds a vendor extension can offer, since QProgram draws no line between
|
|
32
|
+
real-time and host-side execution:
|
|
33
|
+
|
|
34
|
+
- one-to-one sequencer instructions ([`QbloxNamespace.acquire`][qprogram_qblox.QbloxNamespace.acquire],
|
|
35
|
+
[`QbloxNamespace.set_markers`][qprogram_qblox.QbloxNamespace.set_markers],
|
|
36
|
+
[`QbloxNamespace.set_trigger`][qprogram_qblox.QbloxNamespace.set_trigger],
|
|
37
|
+
[`QbloxNamespace.wait_trigger`][qprogram_qblox.QbloxNamespace.wait_trigger]);
|
|
38
|
+
- host-side-only operations
|
|
39
|
+
([`QbloxNamespace.set_acquisition_threshold`][qprogram_qblox.QbloxNamespace.set_acquisition_threshold],
|
|
40
|
+
[`QbloxNamespace.set_acquisition_rotation`][qprogram_qblox.QbloxNamespace.set_acquisition_rotation]), each a
|
|
41
|
+
slow-control parameter write at execution time rather than a sequencer instruction.
|
|
42
|
+
|
|
43
|
+
Both spell the same way in ``.qp`` (``qblox.<op_name> <args>``); the platform decides at execution
|
|
44
|
+
time how to realize each one. A third kind is possible and is not among the operations here: an
|
|
45
|
+
orchestration that lowers to several instructions. Routing a measurement result back to a drive
|
|
46
|
+
sequencer, for instance, is a property of the platform wiring rather than of the qblox instrument
|
|
47
|
+
API, so it belongs to a platform package instead.
|
|
48
|
+
|
|
49
|
+
The shortest way in is the pre-combined class::
|
|
50
|
+
|
|
51
|
+
from qprogram_qblox import QProgram
|
|
52
|
+
|
|
53
|
+
qp = QProgram(label="example")
|
|
54
|
+
qp.qblox.acquire("readout_q0", "weights")
|
|
55
|
+
qp.qblox.set_markers("drive_q0", "0001")
|
|
56
|
+
qp.qblox.set_acquisition_threshold("readout_q0", value=0.42)
|
|
57
|
+
qp.qblox.set_acquisition_rotation("readout_q0", angle=0.7854)
|
|
58
|
+
|
|
59
|
+
Combining several vendors means listing their mixins in the MRO::
|
|
60
|
+
|
|
61
|
+
from qprogram import QProgram as BaseQProgram
|
|
62
|
+
from qprogram_qblox import QbloxMixin
|
|
63
|
+
from qprogram_qdac import QdacMixin
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class QProgram(QbloxMixin, QdacMixin, BaseQProgram):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
qp = QProgram()
|
|
71
|
+
qp.qblox.acquire(...)
|
|
72
|
+
qp.qdac.play(...)
|
|
73
|
+
|
|
74
|
+
A ``.qp`` file carrying qblox operations names the vendor in its header::
|
|
75
|
+
|
|
76
|
+
#!QProgram 1.0
|
|
77
|
+
|
|
78
|
+
require qblox 0.1
|
|
79
|
+
|
|
80
|
+
body:
|
|
81
|
+
qblox.acquire "readout_q0" "weights"
|
|
82
|
+
qblox.set_markers "drive_q0" "0001"
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
86
|
+
|
|
87
|
+
from qprogram.qprogram import QProgram as _BaseQProgram
|
|
88
|
+
from qprogram.serialization._specs import make_measurement_op_parse, measurement_op_serialize
|
|
89
|
+
from qprogram.serialization.registry import register_vendor_operation, register_vendor_version
|
|
90
|
+
|
|
91
|
+
from qprogram_qblox.mixin import QbloxMixin
|
|
92
|
+
from qprogram_qblox.namespace import QbloxNamespace
|
|
93
|
+
from qprogram_qblox.operations import (
|
|
94
|
+
Acquire,
|
|
95
|
+
SetAcquisitionRotation,
|
|
96
|
+
SetAcquisitionThreshold,
|
|
97
|
+
SetMarkers,
|
|
98
|
+
SetTrigger,
|
|
99
|
+
WaitTrigger,
|
|
100
|
+
)
|
|
101
|
+
from qprogram_qblox.profiles import QBLOX_DEFAULT_V1
|
|
102
|
+
from qprogram_qblox.profiles import _register as _register_qblox_profile
|
|
103
|
+
|
|
104
|
+
# Resolve our own package version once. This is the single source of truth for the qblox vendor
|
|
105
|
+
# protocol version: parsers check that a file's `require qblox <major.minor>` is compatible with
|
|
106
|
+
# this number.
|
|
107
|
+
try:
|
|
108
|
+
__version__ = version("qprogram-qblox")
|
|
109
|
+
except PackageNotFoundError: # pragma: no cover - source tree without installed metadata
|
|
110
|
+
__version__ = "0.0.0"
|
|
111
|
+
|
|
112
|
+
# --- Step 1: Register the vendor namespace on base QProgram ---
|
|
113
|
+
# Registering on the base class is what makes program.qblox.<method>() work on any QProgram
|
|
114
|
+
# instance, mixin or not.
|
|
115
|
+
_BaseQProgram.register_vendor("qblox", QbloxNamespace)
|
|
116
|
+
|
|
117
|
+
# --- Step 2: Register the protocol version of this vendor extension ---
|
|
118
|
+
# The .qp parser checks `require qblox <x.y>` against it.
|
|
119
|
+
register_vendor_version("qblox", __version__)
|
|
120
|
+
|
|
121
|
+
# --- Step 3: Register operations with the .qp serializer ---
|
|
122
|
+
# All but `acquire` use the default signature-driven serialize/parse pair. `acquire` is a
|
|
123
|
+
# measurement operation, so it needs the measurement callbacks that carry the handle name across
|
|
124
|
+
# the wire as a `name="..."` kwarg.
|
|
125
|
+
register_vendor_operation(
|
|
126
|
+
"qblox",
|
|
127
|
+
"acquire",
|
|
128
|
+
Acquire,
|
|
129
|
+
serialize=measurement_op_serialize,
|
|
130
|
+
parse=make_measurement_op_parse(Acquire),
|
|
131
|
+
)
|
|
132
|
+
register_vendor_operation("qblox", "set_markers", SetMarkers)
|
|
133
|
+
register_vendor_operation("qblox", "set_trigger", SetTrigger)
|
|
134
|
+
register_vendor_operation("qblox", "wait_trigger", WaitTrigger)
|
|
135
|
+
register_vendor_operation("qblox", "set_acquisition_threshold", SetAcquisitionThreshold)
|
|
136
|
+
register_vendor_operation("qblox", "set_acquisition_rotation", SetAcquisitionRotation)
|
|
137
|
+
|
|
138
|
+
# --- Step 4: Register the qblox capability profile bundle ---
|
|
139
|
+
# Importing qprogram_qblox.profiles above already registered the vendor capability tokens, which is
|
|
140
|
+
# what lets the profile name them.
|
|
141
|
+
_register_qblox_profile()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class QProgram(QbloxMixin, _BaseQProgram):
|
|
145
|
+
"""[`QProgram`][qprogram.QProgram] pre-combined with [`QbloxMixin`][qprogram_qblox.QbloxMixin].
|
|
146
|
+
|
|
147
|
+
Identical to [`qprogram.QProgram`][] but with IDE autocomplete for ``qp.qblox.*``.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = [
|
|
152
|
+
"QBLOX_DEFAULT_V1",
|
|
153
|
+
"Acquire",
|
|
154
|
+
"QProgram",
|
|
155
|
+
"QbloxMixin",
|
|
156
|
+
"QbloxNamespace",
|
|
157
|
+
"SetAcquisitionRotation",
|
|
158
|
+
"SetAcquisitionThreshold",
|
|
159
|
+
"SetMarkers",
|
|
160
|
+
"SetTrigger",
|
|
161
|
+
"WaitTrigger",
|
|
162
|
+
]
|
qprogram_qblox/mixin.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright 2026 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Typed mixin that exposes the Qblox namespace as ``.qblox`` on a QProgram subclass.
|
|
15
|
+
|
|
16
|
+
The mixin exists for the benefit of editors and type-checkers. At runtime the base
|
|
17
|
+
[`QProgram`][qprogram.QProgram]'s dynamic ``__getattr__`` already routes ``program.qblox.*`` to the
|
|
18
|
+
registered [`QbloxNamespace`][qprogram_qblox.QbloxNamespace], but static tooling cannot see that
|
|
19
|
+
dispatch, so the mixin spells the namespace out as a typed ``@property``.
|
|
20
|
+
|
|
21
|
+
One vendor, using the pre-combined class this package ships::
|
|
22
|
+
|
|
23
|
+
from qprogram_qblox import QProgram
|
|
24
|
+
|
|
25
|
+
qp = QProgram() # QProgram with .qblox typed
|
|
26
|
+
qp.qblox.acquire(...) # IDE autocomplete works
|
|
27
|
+
|
|
28
|
+
Several vendors, composed by listing their mixins in the MRO::
|
|
29
|
+
|
|
30
|
+
from qprogram import QProgram as BaseQProgram
|
|
31
|
+
from qprogram_qblox import QbloxMixin
|
|
32
|
+
from qprogram_qdac import QdacMixin
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class QProgram(QbloxMixin, QdacMixin, BaseQProgram):
|
|
36
|
+
pass
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
from typing import TYPE_CHECKING
|
|
42
|
+
|
|
43
|
+
from qprogram_qblox.namespace import QbloxNamespace
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from qprogram.qprogram import QProgram as _BaseQProgram
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class QbloxMixin:
|
|
50
|
+
"""Mixin that adds a typed ``.qblox`` property to QProgram.
|
|
51
|
+
|
|
52
|
+
Compose it with [`qprogram.QProgram`][] through multiple inheritance to get editor
|
|
53
|
+
autocomplete for the Qblox operations. The property caches the namespace on the program, so
|
|
54
|
+
repeated ``program.qblox`` accesses return the same object.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def qblox(self: _BaseQProgram) -> QbloxNamespace: # type: ignore[misc]
|
|
59
|
+
"""This program's typed Qblox namespace.
|
|
60
|
+
|
|
61
|
+
The first access builds a [`QbloxNamespace`][qprogram_qblox.QbloxNamespace] bound to the
|
|
62
|
+
program and stores it under a private attribute; later accesses return that same instance.
|
|
63
|
+
Both the load and the store go through `object` so they bypass the program's own
|
|
64
|
+
attribute hooks: a cache miss has to surface as a plain `AttributeError` here rather
|
|
65
|
+
than reach [`QProgram`][qprogram.QProgram]'s vendor-registry ``__getattr__``.
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
return object.__getattribute__(self, "_qblox_ns") # ruff: ignore[unnecessary-dunder-call]
|
|
69
|
+
except AttributeError:
|
|
70
|
+
pass
|
|
71
|
+
ns = QbloxNamespace(self)
|
|
72
|
+
object.__setattr__(self, "_qblox_ns", ns) # ruff: ignore[unnecessary-dunder-call]
|
|
73
|
+
return ns
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Copyright 2026 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Typed [`VendorNamespace`][qprogram.VendorNamespace] for Qblox operations.
|
|
15
|
+
|
|
16
|
+
Each method on [`QbloxNamespace`][qprogram_qblox.QbloxNamespace] is a typed wrapper that builds the matching
|
|
17
|
+
[`Operation`][qprogram.operations.Operation] subclass from `qprogram_qblox.operations` and appends
|
|
18
|
+
it to the program's active block. This is where explicit parameter types live, so editors complete
|
|
19
|
+
and type-checkers check ``program.qblox.<operation>(...)``. The dynamic ``__getattr__`` on
|
|
20
|
+
[`QProgram`][qprogram.QProgram] dispatches the same calls at runtime; the typed namespace is what makes
|
|
21
|
+
them discoverable.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
from qprogram.operations.operation import MeasurementField
|
|
29
|
+
from qprogram.vendor import VendorNamespace
|
|
30
|
+
|
|
31
|
+
from qprogram_qblox.operations import (
|
|
32
|
+
Acquire,
|
|
33
|
+
SetAcquisitionRotation,
|
|
34
|
+
SetAcquisitionThreshold,
|
|
35
|
+
SetMarkers,
|
|
36
|
+
SetTrigger,
|
|
37
|
+
WaitTrigger,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from collections.abc import Iterable
|
|
42
|
+
|
|
43
|
+
from qprogram.result import MeasurementHandle
|
|
44
|
+
from qprogram.variable import Expression
|
|
45
|
+
from qprogram.waveforms.waveform import IQWaveform
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class QbloxNamespace(VendorNamespace):
|
|
49
|
+
"""Qblox vendor namespace, reached as ``program.qblox.<operation>()``.
|
|
50
|
+
|
|
51
|
+
Attached to every [`QProgram`][qprogram.QProgram] instance as ``.qblox`` once the
|
|
52
|
+
`qprogram_qblox` package is imported. Each method validates its arguments through the typed
|
|
53
|
+
signature, constructs the matching operation, and appends it to the program's currently active
|
|
54
|
+
block.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def acquire(
|
|
58
|
+
self,
|
|
59
|
+
bus: str,
|
|
60
|
+
weights: IQWaveform | str,
|
|
61
|
+
fields: Iterable[MeasurementField] = (MeasurementField.IQ,),
|
|
62
|
+
*,
|
|
63
|
+
name: str | None = None,
|
|
64
|
+
) -> MeasurementHandle:
|
|
65
|
+
"""Append an [`Acquire`][qprogram_qblox.Acquire] operation.
|
|
66
|
+
|
|
67
|
+
The handle's name comes from the same per-bus counter core
|
|
68
|
+
[`measure`][qprogram.QProgram.measure] draws on, so two acquisitions on ``q[0].readout`` produce
|
|
69
|
+
``q0/readout/m0`` and ``q0/readout/m1`` whether or not a core ``measure`` also runs on that
|
|
70
|
+
qubit. A raw-string bus carries no coordinates to build that prefix from, so it falls back
|
|
71
|
+
to the bare ``m0``, ``m1``, ... counter shared by every raw-string measurement.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
bus (str): Readout bus to acquire on.
|
|
75
|
+
weights (IQWaveform | str): Integration weights, either a concrete
|
|
76
|
+
[`IQWaveform`][qprogram.waveforms.IQWaveform] or a string alias.
|
|
77
|
+
fields (Iterable[MeasurementField]): Which measurement fields to produce, as an iterable
|
|
78
|
+
of [`MeasurementField`][qprogram.MeasurementField] members. Default ``(MeasurementField.IQ,)``;
|
|
79
|
+
`RAW` asks for the raw ADC trace.
|
|
80
|
+
name (str | None): Explicit measurement name. Auto-allocated when omitted.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The [`MeasurementHandle`][qprogram.MeasurementHandle] this acquisition writes to, for retrieving the
|
|
84
|
+
result and for referencing the outcome in a conditional.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValidationError: If ``name`` is empty, is not a string, or is already used by another
|
|
88
|
+
measurement in the program, if ``fields`` names a field that is not registered, or
|
|
89
|
+
if ``bus`` is a [`BusRef`][qprogram.BusRef] from another schema than the one attached
|
|
90
|
+
to the program.
|
|
91
|
+
"""
|
|
92
|
+
return self._append_measurement(
|
|
93
|
+
Acquire,
|
|
94
|
+
bus=bus,
|
|
95
|
+
weights=weights,
|
|
96
|
+
fields=fields,
|
|
97
|
+
name=name,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def set_markers(self, bus: str, mask: str) -> None:
|
|
101
|
+
"""Append a [`SetMarkers`][qprogram_qblox.SetMarkers] operation.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
bus (str): Bus whose marker outputs to drive.
|
|
105
|
+
mask (str): Four characters of ``0`` and ``1``, one per marker line, e.g. ``"0001"``.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
ValidationError: If ``bus`` is a [`BusRef`][qprogram.BusRef] from another schema than the
|
|
109
|
+
one attached to the program.
|
|
110
|
+
"""
|
|
111
|
+
self._append(SetMarkers(bus=bus, mask=mask))
|
|
112
|
+
|
|
113
|
+
def set_trigger(
|
|
114
|
+
self,
|
|
115
|
+
bus: str,
|
|
116
|
+
duration: int,
|
|
117
|
+
outputs: list[int] | int | None = None,
|
|
118
|
+
position: str = "start",
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Append a [`SetTrigger`][qprogram_qblox.SetTrigger] operation.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
bus (str): Bus whose trigger outputs to arm.
|
|
124
|
+
duration (int): Trigger-active duration in nanoseconds.
|
|
125
|
+
outputs (list[int] | int | None): Trigger output indices to arm, one index or a list of
|
|
126
|
+
them. ``None`` leaves the selection to the platform.
|
|
127
|
+
position (str): Point in the operation at which the trigger fires, either ``"start"`` or
|
|
128
|
+
``"end"``. Default ``"start"``.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
ValidationError: If ``bus`` is a [`BusRef`][qprogram.BusRef] from another schema than the
|
|
132
|
+
one attached to the program.
|
|
133
|
+
"""
|
|
134
|
+
self._append(SetTrigger(bus=bus, duration=duration, outputs=outputs, position=position))
|
|
135
|
+
|
|
136
|
+
def wait_trigger(self, bus: str, duration: int, port: int | None = None) -> None:
|
|
137
|
+
"""Append a [`WaitTrigger`][qprogram_qblox.WaitTrigger] operation.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
bus (str): Bus whose sequencer waits.
|
|
141
|
+
duration (int): Timeout in nanoseconds, after which the sequencer stops waiting.
|
|
142
|
+
port (int | None): Trigger input port to listen on. ``None`` leaves the choice to the
|
|
143
|
+
platform.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ValidationError: If ``bus`` is a [`BusRef`][qprogram.BusRef] from another schema than the
|
|
147
|
+
one attached to the program.
|
|
148
|
+
"""
|
|
149
|
+
self._append(WaitTrigger(bus=bus, duration=duration, port=port))
|
|
150
|
+
|
|
151
|
+
def set_acquisition_threshold(self, bus: str, value: float | Expression) -> None:
|
|
152
|
+
"""Append a [`SetAcquisitionThreshold`][qprogram_qblox.SetAcquisitionThreshold] operation.
|
|
153
|
+
|
|
154
|
+
Host-side-only: the platform realizes it as a slow-control parameter write at execution
|
|
155
|
+
time, not as a sequencer instruction. A vendor namespace can expose operations whose effect
|
|
156
|
+
is entirely off-sequencer, and the platform decides at execution time how to realize each
|
|
157
|
+
one.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
bus (str): Readout bus whose discrimination threshold to set.
|
|
161
|
+
value (float | Expression): Threshold, in volts after integration. Accepts an
|
|
162
|
+
[`Expression`][qprogram.Expression] so an enclosing loop can sweep it.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ValidationError: If ``bus`` is a [`BusRef`][qprogram.BusRef] from another schema than the
|
|
166
|
+
one attached to the program.
|
|
167
|
+
"""
|
|
168
|
+
self._append(SetAcquisitionThreshold(bus=bus, value=value))
|
|
169
|
+
|
|
170
|
+
def set_acquisition_rotation(self, bus: str, angle: float | Expression) -> None:
|
|
171
|
+
"""Append a [`SetAcquisitionRotation`][qprogram_qblox.SetAcquisitionRotation] operation.
|
|
172
|
+
|
|
173
|
+
The companion to `set_acquisition_threshold`: the integrated IQ point is rotated by
|
|
174
|
+
``angle`` before the comparison against the threshold, so the two populations separate along
|
|
175
|
+
one axis. Host-side-only as well, a parameter write rather than a sequencer instruction.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
bus (str): Readout bus whose acquisition rotation to set.
|
|
179
|
+
angle (float | Expression): Rotation angle in radians, the unit convention of core
|
|
180
|
+
[`set_phase`][qprogram.QProgram.set_phase]. Accepts an [`Expression`][qprogram.Expression] so an
|
|
181
|
+
enclosing loop can sweep it, which is how it is normally calibrated.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
ValidationError: If ``bus`` is a [`BusRef`][qprogram.BusRef] from another schema than the
|
|
185
|
+
one attached to the program.
|
|
186
|
+
"""
|
|
187
|
+
self._append(SetAcquisitionRotation(bus=bus, angle=angle))
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Copyright 2026 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Qblox-specific Operation classes.
|
|
15
|
+
|
|
16
|
+
Each class is a concrete [`Operation`][qprogram.operations.Operation] subclass that lives in the QProgram
|
|
17
|
+
AST. They are the data nodes: typed attributes plus the capability tokens they require, serialized
|
|
18
|
+
to and from ``.qp`` by the vendor registry.
|
|
19
|
+
|
|
20
|
+
They span both kinds of vendor operation. [`Acquire`][qprogram_qblox.Acquire],
|
|
21
|
+
[`SetMarkers`][qprogram_qblox.SetMarkers], [`SetTrigger`][qprogram_qblox.SetTrigger] and
|
|
22
|
+
[`WaitTrigger`][qprogram_qblox.WaitTrigger] each map to one sequencer instruction;
|
|
23
|
+
[`SetAcquisitionThreshold`][qprogram_qblox.SetAcquisitionThreshold] and
|
|
24
|
+
[`SetAcquisitionRotation`][qprogram_qblox.SetAcquisitionRotation] map to no sequencer instruction at all and are
|
|
25
|
+
realized as slow-control parameter writes. The AST draws no distinction between the two, and neither does the ``.qp``
|
|
26
|
+
format.
|
|
27
|
+
|
|
28
|
+
A class declares `BUS_ATTRS` and
|
|
29
|
+
`WAVEFORM_ATTRS` only where its data shape differs from the
|
|
30
|
+
``Operation`` defaults. The base class's introspection methods (``variables``, ``buses``,
|
|
31
|
+
``waveforms``, ``walk``) read those declarations, so no subclass here overrides them.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
37
|
+
|
|
38
|
+
from qprogram.operations.operation import MeasurementField, MeasurementOperation, Operation, normalize_fields
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from collections.abc import Iterable
|
|
42
|
+
|
|
43
|
+
from qprogram.result import MeasurementHandle
|
|
44
|
+
from qprogram.variable import Expression
|
|
45
|
+
from qprogram.waveforms.waveform import IQWaveform
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Acquire(MeasurementOperation):
|
|
49
|
+
"""An acquisition on a readout bus, with no readout pulse of its own.
|
|
50
|
+
|
|
51
|
+
Where core ``measure`` plays a readout pulse and integrates the response, ``acquire`` only
|
|
52
|
+
integrates, which is what a program wants when the pulse is driven separately. It is a
|
|
53
|
+
[`MeasurementOperation`][qprogram.operations.operation.MeasurementOperation] like ``measure``, so it takes part
|
|
54
|
+
in the program's per-bus measurement-name counter: an ``acquire`` after a ``measure`` on the same
|
|
55
|
+
qubit picks up the next free name on that qubit.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
bus (str): Readout bus to acquire on.
|
|
59
|
+
weights (IQWaveform | str): Integration weights, either a concrete
|
|
60
|
+
[`IQWaveform`][qprogram.waveforms.IQWaveform] or a string alias resolved later by
|
|
61
|
+
[`with_waveforms`][qprogram.QProgram.with_waveforms].
|
|
62
|
+
handle (MeasurementHandle): The canonical [`MeasurementHandle`][qprogram.MeasurementHandle] for this
|
|
63
|
+
acquisition. See [`Measure`][qprogram.operations.Measure] for what the runtime writes onto
|
|
64
|
+
it and who reads it.
|
|
65
|
+
fields (Iterable[MeasurementField]): Which measurement fields the platform produces, as an
|
|
66
|
+
iterable of [`MeasurementField`][qprogram.MeasurementField] members. Default
|
|
67
|
+
``(MeasurementField.IQ,)``. See [`Measure`][qprogram.operations.Measure] for the full
|
|
68
|
+
description. Stored canonically ordered and deduplicated by
|
|
69
|
+
[`normalize_fields`][qprogram.operations.operation.normalize_fields].
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValidationError: If ``fields`` is a bare string, is not iterable, requests no field at all,
|
|
73
|
+
or names a field that is not registered.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
WAVEFORM_ATTRS: ClassVar[tuple[str, ...]] = ("weights",)
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
bus: str,
|
|
81
|
+
weights: IQWaveform | str,
|
|
82
|
+
handle: MeasurementHandle,
|
|
83
|
+
fields: Iterable[MeasurementField] = (MeasurementField.IQ,),
|
|
84
|
+
) -> None:
|
|
85
|
+
self.bus = bus
|
|
86
|
+
self.weights = weights
|
|
87
|
+
self.handle = handle
|
|
88
|
+
self.fields: tuple[str, ...] = normalize_fields(fields)
|
|
89
|
+
|
|
90
|
+
def required_capabilities(self) -> set[str]:
|
|
91
|
+
"""Return ``vendor.qblox.acquire`` plus the weights and requested-field tokens.
|
|
92
|
+
|
|
93
|
+
``waveform.iq`` is always required, since an acquisition integrates an IQ pair. String
|
|
94
|
+
weights contribute ``waveform.alias``; concrete weights contribute the per-class token from
|
|
95
|
+
[`qprogram.protocol.waveform_token`][] when their class is registered. The
|
|
96
|
+
``measure.fields.<name>`` tokens come from
|
|
97
|
+
[`required_capabilities`][qprogram.operations.operation.MeasurementOperation.required_capabilities].
|
|
98
|
+
"""
|
|
99
|
+
from qprogram.protocol import waveform_token # ruff: ignore[import-outside-top-level]
|
|
100
|
+
|
|
101
|
+
caps = super().required_capabilities() | {"vendor.qblox.acquire", "waveform.iq"}
|
|
102
|
+
if isinstance(self.weights, str):
|
|
103
|
+
caps.add("waveform.alias")
|
|
104
|
+
else:
|
|
105
|
+
tok = waveform_token(self.weights)
|
|
106
|
+
if tok is not None:
|
|
107
|
+
caps.add(tok)
|
|
108
|
+
return caps
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SetMarkers(Operation):
|
|
112
|
+
"""A new 4-bit marker output mask on a qblox sequencer.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
bus (str): Bus whose marker outputs to drive.
|
|
116
|
+
mask (str): Four characters of ``0`` and ``1``, one per marker line. ``"0001"`` enables
|
|
117
|
+
marker 1.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, bus: str, mask: str) -> None:
|
|
121
|
+
self.bus = bus
|
|
122
|
+
self.mask = mask
|
|
123
|
+
|
|
124
|
+
def required_capabilities(self) -> set[str]:
|
|
125
|
+
"""Return ``vendor.qblox.set_markers``, the operation's identity token."""
|
|
126
|
+
return {"vendor.qblox.set_markers"}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class SetTrigger(Operation):
|
|
130
|
+
"""A trigger output configuration on a qblox sequencer.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
bus (str): Bus whose trigger outputs to arm.
|
|
134
|
+
duration (int): Trigger-active duration in nanoseconds.
|
|
135
|
+
outputs (list[int] | int | None): Trigger output indices to arm, one index or a list of
|
|
136
|
+
them. ``None`` leaves the selection to the platform.
|
|
137
|
+
position (str): Point in the operation at which the trigger fires, either ``"start"`` or
|
|
138
|
+
``"end"``. Default ``"start"``.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
bus: str,
|
|
144
|
+
duration: int,
|
|
145
|
+
outputs: list[int] | int | None = None,
|
|
146
|
+
position: str = "start",
|
|
147
|
+
) -> None:
|
|
148
|
+
self.bus = bus
|
|
149
|
+
self.duration = duration
|
|
150
|
+
self.outputs = outputs
|
|
151
|
+
self.position = position
|
|
152
|
+
|
|
153
|
+
def required_capabilities(self) -> set[str]:
|
|
154
|
+
"""Return ``vendor.qblox.set_trigger``, the operation's identity token."""
|
|
155
|
+
return {"vendor.qblox.set_trigger"}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class WaitTrigger(Operation):
|
|
159
|
+
"""A wait for an external trigger on a qblox sequencer.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
bus (str): Bus whose sequencer waits.
|
|
163
|
+
duration (int): Timeout in nanoseconds, after which the sequencer stops waiting.
|
|
164
|
+
port (int | None): Trigger input port to listen on. ``None`` leaves the choice to the
|
|
165
|
+
platform.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(self, bus: str, duration: int, port: int | None = None) -> None:
|
|
169
|
+
self.bus = bus
|
|
170
|
+
self.duration = duration
|
|
171
|
+
self.port = port
|
|
172
|
+
|
|
173
|
+
def required_capabilities(self) -> set[str]:
|
|
174
|
+
"""Return ``vendor.qblox.wait_trigger``, the operation's identity token."""
|
|
175
|
+
return {"vendor.qblox.wait_trigger"}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class SetAcquisitionThreshold(Operation):
|
|
179
|
+
"""A new qubit-state discrimination threshold on a readout bus.
|
|
180
|
+
|
|
181
|
+
A **host-side-only** vendor operation: the qblox platform realizes it as a slow-control
|
|
182
|
+
parameter write at execution time and emits no sequencer instruction. Vendor operations do not
|
|
183
|
+
have to map onto sequencer instructions at all: an extension may expose any operation whose
|
|
184
|
+
execution its platform knows how to interpret, be that a sequencer command, a parameter write,
|
|
185
|
+
or a multi-step orchestration.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
bus (str): Readout bus whose discrimination threshold to set.
|
|
189
|
+
value (float | Expression): Threshold, in volts after integration. Accepts an
|
|
190
|
+
[`Expression`][qprogram.Expression] for sweeps.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(self, bus: str, value: float | Expression) -> None:
|
|
194
|
+
self.bus = bus
|
|
195
|
+
self.value = value
|
|
196
|
+
|
|
197
|
+
def required_capabilities(self) -> set[str]:
|
|
198
|
+
"""Return ``vendor.qblox.set_acquisition_threshold`` plus the ``value`` expression tokens."""
|
|
199
|
+
from qprogram.protocol import expression_tokens # ruff: ignore[import-outside-top-level]
|
|
200
|
+
|
|
201
|
+
return {"vendor.qblox.set_acquisition_threshold"} | expression_tokens(self.value)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class SetAcquisitionRotation(Operation):
|
|
205
|
+
"""A new acquisition rotation angle on a readout bus.
|
|
206
|
+
|
|
207
|
+
The other half of qblox's thresholded acquisition, and the sibling of
|
|
208
|
+
[`SetAcquisitionThreshold`][qprogram_qblox.SetAcquisitionThreshold]: the integrated IQ point is rotated by this
|
|
209
|
+
angle so that the ground and excited populations separate along a single axis, and only then compared against the
|
|
210
|
+
threshold. Setting one without the other is legal, since they are independent parameters, but a calibrated
|
|
211
|
+
discrimination usually writes both.
|
|
212
|
+
|
|
213
|
+
Host-side-only like the threshold: a slow-control parameter write at execution time, not a
|
|
214
|
+
sequencer instruction.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
bus (str): Readout bus whose acquisition rotation to set.
|
|
218
|
+
angle (float | Expression): Rotation angle in **radians**, the unit convention of the core
|
|
219
|
+
phase operations ([`SetPhase`][qprogram.operations.SetPhase]). Accepts an
|
|
220
|
+
[`Expression`][qprogram.Expression] for sweeps, which is the usual way to calibrate it. Values
|
|
221
|
+
outside ``[0, 2π)`` are the platform's to normalize or reject, not this node's: a swept
|
|
222
|
+
angle has no literal value to check at build time.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __init__(self, bus: str, angle: float | Expression) -> None:
|
|
226
|
+
self.bus = bus
|
|
227
|
+
self.angle = angle
|
|
228
|
+
|
|
229
|
+
def required_capabilities(self) -> set[str]:
|
|
230
|
+
"""Return ``vendor.qblox.set_acquisition_rotation`` plus the ``angle`` expression tokens."""
|
|
231
|
+
from qprogram.protocol import expression_tokens # ruff: ignore[import-outside-top-level]
|
|
232
|
+
|
|
233
|
+
return {"vendor.qblox.set_acquisition_rotation"} | expression_tokens(self.angle)
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Copyright 2026 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Capability profile bundles for the Qblox vendor extension.
|
|
15
|
+
|
|
16
|
+
Defines [`QBLOX_DEFAULT_V1`][qprogram_qblox.QBLOX_DEFAULT_V1], the bus-level profile describing what a qblox-driven bus
|
|
17
|
+
can do: the pulse, timing and parameter operations, every waveform class the sequencers render, the measurement fields a
|
|
18
|
+
readout produces, and the ``vendor.qblox.*`` operations. Qblox drives its sequencers in real time, so a platform fills a
|
|
19
|
+
bus slot's ``rt`` half with it; the platform slot is filled by the core-shipped ``qprogram-base-v1``, which carries the
|
|
20
|
+
block, sweep and expression tokens. Together the two give a complete
|
|
21
|
+
[`PlatformCapabilities`][qprogram.PlatformCapabilities] shape.
|
|
22
|
+
|
|
23
|
+
Two predicates travel with the profile:
|
|
24
|
+
|
|
25
|
+
- `_reject_arbitrary_sweep_at_wait_duration`, a hard [`Diagnostic`][qprogram.Diagnostic]. The
|
|
26
|
+
operand register of the qblox wait instruction advances by a fixed step, so a duration swept from
|
|
27
|
+
an arbitrary source fits no execution model qblox can compile, real-time or host-side.
|
|
28
|
+
- `_drag_sigma_in_loop_is_host_only`, a soft [`DomainConstraint`][qprogram.DomainConstraint]. The
|
|
29
|
+
sequencer cannot recompute an [`IQDrag`][qprogram.waveforms.IQDrag] envelope between iterations, but
|
|
30
|
+
the host can dispatch one shot per iteration. The constraint excludes ``"rt"`` alone, so the
|
|
31
|
+
binding loop classifies as ``{host}`` while the ``play`` inside it stays real-time.
|
|
32
|
+
|
|
33
|
+
Registered as a side effect of importing `qprogram_qblox`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import TYPE_CHECKING
|
|
39
|
+
|
|
40
|
+
from qprogram.operations.play import Play
|
|
41
|
+
from qprogram.operations.wait import Wait
|
|
42
|
+
from qprogram.protocol import (
|
|
43
|
+
Diagnostic,
|
|
44
|
+
DomainConstraint,
|
|
45
|
+
Profile,
|
|
46
|
+
ValidationContext,
|
|
47
|
+
register_capability_tokens,
|
|
48
|
+
register_profile,
|
|
49
|
+
)
|
|
50
|
+
from qprogram.variable import Variable
|
|
51
|
+
from qprogram.waveforms.iq_drag import IQDrag
|
|
52
|
+
|
|
53
|
+
# Register vendor-specific capability tokens *before* constructing the profile that names them:
|
|
54
|
+
# Profile.__post_init__ validates that every listed token is in CAPABILITY_REGISTRY, so registration
|
|
55
|
+
# must come first.
|
|
56
|
+
register_capability_tokens(
|
|
57
|
+
"vendor.qblox.acquire",
|
|
58
|
+
"vendor.qblox.set_markers",
|
|
59
|
+
"vendor.qblox.set_trigger",
|
|
60
|
+
"vendor.qblox.wait_trigger",
|
|
61
|
+
"vendor.qblox.set_acquisition_threshold",
|
|
62
|
+
"vendor.qblox.set_acquisition_rotation",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if TYPE_CHECKING:
|
|
66
|
+
from collections.abc import Iterable
|
|
67
|
+
|
|
68
|
+
from qprogram.blocks.block import Block
|
|
69
|
+
from qprogram.operations.operation import Operation
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _reject_arbitrary_sweep_at_wait_duration(
|
|
73
|
+
node: Operation | Block,
|
|
74
|
+
ctx: ValidationContext,
|
|
75
|
+
) -> Iterable[Diagnostic | DomainConstraint]:
|
|
76
|
+
"""Reject a ``wait`` whose duration is bound to an arbitrary sweep source.
|
|
77
|
+
|
|
78
|
+
The qblox wait instruction takes one integer cycle count, and the register holding it advances
|
|
79
|
+
by a fixed step, so the hardware can only walk a duration that is an exact ``start + step * i``
|
|
80
|
+
ramp. The predicate asks [`sweep_kind_of`][qprogram.ValidationContext.sweep_kind_of] how the variable at
|
|
81
|
+
`duration` is bound and fires when the binding
|
|
82
|
+
[`Sweep`][qprogram.blocks.Sweep]'s source declares ``KIND = "arbitrary"``:
|
|
83
|
+
[`Values`][qprogram.sweeps.Values], [`Logspace`][qprogram.sweeps.Logspace],
|
|
84
|
+
[`File`][qprogram.sweeps.File], and every combinator around them. A linear source
|
|
85
|
+
([`Range`][qprogram.sweeps.Range], [`Linspace`][qprogram.sweeps.Linspace]) passes, and so does a
|
|
86
|
+
constant duration, which is not loop-bound at all.
|
|
87
|
+
|
|
88
|
+
Host-side dispatch cannot rescue the combination, since qblox still has to emit the wait
|
|
89
|
+
instruction per shot. That is why this is a [`Diagnostic`][qprogram.Diagnostic] rather than a
|
|
90
|
+
[`DomainConstraint`][qprogram.DomainConstraint].
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
node (Operation | Block): The AST node currently being checked.
|
|
94
|
+
ctx (ValidationContext): Validation context, used to look up how the duration variable is
|
|
95
|
+
bound.
|
|
96
|
+
|
|
97
|
+
Yields:
|
|
98
|
+
One ``"qblox.arbitrary-wait-sweep"`` error [`Diagnostic`][qprogram.Diagnostic] when ``node`` is a
|
|
99
|
+
``wait`` whose duration variable is bound to an arbitrary source. Nothing otherwise.
|
|
100
|
+
"""
|
|
101
|
+
if not isinstance(node, Wait):
|
|
102
|
+
return
|
|
103
|
+
if not isinstance(node.duration, Variable):
|
|
104
|
+
return
|
|
105
|
+
if ctx.sweep_kind_of(node.duration) == "arbitrary":
|
|
106
|
+
yield Diagnostic(
|
|
107
|
+
severity="error",
|
|
108
|
+
code="qblox.arbitrary-wait-sweep",
|
|
109
|
+
message=(
|
|
110
|
+
f"Variable {node.duration.id!r} is swept with arbitrary "
|
|
111
|
+
f"values and used at Wait.duration, which qblox does not "
|
|
112
|
+
f"support (the wait instruction needs a linear step). Use "
|
|
113
|
+
f"a linear sweep source (Range / Linspace) instead, or a constant duration."
|
|
114
|
+
),
|
|
115
|
+
node=node,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _drag_sigma_in_loop_is_host_only(
|
|
120
|
+
node: Operation | Block,
|
|
121
|
+
ctx: ValidationContext,
|
|
122
|
+
) -> Iterable[Diagnostic | DomainConstraint]:
|
|
123
|
+
"""Restrict the binding loop of a swept ``IQDrag.sigma`` to host-side dispatch.
|
|
124
|
+
|
|
125
|
+
A qblox sequencer re-arms a real-time loop with a new amplitude or duration on its own, but it
|
|
126
|
+
cannot recompute a Drag envelope's ``sigma`` between iterations: the gaussian and its derivative
|
|
127
|
+
are sampled once, at upload. Sweeping ``sigma`` therefore means re-uploading the waveform per
|
|
128
|
+
iteration, which the enclosing loop can only do host-side, one qblox shot at a time. The
|
|
129
|
+
[`Play`][qprogram.operations.Play] itself stays real-time; what changes is how the loop around it
|
|
130
|
+
iterates.
|
|
131
|
+
|
|
132
|
+
The constraint targets the loop returned by [`binding_loop_of`][qprogram.ValidationContext.binding_loop_of],
|
|
133
|
+
not the ``Play``, because that is the node whose iteration mechanism is at stake. The classifier
|
|
134
|
+
subtracts ``"rt"`` from the loop's support and dispatches the ``Play`` as one real-time shot per
|
|
135
|
+
host-side iteration. A ``sigma`` that no loop binds is a constant at upload time and is left
|
|
136
|
+
alone.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
node (Operation | Block): The AST node currently being checked.
|
|
140
|
+
ctx (ValidationContext): Validation context, used to find the loop that binds ``sigma``.
|
|
141
|
+
|
|
142
|
+
Yields:
|
|
143
|
+
One [`DomainConstraint`][qprogram.DomainConstraint] excluding ``"rt"`` from the binding loop, when
|
|
144
|
+
``node`` is a ``play`` of an [`IQDrag`][qprogram.waveforms.IQDrag] whose ``sigma`` is a
|
|
145
|
+
loop-bound variable. Nothing otherwise.
|
|
146
|
+
"""
|
|
147
|
+
if not isinstance(node, Play) or not isinstance(node.waveform, IQDrag):
|
|
148
|
+
return
|
|
149
|
+
sigma = node.waveform.sigma
|
|
150
|
+
if not isinstance(sigma, Variable):
|
|
151
|
+
return
|
|
152
|
+
binding_loop = ctx.binding_loop_of(sigma)
|
|
153
|
+
if binding_loop is None:
|
|
154
|
+
return
|
|
155
|
+
yield DomainConstraint(
|
|
156
|
+
node=binding_loop,
|
|
157
|
+
exclude=frozenset({"rt"}),
|
|
158
|
+
reason=(
|
|
159
|
+
f"Variable {sigma.id!r} sweeps IQDrag.sigma in a contained Play, which qblox "
|
|
160
|
+
f"cannot real-time-update; the loop dispatches per shot host-side instead."
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
_BUS_OPS: frozenset[str] = frozenset(
|
|
166
|
+
{
|
|
167
|
+
"op.play",
|
|
168
|
+
"op.measure",
|
|
169
|
+
"op.wait",
|
|
170
|
+
"op.sync",
|
|
171
|
+
"op.set_frequency",
|
|
172
|
+
"op.set_phase",
|
|
173
|
+
"op.set_gain",
|
|
174
|
+
"op.reset_phase",
|
|
175
|
+
"op.set_offset",
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
"""Core operations a qblox sequencer executes.
|
|
179
|
+
|
|
180
|
+
Each of them targets a bus, so its token belongs on this bus-level profile rather than on the
|
|
181
|
+
platform slot: the validator routes a bus-touching operation to the slot its bus resolves to.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
_WAVEFORMS: frozenset[str] = frozenset(
|
|
185
|
+
{
|
|
186
|
+
"waveform.single",
|
|
187
|
+
"waveform.iq",
|
|
188
|
+
"waveform.alias",
|
|
189
|
+
"waveform.arbitrary",
|
|
190
|
+
"waveform.chained",
|
|
191
|
+
"waveform.flat_top",
|
|
192
|
+
"waveform.gaussian",
|
|
193
|
+
"waveform.gaussian_drag_correction",
|
|
194
|
+
"waveform.ramp",
|
|
195
|
+
"waveform.snz",
|
|
196
|
+
"waveform.square",
|
|
197
|
+
"waveform.iq_drag",
|
|
198
|
+
"waveform.iq_pair",
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
"""Waveform tokens the qblox sequencers render.
|
|
202
|
+
|
|
203
|
+
Two levels, both bus-level because a waveform only reaches the hardware through a bus: the channel
|
|
204
|
+
kind (``waveform.single`` and ``waveform.iq``, since qblox drives both single-channel and IQ buses),
|
|
205
|
+
``waveform.alias`` for a name a [`WaveformLibrary`][qprogram.WaveformLibrary] resolves later, and one per-class
|
|
206
|
+
token for each envelope the sequencers can sample.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
_FIELDS: frozenset[str] = frozenset(
|
|
210
|
+
{
|
|
211
|
+
"measure.fields.iq",
|
|
212
|
+
"measure.fields.raw",
|
|
213
|
+
"measure.fields.state",
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
"""Measurement fields a qblox readout produces.
|
|
217
|
+
|
|
218
|
+
All three core members of [`MeasurementField`][qprogram.MeasurementField]: the integrated IQ point, the raw ADC
|
|
219
|
+
trace, and the thresholded state. [`Measure`][qprogram.operations.Measure] and
|
|
220
|
+
[`acquire`][qprogram_qblox.QbloxNamespace.acquire] attach a ``measure.fields.<name>`` token per
|
|
221
|
+
requested field and both target a bus, which is why the tokens are bus-level.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
_VENDOR: frozenset[str] = frozenset(
|
|
225
|
+
{
|
|
226
|
+
"vendor.qblox.acquire",
|
|
227
|
+
"vendor.qblox.set_markers",
|
|
228
|
+
"vendor.qblox.set_trigger",
|
|
229
|
+
"vendor.qblox.wait_trigger",
|
|
230
|
+
"vendor.qblox.set_acquisition_threshold",
|
|
231
|
+
"vendor.qblox.set_acquisition_rotation",
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
"""The ``vendor.qblox.*`` operation tokens.
|
|
235
|
+
|
|
236
|
+
Every operation this package ships carries a ``bus``, so all six route to the bus slot. A bus-less
|
|
237
|
+
vendor operation would need its token on the platform slot instead.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
QBLOX_DEFAULT_V1 = Profile(
|
|
242
|
+
name="qblox-default-v1",
|
|
243
|
+
version=(0, 1, 0),
|
|
244
|
+
extends=None,
|
|
245
|
+
capabilities=_BUS_OPS | _WAVEFORMS | _FIELDS | _VENDOR,
|
|
246
|
+
limits={"min_wait_duration_ns": 4},
|
|
247
|
+
predicates=(
|
|
248
|
+
_reject_arbitrary_sweep_at_wait_duration,
|
|
249
|
+
_drag_sigma_in_loop_is_host_only,
|
|
250
|
+
),
|
|
251
|
+
vendor_versions={"qblox": (0, 1, 0)},
|
|
252
|
+
)
|
|
253
|
+
"""The default Qblox bus-level capability profile.
|
|
254
|
+
|
|
255
|
+
Holds every token a qblox-driven bus accepts (`_BUS_OPS`, `_WAVEFORMS`, `_FIELDS`
|
|
256
|
+
and `_VENDOR`), the ``min_wait_duration_ns`` floor of the wait instruction, and the two
|
|
257
|
+
predicates above. It carries no ``block.*``, ``sweep.*`` or ``expr.*`` token: those route to the
|
|
258
|
+
platform slot, which a qblox platform fills from ``qprogram-base-v1``.
|
|
259
|
+
|
|
260
|
+
Reach it by name with
|
|
261
|
+
``CompilerCapabilities.from_profile("qblox-default-v1", limit_overrides=...)``, or extend it with a
|
|
262
|
+
profile of your own that declares ``extends="qblox-default-v1"`` and lists only what differs.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _register() -> None:
|
|
267
|
+
"""Idempotently register [`QBLOX_DEFAULT_V1`][qprogram_qblox.QBLOX_DEFAULT_V1] on the global profile registry."""
|
|
268
|
+
register_profile(QBLOX_DEFAULT_V1)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
__all__ = ["QBLOX_DEFAULT_V1"]
|
qprogram_qblox/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: qprogram-qblox
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Qblox vendor extensions for the QProgram pulse-level quantum programming DSL.
|
|
5
|
+
Project-URL: Homepage, https://github.com/qilimanjaro-tech/qprogram-qblox
|
|
6
|
+
Project-URL: Documentation, https://qilimanjaro-tech.github.io/qprogram-qblox/
|
|
7
|
+
Project-URL: Source, https://github.com/qilimanjaro-tech/qprogram-qblox
|
|
8
|
+
Project-URL: Issues, https://github.com/qilimanjaro-tech/qprogram-qblox/issues
|
|
9
|
+
Author-email: Qilimanjaro Quantum Tech <info@qilimanjaro.tech>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: dsl,pulse programming,qblox,qilimanjaro,quantum computing,quantum control
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering :: Quantum Computing
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.11
|
|
30
|
+
Requires-Dist: qprogram>=0.1.0
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# QProgram Qblox
|
|
34
|
+
|
|
35
|
+
[](https://github.com/qilimanjaro-tech/qprogram-qblox/actions/workflows/tests.yml)
|
|
36
|
+
[](https://github.com/qilimanjaro-tech/qprogram-qblox/actions/workflows/code_quality.yml)
|
|
37
|
+
[](https://www.python.org/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Qblox extensions for [QProgram](https://github.com/qilimanjaro-tech/qprogram), a Python DSL for
|
|
41
|
+
pulse-level quantum experiments.
|
|
42
|
+
|
|
43
|
+
The core DSL knows nothing about any instrument. This package teaches it about the Qblox cluster.
|
|
44
|
+
It adds six operations that the portable language does not cover, a capability profile that says
|
|
45
|
+
what QCM and QRM sequencers accept, and `.qp` serialization for everything it adds. Importing the
|
|
46
|
+
package is the whole activation step.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install qprogram-qblox
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The core `qprogram` package installs with it.
|
|
55
|
+
|
|
56
|
+
## A first program
|
|
57
|
+
|
|
58
|
+
Calibrate the acquisition rotation angle of a readout bus: sweep the angle, discriminate the
|
|
59
|
+
qubit state at each point, and average the outcome.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import math
|
|
63
|
+
|
|
64
|
+
import qprogram as qp
|
|
65
|
+
from qprogram.buses import BusSchema
|
|
66
|
+
from qprogram.waveforms import IQDrag, IQPair, Square
|
|
67
|
+
|
|
68
|
+
from qprogram_qblox import QProgram # qprogram.QProgram with a typed .qblox namespace
|
|
69
|
+
|
|
70
|
+
schema = BusSchema.transmon()
|
|
71
|
+
q = schema.q
|
|
72
|
+
|
|
73
|
+
program = QProgram(label="rotation_calibration", schema=schema)
|
|
74
|
+
angle = program.variable("angle", units="rad")
|
|
75
|
+
|
|
76
|
+
with program.average(shots=1000):
|
|
77
|
+
with program.sweep(angle).from_linspace(0.0, math.pi, 21):
|
|
78
|
+
program.qblox.set_acquisition_rotation(q[0].readout, angle)
|
|
79
|
+
program.play(q[0].drive, "pi_pulse")
|
|
80
|
+
program.sync()
|
|
81
|
+
m0 = program.qblox.acquire(q[0].readout, "weights", fields=(qp.MeasurementField.STATE,))
|
|
82
|
+
|
|
83
|
+
# Bind calibrated waveforms at the very end; the program itself only names them.
|
|
84
|
+
resolved = program.with_waveforms(
|
|
85
|
+
{
|
|
86
|
+
"pi_pulse": IQDrag(amplitude=0.5, duration=40, sigma=8, beta=0.1),
|
|
87
|
+
"weights": IQPair(Square(1.0, 2000), Square(1.0, 2000)),
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
result = qp.simulate(resolved)
|
|
92
|
+
population = result.get(m0, field=qp.MeasurementField.STATE)
|
|
93
|
+
print(population.dims, population.shape) # ('angle',) (21,)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`qp.simulate` is the reference software executor that ships with the core package. It runs the
|
|
97
|
+
qblox operations generically: `qblox.acquire` produces measurement records, the rest have no
|
|
98
|
+
effect on the simulated outcome. A Qblox platform is a drop-in for the same call and lowers each
|
|
99
|
+
operation onto real sequencers.
|
|
100
|
+
|
|
101
|
+
## What you get
|
|
102
|
+
|
|
103
|
+
- **Six operations under `program.qblox`.** `acquire` reads a bus without playing a readout
|
|
104
|
+
pulse. `set_markers` and `set_trigger` drive the digital outputs, `wait_trigger` waits on a
|
|
105
|
+
digital input. `set_acquisition_threshold` and `set_acquisition_rotation` configure
|
|
106
|
+
thresholded acquisition, and take effect off the sequencer as slow-control parameter writes
|
|
107
|
+
at execution time.
|
|
108
|
+
- **Typed or dynamic access.** `qprogram_qblox.QProgram` has `.qblox` typed for autocomplete,
|
|
109
|
+
`QbloxMixin` composes with other vendor mixins, and the plain `qprogram.QProgram` gets the
|
|
110
|
+
same namespace at runtime once this package is imported.
|
|
111
|
+
- **A capability profile.** `QBLOX_DEFAULT_V1` declares the operations, waveforms, measurement
|
|
112
|
+
fields, and limits of a qblox-driven bus, plus the two constraints the hardware imposes: an
|
|
113
|
+
arbitrary-valued sweep cannot drive a wait duration, and sweeping an `IQDrag` sigma forces its
|
|
114
|
+
loop to iterate host-side.
|
|
115
|
+
- **Round-tripping `.qp` files.** Every operation serializes as `qblox.<name> <args>` and reloads
|
|
116
|
+
to a structurally equal program. Files carry `require qblox 0.1`, which the parser checks
|
|
117
|
+
against the installed version.
|
|
118
|
+
- **Auto-activation on load.** `qprogram.load("file.qp")` imports this package on demand when the
|
|
119
|
+
file requires the `qblox` vendor, so a reader never has to know which extensions a file uses.
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
Full documentation, including the operation reference, the capability profile, and the generated
|
|
124
|
+
API reference, lives at <https://qilimanjaro-tech.github.io/qprogram-qblox/>.
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
The project uses [uv](https://docs.astral.sh/uv/).
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git clone https://github.com/qilimanjaro-tech/qprogram-qblox
|
|
132
|
+
cd qprogram-qblox
|
|
133
|
+
|
|
134
|
+
uv sync --group dev # create .venv and install the package plus dev tools
|
|
135
|
+
uv run pytest # run the test suite
|
|
136
|
+
uv run ruff check . # lint
|
|
137
|
+
uv run ruff format . # format
|
|
138
|
+
uv run ty check # type-check
|
|
139
|
+
uv run --group docs zensical serve # preview the documentation
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
Apache License 2.0 - see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
qprogram_qblox/__init__.py,sha256=g0LFTyI9OhT42BTPMhRy5GP8Fkfa_Me6vCFv3qveFgw,6532
|
|
2
|
+
qprogram_qblox/mixin.py,sha256=UHVrXkpXpu5yV0ihW1wDW9eJXLh9b_xSHVIGLahUsuM,2975
|
|
3
|
+
qprogram_qblox/namespace.py,sha256=U7jevfYu2LmEaQV2mhbXjAd_s8-fiGCYUZi0JbCNtsg,8515
|
|
4
|
+
qprogram_qblox/operations.py,sha256=DtWXkJ46WpKZp6eroxThUlkEbRftPopUOalMlKHQrkE,10424
|
|
5
|
+
qprogram_qblox/profiles.py,sha256=cfLjnKB-AeucFA-AFdrO72qckjpz83oJeHCcHwWNTGs,11160
|
|
6
|
+
qprogram_qblox/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
qprogram_qblox-0.1.0.dist-info/METADATA,sha256=IjJ2H-G2nGnPlg-HVAR8Xt39e4in5Aj2tFFxGOXEL5o,6443
|
|
8
|
+
qprogram_qblox-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
9
|
+
qprogram_qblox-0.1.0.dist-info/entry_points.txt,sha256=9-k7fH4cck1lXJ9j8j-aebXlzGtHyz3pFovFsEZ3jWU,42
|
|
10
|
+
qprogram_qblox-0.1.0.dist-info/licenses/LICENSE,sha256=SSd4NLHKeN_X9uu9A5TnY3vpro7w7I9ZduIIsjtIBlo,11354
|
|
11
|
+
qprogram_qblox-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2025 Qilimanjaro Quantum Tech
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|