Spaces:
Sleeping
Sleeping
Create QuantumCircuits.py
Browse files- QuantumCircuits.py +400 -0
QuantumCircuits.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
QuantumCircuits.py
|
| 3 |
+
Heisenberg-picture quantum circuit encoding for the Practicality Wisdom Layer.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
import math
|
| 8 |
+
from dataclasses import dataclass, field
|
| 9 |
+
from typing import List, Dict, Tuple, Set, Any
|
| 10 |
+
|
| 11 |
+
# ββ Gate definitions βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
+
|
| 13 |
+
GATE_TYPES = {
|
| 14 |
+
"Rx", "Ry", "Rz", # Parameterized rotations
|
| 15 |
+
"H", # Hadamard
|
| 16 |
+
"S", "T", # Phase gates
|
| 17 |
+
"CNOT", # Entangling gate
|
| 18 |
+
"CZ", # Controlled-Z
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class QGate:
|
| 23 |
+
type: str
|
| 24 |
+
qubits: List[int]
|
| 25 |
+
params: List[str] = field(default_factory=list)
|
| 26 |
+
layer: int = 0
|
| 27 |
+
|
| 28 |
+
def validate(self):
|
| 29 |
+
if self.type not in GATE_TYPES:
|
| 30 |
+
raise ValueError(f"Unknown gate: {self.type}")
|
| 31 |
+
if self.type in ("Rx", "Ry", "Rz") and len(self.params) != 1:
|
| 32 |
+
raise ValueError(f"{self.type} requires exactly 1 parameter")
|
| 33 |
+
if self.type in ("CNOT", "CZ") and len(self.qubits) != 2:
|
| 34 |
+
raise ValueError(f"{self.type} requires exactly 2 qubits")
|
| 35 |
+
if self.type in ("H", "S", "T") and len(self.qubits) != 1:
|
| 36 |
+
raise ValueError(f"{self.type} is a single-qubit gate")
|
| 37 |
+
|
| 38 |
+
@dataclass
|
| 39 |
+
class QuantumCircuit:
|
| 40 |
+
n_qubits: int
|
| 41 |
+
gates: List[QGate]
|
| 42 |
+
param_bounds: Dict[str, Tuple[float, float]] = field(default_factory=dict)
|
| 43 |
+
initial_state: str = "zero"
|
| 44 |
+
initial_obs: Dict[str, float] = field(default_factory=dict)
|
| 45 |
+
target_hamiltonian: Dict[str, float] = field(default_factory=dict)
|
| 46 |
+
include_2body: bool = True
|
| 47 |
+
name: str = "QuantumCircuit"
|
| 48 |
+
description: str = ""
|
| 49 |
+
|
| 50 |
+
@property
|
| 51 |
+
def n_layers(self) -> int:
|
| 52 |
+
if not self.gates: return 0
|
| 53 |
+
return max(g.layer for g in self.gates) + 1
|
| 54 |
+
|
| 55 |
+
@property
|
| 56 |
+
def has_cnot(self) -> bool:
|
| 57 |
+
return any(g.type in ("CNOT", "CZ") for g in self.gates)
|
| 58 |
+
|
| 59 |
+
@property
|
| 60 |
+
def all_params(self) -> List[str]:
|
| 61 |
+
seen = set()
|
| 62 |
+
params = []
|
| 63 |
+
for g in self.gates:
|
| 64 |
+
for p in g.params:
|
| 65 |
+
if p not in seen:
|
| 66 |
+
seen.add(p)
|
| 67 |
+
params.append(p)
|
| 68 |
+
return params
|
| 69 |
+
|
| 70 |
+
# ββ Observable naming βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 71 |
+
|
| 72 |
+
PAULI = ("x", "y", "z")
|
| 73 |
+
PAULI2 = [p1 + p2 for p1 in PAULI for p2 in PAULI]
|
| 74 |
+
|
| 75 |
+
def obs1(pauli: str, qubit: int, layer: int) -> str:
|
| 76 |
+
return f"{pauli}{qubit}_L{layer}"
|
| 77 |
+
|
| 78 |
+
def obs2(pauli1: str, pauli2: str, q1: int, q2: int, layer: int) -> str:
|
| 79 |
+
if q1 > q2:
|
| 80 |
+
q1, q2 = q2, q1
|
| 81 |
+
pauli1, pauli2 = pauli2, pauli1
|
| 82 |
+
return f"{pauli1}{pauli2}{q1}{q2}_L{layer}"
|
| 83 |
+
|
| 84 |
+
def _initial_obs_values(circuit: QuantumCircuit) -> Dict[str, float]:
|
| 85 |
+
obs = {}
|
| 86 |
+
if circuit.initial_state == "zero":
|
| 87 |
+
for q in range(circuit.n_qubits):
|
| 88 |
+
obs[obs1("z", q, 0)] = 1.0
|
| 89 |
+
obs[obs1("x", q, 0)] = 0.0
|
| 90 |
+
obs[obs1("y", q, 0)] = 0.0
|
| 91 |
+
if circuit.include_2body:
|
| 92 |
+
for q1 in range(circuit.n_qubits):
|
| 93 |
+
for q2 in range(q1 + 1, circuit.n_qubits):
|
| 94 |
+
for p1, p2 in PAULI2:
|
| 95 |
+
obs[obs2(p1, p2, q1, q2, 0)] = 1.0 if (p1 == "z" and p2 == "z") else 0.0
|
| 96 |
+
|
| 97 |
+
elif circuit.initial_state == "plus":
|
| 98 |
+
for q in range(circuit.n_qubits):
|
| 99 |
+
obs[obs1("x", q, 0)] = 1.0
|
| 100 |
+
obs[obs1("y", q, 0)] = 0.0
|
| 101 |
+
obs[obs1("z", q, 0)] = 0.0
|
| 102 |
+
if circuit.include_2body:
|
| 103 |
+
for q1 in range(circuit.n_qubits):
|
| 104 |
+
for q2 in range(q1 + 1, circuit.n_qubits):
|
| 105 |
+
for p1p2 in PAULI2:
|
| 106 |
+
obs[obs2(p1p2[0], p1p2[1], q1, q2, 0)] = 1.0 if p1p2 == "xx" else 0.0
|
| 107 |
+
|
| 108 |
+
elif circuit.initial_state == "custom":
|
| 109 |
+
obs.update(circuit.initial_obs)
|
| 110 |
+
return obs
|
| 111 |
+
|
| 112 |
+
# ββ Heisenberg evolution constraints βββββββββββββββββββββββββββββββββ
|
| 113 |
+
|
| 114 |
+
def _rz_constraints(q: int, theta: str, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 115 |
+
constraints = []
|
| 116 |
+
xq_in, yq_in, zq_in = obs1("x", q, layer_in), obs1("y", q, layer_in), obs1("z", q, layer_in)
|
| 117 |
+
xq_out, yq_out, zq_out = obs1("x", q, layer_out), obs1("y", q, layer_out), obs1("z", q, layer_out)
|
| 118 |
+
|
| 119 |
+
constraints.extend([
|
| 120 |
+
{"kind": "EQ", "expr": f"{xq_out} - cos({theta})*{xq_in} - sin({theta})*{yq_in}", "weight": 5.0},
|
| 121 |
+
{"kind": "EQ", "expr": f"{yq_out} + sin({theta})*{xq_in} - cos({theta})*{yq_in}", "weight": 5.0},
|
| 122 |
+
{"kind": "EQ", "expr": f"{zq_out} - {zq_in}", "weight": 5.0}
|
| 123 |
+
])
|
| 124 |
+
|
| 125 |
+
if circuit.include_2body:
|
| 126 |
+
for r in range(circuit.n_qubits):
|
| 127 |
+
if r == q: continue
|
| 128 |
+
for pr in PAULI:
|
| 129 |
+
xpr_in, ypr_in, zpr_in = obs2("x", pr, q, r, layer_in), obs2("y", pr, q, r, layer_in), obs2("z", pr, q, r, layer_in)
|
| 130 |
+
xpr_out, ypr_out, zpr_out = obs2("x", pr, q, r, layer_out), obs2("y", pr, q, r, layer_out), obs2("z", pr, q, r, layer_out)
|
| 131 |
+
constraints.extend([
|
| 132 |
+
{"kind": "EQ", "expr": f"{xpr_out} - cos({theta})*{xpr_in} - sin({theta})*{ypr_in}", "weight": 5.0},
|
| 133 |
+
{"kind": "EQ", "expr": f"{ypr_out} + sin({theta})*{xpr_in} - cos({theta})*{ypr_in}", "weight": 5.0},
|
| 134 |
+
{"kind": "EQ", "expr": f"{zpr_out} - {zpr_in}", "weight": 5.0}
|
| 135 |
+
])
|
| 136 |
+
return constraints
|
| 137 |
+
|
| 138 |
+
def _rx_constraints(q: int, theta: str, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 139 |
+
constraints = []
|
| 140 |
+
xq_in, yq_in, zq_in = obs1("x", q, layer_in), obs1("y", q, layer_in), obs1("z", q, layer_in)
|
| 141 |
+
xq_out, yq_out, zq_out = obs1("x", q, layer_out), obs1("y", q, layer_out), obs1("z", q, layer_out)
|
| 142 |
+
|
| 143 |
+
constraints.extend([
|
| 144 |
+
{"kind": "EQ", "expr": f"{xq_out} - {xq_in}", "weight": 5.0},
|
| 145 |
+
{"kind": "EQ", "expr": f"{yq_out} - cos({theta})*{yq_in} + sin({theta})*{zq_in}", "weight": 5.0},
|
| 146 |
+
{"kind": "EQ", "expr": f"{zq_out} - cos({theta})*{zq_in} - sin({theta})*{yq_in}", "weight": 5.0}
|
| 147 |
+
])
|
| 148 |
+
|
| 149 |
+
if circuit.include_2body:
|
| 150 |
+
for r in range(circuit.n_qubits):
|
| 151 |
+
if r == q: continue
|
| 152 |
+
for pr in PAULI:
|
| 153 |
+
xpr_in, ypr_in, zpr_in = obs2("x", pr, q, r, layer_in), obs2("y", pr, q, r, layer_in), obs2("z", pr, q, r, layer_in)
|
| 154 |
+
xpr_out, ypr_out, zpr_out = obs2("x", pr, q, r, layer_out), obs2("y", pr, q, r, layer_out), obs2("z", pr, q, r, layer_out)
|
| 155 |
+
constraints.extend([
|
| 156 |
+
{"kind": "EQ", "expr": f"{xpr_out} - {xpr_in}", "weight": 5.0},
|
| 157 |
+
{"kind": "EQ", "expr": f"{ypr_out} - cos({theta})*{ypr_in} + sin({theta})*{zpr_in}", "weight": 5.0},
|
| 158 |
+
{"kind": "EQ", "expr": f"{zpr_out} - cos({theta})*{zpr_in} - sin({theta})*{ypr_in}", "weight": 5.0}
|
| 159 |
+
])
|
| 160 |
+
return constraints
|
| 161 |
+
|
| 162 |
+
def _hadamard_constraints(q: int, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 163 |
+
constraints = []
|
| 164 |
+
xq_in, yq_in, zq_in = obs1("x", q, layer_in), obs1("y", q, layer_in), obs1("z", q, layer_in)
|
| 165 |
+
xq_out, yq_out, zq_out = obs1("x", q, layer_out), obs1("y", q, layer_out), obs1("z", q, layer_out)
|
| 166 |
+
|
| 167 |
+
constraints.extend([
|
| 168 |
+
{"kind": "EQ", "expr": f"{xq_out} - {zq_in}", "weight": 5.0},
|
| 169 |
+
{"kind": "EQ", "expr": f"{yq_out} + {yq_in}", "weight": 5.0},
|
| 170 |
+
{"kind": "EQ", "expr": f"{zq_out} - {xq_in}", "weight": 5.0}
|
| 171 |
+
])
|
| 172 |
+
|
| 173 |
+
if circuit.include_2body:
|
| 174 |
+
for r in range(circuit.n_qubits):
|
| 175 |
+
if r == q: continue
|
| 176 |
+
for pr in PAULI:
|
| 177 |
+
xpr_in, ypr_in, zpr_in = obs2("x", pr, q, r, layer_in), obs2("y", pr, q, r, layer_in), obs2("z", pr, q, r, layer_in)
|
| 178 |
+
xpr_out, ypr_out, zpr_out = obs2("x", pr, q, r, layer_out), obs2("y", pr, q, r, layer_out), obs2("z", pr, q, r, layer_out)
|
| 179 |
+
constraints.extend([
|
| 180 |
+
{"kind": "EQ", "expr": f"{xpr_out} - {zpr_in}", "weight": 5.0},
|
| 181 |
+
{"kind": "EQ", "expr": f"{ypr_out} + {ypr_in}", "weight": 5.0},
|
| 182 |
+
{"kind": "EQ", "expr": f"{zpr_out} - {xpr_in}", "weight": 5.0}
|
| 183 |
+
])
|
| 184 |
+
return constraints
|
| 185 |
+
|
| 186 |
+
def _cnot_constraints(control: int, target: int, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 187 |
+
if not circuit.include_2body:
|
| 188 |
+
raise ValueError("CNOT requires include_2body=True")
|
| 189 |
+
k, j = control, target
|
| 190 |
+
constraints = []
|
| 191 |
+
|
| 192 |
+
# ββ Single-qubit output
|
| 193 |
+
constraints.extend([
|
| 194 |
+
{"kind": "EQ", "expr": f"{obs1('x', k, layer_out)} - {obs2('x', 'x', k, j, layer_in)}", "weight": 5.0},
|
| 195 |
+
{"kind": "EQ", "expr": f"{obs1('y', k, layer_out)} - {obs2('y', 'x', k, j, layer_in)}", "weight": 5.0},
|
| 196 |
+
{"kind": "EQ", "expr": f"{obs1('z', k, layer_out)} - {obs1('z', k, layer_in)}", "weight": 5.0},
|
| 197 |
+
{"kind": "EQ", "expr": f"{obs1('x', j, layer_out)} - {obs1('x', j, layer_in)}", "weight": 5.0},
|
| 198 |
+
{"kind": "EQ", "expr": f"{obs1('y', j, layer_out)} - {obs2('z', 'y', k, j, layer_in)}", "weight": 5.0},
|
| 199 |
+
{"kind": "EQ", "expr": f"{obs1('z', j, layer_out)} - {obs2('z', 'z', k, j, layer_in)}", "weight": 5.0}
|
| 200 |
+
])
|
| 201 |
+
|
| 202 |
+
# ββ Two-qubit output (ctrl and tgt)
|
| 203 |
+
constraints.extend([
|
| 204 |
+
{"kind": "EQ", "expr": f"{obs2('x', 'x', k, j, layer_out)} - {obs1('x', k, layer_in)}", "weight": 5.0},
|
| 205 |
+
{"kind": "EQ", "expr": f"{obs2('z', 'z', k, j, layer_out)} - {obs1('z', j, layer_in)}", "weight": 5.0},
|
| 206 |
+
{"kind": "EQ", "expr": f"{obs2('z', 'x', k, j, layer_out)} - {obs2('z', 'x', k, j, layer_in)}", "weight": 5.0},
|
| 207 |
+
{"kind": "EQ", "expr": f"{obs2('x', 'z', k, j, layer_out)} + {obs2('y', 'y', k, j, layer_in)}", "weight": 5.0},
|
| 208 |
+
{"kind": "EQ", "expr": f"{obs2('y', 'z', k, j, layer_out)} - {obs2('x', 'y', k, j, layer_in)}", "weight": 5.0}
|
| 209 |
+
])
|
| 210 |
+
|
| 211 |
+
handled_pairs = {("x","x"), ("z","z"), ("z","x"), ("x","z"), ("y","z"), ("y","x"), ("x","y"), ("z","y"), ("y","y")}
|
| 212 |
+
for p1p2 in PAULI2:
|
| 213 |
+
if (p1p2[0], p1p2[1]) not in handled_pairs:
|
| 214 |
+
constraints.append({
|
| 215 |
+
"kind": "EQ",
|
| 216 |
+
"expr": f"{obs2(p1p2[0], p1p2[1], k, j, layer_out)} - {obs2(p1p2[0], p1p2[1], k, j, layer_in)}",
|
| 217 |
+
"weight": 2.0
|
| 218 |
+
})
|
| 219 |
+
return constraints
|
| 220 |
+
|
| 221 |
+
# ββ Main builder ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 222 |
+
|
| 223 |
+
def build_quantum_axl(circuit: QuantumCircuit, axl_problem_class: Any, axl_invariant_class: Any) -> Any:
|
| 224 |
+
"""
|
| 225 |
+
Builds the AXL problem.
|
| 226 |
+
Pass AXLProblemDef and AXLInvariant classes from your main script to avoid circular imports.
|
| 227 |
+
"""
|
| 228 |
+
for g in circuit.gates: g.validate()
|
| 229 |
+
|
| 230 |
+
n = circuit.n_qubits
|
| 231 |
+
n_layers = circuit.n_layers
|
| 232 |
+
include_2b = circuit.include_2body and circuit.has_cnot
|
| 233 |
+
|
| 234 |
+
variables: List[Dict] = []
|
| 235 |
+
|
| 236 |
+
for pname in circuit.all_params:
|
| 237 |
+
lo, hi = circuit.param_bounds.get(pname, (-math.pi, math.pi))
|
| 238 |
+
variables.append({"name": pname, "lo": lo, "hi": hi})
|
| 239 |
+
|
| 240 |
+
for layer in range(n_layers + 1):
|
| 241 |
+
for q in range(n):
|
| 242 |
+
for p in PAULI:
|
| 243 |
+
variables.append({"name": obs1(p, q, layer), "lo": -1.0, "hi": 1.0})
|
| 244 |
+
if include_2b:
|
| 245 |
+
for q1 in range(n):
|
| 246 |
+
for q2 in range(q1 + 1, n):
|
| 247 |
+
for p1p2 in PAULI2:
|
| 248 |
+
variables.append({"name": obs2(p1p2[0], p1p2[1], q1, q2, layer), "lo": -1.0, "hi": 1.0})
|
| 249 |
+
|
| 250 |
+
obs_fixed = _initial_obs_values(circuit)
|
| 251 |
+
|
| 252 |
+
global_constraints: List[Dict] = []
|
| 253 |
+
for layer in range(n_layers + 1):
|
| 254 |
+
for q in range(n):
|
| 255 |
+
global_constraints.append({
|
| 256 |
+
"kind": "GEQ",
|
| 257 |
+
"expr": f"1.0 - {obs1('x', q, layer)}**2 - {obs1('y', q, layer)}**2 - {obs1('z', q, layer)}**2",
|
| 258 |
+
"weight": 2.0
|
| 259 |
+
})
|
| 260 |
+
|
| 261 |
+
scopes: List[Dict] = []
|
| 262 |
+
for layer_in in range(n_layers):
|
| 263 |
+
layer_out = layer_in + 1
|
| 264 |
+
layer_gates = [g for g in circuit.gates if g.layer == layer_in]
|
| 265 |
+
scope_constraints: List[Dict] = []
|
| 266 |
+
gates_affecting: Set[int] = set()
|
| 267 |
+
|
| 268 |
+
for gate in layer_gates:
|
| 269 |
+
if gate.type == "Rz":
|
| 270 |
+
gates_affecting.add(gate.qubits[0])
|
| 271 |
+
scope_constraints.extend(_rz_constraints(gate.qubits[0], gate.params[0], layer_in, layer_out, circuit))
|
| 272 |
+
elif gate.type == "Rx":
|
| 273 |
+
gates_affecting.add(gate.qubits[0])
|
| 274 |
+
scope_constraints.extend(_rx_constraints(gate.qubits[0], gate.params[0], layer_in, layer_out, circuit))
|
| 275 |
+
elif gate.type == "H":
|
| 276 |
+
gates_affecting.add(gate.qubits[0])
|
| 277 |
+
scope_constraints.extend(_hadamard_constraints(gate.qubits[0], layer_in, layer_out, circuit))
|
| 278 |
+
elif gate.type == "CNOT":
|
| 279 |
+
gates_affecting.update(gate.qubits)
|
| 280 |
+
scope_constraints.extend(_cnot_constraints(gate.qubits[0], gate.qubits[1], layer_in, layer_out, circuit))
|
| 281 |
+
elif gate.type == "S":
|
| 282 |
+
q = gate.qubits[0]
|
| 283 |
+
gates_affecting.add(q)
|
| 284 |
+
scope_constraints.extend([
|
| 285 |
+
{"kind": "EQ", "expr": f"{obs1('x', q, layer_out)} - {obs1('y', q, layer_in)}", "weight": 5.0},
|
| 286 |
+
{"kind": "EQ", "expr": f"{obs1('y', q, layer_out)} + {obs1('x', q, layer_in)}", "weight": 5.0},
|
| 287 |
+
{"kind": "EQ", "expr": f"{obs1('z', q, layer_out)} - {obs1('z', q, layer_in)}", "weight": 5.0}
|
| 288 |
+
])
|
| 289 |
+
elif gate.type == "T":
|
| 290 |
+
q = gate.qubits[0]
|
| 291 |
+
gates_affecting.add(q)
|
| 292 |
+
c = round(math.cos(math.pi / 4), 8)
|
| 293 |
+
scope_constraints.extend([
|
| 294 |
+
{"kind": "EQ", "expr": f"{obs1('x', q, layer_out)} - {c}*{obs1('x', q, layer_in)} - {c}*{obs1('y', q, layer_in)}", "weight": 5.0},
|
| 295 |
+
{"kind": "EQ", "expr": f"{obs1('y', q, layer_out)} + {c}*{obs1('x', q, layer_in)} - {c}*{obs1('y', q, layer_in)}", "weight": 5.0},
|
| 296 |
+
{"kind": "EQ", "expr": f"{obs1('z', q, layer_out)} - {obs1('z', q, layer_in)}", "weight": 5.0}
|
| 297 |
+
])
|
| 298 |
+
|
| 299 |
+
unaffected = [q for q in range(n) if q not in gates_affecting]
|
| 300 |
+
for q in unaffected:
|
| 301 |
+
for p in PAULI:
|
| 302 |
+
scope_constraints.append({"kind": "EQ", "expr": f"{obs1(p, q, layer_out)} - {obs1(p, q, layer_in)}", "weight": 5.0})
|
| 303 |
+
if include_2b:
|
| 304 |
+
for q2 in range(n):
|
| 305 |
+
if q2 == q or q2 in gates_affecting: continue
|
| 306 |
+
for p1p2 in PAULI2:
|
| 307 |
+
scope_constraints.append({
|
| 308 |
+
"kind": "EQ",
|
| 309 |
+
"expr": f"{obs2(p1p2[0], p1p2[1], q, q2, layer_out)} - {obs2(p1p2[0], p1p2[1], q, q2, layer_in)}",
|
| 310 |
+
"weight": 5.0
|
| 311 |
+
})
|
| 312 |
+
|
| 313 |
+
scope_var_names = []
|
| 314 |
+
for q in range(n):
|
| 315 |
+
for p in PAULI:
|
| 316 |
+
scope_var_names.extend([obs1(p, q, layer_in), obs1(p, q, layer_out)])
|
| 317 |
+
if include_2b:
|
| 318 |
+
for q1 in range(n):
|
| 319 |
+
for q2 in range(q1 + 1, n):
|
| 320 |
+
for p1p2 in PAULI2:
|
| 321 |
+
scope_var_names.extend([obs2(p1p2[0], p1p2[1], q1, q2, layer_in), obs2(p1p2[0], p1p2[1], q1, q2, layer_out)])
|
| 322 |
+
scope_var_names.extend(circuit.all_params)
|
| 323 |
+
|
| 324 |
+
scopes.append({
|
| 325 |
+
"name": f"layer_{layer_in}_to_{layer_out}",
|
| 326 |
+
"order": layer_in,
|
| 327 |
+
"vars": scope_var_names,
|
| 328 |
+
"constraints": scope_constraints,
|
| 329 |
+
})
|
| 330 |
+
|
| 331 |
+
anchors = []
|
| 332 |
+
if circuit.target_hamiltonian:
|
| 333 |
+
h_terms = []
|
| 334 |
+
for pauli_str, coeff in circuit.target_hamiltonian.items():
|
| 335 |
+
if len(pauli_str) == 2:
|
| 336 |
+
h_terms.append(f"({coeff})*{obs1(pauli_str[0], int(pauli_str[1:]), n_layers)}")
|
| 337 |
+
elif len(pauli_str) == 4:
|
| 338 |
+
h_terms.append(f"({coeff})*{obs2(pauli_str[0], pauli_str[1], int(pauli_str[2]), int(pauli_str[3]), n_layers)}")
|
| 339 |
+
if h_terms:
|
| 340 |
+
anchors.append(axl_invariant_class(
|
| 341 |
+
name="hamiltonian_expectation",
|
| 342 |
+
expr=" + ".join(h_terms),
|
| 343 |
+
tolerance=0.01,
|
| 344 |
+
mode="eq"
|
| 345 |
+
))
|
| 346 |
+
|
| 347 |
+
for q in range(n):
|
| 348 |
+
anchors.append(axl_invariant_class(
|
| 349 |
+
name=f"bloch_q{q}",
|
| 350 |
+
expr=f"1.0 - {obs1('x', q, n_layers)}**2 - {obs1('y', q, n_layers)}**2 - {obs1('z', q, n_layers)}**2",
|
| 351 |
+
tolerance=0.05,
|
| 352 |
+
mode="geq"
|
| 353 |
+
))
|
| 354 |
+
|
| 355 |
+
return axl_problem_class(
|
| 356 |
+
name=circuit.name,
|
| 357 |
+
description=(circuit.description or f"{n}-qubit circuit, {n_layers} layers"),
|
| 358 |
+
axioms={"CONTINUOUS", "CONSERVED", "TRANSITIVE", "BILINEAR", "METRIC", "SUPERPOSITION", "SYMMETRIC"},
|
| 359 |
+
variables=variables,
|
| 360 |
+
constraints=global_constraints,
|
| 361 |
+
scopes=scopes,
|
| 362 |
+
anchors=anchors,
|
| 363 |
+
observations=obs_fixed,
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 367 |
+
# EXAMPLE CIRCUITS
|
| 368 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 369 |
+
|
| 370 |
+
def example_bell_state() -> QuantumCircuit:
|
| 371 |
+
return QuantumCircuit(
|
| 372 |
+
n_qubits=2, name="BellStatePrep",
|
| 373 |
+
gates=[QGate("H", [0], layer=0), QGate("CNOT", [0, 1], layer=1)],
|
| 374 |
+
initial_state="zero", include_2body=True,
|
| 375 |
+
target_hamiltonian={"zz01": -1.0}
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
def example_vqe_h2() -> QuantumCircuit:
|
| 379 |
+
return QuantumCircuit(
|
| 380 |
+
n_qubits=2, name="VQE_H2",
|
| 381 |
+
gates=[
|
| 382 |
+
QGate("Ry", [0], ["theta0"], layer=0),
|
| 383 |
+
QGate("Ry", [1], ["theta1"], layer=0),
|
| 384 |
+
QGate("CNOT", [0, 1], layer=1),
|
| 385 |
+
],
|
| 386 |
+
param_bounds={"theta0": (-math.pi, math.pi), "theta1": (-math.pi, math.pi)},
|
| 387 |
+
target_hamiltonian={"z0": -0.5, "z1": -0.5, "zz01": 0.25, "xx01": -0.5}
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
def example_qaoa_maxcut_p1() -> QuantumCircuit:
|
| 391 |
+
return QuantumCircuit(
|
| 392 |
+
n_qubits=3, name="QAOA_MaxCut",
|
| 393 |
+
gates=[
|
| 394 |
+
QGate("H", [0], layer=0), QGate("H", [1], layer=0), QGate("H", [2], layer=0),
|
| 395 |
+
QGate("Rz", [0], ["gamma"], layer=1), QGate("Rz", [1], ["gamma"], layer=1), QGate("Rz", [2], ["gamma"], layer=1),
|
| 396 |
+
QGate("Rx", [0], ["beta"], layer=2), QGate("Rx", [1], ["beta"], layer=2), QGate("Rx", [2], ["beta"], layer=2),
|
| 397 |
+
],
|
| 398 |
+
param_bounds={"gamma": (0, math.pi), "beta": (0, math.pi / 2)},
|
| 399 |
+
target_hamiltonian={"zz01": -0.5, "zz12": -0.5, "zz02": -0.5}
|
| 400 |
+
)
|