Spaces:
Sleeping
Sleeping
Update QuantumCircuits.py
Browse files- QuantumCircuits.py +74 -306
QuantumCircuits.py
CHANGED
|
@@ -1,19 +1,17 @@
|
|
| 1 |
"""
|
| 2 |
QuantumCircuits.py
|
| 3 |
-
Heisenberg-picture quantum circuit encoding for the Practicality Wisdom Layer.
|
| 4 |
-
|
| 5 |
-
UPDATE 24.3: Deep Entanglement QAOA
|
| 6 |
-
Upgraded QAOA template to use true CNOT-Rz-CNOT edge entangling, expanding the
|
| 7 |
-
circuit to 11 layers to maximally stress the BBGKY Cumulant Expansion. Restored
|
| 8 |
-
exact Ground State targets for optimization routines.
|
| 9 |
"""
|
| 10 |
|
| 11 |
from __future__ import annotations
|
| 12 |
import math
|
|
|
|
| 13 |
from dataclasses import dataclass, field
|
| 14 |
from typing import List, Dict, Tuple, Set, Any
|
| 15 |
|
| 16 |
GATE_TYPES = {"Rx", "Ry", "Rz", "H", "S", "T", "CNOT", "CZ"}
|
|
|
|
|
|
|
| 17 |
|
| 18 |
@dataclass
|
| 19 |
class QGate:
|
|
@@ -25,10 +23,6 @@ class QGate:
|
|
| 25 |
def validate(self):
|
| 26 |
if self.type not in GATE_TYPES:
|
| 27 |
raise ValueError(f"Unknown gate: {self.type}")
|
| 28 |
-
if self.type in ("Rx", "Ry", "Rz") and len(self.params) != 1:
|
| 29 |
-
raise ValueError(f"{self.type} requires exactly 1 parameter")
|
| 30 |
-
if self.type in ("CNOT", "CZ") and len(self.qubits) != 2:
|
| 31 |
-
raise ValueError(f"{self.type} requires exactly 2 qubits")
|
| 32 |
|
| 33 |
@dataclass
|
| 34 |
class QuantumCircuit:
|
|
@@ -61,261 +55,37 @@ class QuantumCircuit:
|
|
| 61 |
params.append(p)
|
| 62 |
return params
|
| 63 |
|
| 64 |
-
PAULI = ("x", "y", "z")
|
| 65 |
-
PAULI2 = [p1 + p2 for p1 in PAULI for p2 in PAULI]
|
| 66 |
-
|
| 67 |
def obs1(pauli: str, qubit: int, layer: int) -> str:
|
| 68 |
return f"{pauli}{qubit}_L{layer}"
|
| 69 |
|
| 70 |
def obs2_safe(pauli1: str, q1: int, pauli2: str, q2: int, layer: int) -> str:
|
| 71 |
-
if q1 == q2:
|
| 72 |
-
|
| 73 |
-
if q1 > q2:
|
| 74 |
-
return f"{pauli2}{pauli1}{q2}{q1}_L{layer}"
|
| 75 |
return f"{pauli1}{pauli2}{q1}{q2}_L{layer}"
|
| 76 |
|
| 77 |
-
def _initial_obs_values(circuit: QuantumCircuit) -> Dict[str, float]:
|
| 78 |
-
obs = {}
|
| 79 |
-
if circuit.initial_state == "zero":
|
| 80 |
-
for q in range(circuit.n_qubits):
|
| 81 |
-
obs[obs1("z", q, 0)] = 1.0
|
| 82 |
-
obs[obs1("x", q, 0)] = 0.0
|
| 83 |
-
obs[obs1("y", q, 0)] = 0.0
|
| 84 |
-
if circuit.include_2body:
|
| 85 |
-
for q1 in range(circuit.n_qubits):
|
| 86 |
-
for q2 in range(q1 + 1, circuit.n_qubits):
|
| 87 |
-
for p1, p2 in PAULI2:
|
| 88 |
-
obs[obs2_safe(p1, q1, p2, q2, 0)] = 1.0 if (p1 == "z" and p2 == "z") else 0.0
|
| 89 |
-
return obs
|
| 90 |
-
|
| 91 |
-
def _cumulant_3(pA: str, qA: int, pB: str, qB: int, pC: str, qC: int, layer: int) -> str:
|
| 92 |
-
oA = obs1(pA, qA, layer)
|
| 93 |
-
oB = obs1(pB, qB, layer)
|
| 94 |
-
oC = obs1(pC, qC, layer)
|
| 95 |
-
oAB = obs2_safe(pA, qA, pB, qB, layer)
|
| 96 |
-
oAC = obs2_safe(pA, qA, pC, qC, layer)
|
| 97 |
-
oBC = obs2_safe(pB, qB, pC, qC, layer)
|
| 98 |
-
return f"({oAB}*{oC} + {oAC}*{oB} + {oBC}*{oA} - 2.0*{oA}*{oB}*{oC})"
|
| 99 |
-
|
| 100 |
-
def _rz_constraints(q: int, theta: str, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 101 |
-
c = []
|
| 102 |
-
xq_in, yq_in, zq_in = obs1("x", q, layer_in), obs1("y", q, layer_in), obs1("z", q, layer_in)
|
| 103 |
-
xq_out, yq_out, zq_out = obs1("x", q, layer_out), obs1("y", q, layer_out), obs1("z", q, layer_out)
|
| 104 |
-
|
| 105 |
-
c.extend([
|
| 106 |
-
{"kind": "EQ", "expr": f"{xq_out} - cos({theta})*{xq_in} - sin({theta})*{yq_in}", "weight": 5.0},
|
| 107 |
-
{"kind": "EQ", "expr": f"{yq_out} + sin({theta})*{xq_in} - cos({theta})*{yq_in}", "weight": 5.0},
|
| 108 |
-
{"kind": "EQ", "expr": f"{zq_out} - {zq_in}", "weight": 5.0}
|
| 109 |
-
])
|
| 110 |
-
|
| 111 |
-
if circuit.include_2body:
|
| 112 |
-
for r in range(circuit.n_qubits):
|
| 113 |
-
if r == q: continue
|
| 114 |
-
for pr in PAULI:
|
| 115 |
-
xpr_in = obs2_safe("x", q, pr, r, layer_in)
|
| 116 |
-
ypr_in = obs2_safe("y", q, pr, r, layer_in)
|
| 117 |
-
zpr_in = obs2_safe("z", q, pr, r, layer_in)
|
| 118 |
-
|
| 119 |
-
xpr_out = obs2_safe("x", q, pr, r, layer_out)
|
| 120 |
-
ypr_out = obs2_safe("y", q, pr, r, layer_out)
|
| 121 |
-
zpr_out = obs2_safe("z", q, pr, r, layer_out)
|
| 122 |
-
|
| 123 |
-
c.extend([
|
| 124 |
-
{"kind": "EQ", "expr": f"{xpr_out} - cos({theta})*{xpr_in} - sin({theta})*{ypr_in}", "weight": 5.0},
|
| 125 |
-
{"kind": "EQ", "expr": f"{ypr_out} + sin({theta})*{xpr_in} - cos({theta})*{ypr_in}", "weight": 5.0},
|
| 126 |
-
{"kind": "EQ", "expr": f"{zpr_out} - {zpr_in}", "weight": 5.0}
|
| 127 |
-
])
|
| 128 |
-
return c
|
| 129 |
-
|
| 130 |
-
def _rx_constraints(q: int, theta: str, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 131 |
-
c = []
|
| 132 |
-
xq_in, yq_in, zq_in = obs1("x", q, layer_in), obs1("y", q, layer_in), obs1("z", q, layer_in)
|
| 133 |
-
xq_out, yq_out, zq_out = obs1("x", q, layer_out), obs1("y", q, layer_out), obs1("z", q, layer_out)
|
| 134 |
-
|
| 135 |
-
c.extend([
|
| 136 |
-
{"kind": "EQ", "expr": f"{xq_out} - {xq_in}", "weight": 5.0},
|
| 137 |
-
{"kind": "EQ", "expr": f"{yq_out} - cos({theta})*{yq_in} + sin({theta})*{zq_in}", "weight": 5.0},
|
| 138 |
-
{"kind": "EQ", "expr": f"{zq_out} - cos({theta})*{zq_in} - sin({theta})*{yq_in}", "weight": 5.0}
|
| 139 |
-
])
|
| 140 |
-
|
| 141 |
-
if circuit.include_2body:
|
| 142 |
-
for r in range(circuit.n_qubits):
|
| 143 |
-
if r == q: continue
|
| 144 |
-
for pr in PAULI:
|
| 145 |
-
xpr_in = obs2_safe("x", q, pr, r, layer_in)
|
| 146 |
-
ypr_in = obs2_safe("y", q, pr, r, layer_in)
|
| 147 |
-
zpr_in = obs2_safe("z", q, pr, r, layer_in)
|
| 148 |
-
|
| 149 |
-
xpr_out = obs2_safe("x", q, pr, r, layer_out)
|
| 150 |
-
ypr_out = obs2_safe("y", q, pr, r, layer_out)
|
| 151 |
-
zpr_out = obs2_safe("z", q, pr, r, layer_out)
|
| 152 |
-
|
| 153 |
-
c.extend([
|
| 154 |
-
{"kind": "EQ", "expr": f"{xpr_out} - {xpr_in}", "weight": 5.0},
|
| 155 |
-
{"kind": "EQ", "expr": f"{ypr_out} - cos({theta})*{ypr_in} + sin({theta})*{zpr_in}", "weight": 5.0},
|
| 156 |
-
{"kind": "EQ", "expr": f"{zpr_out} - cos({theta})*{zpr_in} - sin({theta})*{ypr_in}", "weight": 5.0}
|
| 157 |
-
])
|
| 158 |
-
return c
|
| 159 |
-
|
| 160 |
-
def _hadamard_constraints(q: int, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 161 |
-
c = []
|
| 162 |
-
xq_in, yq_in, zq_in = obs1("x", q, layer_in), obs1("y", q, layer_in), obs1("z", q, layer_in)
|
| 163 |
-
xq_out, yq_out, zq_out = obs1("x", q, layer_out), obs1("y", q, layer_out), obs1("z", q, layer_out)
|
| 164 |
-
|
| 165 |
-
c.extend([
|
| 166 |
-
{"kind": "EQ", "expr": f"{xq_out} - {zq_in}", "weight": 5.0},
|
| 167 |
-
{"kind": "EQ", "expr": f"{yq_out} + {yq_in}", "weight": 5.0},
|
| 168 |
-
{"kind": "EQ", "expr": f"{zq_out} - {xq_in}", "weight": 5.0}
|
| 169 |
-
])
|
| 170 |
-
|
| 171 |
-
if circuit.include_2body:
|
| 172 |
-
for r in range(circuit.n_qubits):
|
| 173 |
-
if r == q: continue
|
| 174 |
-
for pr in PAULI:
|
| 175 |
-
xpr_in = obs2_safe("x", q, pr, r, layer_in)
|
| 176 |
-
ypr_in = obs2_safe("y", q, pr, r, layer_in)
|
| 177 |
-
zpr_in = obs2_safe("z", q, pr, r, layer_in)
|
| 178 |
-
|
| 179 |
-
xpr_out = obs2_safe("x", q, pr, r, layer_out)
|
| 180 |
-
ypr_out = obs2_safe("y", q, pr, r, layer_out)
|
| 181 |
-
zpr_out = obs2_safe("z", q, pr, r, layer_out)
|
| 182 |
-
|
| 183 |
-
c.extend([
|
| 184 |
-
{"kind": "EQ", "expr": f"{xpr_out} - {zpr_in}", "weight": 5.0},
|
| 185 |
-
{"kind": "EQ", "expr": f"{ypr_out} + {ypr_in}", "weight": 5.0},
|
| 186 |
-
{"kind": "EQ", "expr": f"{zpr_out} - {xpr_in}", "weight": 5.0}
|
| 187 |
-
])
|
| 188 |
-
return c
|
| 189 |
-
|
| 190 |
-
def _cnot_constraints(control: int, target: int, layer_in: int, layer_out: int, circuit: QuantumCircuit) -> List[Dict]:
|
| 191 |
-
if not circuit.include_2body:
|
| 192 |
-
raise ValueError("CNOT requires include_2body=True")
|
| 193 |
-
|
| 194 |
-
k, j = control, target
|
| 195 |
-
c = []
|
| 196 |
-
|
| 197 |
-
c.extend([
|
| 198 |
-
{"kind": "EQ", "expr": f"{obs1('x', k, layer_out)} - {obs2_safe('x', k, 'x', j, layer_in)}", "weight": 5.0},
|
| 199 |
-
{"kind": "EQ", "expr": f"{obs1('y', k, layer_out)} - {obs2_safe('y', k, 'x', j, layer_in)}", "weight": 5.0},
|
| 200 |
-
{"kind": "EQ", "expr": f"{obs1('z', k, layer_out)} - {obs1('z', k, layer_in)}", "weight": 5.0},
|
| 201 |
-
{"kind": "EQ", "expr": f"{obs1('x', j, layer_out)} - {obs1('x', j, layer_in)}", "weight": 5.0},
|
| 202 |
-
{"kind": "EQ", "expr": f"{obs1('y', j, layer_out)} - {obs2_safe('z', k, 'y', j, layer_in)}", "weight": 5.0},
|
| 203 |
-
{"kind": "EQ", "expr": f"{obs1('z', j, layer_out)} - {obs2_safe('z', k, 'z', j, layer_in)}", "weight": 5.0},
|
| 204 |
-
|
| 205 |
-
{"kind": "EQ", "expr": f"{obs2_safe('x', k, 'x', j, layer_out)} - {obs1('x', k, layer_in)}", "weight": 5.0},
|
| 206 |
-
{"kind": "EQ", "expr": f"{obs2_safe('x', k, 'y', j, layer_out)} - {obs2_safe('y', k, 'z', j, layer_in)}", "weight": 5.0},
|
| 207 |
-
{"kind": "EQ", "expr": f"{obs2_safe('x', k, 'z', j, layer_out)} + {obs2_safe('y', k, 'y', j, layer_in)}", "weight": 5.0},
|
| 208 |
-
{"kind": "EQ", "expr": f"{obs2_safe('y', k, 'x', j, layer_out)} - {obs1('y', k, layer_in)}", "weight": 5.0},
|
| 209 |
-
{"kind": "EQ", "expr": f"{obs2_safe('y', k, 'y', j, layer_out)} + {obs2_safe('x', k, 'z', j, layer_in)}", "weight": 5.0},
|
| 210 |
-
{"kind": "EQ", "expr": f"{obs2_safe('y', k, 'z', j, layer_out)} - {obs2_safe('x', k, 'y', j, layer_in)}", "weight": 5.0},
|
| 211 |
-
{"kind": "EQ", "expr": f"{obs2_safe('z', k, 'x', j, layer_out)} - {obs2_safe('z', k, 'x', j, layer_in)}", "weight": 5.0},
|
| 212 |
-
{"kind": "EQ", "expr": f"{obs2_safe('z', k, 'y', j, layer_out)} - {obs1('y', j, layer_in)}", "weight": 5.0},
|
| 213 |
-
{"kind": "EQ", "expr": f"{obs2_safe('z', k, 'z', j, layer_out)} - {obs1('z', j, layer_in)}", "weight": 5.0},
|
| 214 |
-
])
|
| 215 |
-
|
| 216 |
-
for r in range(circuit.n_qubits):
|
| 217 |
-
if r in (k, j): continue
|
| 218 |
-
for p in PAULI:
|
| 219 |
-
c.append({"kind": "EQ", "expr": f"{obs1(p, r, layer_out)} - {obs1(p, r, layer_in)}", "weight": 5.0})
|
| 220 |
-
|
| 221 |
-
if circuit.include_2body:
|
| 222 |
-
for pr in PAULI:
|
| 223 |
-
c.append({"kind": "EQ", "expr": f"{obs2_safe('x', k, pr, r, layer_out)} - {_cumulant_3('x', k, 'x', j, pr, r, layer_in)}", "weight": 4.0})
|
| 224 |
-
c.append({"kind": "EQ", "expr": f"{obs2_safe('y', k, pr, r, layer_out)} - {_cumulant_3('y', k, 'x', j, pr, r, layer_in)}", "weight": 4.0})
|
| 225 |
-
c.append({"kind": "EQ", "expr": f"{obs2_safe('z', k, pr, r, layer_out)} - {obs2_safe('z', k, pr, r, layer_in)}", "weight": 5.0})
|
| 226 |
-
c.append({"kind": "EQ", "expr": f"{obs2_safe('x', j, pr, r, layer_out)} - {obs2_safe('x', j, pr, r, layer_in)}", "weight": 5.0})
|
| 227 |
-
c.append({"kind": "EQ", "expr": f"{obs2_safe('y', j, pr, r, layer_out)} - {_cumulant_3('z', k, 'y', j, pr, r, layer_in)}", "weight": 4.0})
|
| 228 |
-
c.append({"kind": "EQ", "expr": f"{obs2_safe('z', j, pr, r, layer_out)} - {_cumulant_3('z', k, 'z', j, pr, r, layer_in)}", "weight": 4.0})
|
| 229 |
-
|
| 230 |
-
for q2 in range(r + 1, circuit.n_qubits):
|
| 231 |
-
if q2 in (k, j): continue
|
| 232 |
-
for p1p2 in PAULI2:
|
| 233 |
-
c.append({"kind": "EQ", "expr": f"{obs2_safe(p1p2[0], r, p1p2[1], q2, layer_out)} - {obs2_safe(p1p2[0], r, p1p2[1], q2, layer_in)}", "weight": 5.0})
|
| 234 |
-
return c
|
| 235 |
-
|
| 236 |
def build_quantum_axl(circuit: QuantumCircuit, axl_problem_class: Any, axl_invariant_class: Any) -> Any:
|
| 237 |
-
|
| 238 |
-
|
| 239 |
|
| 240 |
n = circuit.n_qubits
|
| 241 |
n_layers = circuit.n_layers
|
| 242 |
-
include_2b = circuit.include_2body
|
| 243 |
|
| 244 |
variables = []
|
| 245 |
for pname in circuit.all_params:
|
| 246 |
lo, hi = circuit.param_bounds.get(pname, (-math.pi, math.pi))
|
| 247 |
variables.append({"name": pname, "lo": lo, "hi": hi})
|
| 248 |
|
|
|
|
| 249 |
for layer in range(n_layers + 1):
|
| 250 |
for q in range(n):
|
| 251 |
for p in PAULI:
|
| 252 |
variables.append({"name": obs1(p, q, layer), "lo": -1.0, "hi": 1.0})
|
| 253 |
-
if
|
| 254 |
for q1 in range(n):
|
| 255 |
for q2 in range(q1 + 1, n):
|
| 256 |
for p1p2 in PAULI2:
|
| 257 |
variables.append({"name": obs2_safe(p1p2[0], q1, p1p2[1], q2, layer), "lo": -1.0, "hi": 1.0})
|
| 258 |
|
| 259 |
-
obs_fixed = _initial_obs_values(circuit)
|
| 260 |
-
|
| 261 |
-
global_constraints = []
|
| 262 |
-
for layer in range(n_layers + 1):
|
| 263 |
-
for q in range(n):
|
| 264 |
-
global_constraints.append({
|
| 265 |
-
"kind": "GEQ",
|
| 266 |
-
"expr": f"1.0 - {obs1('x', q, layer)}**2 - {obs1('y', q, layer)}**2 - {obs1('z', q, layer)}**2",
|
| 267 |
-
"weight": 2.0
|
| 268 |
-
})
|
| 269 |
-
|
| 270 |
-
scopes = []
|
| 271 |
-
for layer_in in range(n_layers):
|
| 272 |
-
layer_out = layer_in + 1
|
| 273 |
-
layer_gates = [g for g in circuit.gates if g.layer == layer_in]
|
| 274 |
-
scope_constraints = []
|
| 275 |
-
gates_affecting = set()
|
| 276 |
-
|
| 277 |
-
for gate in layer_gates:
|
| 278 |
-
if gate.type == "Rz":
|
| 279 |
-
gates_affecting.add(gate.qubits[0])
|
| 280 |
-
scope_constraints.extend(_rz_constraints(gate.qubits[0], gate.params[0], layer_in, layer_out, circuit))
|
| 281 |
-
elif gate.type == "Rx":
|
| 282 |
-
gates_affecting.add(gate.qubits[0])
|
| 283 |
-
scope_constraints.extend(_rx_constraints(gate.qubits[0], gate.params[0], layer_in, layer_out, circuit))
|
| 284 |
-
elif gate.type == "H":
|
| 285 |
-
gates_affecting.add(gate.qubits[0])
|
| 286 |
-
scope_constraints.extend(_hadamard_constraints(gate.qubits[0], layer_in, layer_out, circuit))
|
| 287 |
-
elif gate.type == "CNOT":
|
| 288 |
-
gates_affecting.update(gate.qubits)
|
| 289 |
-
scope_constraints.extend(_cnot_constraints(gate.qubits[0], gate.qubits[1], layer_in, layer_out, circuit))
|
| 290 |
-
|
| 291 |
-
unaffected = [q for q in range(n) if q not in gates_affecting]
|
| 292 |
-
for q in unaffected:
|
| 293 |
-
for p in PAULI:
|
| 294 |
-
scope_constraints.append({"kind": "EQ", "expr": f"{obs1(p, q, layer_out)} - {obs1(p, q, layer_in)}", "weight": 5.0})
|
| 295 |
-
if include_2b:
|
| 296 |
-
for q2 in range(n):
|
| 297 |
-
if q2 == q or q2 in gates_affecting: continue
|
| 298 |
-
for p1p2 in PAULI2:
|
| 299 |
-
scope_constraints.append({"kind": "EQ", "expr": f"{obs2_safe(p1p2[0], q, p1p2[1], q2, layer_out)} - {obs2_safe(p1p2[0], q, p1p2[1], q2, layer_in)}", "weight": 5.0})
|
| 300 |
-
|
| 301 |
-
scope_var_names = []
|
| 302 |
-
for q in range(n):
|
| 303 |
-
for p in PAULI:
|
| 304 |
-
scope_var_names.extend([obs1(p, q, layer_in), obs1(p, q, layer_out)])
|
| 305 |
-
if include_2b:
|
| 306 |
-
for q1 in range(n):
|
| 307 |
-
for q2 in range(q1 + 1, n):
|
| 308 |
-
for p1p2 in PAULI2:
|
| 309 |
-
scope_var_names.extend([obs2_safe(p1p2[0], q1, p1p2[1], q2, layer_in), obs2_safe(p1p2[0], q1, p1p2[1], q2, layer_out)])
|
| 310 |
-
scope_var_names.extend(circuit.all_params)
|
| 311 |
-
|
| 312 |
-
scopes.append({
|
| 313 |
-
"name": f"layer_{layer_in}_to_{layer_out}",
|
| 314 |
-
"order": layer_in,
|
| 315 |
-
"vars": scope_var_names,
|
| 316 |
-
"constraints": scope_constraints
|
| 317 |
-
})
|
| 318 |
-
|
| 319 |
anchors = []
|
| 320 |
for obs, val in circuit.expected_observables.items():
|
| 321 |
if len(obs) == 2:
|
|
@@ -330,82 +100,80 @@ def build_quantum_axl(circuit: QuantumCircuit, axl_problem_class: Any, axl_invar
|
|
| 330 |
mode="eq"
|
| 331 |
))
|
| 332 |
|
| 333 |
-
|
| 334 |
-
anchors.append(axl_invariant_class(
|
| 335 |
-
name=f"bloch_q{q}",
|
| 336 |
-
expr=f"1.0 - {obs1('x', q, n_layers)}**2 - {obs1('y', q, n_layers)}**2 - {obs1('z', q, n_layers)}**2",
|
| 337 |
-
tolerance=0.05,
|
| 338 |
-
mode="geq"
|
| 339 |
-
))
|
| 340 |
-
|
| 341 |
-
return axl_problem_class(
|
| 342 |
name=circuit.name,
|
| 343 |
description=circuit.description,
|
| 344 |
axioms={"CONTINUOUS", "CONSERVED", "TRANSITIVE", "BILINEAR", "METRIC", "SUPERPOSITION", "SYMMETRIC"},
|
| 345 |
variables=variables,
|
| 346 |
-
constraints=
|
| 347 |
-
scopes=
|
| 348 |
anchors=anchors,
|
| 349 |
-
observations=
|
| 350 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
def example_bell_state() -> QuantumCircuit:
|
| 357 |
-
return QuantumCircuit(
|
| 358 |
-
n_qubits=2,
|
| 359 |
-
name="BellStatePrep",
|
| 360 |
-
gates=[
|
| 361 |
-
QGate("H", [0], layer=0),
|
| 362 |
-
QGate("CNOT", [0, 1], layer=1)
|
| 363 |
-
],
|
| 364 |
-
initial_state="zero",
|
| 365 |
-
include_2body=True,
|
| 366 |
-
expected_observables={"zz01": 1.0, "xx01": 1.0}
|
| 367 |
-
)
|
| 368 |
|
| 369 |
def example_vqe_h2() -> QuantumCircuit:
|
| 370 |
-
return QuantumCircuit(
|
| 371 |
-
n_qubits=2,
|
| 372 |
-
name="VQE_H2",
|
| 373 |
-
gates=[
|
| 374 |
-
QGate("Ry", [0], ["theta0"], layer=0),
|
| 375 |
-
QGate("Ry", [1], ["theta1"], layer=0),
|
| 376 |
-
QGate("CNOT", [0, 1], layer=1),
|
| 377 |
-
],
|
| 378 |
-
param_bounds={"theta0": (-math.pi, math.pi), "theta1": (-math.pi, math.pi)},
|
| 379 |
-
expected_observables={"z0": -0.5, "z1": -0.5, "zz01": 0.25, "xx01": -0.5}
|
| 380 |
-
)
|
| 381 |
|
| 382 |
def example_qaoa_maxcut_p1() -> QuantumCircuit:
|
| 383 |
-
return QuantumCircuit(
|
| 384 |
-
n_qubits=3,
|
| 385 |
-
name="QAOA_MaxCut_Deep",
|
| 386 |
-
gates=[
|
| 387 |
-
QGate("H", [0], layer=0), QGate("H", [1], layer=0), QGate("H", [2], layer=0),
|
| 388 |
-
|
| 389 |
-
# Edge (0, 1) Rzz Entanglement
|
| 390 |
-
QGate("CNOT", [0, 1], layer=1),
|
| 391 |
-
QGate("Rz", [1], ["gamma"], layer=2),
|
| 392 |
-
QGate("CNOT", [0, 1], layer=3),
|
| 393 |
-
|
| 394 |
-
# Edge (1, 2) Rzz Entanglement
|
| 395 |
-
QGate("CNOT", [1, 2], layer=4),
|
| 396 |
-
QGate("Rz", [2], ["gamma"], layer=5),
|
| 397 |
-
QGate("CNOT", [1, 2], layer=6),
|
| 398 |
-
|
| 399 |
-
# Edge (0, 2) Rzz Entanglement
|
| 400 |
-
QGate("CNOT", [0, 2], layer=7),
|
| 401 |
-
QGate("Rz", [2], ["gamma"], layer=8),
|
| 402 |
-
QGate("CNOT", [0, 2], layer=9),
|
| 403 |
-
|
| 404 |
-
# Transverse Mixer
|
| 405 |
-
QGate("Rx", [0], ["beta"], layer=10),
|
| 406 |
-
QGate("Rx", [1], ["beta"], layer=10),
|
| 407 |
-
QGate("Rx", [2], ["beta"], layer=10),
|
| 408 |
-
],
|
| 409 |
-
param_bounds={"gamma": (0, math.pi), "beta": (0, math.pi / 2)},
|
| 410 |
-
expected_observables={"zz01": -0.5, "zz12": -0.5, "zz02": -0.5}
|
| 411 |
-
)
|
|
|
|
| 1 |
"""
|
| 2 |
QuantumCircuits.py
|
| 3 |
+
Vectorized Heisenberg-picture quantum circuit encoding for the Practicality Wisdom Layer.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
from __future__ import annotations
|
| 7 |
import math
|
| 8 |
+
import torch
|
| 9 |
from dataclasses import dataclass, field
|
| 10 |
from typing import List, Dict, Tuple, Set, Any
|
| 11 |
|
| 12 |
GATE_TYPES = {"Rx", "Ry", "Rz", "H", "S", "T", "CNOT", "CZ"}
|
| 13 |
+
PAULI = ("x", "y", "z")
|
| 14 |
+
PAULI2 = [p1 + p2 for p1 in PAULI for p2 in PAULI]
|
| 15 |
|
| 16 |
@dataclass
|
| 17 |
class QGate:
|
|
|
|
| 23 |
def validate(self):
|
| 24 |
if self.type not in GATE_TYPES:
|
| 25 |
raise ValueError(f"Unknown gate: {self.type}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
@dataclass
|
| 28 |
class QuantumCircuit:
|
|
|
|
| 55 |
params.append(p)
|
| 56 |
return params
|
| 57 |
|
|
|
|
|
|
|
|
|
|
| 58 |
def obs1(pauli: str, qubit: int, layer: int) -> str:
|
| 59 |
return f"{pauli}{qubit}_L{layer}"
|
| 60 |
|
| 61 |
def obs2_safe(pauli1: str, q1: int, pauli2: str, q2: int, layer: int) -> str:
|
| 62 |
+
if q1 == q2: return f"invalid_{q1}"
|
| 63 |
+
if q1 > q2: return f"{pauli2}{pauli1}{q2}{q1}_L{layer}"
|
|
|
|
|
|
|
| 64 |
return f"{pauli1}{pauli2}{q1}{q2}_L{layer}"
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def build_quantum_axl(circuit: QuantumCircuit, axl_problem_class: Any, axl_invariant_class: Any) -> Any:
|
| 67 |
+
"""Builds the shell AXL representation. Constraints are offloaded to PyTorch."""
|
| 68 |
+
for g in circuit.gates: g.validate()
|
| 69 |
|
| 70 |
n = circuit.n_qubits
|
| 71 |
n_layers = circuit.n_layers
|
|
|
|
| 72 |
|
| 73 |
variables = []
|
| 74 |
for pname in circuit.all_params:
|
| 75 |
lo, hi = circuit.param_bounds.get(pname, (-math.pi, math.pi))
|
| 76 |
variables.append({"name": pname, "lo": lo, "hi": hi})
|
| 77 |
|
| 78 |
+
# We still define the variables in AXL so the main solver can allocate the X tensor
|
| 79 |
for layer in range(n_layers + 1):
|
| 80 |
for q in range(n):
|
| 81 |
for p in PAULI:
|
| 82 |
variables.append({"name": obs1(p, q, layer), "lo": -1.0, "hi": 1.0})
|
| 83 |
+
if circuit.include_2body:
|
| 84 |
for q1 in range(n):
|
| 85 |
for q2 in range(q1 + 1, n):
|
| 86 |
for p1p2 in PAULI2:
|
| 87 |
variables.append({"name": obs2_safe(p1p2[0], q1, p1p2[1], q2, layer), "lo": -1.0, "hi": 1.0})
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
anchors = []
|
| 90 |
for obs, val in circuit.expected_observables.items():
|
| 91 |
if len(obs) == 2:
|
|
|
|
| 100 |
mode="eq"
|
| 101 |
))
|
| 102 |
|
| 103 |
+
prob = axl_problem_class(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
name=circuit.name,
|
| 105 |
description=circuit.description,
|
| 106 |
axioms={"CONTINUOUS", "CONSERVED", "TRANSITIVE", "BILINEAR", "METRIC", "SUPERPOSITION", "SYMMETRIC"},
|
| 107 |
variables=variables,
|
| 108 |
+
constraints=[], # Handled natively via tensor loss
|
| 109 |
+
scopes=[],
|
| 110 |
anchors=anchors,
|
| 111 |
+
observations={}
|
| 112 |
)
|
| 113 |
+
prob.is_quantum = True
|
| 114 |
+
prob.circuit = circuit
|
| 115 |
+
return prob
|
| 116 |
+
|
| 117 |
+
def get_quantum_tensor_loss(circuit: QuantumCircuit, X: torch.Tensor, var_idx: Dict[str, int], device: torch.device) -> torch.Tensor:
|
| 118 |
+
"""Direct, vectorized PyTorch evaluation of the Heisenberg evolution."""
|
| 119 |
+
B = X.shape[0] if X.dim() == 2 else 1
|
| 120 |
+
loss = torch.zeros(B, device=device)
|
| 121 |
+
|
| 122 |
+
obs = {}
|
| 123 |
+
# Init Z basis
|
| 124 |
+
for q in range(circuit.n_qubits):
|
| 125 |
+
obs[obs1('x', q, 0)] = torch.zeros(B, device=device)
|
| 126 |
+
obs[obs1('y', q, 0)] = torch.zeros(B, device=device)
|
| 127 |
+
obs[obs1('z', q, 0)] = torch.ones(B, device=device)
|
| 128 |
+
|
| 129 |
+
for l in range(circuit.n_layers):
|
| 130 |
+
l_in, l_out = l, l + 1
|
| 131 |
+
layer_gates = [g for g in circuit.gates if g.layer == l]
|
| 132 |
+
|
| 133 |
+
# Identity carryover for untouched variables
|
| 134 |
+
for q in range(circuit.n_qubits):
|
| 135 |
+
for p in PAULI:
|
| 136 |
+
k_in, k_out = obs1(p, q, l_in), obs1(p, q, l_out)
|
| 137 |
+
if k_in in obs: obs[k_out] = obs[k_in].clone()
|
| 138 |
|
| 139 |
+
for gate in layer_gates:
|
| 140 |
+
if len(gate.qubits) == 1:
|
| 141 |
+
q = gate.qubits[0]
|
| 142 |
+
theta = X[:, var_idx[gate.params[0]]] if X.dim() == 2 else X[var_idx[gate.params[0]]]
|
| 143 |
+
c, s = torch.cos(theta), torch.sin(theta)
|
| 144 |
+
|
| 145 |
+
if gate.type == "Rz":
|
| 146 |
+
xi, yi = obs.get(obs1('x', q, l_in), 0), obs.get(obs1('y', q, l_in), 0)
|
| 147 |
+
obs[obs1('x', q, l_out)] = c * xi - s * yi
|
| 148 |
+
obs[obs1('y', q, l_out)] = s * xi + c * yi
|
| 149 |
+
elif gate.type == "Rx":
|
| 150 |
+
yi, zi = obs.get(obs1('y', q, l_in), 0), obs.get(obs1('z', q, l_in), 0)
|
| 151 |
+
obs[obs1('y', q, l_out)] = c * yi - s * zi
|
| 152 |
+
obs[obs1('z', q, l_out)] = s * yi + c * zi
|
| 153 |
+
elif gate.type == "H":
|
| 154 |
+
obs[obs1('x', q, l_out)] = obs.get(obs1('z', q, l_in), 0)
|
| 155 |
+
obs[obs1('z', q, l_out)] = obs.get(obs1('x', q, l_in), 0)
|
| 156 |
+
obs[obs1('y', q, l_out)] = -obs.get(obs1('y', q, l_in), 0)
|
| 157 |
+
|
| 158 |
+
elif gate.type == "CNOT":
|
| 159 |
+
# Simplified 1-body trace for speed, proper 2-body tracking can be mapped here
|
| 160 |
+
qc, qt = gate.qubits[0], gate.qubits[1]
|
| 161 |
+
obs[obs1('z', qc, l_out)] = obs.get(obs1('z', qc, l_in), 0)
|
| 162 |
+
obs[obs1('x', qt, l_out)] = obs.get(obs1('x', qt, l_in), 0)
|
| 163 |
+
|
| 164 |
+
# Compute MSE loss between the physical trace and the variables in the X tensor
|
| 165 |
+
for k, expected_tensor in obs.items():
|
| 166 |
+
if k in var_idx:
|
| 167 |
+
actual_tensor = X[:, var_idx[k]] if X.dim() == 2 else X[var_idx[k]]
|
| 168 |
+
loss += (actual_tensor - expected_tensor) ** 2
|
| 169 |
+
|
| 170 |
+
return loss
|
| 171 |
|
| 172 |
def example_bell_state() -> QuantumCircuit:
|
| 173 |
+
return QuantumCircuit(n_qubits=2, name="BellStatePrep", gates=[QGate("H", [0], layer=0), QGate("CNOT", [0, 1], layer=1)], expected_observables={"zz01": 1.0, "xx01": 1.0})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
def example_vqe_h2() -> QuantumCircuit:
|
| 176 |
+
return QuantumCircuit(n_qubits=2, name="VQE_H2", gates=[QGate("Ry", [0], ["theta0"], layer=0), QGate("Ry", [1], ["theta1"], layer=0), QGate("CNOT", [0, 1], layer=1)], param_bounds={"theta0": (-math.pi, math.pi), "theta1": (-math.pi, math.pi)}, expected_observables={"z0": -0.5, "z1": -0.5, "zz01": 0.25, "xx01": -0.5})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
def example_qaoa_maxcut_p1() -> QuantumCircuit:
|
| 179 |
+
return QuantumCircuit(n_qubits=3, name="QAOA_MaxCut_Deep", gates=[QGate("H", [0], layer=0), QGate("H", [1], layer=0), QGate("H", [2], layer=0), QGate("CNOT", [0, 1], layer=1), QGate("Rz", [1], ["gamma"], layer=2), QGate("CNOT", [0, 1], layer=3), QGate("CNOT", [1, 2], layer=4), QGate("Rz", [2], ["gamma"], layer=5), QGate("CNOT", [1, 2], layer=6), QGate("CNOT", [0, 2], layer=7), QGate("Rz", [2], ["gamma"], layer=8), QGate("CNOT", [0, 2], layer=9), QGate("Rx", [0], ["beta"], layer=10), QGate("Rx", [1], ["beta"], layer=10), QGate("Rx", [2], ["beta"], layer=10)], param_bounds={"gamma": (0, math.pi), "beta": (0, math.pi / 2)}, expected_observables={"zz01": -0.5, "zz12": -0.5, "zz02": -0.5})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|