hchevva commited on
Commit
7db25b7
·
verified ·
1 Parent(s): a587cf8

Update quread/engine.py

Browse files
Files changed (1) hide show
  1. quread/engine.py +71 -19
quread/engine.py CHANGED
@@ -44,26 +44,78 @@ class QuantumStateVector:
44
  def apply_single(self, gate_name: str, target: int, theta: Optional[float] = None) -> None:
45
  if not (0 <= target < self.n_qubits):
46
  raise ValueError("target out of range")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  gate = None
48
- if gate_name in SINGLE_QUBIT_GATES:
49
- gate = SINGLE_QUBIT_GATES[gate_name]
50
- elif gate_name == "RX":
51
- if theta is None: raise ValueError("RX requires theta")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  gate = rx(float(theta))
53
- elif gate_name == "RY":
54
- if theta is None: raise ValueError("RY requires theta")
 
 
55
  gate = ry(float(theta))
56
- elif gate_name == "RZ":
57
- if theta is None: raise ValueError("RZ requires theta")
 
 
58
  gate = rz(float(theta))
 
 
 
 
 
59
  else:
60
  raise ValueError(f"Unknown gate: {gate_name}")
61
-
62
  # Apply using pairwise amplitude updates (no full 2^n x 2^n matrix)
63
- # Convention: qubit 0 is the TOP wire in UI, but in basis indexing we treat
64
- # qubit 0 as the most-significant bit (MSB). This keeps bitstrings readable.
65
- msb_index = self.n_qubits - 1 - target # convert "wire index" -> bit position from right
66
-
67
  new_state = self.state.copy()
68
  dim = len(self.state)
69
  for basis in range(dim):
@@ -71,13 +123,13 @@ class QuantumStateVector:
71
  partner = _flip_bit(basis, msb_index)
72
  a0 = self.state[basis]
73
  a1 = self.state[partner]
74
- # [a0'; a1'] = gate * [a0; a1]
75
- new_state[basis] = gate[0,0]*a0 + gate[0,1]*a1
76
- new_state[partner] = gate[1,0]*a0 + gate[1,1]*a1
77
  self.state = _normalize_state(new_state)
78
-
79
- op: Op = {"type": "single", "gate": gate_name, "target": target}
80
- if theta is not None:
81
  op["theta"] = float(theta)
82
  self.history.append(op)
83
 
 
44
  def apply_single(self, gate_name: str, target: int, theta: Optional[float] = None) -> None:
45
  if not (0 <= target < self.n_qubits):
46
  raise ValueError("target out of range")
47
+
48
+ def _parse_angle(label: str) -> float:
49
+ # supports: "π", "pi", "π/2", "pi/2"
50
+ s = label.strip().lower().replace(" ", "")
51
+ if s in ("π", "pi"):
52
+ return float(np.pi)
53
+ if s in ("π/2", "pi/2"):
54
+ return float(np.pi / 2)
55
+ raise ValueError(f"Unsupported angle in {gate_name}. Use π or π/2.")
56
+
57
+ g = gate_name.strip()
58
+
59
+ # --- normalize common UI labels to internal canonical names ---
60
+ # dagger variants
61
+ if g in ("T†", "Tdg"):
62
+ g = "Tdg"
63
+ if g in ("S†", "Sdg"):
64
+ g = "Sdg"
65
+ # identity variants
66
+ if g in ("I†", "Idg"):
67
+ g = "I"
68
+ # sqrt variants
69
+ if g in ("√X", "SX"):
70
+ g = "SQRTX"
71
+ if g in ("√Z", "SZ"):
72
+ g = "SQRTZ"
73
+
74
+ # --- build gate matrix ---
75
  gate = None
76
+
77
+ # fixed-angle rotation labels from UI
78
+ if g.startswith(("Rx(", "RX(")) and g.endswith(")"):
79
+ ang = _parse_angle(g[g.find("(")+1 : -1])
80
+ gate = rx(float(ang))
81
+ g = f"RX({g[g.find('(')+1:-1]})" # preserve readable name in history
82
+
83
+ elif g.startswith(("Ry(", "RY(")) and g.endswith(")"):
84
+ ang = _parse_angle(g[g.find("(")+1 : -1])
85
+ gate = ry(float(ang))
86
+ g = f"RY({g[g.find('(')+1:-1]})"
87
+
88
+ elif g.startswith(("Rz(", "RZ(")) and g.endswith(")"):
89
+ ang = _parse_angle(g[g.find("(")+1 : -1])
90
+ gate = rz(float(ang))
91
+ g = f"RZ({g[g.find('(')+1:-1]})"
92
+
93
+ # original parametric API still supported
94
+ elif g == "RX":
95
+ if theta is None:
96
+ raise ValueError("RX requires theta")
97
  gate = rx(float(theta))
98
+
99
+ elif g == "RY":
100
+ if theta is None:
101
+ raise ValueError("RY requires theta")
102
  gate = ry(float(theta))
103
+
104
+ elif g == "RZ":
105
+ if theta is None:
106
+ raise ValueError("RZ requires theta")
107
  gate = rz(float(theta))
108
+
109
+ # all normal single-qubit gates (incl. new ones) via map
110
+ elif g in SINGLE_QUBIT_GATES:
111
+ gate = SINGLE_QUBIT_GATES[g]
112
+
113
  else:
114
  raise ValueError(f"Unknown gate: {gate_name}")
115
+
116
  # Apply using pairwise amplitude updates (no full 2^n x 2^n matrix)
117
+ msb_index = self.n_qubits - 1 - target # wire index -> bit position from right
118
+
 
 
119
  new_state = self.state.copy()
120
  dim = len(self.state)
121
  for basis in range(dim):
 
123
  partner = _flip_bit(basis, msb_index)
124
  a0 = self.state[basis]
125
  a1 = self.state[partner]
126
+ new_state[basis] = gate[0, 0] * a0 + gate[0, 1] * a1
127
+ new_state[partner] = gate[1, 0] * a0 + gate[1, 1] * a1
128
+
129
  self.state = _normalize_state(new_state)
130
+
131
+ op: Op = {"type": "single", "gate": g, "target": target}
132
+ if theta is not None and g in ("RX", "RY", "RZ"):
133
  op["theta"] = float(theta)
134
  self.history.append(op)
135