mps-xtrap 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mps_xtrap/__init__.py ADDED
@@ -0,0 +1,44 @@
1
+ """
2
+ mps_xtrap — MPS-based quantum circuit simulator with Richardson extrapolation.
3
+
4
+ Quick start
5
+ -----------
6
+ >>> from mps_xtrap import Circuit, MPSSimulator, MultiChiRunner, SweepConfig
7
+ >>>
8
+ >>> # Simple simulation at a fixed bond dimension
9
+ >>> c = Circuit(4)
10
+ >>> c.h(0).cx(0,1).cx(1,2).cx(2,3)
11
+ >>> sim = MPSSimulator(chi=64)
12
+ >>> state = sim.run(c)
13
+ >>> print(state.expectation_pauli_z(0))
14
+ >>>
15
+ >>> # Extrapolated simulation — just set base_chi, scaling_factor, n_levels
16
+ >>> # Bond dimensions are auto-generated: [16, 32, 64]
17
+ >>> runner = MultiChiRunner(SweepConfig(base_chi=16, scaling_factor=2, n_levels=3))
18
+ >>> result = runner.run(c, {'Z0': ('Z', 0), 'Z1': ('Z', 1)})
19
+ >>> print(result.summary())
20
+ """
21
+
22
+ from .core.mps import MPS
23
+ from .circuits import Circuit, MPSSimulator
24
+ from .extrapolation import (
25
+ MultiChiRunner,
26
+ SweepConfig,
27
+ ExtrapolationResult,
28
+ MultiObservableResult,
29
+ )
30
+ from .gates import get_gate
31
+
32
+ __version__ = "1.0.0"
33
+ __author__ = "MPS Sim"
34
+
35
+ __all__ = [
36
+ "MPS",
37
+ "Circuit",
38
+ "MPSSimulator",
39
+ "MultiChiRunner",
40
+ "SweepConfig",
41
+ "ExtrapolationResult",
42
+ "MultiObservableResult",
43
+ "get_gate",
44
+ ]
@@ -0,0 +1,336 @@
1
+ """
2
+ Quantum circuit representation and gate application on MPS.
3
+
4
+ A Circuit is an ordered list of gate instructions. The Simulator
5
+ applies these instructions to an MPS, performing SVD truncations
6
+ at two-qubit gates.
7
+ """
8
+
9
+ from __future__ import annotations
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+ from dataclasses import dataclass, field
13
+ from typing import List, Tuple, Optional, Union, Dict, Any
14
+ import logging
15
+
16
+ from ..core.mps import MPS
17
+ from ..gates import get_gate, SINGLE_QUBIT_GATES, TWO_QUBIT_GATES
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Gate instruction
24
+ # ---------------------------------------------------------------------------
25
+
26
+ @dataclass
27
+ class GateInstruction:
28
+ """
29
+ A single gate application.
30
+
31
+ Attributes
32
+ ----------
33
+ name : str
34
+ Gate name (e.g., 'H', 'CNOT', 'Rx').
35
+ qubits : list of int
36
+ Target qubits. Length 1 for single-qubit gates, 2 for two-qubit.
37
+ params : list of float
38
+ Gate parameters (e.g., rotation angles).
39
+ matrix : ndarray or None
40
+ Explicit gate matrix (overrides name lookup if provided).
41
+ label : str
42
+ Human-readable label for circuit diagrams.
43
+ """
44
+ name: str
45
+ qubits: List[int]
46
+ params: List[float] = field(default_factory=list)
47
+ matrix: Optional[NDArray] = None
48
+ label: str = ""
49
+
50
+ def __post_init__(self):
51
+ if not self.label:
52
+ self.label = self.name
53
+ if not isinstance(self.qubits, list):
54
+ self.qubits = list(self.qubits)
55
+
56
+ def get_matrix(self) -> NDArray:
57
+ if self.matrix is not None:
58
+ return self.matrix
59
+ return get_gate(self.name, *self.params)
60
+
61
+ def __repr__(self) -> str:
62
+ q = ",".join(map(str, self.qubits))
63
+ p = f"({','.join(f'{x:.4f}' for x in self.params)})" if self.params else ""
64
+ return f"{self.name}{p}[{q}]"
65
+
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Circuit
69
+ # ---------------------------------------------------------------------------
70
+
71
+ class Circuit:
72
+ """
73
+ A quantum circuit as an ordered list of gate instructions.
74
+
75
+ Supports a fluent builder API:
76
+ c = Circuit(4)
77
+ c.h(0).cx(0,1).rz(np.pi/4, 2).measure_all()
78
+ """
79
+
80
+ def __init__(self, n_qubits: int):
81
+ self.n = n_qubits
82
+ self.instructions: List[GateInstruction] = []
83
+
84
+ # ------------------------------------------------------------------
85
+ # Builder API
86
+ # ------------------------------------------------------------------
87
+
88
+ def _add(self, name: str, qubits, params=None, matrix=None) -> 'Circuit':
89
+ if isinstance(qubits, int):
90
+ qubits = [qubits]
91
+ inst = GateInstruction(
92
+ name=name,
93
+ qubits=list(qubits),
94
+ params=list(params) if params else [],
95
+ matrix=matrix,
96
+ )
97
+ self.instructions.append(inst)
98
+ return self
99
+
100
+ # Single-qubit gates
101
+ def i(self, q): return self._add('I', [q])
102
+ def x(self, q): return self._add('X', [q])
103
+ def y(self, q): return self._add('Y', [q])
104
+ def z(self, q): return self._add('Z', [q])
105
+ def h(self, q): return self._add('H', [q])
106
+ def s(self, q): return self._add('S', [q])
107
+ def t(self, q): return self._add('T', [q])
108
+ def sdg(self, q): return self._add('Sdg', [q])
109
+ def tdg(self, q): return self._add('Tdg', [q])
110
+
111
+ def rx(self, theta: float, q: int): return self._add('Rx', [q], [theta])
112
+ def ry(self, theta: float, q: int): return self._add('Ry', [q], [theta])
113
+ def rz(self, theta: float, q: int): return self._add('Rz', [q], [theta])
114
+ def p(self, phi: float, q: int): return self._add('P', [q], [phi])
115
+ def u(self, theta, phi, lam, q): return self._add('U', [q], [theta, phi, lam])
116
+
117
+ # Two-qubit gates
118
+ def cx(self, ctrl, tgt): return self._add('CNOT', [ctrl, tgt])
119
+ def cnot(self, ctrl, tgt): return self._add('CNOT', [ctrl, tgt])
120
+ def cz(self, q1, q2): return self._add('CZ', [q1, q2])
121
+ def swap(self, q1, q2): return self._add('SWAP', [q1, q2])
122
+ def iswap(self, q1, q2): return self._add('iSWAP', [q1, q2])
123
+ def xx(self, theta, q1, q2): return self._add('XX', [q1, q2], [theta])
124
+ def yy(self, theta, q1, q2): return self._add('YY', [q1, q2], [theta])
125
+ def zz(self, theta, q1, q2): return self._add('ZZ', [q1, q2], [theta])
126
+ def crz(self, theta, ctrl, tgt): return self._add('CRz', [ctrl, tgt], [theta])
127
+ def cp(self, phi, ctrl, tgt): return self._add('CP', [ctrl, tgt], [phi])
128
+
129
+ # Custom matrix
130
+ def unitary(self, matrix: NDArray, qubits): return self._add('U_custom', qubits, matrix=matrix)
131
+
132
+ # ------------------------------------------------------------------
133
+ # Helpers for common circuit patterns
134
+ # ------------------------------------------------------------------
135
+
136
+ def barrier(self, *qubits):
137
+ """Barrier is a no-op for simulation but marks a layer boundary."""
138
+ return self # no-op
139
+
140
+ def __len__(self):
141
+ return len(self.instructions)
142
+
143
+ def depth(self) -> int:
144
+ """Estimate circuit depth (number of non-overlapping gate layers)."""
145
+ last_use = [-1] * self.n
146
+ max_layer = -1
147
+ for inst in self.instructions:
148
+ d = max(last_use[q] for q in inst.qubits) + 1
149
+ max_layer = max(max_layer, d)
150
+ for q in inst.qubits:
151
+ last_use[q] = d
152
+ return max_layer + 1 # convert 0-indexed layer to count
153
+
154
+ def two_qubit_count(self) -> int:
155
+ return sum(1 for inst in self.instructions if len(inst.qubits) == 2)
156
+
157
+ def __repr__(self) -> str:
158
+ return (f"Circuit(n={self.n}, gates={len(self.instructions)}, "
159
+ f"depth≈{self.depth()}, 2q={self.two_qubit_count()})")
160
+
161
+ def draw(self) -> str:
162
+ """Simple text-based circuit diagram."""
163
+ lines = [f"q{i}: " for i in range(self.n)]
164
+ for inst in self.instructions:
165
+ label = str(inst)
166
+ for i, q in enumerate(inst.qubits):
167
+ lines[q] += f"──{label}──"
168
+ # Pad other qubits
169
+ max_len = max(len(l) for l in lines)
170
+ lines = [l + '─' * (max_len - len(l)) for l in lines]
171
+ return '\n'.join(lines)
172
+
173
+
174
+ # ---------------------------------------------------------------------------
175
+ # MPS Simulator
176
+ # ---------------------------------------------------------------------------
177
+
178
+ class MPSSimulator:
179
+ """
180
+ Applies a Circuit to an MPS state.
181
+
182
+ Two-qubit gates on non-adjacent qubits are handled by first
183
+ swapping qubits into adjacent positions, applying the gate,
184
+ then swapping back.
185
+ """
186
+
187
+ def __init__(self, chi: int, svd_threshold: float = 1e-14):
188
+ """
189
+ Parameters
190
+ ----------
191
+ chi : int
192
+ Maximum bond dimension for SVD truncation.
193
+ svd_threshold : float
194
+ Singular values below this are discarded before bond-dim cap.
195
+ """
196
+ self.chi = chi
197
+ self.svd_threshold = svd_threshold
198
+
199
+ def run(self, circuit: Circuit) -> MPS:
200
+ """
201
+ Simulate circuit from |0...0> and return final MPS state.
202
+
203
+ Returns
204
+ -------
205
+ MPS
206
+ Final state after all gates applied.
207
+ """
208
+ state = MPS(circuit.n, self.chi)
209
+ self._apply_circuit(circuit, state)
210
+ return state
211
+
212
+ def run_from(self, circuit: Circuit, state: MPS) -> MPS:
213
+ """Apply circuit to an existing MPS state (modifies a copy)."""
214
+ new_state = state.copy()
215
+ new_state.chi = self.chi
216
+ self._apply_circuit(circuit, new_state)
217
+ return new_state
218
+
219
+ def _apply_circuit(self, circuit: Circuit, state: MPS):
220
+ for inst in circuit.instructions:
221
+ if len(inst.qubits) == 1:
222
+ self._apply_single(inst, state)
223
+ elif len(inst.qubits) == 2:
224
+ self._apply_two(inst, state)
225
+ else:
226
+ raise ValueError(f"Gates on >2 qubits not yet supported: {inst}")
227
+
228
+ def _apply_single(self, inst: GateInstruction, state: MPS):
229
+ """Apply single-qubit gate via matrix multiplication on physical index."""
230
+ q = inst.qubits[0]
231
+ gate = inst.get_matrix() # (2,2)
232
+ t = state.get_tensor(q) # (chi_l, 2, chi_r)
233
+ # Contract: new[l,s',r] = sum_s gate[s',s] * t[l,s,r]
234
+ new_t = np.einsum('sp,lpr->lsr', gate, t)
235
+ state.set_tensor(q, new_t)
236
+ state.center = None # Canonicality violated
237
+
238
+ def _apply_two(self, inst: GateInstruction, state: MPS):
239
+ """Apply two-qubit gate with SVD truncation."""
240
+ q1, q2 = inst.qubits
241
+
242
+ # Handle non-adjacent qubits via SWAP chain
243
+ if abs(q1 - q2) != 1:
244
+ self._apply_two_nonlocal(inst, state)
245
+ return
246
+
247
+ # Ensure q1 < q2
248
+ if q1 > q2:
249
+ q1, q2 = q2, q1
250
+ gate = inst.get_matrix() # (2,2,2,2): op[s1',s2',s1,s2]
251
+ # Swap qubit ordering
252
+ gate = gate.transpose(1, 0, 3, 2)
253
+ else:
254
+ gate = inst.get_matrix()
255
+
256
+ # Build theta: two-site tensor
257
+ A = state.get_tensor(q1) # (chi_l, 2, chi_m)
258
+ B = state.get_tensor(q2) # (chi_m, 2, chi_r)
259
+ theta = np.einsum('lir,rjs->lijs', A, B) # (chi_l, 2, 2, chi_r)
260
+
261
+ # Apply gate: theta'[l,s1',s2',r] = sum_{s1,s2} gate[s1',s2',s1,s2] * theta[l,s1,s2,r]
262
+ theta = np.einsum('abij,lijs->labs', gate, theta)
263
+
264
+ # SVD truncation
265
+ state.apply_svd_truncation(q1, theta, chi=self.chi,
266
+ svd_threshold=self.svd_threshold)
267
+ state.center = None
268
+
269
+ def _apply_two_nonlocal(self, inst: GateInstruction, state: MPS):
270
+ """
271
+ Apply a gate on non-adjacent qubits via SWAP chain.
272
+ Brings qubits adjacent, applies gate, swaps back.
273
+ """
274
+ from ..gates import SWAP as make_swap
275
+ q1, q2 = inst.qubits
276
+ if q1 > q2:
277
+ q1, q2 = q2, q1
278
+
279
+ # Bubble q2 adjacent to q1
280
+ swap_gate = make_swap()
281
+ swap_inst = GateInstruction('SWAP', [0, 1], matrix=swap_gate)
282
+
283
+ for pos in range(q2, q1 + 1, -1):
284
+ swap_inst.qubits = [pos - 1, pos]
285
+ swap_inst.matrix = swap_gate
286
+ self._apply_two(swap_inst, state)
287
+
288
+ # Now apply actual gate at (q1, q1+1)
289
+ modified_inst = GateInstruction(
290
+ inst.name, [q1, q1 + 1],
291
+ inst.params, inst.matrix, inst.label
292
+ )
293
+ self._apply_two(modified_inst, state)
294
+
295
+ # Swap back
296
+ for pos in range(q1 + 1, q2):
297
+ swap_inst.qubits = [pos, pos + 1]
298
+ self._apply_two(swap_inst, state)
299
+
300
+ def expectation_value(
301
+ self,
302
+ state: MPS,
303
+ observable: str,
304
+ site: int,
305
+ site2: Optional[int] = None,
306
+ ) -> float:
307
+ """
308
+ Convenience wrapper to compute expectation values.
309
+
310
+ Parameters
311
+ ----------
312
+ state : MPS
313
+ observable : str
314
+ 'X', 'Y', 'Z', 'ZZ', 'XX', etc.
315
+ site : int
316
+ Primary site.
317
+ site2 : int, optional
318
+ Second site for two-qubit observables.
319
+ """
320
+ if observable == 'Z':
321
+ return state.expectation_pauli_z(site)
322
+ elif observable == 'X':
323
+ return state.expectation_pauli_x(site)
324
+ elif observable == 'Y':
325
+ return state.expectation_pauli_y(site)
326
+ elif observable in ('ZZ', 'XX', 'YY') and site2 is not None:
327
+ from ..gates import Z, X, Y
328
+ ops = {'ZZ': Z, 'XX': X, 'YY': Y}
329
+ op1 = ops[observable]()
330
+ op = np.einsum('ij,kl->ikjl', op1, op1)
331
+ if abs(site - site2) == 1:
332
+ return float(np.real(state.expectation_two(op, min(site, site2))))
333
+ else:
334
+ raise NotImplementedError("Non-adjacent two-site observables not implemented")
335
+ else:
336
+ raise ValueError(f"Unknown observable: {observable}")
mps_xtrap/cli.py ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ mps_xtrap CLI — MPS quantum circuit simulator with Richardson extrapolation.
4
+
5
+ Usage
6
+ -----
7
+ python cli.py simulate --circuit bell --chi 64
8
+ python cli.py extrapolate --circuit ising --chi-base 16 --levels 3
9
+ python cli.py benchmark --circuit ghz --n 10
10
+ python cli.py info
11
+ """
12
+
13
+ import sys
14
+ import os
15
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
16
+
17
+ import argparse
18
+ import numpy as np
19
+ import time
20
+ from mps_xtrap import Circuit, MPSSimulator, MultiChiRunner, SweepConfig
21
+ from mps_xtrap import RichardsonExtrapolator
22
+
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Built-in circuit library
26
+ # ---------------------------------------------------------------------------
27
+
28
+ def make_bell(n=2) -> Circuit:
29
+ c = Circuit(max(2, n))
30
+ c.h(0).cx(0, 1)
31
+ return c
32
+
33
+ def make_ghz(n=6) -> Circuit:
34
+ c = Circuit(n)
35
+ c.h(0)
36
+ for i in range(n - 1):
37
+ c.cx(i, i + 1)
38
+ return c
39
+
40
+ def make_ising(n=8, steps=5, J=1.0, h=0.5, dt=0.05) -> Circuit:
41
+ c = Circuit(n)
42
+ for _ in range(steps):
43
+ for q in range(n):
44
+ c.rx(2 * h * dt, q)
45
+ for q in range(n - 1):
46
+ c.zz(2 * J * dt, q, q + 1)
47
+ return c
48
+
49
+ def make_qft(n=6) -> Circuit:
50
+ """Quantum Fourier Transform."""
51
+ c = Circuit(n)
52
+ for i in range(n):
53
+ c.h(i)
54
+ for j in range(i + 1, n):
55
+ c.cp(np.pi / 2 ** (j - i), i, j)
56
+ # Bit reversal swaps
57
+ for i in range(n // 2):
58
+ c.swap(i, n - 1 - i)
59
+ return c
60
+
61
+ def make_random(n=8, depth=10, seed=42) -> Circuit:
62
+ np.random.seed(seed)
63
+ c = Circuit(n)
64
+ for _ in range(depth):
65
+ for q in range(n):
66
+ c.ry(np.random.uniform(0, np.pi), q)
67
+ for q in range(n - 1):
68
+ if np.random.random() < 0.5:
69
+ c.cx(q, q + 1)
70
+ return c
71
+
72
+ CIRCUITS = {
73
+ 'bell': make_bell,
74
+ 'ghz': make_ghz,
75
+ 'ising': make_ising,
76
+ 'qft': make_qft,
77
+ 'random': make_random,
78
+ }
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # CLI commands
83
+ # ---------------------------------------------------------------------------
84
+
85
+ def cmd_simulate(args):
86
+ print(f"\n── Simulation ──────────────────────────────────────────")
87
+ circuit_fn = CIRCUITS.get(args.circuit)
88
+ if circuit_fn is None:
89
+ print(f"Unknown circuit '{args.circuit}'. Available: {list(CIRCUITS)}")
90
+ return
91
+
92
+ c = circuit_fn(n=args.n)
93
+ print(f"Circuit: {c}")
94
+ print(f"Bond dim: χ={args.chi}")
95
+
96
+ t0 = time.time()
97
+ sim = MPSSimulator(chi=args.chi)
98
+ state = sim.run(c)
99
+ elapsed = time.time() - t0
100
+
101
+ print(f"\nResults (time: {elapsed:.2f}s):")
102
+ for q in range(min(c.n, 10)):
103
+ z = state.expectation_pauli_z(q)
104
+ x = state.expectation_pauli_x(q)
105
+ print(f" q{q}: <Z>={z:+.6f} <X>={x:+.6f}")
106
+
107
+ print(f"\nBond dims: {state.bond_dimensions()}")
108
+ print(f"Max bond dim reached: {state.max_bond_dim()} / {args.chi}")
109
+ print(f"Total truncation error: {state.total_truncation_error():.2e}")
110
+ print(f"State: {state}")
111
+
112
+
113
+ def cmd_extrapolate(args):
114
+ print(f"\n── Richardson Extrapolation ────────────────────────────")
115
+ circuit_fn = CIRCUITS.get(args.circuit)
116
+ if circuit_fn is None:
117
+ print(f"Unknown circuit '{args.circuit}'. Available: {list(CIRCUITS)}")
118
+ return
119
+
120
+ c = circuit_fn(n=args.n)
121
+ print(f"Circuit: {c}")
122
+
123
+ cfg = SweepConfig(
124
+ base_chi=args.chi_base,
125
+ ratio=args.ratio,
126
+ n_levels=args.levels,
127
+ )
128
+ print(f"Bond dims: {cfg.bond_dims}")
129
+ print(f"Effective equivalent χ: {cfg.effective_chi()}")
130
+ print()
131
+
132
+ observables = {f'Z{q}': ('Z', q) for q in range(min(c.n, args.obs_qubits))}
133
+ runner = MultiChiRunner(cfg, verbose=True)
134
+
135
+ t0 = time.time()
136
+ result = runner.run(c, observables)
137
+ elapsed = time.time() - t0
138
+
139
+ print(f"\n{result.summary()}")
140
+ print(f"Total runtime: {elapsed:.2f}s")
141
+
142
+
143
+ def cmd_benchmark(args):
144
+ print(f"\n── Benchmark ───────────────────────────────────────────")
145
+ circuit_fn = CIRCUITS.get(args.circuit)
146
+ if circuit_fn is None:
147
+ print(f"Unknown circuit '{args.circuit}'. Available: {list(CIRCUITS)}")
148
+ return
149
+
150
+ c = circuit_fn(n=args.n)
151
+ print(f"Circuit: {c}")
152
+ print(f"{'χ':>6} {'Time(s)':>10} {'<Z0>':>12} {'TruncErr':>12} {'MaxBond':>8}")
153
+ print("-" * 60)
154
+
155
+ chi_list = [args.chi_start * (2 ** i) for i in range(args.chi_levels)]
156
+ results = []
157
+
158
+ for chi in chi_list:
159
+ t0 = time.time()
160
+ sim = MPSSimulator(chi=chi)
161
+ state = sim.run(c)
162
+ elapsed = time.time() - t0
163
+ z0 = state.expectation_pauli_z(0)
164
+ trunc = state.total_truncation_error()
165
+ maxb = state.max_bond_dim()
166
+ results.append((chi, elapsed, z0, trunc, maxb))
167
+ print(f"{chi:>6} {elapsed:>10.3f} {z0:>+12.8f} {trunc:>12.2e} {maxb:>8}")
168
+
169
+ if len(results) >= 2:
170
+ print("\nRichardson extrapolation from all points:")
171
+ chi_vals = [r[0] for r in results]
172
+ z0_vals = [r[2] for r in results]
173
+ ext = RichardsonExtrapolator()
174
+ res = ext.extrapolate(chi_vals, z0_vals)
175
+ print(f" Extrapolated <Z0> = {res.extrapolated:.8f} ± {res.uncertainty:.2e}")
176
+ print(f" Alpha (error exponent) = {res.alpha:.3f}")
177
+ print(f" Reliable: {res.is_reliable}")
178
+
179
+
180
+ def cmd_info(args):
181
+ print("""
182
+ mps_xtrap — MPS Quantum Circuit Simulator with Richardson Extrapolation
183
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
184
+
185
+ ABOUT
186
+ Simulates quantum circuits using Matrix Product States (MPS).
187
+ Unique feature: multilevel Richardson extrapolation to estimate
188
+ expectation values at arbitrarily high bond dimensions from
189
+ simulations at a geometric sequence of modest bond dimensions.
190
+
191
+ GATES SUPPORTED
192
+ Single-qubit: I, X, Y, Z, H, S, T, Sdg, Tdg, Rx, Ry, Rz, P, U
193
+ Two-qubit: CNOT/CX, CZ, SWAP, iSWAP, XX, YY, ZZ, CRz, CP
194
+
195
+ BUILT-IN CIRCUITS
196
+ bell — 2-qubit Bell state
197
+ ghz — N-qubit GHZ state
198
+ ising — Transverse-field Ising Trotterized evolution
199
+ qft — Quantum Fourier Transform
200
+ random — Random brick-layer circuit
201
+
202
+ EXTRAPOLATION THEORY
203
+ Truncation error scales as: ε(χ) ≈ a₁/χ^α + a₂/χ^2α + ...
204
+ Richardson extrapolation cancels successive error orders using
205
+ simulations at χ, 2χ, 4χ, ..., analogous to Romberg integration.
206
+ Reliability diagnostics flag when the power-law assumption breaks down.
207
+
208
+ EXAMPLES
209
+ python cli.py simulate --circuit ghz --n 10 --chi 64
210
+ python cli.py extrapolate --circuit ising --n 8 --chi-base 16 --levels 3
211
+ python cli.py benchmark --circuit ising --n 8 --chi-start 8 --chi-levels 4
212
+ """)
213
+
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # Main
217
+ # ---------------------------------------------------------------------------
218
+
219
+ def main():
220
+ parser = argparse.ArgumentParser(
221
+ description='mps_xtrap — MPS Quantum Simulator with Richardson Extrapolation'
222
+ )
223
+ sub = parser.add_subparsers(dest='command')
224
+
225
+ # simulate
226
+ p_sim = sub.add_parser('simulate', help='Run a single simulation')
227
+ p_sim.add_argument('--circuit', default='ghz', choices=list(CIRCUITS))
228
+ p_sim.add_argument('--n', type=int, default=8, help='Number of qubits')
229
+ p_sim.add_argument('--chi', type=int, default=64, help='Bond dimension')
230
+
231
+ # extrapolate
232
+ p_ext = sub.add_parser('extrapolate', help='Run multi-chi sweep with extrapolation')
233
+ p_ext.add_argument('--circuit', default='ising', choices=list(CIRCUITS))
234
+ p_ext.add_argument('--n', type=int, default=8)
235
+ p_ext.add_argument('--chi-base', type=int, default=16)
236
+ p_ext.add_argument('--ratio', type=float, default=2.0)
237
+ p_ext.add_argument('--levels', type=int, default=3)
238
+ p_ext.add_argument('--obs-qubits', type=int, default=4, help='Number of qubits to measure')
239
+
240
+ # benchmark
241
+ p_bench = sub.add_parser('benchmark', help='Benchmark across chi values')
242
+ p_bench.add_argument('--circuit', default='ising', choices=list(CIRCUITS))
243
+ p_bench.add_argument('--n', type=int, default=8)
244
+ p_bench.add_argument('--chi-start', type=int, default=8)
245
+ p_bench.add_argument('--chi-levels', type=int, default=4)
246
+
247
+ # info
248
+ sub.add_parser('info', help='Show package info and supported gates')
249
+
250
+ args = parser.parse_args()
251
+
252
+ if args.command == 'simulate':
253
+ cmd_simulate(args)
254
+ elif args.command == 'extrapolate':
255
+ cmd_extrapolate(args)
256
+ elif args.command == 'benchmark':
257
+ cmd_benchmark(args)
258
+ elif args.command == 'info':
259
+ cmd_info(args)
260
+ else:
261
+ parser.print_help()
262
+
263
+
264
+ if __name__ == '__main__':
265
+ main()
@@ -0,0 +1 @@
1
+ from .mps import MPS