{ "cells": [ { "cell_type": "markdown", "id": "5916653a302aed80", "metadata": {}, "source": [ "# Error mitigation" ] }, { "cell_type": "markdown", "id": "32baece8ec67a97e", "metadata": {}, "source": [ "Perceval offers natively some mitigation techniques, that try to get the noisy results closer to the expected noiseless results by applying some corrections. They rely on extra measurements with different parameters than the original experiment. Some of them will create several sub-jobs and combine them to get better approximations of the results.\n", "\n", "There exist algorithms for different purposes:\n", "- HOM and g2 mitigation\n", "- Loss mitigation\n", "- Compilation/phase error averaging\n", "- Detector balancing\n", "\n", "It is worth noting that, in a noiseless environment (such as noiseless simulators), these techniques should not be used, as they will slow down the whole process without bringing better results. Also, some techniques are not compatible with non-unitary Experiments, including feed-forwarded Experiments, or have some restrictions on parameters (for example, the loss mitigation can't be used if there are less than 3 requested photons).\n", "\n", "Last but not least, these techniques work by combining some results, so they might require many samples to properly run on a real QPU, and will only mitigate the results. Although some techniques will correct every error perfectly in simulation, this will not be the case on real hardware as they are limited by how well the hardware deviates from its characterization.\n", "\n", "Some of these techniques may also be computationally heavy, depending on the size of the output space and the number of photons." ] }, { "cell_type": "code", "execution_count": 1, "id": "b7eb8b1f15b8bbd5", "metadata": {}, "outputs": [], "source": [ "from perceval import (Experiment, BS, FockState, pdisplay, SimulatedComputer, NoiseModel, RemoteComputer,\n", " ExecutionFactory,\n", " Detector, MitigationFactory, MitigationLevel, DetectorBalancing, DistinguishablePhotonMitigation,\n", " QuandelaCommunicationLayer)" ] }, { "cell_type": "markdown", "id": "b2362ea08031d961", "metadata": {}, "source": [ "## Basic example" ] }, { "cell_type": "markdown", "id": "a6b85d3c6fa3d102", "metadata": {}, "source": [ "We will take a simple HOM experiment, and show that our mitigation techniques manage to get closer to the perfect distribution.\n", "\n", "As a reminder, for an HOM experiment, we take a single beam splitter and inject 1 photon into each mode |1,1>. We then expect to see 50% of |2, 0> and 50% of |0, 2>." ] }, { "cell_type": "code", "execution_count": 2, "id": "bd555f3bd399acc2", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "1\n", "\n", "\n", "1\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Start as usual by defining your Experiment\n", "\n", "e = Experiment(2)\n", "e.add(0, BS())\n", "e.with_input(FockState([1, 1]))\n", "e.min_detected_photons_filter(2)\n", "\n", "pdisplay(e)" ] }, { "cell_type": "code", "execution_count": 3, "id": "initial_id", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'results': {\n", "\t|2,0>: 0.5\n", "\t|0,2>: 0.5\n", "}, 'global_perf': 1.0000000000000004}\n" ] } ], "source": [ "# As a reminder, we can compute for the perfect case like this\n", "comp = SimulatedComputer(\"SLOS\")\n", "comp.start()\n", "\n", "exec = ExecutionFactory(comp, e).probs\n", "print(exec())" ] }, { "cell_type": "markdown", "id": "426c0039a19e0b15", "metadata": {}, "source": [ "Now, we are going to add some noise and imperfect detectors, and investigate how different mitigation techniques can be combined to get better results." ] }, { "cell_type": "code", "execution_count": 4, "id": "7b7091b00e797398", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|2,0> 0.452779
|0,2> 0.402031
|1,1> 0.125265
|1,2> 0.006667
|2,1> 0.006549
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "comp.noise = NoiseModel(indistinguishability=0.8, g2=0.03)\n", "\n", "# Add imbalanced detectors in the experiment\n", "e.add(0, Detector.ppnr(4, wire_efficiency=0.87))\n", "e.add(1, Detector.ppnr(8, wire_efficiency=0.76))\n", "\n", "# That's all, we can now do as before\n", "exec = ExecutionFactory(comp, e).probs\n", "pdisplay(exec()['results'], max_v = 5)" ] }, { "cell_type": "markdown", "id": "6431058be6a2707", "metadata": {}, "source": [ "We can see that many unwanted states appeared, as we only get a desired state 85.4% of the time, and that the wanted probabilities are not equal due to the detector unbalance. We are going to use some mitigation to fix this." ] }, { "cell_type": "markdown", "id": "36ec0470cb30687b", "metadata": {}, "source": [ "The mitigations are defined at the Computer level, so that they can go along with the noise. The easiest way to add mitigation techniques is to use the MitigationFactory, but we'll see later how to add custom mitigations." ] }, { "cell_type": "code", "execution_count": 5, "id": "622e1c27ca1b28d7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'results': {\n", "\t|2,0>: 0.5\n", "\t|0,2>: 0.5\n", "}, 'global_perf': 1.0000000000000007}\n" ] } ], "source": [ "# The highest the level, the more mitigations are applied, but the more expensive they become\n", "comp.mitigations = MitigationFactory(MitigationLevel.medium).build()\n", "\n", "# That's all, we can now do as before\n", "exec = ExecutionFactory(comp, e).probs\n", "print(exec())" ] }, { "cell_type": "markdown", "id": "4079f9138b8d01cc", "metadata": {}, "source": [ "We can see that, in this example using strong simulation, the mitigation techniques perfectly corrected the errors. Now, we are going to see how to define the mitigations by yourself." ] }, { "cell_type": "markdown", "id": "9a2c7ec62059b799", "metadata": {}, "source": [ "## Advanced example" ] }, { "cell_type": "markdown", "id": "598d3701b99b406", "metadata": {}, "source": [ "In our example, we only set HOM, g2 and detector imbalance errors. Let's investigate on how we can combine the mitigations for these kind of errors by choosing them by hand." ] }, { "cell_type": "code", "execution_count": 6, "id": "3d307bbe6d3fb7fa", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|2,0> 0.436187
|0,2> 0.436187
|1,1> 0.09693
|1,2> 0.008376
|2,1> 0.008376
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# As a starting point, we will use detector balancing only\n", "comp.mitigations = [DetectorBalancing()]\n", "\n", "exec = ExecutionFactory(comp, e).probs\n", "pdisplay(exec()[\"results\"], max_v = 5)" ] }, { "cell_type": "markdown", "id": "c776716f7b5cdce8", "metadata": {}, "source": [ "We can see that this fixed the imbalance imposed by the detectors, and increased the probability of getting a wanted state to 87.2%." ] }, { "cell_type": "code", "execution_count": 7, "id": "a96fd4561a496e01", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The total number of computations will be: 3\n" ] } ], "source": [ "# Now, we use HOM and g2 mitigations only\n", "\n", "# Order is the number of photons that we will remove on sub-jobs.\n", "# The higher the order, the better the results, but the number of sub-jobs increases as the sum of (n choose k) where k is all the values up to order\n", "dist_mitigation = DistinguishablePhotonMitigation(order=1)\n", "print(f\"The total number of computations will be: {dist_mitigation.overhead(e.input_state)}\")" ] }, { "cell_type": "code", "execution_count": 8, "id": "1657a5723911e300", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|2,0> 0.517868
|0,2> 0.471126
|1,1> 0.011006
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Do the computation\n", "comp.mitigations = [dist_mitigation]\n", "\n", "exec = ExecutionFactory(comp, e).probs\n", "pdisplay(exec()[\"results\"], max_v = 5)" ] }, { "cell_type": "markdown", "id": "116ae4fe46b27144", "metadata": {}, "source": [ "In this case, we got a far better success rate, which is at 98.9%, but using distinguishable photon mitigation alone can't compensate for the detector imbalance." ] }, { "cell_type": "markdown", "id": "f8a9244937473e9c", "metadata": {}, "source": [ "Finally, we can use both at the same time. Note that the order of application of the mitigations matter.\n", "\n", "Mitigations are applied left to right, meaning that each sub-job generated by a mitigation is fed to the next mitigation of the list. As such, it is advised not to repeat a given mitigation technique, and to use the same order as provided by the MitigationFactory" ] }, { "cell_type": "code", "execution_count": 9, "id": "9ffb277694708781", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,2> 0.50088
|2,0> 0.490176
|1,1> 0.008944
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# To emphasize the difference when applying the mitigations in different order\n", "\n", "# We can also change the noise or the mitigations for a limited scope\n", "with comp.apply_configuration(mitigations=[DetectorBalancing(), dist_mitigation]):\n", " exec = ExecutionFactory(comp, e).probs\n", " pdisplay(exec()[\"results\"], max_v = 5)" ] }, { "cell_type": "markdown", "id": "44707aec7f90c7fd", "metadata": {}, "source": [ "In this order, we get better results than with only one mitigation, but we still have a 0.1% failure rate. Also, we didn't perfectly correct for the imbalance." ] }, { "cell_type": "code", "execution_count": 10, "id": "7608b7880a53b249", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|2,0> 1/2
|0,2> 1/2
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Using the MitigationFactory order, configuring it by hand\n", "mitigation_factory = MitigationFactory(MitigationLevel.none)\n", "\n", "# The call order of these methods doesn't matter\n", "mitigation_factory.set_custom_mitigation(dist_mitigation)\n", "mitigation_factory.set_detector_balancing()\n", "\n", "with comp.apply_configuration(mitigations=mitigation_factory.build()):\n", " exec = ExecutionFactory(comp, e).probs\n", " pdisplay(exec()[\"results\"], max_v = 5)" ] }, { "cell_type": "markdown", "id": "131e4bad0ffe8758", "metadata": {}, "source": [ "We got exactly what we wanted!" ] }, { "cell_type": "code", "execution_count": 11, "id": "c1b49c4ab91e3f05", "metadata": {}, "outputs": [], "source": [ "comp.stop()" ] }, { "cell_type": "markdown", "id": "d5d1e1114ca6ff0e", "metadata": {}, "source": [ "## Remote Mitigation" ] }, { "cell_type": "markdown", "id": "ead33483bbc1602a", "metadata": {}, "source": [ "When using RemoteComputer, a few things can change with error mitigation.\n", "\n", "First, some QPUs may have a default mitigation, which is applied if nothing is specified. If a mitigation is manually given, it replaces the default mitigation, including if the given mitigation is empty.\n", "\n", "Then, the mitigated Execution is sent to the cloud as a single job, and everything happens remotely if the target knows all the mitigations. Else, the mitigations happen locally and may generate several jobs. If needed, the local application of the mitigations can be enforced." ] }, { "cell_type": "code", "execution_count": 12, "id": "826ceabd84977672", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "comp = RemoteComputer(QuandelaCommunicationLayer(\"qpu:belenos\"))\n", "\n", "print(comp.specs.default_mitigations)" ] }, { "cell_type": "code", "execution_count": null, "id": "4972812a3aa53ca0", "metadata": {}, "outputs": [], "source": [ "# Here, the mitigation would be sent with a single cloud job\n", "exec = ExecutionFactory(comp, e, max_shots_per_call=100_000).probs\n", "\n", "# We can change this behaviour and apply the mitigations locally.\n", "# In this case, several jobs may be sent to the cloud and the postprocessing happens locally\n", "# comp.use_mitigations_remotely = False\n", "\n", "with comp.acquire():\n", " res = exec(max_samples = 10_000)\n", "\n", "pdisplay(res[\"results\"], max_v = 5)" ] }, { "cell_type": "markdown", "id": "316082d08176b990", "metadata": {}, "source": [ "It is also worth mentioning that the number of samples received at the end of a mitigated job may not correspond to the actual number of samples. Also, the number of shots is split between all sub-jobs, so the total number of shots will never exceed the requested number of shots." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }