Skip to main content

Efficient sampling of noisy quantum circuits and protocols

Project description

qsample

Install

At the moment only build from source:

git clone https://github.com/dpwinter/qsample.git
cd qsample
python setup.py install

Prerequisites

  • This package requires Python 3.9 or higher.
  • pdflatex (for circuit rendering)

When to use

This package is for you if you want to
* model circuit-level incoherent Pauli noise (we don’t do coherent noise here, neither are our auxiliary qubits modelled as ideal)
* with high fidelity physical operations aka low physical error rates
* for a QEC protocol that consists of execution of one or more quantum circuits with in-sequence measurements and feed-forward of measurement information
* over a specific range of varying physical error rates

Getting started


Library overview

  1. Circuit
  2. Protocol
  3. Error Model
  4. Simulator
  5. Sampler
    5.1. Direct Sampler
    5.2. Interlude: Callbacks
    5.3. Subset Sampler

1. Circuit

  • List of ticks
  • Each tick is a dictionary, key: gate type, value: set of qubit(s)
  • Recommended: 1 gate type per tick

Example: Flagged-GHZ preparation: * Produce GHZ state on qubits 0-3
* Flag-qubit 4, measure:
* 0: error-free*
* 1: flip on one data qubit*

* Only for max. 1 allowed fault.

from qsample import Circuit
ghz = Circuit([ {"init": {0,1,2,3,4}},
                {"H": {0}},
                {"CNOT": {(0,1)}},
                {"CNOT": {(1,2)}},
                {"CNOT": {(2,3)}},
                {"CNOT": {(3,4)}},
                {"CNOT": {(0,4)}},
                {"measure": {4}}], ff_det=True)

ff_det: fault-free deterministic. Set to True if circuit gives unique measurement result when executed without faults.

ghz.draw()

2. Protocol

  • Graph (can be cyclic)
    • Vertices: Circuits
    • Edges: transition rules = boolean* functions (checks)
    • Must include start and end nodes.

* Exception: Correction functions can return circuits for on-the-fly execution. (special case, will not show here)

Example: Flagged-GHZ repeat(3)-until-success
* Execute flagged-GHZ circuit max. 3 times.
* Only repeat if measured 1.
* If measured flag to be 0 within 3 iteration -> No fail
* If after 2 iterations 3rd measurement is also 1 -> Fail

from qsample import Protocol
ghz3 = Protocol(fault_tolerant=True)

ghz3.add_node('ghz', circuit=ghz) # Add node with corresponding circuit
ghz3.add_edge('START', 'ghz', check='True') # Transition START -> first circuit node always True
ghz3.add_edge('ghz', 'ghz', check='repeat(ghz)') # Transition to ghz if repeat(ghz) True.
ghz3.add_edge('ghz', 'FAIL', check='logErr(ghz)') # Transition to final node FAIL if logErr(ghz) True.

fault_tolerant: Define all weight-1 paths (circuit sequence with max. 1 fault) to never result in a logical fail.

ghz3.draw()

  • repeat() and logErr() are user-defined (boolean) check functions
  • Measurement history of circuits stored during for protocol run
    • Can access measurement history of any circuit by passing its name as argument

Next, we define what the two check functions should do:

def repeat(msmt_list): # arg: list of ghz's measurment history
    return len(msmt_list) < 3 and msmt_list[-1] == 1 # If True repeat ghz

def logErr(msmt_list):
    return len(msmt_list) == 3 and msmt_list[-1] == 1 # If True transition to `FAIL`

functions = {'logErr': logErr, 'repeat': repeat}
ghz3._check_fns.update(functions) # Let protocol know about user-defined checks

Note: It is also possible that all checks are false. In this case the protocol exits “insignficantly”.
Here: “insignificant” when Flag=0 within 3 protocol runs -> Go to next protocol run, i.e. sample.

