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
@@ -14,9 +14,20 @@
14
14
  # ruff: noqa: ANN001, ANN202, PLR6301
15
15
  from email.utils import parsedate_to_datetime
16
16
  from enum import Enum
17
+ from typing import Any, Callable, Generic, TypeVar, cast, overload
17
18
 
18
19
  from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, field_serializer, field_validator
19
20
 
21
+ from qilisdk.experiments import (
22
+ RabiExperiment,
23
+ RabiExperimentResult,
24
+ T1Experiment,
25
+ T1ExperimentResult,
26
+ T2Experiment,
27
+ T2ExperimentResult,
28
+ TwoTonesExperiment,
29
+ TwoTonesExperimentResult,
30
+ )
20
31
  from qilisdk.functionals import (
21
32
  Sampling,
22
33
  SamplingResult,
@@ -25,7 +36,7 @@ from qilisdk.functionals import (
25
36
  VariationalProgram,
26
37
  VariationalProgramResult,
27
38
  )
28
- from qilisdk.speqtrum.experiments import RabiExperiment, RabiExperimentResult, T1Experiment, T1ExperimentResult
39
+ from qilisdk.functionals.functional_result import FunctionalResult
29
40
  from qilisdk.utils.serialization import deserialize, serialize
30
41
 
31
42
 
@@ -50,7 +61,7 @@ class Token(SpeQtrumModel):
50
61
 
51
62
  access_token: str = Field(alias="accessToken")
52
63
  expires_in: int = Field(alias="expiresIn")
53
- issued_at: str = Field(alias="issuedAt")
64
+ issued_at: int = Field(alias="issuedAt")
54
65
  refresh_token: str = Field(alias="refreshToken")
55
66
  token_type: str = Field(alias="tokenType")
56
67
 
@@ -86,6 +97,8 @@ class ExecuteType(str, Enum):
86
97
  VARIATIONAL_PROGRAM = "variational_program"
87
98
  RABI_EXPERIMENT = "rabi_experiment"
88
99
  T1_EXPERIMENT = "t1_experiment"
100
+ T2_EXPERIMENT = "t2_experiment"
101
+ TWO_TONES_EXPERIMENT = "two_tones_experiment"
89
102
 
90
103
 
91
104
  class SamplingPayload(SpeQtrumModel):
@@ -158,6 +171,34 @@ class T1ExperimentPayload(SpeQtrumModel):
158
171
  return v
159
172
 
160
173
 
174
+ class T2ExperimentPayload(SpeQtrumModel):
175
+ t2_experiment: T2Experiment = Field(...)
176
+
177
+ @field_serializer("t2_experiment")
178
+ def _serialize_t2_experiment(self, t2_experiment: T2Experiment, _info):
179
+ return serialize(t2_experiment)
180
+
181
+ @field_validator("t2_experiment", mode="before")
182
+ def _load_t2_experiment(cls, v):
183
+ if isinstance(v, str):
184
+ return deserialize(v, T2Experiment)
185
+ return v
186
+
187
+
188
+ class TwoTonesExperimentPayload(SpeQtrumModel):
189
+ two_tones_experiment: TwoTonesExperiment = Field(...)
190
+
191
+ @field_serializer("two_tones_experiment")
192
+ def _serialize_two_tones_experiment(self, two_tones_experiment: TwoTonesExperiment, _info):
193
+ return serialize(two_tones_experiment)
194
+
195
+ @field_validator("two_tones_experiment", mode="before")
196
+ def _load_two_tones_experiment(cls, v):
197
+ if isinstance(v, str):
198
+ return deserialize(v, TwoTonesExperiment)
199
+ return v
200
+
201
+
161
202
  class ExecutePayload(SpeQtrumModel):
162
203
  type: ExecuteType = Field(...)
163
204
  sampling_payload: SamplingPayload | None = None
@@ -165,6 +206,8 @@ class ExecutePayload(SpeQtrumModel):
165
206
  variational_program_payload: VariationalProgramPayload | None = None
166
207
  rabi_experiment_payload: RabiExperimentPayload | None = None
167
208
  t1_experiment_payload: T1ExperimentPayload | None = None
209
+ t2_experiment_payload: T2ExperimentPayload | None = None
210
+ two_tones_experiment_payload: TwoTonesExperimentPayload | None = None
168
211
 
169
212
 
170
213
  class ExecuteResult(SpeQtrumModel):
@@ -174,6 +217,8 @@ class ExecuteResult(SpeQtrumModel):
174
217
  variational_program_result: VariationalProgramResult | None = None
175
218
  rabi_experiment_result: RabiExperimentResult | None = None
176
219
  t1_experiment_result: T1ExperimentResult | None = None
220
+ t2_experiment_result: T2ExperimentResult | None = None
221
+ two_tones_experiment_result: TwoTonesExperimentResult | None = None
177
222
 
178
223
  @field_serializer("sampling_result")
179
224
  def _serialize_sampling_result(self, sampling_result: SamplingResult, _info):
@@ -225,6 +270,188 @@ class ExecuteResult(SpeQtrumModel):
225
270
  return deserialize(v, T1ExperimentResult)
226
271
  return v
227
272
 
273
+ @field_serializer("t2_experiment_result")
274
+ def _serialize_t2_experiment_resultt(self, t2_experiment_result: T2ExperimentResult, _info):
275
+ return serialize(t2_experiment_result) if t2_experiment_result is not None else None
276
+
277
+ @field_validator("t2_experiment_result", mode="before")
278
+ def _load_t2_experiment_result(cls, v):
279
+ if isinstance(v, str) and v.startswith("!"):
280
+ return deserialize(v, T2ExperimentResult)
281
+ return v
282
+
283
+ @field_serializer("two_tones_experiment_result")
284
+ def _serialize_two_tones_experiment_result(self, two_tones_experiment_result: TwoTonesExperimentResult, _info):
285
+ return serialize(two_tones_experiment_result) if two_tones_experiment_result is not None else None
286
+
287
+ @field_validator("two_tones_experiment_result", mode="before")
288
+ def _load_two_tones_experiment_result(cls, v):
289
+ if isinstance(v, str) and v.startswith("!"):
290
+ return deserialize(v, TwoTonesExperimentResult)
291
+ return v
292
+
293
+
294
+ TFunctionalResult_co = TypeVar("TFunctionalResult_co", bound=FunctionalResult, covariant=True)
295
+ TVariationalInnerResult = TypeVar("TVariationalInnerResult", bound=FunctionalResult)
296
+
297
+
298
+ ResultExtractor = Callable[[ExecuteResult], TFunctionalResult_co]
299
+
300
+
301
+ # these helpers live outside the models so they can be referenced by default values
302
+ def _require_sampling_result(result: ExecuteResult) -> SamplingResult:
303
+ if result.sampling_result is None:
304
+ raise RuntimeError("SpeQtrum did not return a sampling_result for a sampling execution.")
305
+ return result.sampling_result
306
+
307
+
308
+ def _require_time_evolution_result(result: ExecuteResult) -> TimeEvolutionResult:
309
+ if result.time_evolution_result is None:
310
+ raise RuntimeError("SpeQtrum did not return a time_evolution_result for a time evolution execution.")
311
+ return result.time_evolution_result
312
+
313
+
314
+ def _require_variational_program_result(result: ExecuteResult) -> VariationalProgramResult:
315
+ if result.variational_program_result is None:
316
+ raise RuntimeError("SpeQtrum did not return a variational_program_result for a variational program execution.")
317
+ return result.variational_program_result
318
+
319
+
320
+ def _require_rabi_experiment_result(result: ExecuteResult) -> RabiExperimentResult:
321
+ if result.rabi_experiment_result is None:
322
+ raise RuntimeError("SpeQtrum did not return a rabi_experiment_result for a Rabi experiment execution.")
323
+ return result.rabi_experiment_result
324
+
325
+
326
+ def _require_t1_experiment_result(result: ExecuteResult) -> T1ExperimentResult:
327
+ if result.t1_experiment_result is None:
328
+ raise RuntimeError("SpeQtrum did not return a t1_experiment_result for a T1 experiment execution.")
329
+ return result.t1_experiment_result
330
+
331
+
332
+ def _require_t2_experiment_result(result: ExecuteResult) -> T2ExperimentResult:
333
+ if result.t2_experiment_result is None:
334
+ raise RuntimeError("SpeQtrum did not return a t2_experiment_result for a T2 experiment execution.")
335
+ return result.t2_experiment_result
336
+
337
+
338
+ def _require_two_tones_experiment_result(result: ExecuteResult) -> TwoTonesExperimentResult:
339
+ if result.two_tones_experiment_result is None:
340
+ raise RuntimeError(
341
+ "SpeQtrum did not return a two_tones_experiment_result for a Two-Tones experiment execution."
342
+ )
343
+ return result.two_tones_experiment_result
344
+
345
+
346
+ def _require_variational_program_result_typed(
347
+ inner_result_type: type[TVariationalInnerResult],
348
+ ) -> ResultExtractor[VariationalProgramResult[TVariationalInnerResult]]:
349
+ def _extractor(result: ExecuteResult) -> VariationalProgramResult[TVariationalInnerResult]:
350
+ variational_result = _require_variational_program_result(result)
351
+ optimal_results = variational_result.optimal_execution_results
352
+ if not isinstance(optimal_results, inner_result_type):
353
+ raise RuntimeError(
354
+ "SpeQtrum returned a variational program result whose optimal execution result "
355
+ f"({type(optimal_results).__qualname__}) does not match the expected "
356
+ f"{inner_result_type.__qualname__}."
357
+ )
358
+ return cast("VariationalProgramResult[TVariationalInnerResult]", variational_result)
359
+
360
+ return _extractor
361
+
362
+
363
+ class JobHandle(SpeQtrumModel, Generic[TFunctionalResult_co]):
364
+ """Strongly typed reference to a submitted SpeQtrum job."""
365
+
366
+ id: int
367
+ execute_type: ExecuteType
368
+ extractor: ResultExtractor[TFunctionalResult_co] = Field(repr=False, exclude=True)
369
+
370
+ @classmethod
371
+ def sampling(cls, job_id: int) -> "JobHandle[SamplingResult]":
372
+ return cls(id=job_id, execute_type=ExecuteType.SAMPLING, extractor=_require_sampling_result) # type: ignore[return-value, arg-type]
373
+
374
+ @classmethod
375
+ def time_evolution(cls, job_id: int) -> "JobHandle[TimeEvolutionResult]":
376
+ return cls(id=job_id, execute_type=ExecuteType.TIME_EVOLUTION, extractor=_require_time_evolution_result) # type: ignore[return-value, arg-type]
377
+
378
+ @overload
379
+ @classmethod
380
+ def variational_program(cls, job_id: int) -> "JobHandle[VariationalProgramResult]": ...
381
+
382
+ @overload
383
+ @classmethod
384
+ def variational_program(
385
+ cls, job_id: int, *, result_type: type[TVariationalInnerResult]
386
+ ) -> "JobHandle[VariationalProgramResult[TVariationalInnerResult]]": ...
387
+
388
+ @classmethod
389
+ def variational_program(
390
+ cls, job_id: int, *, result_type: type[TVariationalInnerResult] | None = None
391
+ ) -> "JobHandle[Any]":
392
+ """Create a variational-program handle for an existing job identifier.
393
+
394
+ Args:
395
+ job_id: Numeric identifier returned by the SpeQtrum service.
396
+ result_type: Optional functional result type expected within the
397
+ variational program payload. When provided the returned handle
398
+ enforces that the optimiser output matches this type.
399
+
400
+ Returns:
401
+ JobHandle: A handle whose ``get_results`` invocation yields a
402
+ ``VariationalProgramResult`` preserving the requested inner result
403
+ type when supplied.
404
+ """
405
+ if result_type is None:
406
+ handle = cls(
407
+ id=job_id,
408
+ execute_type=ExecuteType.VARIATIONAL_PROGRAM,
409
+ extractor=_require_variational_program_result, # type: ignore[arg-type]
410
+ )
411
+ return cast("JobHandle[VariationalProgramResult]", handle)
412
+
413
+ extractor = _require_variational_program_result_typed(result_type)
414
+ handle = cls(id=job_id, execute_type=ExecuteType.VARIATIONAL_PROGRAM, extractor=extractor) # type: ignore[arg-type]
415
+ return cast("JobHandle[VariationalProgramResult[TVariationalInnerResult]]", handle)
416
+
417
+ @classmethod
418
+ def rabi_experiment(cls, job_id: int) -> "JobHandle[RabiExperimentResult]":
419
+ return cls(id=job_id, execute_type=ExecuteType.RABI_EXPERIMENT, extractor=_require_rabi_experiment_result) # type: ignore[return-value, arg-type]
420
+
421
+ @classmethod
422
+ def t1_experiment(cls, job_id: int) -> "JobHandle[T1ExperimentResult]":
423
+ return cls(id=job_id, execute_type=ExecuteType.T1_EXPERIMENT, extractor=_require_t1_experiment_result) # type: ignore[return-value, arg-type]
424
+
425
+ @classmethod
426
+ def t2_experiment(cls, job_id: int) -> "JobHandle[T2ExperimentResult]":
427
+ return cls(id=job_id, execute_type=ExecuteType.T2_EXPERIMENT, extractor=_require_t2_experiment_result) # type: ignore[return-value, arg-type]
428
+
429
+ @classmethod
430
+ def two_tones_experiment(cls, job_id: int) -> "JobHandle[TwoTonesExperimentResult]":
431
+ return cls(
432
+ id=job_id,
433
+ execute_type=ExecuteType.TWO_TONES_EXPERIMENT,
434
+ extractor=_require_two_tones_experiment_result, # type: ignore[return-value, arg-type]
435
+ )
436
+
437
+ def bind(self, detail: "JobDetail") -> "TypedJobDetail[TFunctionalResult_co]":
438
+ """Attach this handle's typing information to a concrete job detail.
439
+
440
+ Args:
441
+ detail: Un-typed job detail payload returned by the SpeQtrum API.
442
+
443
+ Returns:
444
+ TypedJobDetail: Wrapper exposing ``get_results`` with the typing
445
+ captured when the handle was created.
446
+ """
447
+ return TypedJobDetail.model_validate(
448
+ {
449
+ **detail.model_dump(),
450
+ "expected_type": self.execute_type,
451
+ "extractor": self.extractor,
452
+ }
453
+ )
454
+
228
455
 
229
456
  class JobStatus(str, Enum):
230
457
  "Job has not been submitted to the Lab api"
@@ -298,3 +525,32 @@ class JobDetail(JobInfo):
298
525
  logs: str | None = None
299
526
  error: str | None = None
300
527
  error_logs: str | None = None
528
+
529
+
530
+ class TypedJobDetail(JobDetail, Generic[TFunctionalResult_co]):
531
+ """`JobDetail` subclass that exposes a strongly typed `get_results` method."""
532
+
533
+ expected_type: ExecuteType = Field(repr=False)
534
+ extractor: ResultExtractor[TFunctionalResult_co] = Field(repr=False, exclude=True)
535
+
536
+ def get_results(self) -> TFunctionalResult_co:
537
+ """Return the strongly typed execution result.
538
+
539
+ Returns:
540
+ ResultT_co: Result payload associated with the completed job,
541
+ respecting the type information carried by the originating
542
+ ``JobHandle``.
543
+
544
+ Raises:
545
+ RuntimeError: If SpeQtrum has not populated the result payload or
546
+ the execute type disagrees with the handle.
547
+ """
548
+ if self.result is None:
549
+ raise RuntimeError("The job completed without a result payload; inspect `error` or `logs` for details.")
550
+
551
+ if self.result.type != self.expected_type:
552
+ raise RuntimeError(
553
+ f"Expected a result of type '{self.expected_type.value}' but received '{self.result.type.value}'."
554
+ )
555
+
556
+ return self.extractor(self.result)
@@ -0,0 +1,38 @@
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
+ import sys
15
+
16
+ from qilisdk._optionals import ImportedFeature, OptionalFeature, Symbol, import_optional_dependencies
17
+
18
+ __all__ = []
19
+
20
+ OPTIONAL_FEATURES: list[OptionalFeature] = [
21
+ OptionalFeature(
22
+ name="openfermion",
23
+ dependencies=["openfermion"],
24
+ symbols=[
25
+ Symbol(path="qilisdk.utils.openfermion.openfermion", name="openfermion_to_qilisdk"),
26
+ Symbol(path="qilisdk.utils.openfermion.openfermion", name="qilisdk_to_openfermion"),
27
+ ],
28
+ ),
29
+ ]
30
+
31
+ current_module = sys.modules[__name__]
32
+
33
+ # Dynamically import (or stub) each feature's symbols and attach them
34
+ for feature in OPTIONAL_FEATURES:
35
+ imported_feature: ImportedFeature = import_optional_dependencies(feature)
36
+ for symbol_name, symbol_obj in imported_feature.symbols.items():
37
+ setattr(current_module, symbol_name, symbol_obj)
38
+ __all__ += [symbol_name] # noqa: PLE0604
@@ -11,8 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- from abc import ABC
15
14
 
15
+ from .openfermion import openfermion_to_qilisdk, qilisdk_to_openfermion
16
16
 
17
- class Algorithm(ABC):
18
- """Abstract base class for SDK algorithms."""
17
+ __all__ = ["openfermion_to_qilisdk", "qilisdk_to_openfermion"]
@@ -0,0 +1,45 @@
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
+
15
+ from openfermion import QubitOperator
16
+
17
+ from qilisdk.analog import Hamiltonian, PauliI, PauliX, PauliY, PauliZ
18
+
19
+
20
+ def openfermion_to_qilisdk(qubit_operator: QubitOperator) -> Hamiltonian:
21
+ pauli_map = {"X": PauliX, "Y": PauliY, "Z": PauliZ}
22
+
23
+ return Hamiltonian(
24
+ {
25
+ (tuple((pauli_map[op](q)) for q, op in term) if len(term) > 0 else (PauliI(0),)): coeff
26
+ for term, coeff in qubit_operator.terms.items()
27
+ }
28
+ )
29
+
30
+
31
+ def qilisdk_to_openfermion(hamiltonian: Hamiltonian) -> QubitOperator:
32
+ of_ham = QubitOperator()
33
+
34
+ for coeff, terms in hamiltonian:
35
+ of_term = ""
36
+ for t in terms:
37
+ if isinstance(t, PauliI):
38
+ continue
39
+
40
+ of_term += str(t).replace("(", "").replace(")", "") + " "
41
+ of_term = of_term.rstrip()
42
+
43
+ of_ham += QubitOperator(of_term, coeff)
44
+
45
+ return of_ham
@@ -18,10 +18,11 @@ from __future__ import annotations
18
18
  from typing import TYPE_CHECKING
19
19
 
20
20
  import matplotlib.pyplot as plt
21
+ import numpy as np
21
22
 
22
23
  if TYPE_CHECKING:
23
24
  from qilisdk.analog.schedule import Schedule
24
- from qilisdk.common.variables import Number
25
+ from qilisdk.core.variables import Number
25
26
 
26
27
  from qilisdk.utils.visualization.style import ScheduleStyle
27
28
 
@@ -29,7 +30,13 @@ from qilisdk.utils.visualization.style import ScheduleStyle
29
30
  class MatplotlibScheduleRenderer:
30
31
  """Render a Schedule using matplotlib, with theme support."""
31
32
 
32
- def __init__(self, schedule: Schedule, ax: plt.Axes | None = None, *, style: ScheduleStyle | None = None) -> None:
33
+ def __init__(
34
+ self,
35
+ schedule: Schedule,
36
+ ax: plt.Axes | None = None,
37
+ *,
38
+ style: ScheduleStyle | None = None,
39
+ ) -> None:
33
40
  self.schedule = schedule
34
41
  self.style = style or ScheduleStyle()
35
42
  self.ax = ax or self._make_axes(self.style.dpi, self.style)
@@ -57,13 +64,14 @@ class MatplotlibScheduleRenderer:
57
64
  T = self.schedule.T
58
65
  dt = self.schedule.dt
59
66
  hamiltonians = self.schedule.hamiltonians
60
- times = [i * dt for i in range(int(T / dt))]
67
+ times = list(np.linspace(0, T, int(1 / dt), dtype=float)) # [i * dt for i in range(int((T + dt) / dt))]
68
+ for t in self.schedule.tlist:
69
+ if t not in times:
70
+ times.append(t)
71
+ times = sorted(times)
61
72
  for h in hamiltonians:
62
- plots[h] = []
63
- for _t in range(int(T / dt)):
64
- t = _t * dt
65
- for h in hamiltonians:
66
- plots[h].append(self.schedule.get_coefficient(t, h))
73
+ coef = self.schedule.coefficients[h]
74
+ plots[h] = [coef[float(t)] for t in times]
67
75
 
68
76
  # Generate gradient colors between primary and accent
69
77
  def hex_to_rgb(hex_color: str) -> tuple[int, ...]:
@@ -146,6 +154,11 @@ class MatplotlibScheduleRenderer:
146
154
 
147
155
  self.ax.figure.savefig(filename, bbox_inches="tight") # type: ignore[union-attr]
148
156
 
157
+ def show(self) -> None: # noqa: PLR6301
158
+ """Show the current figure."""
159
+
160
+ plt.show()
161
+
149
162
  @staticmethod
150
163
  def _make_axes(dpi: int, style: ScheduleStyle) -> plt.Axes:
151
164
  """
@@ -157,5 +170,5 @@ class MatplotlibScheduleRenderer:
157
170
  Returns:
158
171
  A newly created Matplotlib Axes.
159
172
  """
160
- _, ax = plt.subplots(figsize=style.figsize, dpi=style.dpi, facecolor=style.theme.background)
173
+ _, ax = plt.subplots(figsize=style.figsize, dpi=dpi or style.dpi, facecolor=style.theme.background)
161
174
  return ax