|
| 1 | +""" |
| 2 | +Quantum Trajectory Simulation for Measurement-Induced Phase Transitions (MIPT) in Measurement-Only Models |
| 3 | +------------------------------------------------------------------------------ |
| 4 | +This script simulates a quantum circuit under monitoring (weak measurements). |
| 5 | +It calculates the evolution of a quantum state under: |
| 6 | +1. Weak X measurements (single-site) |
| 7 | +2. Weak ZZ measurements (two-site link) |
| 8 | +3. Weak ZXZ measurements (three-site) |
| 9 | +
|
| 10 | +It utilizes TensorCircuit with a JAX backend for Just-In-Time (JIT) compilation |
| 11 | +to speed up the simulation of many trajectories. It computes Topological |
| 12 | +Entanglement Entropy (TEE) and Mutual Information (MEE). |
| 13 | +""" |
| 14 | + |
| 15 | +from typing import Tuple, List, Any |
| 16 | +from functools import partial |
| 17 | +import numpy as np |
| 18 | +import numpy.typing as npt |
| 19 | +import tensorcircuit as tc |
| 20 | + |
| 21 | +# Setup |
| 22 | +K = tc.set_backend("jax") |
| 23 | +tc.set_dtype("complex128") |
| 24 | + |
| 25 | +# Operators with type annotations - explicitly set dtype |
| 26 | +Z: npt.NDArray[np.complex128] = np.array([[1, 0], [0, -1]], dtype=np.complex128) |
| 27 | +X: npt.NDArray[np.complex128] = np.array([[0, 1], [1, 0]], dtype=np.complex128) |
| 28 | +I8: npt.NDArray[np.complex128] = np.eye(8, dtype=np.complex128) |
| 29 | +ZXZ: npt.NDArray[np.complex128] = np.kron(np.kron(Z, X), Z) |
| 30 | + |
| 31 | + |
| 32 | +@partial(K.jit, static_argnums=(3, 4)) |
| 33 | +def mipt_circuit( |
| 34 | + sx: npt.NDArray[np.float64], |
| 35 | + szz: npt.NDArray[np.float64], |
| 36 | + szxz: npt.NDArray[np.float64], |
| 37 | + n: int, |
| 38 | + d: int, |
| 39 | + cx_params: float, |
| 40 | + sx_params: float, |
| 41 | + czz_params: float, |
| 42 | + szz_params: float, |
| 43 | + czxz_params: float, |
| 44 | + szxz_params: float, |
| 45 | +) -> Tuple[Any, Any, Any, Any]: |
| 46 | + """ |
| 47 | + Simulates a single quantum trajectory. |
| 48 | +
|
| 49 | + Args: |
| 50 | + status_x, status_zz, status_zxz: Random numbers used to determine measurement outcomes. |
| 51 | + n: Number of qubits. |
| 52 | + d: Circuit depth (time steps). |
| 53 | + cx, sx: Cosine/Sine parameters for weak X measurement strength. |
| 54 | + czz, szz: Cosine/Sine parameters for weak ZZ measurement strength. |
| 55 | + czxz, szxz: Cosine/Sine parameters for weak ZXZ measurement strength. |
| 56 | +
|
| 57 | + Returns: |
| 58 | + x_history, zz_history, zxz_history: Record of measurement outcomes (0 or 1). |
| 59 | + state: The final normalized quantum state vector. |
| 60 | + """ |
| 61 | + sx = K.reshape(sx, [d, n]) |
| 62 | + szz = K.reshape(szz, [d, n]) |
| 63 | + szxz = K.reshape(szxz, [d, n]) |
| 64 | + |
| 65 | + # Initialize |+++...⟩ state |
| 66 | + c = tc.Circuit(n) |
| 67 | + for j in range(n): |
| 68 | + c.h(j) # type: ignore |
| 69 | + state = c.state() |
| 70 | + |
| 71 | + # A. Weak X Measurement Operators (Single Qubit) |
| 72 | + M1_x = 0.5 * cx_params * K.convert_to_tensor( |
| 73 | + np.array([[1, 1], [1, 1]], dtype=np.complex128) |
| 74 | + ) + 0.5 * sx_params * K.convert_to_tensor( |
| 75 | + np.array([[1, -1], [-1, 1]], dtype=np.complex128) |
| 76 | + ) |
| 77 | + M2_x = 0.5 * sx_params * K.convert_to_tensor( |
| 78 | + np.array([[1, 1], [1, 1]], dtype=np.complex128) |
| 79 | + ) + 0.5 * cx_params * K.convert_to_tensor( |
| 80 | + np.array([[1, -1], [-1, 1]], dtype=np.complex128) |
| 81 | + ) |
| 82 | + |
| 83 | + # B. Weak ZZ Measurement Operators (Two Qubits) |
| 84 | + M1_zz = czz_params * K.convert_to_tensor( |
| 85 | + np.array( |
| 86 | + [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1]], |
| 87 | + dtype=np.complex128, |
| 88 | + ) |
| 89 | + ) + szz_params * K.convert_to_tensor( |
| 90 | + np.array( |
| 91 | + [[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]], |
| 92 | + dtype=np.complex128, |
| 93 | + ) |
| 94 | + ) |
| 95 | + M2_zz = szz_params * K.convert_to_tensor( |
| 96 | + np.array( |
| 97 | + [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1]], |
| 98 | + dtype=np.complex128, |
| 99 | + ) |
| 100 | + ) + czz_params * K.convert_to_tensor( |
| 101 | + np.array( |
| 102 | + [[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]], |
| 103 | + dtype=np.complex128, |
| 104 | + ) |
| 105 | + ) |
| 106 | + M1_zz = M1_zz.reshape([2, 2, 2, 2]) |
| 107 | + M2_zz = M2_zz.reshape([2, 2, 2, 2]) |
| 108 | + |
| 109 | + # Convert global constants to tensors |
| 110 | + I8_t = K.convert_to_tensor(I8) |
| 111 | + ZXZ_t = K.convert_to_tensor(ZXZ) |
| 112 | + |
| 113 | + # C. Weak ZXZ Measurement Operators (Three Qubits) |
| 114 | + M1_zxz = 0.5 * (czxz_params * (I8_t + ZXZ_t) + szxz_params * (I8_t - ZXZ_t)) |
| 115 | + M2_zxz = 0.5 * (szxz_params * (I8_t + ZXZ_t) + czxz_params * (I8_t - ZXZ_t)) |
| 116 | + M1_zxz = M1_zxz.reshape([2, 2, 2, 2, 2, 2]) |
| 117 | + M2_zxz = M2_zxz.reshape([2, 2, 2, 2, 2, 2]) |
| 118 | + |
| 119 | + x_h: List[Any] = [] |
| 120 | + zz_h: List[Any] = [] |
| 121 | + zxz_h: List[Any] = [] |
| 122 | + |
| 123 | + for t in range(d): |
| 124 | + c = tc.Circuit(n, inputs=state) |
| 125 | + |
| 126 | + # ZZ measurements (alternating bonds) |
| 127 | + for i in range(t % 2, n - 1, 2): |
| 128 | + zz_h.append( |
| 129 | + c.general_kraus( |
| 130 | + [M1_zz, M2_zz], i, i + 1, status=szz[t, i], with_prob=False |
| 131 | + ) |
| 132 | + ) |
| 133 | + state = c.state() |
| 134 | + state = state / K.norm(state) |
| 135 | + c = tc.Circuit(n, inputs=state) |
| 136 | + |
| 137 | + # ZXZ measurements (3-qubit blocks) |
| 138 | + for i in range(t % 3, n - 2, 3): |
| 139 | + zxz_h.append( |
| 140 | + c.general_kraus( |
| 141 | + [M1_zxz, M2_zxz], |
| 142 | + i, |
| 143 | + i + 1, |
| 144 | + i + 2, |
| 145 | + status=szxz[t, i], |
| 146 | + with_prob=False, |
| 147 | + ) |
| 148 | + ) |
| 149 | + state = c.state() |
| 150 | + state = state / K.norm(state) |
| 151 | + c = tc.Circuit(n, inputs=state) |
| 152 | + |
| 153 | + # X measurements (all qubits) |
| 154 | + for i in range(n): |
| 155 | + x_h.append( |
| 156 | + c.general_kraus([M1_x, M2_x], i, status=sx[t, i], with_prob=False) |
| 157 | + ) |
| 158 | + state = c.state() |
| 159 | + state = state / K.norm(state) |
| 160 | + |
| 161 | + return K.stack(x_h), K.stack(zz_h), K.stack(zxz_h), state |
| 162 | + |
| 163 | + |
| 164 | +# --- Entanglement Metrics --- |
| 165 | + |
| 166 | + |
| 167 | +def TEE(state: Any, n: int) -> Any: |
| 168 | + """Topological Entanglement Entropy: S_AB + S_BC - S_B - S_D""" |
| 169 | + p = n // 4 |
| 170 | + A, B, D, C = range(0, p), range(p, 2 * p), range(2 * p, 3 * p), range(3 * p, n) |
| 171 | + |
| 172 | + rho_AB = tc.quantum.reduced_density_matrix(state, cut=list(C) + list(D)) |
| 173 | + rho_BC = tc.quantum.reduced_density_matrix(state, cut=list(A) + list(D)) |
| 174 | + rho_B = tc.quantum.reduced_density_matrix(state, cut=list(A) + list(C) + list(D)) |
| 175 | + rho_D = tc.quantum.reduced_density_matrix(state, cut=list(A) + list(B) + list(C)) |
| 176 | + |
| 177 | + return ( |
| 178 | + tc.quantum.entropy(rho_AB) |
| 179 | + + tc.quantum.entropy(rho_BC) |
| 180 | + - tc.quantum.entropy(rho_B) |
| 181 | + - tc.quantum.entropy(rho_D) |
| 182 | + ) |
| 183 | + |
| 184 | + |
| 185 | +def MEE(state: Any, n: int) -> Any: |
| 186 | + """Mutual Information between first and last qubit: S_A + S_B - S_AB""" |
| 187 | + rho_A = tc.quantum.reduced_density_matrix(state, cut=list(range(1, n))) |
| 188 | + rho_B = tc.quantum.reduced_density_matrix(state, cut=list(range(0, n - 1))) |
| 189 | + rho_AB = tc.quantum.reduced_density_matrix(state, cut=list(range(1, n - 1))) |
| 190 | + return ( |
| 191 | + tc.quantum.entropy(rho_A) |
| 192 | + + tc.quantum.entropy(rho_B) |
| 193 | + - tc.quantum.entropy(rho_AB) |
| 194 | + ) |
| 195 | + |
| 196 | + |
| 197 | +def main() -> None: |
| 198 | + """Main execution function.""" |
| 199 | + n, d, trajectories = 6, 12, 10 |
| 200 | + |
| 201 | + # Measurement strengths (γ) |
| 202 | + # γ_{a} = 0: No measurement |
| 203 | + # γ_{a} = 1: Strong projective measurement with operator a |
| 204 | + γ_x, γ_zz = 0.01, 0.01 |
| 205 | + |
| 206 | + cx, sx = np.cos((1 - γ_x) * np.pi / 4), np.sin((1 - γ_x) * np.pi / 4) |
| 207 | + czz, szz = np.cos((1 - γ_zz) * np.pi / 4), np.sin((1 - γ_zz) * np.pi / 4) |
| 208 | + czxz, szxz = np.cos((γ_zz + γ_x) * np.pi / 4), np.sin((γ_zz + γ_x) * np.pi / 4) |
| 209 | + |
| 210 | + I_ab, T_ee = 0, 0 |
| 211 | + results_x, results_zz, results_zxz = [], [], [] |
| 212 | + |
| 213 | + for _ in range(trajectories): |
| 214 | + sx_r = np.random.uniform(size=[d * n]) |
| 215 | + szz_r = np.random.uniform(size=[d * n]) |
| 216 | + szxz_r = np.random.uniform(size=[d * n]) |
| 217 | + |
| 218 | + x, zz, zxz, state = mipt_circuit( |
| 219 | + sx_r, szz_r, szxz_r, n, d, cx, sx, czz, szz, czxz, szxz |
| 220 | + ) |
| 221 | + |
| 222 | + results_x.append(x) |
| 223 | + results_zz.append(zz) |
| 224 | + results_zxz.append(zxz) |
| 225 | + |
| 226 | + I_ab += MEE(state, n) / trajectories |
| 227 | + T_ee += TEE(state, n) / trajectories |
| 228 | + |
| 229 | + # Output |
| 230 | + print("X outcomes:", np.concatenate(results_x).tolist()) |
| 231 | + print("ZZ outcomes:", np.concatenate(results_zz).tolist()) |
| 232 | + print("ZXZ outcomes:", np.concatenate(results_zxz).tolist()) |
| 233 | + print(f"MI (bits): {I_ab/np.log(2):.4f}") |
| 234 | + print(f"TEE (bits): {T_ee/np.log(2):.4f}") |
| 235 | + |
| 236 | + |
| 237 | +if __name__ == "__main__": |
| 238 | + main() |
0 commit comments