From e7dff5318b0c3b0165c68d474c0d090a7b216070 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 06:09:09 -0700 Subject: [PATCH 1/9] updating qiskit docs settings --- docs/conf.py | 4 ++++ requirements-dev.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 439fb1b58..8e0d3f754 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,6 +61,10 @@ html_context = {"version_list": ["0.4"]} +html_theme_options = { + "sidebar_qiskit_ecosystem_member": True, +} + # autodoc/autosummary options autosummary_generate = True autosummary_generate_overwrite = False diff --git a/requirements-dev.txt b/requirements-dev.txt index 3516af256..86d98b2c5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ stestr>=3.0.0 astroid==2.14.2 pylint==2.16.2 black~=24.1 -qiskit-sphinx-theme~=1.16.0 +qiskit-sphinx-theme~=2.0 sphinx-autodoc-typehints jupyter-sphinx # The following line is needed until From 77029ff54079a26b83c6136f733f4d27b751e29b Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 07:07:04 -0700 Subject: [PATCH 2/9] testing switching to plot directive --- docs/conf.py | 1 + docs/userguide/how_to_use_different_array_libraries.rst | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 8e0d3f754..36354d08e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,6 +37,7 @@ 'nbsphinx', 'sphinxcontrib.bibtex', "qiskit_sphinx_theme", + "matplotlib.sphinxext.plot_directive", ] templates_path = ["_templates"] diff --git a/docs/userguide/how_to_use_different_array_libraries.rst b/docs/userguide/how_to_use_different_array_libraries.rst index e125f032b..9557881b3 100644 --- a/docs/userguide/how_to_use_different_array_libraries.rst +++ b/docs/userguide/how_to_use_different_array_libraries.rst @@ -23,6 +23,11 @@ This guide addresses the following topics: First, configure JAX and import array libraries. +.. plot:: + + 1 + 1 + + .. jupyter-execute:: # configure jax to use 64 bit mode From e36e0427b0884b800f60a570cdebfc229fa50b94 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 07:10:21 -0700 Subject: [PATCH 3/9] fixing test to include source --- docs/userguide/how_to_use_different_array_libraries.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/userguide/how_to_use_different_array_libraries.rst b/docs/userguide/how_to_use_different_array_libraries.rst index 9557881b3..6e71e9c48 100644 --- a/docs/userguide/how_to_use_different_array_libraries.rst +++ b/docs/userguide/how_to_use_different_array_libraries.rst @@ -24,6 +24,7 @@ This guide addresses the following topics: First, configure JAX and import array libraries. .. plot:: + :include-source: 1 + 1 From 377401dd76204a5761fafc8130903b9f61d29c85 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 07:20:20 -0700 Subject: [PATCH 4/9] converting everything to use plot instead of jupyter execute --- docs/conf.py | 1 - .../Lindblad_dynamics_simulation.rst | 15 ++- docs/tutorials/Rabi_oscillations.rst | 12 ++- docs/tutorials/dynamics_backend.rst | 93 ++++++++++++------- docs/tutorials/optimizing_pulse_sequence.rst | 42 ++++++--- docs/tutorials/qiskit_pulse.rst | 18 ++-- .../how_to_configure_simulations.rst | 48 ++++++---- .../how_to_use_different_array_libraries.rst | 38 ++++---- .../how_to_use_pulse_schedule_for_jax_jit.rst | 12 ++- docs/userguide/perturbative_solvers.rst | 48 ++++++---- qiskit_dynamics/pulse/__init__.py | 3 +- qiskit_dynamics/signals/__init__.py | 6 +- 12 files changed, 217 insertions(+), 119 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 36354d08e..68ca060c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,6 @@ 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', 'sphinx.ext.extlinks', - 'jupyter_sphinx', 'sphinx_autodoc_typehints', 'reno.sphinxext', 'sphinx.ext.intersphinx', diff --git a/docs/tutorials/Lindblad_dynamics_simulation.rst b/docs/tutorials/Lindblad_dynamics_simulation.rst index 793706166..194238767 100644 --- a/docs/tutorials/Lindblad_dynamics_simulation.rst +++ b/docs/tutorials/Lindblad_dynamics_simulation.rst @@ -64,7 +64,8 @@ syntax of ``Pauli`` classes to indicate a qubit number, as below. Below, we first set the number of qubits :math:`N` to be simulated, and then prepare and store the single-qubit Pauli operators that will be used in the rest of this tutorial. -.. jupyter-execute:: +.. plot:: + :include-source: import numpy as np from qiskit.quantum_info import Operator, Pauli @@ -100,7 +101,8 @@ two-qubit terms. Since there are no time-dependent terms, and we do not plan to derivatives of parameters, we do not use the :class:`Signal` class in this tutorial. See the other tutorials for various generalizations of this approach supported with ``qiskit-dynamics``. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_dynamics import Solver, Signal @@ -140,7 +142,8 @@ tutorials for various generalizations of this approach supported with ``qiskit-d We now define the initial state for the simulation, the time span to simulate for, and the intermediate times for which the solution is requested. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit.quantum_info import DensityMatrix @@ -175,7 +178,8 @@ invariant as well. Hence the mean Bloch vector should be equal to any qubit’s observing that this equality holds is a simple and useful verification of the numerical solution that will be added in the next section. -.. jupyter-execute:: +.. plot:: + :include-source: n_times = len(sol.y) x_data = np.zeros((N, n_times)) @@ -218,7 +222,8 @@ tilt along :math:`+x`, while for :math:`J=3` it will significantly shorten (the a mixed state), becoming tilted along :math:`-y`. This complex dependence of the Bloch vector on the parameters can be systematically analyzed - we encourage you to try it! -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit.visualization import plot_bloch_vector import matplotlib.pyplot as plt diff --git a/docs/tutorials/Rabi_oscillations.rst b/docs/tutorials/Rabi_oscillations.rst index 729dfd757..a9e8a7337 100644 --- a/docs/tutorials/Rabi_oscillations.rst +++ b/docs/tutorials/Rabi_oscillations.rst @@ -35,7 +35,8 @@ then setup the :class:`.Solver` class instance that stores and manipulates the m using matrices and :class:`.Signal` instances. For the time-independent :math:`z` term we set the signal to a constant, while for the trasverse driving term we setup a harmonic signal. -.. jupyter-execute:: +.. plot:: + :include-source: import numpy as np from qiskit.quantum_info import Operator @@ -62,7 +63,8 @@ signal to a constant, while for the trasverse driving term we setup a harmonic s We now define the initial state for the simulation, the time span to simulate for, and the intermediate times for which the solution is requested, and solve the evolution. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit.quantum_info.states import Statevector from qiskit.quantum_info import DensityMatrix @@ -96,7 +98,8 @@ increases and decreases). This mechanism of Rabi oscillations is the basis for t gates used to manipulate quantum devices - in particular this is a realization of the :math:`X` gate. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit.visualization import plot_bloch_vector import matplotlib.pyplot as plt @@ -161,7 +164,8 @@ since in ``qiskit`` and in ``qiskit-dynamics`` the syntax of many functions is i state vectors and density matrices. The shrinking of the qubit’s state within the Bloch sphere due to the incoherent evolution can be clearly seen in the plots below. -.. jupyter-execute:: +.. plot:: + :include-source: Gamma_1 = .8 Gamma_2 = .2 diff --git a/docs/tutorials/dynamics_backend.rst b/docs/tutorials/dynamics_backend.rst index a78058248..e52d79bdf 100644 --- a/docs/tutorials/dynamics_backend.rst +++ b/docs/tutorials/dynamics_backend.rst @@ -28,14 +28,14 @@ when using a JAX solver method. Here we configure JAX to run on CPU in 64 bit mo :ref:`User Guide entry on using different array libraries with Qiskit Dynamics ` for more information. -.. jupyter-execute:: - :hide-code: +.. plot:: # a parallelism warning raised by JAX is being raised due to somethign outside of Dynamics import warnings warnings.filterwarnings("ignore", message="os.fork") -.. jupyter-execute:: +.. plot:: + :include-source: # Configure JAX import jax @@ -68,7 +68,8 @@ where - :math:`N_0` and :math:`N_1` are the number operators for qubits :math:`0` and :math:`1` respectively. -.. jupyter-execute:: +.. plot:: + :include-source: import numpy as np @@ -118,7 +119,8 @@ outcomes of :meth:`.DynamicsBackend.run` are independent of the choice of rotati :class:`.Solver`, and as such we are free to choose the rotating frame that provides the best performance. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_dynamics import Solver @@ -146,7 +148,8 @@ Furthermore, note that in the solver options we set the max step size to the pul ``dt`` via the ``"hmax"`` argument for the method ``"jax_odeint"``. This is important for preventing variable step solvers from accidentally stepping over pulses in schedules with long idle times. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_dynamics import DynamicsBackend @@ -182,7 +185,8 @@ that the usual instructions work on the :class:`.DynamicsBackend`. impact on the returned results. -.. jupyter-execute:: +.. plot:: + :include-source: %%time @@ -213,14 +217,16 @@ that the usual instructions work on the :class:`.DynamicsBackend`. Visualize one of the schedules. -.. jupyter-execute:: +.. plot:: + :include-source: schedules[3].draw() Retrieve the counts for one of the experiments as would be done using the results object from a real backend. -.. jupyter-execute:: +.. plot:: + :include-source: result.get_counts(3) @@ -238,7 +244,8 @@ contained in the :class:`.DynamicsBackend`. Build a simple circuit. Here we build one consisting of a single Hadamard gate on qubit :math:`0`, followed by measurement. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit import QuantumCircuit @@ -252,7 +259,8 @@ Next, attach a calibration for the Hadamard gate on qubit :math:`0` to the circu we are only demonstrating the mechanics of adding a calibration; we have not attempted to calibrate the schedule to implement the Hadamard gate with high fidelity. -.. jupyter-execute:: +.. plot:: + :include-source: with pulse.build() as h_q0: pulse.play( @@ -264,7 +272,8 @@ the schedule to implement the Hadamard gate with high fidelity. Call run on the circuit, and get counts as usual. -.. jupyter-execute:: +.. plot:: + :include-source: %time res = backend.run(circ).result() @@ -277,7 +286,8 @@ Alternatively to the above work flow, add the above schedule as the pulse-level Hadamard gate on qubit :math:`0` to `backend.target`, which impacts how jobs are transpiled for the backend. See the :class:`~qiskit.transpiler.Target` class documentation for further information. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit.circuit.library import HGate from qiskit.transpiler import InstructionProperties @@ -287,7 +297,8 @@ backend. See the :class:`~qiskit.transpiler.Target` class documentation for furt Rebuild the same circuit, however this time we do not need to add the calibration for the Hadamard gate to the circuit object. -.. jupyter-execute:: +.. plot:: + :include-source: circ2 = QuantumCircuit(1, 1) circ2.h(0) @@ -295,7 +306,8 @@ gate to the circuit object. %time result = backend.run(circ2).result() -.. jupyter-execute:: +.. plot:: + :include-source: result.get_counts(0) @@ -324,7 +336,8 @@ To enable running of the single qubit experiments, we add the following to the ` be utilizing it, this ensures that validation steps checking that the device is fully connected will pass. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit.circuit.library import XGate, SXGate, RZGate, CXGate from qiskit.circuit import Parameter @@ -364,7 +377,8 @@ object. Here we use the :class:`~qiskit_experiments.calibration_management.basis_gate_library.FixedFrequencyTransmon` template library to initialize our calibrations. -.. jupyter-execute:: +.. plot:: + :include-source: import pandas as pd from qiskit_experiments.calibration_management.calibrations import Calibrations @@ -380,7 +394,8 @@ template library to initialize our calibrations. Next, run a rough amplitude calibration for ``X`` and ``SX`` gates for both qubits. First, build the experiments. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_experiments.library.calibration import RoughXSXAmplitudeCal @@ -392,7 +407,8 @@ experiments. Run the Rabi experiments. -.. jupyter-execute:: +.. plot:: + :include-source: %%time rabi0_data = rabi0.run().block_for_results() @@ -400,17 +416,20 @@ Run the Rabi experiments. Plot the results. -.. jupyter-execute:: +.. plot:: + :include-source: rabi0_data.figure(0) -.. jupyter-execute:: +.. plot:: + :include-source: rabi1_data.figure(0) Observe the updated parameters for qubit 0. -.. jupyter-execute:: +.. plot:: + :include-source: pd.DataFrame(**cals.parameters_table(qubit_list=[0, ()], parameters="amp")) @@ -420,7 +439,8 @@ Observe the updated parameters for qubit 0. Run rough Drag parameter calibration for the ``X`` and ``SX`` gates. This follows the same procedure as above. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_experiments.library.calibration import RoughDragCal @@ -432,24 +452,28 @@ as above. cal_drag0.circuits()[5].draw(output="mpl") -.. jupyter-execute:: +.. plot:: + :include-source: %%time drag0_data = cal_drag0.run().block_for_results() drag1_data = cal_drag1.run().block_for_results() -.. jupyter-execute:: +.. plot:: + :include-source: drag0_data.figure(0) -.. jupyter-execute:: +.. plot:: + :include-source: drag1_data.figure(0) The updated calibrations object: -.. jupyter-execute:: +.. plot:: + :include-source: pd.DataFrame(**cals.parameters_table(qubit_list=[0, ()], parameters="amp")) @@ -463,7 +487,8 @@ channel map, which is a dictionary mapping control-target qubit index pairs (giv the control channel index used to drive the corresponding cross-resonance interaction. This is required by the experiment to determine which channel to drive for each control-target pair. -.. jupyter-execute:: +.. plot:: + :include-source: # set the control channel map backend.set_options(control_channel_map={(0, 1): 0, (1, 0): 1}) @@ -471,7 +496,8 @@ required by the experiment to determine which channel to drive for each control- Build the characterization experiment object, and update gate definitions in ``target`` with the values for the single qubit gates calibrated above. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_experiments.library import CrossResonanceHamiltonian @@ -483,17 +509,20 @@ values for the single qubit gates calibrated above. backend.target.update_from_instruction_schedule_map(cals.get_inst_map()) -.. jupyter-execute:: +.. plot:: + :include-source: cr_ham_experiment.circuits()[10].draw("mpl") Run the simulation. -.. jupyter-execute:: +.. plot:: + :include-source: %time data_cr = cr_ham_experiment.run().block_for_results() -.. jupyter-execute:: +.. plot:: + :include-source: data_cr.figure(0) diff --git a/docs/tutorials/optimizing_pulse_sequence.rst b/docs/tutorials/optimizing_pulse_sequence.rst index 2c3f25191..1176bf3e6 100644 --- a/docs/tutorials/optimizing_pulse_sequence.rst +++ b/docs/tutorials/optimizing_pulse_sequence.rst @@ -24,7 +24,8 @@ We will optimize an :math:`X`-gate on a model of a qubit system using the follow First, set JAX to operate in 64-bit mode and to run on CPU. -.. jupyter-execute:: +.. plot:: + :include-source: import jax jax.config.update("jax_enable_x64", True) @@ -51,7 +52,8 @@ In the above: We will setup the problem to be in the rotating frame of the drift term. -.. jupyter-execute:: +.. plot:: + :include-source: import numpy as np from qiskit.quantum_info import Operator @@ -100,7 +102,8 @@ We remark that there are many other parameterizations that may achieve the same more efficient strategies for achieving a value of :math:`0` at the beginning and end of the pulse. This is only meant to demonstrate the need for such an approach, and one simple example of one. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_dynamics import DiscreteSignal from qiskit_dynamics.signals import Convolution @@ -134,7 +137,8 @@ This is only meant to demonstrate the need for such an approach, and one simple Observe, for example, the signal generated when all parameters are :math:`10^8`: -.. jupyter-execute:: +.. plot:: + :include-source: signal = signal_mapping(np.ones(80) * 1e8) signal.draw(t0=0., tf=signal.duration * signal.dt, n=1000, function='envelope') @@ -148,7 +152,8 @@ the pulse via the standard fidelity measure: .. math:: f(U) = \frac{|\text{Tr}(XU)|^2}{4} -.. jupyter-execute:: +.. plot:: + :include-source: X_op = Operator.from_label('X').data @@ -164,7 +169,8 @@ The function we want to optimize consists of: - Simulating the Schrodinger equation over the length of the pulse sequence. - Computing and return the infidelity (we minimize :math:`1 - f(U)`). -.. jupyter-execute:: +.. plot:: + :include-source: def objective(params): @@ -199,7 +205,8 @@ Finally, we gradient optimize the objective: - Call ``scipy.optimize.minimize`` with the above, with ``method='BFGS'`` and ``jac=True`` to indicate that the passed objective also computes the gradient. -.. jupyter-execute:: +.. plot:: + :include-source: from jax import jit, value_and_grad from scipy.optimize import minimize @@ -220,7 +227,8 @@ solver. We can draw the optimized signal, which is retrieved by applying the ``signal_mapping`` to the optimized parameters. -.. jupyter-execute:: +.. plot:: + :include-source: opt_signal = signal_mapping(opt_results.x) @@ -236,7 +244,8 @@ optimized parameters. Summing the signal samples yields approximately :math:`\pm 50`, which is equivalent to what one would expect based on a rotating wave approximation analysis. -.. jupyter-execute:: +.. plot:: + :include-source: opt_signal.samples.sum() @@ -252,7 +261,8 @@ instance, parameterized by ``sigma`` and ``width``. Although qiskit pulse provid :class:`~qiskit.pulse.library.GaussianSquare`, this class is not JAX compatible. See the user guide entry on :ref:`JAX-compatible pulse schedules `. -.. jupyter-execute:: +.. plot:: + :include-source: import sympy as sym from qiskit import pulse @@ -310,7 +320,8 @@ entry on :ref:`JAX-compatible pulse schedules `_. These can be imported as: -.. jupyter-execute:: +.. plot:: + :include-source: # alias for NumPy and corresponding aliased library from qiskit_dynamics import DYNAMICS_NUMPY_ALIAS @@ -114,7 +114,8 @@ scans over a control parameter. First, we construct a :class:`.Solver` instance with a simple qubit model. -.. jupyter-execute:: +.. plot:: + :include-source: import numpy as np from qiskit.quantum_info import Operator @@ -142,7 +143,8 @@ Next, define the function to be compiled: - The output is the state of the system, starting in the ground state, at ``100`` points over the total evolution time. -.. jupyter-execute:: +.. plot:: + :include-source: def sim_function(amp): @@ -162,7 +164,8 @@ Next, define the function to be compiled: Compile the function. -.. jupyter-execute:: +.. plot:: + :include-source: from jax import jit fast_sim = jit(sim_function) @@ -171,7 +174,8 @@ The first time the function is called, JAX will compile an `XLA ` for a more detailed explanation of how to work with JAX in Qiskit Dynamics. -.. jupyter-execute:: +.. plot:: + :include-source: # configure jax to use 64 bit mode import jax @@ -73,7 +74,8 @@ the model, which will slow down both the ODE solver and the initial construction solver. However after the initial construction, the higher frequencies in the model have no impact on the perturbative solver speed. -.. jupyter-execute:: +.. plot:: + :include-source: import numpy as np @@ -124,7 +126,8 @@ See the :class:`.DysonSolver` API docs for more details. For our example Hamiltonian we configure the :class:`.DysonSolver` as follows: -.. jupyter-execute:: +.. plot:: + :include-source: %%time @@ -159,7 +162,8 @@ over the interval ``[0, (T // dt) * dt]`` for an on-resonance drive with envelop ``envelope_func`` above. Running compiled versions of these functions gives a sense of the speeds attainable by these solvers. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_dynamics import Signal from jax import jit @@ -180,7 +184,8 @@ attainable by these solvers. First run includes compile time. -.. jupyter-execute:: +.. plot:: + :include-source: %time yf_dyson = dyson_sim(1.).block_until_ready() @@ -188,7 +193,8 @@ First run includes compile time. Once JIT compilation has been performance we can benchmark the performance of the JIT-compiled solver: -.. jupyter-execute:: +.. plot:: + :include-source: %time yf_dyson = dyson_sim(1.).block_until_ready() @@ -199,7 +205,8 @@ solver: We now construct the same simulation using a standard solver to compare accuracy and simulation speed. -.. jupyter-execute:: +.. plot:: + :include-source: from qiskit_dynamics import Solver @@ -224,7 +231,8 @@ speed. Simulate with low tolerance for comparison to high accuracy solution. -.. jupyter-execute:: +.. plot:: + :include-source: yf_low_tol = ode_sim(1., 1e-13) np.linalg.norm(yf_low_tol - yf_dyson) @@ -232,7 +240,8 @@ Simulate with low tolerance for comparison to high accuracy solution. For speed comparison, compile at a tolerance with similar accuracy. -.. jupyter-execute:: +.. plot:: + :include-source: jit_ode_sim = jit(lambda amp: ode_sim(amp, 1e-8)) @@ -240,14 +249,16 @@ For speed comparison, compile at a tolerance with similar accuracy. Measure compiled time. -.. jupyter-execute:: +.. plot:: + :include-source: %time yf_ode = jit_ode_sim(1.).block_until_ready() Confirm similar accuracy solution. -.. jupyter-execute:: +.. plot:: + :include-source: np.linalg.norm(yf_low_tol - yf_ode) @@ -262,7 +273,8 @@ Next, we repeat our example using the Magnus-based perturbative solver. Setup of :class:`.MagnusSolver` is similar to the :class:`.DysonSolver`, but it uses the Magnus expansion and matrix exponentiation to simulate over each fixed time step. -.. jupyter-execute:: +.. plot:: + :include-source: %%time @@ -284,7 +296,8 @@ matrix exponentiation to simulate over each fixed time step. Setup simulation function. -.. jupyter-execute:: +.. plot:: + :include-source: @jit def magnus_sim(amp): @@ -299,18 +312,21 @@ Setup simulation function. First run includes compile time. -.. jupyter-execute:: +.. plot:: + :include-source: %time yf_magnus = magnus_sim(1.).block_until_ready() Second run demonstrates speed of the simulation. -.. jupyter-execute:: +.. plot:: + :include-source: %time yf_magnus = magnus_sim(1.).block_until_ready() -.. jupyter-execute:: +.. plot:: + :include-source: np.linalg.norm(yf_magnus - yf_low_tol) diff --git a/qiskit_dynamics/pulse/__init__.py b/qiskit_dynamics/pulse/__init__.py index 99733ab58..75c1c0712 100644 --- a/qiskit_dynamics/pulse/__init__.py +++ b/qiskit_dynamics/pulse/__init__.py @@ -40,8 +40,7 @@ An example schedule, and the corresponding converted signals, is shown below. -.. jupyter-execute:: - :hide-code: +.. plot:: import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec diff --git a/qiskit_dynamics/signals/__init__.py b/qiskit_dynamics/signals/__init__.py index 8e4e41225..839e1fdc9 100644 --- a/qiskit_dynamics/signals/__init__.py +++ b/qiskit_dynamics/signals/__init__.py @@ -142,8 +142,7 @@ signal superimposed with sampled versions, both in the case of sampling the carrier, and in the case of sampling just the envelope (and keeping the carrier analog). -.. jupyter-execute:: - :hide-code: +.. plot:: from qiskit_dynamics.signals import Signal, DiscreteSignal from matplotlib import pyplot as plt @@ -177,7 +176,8 @@ carrier frequency of 400 MHz to create a signal at 500 MHz. Note that the code below does not make any assumptions about the time and frequency units which we interpret as ns and GHz, respectively. -.. jupyter-execute:: +.. plot:: + :include-source: import numpy as np from qiskit_dynamics.signals import DiscreteSignal, Sampler, IQMixer From ff7a4a74ed948a3b84e390ac4d4a48d227abd1f1 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 07:35:39 -0700 Subject: [PATCH 5/9] removing cell magic --- docs/tutorials/dynamics_backend.rst | 39 ++++++++++---- docs/tutorials/qiskit_pulse.rst | 7 ++- .../how_to_configure_simulations.rst | 37 ++++++++++++-- .../how_to_use_different_array_libraries.rst | 12 ++++- docs/userguide/perturbative_solvers.rst | 51 +++++++++++++++---- 5 files changed, 119 insertions(+), 27 deletions(-) diff --git a/docs/tutorials/dynamics_backend.rst b/docs/tutorials/dynamics_backend.rst index e52d79bdf..c943ba6f9 100644 --- a/docs/tutorials/dynamics_backend.rst +++ b/docs/tutorials/dynamics_backend.rst @@ -188,10 +188,9 @@ that the usual instructions work on the :class:`.DynamicsBackend`. .. plot:: :include-source: - %%time - + import time from qiskit import pulse - + sigma = 128 num_samples = 256 @@ -211,10 +210,14 @@ that the usual instructions work on the :class:`.DynamicsBackend`. pulse.acquire(duration=1, qubit_or_channel=0, register=pulse.MemorySlot(0)) schedules.append(schedule) - + + start_time = time.time() + job = backend.run(schedules, shots=100) result = job.result() + print(f"Run time: {time.time() - start_time}") + Visualize one of the schedules. .. plot:: @@ -275,8 +278,12 @@ Call run on the circuit, and get counts as usual. .. plot:: :include-source: - %time res = backend.run(circ).result() + start_time = time.time() + res = backend.run(circ).result() + + print(f"Run time: {time.time() - start_time}") + res.get_counts(0) 4.2 Simulating circuits via gate definitions in the backend :class:`~qiskit.transpiler.Target` @@ -304,7 +311,11 @@ gate to the circuit object. circ2.h(0) circ2.measure([0], [0]) - %time result = backend.run(circ2).result() + start_time = time.time() + + result = backend.run(circ2).result() + + print(f"Run time: {time.time() - start_time}") .. plot:: :include-source: @@ -410,10 +421,13 @@ Run the Rabi experiments. .. plot:: :include-source: - %%time + start_time = time.time() + rabi0_data = rabi0.run().block_for_results() rabi1_data = rabi1.run().block_for_results() + print(f"Run time: {time.time() - start_time}") + Plot the results. .. plot:: @@ -455,10 +469,13 @@ as above. .. plot:: :include-source: - %%time + start_time = time.time() + drag0_data = cal_drag0.run().block_for_results() drag1_data = cal_drag1.run().block_for_results() + print(f"Run time: {time.time() - start_time}") + .. plot:: :include-source: @@ -519,7 +536,11 @@ Run the simulation. .. plot:: :include-source: - %time data_cr = cr_ham_experiment.run().block_for_results() + start_time = time.time() + + data_cr = cr_ham_experiment.run().block_for_results() + + print(f"Run time: {time.time() - start_time}") .. plot:: diff --git a/docs/tutorials/qiskit_pulse.rst b/docs/tutorials/qiskit_pulse.rst index 22d8e96d8..354228015 100644 --- a/docs/tutorials/qiskit_pulse.rst +++ b/docs/tutorials/qiskit_pulse.rst @@ -137,12 +137,17 @@ should produce identical behavior. .. plot:: :include-source: + import time from qiskit.quantum_info.states import Statevector # Start the qubit in its ground state. y0 = Statevector([1., 0.]) - %time sol = hamiltonian_solver.solve(t_span=[0., 2*T], y0=y0, signals=sxp, atol=1e-8, rtol=1e-8) + start_time = time.time() + + sol = hamiltonian_solver.solve(t_span=[0., 2*T], y0=y0, signals=sxp, atol=1e-8, rtol=1e-8) + + print(f"Run time: {time.time() - start_time}") .. plot:: diff --git a/docs/userguide/how_to_configure_simulations.rst b/docs/userguide/how_to_configure_simulations.rst index 0490e1723..b7909adac 100644 --- a/docs/userguide/how_to_configure_simulations.rst +++ b/docs/userguide/how_to_configure_simulations.rst @@ -74,13 +74,20 @@ timing the solver. .. plot:: :include-source: + import time + solver = Solver( static_hamiltonian=static_hamiltonian, hamiltonian_operators=[drive_hamiltonian], ) y0 = np.eye(dim, dtype=complex) - %time results = solver.solve(t_span=[0., T], y0=y0, signals=[drive_signal], atol=1e-10, rtol=1e-10) + + start_time = time.time() + + results = solver.solve(t_span=[0., T], y0=y0, signals=[drive_signal], atol=1e-10, rtol=1e-10) + + print(f"Run time: {time.time() - start_time}") Next, define a :class:`.Solver` in the rotating frame of the static Hamiltonian by setting the ``rotating_frame`` kwarg, and solve, again timing the solver. @@ -95,7 +102,12 @@ Next, define a :class:`.Solver` in the rotating frame of the static Hamiltonian ) y0 = np.eye(dim, dtype=complex) - %time rf_results = rf_solver.solve(t_span=[0., T], y0=y0, signals=[drive_signal], atol=1e-10, rtol=1e-10) + + start_time = time.time() + + rf_results = rf_solver.solve(t_span=[0., T], y0=y0, signals=[drive_signal], atol=1e-10, rtol=1e-10) + + print(f"Run time: {time.time() - start_time}") Observe that despite the two simulation problems being mathematically equivalent, it takes less time to solve in the rotating frame. @@ -170,7 +182,12 @@ frequencies relative to which the cutoff should be applied: ) y0 = np.eye(dim, dtype=complex) - %time rwa_results = rwa_solver.solve(t_span=[0., T], y0=y0, signals=[drive_signal], atol=1e-10, rtol=1e-10) + + start_time = time.time() + + rwa_results = rwa_solver.solve(t_span=[0., T], y0=y0, signals=[drive_signal], atol=1e-10, rtol=1e-10) + + print(f"Run time: {time.time() - start_time}") We observe a further reduction in time, which is a result of the solver requiring even fewer RHS evaluations with the RWA: @@ -310,7 +327,12 @@ Run the dense simulation (twice to see the true compiled speed). :include-source: yf = jitted_dense_func(1.).block_until_ready() - %time yf = jitted_dense_func(1.).block_until_ready() + + start_time = time.time() + + yf = jitted_dense_func(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") Run the sparse solver (twice to see the true compiled speed). @@ -318,7 +340,12 @@ Run the sparse solver (twice to see the true compiled speed). :include-source: yf_sparse = jitted_sparse_func(1.).block_until_ready() - %time yf_sparse = jitted_sparse_func(1.).block_until_ready() + + start_time = time.time() + + yf_sparse = jitted_sparse_func(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") Verify equality of the results in a common frame. diff --git a/docs/userguide/how_to_use_different_array_libraries.rst b/docs/userguide/how_to_use_different_array_libraries.rst index bc59ef050..1c1bc6806 100644 --- a/docs/userguide/how_to_use_different_array_libraries.rst +++ b/docs/userguide/how_to_use_different_array_libraries.rst @@ -177,7 +177,11 @@ compilation time. .. plot:: :include-source: - %time ys = fast_sim(1.).block_until_ready() + start_time = time.time() + + ys = fast_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") On subsequent calls the compiled function is directly executed, demonstrating the true speed of the @@ -186,7 +190,11 @@ compiled function. .. plot:: :include-source: - %timeit fast_sim(1.).block_until_ready() + start_time = time.time() + + fast_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") We use this function to plot the :math:`Z` expectation value over a range of input amplitudes. diff --git a/docs/userguide/perturbative_solvers.rst b/docs/userguide/perturbative_solvers.rst index 809c23798..84580cb0a 100644 --- a/docs/userguide/perturbative_solvers.rst +++ b/docs/userguide/perturbative_solvers.rst @@ -129,10 +129,11 @@ For our example Hamiltonian we configure the :class:`.DysonSolver` as follows: .. plot:: :include-source: - %%time - + import time from qiskit_dynamics import DysonSolver + start_time = time.time() + dt = 0.1 dyson_solver = DysonSolver( operators=[-1j * drive_hamiltonian], @@ -146,6 +147,8 @@ For our example Hamiltonian we configure the :class:`.DysonSolver` as follows: rtol=1e-12 ) + print(f"Run time: {time.time() - start_time}") + The above parameters are chosen so that the :class:`.DysonSolver` is fast and produces high accuracy solutions (measured and confirmed after the fact). The relatively large step size ``dt = 0.1`` is chosen for speed: the larger the step size, the fewer steps required. To ensure high accuracy given @@ -187,7 +190,13 @@ First run includes compile time. .. plot:: :include-source: - %time yf_dyson = dyson_sim(1.).block_until_ready() + import time + + start_time = time.time() + + yf_dyson = dyson_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") Once JIT compilation has been performance we can benchmark the performance of the JIT-compiled @@ -196,7 +205,11 @@ solver: .. plot:: :include-source: - %time yf_dyson = dyson_sim(1.).block_until_ready() + start_time = time.time() + + yf_dyson = dyson_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") 4. Comparison to traditional ODE solver @@ -245,14 +258,22 @@ For speed comparison, compile at a tolerance with similar accuracy. jit_ode_sim = jit(lambda amp: ode_sim(amp, 1e-8)) - %time yf_ode = jit_ode_sim(1.).block_until_ready() + start_time = time.time() + + yf_ode = jit_ode_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") Measure compiled time. .. plot:: :include-source: - %time yf_ode = jit_ode_sim(1.).block_until_ready() + start_time = time.time() + + yf_ode = jit_ode_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") Confirm similar accuracy solution. @@ -276,10 +297,10 @@ matrix exponentiation to simulate over each fixed time step. .. plot:: :include-source: - %%time - from qiskit_dynamics import MagnusSolver + start_time = time.time() + dt = 0.1 magnus_solver = MagnusSolver( operators=[-1j * drive_hamiltonian], @@ -293,6 +314,8 @@ matrix exponentiation to simulate over each fixed time step. rtol=1e-12 ) + print(f"Run time: {time.time() - start_time}") + Setup simulation function. @@ -315,14 +338,22 @@ First run includes compile time. .. plot:: :include-source: - %time yf_magnus = magnus_sim(1.).block_until_ready() + start_time = time.time() + + yf_magnus = magnus_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") Second run demonstrates speed of the simulation. .. plot:: :include-source: - %time yf_magnus = magnus_sim(1.).block_until_ready() + start_time = time.time() + + yf_magnus = magnus_sim(1.).block_until_ready() + + print(f"Run time: {time.time() - start_time}") .. plot:: From 6a9408d8cc6ca86e885105bc65518078985c82d9 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 07:48:36 -0700 Subject: [PATCH 6/9] adding context --- .../Lindblad_dynamics_simulation.rst | 5 +++ docs/tutorials/Rabi_oscillations.rst | 4 +++ docs/tutorials/dynamics_backend.rst | 31 +++++++++++++++++++ docs/tutorials/optimizing_pulse_sequence.rst | 14 +++++++++ docs/tutorials/qiskit_pulse.rst | 6 ++++ .../how_to_configure_simulations.rst | 16 ++++++++++ .../how_to_use_different_array_libraries.rst | 12 +++++++ .../how_to_use_pulse_schedule_for_jax_jit.rst | 4 +++ docs/userguide/perturbative_solvers.rst | 16 ++++++++++ qiskit_dynamics/pulse/__init__.py | 1 + qiskit_dynamics/signals/__init__.py | 2 ++ 11 files changed, 111 insertions(+) diff --git a/docs/tutorials/Lindblad_dynamics_simulation.rst b/docs/tutorials/Lindblad_dynamics_simulation.rst index 194238767..dc1020ffb 100644 --- a/docs/tutorials/Lindblad_dynamics_simulation.rst +++ b/docs/tutorials/Lindblad_dynamics_simulation.rst @@ -65,6 +65,7 @@ Below, we first set the number of qubits :math:`N` to be simulated, and then pre single-qubit Pauli operators that will be used in the rest of this tutorial. .. plot:: + :context: :include-source: import numpy as np @@ -102,6 +103,7 @@ derivatives of parameters, we do not use the :class:`Signal` class in this tutor tutorials for various generalizations of this approach supported with ``qiskit-dynamics``. .. plot:: + :context: :include-source: from qiskit_dynamics import Solver, Signal @@ -143,6 +145,7 @@ We now define the initial state for the simulation, the time span to simulate fo intermediate times for which the solution is requested. .. plot:: + :context: :include-source: from qiskit.quantum_info import DensityMatrix @@ -179,6 +182,7 @@ observing that this equality holds is a simple and useful verification of the nu that will be added in the next section. .. plot:: + :context: :include-source: n_times = len(sol.y) @@ -223,6 +227,7 @@ a mixed state), becoming tilted along :math:`-y`. This complex dependence of the parameters can be systematically analyzed - we encourage you to try it! .. plot:: + :context: :include-source: from qiskit.visualization import plot_bloch_vector diff --git a/docs/tutorials/Rabi_oscillations.rst b/docs/tutorials/Rabi_oscillations.rst index a9e8a7337..6b4f83f67 100644 --- a/docs/tutorials/Rabi_oscillations.rst +++ b/docs/tutorials/Rabi_oscillations.rst @@ -36,6 +36,7 @@ using matrices and :class:`.Signal` instances. For the time-independent :math:`z signal to a constant, while for the trasverse driving term we setup a harmonic signal. .. plot:: + :context: :include-source: import numpy as np @@ -64,6 +65,7 @@ We now define the initial state for the simulation, the time span to simulate fo intermediate times for which the solution is requested, and solve the evolution. .. plot:: + :context: :include-source: from qiskit.quantum_info.states import Statevector @@ -99,6 +101,7 @@ gates used to manipulate quantum devices - in particular this is a realization o gate. .. plot:: + :context: :include-source: from qiskit.visualization import plot_bloch_vector @@ -165,6 +168,7 @@ state vectors and density matrices. The shrinking of the qubit’s state within to the incoherent evolution can be clearly seen in the plots below. .. plot:: + :context: :include-source: Gamma_1 = .8 diff --git a/docs/tutorials/dynamics_backend.rst b/docs/tutorials/dynamics_backend.rst index c943ba6f9..7de4521e1 100644 --- a/docs/tutorials/dynamics_backend.rst +++ b/docs/tutorials/dynamics_backend.rst @@ -29,12 +29,14 @@ when using a JAX solver method. Here we configure JAX to run on CPU in 64 bit mo array libraries>` for more information. .. plot:: + :context: # a parallelism warning raised by JAX is being raised due to somethign outside of Dynamics import warnings warnings.filterwarnings("ignore", message="os.fork") .. plot:: + :context: :include-source: # Configure JAX @@ -69,6 +71,7 @@ where respectively. .. plot:: + :context: :include-source: import numpy as np @@ -120,6 +123,7 @@ outcomes of :meth:`.DynamicsBackend.run` are independent of the choice of rotati performance. .. plot:: + :context: :include-source: from qiskit_dynamics import Solver @@ -149,6 +153,7 @@ Furthermore, note that in the solver options we set the max step size to the pul variable step solvers from accidentally stepping over pulses in schedules with long idle times. .. plot:: + :context: :include-source: from qiskit_dynamics import DynamicsBackend @@ -186,6 +191,7 @@ that the usual instructions work on the :class:`.DynamicsBackend`. .. plot:: + :context: :include-source: import time @@ -221,6 +227,7 @@ that the usual instructions work on the :class:`.DynamicsBackend`. Visualize one of the schedules. .. plot:: + :context: :include-source: schedules[3].draw() @@ -229,6 +236,7 @@ Retrieve the counts for one of the experiments as would be done using the result backend. .. plot:: + :context: :include-source: result.get_counts(3) @@ -248,6 +256,7 @@ Build a simple circuit. Here we build one consisting of a single Hadamard gate o followed by measurement. .. plot:: + :context: :include-source: from qiskit import QuantumCircuit @@ -263,6 +272,7 @@ we are only demonstrating the mechanics of adding a calibration; we have not att the schedule to implement the Hadamard gate with high fidelity. .. plot:: + :context: :include-source: with pulse.build() as h_q0: @@ -276,6 +286,7 @@ the schedule to implement the Hadamard gate with high fidelity. Call run on the circuit, and get counts as usual. .. plot:: + :context: :include-source: start_time = time.time() @@ -294,6 +305,7 @@ Hadamard gate on qubit :math:`0` to `backend.target`, which impacts how jobs are backend. See the :class:`~qiskit.transpiler.Target` class documentation for further information. .. plot:: + :context: :include-source: from qiskit.circuit.library import HGate @@ -305,6 +317,7 @@ Rebuild the same circuit, however this time we do not need to add the calibratio gate to the circuit object. .. plot:: + :context: :include-source: circ2 = QuantumCircuit(1, 1) @@ -318,6 +331,7 @@ gate to the circuit object. print(f"Run time: {time.time() - start_time}") .. plot:: + :context: :include-source: result.get_counts(0) @@ -348,6 +362,7 @@ To enable running of the single qubit experiments, we add the following to the ` will pass. .. plot:: + :context: :include-source: from qiskit.circuit.library import XGate, SXGate, RZGate, CXGate @@ -389,6 +404,7 @@ object. Here we use the template library to initialize our calibrations. .. plot:: + :context: :include-source: import pandas as pd @@ -406,6 +422,7 @@ Next, run a rough amplitude calibration for ``X`` and ``SX`` gates for both qubi experiments. .. plot:: + :context: :include-source: from qiskit_experiments.library.calibration import RoughXSXAmplitudeCal @@ -419,6 +436,7 @@ experiments. Run the Rabi experiments. .. plot:: + :context: :include-source: start_time = time.time() @@ -431,11 +449,13 @@ Run the Rabi experiments. Plot the results. .. plot:: + :context: :include-source: rabi0_data.figure(0) .. plot:: + :context: :include-source: rabi1_data.figure(0) @@ -443,6 +463,7 @@ Plot the results. Observe the updated parameters for qubit 0. .. plot:: + :context: :include-source: pd.DataFrame(**cals.parameters_table(qubit_list=[0, ()], parameters="amp")) @@ -454,6 +475,7 @@ Run rough Drag parameter calibration for the ``X`` and ``SX`` gates. This follow as above. .. plot:: + :context: :include-source: from qiskit_experiments.library.calibration import RoughDragCal @@ -467,6 +489,7 @@ as above. cal_drag0.circuits()[5].draw(output="mpl") .. plot:: + :context: :include-source: start_time = time.time() @@ -477,12 +500,14 @@ as above. print(f"Run time: {time.time() - start_time}") .. plot:: + :context: :include-source: drag0_data.figure(0) .. plot:: + :context: :include-source: drag1_data.figure(0) @@ -490,6 +515,7 @@ as above. The updated calibrations object: .. plot:: + :context: :include-source: pd.DataFrame(**cals.parameters_table(qubit_list=[0, ()], parameters="amp")) @@ -505,6 +531,7 @@ the control channel index used to drive the corresponding cross-resonance intera required by the experiment to determine which channel to drive for each control-target pair. .. plot:: + :context: :include-source: # set the control channel map @@ -514,6 +541,7 @@ Build the characterization experiment object, and update gate definitions in ``t values for the single qubit gates calibrated above. .. plot:: + :context: :include-source: from qiskit_experiments.library import CrossResonanceHamiltonian @@ -527,6 +555,7 @@ values for the single qubit gates calibrated above. backend.target.update_from_instruction_schedule_map(cals.get_inst_map()) .. plot:: + :context: :include-source: cr_ham_experiment.circuits()[10].draw("mpl") @@ -534,6 +563,7 @@ values for the single qubit gates calibrated above. Run the simulation. .. plot:: + :context: :include-source: start_time = time.time() @@ -544,6 +574,7 @@ Run the simulation. .. plot:: + :context: :include-source: data_cr.figure(0) diff --git a/docs/tutorials/optimizing_pulse_sequence.rst b/docs/tutorials/optimizing_pulse_sequence.rst index 1176bf3e6..21f4f76e1 100644 --- a/docs/tutorials/optimizing_pulse_sequence.rst +++ b/docs/tutorials/optimizing_pulse_sequence.rst @@ -25,6 +25,7 @@ We will optimize an :math:`X`-gate on a model of a qubit system using the follow First, set JAX to operate in 64-bit mode and to run on CPU. .. plot:: + :context: :include-source: import jax @@ -53,6 +54,7 @@ In the above: We will setup the problem to be in the rotating frame of the drift term. .. plot:: + :context: :include-source: import numpy as np @@ -103,6 +105,7 @@ more efficient strategies for achieving a value of :math:`0` at the beginning an This is only meant to demonstrate the need for such an approach, and one simple example of one. .. plot:: + :context: :include-source: from qiskit_dynamics import DiscreteSignal @@ -138,6 +141,7 @@ This is only meant to demonstrate the need for such an approach, and one simple Observe, for example, the signal generated when all parameters are :math:`10^8`: .. plot:: + :context: :include-source: signal = signal_mapping(np.ones(80) * 1e8) @@ -153,6 +157,7 @@ the pulse via the standard fidelity measure: .. math:: f(U) = \frac{|\text{Tr}(XU)|^2}{4} .. plot:: + :context: :include-source: X_op = Operator.from_label('X').data @@ -170,6 +175,7 @@ The function we want to optimize consists of: - Computing and return the infidelity (we minimize :math:`1 - f(U)`). .. plot:: + :context: :include-source: def objective(params): @@ -206,6 +212,7 @@ Finally, we gradient optimize the objective: indicate that the passed objective also computes the gradient. .. plot:: + :context: :include-source: from jax import jit, value_and_grad @@ -228,6 +235,7 @@ We can draw the optimized signal, which is retrieved by applying the ``signal_ma optimized parameters. .. plot:: + :context: :include-source: opt_signal = signal_mapping(opt_results.x) @@ -245,6 +253,7 @@ Summing the signal samples yields approximately :math:`\pm 50`, which is equival would expect based on a rotating wave approximation analysis. .. plot:: + :context: :include-source: opt_signal.samples.sum() @@ -262,6 +271,7 @@ instance, parameterized by ``sigma`` and ``width``. Although qiskit pulse provid entry on :ref:`JAX-compatible pulse schedules `. .. plot:: + :context: :include-source: import sympy as sym @@ -321,6 +331,7 @@ Next, we construct a pulse schedule using the above parametrized Gaussian square to a signal, and simulate the equation over the length of the pulse sequence. .. plot:: + :context: :include-source: from qiskit_dynamics.pulse import InstructionToSignals @@ -353,12 +364,14 @@ We set the initial values of ``sigma`` and ``width`` for the optimization as ``initial_params = np.array([10, 10])``. .. plot:: + :context: :include-source: initial_params = np.array([10, 10]) gaussian_square_generated_by_pulse(initial_params).draw() .. plot:: + :context: :include-source: from jax import jit, value_and_grad @@ -381,6 +394,7 @@ We set the initial values of ``sigma`` and ``width`` for the optimization as We can draw the optimized pulse, whose parameters are retrieved by ``opt_results.x``. .. plot:: + :context: :include-source: gaussian_square_generated_by_pulse(opt_results.x).draw() \ No newline at end of file diff --git a/docs/tutorials/qiskit_pulse.rst b/docs/tutorials/qiskit_pulse.rst index 354228015..c751932f5 100644 --- a/docs/tutorials/qiskit_pulse.rst +++ b/docs/tutorials/qiskit_pulse.rst @@ -33,6 +33,7 @@ steps: First, we use the pulse module in Qiskit to create a pulse schedule. .. plot:: + :context: :include-source: import numpy as np @@ -73,6 +74,7 @@ envelopes and the signals resulting from this conversion. The dashed line shows the virtual ``Z`` gate is applied. .. plot:: + :context: :include-source: from matplotlib import pyplot as plt @@ -102,6 +104,7 @@ carrier frequencies, and sample width ``dt``. Additionally, we setup this solver frame and perform the rotating wave approximation. .. plot:: + :context: :include-source: from qiskit.quantum_info.operators import Operator @@ -135,6 +138,7 @@ In the last step we perform the simulation and plot the results. Note that, as w should produce identical behavior. .. plot:: + :context: :include-source: import time @@ -151,6 +155,7 @@ should produce identical behavior. .. plot:: + :context: :include-source: def plot_populations(sol): @@ -174,6 +179,7 @@ the ``Y`` operator. Therefore, the second pulse, which drives around the ``Y``-a shift, has hardley any influence on the populations of the qubit. .. plot:: + :context: :include-source: plot_populations(sol) diff --git a/docs/userguide/how_to_configure_simulations.rst b/docs/userguide/how_to_configure_simulations.rst index b7909adac..ce7313622 100644 --- a/docs/userguide/how_to_configure_simulations.rst +++ b/docs/userguide/how_to_configure_simulations.rst @@ -42,6 +42,7 @@ where: First, construct the components of the model: .. plot:: + :context: :include-source: import numpy as np @@ -72,6 +73,7 @@ Construct a :class:`.Solver` for the model as stated, without entering a rotatin timing the solver. .. plot:: + :context: :include-source: import time @@ -93,6 +95,7 @@ Next, define a :class:`.Solver` in the rotating frame of the static Hamiltonian ``rotating_frame`` kwarg, and solve, again timing the solver. .. plot:: + :context: :include-source: rf_solver = Solver( @@ -123,6 +126,7 @@ To compare the results, we use the fidelity function for unitary matrices: where :math:`d` is the dimension. A value of :math:`1` indicates equality of the unitaries. .. plot:: + :context: :include-source: def fidelity(U, V): @@ -144,6 +148,7 @@ evaluations when solving the differential equation in each instance. The number for the first simulation (not in the rotating frame) was: .. plot:: + :context: :include-source: results.nfev @@ -151,6 +156,7 @@ for the first simulation (not in the rotating frame) was: Whereas the number of evaluations for the second simulation in the rotating frame was: .. plot:: + :context: :include-source: rf_results.nfev @@ -171,6 +177,7 @@ Construct a solver for the same problem, now specifying an RWA cutoff frequency frequencies relative to which the cutoff should be applied: .. plot:: + :context: :include-source: rwa_solver = Solver( @@ -193,6 +200,7 @@ We observe a further reduction in time, which is a result of the solver requirin evaluations with the RWA: .. plot:: + :context: :include-source: rwa_results.nfev @@ -201,6 +209,7 @@ This speed comes at the cost of lower accuracy, owing to the fact that RWA is a *approximation*, which modifies the structure of the solution: .. plot:: + :context: :include-source: U_rwa = rwa_solver.model.rotating_frame.state_out_of_frame(T, rwa_results.y[-1]) @@ -232,6 +241,7 @@ Dynamics. Start off by configuring JAX. .. plot:: + :context: :include-source: # configure jax to use 64 bit mode @@ -246,6 +256,7 @@ arrays. Furthermore, set up the initial state to be a single column vector, to f benefits of the sparse representation. .. plot:: + :context: :include-source: dim = 300 @@ -271,6 +282,7 @@ Construct standard dense solver in the rotating frame of the static Hamiltonian, to solve the system for a given amplitude, and just-in-time compile it using JAX. .. plot:: + :context: :include-source: solver = Solver( @@ -298,6 +310,7 @@ function to solve the system for a given amplitude, and just-in-time compile it. case the static Hamiltonian is already diagonal, but we explicitly highlight the need for this. .. plot:: + :context: :include-source: sparse_solver = Solver( @@ -324,6 +337,7 @@ case the static Hamiltonian is already diagonal, but we explicitly highlight the Run the dense simulation (twice to see the true compiled speed). .. plot:: + :context: :include-source: yf = jitted_dense_func(1.).block_until_ready() @@ -337,6 +351,7 @@ Run the dense simulation (twice to see the true compiled speed). Run the sparse solver (twice to see the true compiled speed). .. plot:: + :context: :include-source: yf_sparse = jitted_sparse_func(1.).block_until_ready() @@ -350,6 +365,7 @@ Run the sparse solver (twice to see the true compiled speed). Verify equality of the results in a common frame. .. plot:: + :context: :include-source: yf = solver.model.rotating_frame.state_out_of_frame(T, yf) diff --git a/docs/userguide/how_to_use_different_array_libraries.rst b/docs/userguide/how_to_use_different_array_libraries.rst index 1c1bc6806..2f3da23a0 100644 --- a/docs/userguide/how_to_use_different_array_libraries.rst +++ b/docs/userguide/how_to_use_different_array_libraries.rst @@ -24,6 +24,7 @@ This guide addresses the following topics: First, configure JAX and import array libraries. .. plot:: + :context: :include-source: # configure jax to use 64 bit mode @@ -40,6 +41,7 @@ First, configure JAX and import array libraries. Defining equivalent :class:`.Signal` instances, with envelope implemented in either NumPy or JAX. .. plot:: + :context: :include-source: from qiskit_dynamics import Signal @@ -57,6 +59,7 @@ Defining equivalent :class:`.Signal` instances, with envelope implemented in eit Evaluation of ``signal_numpy`` is executed with NumPy: .. plot:: + :context: :include-source: type(signal_numpy(0.1)) @@ -64,6 +67,7 @@ Evaluation of ``signal_numpy`` is executed with NumPy: Evaluation of ``signal_jax`` is executed with JAX: .. plot:: + :context: :include-source: type(signal_jax(0.1)) @@ -71,6 +75,7 @@ Evaluation of ``signal_jax`` is executed with JAX: JAX transformations can be applied to ``signal_jax``, e.g. just-in-time compilation: .. plot:: + :context: :include-source: from jax import jit @@ -86,6 +91,7 @@ Internally, Qiskit Dynamics uses an extension of the default NumPy and SciPy arr by `Arraylias `_. These can be imported as: .. plot:: + :context: :include-source: # alias for NumPy and corresponding aliased library @@ -115,6 +121,7 @@ scans over a control parameter. First, we construct a :class:`.Solver` instance with a simple qubit model. .. plot:: + :context: :include-source: import numpy as np @@ -144,6 +151,7 @@ Next, define the function to be compiled: total evolution time. .. plot:: + :context: :include-source: def sim_function(amp): @@ -165,6 +173,7 @@ Next, define the function to be compiled: Compile the function. .. plot:: + :context: :include-source: from jax import jit @@ -175,6 +184,7 @@ version of the function, which is then executed. Hence, the time taken on the fi compilation time. .. plot:: + :context: :include-source: start_time = time.time() @@ -188,6 +198,7 @@ On subsequent calls the compiled function is directly executed, demonstrating th compiled function. .. plot:: + :context: :include-source: start_time = time.time() @@ -200,6 +211,7 @@ compiled function. We use this function to plot the :math:`Z` expectation value over a range of input amplitudes. .. plot:: + :context: :include-source: import matplotlib.pyplot as plt diff --git a/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst b/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst index 1ba38cfaa..40c971d5a 100644 --- a/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst +++ b/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst @@ -31,6 +31,7 @@ Dynamics. First, configure JAX to run on CPU in 64 bit mode. .. plot:: + :context: :include-source: # configure jax to use 64 bit mode @@ -50,6 +51,7 @@ Gaussian pulse to use in optimization, we need to instantiate a the symbolic representation in `sympy`. .. plot:: + :context: :include-source: from qiskit import pulse @@ -78,6 +80,7 @@ the symbolic representation in `sympy`. Next, define the :class:`~qiskit.pulse.library.ScalableSymbolicPulse` using the above expression. .. plot:: + :context: :include-source: _t, _duration, _amp, _sigma, _angle = sym.symbols("t, duration, amp, sigma, angle") @@ -109,6 +112,7 @@ Using a Gaussian pulse as an example, we show that a function involving JAX-compiled (or more generally, JAX-transformed). .. plot:: + :context: :include-source: # use amplitude as the function argument diff --git a/docs/userguide/perturbative_solvers.rst b/docs/userguide/perturbative_solvers.rst index 84580cb0a..48f1197c4 100644 --- a/docs/userguide/perturbative_solvers.rst +++ b/docs/userguide/perturbative_solvers.rst @@ -54,6 +54,7 @@ different array libraries>` for a more detailed explanation of how to work with Dynamics. .. plot:: + :context: :include-source: # configure jax to use 64 bit mode @@ -75,6 +76,7 @@ solver. However after the initial construction, the higher frequencies in the mo on the perturbative solver speed. .. plot:: + :context: :include-source: import numpy as np @@ -127,6 +129,7 @@ See the :class:`.DysonSolver` API docs for more details. For our example Hamiltonian we configure the :class:`.DysonSolver` as follows: .. plot:: + :context: :include-source: import time @@ -166,6 +169,7 @@ over the interval ``[0, (T // dt) * dt]`` for an on-resonance drive with envelop attainable by these solvers. .. plot:: + :context: :include-source: from qiskit_dynamics import Signal @@ -188,6 +192,7 @@ attainable by these solvers. First run includes compile time. .. plot:: + :context: :include-source: import time @@ -203,6 +208,7 @@ Once JIT compilation has been performance we can benchmark the performance of th solver: .. plot:: + :context: :include-source: start_time = time.time() @@ -219,6 +225,7 @@ We now construct the same simulation using a standard solver to compare accuracy speed. .. plot:: + :context: :include-source: from qiskit_dynamics import Solver @@ -245,6 +252,7 @@ speed. Simulate with low tolerance for comparison to high accuracy solution. .. plot:: + :context: :include-source: yf_low_tol = ode_sim(1., 1e-13) @@ -254,6 +262,7 @@ Simulate with low tolerance for comparison to high accuracy solution. For speed comparison, compile at a tolerance with similar accuracy. .. plot:: + :context: :include-source: jit_ode_sim = jit(lambda amp: ode_sim(amp, 1e-8)) @@ -267,6 +276,7 @@ For speed comparison, compile at a tolerance with similar accuracy. Measure compiled time. .. plot:: + :context: :include-source: start_time = time.time() @@ -279,6 +289,7 @@ Measure compiled time. Confirm similar accuracy solution. .. plot:: + :context: :include-source: np.linalg.norm(yf_low_tol - yf_ode) @@ -295,6 +306,7 @@ Next, we repeat our example using the Magnus-based perturbative solver. Setup of matrix exponentiation to simulate over each fixed time step. .. plot:: + :context: :include-source: from qiskit_dynamics import MagnusSolver @@ -320,6 +332,7 @@ matrix exponentiation to simulate over each fixed time step. Setup simulation function. .. plot:: + :context: :include-source: @jit @@ -336,6 +349,7 @@ Setup simulation function. First run includes compile time. .. plot:: + :context: :include-source: start_time = time.time() @@ -347,6 +361,7 @@ First run includes compile time. Second run demonstrates speed of the simulation. .. plot:: + :context: :include-source: start_time = time.time() @@ -357,6 +372,7 @@ Second run demonstrates speed of the simulation. .. plot:: + :context: :include-source: np.linalg.norm(yf_magnus - yf_low_tol) diff --git a/qiskit_dynamics/pulse/__init__.py b/qiskit_dynamics/pulse/__init__.py index 75c1c0712..76697f16e 100644 --- a/qiskit_dynamics/pulse/__init__.py +++ b/qiskit_dynamics/pulse/__init__.py @@ -41,6 +41,7 @@ An example schedule, and the corresponding converted signals, is shown below. .. plot:: + :context: import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec diff --git a/qiskit_dynamics/signals/__init__.py b/qiskit_dynamics/signals/__init__.py index 839e1fdc9..751e8dbd0 100644 --- a/qiskit_dynamics/signals/__init__.py +++ b/qiskit_dynamics/signals/__init__.py @@ -143,6 +143,7 @@ of sampling just the envelope (and keeping the carrier analog). .. plot:: + :context: from qiskit_dynamics.signals import Signal, DiscreteSignal from matplotlib import pyplot as plt @@ -177,6 +178,7 @@ any assumptions about the time and frequency units which we interpret as ns and GHz, respectively. .. plot:: + :context: :include-source: import numpy as np From 8254718c3e9bd9de633c6bc25c17fbf7f10b66f0 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 07:58:36 -0700 Subject: [PATCH 7/9] removing %matplotlib cell magic --- docs/tutorials/Lindblad_dynamics_simulation.rst | 1 - docs/tutorials/Rabi_oscillations.rst | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/tutorials/Lindblad_dynamics_simulation.rst b/docs/tutorials/Lindblad_dynamics_simulation.rst index dc1020ffb..9602d2299 100644 --- a/docs/tutorials/Lindblad_dynamics_simulation.rst +++ b/docs/tutorials/Lindblad_dynamics_simulation.rst @@ -232,7 +232,6 @@ parameters can be systematically analyzed - we encourage you to try it! from qiskit.visualization import plot_bloch_vector import matplotlib.pyplot as plt - %matplotlib inline fontsize = 16 diff --git a/docs/tutorials/Rabi_oscillations.rst b/docs/tutorials/Rabi_oscillations.rst index 6b4f83f67..e5a84610c 100644 --- a/docs/tutorials/Rabi_oscillations.rst +++ b/docs/tutorials/Rabi_oscillations.rst @@ -106,7 +106,6 @@ gate. from qiskit.visualization import plot_bloch_vector import matplotlib.pyplot as plt - %matplotlib inline fontsize = 16 From a1dd40e1cba20d68452ff0456219f055fae01f4a Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 10:00:18 -0700 Subject: [PATCH 8/9] removing display commmand --- docs/tutorials/Lindblad_dynamics_simulation.rst | 4 ++-- docs/tutorials/Rabi_oscillations.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/Lindblad_dynamics_simulation.rst b/docs/tutorials/Lindblad_dynamics_simulation.rst index 9602d2299..898cd6a7a 100644 --- a/docs/tutorials/Lindblad_dynamics_simulation.rst +++ b/docs/tutorials/Lindblad_dynamics_simulation.rst @@ -244,8 +244,8 @@ parameters can be systematically analyzed - we encourage you to try it! ax.set_xlabel('$t$', fontsize = fontsize) ax.set_title('Mean Bloch vector vs. $t$', fontsize = fontsize) - display(plot_bloch_vector([x_mean[-1], y_mean[-1], z_mean[-1]], - f'Mean Bloch vector at $t = {t_eval[-1]}$')) + plot_bloch_vector([x_mean[-1], y_mean[-1], z_mean[-1]], + f'Mean Bloch vector at $t = {t_eval[-1]}$') if N > 1 and ((abs(x_mean[-1]) > 1e-5 and abs(x_data[0, -1] / x_mean[-1] - 1) > 1e-5 or (abs(z_mean[-1]) > 1e-5 and abs(z_data[1, -1] / z_mean[-1] - 1) > 1e-5))): diff --git a/docs/tutorials/Rabi_oscillations.rst b/docs/tutorials/Rabi_oscillations.rst index e5a84610c..f498a56ad 100644 --- a/docs/tutorials/Rabi_oscillations.rst +++ b/docs/tutorials/Rabi_oscillations.rst @@ -130,8 +130,8 @@ gate. ax.set_title('Bloch vector vs. $t$', fontsize = fontsize) plt.show() - display(plot_bloch_vector([x_data[-1], y_data[-1], z_data[-1]], - f'Bloch vector at $t = {t_eval[-1]}$')) + plot_bloch_vector([x_data[-1], y_data[-1], z_data[-1]], + f'Bloch vector at $t = {t_eval[-1]}$') plot_qubit_dynamics(sol, t_eval, X, Y, Z) From 2b81e14e47b149a10a256eed60a743f6a6f5a74b Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Tue, 24 Sep 2024 10:23:27 -0700 Subject: [PATCH 9/9] adding close-figs option --- .../Lindblad_dynamics_simulation.rst | 10 +-- docs/tutorials/Rabi_oscillations.rst | 8 +-- docs/tutorials/dynamics_backend.rst | 62 +++++++++---------- docs/tutorials/optimizing_pulse_sequence.rst | 28 ++++----- docs/tutorials/qiskit_pulse.rst | 12 ++-- .../how_to_configure_simulations.rst | 32 +++++----- .../how_to_use_different_array_libraries.rst | 24 +++---- .../how_to_use_pulse_schedule_for_jax_jit.rst | 8 +-- docs/userguide/perturbative_solvers.rst | 32 +++++----- qiskit_dynamics/pulse/__init__.py | 2 +- qiskit_dynamics/signals/__init__.py | 4 +- 11 files changed, 111 insertions(+), 111 deletions(-) diff --git a/docs/tutorials/Lindblad_dynamics_simulation.rst b/docs/tutorials/Lindblad_dynamics_simulation.rst index 898cd6a7a..64c170706 100644 --- a/docs/tutorials/Lindblad_dynamics_simulation.rst +++ b/docs/tutorials/Lindblad_dynamics_simulation.rst @@ -65,7 +65,7 @@ Below, we first set the number of qubits :math:`N` to be simulated, and then pre single-qubit Pauli operators that will be used in the rest of this tutorial. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -103,7 +103,7 @@ derivatives of parameters, we do not use the :class:`Signal` class in this tutor tutorials for various generalizations of this approach supported with ``qiskit-dynamics``. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import Solver, Signal @@ -145,7 +145,7 @@ We now define the initial state for the simulation, the time span to simulate fo intermediate times for which the solution is requested. .. plot:: - :context: + :context: close-figs :include-source: from qiskit.quantum_info import DensityMatrix @@ -182,7 +182,7 @@ observing that this equality holds is a simple and useful verification of the nu that will be added in the next section. .. plot:: - :context: + :context: close-figs :include-source: n_times = len(sol.y) @@ -227,7 +227,7 @@ a mixed state), becoming tilted along :math:`-y`. This complex dependence of the parameters can be systematically analyzed - we encourage you to try it! .. plot:: - :context: + :context: close-figs :include-source: from qiskit.visualization import plot_bloch_vector diff --git a/docs/tutorials/Rabi_oscillations.rst b/docs/tutorials/Rabi_oscillations.rst index f498a56ad..ac69b0d4d 100644 --- a/docs/tutorials/Rabi_oscillations.rst +++ b/docs/tutorials/Rabi_oscillations.rst @@ -36,7 +36,7 @@ using matrices and :class:`.Signal` instances. For the time-independent :math:`z signal to a constant, while for the trasverse driving term we setup a harmonic signal. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -65,7 +65,7 @@ We now define the initial state for the simulation, the time span to simulate fo intermediate times for which the solution is requested, and solve the evolution. .. plot:: - :context: + :context: close-figs :include-source: from qiskit.quantum_info.states import Statevector @@ -101,7 +101,7 @@ gates used to manipulate quantum devices - in particular this is a realization o gate. .. plot:: - :context: + :context: close-figs :include-source: from qiskit.visualization import plot_bloch_vector @@ -167,7 +167,7 @@ state vectors and density matrices. The shrinking of the qubit’s state within to the incoherent evolution can be clearly seen in the plots below. .. plot:: - :context: + :context: close-figs :include-source: Gamma_1 = .8 diff --git a/docs/tutorials/dynamics_backend.rst b/docs/tutorials/dynamics_backend.rst index 7de4521e1..fe9ae1e59 100644 --- a/docs/tutorials/dynamics_backend.rst +++ b/docs/tutorials/dynamics_backend.rst @@ -29,14 +29,14 @@ when using a JAX solver method. Here we configure JAX to run on CPU in 64 bit mo array libraries>` for more information. .. plot:: - :context: + :context: close-figs # a parallelism warning raised by JAX is being raised due to somethign outside of Dynamics import warnings warnings.filterwarnings("ignore", message="os.fork") .. plot:: - :context: + :context: close-figs :include-source: # Configure JAX @@ -71,7 +71,7 @@ where respectively. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -123,7 +123,7 @@ outcomes of :meth:`.DynamicsBackend.run` are independent of the choice of rotati performance. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import Solver @@ -153,7 +153,7 @@ Furthermore, note that in the solver options we set the max step size to the pul variable step solvers from accidentally stepping over pulses in schedules with long idle times. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import DynamicsBackend @@ -191,7 +191,7 @@ that the usual instructions work on the :class:`.DynamicsBackend`. .. plot:: - :context: + :context: close-figs :include-source: import time @@ -227,7 +227,7 @@ that the usual instructions work on the :class:`.DynamicsBackend`. Visualize one of the schedules. .. plot:: - :context: + :context: close-figs :include-source: schedules[3].draw() @@ -236,7 +236,7 @@ Retrieve the counts for one of the experiments as would be done using the result backend. .. plot:: - :context: + :context: close-figs :include-source: result.get_counts(3) @@ -256,7 +256,7 @@ Build a simple circuit. Here we build one consisting of a single Hadamard gate o followed by measurement. .. plot:: - :context: + :context: close-figs :include-source: from qiskit import QuantumCircuit @@ -272,7 +272,7 @@ we are only demonstrating the mechanics of adding a calibration; we have not att the schedule to implement the Hadamard gate with high fidelity. .. plot:: - :context: + :context: close-figs :include-source: with pulse.build() as h_q0: @@ -286,7 +286,7 @@ the schedule to implement the Hadamard gate with high fidelity. Call run on the circuit, and get counts as usual. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -305,7 +305,7 @@ Hadamard gate on qubit :math:`0` to `backend.target`, which impacts how jobs are backend. See the :class:`~qiskit.transpiler.Target` class documentation for further information. .. plot:: - :context: + :context: close-figs :include-source: from qiskit.circuit.library import HGate @@ -317,7 +317,7 @@ Rebuild the same circuit, however this time we do not need to add the calibratio gate to the circuit object. .. plot:: - :context: + :context: close-figs :include-source: circ2 = QuantumCircuit(1, 1) @@ -331,7 +331,7 @@ gate to the circuit object. print(f"Run time: {time.time() - start_time}") .. plot:: - :context: + :context: close-figs :include-source: result.get_counts(0) @@ -362,7 +362,7 @@ To enable running of the single qubit experiments, we add the following to the ` will pass. .. plot:: - :context: + :context: close-figs :include-source: from qiskit.circuit.library import XGate, SXGate, RZGate, CXGate @@ -404,7 +404,7 @@ object. Here we use the template library to initialize our calibrations. .. plot:: - :context: + :context: close-figs :include-source: import pandas as pd @@ -422,7 +422,7 @@ Next, run a rough amplitude calibration for ``X`` and ``SX`` gates for both qubi experiments. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_experiments.library.calibration import RoughXSXAmplitudeCal @@ -436,7 +436,7 @@ experiments. Run the Rabi experiments. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -449,13 +449,13 @@ Run the Rabi experiments. Plot the results. .. plot:: - :context: + :context: close-figs :include-source: rabi0_data.figure(0) .. plot:: - :context: + :context: close-figs :include-source: rabi1_data.figure(0) @@ -463,7 +463,7 @@ Plot the results. Observe the updated parameters for qubit 0. .. plot:: - :context: + :context: close-figs :include-source: pd.DataFrame(**cals.parameters_table(qubit_list=[0, ()], parameters="amp")) @@ -475,7 +475,7 @@ Run rough Drag parameter calibration for the ``X`` and ``SX`` gates. This follow as above. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_experiments.library.calibration import RoughDragCal @@ -489,7 +489,7 @@ as above. cal_drag0.circuits()[5].draw(output="mpl") .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -500,14 +500,14 @@ as above. print(f"Run time: {time.time() - start_time}") .. plot:: - :context: + :context: close-figs :include-source: drag0_data.figure(0) .. plot:: - :context: + :context: close-figs :include-source: drag1_data.figure(0) @@ -515,7 +515,7 @@ as above. The updated calibrations object: .. plot:: - :context: + :context: close-figs :include-source: pd.DataFrame(**cals.parameters_table(qubit_list=[0, ()], parameters="amp")) @@ -531,7 +531,7 @@ the control channel index used to drive the corresponding cross-resonance intera required by the experiment to determine which channel to drive for each control-target pair. .. plot:: - :context: + :context: close-figs :include-source: # set the control channel map @@ -541,7 +541,7 @@ Build the characterization experiment object, and update gate definitions in ``t values for the single qubit gates calibrated above. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_experiments.library import CrossResonanceHamiltonian @@ -555,7 +555,7 @@ values for the single qubit gates calibrated above. backend.target.update_from_instruction_schedule_map(cals.get_inst_map()) .. plot:: - :context: + :context: close-figs :include-source: cr_ham_experiment.circuits()[10].draw("mpl") @@ -563,7 +563,7 @@ values for the single qubit gates calibrated above. Run the simulation. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -574,7 +574,7 @@ Run the simulation. .. plot:: - :context: + :context: close-figs :include-source: data_cr.figure(0) diff --git a/docs/tutorials/optimizing_pulse_sequence.rst b/docs/tutorials/optimizing_pulse_sequence.rst index 21f4f76e1..c681ddc4d 100644 --- a/docs/tutorials/optimizing_pulse_sequence.rst +++ b/docs/tutorials/optimizing_pulse_sequence.rst @@ -25,7 +25,7 @@ We will optimize an :math:`X`-gate on a model of a qubit system using the follow First, set JAX to operate in 64-bit mode and to run on CPU. .. plot:: - :context: + :context: close-figs :include-source: import jax @@ -54,7 +54,7 @@ In the above: We will setup the problem to be in the rotating frame of the drift term. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -105,7 +105,7 @@ more efficient strategies for achieving a value of :math:`0` at the beginning an This is only meant to demonstrate the need for such an approach, and one simple example of one. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import DiscreteSignal @@ -141,7 +141,7 @@ This is only meant to demonstrate the need for such an approach, and one simple Observe, for example, the signal generated when all parameters are :math:`10^8`: .. plot:: - :context: + :context: close-figs :include-source: signal = signal_mapping(np.ones(80) * 1e8) @@ -157,7 +157,7 @@ the pulse via the standard fidelity measure: .. math:: f(U) = \frac{|\text{Tr}(XU)|^2}{4} .. plot:: - :context: + :context: close-figs :include-source: X_op = Operator.from_label('X').data @@ -175,7 +175,7 @@ The function we want to optimize consists of: - Computing and return the infidelity (we minimize :math:`1 - f(U)`). .. plot:: - :context: + :context: close-figs :include-source: def objective(params): @@ -212,7 +212,7 @@ Finally, we gradient optimize the objective: indicate that the passed objective also computes the gradient. .. plot:: - :context: + :context: close-figs :include-source: from jax import jit, value_and_grad @@ -235,7 +235,7 @@ We can draw the optimized signal, which is retrieved by applying the ``signal_ma optimized parameters. .. plot:: - :context: + :context: close-figs :include-source: opt_signal = signal_mapping(opt_results.x) @@ -253,7 +253,7 @@ Summing the signal samples yields approximately :math:`\pm 50`, which is equival would expect based on a rotating wave approximation analysis. .. plot:: - :context: + :context: close-figs :include-source: opt_signal.samples.sum() @@ -271,7 +271,7 @@ instance, parameterized by ``sigma`` and ``width``. Although qiskit pulse provid entry on :ref:`JAX-compatible pulse schedules `. .. plot:: - :context: + :context: close-figs :include-source: import sympy as sym @@ -331,7 +331,7 @@ Next, we construct a pulse schedule using the above parametrized Gaussian square to a signal, and simulate the equation over the length of the pulse sequence. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics.pulse import InstructionToSignals @@ -364,14 +364,14 @@ We set the initial values of ``sigma`` and ``width`` for the optimization as ``initial_params = np.array([10, 10])``. .. plot:: - :context: + :context: close-figs :include-source: initial_params = np.array([10, 10]) gaussian_square_generated_by_pulse(initial_params).draw() .. plot:: - :context: + :context: close-figs :include-source: from jax import jit, value_and_grad @@ -394,7 +394,7 @@ We set the initial values of ``sigma`` and ``width`` for the optimization as We can draw the optimized pulse, whose parameters are retrieved by ``opt_results.x``. .. plot:: - :context: + :context: close-figs :include-source: gaussian_square_generated_by_pulse(opt_results.x).draw() \ No newline at end of file diff --git a/docs/tutorials/qiskit_pulse.rst b/docs/tutorials/qiskit_pulse.rst index c751932f5..5d1d319d5 100644 --- a/docs/tutorials/qiskit_pulse.rst +++ b/docs/tutorials/qiskit_pulse.rst @@ -33,7 +33,7 @@ steps: First, we use the pulse module in Qiskit to create a pulse schedule. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -74,7 +74,7 @@ envelopes and the signals resulting from this conversion. The dashed line shows the virtual ``Z`` gate is applied. .. plot:: - :context: + :context: close-figs :include-source: from matplotlib import pyplot as plt @@ -104,7 +104,7 @@ carrier frequencies, and sample width ``dt``. Additionally, we setup this solver frame and perform the rotating wave approximation. .. plot:: - :context: + :context: close-figs :include-source: from qiskit.quantum_info.operators import Operator @@ -138,7 +138,7 @@ In the last step we perform the simulation and plot the results. Note that, as w should produce identical behavior. .. plot:: - :context: + :context: close-figs :include-source: import time @@ -155,7 +155,7 @@ should produce identical behavior. .. plot:: - :context: + :context: close-figs :include-source: def plot_populations(sol): @@ -179,7 +179,7 @@ the ``Y`` operator. Therefore, the second pulse, which drives around the ``Y``-a shift, has hardley any influence on the populations of the qubit. .. plot:: - :context: + :context: close-figs :include-source: plot_populations(sol) diff --git a/docs/userguide/how_to_configure_simulations.rst b/docs/userguide/how_to_configure_simulations.rst index ce7313622..697d57ac8 100644 --- a/docs/userguide/how_to_configure_simulations.rst +++ b/docs/userguide/how_to_configure_simulations.rst @@ -42,7 +42,7 @@ where: First, construct the components of the model: .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -73,7 +73,7 @@ Construct a :class:`.Solver` for the model as stated, without entering a rotatin timing the solver. .. plot:: - :context: + :context: close-figs :include-source: import time @@ -95,7 +95,7 @@ Next, define a :class:`.Solver` in the rotating frame of the static Hamiltonian ``rotating_frame`` kwarg, and solve, again timing the solver. .. plot:: - :context: + :context: close-figs :include-source: rf_solver = Solver( @@ -126,7 +126,7 @@ To compare the results, we use the fidelity function for unitary matrices: where :math:`d` is the dimension. A value of :math:`1` indicates equality of the unitaries. .. plot:: - :context: + :context: close-figs :include-source: def fidelity(U, V): @@ -148,7 +148,7 @@ evaluations when solving the differential equation in each instance. The number for the first simulation (not in the rotating frame) was: .. plot:: - :context: + :context: close-figs :include-source: results.nfev @@ -156,7 +156,7 @@ for the first simulation (not in the rotating frame) was: Whereas the number of evaluations for the second simulation in the rotating frame was: .. plot:: - :context: + :context: close-figs :include-source: rf_results.nfev @@ -177,7 +177,7 @@ Construct a solver for the same problem, now specifying an RWA cutoff frequency frequencies relative to which the cutoff should be applied: .. plot:: - :context: + :context: close-figs :include-source: rwa_solver = Solver( @@ -200,7 +200,7 @@ We observe a further reduction in time, which is a result of the solver requirin evaluations with the RWA: .. plot:: - :context: + :context: close-figs :include-source: rwa_results.nfev @@ -209,7 +209,7 @@ This speed comes at the cost of lower accuracy, owing to the fact that RWA is a *approximation*, which modifies the structure of the solution: .. plot:: - :context: + :context: close-figs :include-source: U_rwa = rwa_solver.model.rotating_frame.state_out_of_frame(T, rwa_results.y[-1]) @@ -241,7 +241,7 @@ Dynamics. Start off by configuring JAX. .. plot:: - :context: + :context: close-figs :include-source: # configure jax to use 64 bit mode @@ -256,7 +256,7 @@ arrays. Furthermore, set up the initial state to be a single column vector, to f benefits of the sparse representation. .. plot:: - :context: + :context: close-figs :include-source: dim = 300 @@ -282,7 +282,7 @@ Construct standard dense solver in the rotating frame of the static Hamiltonian, to solve the system for a given amplitude, and just-in-time compile it using JAX. .. plot:: - :context: + :context: close-figs :include-source: solver = Solver( @@ -310,7 +310,7 @@ function to solve the system for a given amplitude, and just-in-time compile it. case the static Hamiltonian is already diagonal, but we explicitly highlight the need for this. .. plot:: - :context: + :context: close-figs :include-source: sparse_solver = Solver( @@ -337,7 +337,7 @@ case the static Hamiltonian is already diagonal, but we explicitly highlight the Run the dense simulation (twice to see the true compiled speed). .. plot:: - :context: + :context: close-figs :include-source: yf = jitted_dense_func(1.).block_until_ready() @@ -351,7 +351,7 @@ Run the dense simulation (twice to see the true compiled speed). Run the sparse solver (twice to see the true compiled speed). .. plot:: - :context: + :context: close-figs :include-source: yf_sparse = jitted_sparse_func(1.).block_until_ready() @@ -365,7 +365,7 @@ Run the sparse solver (twice to see the true compiled speed). Verify equality of the results in a common frame. .. plot:: - :context: + :context: close-figs :include-source: yf = solver.model.rotating_frame.state_out_of_frame(T, yf) diff --git a/docs/userguide/how_to_use_different_array_libraries.rst b/docs/userguide/how_to_use_different_array_libraries.rst index 2f3da23a0..df162ddfa 100644 --- a/docs/userguide/how_to_use_different_array_libraries.rst +++ b/docs/userguide/how_to_use_different_array_libraries.rst @@ -24,7 +24,7 @@ This guide addresses the following topics: First, configure JAX and import array libraries. .. plot:: - :context: + :context: close-figs :include-source: # configure jax to use 64 bit mode @@ -41,7 +41,7 @@ First, configure JAX and import array libraries. Defining equivalent :class:`.Signal` instances, with envelope implemented in either NumPy or JAX. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import Signal @@ -59,7 +59,7 @@ Defining equivalent :class:`.Signal` instances, with envelope implemented in eit Evaluation of ``signal_numpy`` is executed with NumPy: .. plot:: - :context: + :context: close-figs :include-source: type(signal_numpy(0.1)) @@ -67,7 +67,7 @@ Evaluation of ``signal_numpy`` is executed with NumPy: Evaluation of ``signal_jax`` is executed with JAX: .. plot:: - :context: + :context: close-figs :include-source: type(signal_jax(0.1)) @@ -75,7 +75,7 @@ Evaluation of ``signal_jax`` is executed with JAX: JAX transformations can be applied to ``signal_jax``, e.g. just-in-time compilation: .. plot:: - :context: + :context: close-figs :include-source: from jax import jit @@ -91,7 +91,7 @@ Internally, Qiskit Dynamics uses an extension of the default NumPy and SciPy arr by `Arraylias `_. These can be imported as: .. plot:: - :context: + :context: close-figs :include-source: # alias for NumPy and corresponding aliased library @@ -121,7 +121,7 @@ scans over a control parameter. First, we construct a :class:`.Solver` instance with a simple qubit model. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -151,7 +151,7 @@ Next, define the function to be compiled: total evolution time. .. plot:: - :context: + :context: close-figs :include-source: def sim_function(amp): @@ -173,7 +173,7 @@ Next, define the function to be compiled: Compile the function. .. plot:: - :context: + :context: close-figs :include-source: from jax import jit @@ -184,7 +184,7 @@ version of the function, which is then executed. Hence, the time taken on the fi compilation time. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -198,7 +198,7 @@ On subsequent calls the compiled function is directly executed, demonstrating th compiled function. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -211,7 +211,7 @@ compiled function. We use this function to plot the :math:`Z` expectation value over a range of input amplitudes. .. plot:: - :context: + :context: close-figs :include-source: import matplotlib.pyplot as plt diff --git a/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst b/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst index 40c971d5a..0407cf627 100644 --- a/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst +++ b/docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst @@ -31,7 +31,7 @@ Dynamics. First, configure JAX to run on CPU in 64 bit mode. .. plot:: - :context: + :context: close-figs :include-source: # configure jax to use 64 bit mode @@ -51,7 +51,7 @@ Gaussian pulse to use in optimization, we need to instantiate a the symbolic representation in `sympy`. .. plot:: - :context: + :context: close-figs :include-source: from qiskit import pulse @@ -80,7 +80,7 @@ the symbolic representation in `sympy`. Next, define the :class:`~qiskit.pulse.library.ScalableSymbolicPulse` using the above expression. .. plot:: - :context: + :context: close-figs :include-source: _t, _duration, _amp, _sigma, _angle = sym.symbols("t, duration, amp, sigma, angle") @@ -112,7 +112,7 @@ Using a Gaussian pulse as an example, we show that a function involving JAX-compiled (or more generally, JAX-transformed). .. plot:: - :context: + :context: close-figs :include-source: # use amplitude as the function argument diff --git a/docs/userguide/perturbative_solvers.rst b/docs/userguide/perturbative_solvers.rst index 48f1197c4..941b7e921 100644 --- a/docs/userguide/perturbative_solvers.rst +++ b/docs/userguide/perturbative_solvers.rst @@ -54,7 +54,7 @@ different array libraries>` for a more detailed explanation of how to work with Dynamics. .. plot:: - :context: + :context: close-figs :include-source: # configure jax to use 64 bit mode @@ -76,7 +76,7 @@ solver. However after the initial construction, the higher frequencies in the mo on the perturbative solver speed. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np @@ -129,7 +129,7 @@ See the :class:`.DysonSolver` API docs for more details. For our example Hamiltonian we configure the :class:`.DysonSolver` as follows: .. plot:: - :context: + :context: close-figs :include-source: import time @@ -169,7 +169,7 @@ over the interval ``[0, (T // dt) * dt]`` for an on-resonance drive with envelop attainable by these solvers. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import Signal @@ -192,7 +192,7 @@ attainable by these solvers. First run includes compile time. .. plot:: - :context: + :context: close-figs :include-source: import time @@ -208,7 +208,7 @@ Once JIT compilation has been performance we can benchmark the performance of th solver: .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -225,7 +225,7 @@ We now construct the same simulation using a standard solver to compare accuracy speed. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import Solver @@ -252,7 +252,7 @@ speed. Simulate with low tolerance for comparison to high accuracy solution. .. plot:: - :context: + :context: close-figs :include-source: yf_low_tol = ode_sim(1., 1e-13) @@ -262,7 +262,7 @@ Simulate with low tolerance for comparison to high accuracy solution. For speed comparison, compile at a tolerance with similar accuracy. .. plot:: - :context: + :context: close-figs :include-source: jit_ode_sim = jit(lambda amp: ode_sim(amp, 1e-8)) @@ -276,7 +276,7 @@ For speed comparison, compile at a tolerance with similar accuracy. Measure compiled time. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -289,7 +289,7 @@ Measure compiled time. Confirm similar accuracy solution. .. plot:: - :context: + :context: close-figs :include-source: np.linalg.norm(yf_low_tol - yf_ode) @@ -306,7 +306,7 @@ Next, we repeat our example using the Magnus-based perturbative solver. Setup of matrix exponentiation to simulate over each fixed time step. .. plot:: - :context: + :context: close-figs :include-source: from qiskit_dynamics import MagnusSolver @@ -332,7 +332,7 @@ matrix exponentiation to simulate over each fixed time step. Setup simulation function. .. plot:: - :context: + :context: close-figs :include-source: @jit @@ -349,7 +349,7 @@ Setup simulation function. First run includes compile time. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -361,7 +361,7 @@ First run includes compile time. Second run demonstrates speed of the simulation. .. plot:: - :context: + :context: close-figs :include-source: start_time = time.time() @@ -372,7 +372,7 @@ Second run demonstrates speed of the simulation. .. plot:: - :context: + :context: close-figs :include-source: np.linalg.norm(yf_magnus - yf_low_tol) diff --git a/qiskit_dynamics/pulse/__init__.py b/qiskit_dynamics/pulse/__init__.py index 76697f16e..d8a6382ea 100644 --- a/qiskit_dynamics/pulse/__init__.py +++ b/qiskit_dynamics/pulse/__init__.py @@ -41,7 +41,7 @@ An example schedule, and the corresponding converted signals, is shown below. .. plot:: - :context: + :context: close-figs import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec diff --git a/qiskit_dynamics/signals/__init__.py b/qiskit_dynamics/signals/__init__.py index 751e8dbd0..9626ce515 100644 --- a/qiskit_dynamics/signals/__init__.py +++ b/qiskit_dynamics/signals/__init__.py @@ -143,7 +143,7 @@ of sampling just the envelope (and keeping the carrier analog). .. plot:: - :context: + :context: close-figs from qiskit_dynamics.signals import Signal, DiscreteSignal from matplotlib import pyplot as plt @@ -178,7 +178,7 @@ any assumptions about the time and frequency units which we interpret as ns and GHz, respectively. .. plot:: - :context: + :context: close-figs :include-source: import numpy as np