|
| 1 | +Save and load experiment data with an experiment service |
| 2 | +======================================================== |
| 3 | + |
| 4 | +.. note:: |
| 5 | + The cloud service at https://quantum.ibm.com/experiments has been |
| 6 | + sunset in the move to the new IBM Quantum cloud platform. Saving and loading |
| 7 | + to the cloud will not work. |
| 8 | + |
| 9 | +Problem |
| 10 | +------- |
| 11 | + |
| 12 | +You want to save and retrieve experiment data from an experiment service. |
| 13 | + |
| 14 | +Solution |
| 15 | +-------- |
| 16 | + |
| 17 | +The :class:`~.ExperimentData` supports saving and loading data with an |
| 18 | +experiment service class satisfying the :class:`~.ExperimentService` protocol. |
| 19 | +Here we demonstrate with the :class:`~.LocalExperimentService` class in Qiskit |
| 20 | +Experiments. |
| 21 | + |
| 22 | +Saving |
| 23 | +~~~~~~ |
| 24 | + |
| 25 | +.. note:: |
| 26 | + |
| 27 | + In the examples below, the service is instantiated with |
| 28 | + ``LocalExperimentService()`` which only saves results to |
| 29 | + memory. You might want to use ``LocalExperimentService(db_dir=db_dir)`` |
| 30 | + instead specifying some local file path ``db_dir`` to save results to. Keep |
| 31 | + in mind that :class:`~.LocalExperimentService` was not designed to scale to |
| 32 | + saving a large amount of data. |
| 33 | + |
| 34 | +Saving results is done by calling :meth:`.ExperimentData.save`: |
| 35 | + |
| 36 | +.. jupyter-execute:: |
| 37 | + :hide-code: |
| 38 | + |
| 39 | + # backend |
| 40 | + from qiskit_ibm_runtime.fake_provider import FakeManilaV2 |
| 41 | + from qiskit_aer import AerSimulator |
| 42 | + backend = AerSimulator.from_backend(FakeManilaV2()) |
| 43 | + |
| 44 | +.. jupyter-execute:: |
| 45 | + |
| 46 | + import numpy as np |
| 47 | + from qiskit_experiments.library import T1 |
| 48 | + from qiskit_experiments.database_service import LocalExperimentService |
| 49 | + |
| 50 | + delays = np.arange(1.e-6, 300.e-6, 30.e-6) |
| 51 | + exp = T1(physical_qubits=(0, ), delays=delays, backend=backend) |
| 52 | + |
| 53 | + exp_data = exp.run().block_for_results() |
| 54 | + service = LocalExperimentService() |
| 55 | + exp_data.service = service |
| 56 | + exp_data.save() |
| 57 | + |
| 58 | +Loading |
| 59 | +~~~~~~~ |
| 60 | + |
| 61 | +Let's load the previous experiment again from the service. First, we create a |
| 62 | +:class:`~qiskit_experiments.framework.Provider` object that has a |
| 63 | +``job(job_id)`` method that can return a |
| 64 | +:class:`~qiskit_experiments.framework.Job` instance. Since this is a local |
| 65 | +test, a fake provider class that just returns jobs it has been given is used. |
| 66 | +Another provider like :class:`~qiskit_ibm_runtime.QiskitRuntimeService` could |
| 67 | +be used instead. Also, the provider is only needed for reloading the raw job |
| 68 | +data for rerunning analysis. If only the experiment results and figures are |
| 69 | +needed, the ``provider`` argument to :meth:`.ExperimentData.load` can omitted. |
| 70 | +A warning about not being able to access the job data will be emitted in this |
| 71 | +case. |
| 72 | + |
| 73 | +.. jupyter-execute:: |
| 74 | + |
| 75 | + from qiskit_experiments.test.utils import FakeProvider |
| 76 | + |
| 77 | + provider = FakeProvider() |
| 78 | + for job in exp_data.jobs(): |
| 79 | + provider.add_job(job) |
| 80 | + |
| 81 | +Now the experiment data can be reloaded: |
| 82 | + |
| 83 | +.. jupyter-execute:: |
| 84 | + |
| 85 | + from qiskit_experiments.framework import ExperimentData |
| 86 | + load_expdata = ExperimentData.load(exp_data.experiment_id, service=service, provider=provider) |
| 87 | + |
| 88 | +Now we can display the figure from the loaded experiment data: |
| 89 | + |
| 90 | +.. jupyter-execute:: |
| 91 | + |
| 92 | + load_expdata.figure(0) |
| 93 | + |
| 94 | +The analysis results have been retrieved as well and can be accessed normally. |
| 95 | + |
| 96 | +.. jupyter-execute:: |
| 97 | + |
| 98 | + load_expdata.analysis_results(dataframe=True) |
| 99 | + |
| 100 | +Discussion |
| 101 | +---------- |
| 102 | + |
| 103 | +Note that calling :meth:`~.ExperimentData.save` before the experiment is complete will |
| 104 | +instantiate an experiment entry in the database, but it will not have |
| 105 | +complete data. To fix this, you can call :meth:`~.ExperimentData.save` again once the |
| 106 | +experiment is done running. |
| 107 | + |
| 108 | +Sometimes the metadata of an experiment can be very large and cannot be stored directly in the database. |
| 109 | +In this case, a separate ``metadata.json`` file will be stored along with the experiment. Saving and loading |
| 110 | +this file is done automatically in :meth:`~.ExperimentData.save` and :meth:`~.ExperimentData.load`. |
| 111 | + |
| 112 | +Auto-saving an experiment |
| 113 | +~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 114 | + |
| 115 | +The `auto_save` feature automatically saves changes to the |
| 116 | +:class:`.ExperimentData` object to the experiment service whenever it's updated. |
| 117 | + |
| 118 | +.. jupyter-execute:: |
| 119 | + |
| 120 | + delays = np.arange(1.e-6, 300.e-6, 30.e-6) |
| 121 | + exp = T1(physical_qubits=(0, ), delays=delays, backend=backend) |
| 122 | + |
| 123 | + exp_data = exp.run() |
| 124 | + service = LocalExperimentService() |
| 125 | + exp_data.service = service |
| 126 | + exp_data.auto_save = True |
| 127 | + exp_data.block_for_results() |
| 128 | + |
| 129 | + |
| 130 | +Setting ``auto_save = True`` works by triggering :meth:`.ExperimentData.save` |
| 131 | +once the experiment's analysis completes. |
| 132 | + |
| 133 | +When working with composite experiments, setting ``auto_save`` will propagate this |
| 134 | +setting to the child experiments. |
| 135 | + |
| 136 | +Deleting an experiment |
| 137 | +~~~~~~~~~~~~~~~~~~~~~~ |
| 138 | + |
| 139 | +Both figures and analysis results can be deleted. Note that unless you |
| 140 | +have auto save on, the update has to be manually saved to the |
| 141 | +database by calling :meth:`~.ExperimentData.save`. Because there are two analysis |
| 142 | +results, one for the T1 parameter and one for the curve fitting results, we must |
| 143 | +delete twice to fully remove the analysis results. |
| 144 | + |
| 145 | +.. jupyter-input:: |
| 146 | + |
| 147 | + t1_expdata.delete_figure(0) |
| 148 | + t1_expdata.delete_analysis_result(0) |
| 149 | + t1_expdata.delete_analysis_result(0) |
| 150 | + |
| 151 | +Tagging experiments |
| 152 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 153 | + |
| 154 | +Tags and notes can be added to experiments to help identify specific experiments in the interface. |
| 155 | +For example, an experiment can be tagged with the following code. |
| 156 | + |
| 157 | +.. jupyter-input:: |
| 158 | + |
| 159 | + t1_expdata.tags = ['tag1', 'tag2'] |
| 160 | + t1_expdata.notes = "Example note." |
0 commit comments