pyzx

repository·master·Indexed 19 days ago

https://github.com/zxcalc/pyzx

A Python library for quantum circuit rewriting and optimisation using the ZX-calculus. PyZX enables the creation, visualization, and automated rewriting of large-scale quantum circuits, with a specific focus on optimizing Clifford circuits. It supports circuit I/O for QASM, Quipper, and Quantomatic formats, provides a high-level API for diagrammatic composition and simplification (including full_reduce), and includes tools for extracting optimized circuits and routing them to hardware architectures using Gray synthesis.

Tokens
28.6K
Snippets
128
Records
147
Agent score
68%

What's inside pyzx

  1. Core capabilities of PyZX

    master

    PyZX is a tool for the creation, visualization, and automated rewriting of large-scale quantum circuits using the ZX-calculus.

    Key features include:

    • Circuit I/O: Read circuits in QASM, Quipper, or Quantomatic formats. Output optimized circuits in QASM, QC, or QUIPPER formats.
    • Rewriting: Rewrite circuits into a pseudo-normal form using ZX-calculus rules (specifically focusing on the Clifford fragment).
    • Extraction: Extract new simplified circuits from reduced ZX-graphs.
    • Visualization: Visualize ZX-graphs and rewrites using Matplotlib, Quantomatic, or as TikZ files for LaTeX.
  2. What is the ZX-calculus in PyZX?

    master

    ZX-diagrams are tensor networks composed of two types of 'spiders':

    • Z-spiders: Represented as green dots.
    • X-spiders: Represented as red dots.

    PyZX implements a complete set of rewrite rules for the Clifford fragment of the ZX-calculus. This means two representations of a Clifford map can be rewritten into one another if and only if the two linear maps they represent are equal. PyZX extensively uses two derived rewrite rules: local complementation and pivoting.

  3. How Circuits and Graphs work in PyZX

    master

    PyZX uses two primary data structures to represent quantum information:

    1. Circuits (pyzx.circuit.Circuit): A list of gates representing a quantum circuit. Use this for circuit-level operations and standard gate sequences.
    2. Graphs (pyzx.graph.base.BaseGraph): A representation of a ZX-diagram. Most advanced simplification routines in PyZX operate on Graphs rather than Circuits.

    To convert between them:

    • Circuit to Graph: Use circuit.to_graph().
    • Graph to Circuit: Use zx.extract_circuit(graph).

    In a Graph, vertices represent phase gates (Green for Z-phase, Red for X-phase) and edges represent Hadamard gates (shown as blue lines).

    import pyzx as zx
    
    # Create a circuit
    circuit = zx.Circuit.load("path/to/circuit.qasm")
    
    # Convert to a ZX-diagram (Graph)
    g = circuit.to_graph()
    
    # Convert back to a circuit
    c = zx.extract_circuit(g)
  4. Understand ZX-diagram representations in PyZX

    master

    ZX-diagrams are represented by instances of the BaseGraph class. The graph consists of vertices and edges with specific properties:

    Vertex Types

    Vertices are categorized into four types using pyzx.utils.VertexType:

    • VertexType.BOUNDARY: Represents inputs or outputs; carries no phase information.
    • VertexType.Z: Z-spiders.
    • VertexType.X: X-spiders.
    • VertexType.H_BOX: H-boxes (used in ZH-diagrams).

    Non-boundary vertices carry a phase, which is a fraction q representing a phase of $\pi \cdot q$.

    Edge Types

    Edges are categorized using pyzx.utils.EdgeType:

    • EdgeType.SIMPLE: A regular connection.
    • EdgeType.HADAMARD: A connection with a Hadamard gate applied (represented as blue edges in drawings).
  5. Understand the PyZX graph format

    master

    The PyZX graph format is a simple, text-based input language used to define ZX diagrams. It is similar to the GraphViz dot format and allows for easy manual input of diagrams.

    Key concepts:

    • Coordinates: Every vertex is defined by a row and a qubit index.
    • Implicit Rows: The row is a global variable that starts at 1. Using a row separator === increments the row.
    • Boundary Vertices: Vertices on the first row are treated as inputs; vertices on the last row are treated as outputs.
    • Vertex Declaration: Vertices are declared with a name, type, qubit index, and an optional phase (as a rational multiple of $\pi$).
    • Edge Types: Supports normal edges (->) and Hadamard edges (h>).
  6. Choose between Graph backends (Simple vs Multigraph)

    master

    PyZX supports different internal representations via backends:

    • GraphS (Default): A simple graph backend written in Python. It stores at most one edge between a pair of vertices. Adding an edge between already connected vertices will either simplify the edge via ZX rules or raise an error if it cannot be reduced. Use this for standard ZX-diagram manipulation.
    • Multigraph: Stores each edge separately, allowing parallel edges. This is essential for ZH- and ZW-diagrams or when developing rewrite rules that must inspect parallel edges before reduction.
    • GraphIG: A partial implementation using the python-igraph package.

    To create a multigraph, use the graph factory or the class directly:

    import pyzx as zx
    
    # Using factory
    g = zx.Graph("multigraph")
    
    # Using class directly
    from pyzx.graph.multigraph import Multigraph
    g = Multigraph()
  7. Manage parallel edges in a Multigraph

    master

    By default, the Multigraph backend attempts to simplify reducible parallel edges as they are added. To preserve parallel edges exactly (e.g., for testing rewrite rules), disable auto-simplification.

    import pyzx as zx
    
    g = zx.Graph("multigraph")
    g.set_auto_simplify(False)
    
    v = g.add_vertex(zx.VertexType.Z)
    w = g.add_vertex(zx.VertexType.X)
    
    g.add_edge((v, w))
    g.add_edge((v, w))
    
    assert g.num_edges() == 2

    Use g.get_auto_simplify() to check the current setting. Note that for multigraphs, g.edges() yields triples of (source, target, edge_type) and g.edge_set() returns a Counter instead of a plain set.

    import pyzx as zx
    
    g = zx.Graph("multigraph")
    g.set_auto_simplify(False)
    v = g.add_vertex(zx.VertexType.Z)
    w = g.add_vertex(zx.VertexType.X)
    g.add_edge((v, w))
    g.add_edge((v, w))
    
    assert g.num_edges() == 2
  8. Optimize circuits using ZX-calculus simplification

    master

    The primary optimization workflow in PyZX involves converting a quantum circuit into a ZX-diagram, applying simplification rules to the diagram, and then extracting a new circuit.

    Full Reduction Workflow

    1. Convert to Graph: Use c.to_graph() to transform the circuit into a ZX-diagram.
    2. Simplify: Use zx.full_reduce(g) to perform powerful in-place simplification of the graph.
    3. Normalize: Use g.normalize() to prepare the graph for visualization.
    4. Extract: Use zx.extract_circuit(g.copy()) to convert the simplified diagram back into a quantum circuit. Note that full_reduce often results in diagrams that do not resemble standard circuits, so extraction is necessary.

    Phase-Teleportation (T-count optimization)

    If you only want to optimize the T-count without drastically changing the circuit structure, use zx.teleport_reduce(g). This method moves phases around the circuit while keeping the rest of the structure intact. You can then reconstruct the circuit using zx.Circuit.from_graph(g).

    # 1. Generate a circuit
    c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=8, depth=100)
    
    # 2. Convert to ZX-diagram
    g = c.to_graph()
    
    # 3. Simplify the diagram in-place
    zx.full_reduce(g)
    g.normalize()
    
    # 4. Extract the optimized circuit
    c_opt = zx.extract_circuit(g.copy())
  9. Install PyZX via pip

    master

    To use PyZX as a Python module in your own projects, install it using pip:

    pip install pyzx

    Note that while PyZX has no strict dependencies, some functionality requires numpy. For visualization and interactive use, it is recommended to also have matplotlib and jupyter installed.

  10. Verify circuit equality

    master

    PyZX provides two methods to ensure a simplified circuit c_opt is equivalent to the original circuit c.

    For small circuits (< 10 qubits)

    Use zx.compare_tensors(c, c_opt). This method calculates the linear maps (tensors) implemented by the circuits and checks for equality up to a global phase. You can also inspect the linear map manually using c.to_matrix().

    For large circuits

    Use c.verify_equality(c_opt). This method composes the original circuit with the adjoint of the optimized circuit and attempts to reduce the result to the identity using ZX-diagram rewrite strategies. If it returns True, the circuits are likely equivalent.

    # Method 1: Tensor comparison (small circuits)
    is_equal = zx.compare_tensors(c, c_opt)
    
    # Method 2: ZX-reduction (large circuits)
    is_equal = c.verify_equality(c_opt)
  11. Route circuits to specific architectures

    master

    To optimize a circuit for specific hardware constraints (like qubit connectivity), you can use the routing tools in PyZX.

    1. Define an Architecture

    Use pyzx.routing.architecture.create_architecture to create an Architecture object. Supported types include:

    • architecture.SQUARE: A square grid architecture (e.g., create_architecture(architecture.SQUARE, 9) for a 9-qubit grid).
    • architecture.IBM_QX5: A predefined IBM architecture.

    2. Route Phase-Polynomial Circuits

    Routing is specifically designed for phase-polynomial circuits (composed of CNOT, XCX, and ZPhase gates). You can generate such a circuit using zx.generate.phase_poly and then route it using zx.routing.route_phase_poly(circuit, architecture).

    import pyzx as zx
    from pyzx.routing import architecture
    
    # Create an architecture
    ibm_arch = architecture.create_architecture(architecture.IBM_QX5)
    
    # Generate a phase-polynomial circuit
    c_pp = zx.generate.phase_poly(n_qubits=16, n_phase_layers=10, cnots_per_layer=10)
    
    # Route the circuit to the architecture
    routed_circuit = zx.routing.route_phase_poly(c_pp, ibm_arch)