everydaytok commited on
Commit
cb857fe
·
verified ·
1 Parent(s): ac51ed0

Update QuantumCircuits.py

Browse files
Files changed (1) hide show
  1. 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
- raise ValueError(f"Invalid 2-body observable on same qubit: {q1}")
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
- for g in circuit.gates:
238
- g.validate()
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 include_2b:
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
- for q in range(n):
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=global_constraints,
347
- scopes=scopes,
348
  anchors=anchors,
349
- observations=obs_fixed
350
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
- # ══════════════════════════════════════════════════════════════════════
353
- # EXAMPLE CIRCUITS
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})