qilisdk 0.1.3__py3-none-any.whl → 0.1.5__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/__init__.py +11 -2
- qilisdk/__init__.pyi +2 -3
- qilisdk/_logging.py +135 -0
- qilisdk/_optionals.py +5 -7
- qilisdk/analog/__init__.py +3 -18
- qilisdk/analog/exceptions.py +2 -4
- qilisdk/analog/hamiltonian.py +455 -110
- qilisdk/analog/linear_schedule.py +118 -0
- qilisdk/analog/schedule.py +272 -79
- qilisdk/backends/__init__.py +45 -0
- qilisdk/{digital/digital_algorithm.py → backends/__init__.pyi} +3 -5
- qilisdk/backends/backend.py +117 -0
- qilisdk/{extras/cuda → backends}/cuda_backend.py +153 -161
- qilisdk/backends/qutip_backend.py +492 -0
- qilisdk/common/__init__.py +48 -2
- qilisdk/common/algorithm.py +2 -1
- qilisdk/{extras/qaas/qaas_settings.py → common/exceptions.py} +12 -6
- qilisdk/common/model.py +1019 -1
- qilisdk/common/parameterizable.py +75 -0
- qilisdk/common/qtensor.py +666 -0
- qilisdk/common/result.py +2 -1
- qilisdk/common/variables.py +1931 -0
- qilisdk/{extras/cuda/cuda_analog_result.py → cost_functions/__init__.py} +3 -4
- qilisdk/cost_functions/cost_function.py +77 -0
- qilisdk/cost_functions/model_cost_function.py +145 -0
- qilisdk/cost_functions/observable_cost_function.py +109 -0
- qilisdk/digital/__init__.py +3 -22
- qilisdk/digital/ansatz.py +203 -160
- qilisdk/digital/circuit.py +81 -9
- qilisdk/digital/exceptions.py +12 -6
- qilisdk/digital/gates.py +228 -85
- qilisdk/{extras/qaas/qaas_analog_result.py → functionals/__init__.py} +14 -5
- qilisdk/functionals/functional.py +39 -0
- qilisdk/{extras/cuda/cuda_digital_result.py → functionals/functional_result.py} +3 -4
- qilisdk/functionals/sampling.py +81 -0
- qilisdk/functionals/sampling_result.py +92 -0
- qilisdk/functionals/time_evolution.py +98 -0
- qilisdk/functionals/time_evolution_result.py +84 -0
- qilisdk/functionals/variational_program.py +80 -0
- qilisdk/functionals/variational_program_result.py +69 -0
- qilisdk/logging_config.yaml +16 -0
- qilisdk/{common/backend.py → optimizers/__init__.py} +2 -1
- qilisdk/optimizers/optimizer.py +39 -0
- qilisdk/{common → optimizers}/optimizer_result.py +3 -12
- qilisdk/{common/optimizer.py → optimizers/scipy_optimizer.py} +10 -28
- qilisdk/settings.py +78 -0
- qilisdk/{extras → speqtrum}/__init__.py +7 -8
- qilisdk/{extras → speqtrum}/__init__.pyi +3 -3
- qilisdk/speqtrum/experiments/__init__.py +25 -0
- qilisdk/speqtrum/experiments/experiment_functional.py +124 -0
- qilisdk/speqtrum/experiments/experiment_result.py +231 -0
- qilisdk/{extras/qaas → speqtrum}/keyring.py +8 -4
- qilisdk/speqtrum/speqtrum.py +432 -0
- qilisdk/speqtrum/speqtrum_models.py +300 -0
- qilisdk/utils/__init__.py +0 -14
- qilisdk/utils/openqasm2.py +1 -1
- qilisdk/utils/serialization.py +1 -1
- qilisdk/utils/visualization/PlusJakartaSans-SemiBold.ttf +0 -0
- qilisdk/utils/visualization/__init__.py +24 -0
- qilisdk/utils/visualization/circuit_renderers.py +781 -0
- qilisdk/utils/visualization/schedule_renderers.py +161 -0
- qilisdk/utils/visualization/style.py +154 -0
- qilisdk/utils/visualization/themes.py +76 -0
- qilisdk/yaml.py +126 -0
- {qilisdk-0.1.3.dist-info → qilisdk-0.1.5.dist-info}/METADATA +180 -135
- qilisdk-0.1.5.dist-info/RECORD +69 -0
- qilisdk/analog/algorithms.py +0 -111
- qilisdk/analog/analog_backend.py +0 -43
- qilisdk/analog/analog_result.py +0 -114
- qilisdk/analog/quantum_objects.py +0 -533
- qilisdk/digital/digital_backend.py +0 -90
- qilisdk/digital/digital_result.py +0 -145
- qilisdk/digital/vqe.py +0 -166
- qilisdk/extras/cuda/__init__.py +0 -13
- qilisdk/extras/qaas/__init__.py +0 -13
- qilisdk/extras/qaas/models.py +0 -132
- qilisdk/extras/qaas/qaas_backend.py +0 -255
- qilisdk/extras/qaas/qaas_digital_result.py +0 -20
- qilisdk/extras/qaas/qaas_time_evolution_result.py +0 -20
- qilisdk/extras/qaas/qaas_vqe_result.py +0 -20
- qilisdk-0.1.3.dist-info/RECORD +0 -51
- {qilisdk-0.1.3.dist-info → qilisdk-0.1.5.dist-info}/WHEEL +0 -0
- {qilisdk-0.1.3.dist-info → qilisdk-0.1.5.dist-info}/licenses/LICENCE +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from qilisdk.analog.hamiltonian import Hamiltonian
|
|
17
|
+
from qilisdk.analog.schedule import Schedule
|
|
18
|
+
from qilisdk.common.variables import Number, Parameter, Term
|
|
19
|
+
from qilisdk.yaml import yaml
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@yaml.register_class
|
|
23
|
+
class LinearSchedule(Schedule):
|
|
24
|
+
"""
|
|
25
|
+
Schedule implementation that linearly interpolates coefficients between defined time steps.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
.. code-block:: python
|
|
29
|
+
|
|
30
|
+
from qilisdk.analog.hamiltonian import Hamiltonian, Z
|
|
31
|
+
from qilisdk.analog.linear_schedule import LinearSchedule
|
|
32
|
+
|
|
33
|
+
h = 2 * Z(0)
|
|
34
|
+
schedule = LinearSchedule(T=4.0, dt=1.0, hamiltonians={"hz": h})
|
|
35
|
+
schedule.add_schedule_step(0, {"hz": 0.0})
|
|
36
|
+
schedule.add_schedule_step(4, {"hz": 1.0})
|
|
37
|
+
assert schedule.get_coefficient(2.0, "hz") == 0.5
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def get_coefficient_expression(self, time_step: float, hamiltonian_key: str) -> Number | Term | Parameter:
|
|
41
|
+
"""
|
|
42
|
+
Return the symbolic coefficient for a Hamiltonian at an arbitrary time step.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
time_step (float): The time at which to evaluate the coefficient.
|
|
46
|
+
hamiltonian_key (str): Label of the Hamiltonian inside the schedule.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Number | Term: The (possibly symbolic) coefficient associated with ``hamiltonian_key``.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If something unexpected happens during coefficient retrieval.
|
|
53
|
+
"""
|
|
54
|
+
t = time_step / self.dt
|
|
55
|
+
t_idx = int(t)
|
|
56
|
+
|
|
57
|
+
if t_idx in self._schedule and hamiltonian_key in self._schedule[t_idx]:
|
|
58
|
+
return self._schedule[t_idx][hamiltonian_key]
|
|
59
|
+
|
|
60
|
+
# search backwards
|
|
61
|
+
prev_idx, prev_expr = None, None
|
|
62
|
+
for i in range(t_idx, -1, -1):
|
|
63
|
+
if i in self._schedule and hamiltonian_key in self._schedule[i]:
|
|
64
|
+
prev_idx = i
|
|
65
|
+
prev_expr = self._schedule[i][hamiltonian_key]
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
# search forwards
|
|
69
|
+
next_idx, next_expr = None, None
|
|
70
|
+
for i in range(t_idx + 1, int(self.T / self.dt) + 1):
|
|
71
|
+
if i in self._schedule and hamiltonian_key in self._schedule[i]:
|
|
72
|
+
next_idx = i
|
|
73
|
+
next_expr = self._schedule[i][hamiltonian_key]
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
# cases
|
|
77
|
+
if prev_expr is None and next_expr is None:
|
|
78
|
+
return 0
|
|
79
|
+
if prev_expr is None and next_expr is not None:
|
|
80
|
+
return next_expr
|
|
81
|
+
if next_expr is None and prev_expr is not None:
|
|
82
|
+
return prev_expr
|
|
83
|
+
|
|
84
|
+
# linear interpolation (keeps expressions if they are Terms/Parameters)
|
|
85
|
+
if next_idx is None or prev_idx is None or prev_expr is None or next_expr is None:
|
|
86
|
+
raise ValueError("Something unexpected happened while retrieving the coefficient.")
|
|
87
|
+
alpha: float = (t - prev_idx) / (next_idx - prev_idx)
|
|
88
|
+
return (1 - alpha) * prev_expr + alpha * next_expr
|
|
89
|
+
|
|
90
|
+
def get_coefficient(self, time_step: float, hamiltonian_key: str) -> Number:
|
|
91
|
+
"""
|
|
92
|
+
Return the numeric coefficient for a Hamiltonian at ``time_step``.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
time_step (float): Time at which to evaluate the coefficient.
|
|
96
|
+
hamiltonian_key (str): Label of the Hamiltonian.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Number: Evaluated coefficient value.
|
|
100
|
+
"""
|
|
101
|
+
val = self.get_coefficient_expression(time_step=time_step, hamiltonian_key=hamiltonian_key)
|
|
102
|
+
return val.evaluate({}) if isinstance(val, Term) else (val.evaluate() if isinstance(val, Parameter) else val)
|
|
103
|
+
|
|
104
|
+
def __getitem__(self, time_step: int) -> Hamiltonian:
|
|
105
|
+
"""
|
|
106
|
+
Retrieve the interpolated Hamiltonian at the specified discrete time step.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
time_step (int): Discrete index to evaluate (converted internally to ``time_step * dt``).
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Hamiltonian: Hamiltonian with coefficients interpolated at the requested time step.
|
|
113
|
+
"""
|
|
114
|
+
ham = Hamiltonian()
|
|
115
|
+
for ham_label in self._hamiltonians:
|
|
116
|
+
coeff = self.get_coefficient(time_step * self.dt, ham_label)
|
|
117
|
+
ham += coeff * self._hamiltonians[ham_label]
|
|
118
|
+
return ham.get_static_hamiltonian()
|
qilisdk/analog/schedule.py
CHANGED
|
@@ -14,62 +14,81 @@
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
from typing import Callable
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
from loguru import logger
|
|
18
19
|
|
|
19
20
|
from qilisdk.analog.hamiltonian import Hamiltonian
|
|
21
|
+
from qilisdk.common.parameterizable import Parameterizable
|
|
22
|
+
from qilisdk.common.variables import BaseVariable, Number, Parameter, Term
|
|
23
|
+
from qilisdk.utils.visualization import ScheduleStyle
|
|
20
24
|
from qilisdk.yaml import yaml
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
@yaml.register_class
|
|
24
|
-
class Schedule:
|
|
28
|
+
class Schedule(Parameterizable):
|
|
25
29
|
"""
|
|
26
|
-
|
|
30
|
+
Builds a set of time-dependent coefficients applied to a collection of Hamiltonians.
|
|
27
31
|
|
|
28
32
|
A Schedule defines the evolution of a system by associating time steps with a set
|
|
29
33
|
of Hamiltonian coefficients. It maintains a dictionary of Hamiltonian objects and a
|
|
30
34
|
corresponding schedule that specifies the coefficients (weights) for each Hamiltonian
|
|
31
35
|
at discrete time steps.
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
Example:
|
|
38
|
+
.. code-block:: python
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
from qilisdk.analog import Schedule, X, Z
|
|
42
|
+
|
|
43
|
+
T, dt = 10, 1
|
|
44
|
+
steps = np.linspace(0, T, int(T / dt))
|
|
45
|
+
|
|
46
|
+
h1 = X(0) + X(1) + X(2)
|
|
47
|
+
h2 = -Z(0) - Z(1) - 2 * Z(2) + 3 * Z(0) * Z(1)
|
|
48
|
+
|
|
49
|
+
schedule = Schedule(
|
|
50
|
+
T=T,
|
|
51
|
+
dt=dt,
|
|
52
|
+
hamiltonians={"driver": h1, "problem": h2},
|
|
53
|
+
schedule={i: {"driver": 1 - t / T, "problem": t / T} for i, t in enumerate(steps)},
|
|
54
|
+
)
|
|
55
|
+
schedule.draw()
|
|
40
56
|
"""
|
|
41
57
|
|
|
42
58
|
def __init__(
|
|
43
59
|
self,
|
|
44
60
|
T: float,
|
|
45
|
-
dt: float,
|
|
61
|
+
dt: float = 1,
|
|
46
62
|
hamiltonians: dict[str, Hamiltonian] | None = None,
|
|
47
|
-
schedule: dict[int, dict[str, float]] | None = None,
|
|
63
|
+
schedule: dict[int, dict[str, float | Term | Parameter]] | None = None,
|
|
48
64
|
) -> None:
|
|
49
65
|
"""
|
|
50
|
-
Initialize a Schedule object.
|
|
51
|
-
|
|
52
66
|
Args:
|
|
53
|
-
T (float):
|
|
54
|
-
dt (float):
|
|
55
|
-
hamiltonians (dict[str, Hamiltonian], optional):
|
|
56
|
-
Defaults to an empty
|
|
57
|
-
schedule (dict[int, dict[str, float]], optional):
|
|
58
|
-
|
|
59
|
-
Defaults to {0: {}} if None.
|
|
67
|
+
T (float): Total annealing time (in nanoseconds).
|
|
68
|
+
dt (float, optional): Discretization step of the time grid. Defaults to 1.
|
|
69
|
+
hamiltonians (dict[str, Hamiltonian] | None, optional): Mapping from labels to Hamiltonian instances that
|
|
70
|
+
define the building blocks of the schedule. Defaults to None, which creates an empty mapping.
|
|
71
|
+
schedule (dict[int, dict[str, float | Term | Parameter]] | None, optional): Predefined coefficients for
|
|
72
|
+
specific time steps. Each inner dictionary maps Hamiltonian labels to numerical or symbolic
|
|
73
|
+
coefficients. Defaults to {0: {}} if None.
|
|
60
74
|
|
|
61
75
|
Raises:
|
|
62
76
|
ValueError: If the provided schedule references Hamiltonians that have not been defined.
|
|
63
77
|
"""
|
|
64
|
-
|
|
78
|
+
if dt <= 0:
|
|
79
|
+
raise ValueError("dt must be greater than zero.")
|
|
65
80
|
self._hamiltonians: dict[str, Hamiltonian] = hamiltonians if hamiltonians is not None else {}
|
|
66
|
-
self._schedule: dict[int, dict[str, float]] = schedule if schedule is not None else {0: {}}
|
|
81
|
+
self._schedule: dict[int, dict[str, float | Term | Parameter]] = schedule if schedule is not None else {0: {}}
|
|
82
|
+
self._parameters: dict[str, Parameter] = {}
|
|
67
83
|
self._T = T
|
|
68
84
|
self._dt = dt
|
|
69
85
|
self.iter_time_step = 0
|
|
70
86
|
self._nqubits = 0
|
|
87
|
+
|
|
71
88
|
for hamiltonian in self._hamiltonians.values():
|
|
72
89
|
self._nqubits = max(self._nqubits, hamiltonian.nqubits)
|
|
90
|
+
for l, param in hamiltonian.parameters.items():
|
|
91
|
+
self._parameters[param.label] = param
|
|
73
92
|
|
|
74
93
|
if 0 not in self._schedule:
|
|
75
94
|
self._schedule[0] = dict.fromkeys(self._hamiltonians, 0.0)
|
|
@@ -83,58 +102,133 @@ class Schedule:
|
|
|
83
102
|
raise ValueError(
|
|
84
103
|
"All hamiltonians defined in the schedule need to be declared in the hamiltonians dictionary."
|
|
85
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
|
|
86
119
|
|
|
87
120
|
@property
|
|
88
121
|
def hamiltonians(self) -> dict[str, Hamiltonian]:
|
|
89
122
|
"""
|
|
90
|
-
|
|
123
|
+
Return the Hamiltonians managed by the schedule.
|
|
91
124
|
|
|
92
125
|
Returns:
|
|
93
|
-
dict[str, Hamiltonian]:
|
|
126
|
+
dict[str, Hamiltonian]: Mapping of labels to Hamiltonian instances.
|
|
94
127
|
"""
|
|
95
128
|
return self._hamiltonians
|
|
96
129
|
|
|
97
130
|
@property
|
|
98
|
-
def schedule(self) -> dict[int, dict[str,
|
|
131
|
+
def schedule(self) -> dict[int, dict[str, Number]]:
|
|
99
132
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
The schedule is returned as a dictionary sorted by time step.
|
|
133
|
+
Return the evaluated schedule of Hamiltonian coefficients.
|
|
103
134
|
|
|
104
135
|
Returns:
|
|
105
|
-
dict[int, dict[str,
|
|
136
|
+
dict[int, dict[str, Number]]: Mapping from time indices to evaluated coefficients.
|
|
106
137
|
"""
|
|
107
|
-
|
|
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()))
|
|
108
149
|
|
|
109
150
|
@property
|
|
110
151
|
def T(self) -> float:
|
|
111
|
-
"""
|
|
112
|
-
Get the total annealing time.
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
float: The total time T.
|
|
116
|
-
"""
|
|
152
|
+
"""Total annealing time of the schedule."""
|
|
117
153
|
return self._T
|
|
118
154
|
|
|
119
155
|
@property
|
|
120
156
|
def dt(self) -> float:
|
|
121
|
-
"""
|
|
122
|
-
Get the time step duration.
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
float: The duration of each time step.
|
|
126
|
-
"""
|
|
157
|
+
"""Duration of a single time step in the annealing grid."""
|
|
127
158
|
return self._dt
|
|
128
159
|
|
|
129
160
|
@property
|
|
130
161
|
def nqubits(self) -> int:
|
|
162
|
+
"""Maximum number of qubits affected by Hamiltonians contained in the schedule."""
|
|
163
|
+
return self._nqubits
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def nparameters(self) -> int:
|
|
167
|
+
"""Number of symbolic parameters introduced by the Hamiltonians or coefficients."""
|
|
168
|
+
return len(self._parameters)
|
|
169
|
+
|
|
170
|
+
def get_parameter_values(self) -> list[float]:
|
|
171
|
+
"""Return the current values associated with the schedule parameters."""
|
|
172
|
+
return [param.value for param in self._parameters.values()]
|
|
173
|
+
|
|
174
|
+
def get_parameter_names(self) -> list[str]:
|
|
175
|
+
"""Return the ordered list of parameter labels managed by the schedule."""
|
|
176
|
+
return list(self._parameters.keys())
|
|
177
|
+
|
|
178
|
+
def get_parameters(self) -> dict[str, float]:
|
|
179
|
+
"""Return a mapping from parameter labels to their current numerical values."""
|
|
180
|
+
return {label: param.value for label, param in self._parameters.items()}
|
|
181
|
+
|
|
182
|
+
def set_parameter_values(self, values: list[float]) -> None:
|
|
131
183
|
"""
|
|
132
|
-
|
|
184
|
+
Update the numerical values of all parameters referenced by the schedule.
|
|
133
185
|
|
|
134
|
-
|
|
135
|
-
|
|
186
|
+
Args:
|
|
187
|
+
values (list[float]): New parameter values ordered according to ``get_parameter_names()``.
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ValueError: If the number of provided values does not match ``nparameters``.
|
|
136
191
|
"""
|
|
137
|
-
|
|
192
|
+
if len(values) != self.nparameters:
|
|
193
|
+
raise ValueError(f"Provided {len(values)} but Schedule has {self.nparameters} parameters.")
|
|
194
|
+
for i, parameter in enumerate(self._parameters.values()):
|
|
195
|
+
parameter.set_value(values[i])
|
|
196
|
+
|
|
197
|
+
def set_parameters(self, parameter_dict: dict[str, int | float]) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Update a subset of parameters by label.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
parameter_dict (dict[str, float]): Mapping from parameter labels to new values.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ValueError: If an unknown parameter label is provided.
|
|
206
|
+
"""
|
|
207
|
+
for label, param in parameter_dict.items():
|
|
208
|
+
if label not in self._parameters:
|
|
209
|
+
raise ValueError(f"Parameter {label} is not defined in this Schedule.")
|
|
210
|
+
self._parameters[label].set_value(param)
|
|
211
|
+
|
|
212
|
+
def get_parameter_bounds(self) -> dict[str, tuple[float, float]]:
|
|
213
|
+
"""Return the bounds registered for each schedule parameter."""
|
|
214
|
+
return {k: v.bounds for k, v in self._parameters.items()}
|
|
215
|
+
|
|
216
|
+
def set_parameter_bounds(self, ranges: dict[str, tuple[float, float]]) -> None:
|
|
217
|
+
"""
|
|
218
|
+
Update the bounds of existing parameters.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
ranges (dict[str, tuple[float, float]]): Mapping from label to ``(lower, upper)`` bounds.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
ValueError: If an unknown parameter label is provided.
|
|
225
|
+
"""
|
|
226
|
+
for label, bound in ranges.items():
|
|
227
|
+
if label not in self._parameters:
|
|
228
|
+
raise ValueError(
|
|
229
|
+
f"The provided parameter label {label} is not defined in the list of parameters in this object."
|
|
230
|
+
)
|
|
231
|
+
self._parameters[label].set_bounds(bound[0], bound[1])
|
|
138
232
|
|
|
139
233
|
def add_hamiltonian(
|
|
140
234
|
self, label: str, hamiltonian: Hamiltonian, schedule: Callable | None = None, **kwargs: dict
|
|
@@ -151,50 +245,78 @@ class Schedule:
|
|
|
151
245
|
schedule (Callable, optional): A function that returns the coefficient of the Hamiltonian at time t.
|
|
152
246
|
It should accept time (and any additional keyword arguments) and return a float.
|
|
153
247
|
**kwargs (dict): Additional keyword arguments to pass to the schedule function.
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
ValueError: if the parameterized schedule contains generic variables instead of only Parameters.
|
|
154
251
|
"""
|
|
155
|
-
if label
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
self._nqubits = max(self._nqubits, hamiltonian.nqubits)
|
|
159
|
-
else:
|
|
160
|
-
warn(
|
|
161
|
-
(
|
|
162
|
-
f"label {label} is already assigned to a hamiltonian, "
|
|
163
|
-
+ "ignoring new hamiltonian and updating schedule of existing hamiltonian."
|
|
164
|
-
),
|
|
165
|
-
RuntimeWarning,
|
|
252
|
+
if label in self._hamiltonians:
|
|
253
|
+
logger.warning(
|
|
254
|
+
(f"label {label} is already assigned to a hamiltonian, " + "updating schedule of existing hamiltonian.")
|
|
166
255
|
)
|
|
256
|
+
self._hamiltonians[label] = hamiltonian
|
|
257
|
+
self._schedule[0][label] = 0
|
|
258
|
+
self._nqubits = max(self._nqubits, hamiltonian.nqubits)
|
|
259
|
+
for l, param in hamiltonian.parameters.items():
|
|
260
|
+
self._parameters[param.label] = param
|
|
167
261
|
|
|
168
262
|
if schedule is not None:
|
|
169
|
-
for t in range(int(self.T / self.dt)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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:
|
|
173
283
|
"""
|
|
174
284
|
Add or update a schedule step with specified Hamiltonian coefficients.
|
|
175
285
|
|
|
176
286
|
Args:
|
|
177
287
|
time_step (int): The time step index at which the Hamiltonian coefficients are updated.
|
|
178
288
|
The actual time is computed as dt * time_step.
|
|
179
|
-
hamiltonian_coefficient_list (dict[str, float]):
|
|
180
|
-
|
|
289
|
+
hamiltonian_coefficient_list (dict[str, float | Term | Parameter]): Mapping from Hamiltonian labels to coefficients
|
|
290
|
+
(numeric or symbolic) at this time step.
|
|
181
291
|
If a Hamiltonian is not included in the dictionary, it is assumed its coefficient remains unchanged.
|
|
182
292
|
|
|
183
293
|
Raises:
|
|
184
294
|
ValueError: If hamiltonian_coefficient_list references a Hamiltonian that is not defined in the schedule.
|
|
185
295
|
"""
|
|
186
296
|
if time_step in self._schedule:
|
|
187
|
-
|
|
297
|
+
logger.warning(
|
|
188
298
|
f"time step {time_step} is already defined in the schedule, the values are going to be overwritten.",
|
|
189
|
-
RuntimeWarning,
|
|
190
299
|
)
|
|
191
|
-
for key in hamiltonian_coefficient_list:
|
|
300
|
+
for key, coeff in hamiltonian_coefficient_list.items():
|
|
192
301
|
if key not in self._hamiltonians:
|
|
193
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
|
|
194
316
|
self._schedule[time_step] = hamiltonian_coefficient_list
|
|
195
317
|
|
|
196
318
|
def update_hamiltonian_coefficient_at_time_step(
|
|
197
|
-
self, time_step: int, hamiltonian_label: str, new_coefficient: float
|
|
319
|
+
self, time_step: int, hamiltonian_label: str, new_coefficient: float | Term | Parameter
|
|
198
320
|
) -> None:
|
|
199
321
|
"""
|
|
200
322
|
Update the coefficient value of a specific Hamiltonian at a given time step.
|
|
@@ -202,7 +324,7 @@ class Schedule:
|
|
|
202
324
|
Args:
|
|
203
325
|
time_step (int): The time step (as an integer multiple of dt) at which to update the coefficient.
|
|
204
326
|
hamiltonian_label (str): The label of the Hamiltonian to update.
|
|
205
|
-
new_coefficient (float): The new coefficient value.
|
|
327
|
+
new_coefficient (float | Term | Parameter): The new coefficient value or symbolic expression.
|
|
206
328
|
|
|
207
329
|
Raises:
|
|
208
330
|
ValueError: If the specified time step exceeds the total annealing time.
|
|
@@ -214,6 +336,20 @@ class Schedule:
|
|
|
214
336
|
self._schedule[time_step] = {}
|
|
215
337
|
self._schedule[time_step][hamiltonian_label] = new_coefficient
|
|
216
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
|
+
|
|
217
353
|
def __getitem__(self, time_step: int) -> Hamiltonian:
|
|
218
354
|
"""
|
|
219
355
|
Retrieve the effective Hamiltonian at a given time step.
|
|
@@ -222,10 +358,10 @@ class Schedule:
|
|
|
222
358
|
using the latest defined coefficients at or before the given time step.
|
|
223
359
|
|
|
224
360
|
Args:
|
|
225
|
-
time_step (int):
|
|
361
|
+
time_step (int): Time step index for which to retrieve the Hamiltonian (``time_step * dt`` in units).
|
|
226
362
|
|
|
227
363
|
Returns:
|
|
228
|
-
Hamiltonian: The effective Hamiltonian at the specified time step.
|
|
364
|
+
Hamiltonian: The effective Hamiltonian at the specified time step with coefficients evaluated to numbers.
|
|
229
365
|
"""
|
|
230
366
|
ham = Hamiltonian()
|
|
231
367
|
read_labels = []
|
|
@@ -235,12 +371,24 @@ class Schedule:
|
|
|
235
371
|
time_step -= 1
|
|
236
372
|
if time_step in self._schedule:
|
|
237
373
|
for ham_label in self._schedule[time_step]:
|
|
238
|
-
|
|
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]
|
|
239
381
|
read_labels.append(ham_label)
|
|
240
382
|
break
|
|
241
383
|
else:
|
|
242
384
|
for ham_label in self._schedule[time_step]:
|
|
243
|
-
|
|
385
|
+
aux = self._schedule[time_step][ham_label]
|
|
386
|
+
coeff = (
|
|
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]
|
|
244
392
|
read_labels.append(ham_label)
|
|
245
393
|
if len(read_labels) < len(self._hamiltonians):
|
|
246
394
|
all_labels = self._hamiltonians.keys()
|
|
@@ -250,11 +398,17 @@ class Schedule:
|
|
|
250
398
|
while current_time > 0:
|
|
251
399
|
current_time -= 1
|
|
252
400
|
if current_time in self._schedule and label in self._schedule[current_time]:
|
|
253
|
-
|
|
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]
|
|
254
408
|
break
|
|
255
|
-
return ham
|
|
409
|
+
return ham.get_static_hamiltonian()
|
|
256
410
|
|
|
257
|
-
def get_coefficient(self, time_step: float, hamiltonian_key: str) ->
|
|
411
|
+
def get_coefficient(self, time_step: float, hamiltonian_key: str) -> Number:
|
|
258
412
|
"""
|
|
259
413
|
Retrieve the coefficient of a specified Hamiltonian at a given time.
|
|
260
414
|
|
|
@@ -262,16 +416,35 @@ class Schedule:
|
|
|
262
416
|
coefficient for the given Hamiltonian.
|
|
263
417
|
|
|
264
418
|
Args:
|
|
265
|
-
time_step (float): The time (in the same units as T) at which to query the coefficient.
|
|
419
|
+
time_step (float): The time (in the same units as ``T``) at which to query the coefficient.
|
|
420
|
+
hamiltonian_key (str): The label of the Hamiltonian.
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Number: The coefficient of the Hamiltonian at the specified time, or 0 if not defined.
|
|
424
|
+
"""
|
|
425
|
+
val = self.get_coefficient_expression(time_step=time_step, hamiltonian_key=hamiltonian_key)
|
|
426
|
+
return val.evaluate({}) if isinstance(val, Term) else (val.evaluate() if isinstance(val, Parameter) else val)
|
|
427
|
+
|
|
428
|
+
def get_coefficient_expression(self, time_step: float, hamiltonian_key: str) -> Number | Term | Parameter:
|
|
429
|
+
"""
|
|
430
|
+
Retrieve the expression of a specified Hamiltonian at a given time. If any parameters are
|
|
431
|
+
present in the expression they will be printed in the expression.
|
|
432
|
+
|
|
433
|
+
This function searches backwards in time (by multiples of dt) until it finds a defined
|
|
434
|
+
coefficient for the given Hamiltonian.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
time_step (float): The time (in the same units as ``T``) at which to query the coefficient.
|
|
266
438
|
hamiltonian_key (str): The label of the Hamiltonian.
|
|
267
439
|
|
|
268
440
|
Returns:
|
|
269
|
-
|
|
441
|
+
Number | Term: The coefficient expression of the Hamiltonian at the specified time, or 0 if not defined.
|
|
270
442
|
"""
|
|
271
443
|
time_idx = int(time_step / self.dt)
|
|
272
444
|
while time_idx >= 0:
|
|
273
445
|
if time_idx in self._schedule and hamiltonian_key in self._schedule[time_idx]:
|
|
274
|
-
|
|
446
|
+
val = self._schedule[time_idx][hamiltonian_key]
|
|
447
|
+
return val
|
|
275
448
|
time_idx -= 1
|
|
276
449
|
return 0
|
|
277
450
|
|
|
@@ -309,3 +482,23 @@ class Schedule:
|
|
|
309
482
|
self.iter_time_step += 1
|
|
310
483
|
return result
|
|
311
484
|
raise StopIteration
|
|
485
|
+
|
|
486
|
+
def draw(self, style: ScheduleStyle | None = None, filepath: str | None = None) -> None:
|
|
487
|
+
"""Render a plot of the schedule using matplotlib and optionally save it to a file.
|
|
488
|
+
|
|
489
|
+
The schedule is rendered using the provided style configuration. If ``filepath`` is
|
|
490
|
+
given, the resulting figure is saved to disk (the output format is inferred
|
|
491
|
+
from the file extension, e.g. ``.png``, ``.pdf``, ``.svg``).
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
style (ScheduleStyle, optional): Customization options for the plot appearance.
|
|
495
|
+
Defaults to ScheduleStyle().
|
|
496
|
+
filepath (str | None, optional): If provided, saves the plot to the specified file path.
|
|
497
|
+
"""
|
|
498
|
+
from qilisdk.utils.visualization.schedule_renderers import MatplotlibScheduleRenderer # noqa: PLC0415
|
|
499
|
+
|
|
500
|
+
style = style or ScheduleStyle()
|
|
501
|
+
renderer = MatplotlibScheduleRenderer(self, style=style)
|
|
502
|
+
renderer.plot()
|
|
503
|
+
if filepath:
|
|
504
|
+
renderer.save(filepath)
|