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.
Files changed (49) hide show
  1. qilisdk/analog/__init__.py +1 -2
  2. qilisdk/analog/hamiltonian.py +4 -71
  3. qilisdk/analog/schedule.py +291 -313
  4. qilisdk/backends/backend.py +5 -1
  5. qilisdk/backends/cuda_backend.py +10 -6
  6. qilisdk/backends/qutip_backend.py +24 -32
  7. qilisdk/{common → core}/__init__.py +4 -0
  8. qilisdk/core/interpolator.py +406 -0
  9. qilisdk/{common → core}/model.py +7 -7
  10. qilisdk/core/parameterizable.py +131 -0
  11. qilisdk/{common → core}/qtensor.py +1 -1
  12. qilisdk/{common → core}/variables.py +192 -11
  13. qilisdk/cost_functions/cost_function.py +1 -1
  14. qilisdk/cost_functions/model_cost_function.py +5 -5
  15. qilisdk/cost_functions/observable_cost_function.py +2 -2
  16. qilisdk/digital/ansatz.py +0 -3
  17. qilisdk/digital/circuit.py +3 -2
  18. qilisdk/digital/circuit_transpiler.py +46 -0
  19. qilisdk/digital/circuit_transpiler_passes/__init__.py +18 -0
  20. qilisdk/digital/circuit_transpiler_passes/circuit_transpiler_pass.py +36 -0
  21. qilisdk/digital/circuit_transpiler_passes/decompose_multi_controlled_gates_pass.py +216 -0
  22. qilisdk/digital/circuit_transpiler_passes/numeric_helpers.py +82 -0
  23. qilisdk/digital/gates.py +15 -5
  24. qilisdk/{speqtrum/experiments → experiments}/__init__.py +13 -2
  25. qilisdk/{speqtrum/experiments → experiments}/experiment_functional.py +90 -2
  26. qilisdk/{speqtrum/experiments → experiments}/experiment_result.py +16 -0
  27. qilisdk/functionals/functional.py +2 -2
  28. qilisdk/functionals/functional_result.py +1 -1
  29. qilisdk/functionals/sampling.py +8 -1
  30. qilisdk/functionals/time_evolution.py +8 -4
  31. qilisdk/functionals/time_evolution_result.py +2 -2
  32. qilisdk/functionals/variational_program.py +58 -0
  33. qilisdk/optimizers/optimizer_result.py +1 -1
  34. qilisdk/speqtrum/__init__.py +2 -0
  35. qilisdk/speqtrum/speqtrum.py +537 -152
  36. qilisdk/speqtrum/speqtrum_models.py +258 -2
  37. qilisdk/utils/openfermion/__init__.py +38 -0
  38. qilisdk/{common/algorithm.py → utils/openfermion/__init__.pyi} +2 -3
  39. qilisdk/utils/openfermion/openfermion.py +45 -0
  40. qilisdk/utils/visualization/schedule_renderers.py +22 -9
  41. {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/METADATA +89 -39
  42. qilisdk-0.1.7.dist-info/RECORD +76 -0
  43. {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/WHEEL +1 -1
  44. qilisdk/analog/linear_schedule.py +0 -118
  45. qilisdk/common/parameterizable.py +0 -75
  46. qilisdk-0.1.5.dist-info/RECORD +0 -69
  47. /qilisdk/{common → core}/exceptions.py +0 -0
  48. /qilisdk/{common → core}/result.py +0 -0
  49. {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/licenses/LICENCE +0 -0
@@ -13,16 +13,24 @@
13
13
  # limitations under the License.
14
14
  from __future__ import annotations
15
15
 
16
- from typing import Callable
17
-
18
- from loguru import logger
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.common.parameterizable import Parameterizable
22
- from qilisdk.common.variables import BaseVariable, Number, Parameter, Term
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. It maintains a dictionary of Hamiltonian objects and a
34
- corresponding schedule that specifies the coefficients (weights) for each Hamiltonian
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
- schedule={i: {"driver": 1 - t / T, "problem": t / T} for i, t in enumerate(steps)},
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
- schedule: dict[int, dict[str, float | Term | Parameter]] | None = None,
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
- 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.
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: If the provided schedule references Hamiltonians that have not been defined.
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
- for hamiltonian in self._hamiltonians.values():
89
- self._nqubits = max(self._nqubits, hamiltonian.nqubits)
90
- for l, param in hamiltonian.parameters.items():
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
- if 0 not in self._schedule:
94
- self._schedule[0] = dict.fromkeys(self._hamiltonians, 0.0)
95
- else:
96
- for label in self._hamiltonians:
97
- if label not in self._schedule[0]:
98
- self._schedule[0][label] = 0
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
- for time_step in self._schedule.values():
101
- if not all(s in self._hamiltonians for s in time_step):
102
- raise ValueError(
103
- "All hamiltonians defined in the schedule need to be declared in the hamiltonians dictionary."
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 schedule(self) -> dict[int, dict[str, Number]]:
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
- Returns:
136
- dict[int, dict[str, Number]]: Mapping from time indices to evaluated coefficients.
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._T
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
- return self._nqubits
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 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:
183
- """
184
- Update the numerical values of all parameters referenced by the schedule.
185
-
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``.
191
- """
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])
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, parameter_dict: dict[str, int | float]) -> None:
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
- parameter_dict (dict[str, float]): Mapping from parameter labels to new values.
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, param in parameter_dict.items():
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
- 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()}
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 from label to ``(lower, upper)`` bounds.
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, bound in ranges.items():
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
- self._parameters[label].set_bounds(bound[0], bound[1])
232
-
233
- def add_hamiltonian(
234
- self, label: str, hamiltonian: Hamiltonian, schedule: Callable | None = None, **kwargs: dict
235
- ) -> None:
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
- If a Hamiltonian with the given label already exists, a warning is issued and only
240
- the schedule is updated if a callable is provided.
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
- Args:
243
- label (str): The unique label to identify the Hamiltonian.
244
- hamiltonian (Hamiltonian): The Hamiltonian object to add.
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: if the parameterized schedule contains generic variables instead of only Parameters.
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
- logger.warning(
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._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
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
- Args:
287
- time_step (int): The time step index at which the Hamiltonian coefficients are updated.
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
- Raises:
294
- ValueError: If hamiltonian_coefficient_list references a Hamiltonian that is not defined in the schedule.
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
- Update the coefficient value of a specific Hamiltonian at a given time step.
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
- Args:
325
- time_step (int): The time step (as an integer multiple of dt) at which to update the coefficient.
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
- Raises:
330
- ValueError: If the specified time step exceeds the total annealing time.
331
- """
332
- if not (time_step * self.dt <= self.T):
333
- raise ValueError("Can't add a time step which happens after the end of the annealing process.")
334
-
335
- if time_step not in self._schedule:
336
- self._schedule[time_step] = {}
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
- The effective Hamiltonian is computed by summing the contributions of all Hamiltonians,
358
- using the latest defined coefficients at or before the given time step.
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
- Args:
361
- time_step (int): Time step index for which to retrieve the Hamiltonian (``time_step * dt`` in units).
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
- Returns:
364
- Hamiltonian: The effective Hamiltonian at the specified time step with coefficients evaluated to numbers.
365
- """
366
- ham = Hamiltonian()
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
- for ham_label in self._schedule[time_step]:
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]
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
- This function searches backwards in time (by multiples of dt) until it finds a defined
416
- coefficient for the given Hamiltonian.
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
- Args:
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.
355
+ for p_name, p_value in self._coefficients[label].parameters.items():
356
+ self._parameters[p_name] = p_value
421
357
 
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)
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 get_coefficient_expression(self, time_step: float, hamiltonian_key: str) -> Number | Term | Parameter:
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 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.
411
+ Retrieve the effective Hamiltonian at a given time step.
432
412
 
433
- This function searches backwards in time (by multiples of dt) until it finds a defined
434
- coefficient for the given Hamiltonian.
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): The time (in the same units as ``T``) at which to query the coefficient.
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
- Number | Term: The coefficient expression of the Hamiltonian at the specified time, or 0 if not defined.
420
+ Hamiltonian: The effective Hamiltonian at the specified time step with coefficients evaluated to numbers.
442
421
  """
443
- time_idx = int(time_step / self.dt)
444
- while time_idx >= 0:
445
- if time_idx in self._schedule and hamiltonian_key in self._schedule[time_idx]:
446
- val = self._schedule[time_idx][hamiltonian_key]
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 int(self.T / self.dt)
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 <= self.__len__():
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()