3. Error model

  • Strategy:
    • Generate fault circuit $C_f$ of same length as reference circuit $C$
    • During simulation iterate $C$ and $C_f$ simulateously and apply to state
  • Must include:
    • group(): group circuit locations by key, e.g. all 1-qubit-gates
    • select(): picks certain amount of locations from each group (not required by user)
    • generate(): generator function, returns a Pauli fault operator for given location
from qsample import E1

E1.groups, E1().group(ghz) # All gates in group `q`
(['q'],
 {'q': [(1, 0),
   (2, (0, 1)),
   (3, (1, 2)),
   (4, (2, 3)),
   (5, (3, 4)),
   (6, (0, 4))]})

4. Simulator

from qsample import StabilizerSimulator as CHP

5. Sampler

  • Two types: Direct (Monte Carlo) and Subset sampler
  • All relevant information stored in CountTree data structure

5.1. Direct sampler

from qsample import DirectSampler
import numpy as np
import matplotlib.pyplot as plt

Let’s define some physical error rates at which to sample:

sample_range = np.logspace(-3,0,5)
err_probs = {'q': sample_range} # Note: Must provide rate(s) for each group specified in `ErrorModel`
err_probs
{'q': array([0.001     , 0.00562341, 0.03162278, 0.17782794, 1.        ])}
dsam = DirectSampler(protocol=ghz3, simulator=CHP, err_probs=err_probs, err_model=E1)
dsam.run(10000)
p_phy=1.00E-03:   0%|          | 0/10000 [00:00<?, ?it/s]

p_phy=5.62E-03:   0%|          | 0/10000 [00:00<?, ?it/s]

p_phy=3.16E-02:   0%|          | 0/10000 [00:00<?, ?it/s]

p_phy=1.78E-01:   0%|          | 0/10000 [00:00<?, ?it/s]

p_phy=1.00E+00:   0%|          | 0/10000 [00:00<?, ?it/s]
p_L, std = dsam.stats()

plt.errorbar(sample_range, p_L, fmt='--', c="black", yerr=std, label="Direct MC")
plt.plot(sample_range, sample_range,'k:', alpha=0.5)
plt.xscale('log')
plt.yscale('log')
plt.xlabel('$p_{phy}$(q)')
plt.ylabel('$p_L$');

Check what has been sampled for the last (1e0) physical error rate:

dsam.trees[(1.0,)].draw()

5.2. Interlude: Callbacks

  • Used to receive intermediate results inside sampling process
  • E.g. we might want to avoid unnecessary samples (at large $p_{phy}$)
    • We can use the callback RelStdTarget
from qsample import callbacks as cb

dsam2 = DirectSampler(protocol=ghz3, simulator=CHP, err_probs=err_probs, err_model=E1)
dsam2.run(10000, callbacks=[cb.RelStdTarget(target=0.2)])
p_phy=1.00E-03:   0%|          | 0/10000 [00:00<?, ?it/s]

p_phy=5.62E-03:   0%|          | 0/10000 [00:00<?, ?it/s]

p_phy=3.16E-02:   0%|          | 0/10000 [00:00<?, ?it/s]

p_phy=1.78E-01:   0%|          | 0/10000 [00:00<?, ?it/s]

Rel. std target of 0.2 reached. Sampling stopped.

p_phy=1.00E+00:   0%|          | 0/10000 [00:00<?, ?it/s]

Rel. std target of 0.2 reached. Sampling stopped.

Another callback is VerboseCircuitExec, which gives a detailed log of which circuits, faults and measurements took place:

dsam3 = DirectSampler(protocol=ghz3, simulator=CHP, err_probs={'q': 0.1}, err_model=E1)
dsam3.run(10, callbacks=[cb.VerboseCircuitExec()])
p_phy=1.00E-01:   0%|          | 0/10 [00:00<?, ?it/s]

