@@ -1812,3 +1812,255 @@ if __name__ == "__main__":
18121812
18131813!ec
18141814
1815+
1816+ !split
1817+ ===== More codes =====
1818+
1819+ !bc pycod
1820+ import numpy as np
1821+
1822+ # One-qubit gates
1823+ def I(): return np.eye(2)
1824+ def X(): return np.array([[0,1],[1,0]])
1825+ def Y(): return np.array([[0,-1j],[1j,0]])
1826+ def Z(): return np.array([[1,0],[0,-1]])
1827+ def H(): return (1/np.sqrt(2))*np.array([[1,1],[1,-1]])
1828+ def S(): return np.array([[1,0],[0,1j]])
1829+ def T(): return np.array([[1,0],[0,np.exp(1j*np.pi/4)]])
1830+
1831+ def Rx(theta):
1832+ return np.array([
1833+ [np.cos(theta/2), -1j*np.sin(theta/2)],
1834+ [-1j*np.sin(theta/2), np.cos(theta/2)]
1835+ ])
1836+
1837+ def Ry(theta):
1838+ return np.array([
1839+ [np.cos(theta/2), -np.sin(theta/2)],
1840+ [np.sin(theta/2), np.cos(theta/2)]
1841+ ])
1842+
1843+ def Rz(theta):
1844+ return np.array([
1845+ [np.exp(-1j*theta/2), 0],
1846+ [0, np.exp(1j*theta/2)]
1847+ ])
1848+
1849+ # Two-qubit gates
1850+ def CNOT():
1851+ return np.array([
1852+ [1,0,0,0],
1853+ [0,1,0,0],
1854+ [0,0,0,1],
1855+ [0,0,1,0]
1856+ ])
1857+
1858+ def CZ():
1859+ return np.array([
1860+ [1,0,0,0],
1861+ [0,1,0,0],
1862+ [0,0,1,0],
1863+ [0,0,0,-1]
1864+ ])
1865+
1866+ def SWAP():
1867+ return np.array([
1868+ [1,0,0,0],
1869+ [0,0,1,0],
1870+ [0,1,0,0],
1871+ [0,0,0,1]
1872+ ])
1873+
1874+
1875+ !ec
1876+
1877+
1878+ !split
1879+ ===== Circuit =====
1880+ !bc pycod
1881+ import random
1882+ from collections import Counter
1883+
1884+ class Gate:
1885+ def __init__(self, matrix, targets):
1886+ self.matrix = matrix
1887+ self.targets = targets
1888+
1889+ class Circuit:
1890+ def __init__(self, num_qubits):
1891+ self.n = num_qubits
1892+ self.reset()
1893+
1894+ def reset(self):
1895+ self.state = np.zeros(2**self.n, dtype=np.complex128)
1896+ self.state[0] = 1.0
1897+ self.gates = []
1898+
1899+ def add_gate(self, gate):
1900+ self.gates.append(gate)
1901+
1902+ def run(self):
1903+ for gate in self.gates:
1904+ self.apply_gate(gate)
1905+
1906+ def apply_gate(self, gate):
1907+ full_U = self.expand_gate(gate)
1908+ self.state = full_U @ self.state
1909+
1910+ def expand_gate(self, gate):
1911+ if len(gate.targets) == 1:
1912+ return self.tensor_product_gate(gate)
1913+ elif len(gate.targets) == 2:
1914+ return self.tensor_product_two_qubit_gate(gate)
1915+ else:
1916+ raise ValueError("Only 1 or 2 qubit gates supported.")
1917+
1918+ def tensor_product_gate(self, gate):
1919+ ops = [np.eye(2) for _ in range(self.n)]
1920+ ops[gate.targets[0]] = gate.matrix
1921+ U = ops[0]
1922+ for op in ops[1:]:
1923+ U = np.kron(U, op)
1924+ return U
1925+
1926+ def tensor_product_two_qubit_gate(self, gate):
1927+ ops = [np.eye(2) for _ in range(self.n)]
1928+ idx = sorted(gate.targets)
1929+ # naive expansion for generality:
1930+ full_U = np.eye(1)
1931+ for i in range(self.n):
1932+ if i in gate.targets:
1933+ continue
1934+ full_U = np.kron(full_U, np.eye(2))
1935+ full_U = np.kron(gate.matrix, full_U)
1936+ return full_U # for small N this works; we can optimize later
1937+
1938+ def get_statevector(self):
1939+ return self.state
1940+
1941+ def get_probabilities(self):
1942+ return np.abs(self.state)**2
1943+
1944+ def measure(self, shots=1024):
1945+ probs = self.get_probabilities()
1946+ basis_states = [format(i, f'0{self.n}b') for i in range(2**self.n)]
1947+ samples = random.choices(basis_states, weights=probs, k=shots)
1948+ return dict(Counter(samples))
1949+
1950+
1951+ !ec
1952+
1953+ !split
1954+ ===== Noise model =====
1955+ !bc pycod
1956+
1957+ import numpy as np
1958+ import random
1959+
1960+ def bit_flip(state, p):
1961+ noisy_state = state.copy()
1962+ for i in range(len(state)):
1963+ if random.random() < p:
1964+ flipped = i ^ 1 # flips qubit 0
1965+ noisy_state[flipped] += state[i]
1966+ noisy_state[i] = 0
1967+ return noisy_state / np.linalg.norm(noisy_state)
1968+
1969+ def depolarizing(state, p):
1970+ d = len(state)
1971+ noisy_state = (1-p) * state + p/d * np.ones(d)
1972+ return noisy_state / np.linalg.norm(noisy_state)
1973+
1974+ def bit_flip(state, p):
1975+ noisy_state = state.copy()
1976+ for i in range(len(state)):
1977+ if random.random() < p:
1978+ flipped = i ^ 1 # flips qubit 0
1979+ noisy_state[flipped] += state[i]
1980+ noisy_state[i] = 0
1981+ return noisy_state / np.linalg.norm(noisy_state)
1982+
1983+ def depolarizing(state, p):
1984+ d = len(state)
1985+ noisy_state = (1-p) * state + p/d * np.ones(d)
1986+ return noisy_state / np.linalg.norm(noisy_state)
1987+
1988+
1989+ !ec
1990+
1991+ !split
1992+ ===== Bell states =====
1993+ !bc pycod
1994+ from core.circuit import Circuit, Gate
1995+ from core import gates
1996+
1997+ def bell_state(label="Phi+"):
1998+ c = Circuit(2)
1999+ c.add_gate(Gate(gates.H(), [0]))
2000+ c.add_gate(Gate(gates.CNOT(), [0,1]))
2001+
2002+ if label == "Phi+":
2003+ pass
2004+ elif label == "Phi-":
2005+ c.add_gate(Gate(gates.Z(), [0]))
2006+ elif label == "Psi+":
2007+ c.add_gate(Gate(gates.X(), [1]))
2008+ elif label == "Psi-":
2009+ c.add_gate(Gate(gates.X(), [1]))
2010+ c.add_gate(Gate(gates.Z(), [0]))
2011+ else:
2012+ raise ValueError("Unknown Bell state label")
2013+
2014+ c.run()
2015+ # Assuming the Circuit class has a measure method for simulation
2016+ # If not, this line will need to be adjusted based on how measurement is handled
2017+ return c
2018+
2019+ !ec
2020+
2021+ !split
2022+ ===== Example =====
2023+ !bc pycod
2024+ import matplotlib.pyplot as plt
2025+ import numpy as np # Add numpy import
2026+
2027+ # Assuming Circuit and Gate are available in the environment from previous cell execution
2028+ # If not, you might need to re-import them here or ensure the 'core' module is structured correctly.
2029+ # For now, let's assume they are available after running the previous cell.
2030+
2031+ labels = ["Phi+", "Phi-", "Psi+", "Psi-"]
2032+ shots = 1000
2033+
2034+ for label in labels:
2035+ print(f"\nBell state {label}")
2036+ c = bell_state(label)
2037+ probs = c.get_probabilities()
2038+ print("Statevector probabilities:", probs)
2039+ # The Circuit class defined earlier does NOT have a 'measure' method.
2040+ # You need to implement a simulation of measurement based on probabilities.
2041+ # Here's a possible implementation:
2042+ counts = {}
2043+ statevector = c.get_statevector()
2044+ num_qubits = int(np.log2(len(statevector))) # infer number of qubits
2045+ basis_states = [bin(i)[2:].zfill(num_qubits) for i in range(len(statevector))]
2046+
2047+ # Simulate shots based on probabilities
2048+ measured_indices = np.random.choice(len(statevector), size=shots, p=probs)
2049+ for idx in measured_indices:
2050+ basis_state_str = basis_states[idx]
2051+ counts[basis_state_str] = counts.get(basis_state_str, 0) + 1
2052+
2053+ print("Measurement counts:", counts)
2054+ plt.bar(counts.keys(), counts.values())
2055+ plt.title(f"Bell state {label}")
2056+ plt.show()
2057+
2058+ !ec
2059+
2060+
2061+
2062+
2063+
2064+
2065+
2066+
0 commit comments