pyQuil Documentation

repository·master·Indexed 23 days ago

https://github.com/rigetti/pyquil

A Python library for creating Quantum Instruction Language (Quil) programs. Part of the Forest SDK, pyQuil allows users to generate quantum programs and compile or simulate them using quilc and the Quantum Virtual Machine (QVM), or execute them on real quantum processors via Rigetti's Quantum Cloud Services.

Tokens
41.2K
Snippets
72
Records
233
Agent score
81%

What's inside pyQuil

  1. What is pyQuil and how does it work?

    master

    pyQuil is a Python library that allows you to build and execute Quil (Quantum Instruction Language) programs. It acts as an interface to the Quil SDK components. To function, pyQuil requires the following components:

    1. quilc: The Quil compiler.
    2. QVM (Quantum Virtual Machine): Used for simulating quantum computers.

    Beyond simulation, pyQuil can also be used to run programs on real quantum hardware via Rigetti's Quantum Cloud Services (QCS).

  2. What is Quil-T and when to use it

    master

    Quil-T is an extension to the Quil language that introduces pulse-level control to quantum programs. While standard Quil is used for circuit-type programming (where gates like H 0 are treated as abstractions), Quil-T allows for explicit control over the RF waveforms played by the QPU's control hardware.

    Use Quil-T when you need:

    • Precise control over the underlying hardware implementation of gates.
    • To perform hardware characterization experiments (e.g., determining T1 times).
    • To define custom pulse-level instructions rather than relying on high-level gate abstractions.

    Note that Quil-T introduces a notion of time to the program execution model.

  3. Model classical readout bit-flip error

    master

    Classical readout bit-flip error can be modeled using an assignment probability matrix $P_{x'|x}$. This matrix defines the conditional probabilities $p(x'|x)$ of observing a final outcome $x'$ given the true underlying measurement outcome $x$.

    For a single qubit, the matrix is defined as:

    $$P_{x'|x} = \begin{pmatrix} p(0 | 0) & p(0 | 1) \ p(1 | 0) & p(1 | 1) \end{pmatrix}$$

    Where:

    • Each column must sum to 1 (valid probability distribution).
    • The resulting outcome probabilities $\mathbf{p}'$ are calculated from the ideal probabilities $\mathbf{p}$ via $\mathbf{p}' = P_{x'|x}\mathbf{p}$.

    This type of noise is mathematically described as a Positive Operator Valued Measure (POVM) using operators $E_{x'} = \sum_{x\in \mathcal{O}} p(x'|x)\Pi_x$, where $\Pi_x$ are the ideal projection operators.

  4. Understand WavefunctionSimulator bitstring ordering

    master

    The WavefunctionSimulator uses a specific bitstring enumeration convention: qubit 0 is the least significant bit (LSB) and is positioned at the right end of the bitstring.

    This differs from many quantum computing literature sources where the lowest-index qubit is on the left.

    Example Mapping:

    bitstringqubit_(n-1)...qubit_2qubit_1qubit_0
    1...1011...101
    0...1100...110

    Because of this convention, the matrix representation of multi-qubit gates (like CNOT) may appear different than expected if you are used to the other ordering. For example, CNOT(1, 0) in Quil corresponds to a specific matrix where the control is qubit 1 and the target is qubit 0, following this LSB-on-the-right convention.

  5. Differences between QVM and QPU QuantumComputers

    master

    While QuantumComputer objects follow common interfaces (QAM and AbstractCompiler), their underlying implementations differ:

    • QVM Target: qc.qam is a QVM instance and qc.compiler is a QVMCompiler instance.
    • QPU Target: qc.qam is a QPU instance and qc.compiler is a QPUCompiler instance.

    To write robust code that works for both, use isinstance() checks on qc.qam or qc.compiler to access hardware-specific features like calibration refreshes or job cancellations.

  6. Construct a Sentinel-Based Loop

    master

    A sentinel-based loop repeats a program body until a specific condition (the sentinel) is met. This is useful for probabilistic algorithms where you want to repeat an attempt until a desired outcome is achieved.

    Pattern for implementation:

    1. Define the Body: The quantum circuit you want to repeat.
    2. Define Reset Logic: A program to reset qubits to a known state if the outcome was unsuccessful.
    3. Define the Sentinel Condition: Use if_then to check the measurement. If the condition is met (e.g., result is 1), execute the reset and a Jump back to a Label at the start of the loop. If the condition is not met (e.g., result is 0), execute a Halt instruction to end the program.
    4. Compose: Use pyquil.quilbase.Label and pyquil.quilbase.Jump to create the loop structure manually, or use if_then to manage the branches.
    from pyquil import Program, get_qc
    from pyquil.gates import CNOT, H, X
    from pyquil.quilbase import Halt, Qubit, MemoryReference, JumpTarget, Jump
    from pyquil.quilatom import Label
    
    def sentinel_program(qubits: Tuple[Qubit, Qubit]) -> Program:
        start_label = Label("start-loop")
        program = Program(JumpTarget(start_label))
        measures = program.declare("measures", "BIT", 2)
    
        # Add body
        program += body(qubits, measures)
        
        # Define reset and jump back
        reset = Program(
            reset_bell_state(qubits, measures),
            Jump(start_label)
        )
        
        # Enforce sentinel: if measures[0] is 1, run reset; else Halt
        program += enforce_sentinel(measures[0], reset)
        
        program.resolve_label_placeholders()
        return program
  7. How the Quil compiler handles rewiring and SWAPs

    master

    When a Quil program contains multi-qubit instructions that do not match the physical connectivity (topology) of the target device, the compiler performs rewiring to rearrange qubits so execution is possible.

    Rewiring Comments

    To assist with debugging, the compiler inserts human-readable comments in the raw Quil code:

    • # Entering rewiring
    • # Exiting rewiring: #(n0 n1 ... nk) (where nj is the physical qubit assigned to logical qubit j). Note: These comments are for human readability and are discarded during execution.

    SWAP Gates

    If the compiler cannot avoid it, it will insert SWAP gates to move qubits closer.

    • Virtual Swaps: If swaps are required at the very beginning of a program, the compiler can treat them as 'virtual'. These do not appear as gates in the final program but instead influence the initial qubit rewiring, preventing gate inflation.
    • Real Swaps: For complex programs with high entanglement, real SWAP gates may be inserted, increasing the gate depth.
    • Cost: A SWAP typically costs three CZ or three XY gates. If the device supports both CZ and XY gates, the compiler can optimize a SWAP to use only two gates (one CZ and one XY).
  8. Implement Amplitude Damping noise

    master

    Amplitude damping models the decay of a qubit from state $|1\rangle$ to $|0\rangle$ with probability $p$. The noise channel is parameterized by a list of Kraus operators:

    $K_1 = \begin{pmatrix} 1 & 0 \ 0 & \sqrt{1-p} \end{pmatrix}$ $K_2 = \begin{pmatrix} 0 & \sqrt{p} \ 0 & 0 \end{pmatrix}$

    In PyQuil simulations, you can generate these operators manually to pass to define_noisy_gate.

    import numpy as np
    
    def damping_channel(damp_prob=.1):
        """
        Generate the Kraus operators corresponding to an amplitude damping
        noise channel.
    
        :params float damp_prob: The one-step damping probability.
        :return: A list [k1, k2] of the Kraus operators that parametrize the map.
        :rtype: list
        """
        damping_op = np.sqrt(damp_prob) * np.array([[0, 1],
                                                    [0, 0]])
    
        residual_kraus = np.diag([1, np.sqrt(1-damp_prob)])
        return [residual_kraus, damping_op]
  9. Model T1 error during readout

    master

    T1 errors are distinct from classical bit-flip errors because the quantum state itself is corrupted during the measurement process, potentially erasing pre-measurement information.

    To model T1 error in a practical simulation framework, it is proposed to use a two-step process:

    1. Apply a T1 damping Kraus map to the quantum state.
    2. Follow this with the noisy readout process (the classical bit-flip model described above).

    This approach approximates the effect of a qubit decaying to the ground state before the measurement operation is finalized.

  10. Understand the Forest SDK components

    master

    PyQuil is part of the Forest SDK. To fully utilize pyQuil for compiling and simulating programs, you must also have the following components installed:

    • quilc: The Quil Compiler.
    • QVM: The Quantum Virtual Machine.

    PyQuil provides the interface to generate Quil programs, while quilc and QVM handle the compilation and simulation/execution logic.

  11. Understand the difference between coherent and incoherent errors

    master

    When modeling noise in quantum computation, it is important to distinguish between two types of gate errors:

    1. Coherent errors: These preserve the purity of the input state. Instead of the intended unitary $U$, a perturbed but still unitary operation $\tilde{U}$ is applied (${\tilde{U} \neq U}$). These are, in principle, amendable by more precise control calibration.
    2. Incoherent errors: These do not preserve the purity of the input state and require density matrices for representation. The evolution is described by a Kraus map: $\rho \mapsto \sum_{j=1}^m K_j\rho K_j^\dagger$, where $K_j$ are Kraus operators. These errors typically arise from the system coupling to its environment.
  12. Implement Dephasing noise

    master

    Dephasing (characterized by $T_2$ time) is a diagonal error channel. For a single qubit, the Kraus operators are:

    $K_1(p) = \sqrt{1-p} I_2$ $K_2(p) = \sqrt{p} \sigma_Z$

    where $p$ is the dephasing probability. For multi-qubit gates, you can construct a composite Kraus map by taking the tensor product of individual qubit Kraus maps.

    import numpy as np
    
    def dephasing_kraus_map(p=.1):
        """
        Generate the Kraus operators corresponding to a dephasing channel.
    
        :params float p: The one-step dephasing probability.
        :return: A list [k1, k2] of the Kraus operators that parametrize the map.
        :rtype: list
        """
        return [np.sqrt(1-p)*np.eye(2), np.sqrt(p)*np.diag([1, -1])]
    
    def tensor_kraus_maps(k1, k2):
        """
        Generate the Kraus map corresponding to the composition
        of two maps on different qubits.
    
        :param list k1: The Kraus operators for the first qubit.
        :param list k2: The Kraus operators for the second qubit.
        :return: A list of tensored Kraus operators.
        """
        return [np.kron(k1j, k2l) for k1j in k1 for k2l in k2]