ghz -> Faults: [] -> Msmt: 0
None
ghz -> Faults: [] -> Msmt: 0
None
ghz -> Faults: [(6, {'Y': {0}})] -> Msmt: 0
None
ghz -> Faults: [] -> Msmt: 0
None
ghz -> Faults: [(4, {'Z': {2}})] -> Msmt: 0
None
ghz -> Faults: [(1, {'Z': {0}})] -> Msmt: 0
None
ghz -> Faults: [] -> Msmt: 0
None
ghz -> Faults: [] -> Msmt: 0
None
ghz -> Faults: [(4, {'X': {2}})] -> Msmt: 0
None
ghz -> Faults: [(2, {'Y': {0}, 'Z': {1}}), (5, {'Z': {3}})] -> Msmt: 1
ghz -> Faults: [] -> Msmt: 0
None

We can also write our own callback. Every sampler has 6 callback hooks:
* Begin/End sampler
* Begin/End protocol
* Begin/End circuit

5.3. Subset Sampler

  • Samples only at one physical error rate pmax
  • Scaling obtained analytically
  • pmax must be chosen in “representative” region

Note: 1. For multi-parameter error model pmax is a tuple of one physical error rate per group.
2. The choice of pmax has a direct impact of which subsets are sampled.

How to choose pmax? What is the heuristic?
* We want to sample, s.t. the subset occurence probability is max. for 0-weight subset and subsequently falling for higher order subsets.
* We want to have also relatively high probability for other (important) subsets, i.e. weight-1, weight-2,..

Example: For the Flagged-GHZ circuit we would choose a pmax close to 0.1:

from qsample.sampler.base import subset_occurence, all_subsets, err_probs_tomatrix

grp = E1().group(ghz).values()
wgts_combis = all_subsets(grp)

for p_phy in [0.01, 0.1, 0.3]:
    Aws = subset_occurence(grp, wgts_combis, p_phy)
    plt.figure()
    plt.title("Subset occurence prob. $A_w$ at $p_{phy}$=%.2f" % p_phy)
    plt.bar(range(len(Aws)), Aws)
    plt.ylabel("$A_w$")
    plt.xlabel("Subsets")

from qsample import SubsetSampler
pmax = {'q': 0.1}

ss_sam = SubsetSampler(ghz3, CHP,  pmax=pmax, err_probs=err_probs, err_model=E1)
ss_sam.run(300)
p_phy=1.00E-01:   0%|          | 0/300 [00:00<?, ?it/s]

Note: Although we passed err_probs those are not used for sampling. Only when we call stats() those probs are used:

p_L_low, std_low, p_L_up, std_up = ss_sam.stats()

plt.errorbar(sample_range, p_L, fmt='--', c="black", yerr=std, label="Direct MC")
plt.loglog(sample_range, p_L_low, label='SS low')
plt.fill_between(sample_range, p_L_low - std_low, p_L_low + std_low, alpha=0.2)
plt.loglog(sample_range, p_L_up, label='SS low')
plt.fill_between(sample_range, p_L_up - std_up, p_L_up + std_up, alpha=0.2)
plt.plot(sample_range, sample_range,'k:', alpha=0.5)
plt.xlabel('$p_{phy}$(q)')
plt.ylabel('$p_L$')
plt.legend();

We sampled at a single error rate with much less samples and get a much better bound on the logical error rate. Let’s inspect what has been sampled:

ss_sam.tree.draw() # only one tree

We can store and later reload our sample results:

ss_sam.save('ghz3ss.samp')

from qsample import Sampler
stored_sam = Sampler.load('ghz3ss.samp')
counts_before = stored_sam.tree.root.counts

stored_sam.run(200) # After loading we can for example run a few more samples.
counts_after = stored_sam.tree.root.counts

print(counts_before, counts_after)
p_phy=1.00E-01:   0%|          | 0/200 [00:00<?, ?it/s]

300 500

More complex examples can be found here: https://github.com/dpwinter/qsample/blob/master/08_examples.ipynb

Contribute

  • submit your feature request via github issue

Team

qsample was developed by Don Winter based on and in collaboration with Sascha Heußen under supervision of Prof. Dr. Markus Müller.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qsample-0.0.1.tar.gz (39.2 kB view hashes)

Uploaded Source

Built Distribution

qsample-0.0.1-py3-none-any.whl (36.5 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page