Skip to content

tn_noise_learning.circuits#

Ideal circuit as a tensor network that conjugates input states, ρ → U ρ U†.

Each two-qubit gate is split by an operator-Schmidt decomposition into two site tensors joined by a bond, so the construction works on any qubit connectivity without compression. Raw states enter on legs p{j} (ket) and q{j} (bra) and leave on the LPDO input legs k{j} and b{j}.

apply_circuit #

apply_circuit(
    states_tn: TensorNetwork, circuit: QuantumCircuit
) -> qtn.TensorNetwork

Evolve a raw state network (legs p, q) to U ρ U† with legs k, b, one tensor per site.

Source code in src/tn_noise_learning/circuits.py
123
124
125
126
127
128
def apply_circuit(states_tn: qtn.TensorNetwork, circuit: QuantumCircuit) -> qtn.TensorNetwork:
    """Evolve a raw state network (legs ``p, q``) to ``U ρ U†`` with legs ``k, b``, one tensor per site."""
    tn = qtn.TensorNetwork([states_tn, circuit_network(circuit)], check_collisions=False)
    for j in range(circuit.num_qubits):
        tn.contract_tags_(site_tag(j))
    return tn

circuit_network #

circuit_network(
    circuit: QuantumCircuit,
) -> qtn.TensorNetwork

One tensor per site mapping (p_j, q_j) to (k_j, b_j) such that the whole network is U · U†.

Only one- and two-qubit unitary gates are supported; barriers are skipped.

Source code in src/tn_noise_learning/circuits.py
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def circuit_network(circuit: QuantumCircuit) -> qtn.TensorNetwork:
    """One tensor per site mapping ``(p_j, q_j)`` to ``(k_j, b_j)`` such that the whole network is ``U · U†``.

    Only one- and two-qubit unitary gates are supported; barriers are skipped.
    """
    num_sites = circuit.num_qubits
    identity = np.eye(4, dtype=complex).reshape(2, 2, 2, 2)
    tn = qtn.TensorNetwork(
        [
            qtn.Tensor(
                identity,
                inds=(raw_ind(j), raw_ind(j, dagger=True), input_ind(j), input_ind(j, dagger=True)),
                tags=site_tag(j),
            )
            for j in range(num_sites)
        ]
    )
    for instruction in circuit.data:
        operation = instruction.operation
        if operation.name == "barrier":
            continue
        sites = [circuit.find_bit(q).index for q in instruction.qubits]
        if len(sites) > 2:
            raise ValueError(
                f"Gate {operation.name} acts on {len(sites)} qubits; only one- and two-qubit gates are supported."
            )
        try:
            gate = np.asarray(Operator(operation).data, dtype=complex)
        except Exception as error:
            raise ValueError(f"Operation {operation.name} is not a unitary gate.") from error
        pieces = [(sites[0], gate[None])] if len(sites) == 1 else list(zip(sites, _schmidt_pair(gate), strict=True))
        bond_ket, bond_bra = qtn.rand_uuid("s"), qtn.rand_uuid("s")
        for site, piece in pieces:
            old_ket, old_bra = qtn.rand_uuid("s"), qtn.rand_uuid("s")
            tn.reindex_({input_ind(site): old_ket, input_ind(site, dagger=True): old_bra})
            tn.add_tensor(qtn.Tensor(piece, inds=(bond_ket, input_ind(site), old_ket), tags=site_tag(site)))
            tn.add_tensor(
                qtn.Tensor(piece.conj(), inds=(bond_bra, input_ind(site, dagger=True), old_bra), tags=site_tag(site))
            )
    for j in range(num_sites):
        tn.contract_tags_(site_tag(j))
    tn.fuse_multibonds_()
    tn.squeeze_()
    largest = max(t.size for t in tn)
    if largest > LARGE_TENSOR_SIZE:
        logger.warning(
            "Circuit network has a site tensor with %d elements; the circuit is deep for an uncompressed decomposition",
            largest,
        )
    return tn

product_state_network #

product_state_network(
    states: ndarray | Array, *, raw: bool = True
) -> qtn.TensorNetwork

Batched product states states[j, exp] of shape (num_sites, batch, 2, 2) as site tensors.

Each tensor has legs (exp, ket_j, bra_j): p{j}, q{j} with raw=True (to be evolved by a circuit), otherwise k{j}, b{j}.

Source code in src/tn_noise_learning/circuits.py
111
112
113
114
115
116
117
118
119
120
def product_state_network(states: np.ndarray | Array, *, raw: bool = True) -> qtn.TensorNetwork:
    """Batched product states ``states[j, exp]`` of shape ``(num_sites, batch, 2, 2)`` as site tensors.

    Each tensor has legs ``(exp, ket_j, bra_j)``: ``p{j}, q{j}`` with ``raw=True`` (to be evolved by a circuit),
    otherwise ``k{j}, b{j}``.
    """
    ket, bra = (raw_ind, raw_ind) if raw else (input_ind, input_ind)
    return qtn.TensorNetwork(
        [qtn.Tensor(s, inds=(BATCH_IND, ket(j), bra(j, dagger=True)), tags=site_tag(j)) for j, s in enumerate(states)]
    )

raw_ind #

raw_ind(site: int, *, dagger: bool = False) -> str

Leg of a raw (not yet evolved) state: ket side p{j} or bra side q{j}.

Source code in src/tn_noise_learning/circuits.py
44
45
46
def raw_ind(site: int, *, dagger: bool = False) -> str:
    """Leg of a raw (not yet evolved) state: ket side ``p{j}`` or bra side ``q{j}``."""
    return f"{'q' if dagger else 'p'}{site}"