qilisdk 0.1.5__py3-none-any.whl → 0.1.7__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.
- qilisdk/analog/__init__.py +1 -2
- qilisdk/analog/hamiltonian.py +4 -71
- qilisdk/analog/schedule.py +291 -313
- qilisdk/backends/backend.py +5 -1
- qilisdk/backends/cuda_backend.py +10 -6
- qilisdk/backends/qutip_backend.py +24 -32
- qilisdk/{common → core}/__init__.py +4 -0
- qilisdk/core/interpolator.py +406 -0
- qilisdk/{common → core}/model.py +7 -7
- qilisdk/core/parameterizable.py +131 -0
- qilisdk/{common → core}/qtensor.py +1 -1
- qilisdk/{common → core}/variables.py +192 -11
- qilisdk/cost_functions/cost_function.py +1 -1
- qilisdk/cost_functions/model_cost_function.py +5 -5
- qilisdk/cost_functions/observable_cost_function.py +2 -2
- qilisdk/digital/ansatz.py +0 -3
- qilisdk/digital/circuit.py +3 -2
- qilisdk/digital/circuit_transpiler.py +46 -0
- qilisdk/digital/circuit_transpiler_passes/__init__.py +18 -0
- qilisdk/digital/circuit_transpiler_passes/circuit_transpiler_pass.py +36 -0
- qilisdk/digital/circuit_transpiler_passes/decompose_multi_controlled_gates_pass.py +216 -0
- qilisdk/digital/circuit_transpiler_passes/numeric_helpers.py +82 -0
- qilisdk/digital/gates.py +15 -5
- qilisdk/{speqtrum/experiments → experiments}/__init__.py +13 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_functional.py +90 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_result.py +16 -0
- qilisdk/functionals/functional.py +2 -2
- qilisdk/functionals/functional_result.py +1 -1
- qilisdk/functionals/sampling.py +8 -1
- qilisdk/functionals/time_evolution.py +8 -4
- qilisdk/functionals/time_evolution_result.py +2 -2
- qilisdk/functionals/variational_program.py +58 -0
- qilisdk/optimizers/optimizer_result.py +1 -1
- qilisdk/speqtrum/__init__.py +2 -0
- qilisdk/speqtrum/speqtrum.py +537 -152
- qilisdk/speqtrum/speqtrum_models.py +258 -2
- qilisdk/utils/openfermion/__init__.py +38 -0
- qilisdk/{common/algorithm.py → utils/openfermion/__init__.pyi} +2 -3
- qilisdk/utils/openfermion/openfermion.py +45 -0
- qilisdk/utils/visualization/schedule_renderers.py +22 -9
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/METADATA +89 -39
- qilisdk-0.1.7.dist-info/RECORD +76 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/WHEEL +1 -1
- qilisdk/analog/linear_schedule.py +0 -118
- qilisdk/common/parameterizable.py +0 -75
- qilisdk-0.1.5.dist-info/RECORD +0 -69
- /qilisdk/{common → core}/exceptions.py +0 -0
- /qilisdk/{common → core}/result.py +0 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/licenses/LICENCE +0 -0
qilisdk/analog/schedule.py
CHANGED
|
@@ -13,16 +13,24 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
|
|
18
|
-
from
|
|
16
|
+
from copy import copy
|
|
17
|
+
from itertools import chain
|
|
18
|
+
from typing import Any, Mapping, overload
|
|
19
19
|
|
|
20
20
|
from qilisdk.analog.hamiltonian import Hamiltonian
|
|
21
|
-
from qilisdk.
|
|
22
|
-
from qilisdk.
|
|
21
|
+
from qilisdk.core.interpolator import Interpolation, Interpolator, TimeDict
|
|
22
|
+
from qilisdk.core.parameterizable import Parameterizable
|
|
23
|
+
from qilisdk.core.variables import BaseVariable, ComparisonTerm, Domain, Parameter, Term
|
|
23
24
|
from qilisdk.utils.visualization import ScheduleStyle
|
|
24
25
|
from qilisdk.yaml import yaml
|
|
25
26
|
|
|
27
|
+
_TIME_PARAMETER_NAME = "t"
|
|
28
|
+
PARAMETERIZED_NUMBER = float | Parameter | Term
|
|
29
|
+
|
|
30
|
+
# type aliases just to keep this short
|
|
31
|
+
CoeffDict = dict[str, TimeDict]
|
|
32
|
+
InterpDict = dict[str, "Interpolator"]
|
|
33
|
+
|
|
26
34
|
|
|
27
35
|
@yaml.register_class
|
|
28
36
|
class Schedule(Parameterizable):
|
|
@@ -30,9 +38,8 @@ class Schedule(Parameterizable):
|
|
|
30
38
|
Builds a set of time-dependent coefficients applied to a collection of Hamiltonians.
|
|
31
39
|
|
|
32
40
|
A Schedule defines the evolution of a system by associating time steps with a set
|
|
33
|
-
of Hamiltonian coefficients.
|
|
34
|
-
|
|
35
|
-
at discrete time steps.
|
|
41
|
+
of Hamiltonian coefficients. Coefficients can be provided directly, defined as
|
|
42
|
+
functions of time, or specified over time intervals and interpolated (step or linear).
|
|
36
43
|
|
|
37
44
|
Example:
|
|
38
45
|
.. code-block:: python
|
|
@@ -40,82 +47,83 @@ class Schedule(Parameterizable):
|
|
|
40
47
|
import numpy as np
|
|
41
48
|
from qilisdk.analog import Schedule, X, Z
|
|
42
49
|
|
|
43
|
-
T, dt = 10, 1
|
|
44
|
-
steps = np.linspace(0, T, int(T / dt))
|
|
50
|
+
T, dt = 10.0, 1.0
|
|
45
51
|
|
|
46
52
|
h1 = X(0) + X(1) + X(2)
|
|
47
53
|
h2 = -Z(0) - Z(1) - 2 * Z(2) + 3 * Z(0) * Z(1)
|
|
48
54
|
|
|
49
55
|
schedule = Schedule(
|
|
50
|
-
T=T,
|
|
51
56
|
dt=dt,
|
|
52
57
|
hamiltonians={"driver": h1, "problem": h2},
|
|
53
|
-
|
|
58
|
+
coefficients={
|
|
59
|
+
"driver": {(0, T): lambda t: 1 - t / T},
|
|
60
|
+
"problem": {(0, T): lambda t: t / T},
|
|
61
|
+
},
|
|
54
62
|
)
|
|
55
63
|
schedule.draw()
|
|
56
64
|
"""
|
|
57
65
|
|
|
58
66
|
def __init__(
|
|
59
67
|
self,
|
|
60
|
-
T: float,
|
|
61
|
-
dt: float = 1,
|
|
62
68
|
hamiltonians: dict[str, Hamiltonian] | None = None,
|
|
63
|
-
|
|
69
|
+
coefficients: InterpDict | CoeffDict | None = None,
|
|
70
|
+
dt: float = 0.1,
|
|
71
|
+
total_time: PARAMETERIZED_NUMBER | None = None,
|
|
72
|
+
interpolation: Interpolation = Interpolation.LINEAR,
|
|
73
|
+
**kwargs: Any,
|
|
64
74
|
) -> None:
|
|
65
|
-
"""
|
|
75
|
+
"""Create a Schedule that assigns time-dependent coefficients to Hamiltonians.
|
|
76
|
+
|
|
66
77
|
Args:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
coefficients. Defaults to {0: {}} if None.
|
|
78
|
+
hamiltonians (dict[str, Hamiltonian] | None): Mapping of labels to Hamiltonian objects. If omitted, an empty schedule is created.
|
|
79
|
+
coefficients (InterpDict | CoeffDict | None): Per-Hamiltonian time definitions. Keys are time points or intervals; values are coefficients or callables. If an :class:`Interpolator` is supplied, it is used directly.
|
|
80
|
+
dt (float): Time resolution used for sampling callable/interval definitions and plotting. Must be positive.
|
|
81
|
+
total_time (float | Parameter | Term | None): Optional maximum time that rescales all defined time points proportionally.
|
|
82
|
+
interpolation (Interpolation): How to interpolate between provided time points (``LINEAR`` or ``STEP``).
|
|
83
|
+
**kwargs: Passed to :class:`Interpolator` construction when coefficients are provided as dictionaries.
|
|
74
84
|
|
|
75
85
|
Raises:
|
|
76
|
-
ValueError:
|
|
86
|
+
ValueError: if the coefficients reference an undefined hamiltonian.
|
|
77
87
|
"""
|
|
88
|
+
# THIS is the only runtime implementation
|
|
89
|
+
super(Schedule, self).__init__()
|
|
90
|
+
self._hamiltonians = hamiltonians if hamiltonians is not None else {}
|
|
91
|
+
self._coefficients: dict[str, Interpolator] = {}
|
|
92
|
+
self._interpolation = None
|
|
93
|
+
self._parameters: dict[str, Parameter] = {}
|
|
94
|
+
self._current_time: Parameter = Parameter(_TIME_PARAMETER_NAME, 0, Domain.REAL)
|
|
95
|
+
self.iter_time_step = 0
|
|
96
|
+
self._max_time: PARAMETERIZED_NUMBER | None = None
|
|
78
97
|
if dt <= 0:
|
|
79
98
|
raise ValueError("dt must be greater than zero.")
|
|
80
|
-
self._hamiltonians: dict[str, Hamiltonian] = hamiltonians if hamiltonians is not None else {}
|
|
81
|
-
self._schedule: dict[int, dict[str, float | Term | Parameter]] = schedule if schedule is not None else {0: {}}
|
|
82
|
-
self._parameters: dict[str, Parameter] = {}
|
|
83
|
-
self._T = T
|
|
84
99
|
self._dt = dt
|
|
85
|
-
self.iter_time_step = 0
|
|
86
|
-
self._nqubits = 0
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
coefficients = coefficients or {}
|
|
102
|
+
|
|
103
|
+
if coefficients.keys() > self._hamiltonians.keys():
|
|
104
|
+
missing = coefficients.keys() - self._hamiltonians.keys()
|
|
105
|
+
raise ValueError(f"Missing keys in hamiltonians: {missing}")
|
|
106
|
+
|
|
107
|
+
for ham, hamiltonian in self._hamiltonians.items():
|
|
108
|
+
# Gather Hamiltonian parameters and nqubits
|
|
109
|
+
for param in hamiltonian.parameters.values():
|
|
91
110
|
self._parameters[param.label] = param
|
|
92
111
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
# Build hamiltonian schedule
|
|
113
|
+
if ham not in coefficients:
|
|
114
|
+
self._coefficients[ham] = Interpolator({0: 1}, interpolation=interpolation, nsamples=int(1 / dt))
|
|
115
|
+
continue
|
|
116
|
+
coeff = copy(coefficients[ham])
|
|
117
|
+
if isinstance(coeff, Interpolator):
|
|
118
|
+
self._coefficients[ham] = coeff
|
|
119
|
+
elif isinstance(coeff, dict):
|
|
120
|
+
self._coefficients[ham] = Interpolator(coeff, interpolation, nsamples=int(1 / dt), **kwargs)
|
|
99
121
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for coeff in time_step.values():
|
|
106
|
-
if isinstance(coeff, Term):
|
|
107
|
-
for v in coeff.variables():
|
|
108
|
-
if not isinstance(v, Parameter):
|
|
109
|
-
raise ValueError(
|
|
110
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
111
|
-
)
|
|
112
|
-
self._parameters[v.label] = v
|
|
113
|
-
if isinstance(coeff, BaseVariable):
|
|
114
|
-
if not isinstance(coeff, Parameter):
|
|
115
|
-
raise ValueError(
|
|
116
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
117
|
-
)
|
|
118
|
-
self._parameters[coeff.label] = coeff
|
|
122
|
+
for p_name, p_value in self._coefficients[ham].parameters.items():
|
|
123
|
+
self._parameters[p_name] = p_value
|
|
124
|
+
|
|
125
|
+
if total_time is not None:
|
|
126
|
+
self.scale_max_time(total_time)
|
|
119
127
|
|
|
120
128
|
@property
|
|
121
129
|
def hamiltonians(self) -> dict[str, Hamiltonian]:
|
|
@@ -128,325 +136,293 @@ class Schedule(Parameterizable):
|
|
|
128
136
|
return self._hamiltonians
|
|
129
137
|
|
|
130
138
|
@property
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
Return the evaluated schedule of Hamiltonian coefficients.
|
|
139
|
+
def coefficients_dict(self) -> dict[str, dict[PARAMETERIZED_NUMBER, PARAMETERIZED_NUMBER]]:
|
|
140
|
+
return {ham: self._coefficients[ham].coefficients_dict for ham in self._hamiltonians}
|
|
134
141
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
out_dict = {}
|
|
139
|
-
for k, v in self._schedule.items():
|
|
140
|
-
out_dict[k] = {
|
|
141
|
-
ham: (
|
|
142
|
-
coeff
|
|
143
|
-
if isinstance(coeff, Number)
|
|
144
|
-
else (coeff.evaluate() if isinstance(coeff, Parameter) else coeff.evaluate({}))
|
|
145
|
-
)
|
|
146
|
-
for ham, coeff in v.items()
|
|
147
|
-
}
|
|
148
|
-
return dict(sorted(out_dict.items()))
|
|
142
|
+
@property
|
|
143
|
+
def coefficients(self) -> dict[str, Interpolator]:
|
|
144
|
+
return {ham: self._coefficients[ham] for ham in self._hamiltonians}
|
|
149
145
|
|
|
150
146
|
@property
|
|
151
147
|
def T(self) -> float:
|
|
152
148
|
"""Total annealing time of the schedule."""
|
|
153
|
-
return self.
|
|
149
|
+
return max(self.tlist)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def tlist(self) -> list[float]:
|
|
153
|
+
_tlist: set[float] = set()
|
|
154
|
+
if len(self._hamiltonians) == 0:
|
|
155
|
+
tlist = [0.0]
|
|
156
|
+
else:
|
|
157
|
+
for ham in self._hamiltonians:
|
|
158
|
+
_tlist.update(self._coefficients[ham].fixed_tlist)
|
|
159
|
+
tlist = list(_tlist)
|
|
160
|
+
if self._max_time is not None:
|
|
161
|
+
max_t = max(tlist) or 1
|
|
162
|
+
max_t = max_t if max_t != 0 else 1
|
|
163
|
+
T = self._get_value(self._max_time)
|
|
164
|
+
tlist = [t * T / max_t for t in tlist]
|
|
165
|
+
if T not in tlist:
|
|
166
|
+
tlist.append(T)
|
|
167
|
+
return sorted(tlist)
|
|
154
168
|
|
|
155
169
|
@property
|
|
156
170
|
def dt(self) -> float:
|
|
157
|
-
"""Duration of a single time step in the annealing grid."""
|
|
158
171
|
return self._dt
|
|
159
172
|
|
|
173
|
+
def set_dt(self, dt: float) -> None:
|
|
174
|
+
if not isinstance(dt, float):
|
|
175
|
+
raise ValueError(f"dt is only allowed to be a float but {type(dt)} was provided")
|
|
176
|
+
self._dt = dt
|
|
177
|
+
|
|
160
178
|
@property
|
|
161
179
|
def nqubits(self) -> int:
|
|
162
180
|
"""Maximum number of qubits affected by Hamiltonians contained in the schedule."""
|
|
163
|
-
|
|
181
|
+
if len(self._hamiltonians) == 0:
|
|
182
|
+
return 0
|
|
183
|
+
return max(self._hamiltonians.values(), key=lambda v: v.nqubits).nqubits
|
|
164
184
|
|
|
165
185
|
@property
|
|
166
186
|
def nparameters(self) -> int:
|
|
167
187
|
"""Number of symbolic parameters introduced by the Hamiltonians or coefficients."""
|
|
168
188
|
return len(self._parameters)
|
|
169
189
|
|
|
170
|
-
def
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
def _get_value(self, value: PARAMETERIZED_NUMBER | complex, t: float | None = None) -> float:
|
|
191
|
+
if isinstance(value, (int, float)):
|
|
192
|
+
return value
|
|
193
|
+
if isinstance(value, complex):
|
|
194
|
+
return value.real
|
|
195
|
+
if isinstance(value, Parameter):
|
|
196
|
+
if value.label == _TIME_PARAMETER_NAME:
|
|
197
|
+
if t is None:
|
|
198
|
+
raise ValueError("Can't evaluate Parameter because time is not provided.")
|
|
199
|
+
value.set_value(t)
|
|
200
|
+
return float(value.evaluate())
|
|
201
|
+
if isinstance(value, Term):
|
|
202
|
+
ctx: Mapping[BaseVariable, list[int] | int | float] = {self._current_time: t} if t is not None else {}
|
|
203
|
+
aux = value.evaluate(ctx)
|
|
204
|
+
|
|
205
|
+
return aux.real if isinstance(aux, complex) else float(aux)
|
|
206
|
+
raise ValueError(f"Invalid value of type {type(value)} is being evaluated.")
|
|
207
|
+
|
|
208
|
+
def _extract_parameters(self, element: PARAMETERIZED_NUMBER) -> None:
|
|
209
|
+
if isinstance(element, Parameter):
|
|
210
|
+
self._parameters[element.label] = element
|
|
211
|
+
elif isinstance(element, Term):
|
|
212
|
+
if not element.is_parameterized_term():
|
|
213
|
+
raise ValueError(
|
|
214
|
+
f"Tlist can only contain parameters and no variables, but the term {element} contains objects other than parameters."
|
|
215
|
+
)
|
|
216
|
+
for p in element.variables():
|
|
217
|
+
if isinstance(p, Parameter):
|
|
218
|
+
self._parameters[p.label] = p
|
|
196
219
|
|
|
197
|
-
def set_parameters(self,
|
|
198
|
-
"""
|
|
199
|
-
Update a subset of parameters by label.
|
|
220
|
+
def set_parameters(self, parameters: dict[str, int | float]) -> None:
|
|
221
|
+
"""Update parameter values across all Hamiltonian coefficient interpolators.
|
|
200
222
|
|
|
201
223
|
Args:
|
|
202
|
-
|
|
224
|
+
parameters (dict[str, int | float]): Mapping from parameter labels to numeric values.
|
|
203
225
|
|
|
204
226
|
Raises:
|
|
205
227
|
ValueError: If an unknown parameter label is provided.
|
|
206
228
|
"""
|
|
207
|
-
for label
|
|
229
|
+
for label in parameters:
|
|
208
230
|
if label not in self._parameters:
|
|
209
231
|
raise ValueError(f"Parameter {label} is not defined in this Schedule.")
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
232
|
+
for h in self._hamiltonians:
|
|
233
|
+
self._coefficients[h].set_parameters(
|
|
234
|
+
{p: parameters[p] for p in self._coefficients[h].get_parameter_names() if p in parameters}
|
|
235
|
+
)
|
|
236
|
+
super().set_parameters(parameters)
|
|
215
237
|
|
|
216
238
|
def set_parameter_bounds(self, ranges: dict[str, tuple[float, float]]) -> None:
|
|
217
|
-
"""
|
|
218
|
-
Update the bounds of existing parameters.
|
|
239
|
+
"""Propagate bound updates to all interpolators and cached parameters.
|
|
219
240
|
|
|
220
241
|
Args:
|
|
221
|
-
ranges (dict[str, tuple[float, float]]): Mapping
|
|
242
|
+
ranges (dict[str, tuple[float, float]]): Mapping of parameter label to ``(lower, upper)`` bounds.
|
|
222
243
|
|
|
223
244
|
Raises:
|
|
224
245
|
ValueError: If an unknown parameter label is provided.
|
|
225
246
|
"""
|
|
226
|
-
for label
|
|
247
|
+
for label in ranges:
|
|
227
248
|
if label not in self._parameters:
|
|
228
249
|
raise ValueError(
|
|
229
250
|
f"The provided parameter label {label} is not defined in the list of parameters in this object."
|
|
230
251
|
)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"""
|
|
237
|
-
Add a Hamiltonian to the schedule with an optional coefficient schedule function.
|
|
252
|
+
for h in self._hamiltonians:
|
|
253
|
+
self._coefficients[h].set_parameter_bounds(
|
|
254
|
+
{p: ranges[p] for p in self._coefficients[h].get_parameter_names() if p in ranges}
|
|
255
|
+
)
|
|
256
|
+
super().set_parameter_bounds(ranges)
|
|
238
257
|
|
|
239
|
-
|
|
240
|
-
the
|
|
258
|
+
def get_constraints(self) -> list[ComparisonTerm]:
|
|
259
|
+
"""Return the set of parameter constraints arising from all interpolators."""
|
|
260
|
+
const_lists = [coeff.get_constraints() for coeff in self._coefficients.values()]
|
|
261
|
+
combined_list = chain.from_iterable(const_lists)
|
|
262
|
+
return list(set(combined_list))
|
|
241
263
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
schedule (Callable, optional): A function that returns the coefficient of the Hamiltonian at time t.
|
|
246
|
-
It should accept time (and any additional keyword arguments) and return a float.
|
|
247
|
-
**kwargs (dict): Additional keyword arguments to pass to the schedule function.
|
|
264
|
+
def scale_max_time(self, max_time: PARAMETERIZED_NUMBER) -> None: # FIX!
|
|
265
|
+
"""
|
|
266
|
+
Rescale the schedule to a new maximum time while keeping relative points fixed.
|
|
248
267
|
|
|
249
268
|
Raises:
|
|
250
|
-
ValueError:
|
|
269
|
+
ValueError: If the max time provided is zero.
|
|
251
270
|
"""
|
|
271
|
+
if self._get_value(max_time) == 0:
|
|
272
|
+
raise ValueError("Setting the total time to zero.")
|
|
273
|
+
self._extract_parameters(max_time)
|
|
274
|
+
self._max_time = max_time
|
|
275
|
+
for ham in self._hamiltonians:
|
|
276
|
+
self._coefficients[ham].set_max_time(max_time)
|
|
277
|
+
|
|
278
|
+
def _add_hamiltonian_from_dict(
|
|
279
|
+
self,
|
|
280
|
+
label: str,
|
|
281
|
+
hamiltonian: Hamiltonian,
|
|
282
|
+
coefficients: TimeDict,
|
|
283
|
+
interpolation: Interpolation = Interpolation.LINEAR,
|
|
284
|
+
**kwargs: Any,
|
|
285
|
+
) -> None:
|
|
252
286
|
if label in self._hamiltonians:
|
|
253
|
-
|
|
254
|
-
(f"label {label} is already assigned to a hamiltonian, " + "updating schedule of existing hamiltonian.")
|
|
255
|
-
)
|
|
287
|
+
raise ValueError(f"Can't add Hamiltonian because label {label} is already associated with a Hamiltonian.")
|
|
256
288
|
self._hamiltonians[label] = hamiltonian
|
|
257
|
-
self.
|
|
258
|
-
self._nqubits = max(self._nqubits, hamiltonian.nqubits)
|
|
259
|
-
for l, param in hamiltonian.parameters.items():
|
|
260
|
-
self._parameters[param.label] = param
|
|
261
|
-
|
|
262
|
-
if schedule is not None:
|
|
263
|
-
for t in range(int(self.T / self.dt)):
|
|
264
|
-
time_step = schedule(t, **kwargs)
|
|
265
|
-
if isinstance(time_step, Term):
|
|
266
|
-
for v in time_step.variables():
|
|
267
|
-
if not isinstance(v, Parameter):
|
|
268
|
-
raise ValueError(
|
|
269
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
270
|
-
)
|
|
271
|
-
self._parameters[v.label] = v
|
|
272
|
-
elif isinstance(time_step, BaseVariable):
|
|
273
|
-
if not isinstance(time_step, Parameter):
|
|
274
|
-
raise ValueError(
|
|
275
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
276
|
-
)
|
|
277
|
-
self._parameters[time_step.label] = time_step
|
|
278
|
-
self.update_hamiltonian_coefficient_at_time_step(t, label, time_step)
|
|
279
|
-
|
|
280
|
-
def add_schedule_step(
|
|
281
|
-
self, time_step: int, hamiltonian_coefficient_list: dict[str, float | Term | Parameter]
|
|
282
|
-
) -> None:
|
|
283
|
-
"""
|
|
284
|
-
Add or update a schedule step with specified Hamiltonian coefficients.
|
|
289
|
+
self._coefficients[label] = Interpolator(coefficients, interpolation, nsamples=int(1 / self.dt), **kwargs)
|
|
285
290
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
The actual time is computed as dt * time_step.
|
|
289
|
-
hamiltonian_coefficient_list (dict[str, float | Term | Parameter]): Mapping from Hamiltonian labels to coefficients
|
|
290
|
-
(numeric or symbolic) at this time step.
|
|
291
|
-
If a Hamiltonian is not included in the dictionary, it is assumed its coefficient remains unchanged.
|
|
291
|
+
for p_name, p_value in self._coefficients[label].parameters.items():
|
|
292
|
+
self._parameters[p_name] = p_value
|
|
292
293
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
"""
|
|
296
|
-
if time_step in self._schedule:
|
|
297
|
-
logger.warning(
|
|
298
|
-
f"time step {time_step} is already defined in the schedule, the values are going to be overwritten.",
|
|
299
|
-
)
|
|
300
|
-
for key, coeff in hamiltonian_coefficient_list.items():
|
|
301
|
-
if key not in self._hamiltonians:
|
|
302
|
-
raise ValueError(f"trying to reference a hamiltonian {key} that is not defined in this schedule.")
|
|
303
|
-
if isinstance(coeff, Term):
|
|
304
|
-
for v in coeff.variables():
|
|
305
|
-
if not isinstance(v, Parameter):
|
|
306
|
-
raise ValueError(
|
|
307
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
308
|
-
)
|
|
309
|
-
self._parameters[v.label] = v
|
|
310
|
-
if isinstance(coeff, BaseVariable):
|
|
311
|
-
if not isinstance(coeff, Parameter):
|
|
312
|
-
raise ValueError(
|
|
313
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
314
|
-
)
|
|
315
|
-
self._parameters[coeff.label] = coeff
|
|
316
|
-
self._schedule[time_step] = hamiltonian_coefficient_list
|
|
317
|
-
|
|
318
|
-
def update_hamiltonian_coefficient_at_time_step(
|
|
319
|
-
self, time_step: int, hamiltonian_label: str, new_coefficient: float | Term | Parameter
|
|
294
|
+
def _add_hamiltonian_from_interpolator(
|
|
295
|
+
self, label: str, hamiltonian: Hamiltonian, coefficients: Interpolator
|
|
320
296
|
) -> None:
|
|
321
|
-
|
|
322
|
-
|
|
297
|
+
if label in self._hamiltonians:
|
|
298
|
+
raise ValueError(f"Can't add Hamiltonian because label {label} is already associated with a Hamiltonian.")
|
|
299
|
+
self._hamiltonians[label] = hamiltonian
|
|
300
|
+
self._coefficients[label] = coefficients
|
|
323
301
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
hamiltonian_label (str): The label of the Hamiltonian to update.
|
|
327
|
-
new_coefficient (float | Term | Parameter): The new coefficient value or symbolic expression.
|
|
302
|
+
for p_name, p_value in self._coefficients[label].parameters.items():
|
|
303
|
+
self._parameters[p_name] = p_value
|
|
328
304
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
self._schedule[time_step][hamiltonian_label] = new_coefficient
|
|
338
|
-
|
|
339
|
-
if isinstance(new_coefficient, Term):
|
|
340
|
-
for v in new_coefficient.variables():
|
|
341
|
-
if not isinstance(v, Parameter):
|
|
342
|
-
raise ValueError(
|
|
343
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
344
|
-
)
|
|
345
|
-
self._parameters[v.label] = v
|
|
346
|
-
if isinstance(new_coefficient, BaseVariable):
|
|
347
|
-
if not isinstance(new_coefficient, Parameter):
|
|
348
|
-
raise ValueError(
|
|
349
|
-
f"The schedule can only contain Parameters, but a generic variable was provided ({time_step})"
|
|
350
|
-
)
|
|
351
|
-
self._parameters[new_coefficient.label] = new_coefficient
|
|
352
|
-
|
|
353
|
-
def __getitem__(self, time_step: int) -> Hamiltonian:
|
|
354
|
-
"""
|
|
355
|
-
Retrieve the effective Hamiltonian at a given time step.
|
|
305
|
+
@overload
|
|
306
|
+
def add_hamiltonian(
|
|
307
|
+
self,
|
|
308
|
+
label: str,
|
|
309
|
+
hamiltonian: Hamiltonian,
|
|
310
|
+
coefficients: TimeDict,
|
|
311
|
+
**kwargs: Any,
|
|
312
|
+
) -> None: ...
|
|
356
313
|
|
|
357
|
-
|
|
358
|
-
|
|
314
|
+
@overload
|
|
315
|
+
def add_hamiltonian(
|
|
316
|
+
self,
|
|
317
|
+
label: str,
|
|
318
|
+
hamiltonian: Hamiltonian,
|
|
319
|
+
coefficients: Interpolator,
|
|
320
|
+
**kwargs: Any,
|
|
321
|
+
) -> None: ...
|
|
359
322
|
|
|
360
|
-
|
|
361
|
-
|
|
323
|
+
def add_hamiltonian(
|
|
324
|
+
self,
|
|
325
|
+
label: str,
|
|
326
|
+
hamiltonian: Hamiltonian,
|
|
327
|
+
coefficients: Interpolator | TimeDict,
|
|
328
|
+
interpolation: Interpolation = Interpolation.LINEAR,
|
|
329
|
+
**kwargs: Any,
|
|
330
|
+
) -> None:
|
|
331
|
+
if not isinstance(hamiltonian, Hamiltonian):
|
|
332
|
+
raise ValueError(f"Expecting a Hamiltonian object but received {type(hamiltonian)} instead.")
|
|
362
333
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
read_labels = []
|
|
368
|
-
|
|
369
|
-
if time_step not in self._schedule:
|
|
370
|
-
while time_step > 0:
|
|
371
|
-
time_step -= 1
|
|
372
|
-
if time_step in self._schedule:
|
|
373
|
-
for ham_label in self._schedule[time_step]:
|
|
374
|
-
aux = self._schedule[time_step][ham_label]
|
|
375
|
-
coeff = (
|
|
376
|
-
aux.evaluate({})
|
|
377
|
-
if isinstance(aux, Term)
|
|
378
|
-
else (aux.evaluate() if isinstance(aux, Parameter) else aux)
|
|
379
|
-
)
|
|
380
|
-
ham += coeff * self._hamiltonians[ham_label]
|
|
381
|
-
read_labels.append(ham_label)
|
|
382
|
-
break
|
|
334
|
+
if isinstance(coefficients, Interpolator):
|
|
335
|
+
self._add_hamiltonian_from_interpolator(label, hamiltonian, coefficients)
|
|
336
|
+
elif isinstance(coefficients, dict):
|
|
337
|
+
self._add_hamiltonian_from_dict(label, hamiltonian, coefficients, interpolation, **kwargs)
|
|
383
338
|
else:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
aux.evaluate({})
|
|
388
|
-
if isinstance(aux, Term)
|
|
389
|
-
else (aux.evaluate() if isinstance(aux, Parameter) else aux)
|
|
390
|
-
)
|
|
391
|
-
ham += coeff * self._hamiltonians[ham_label]
|
|
392
|
-
read_labels.append(ham_label)
|
|
393
|
-
if len(read_labels) < len(self._hamiltonians):
|
|
394
|
-
all_labels = self._hamiltonians.keys()
|
|
395
|
-
remaining_labels = list(filter(lambda x: x not in read_labels, all_labels))
|
|
396
|
-
for label in remaining_labels:
|
|
397
|
-
current_time = time_step
|
|
398
|
-
while current_time > 0:
|
|
399
|
-
current_time -= 1
|
|
400
|
-
if current_time in self._schedule and label in self._schedule[current_time]:
|
|
401
|
-
aux = self._schedule[current_time][label]
|
|
402
|
-
coeff = (
|
|
403
|
-
aux.evaluate({})
|
|
404
|
-
if isinstance(aux, Term)
|
|
405
|
-
else (aux.evaluate() if isinstance(aux, Parameter) else aux)
|
|
406
|
-
)
|
|
407
|
-
ham += coeff * self._hamiltonians[label]
|
|
408
|
-
break
|
|
409
|
-
return ham.get_static_hamiltonian()
|
|
410
|
-
|
|
411
|
-
def get_coefficient(self, time_step: float, hamiltonian_key: str) -> Number:
|
|
412
|
-
"""
|
|
413
|
-
Retrieve the coefficient of a specified Hamiltonian at a given time.
|
|
339
|
+
raise ValueError("Unsupported type of coefficient.")
|
|
340
|
+
if self._max_time is not None:
|
|
341
|
+
self._coefficients[label].set_max_time(self._max_time)
|
|
414
342
|
|
|
415
|
-
|
|
416
|
-
|
|
343
|
+
def _update_hamiltonian_from_dict(
|
|
344
|
+
self,
|
|
345
|
+
label: str,
|
|
346
|
+
new_coefficients: TimeDict | None = None,
|
|
347
|
+
interpolation: Interpolation = Interpolation.LINEAR,
|
|
348
|
+
**kwargs: Any,
|
|
349
|
+
) -> None:
|
|
350
|
+
if new_coefficients is not None:
|
|
351
|
+
self._coefficients[label] = Interpolator(
|
|
352
|
+
new_coefficients, interpolation, nsamples=int(1 / self.dt), **kwargs
|
|
353
|
+
) # TODO (ameer): allow for partial updates of the coefficients
|
|
417
354
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
hamiltonian_key (str): The label of the Hamiltonian.
|
|
355
|
+
for p_name, p_value in self._coefficients[label].parameters.items():
|
|
356
|
+
self._parameters[p_name] = p_value
|
|
421
357
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
358
|
+
def _update_hamiltonian_from_interpolator(self, label: str, new_coefficients: Interpolator | None = None) -> None:
|
|
359
|
+
if new_coefficients is not None:
|
|
360
|
+
self._coefficients[label] = new_coefficients
|
|
361
|
+
|
|
362
|
+
for p_name, p_value in self._coefficients[label].parameters.items():
|
|
363
|
+
self._parameters[p_name] = p_value
|
|
364
|
+
|
|
365
|
+
@overload
|
|
366
|
+
def update_hamiltonian(
|
|
367
|
+
self,
|
|
368
|
+
label: str,
|
|
369
|
+
new_hamiltonian: Hamiltonian | None = None,
|
|
370
|
+
new_coefficients: TimeDict | None = None,
|
|
371
|
+
interpolation: Interpolation = Interpolation.LINEAR,
|
|
372
|
+
**kwargs: Any,
|
|
373
|
+
) -> None: ...
|
|
374
|
+
|
|
375
|
+
@overload
|
|
376
|
+
def update_hamiltonian(
|
|
377
|
+
self,
|
|
378
|
+
label: str,
|
|
379
|
+
new_hamiltonian: Hamiltonian | None = None,
|
|
380
|
+
new_coefficients: Interpolator | None = None,
|
|
381
|
+
**kwargs: Any,
|
|
382
|
+
) -> None: ...
|
|
427
383
|
|
|
428
|
-
def
|
|
384
|
+
def update_hamiltonian(
|
|
385
|
+
self,
|
|
386
|
+
label: str,
|
|
387
|
+
new_hamiltonian: Hamiltonian | None = None,
|
|
388
|
+
new_coefficients: Interpolator | TimeDict | None = None,
|
|
389
|
+
interpolation: Interpolation = Interpolation.LINEAR,
|
|
390
|
+
**kwargs: Any,
|
|
391
|
+
) -> None:
|
|
392
|
+
if label not in self._hamiltonians:
|
|
393
|
+
raise ValueError(f"Can't update unknown hamiltonian {label}. Did you mean `add_hamiltonian`?")
|
|
394
|
+
if new_hamiltonian is not None:
|
|
395
|
+
if not isinstance(new_hamiltonian, Hamiltonian):
|
|
396
|
+
raise ValueError(f"Expecting a Hamiltonian object but received {type(new_hamiltonian)} instead.")
|
|
397
|
+
self._hamiltonians[label] = new_hamiltonian
|
|
398
|
+
if new_coefficients is not None:
|
|
399
|
+
if isinstance(new_coefficients, Interpolator):
|
|
400
|
+
self._update_hamiltonian_from_interpolator(label, new_coefficients)
|
|
401
|
+
elif isinstance(new_coefficients, dict):
|
|
402
|
+
self._update_hamiltonian_from_dict(label, new_coefficients, interpolation, **kwargs)
|
|
403
|
+
else:
|
|
404
|
+
raise ValueError("Unsupported type of coefficient.")
|
|
405
|
+
|
|
406
|
+
if self._max_time is not None:
|
|
407
|
+
self._coefficients[label].set_max_time(self._max_time)
|
|
408
|
+
|
|
409
|
+
def __getitem__(self, time_step: float) -> Hamiltonian:
|
|
429
410
|
"""
|
|
430
|
-
Retrieve the
|
|
431
|
-
present in the expression they will be printed in the expression.
|
|
411
|
+
Retrieve the effective Hamiltonian at a given time step.
|
|
432
412
|
|
|
433
|
-
|
|
434
|
-
|
|
413
|
+
The effective Hamiltonian is computed by summing the contributions of all Hamiltonians,
|
|
414
|
+
using the latest defined coefficients at or before the given time step.
|
|
435
415
|
|
|
436
416
|
Args:
|
|
437
|
-
time_step (float):
|
|
438
|
-
hamiltonian_key (str): The label of the Hamiltonian.
|
|
417
|
+
time_step (float): Physical time (same units as the schedule definition) at which to sample.
|
|
439
418
|
|
|
440
419
|
Returns:
|
|
441
|
-
|
|
420
|
+
Hamiltonian: The effective Hamiltonian at the specified time step with coefficients evaluated to numbers.
|
|
442
421
|
"""
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return val
|
|
448
|
-
time_idx -= 1
|
|
449
|
-
return 0
|
|
422
|
+
final_ham = Hamiltonian()
|
|
423
|
+
for label, ham in self._hamiltonians.items():
|
|
424
|
+
final_ham += ham * self._coefficients[label][time_step]
|
|
425
|
+
return final_ham
|
|
450
426
|
|
|
451
427
|
def __len__(self) -> int:
|
|
452
428
|
"""
|
|
@@ -455,7 +431,7 @@ class Schedule(Parameterizable):
|
|
|
455
431
|
Returns:
|
|
456
432
|
int: The number of time steps, calculated as T / dt.
|
|
457
433
|
"""
|
|
458
|
-
return
|
|
434
|
+
return len(self.tlist)
|
|
459
435
|
|
|
460
436
|
def __iter__(self) -> Schedule:
|
|
461
437
|
"""
|
|
@@ -477,8 +453,8 @@ class Schedule(Parameterizable):
|
|
|
477
453
|
Raises:
|
|
478
454
|
StopIteration: When the iteration has reached beyond the total number of time steps.
|
|
479
455
|
"""
|
|
480
|
-
if self.iter_time_step
|
|
481
|
-
result = self[self.iter_time_step]
|
|
456
|
+
if self.iter_time_step < self.__len__():
|
|
457
|
+
result = self[self.tlist[self.iter_time_step]]
|
|
482
458
|
self.iter_time_step += 1
|
|
483
459
|
return result
|
|
484
460
|
raise StopIteration
|
|
@@ -502,3 +478,5 @@ class Schedule(Parameterizable):
|
|
|
502
478
|
renderer.plot()
|
|
503
479
|
if filepath:
|
|
504
480
|
renderer.save(filepath)
|
|
481
|
+
else:
|
|
482
|
+
renderer.show()
|