def right_multiply(state: State, apply_to: Union[int, list], op): """ Apply a multi-qubit operator on several qubits (indexed in apply_to) of the input codes. :param state: input wavefunction or density matrix :type state: np.ndarray :param apply_to: zero-based indices of qudit locations to apply the operator :type apply_to: list of int :param op: Operator to act with. :type op: np.ndarray (2-dimensional) """ if isinstance(apply_to, int): apply_to = [apply_to] if not isinstance(op, list): op = [op] # Type handling: to determine if Pauli, check if a list of strings pauli = False if isinstance(op, list): if all(isinstance(elem, str) for elem in op): pauli = True else: op = tools.tensor_product(op) if state.is_ket: print( 'Warning: right multiply functionality currently applies the operator and daggers the s.' ) n_op = len(apply_to) if not pauli: if tools.is_sorted(apply_to): # generate necessary shapes preshape = (d**n) * np.ones((2, n_op), dtype=int) preshape[0, 0] = int(state.dimension / ((d**n)**(1 + apply_to[n_op - 1]))) if n_op > 1: preshape[0, 1:] = np.flip((d**n)**np.diff(apply_to)) / (d**n) shape3 = np.zeros(2 * n_op + 2, dtype=int) shape3[0] = state.dimension shape3[1:-1] = np.reshape(preshape, (2 * n_op), order='F') shape3[-1] = -1 shape4 = np.zeros(2 * n_op + 2, dtype=int) shape4[0] = state.dimension shape4[1:n_op + 1] = preshape[0] shape4[n_op + 1] = -1 shape4[n_op + 2:] = preshape[1] order3 = np.zeros(2 * n_op + 2, dtype=int) order3[0] = 0 order3[1:n_op + 2] = 2 * np.arange(n_op + 1) + np.ones(n_op + 1) order3[n_op + 2:] = 2 * np.arange(1, n_op + 1) order4 = np.zeros(2 * n_op + 2, dtype=int) order4[0] = 0 order4[1] = 1 order4[2:] = np.flip(np.arange(2, 2 * n_op + 2).reshape((2, -1), order='C'), axis=0).reshape((-1), order='F') # right multiply out = state.reshape(shape3, order='F').transpose(order3) out = np.dot(out.reshape((-1, (d**n)**n_op), order='F'), op.conj().T) out = out.reshape(shape4, order='F').transpose(order4) out = out.reshape(state.shape, order='F') return State(out, is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code) else: new_shape = 2 * np.ones(2 * n_op, dtype=int) permut = np.argsort(apply_to) transpose_ord = np.zeros(2 * n_op, dtype=int) transpose_ord[:n_op] = permut transpose_ord[n_op:] = n_op * np.ones(n_op, dtype=int) + permut sorted_op = np.reshape(np.transpose(np.reshape(op, new_shape, order='F'), axes=transpose_ord), ((d**n)**n_op, (d**n)**n_op), order='F') sorted_apply_to = apply_to[permut] return right_multiply(state, sorted_apply_to, sorted_op) else: out = state.copy() for i in range(len(apply_to)): # Note index start from the right (sN,...,s3,s2,s1) if op[i] == 'X': # Sigma_X out = qubit.left_multiply( out, [n * apply_to[i], n * apply_to[i] + 2], ['Y', 'Y']) elif op[i] == 'Y': # Sigma_Y out = -1 * qubit.left_multiply( out, [n * apply_to[i] + 1, n * apply_to[i] + 2], ['X', 'X']) elif op[i] == 'Z': # Sigma_Z out = qubit.left_multiply( out, [n * apply_to[i], n * apply_to[i] + 1], ['Z', 'Z']) return State(out, is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code)
def test_hamiltonian_driver(self): N = 6 hl_qubit = hamiltonian.HamiltonianDriver( graph=tools_test.sample_graph()) psi0 = State(np.zeros((2**N, 1))) psi0[0, 0] = 1 psi1 = State(tools.outer_product(psi0, psi0)) # Evolve by e^{-i (\pi/2) \sum_i X_i} psi0 = hl_qubit.evolve(psi0, np.pi / 2) # Should get (-1j)^N |111111> self.assertTrue(np.vdot(psi0, psi0) == 1) self.assertTrue(psi0[-1, 0] == (-1j)**N) # Evolve by e^{-i (\pi/2) \sum_i X_i} psi1 = hl_qubit.evolve(psi1, np.pi / 2) # Should get (-1j)^N |111111> self.assertTrue(tools.is_valid_state(psi1)) self.assertAlmostEqual(psi1[-1, -1], 1) psi0 = State(np.zeros((2**N, 1))) psi0[0, 0] = 1 psi0 = hl_qubit.left_multiply(psi0) psi1 = np.zeros((2**N, 1), dtype=np.complex128) for i in range(N): psi1[2**i, 0] = 1 self.assertTrue(np.allclose(psi0, psi1)) psi2 = State(np.zeros((2**N, 1))) psi2[0, 0] = 1 psi2 = hl_qubit.hamiltonian @ psi2 self.assertTrue(np.allclose(psi0, psi2)) N = 3 hl = hamiltonian.HamiltonianDriver(transition=(0, 1), code=rydberg) psi0 = State(np.zeros((rydberg.d**N, 1)), code=rydberg) psi0[5, 0] = 1 psi1 = State(tools.outer_product(psi0, psi0), code=rydberg) psi0 = hl.left_multiply(psi0) self.assertTrue(psi0[2, 0] == 1) self.assertTrue(psi0[14, 0] == 1) psi1 = hl.left_multiply(psi1) self.assertTrue(psi1[2, 5] == 1) self.assertTrue(psi1[14, 5] == 1) psi0 = State(np.zeros((rydberg.d**N, 1)), code=rydberg) psi0[5, 0] = 1 psi0 = hl.evolve(psi0, np.pi / 2) self.assertTrue(np.isclose(psi0[11, 0], -1)) # IS subspace hl = hamiltonian.HamiltonianDriver(transition=(0, 2), code=rydberg, IS_subspace=True, graph=line_graph(2)) psi0 = State(np.zeros((8, 1)), code=rydberg) psi0[-1, 0] = 1 psi0 = State(tools.outer_product(psi0, psi0), code=rydberg) self.assertTrue(tools.is_hermitian(hl.evolve(psi0, 1)))
from scipy.linalg import expm from scipy.sparse.linalg import expm_multiply from scipy.sparse import csr_matrix n = 10 print('Timer test with', n, 'qubits') Hb = np.zeros((2**n, 2**n), dtype=int) for i in range(n): Hb = Hb + tools.tensor_product( [tools.identity(i), qubit.X, tools.identity(n - i - 1)]) # Make sparse matrix for Hb Hb_sparse = csr_matrix(Hb) psi0 = State(np.zeros((2**n, 1))) psi0[-1, -1] = 1 t0 = time.perf_counter() res = expm_multiply(Hb, psi0) t1 = time.perf_counter() print('scipy expm_multiply with non-sparse matrix: ', t1 - t0) res = expm(Hb) @ psi0 t2 = time.perf_counter() print('numpy expm with non-spares matrix: ', t2 - t1) res = expm_multiply(Hb_sparse, psi0) t3 = time.perf_counter() print('scipy expm_multiply with sparse matrix:', t3 - t2) for j in range(n): # Do single qubit rotations psi0 = qubit.rotation(psi0, 1, j, qubit.X, is_idempotent=True) print('qsim qubit operations:', time.perf_counter() - t3)
def left_multiply(state: State, apply_to: Union[int, list], op): """ Apply a multi-qubit operator on several qubits (indexed in apply_to) of the input codes. :param state: input wavefunction or density matrix :type state: np.ndarray :param apply_to: zero-based indices of qudit locations to apply the operator :type apply_to: list of int :param op: Operator to act with. :type op: np.ndarray (2-dimensional) """ if isinstance(apply_to, int): apply_to = [apply_to] if not isinstance(op, list): op = [op] # Type handling: to determine if Pauli, check if a list of strings pauli = False if isinstance(op, list): if all(isinstance(elem, str) for elem in op): pauli = True else: op = tools.tensor_product(op) n_op = len(apply_to) if not pauli: if tools.is_sorted(apply_to): # Generate all shapes for left multiplication preshape = (d**n) * np.ones((2, n_op), dtype=int) preshape[1, 0] = int(state.dimension / ((d**n)**(1 + apply_to[n_op - 1]))) if n_op > 1: preshape[1, 1:] = np.flip((d**n)**np.diff(apply_to)) / (d**n) shape1 = np.zeros(2 * n_op + 1, dtype=int) shape2 = np.zeros(2 * n_op + 1, dtype=int) order1 = np.zeros(2 * n_op + 1, dtype=int) order2 = np.zeros(2 * n_op + 1, dtype=int) shape1[:-1] = np.flip(preshape, axis=0).reshape((2 * n_op), order='F') shape1[-1] = -1 shape2[:-1] = preshape.reshape((-1), order='C') shape2[-1] = -1 preorder = np.arange(2 * n_op) order1[:-1] = np.flip(preorder.reshape((-1, 2), order='C'), axis=1).reshape((-1), order='F') order2[:-1] = np.flip(preorder.reshape((2, -1), order='C'), axis=0).reshape((-1), order='F') order1[-1] = 2 * n_op order2[-1] = 2 * n_op # Now left multiply out = state.reshape(shape1, order='F').transpose(order1) out = np.dot(op, out.reshape(((d**n)**n_op, -1), order='F')) out = out.reshape(shape2, order='F').transpose(order2) out = out.reshape(state.shape, order='F') return State(out, is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code) else: # Need to reshape the operator given new_shape = (d**n) * np.ones(2 * n_op, dtype=int) permut = np.argsort(apply_to) transpose_ord = np.zeros(2 * n_op, dtype=int) transpose_ord[:n_op] = permut transpose_ord[n_op:] = n_op * np.ones(n_op, dtype=int) + permut sorted_op = np.reshape(np.transpose(np.reshape(op, new_shape, order='F'), axes=transpose_ord), ((d**n)**n_op, (d**n)**n_op), order='F') sorted_apply_to = apply_to[permut] return left_multiply(state, sorted_apply_to, sorted_op) else: # op should be a list of Pauli operators, or out = state.copy() for i in range(len(apply_to)): if op[i] == 'X': # Sigma_X out = qubit.left_multiply( out, [n * apply_to[i], n * apply_to[i] + 2], ['Y', 'Y']) elif op[i] == 'Y': # Sigma_Y out = -1 * qubit.left_multiply( out, [n * apply_to[i] + 1, n * apply_to[i] + 2], ['X', 'X']) elif op[i] == 'Z': # Sigma_Z out = qubit.left_multiply( out, [n * apply_to[i], n * apply_to[i] + 1], ['Z', 'Z']) return State(out, is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code)
def f(flattened): s = State(flattened.reshape(state_shape)) res = self.evolution_generator(s) return res.reshape(flattened.shape)
hb = hamiltonian.HamiltonianDriver() hamiltonians = [hc, hb] ring_hamiltonians = [hc_ring, hb] sim = qaoa.SimulateQAOA(g, cost_hamiltonian=hc, hamiltonian=hamiltonians) sim_ring = qaoa.SimulateQAOA(ring, cost_hamiltonian=hc_ring, hamiltonian=ring_hamiltonians) sim_ket = qaoa.SimulateQAOA(g, cost_hamiltonian=hc, hamiltonian=hamiltonians) sim_noisy = qaoa.SimulateQAOA(g, cost_hamiltonian=hc, hamiltonian=hamiltonians, noise_model='channel') # Initialize in |000000> psi0 = State(equal_superposition(N)) rho0 = State(outer_product(psi0, psi0)) noises = [quantum_channels.DepolarizingChannel(rates=(.001, ))] sim_noisy.noise = noises * 2 class TestSimulateQAOA(unittest.TestCase): def test_variational_grad(self): # Test that the calculated objective function and gradients are correct # p = 1 sim_ket.hamiltonian = hamiltonians F, Fgrad = sim_ket.variational_grad(np.array([1, 0.5]), initial_state=psi0) self.assertTrue(np.abs(F - 5.066062984904652) <= 1e-5) self.assertTrue(
def imperfect_blockade_performance(): graph = line_graph(n=5, return_mis=False) phi = .02 laser = hamiltonian.HamiltonianDriver(pauli='X', energies=(np.cos(phi) * np.sin(phi), ), graph=graph) energy_shift_r = hamiltonian.HamiltonianEnergyShift( index=0, energies=(np.sin(phi)**2, ), graph=graph) energy_shift_g = hamiltonian.HamiltonianEnergyShift( index=1, energies=(np.cos(phi)**2, ), graph=graph) dissipation = EffectiveOperatorDissipation(omega_g=np.cos(phi), omega_r=np.sin(phi), rates=(1, ), graph=graph, IS_subspace=False) rydberg_hamiltonian = hamiltonian.HamiltonianMIS(graph, IS_subspace=False, code=qubit, energies=( 0, 4, )) rydberg_hamiltonian_cost_IS = hamiltonian.HamiltonianMIS(graph, IS_subspace=True, code=qubit) rydberg_hamiltonian_cost = hamiltonian.HamiltonianMIS(graph, IS_subspace=False, code=qubit, energies=( 1, -4, )) eq = LindbladMasterEquation(hamiltonians=[ laser, energy_shift_g, energy_shift_r, rydberg_hamiltonian ], jump_operators=[dissipation]) state = np.zeros(rydberg_hamiltonian_cost.hamiltonian.shape) state[graph.independent_sets[np.argmax(rydberg_hamiltonian_cost_IS. hamiltonian)][0], graph.independent_sets[np.argmax(rydberg_hamiltonian_cost_IS. hamiltonian)][0]] = 1 state = State(state, is_ket=False, graph=graph, IS_subspace=False) ss = eq.steady_state(state, k=80, use_initial_guess=True) print(ss[1].shape) ss = ss[1][0] print(np.diagonal(ss), rydberg_hamiltonian_cost.hamiltonian) state = State(ss, is_ket=False, graph=graph, IS_subspace=False) print(np.around(ss, decimals=3), rydberg_hamiltonian_cost.optimum_overlap(state)) layout = np.zeros((2, 2)) layout[0, 0] = 1 layout[1, 1] = 1 layout[0, 1] = 1 adjacent_energy = 4 diag_energy = adjacent_energy / 8 second_nearest_energy = adjacent_energy / 64 for i in range(layout.shape[0] - 1): for j in range(layout.shape[1] - 1): if layout[i, j] == 1: # There is a spin here pass
plt.scatter(depths, [i / (i + 1) for i in depths], label='maxcut') plt.scatter(depths, mis, label='mis with $n=$' + str(n)) plt.plot(depths, mis) plt.legend() if show: plt.show() if __name__ == "__main__": i, j = 4, 4 graph = node_defect_torus(i, j) #simulation = adiabatic_simulation(graph, IS_subspace=True) #t0 = time.time() #res = simulation.performance_vs_total_time(np.arange(0, 0), metric='optimum_overlap', # schedule=lambda t, tf: experiment_rydberg_MIS_schedule(t, tf, simulation, # coefficients=[10, # 10]), # plot=True, verbose=True, method='trotterize') #print('results: ', res, flush=True) #simulation = mis_qaoa(4, IS_subspace=True, method='basinhopping') simulation = adiabatic_simulation(graph, IS_subspace=False) res = simulation.performance_vs_total_time(np.arange(40, 95, 5), metric='optimum_overlap', initial_state=State(equal_superposition(i * j)), schedule=lambda t, tf: simulation.linear_schedule(t, tf, coefficients=[1, 1]), plot=True, verbose=True, method='trotterize') print('results: ', res, flush=True)
def domain_wall_dissipation(n:list, dt=0.001, trials = 30): tf = 3 times = np.arange(0, tf, dt) pump_rate = 10 for i in n: # Ensure an odd number of nodes assert i % 2 == 1 def psi0(): """Generates the initial domain wall codes""" s = np.zeros((1, 2**i)) middle = i//2-1 arr = np.ones(i) for j in range(i): if j <= middle and j % 2 == 0: arr[j] = 0 elif j > middle+1 and j % 2 == 1: arr[j] = 0 s[0,tools.nary_to_int(arr)] = 1 return s.T def mis_state(): """Generates the initial domain wall codes""" s = np.zeros((1, 2**i)) arr = np.ones(i) for j in range(i): if j % 2 == 0: arr[j] = 0 s[0, tools.nary_to_int(arr)] = 1 return s.T def spin_hamiltonian(): s = np.zeros((1, 2 ** i)) for j in range(2**i): s[0,j] = i-np.sum(np.array([tools.int_to_nary(j)])) return s.T g = chain_graph(i) graph = Graph(g) psi0 = psi0()#np.zeros((2**i, 1)) # #psi0[-1,0] = 1 s = State(psi0, graph.n, is_ket=True) greedy = GreedyNoise(graph, rate = pump_rate) heisenberg = HamiltonianHeisenberg(graph, k=100) sw = StochasticWavefunction(hamiltonians=[heisenberg, greedy], jumps=[greedy]) quantum_outputs = np.zeros((trials, np.arange(0, tf, dt).shape[0], psi0.shape[0]), dtype=np.complex128) classical_outputs = np.zeros((trials, np.arange(0, tf, dt).shape[0], graph.n), dtype=np.complex128) for k in range(trials): quantum_output = sw.run(s.state.copy(), 0, tf, dt) quantum_outputs[k,...] = np.squeeze(quantum_output, axis=-1) classical_output = graph.run(0, tf, dt, rates=[1, pump_rate], config=np.array([1,0,0,1,0])) classical_outputs[k,...] = np.squeeze(classical_output, axis=-1) mis_size = i//2+1 # Assume the amount in the ground codes is very small quantum_overlap_mis = np.abs(quantum_outputs@mis_state())**2 quantum_spin = np.abs(quantum_outputs)**2 @ spin_hamiltonian()/mis_size classical_spin = np.sum(classical_outputs, axis=-1) / mis_size classical_overlap_mis = classical_spin.copy() classical_overlap_mis[classical_overlap_mis==1] =1 classical_overlap_mis[classical_overlap_mis!=1] =0 plt.plot(times, np.mean(np.squeeze(quantum_overlap_mis, axis=-1), axis=0), label='Quantum MIS overlap, n='+str(i)) plt.plot(times, np.mean(np.squeeze(quantum_spin, axis=-1), axis=0), label='Quantum AR, n='+str(i)) plt.plot(times, np.mean(classical_overlap_mis, axis=0), label='Classical MIS overlap, n='+str(i)) plt.plot(times, np.mean(classical_spin, axis=0), label='Classical AR, n='+str(i)) plt.legend(loc='upper left') plt.show()
def dephasing_performance(): # adiabatic = SimulateAdiabatic(hamiltonian=[laser], noise = [dephasing, dissipation], noise_model='continuous', # graph=graph, IS_subspace=True, cost_hamiltonian=rydberg_hamiltonian_cost) # def schedule(t, tf): # phi = (tf-t)/tf*np.pi/2 # laser.omega_g = np.cos(phi) # laser.omega_r = np.sin(phi) # dissipation.omega_g = np.cos(phi) # dissipation.omega_r = np.sin(phi) # adiabatic.performance_vs_total_time(np.arange(5, 100, 5), schedule=schedule, verbose=True, plot=True, method='odeint') dephasing_rates = 10.**(np.arange(-3, 0, .2)) performance_3 = [] performance_5 = [] for j in [3, 5]: graph = line_graph(n=j) phi = .02 laser = EffectiveOperatorHamiltonian(omega_g=np.cos(phi), omega_r=np.sin(phi), energies=(1, ), graph=graph) dissipation = EffectiveOperatorDissipation(omega_g=np.cos(phi), omega_r=np.sin(phi), rates=(1, ), graph=graph) dephasing = lindblad_operators.LindbladPauliOperator(pauli='Z', IS_subspace=True, graph=graph, rates=(.1, )) rydberg_hamiltonian_cost = hamiltonian.HamiltonianMIS(graph, IS_subspace=True, code=qubit) eq = LindbladMasterEquation(hamiltonians=[laser], jump_operators=[dissipation, dephasing]) for i in range(len(dephasing_rates)): dissipation.rates = (1, ) laser.energies = (1, ) dephasing.rates = (dephasing_rates[i], ) state = np.zeros(dissipation.nh_hamiltonian.shape) state[-1, -1] = 1 state = State(state, is_ket=False, graph=graph, IS_subspace=True) ss = eq.steady_state(state) ss = ss[1][0] state = State(ss, is_ket=False, graph=graph, IS_subspace=True) print(rydberg_hamiltonian_cost.optimum_overlap(state)) if j == 3: performance_3.append( rydberg_hamiltonian_cost.optimum_overlap(state)) else: performance_5.append( rydberg_hamiltonian_cost.optimum_overlap(state)) plt.scatter(dephasing_rates, performance_3, color='teal', label=r'$n=3$ line graph') plt.scatter(dephasing_rates, performance_5, color='purple', label=r'$n=5$ line graph') plt.ylabel(r'log(-log(optimum overlap))') plt.xlabel(r'$\log(\gamma\Omega^2/(\delta^2\Gamma_{\rm{dephasing}}))$') plt.legend() plt.show()
def matvec_disorder(x): if len(x.shape) == 2: return matvec_heisenberg(Heis, disorder, State(x)) else: return matvec_heisenberg(Heis, disorder, State(np.expand_dims(x, axis=-1)))
def f(flattened): s = State(flattened.reshape(state_shape)) res = P @ self.evolution_generator(P @ s @ P) @ P + Q @ self.evolution_generator(Q @ s @ P) @ P + \ P @ self.evolution_generator(P @ s @ Q) @ Q return res.reshape(flattened.shape)
def left_multiply(state: State, apply_to: Union[int, list], op): """ Apply a multi-qubit operator on several qubits (indexed in apply_to) of the input codes. :param state: input wavefunction or density matrix :type state: np.ndarray :param apply_to: zero-based indices of qudit locations to apply the operator :type apply_to: list of int :param op: Operator to act with. :type op: np.ndarray (2-dimensional) """ # Handle typing if isinstance(apply_to, int): apply_to = [apply_to] pauli = False if isinstance(op, list): if all(isinstance(elem, str) for elem in op): pauli = True else: op = tensor_product(op) n_op = len(apply_to) if not pauli: if is_sorted(apply_to): # Generate all shapes for left multiplication preshape = d * np.ones((2, n_op), dtype=int) preshape[1, 0] = int(state.dimension / (d**(1 + apply_to[n_op - 1]))) if n_op > 1: preshape[1, 1:] = np.flip(d**np.diff(apply_to)) / 2 shape1 = np.zeros(2 * n_op + 1, dtype=int) shape2 = np.zeros(2 * n_op + 1, dtype=int) order1 = np.zeros(2 * n_op + 1, dtype=int) order2 = np.zeros(2 * n_op + 1, dtype=int) shape1[:-1] = np.flip(preshape, axis=0).reshape((2 * n_op), order='F') shape1[-1] = -1 shape2[:-1] = preshape.reshape((-1), order='C') shape2[-1] = -1 preorder = np.arange(2 * n_op) order1[:-1] = np.flip(preorder.reshape((-1, 2), order='C'), axis=1).reshape((-1), order='F') order2[:-1] = np.flip(preorder.reshape((2, -1), order='C'), axis=0).reshape((-1), order='F') order1[-1] = 2 * n_op order2[-1] = 2 * n_op # Now left multiply out = state.reshape(shape1, order='F').transpose(order1) out = np.dot(op, out.reshape((d**n_op, -1), order='F')) out = out.reshape(shape2, order='F').transpose(order2) out = out.reshape(state.shape, order='F') return State(out, is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code) else: # Need to reshape the operator given apply_to = np.asarray(apply_to, dtype=int) new_shape = d * np.ones(2 * n_op, dtype=int) permut = np.argsort(apply_to) transpose_ord = np.zeros(2 * n_op, dtype=int) transpose_ord[:n_op] = (n_op - 1) * np.ones( n_op, dtype=int) - np.flip(permut, axis=0) transpose_ord[n_op:] = (2 * n_op - 1) * np.ones( n_op, dtype=int) - np.flip(permut, axis=0) sorted_op = np.reshape(np.transpose(np.reshape(op, new_shape, order='F'), axes=transpose_ord), (d**n_op, d**n_op), order='F') sorted_apply_to = apply_to[permut] return left_multiply(state, sorted_apply_to, sorted_op) else: # op should be a list of Pauli operators out = state.copy() for i in range(len(apply_to)): ind = d**apply_to[i] if state.is_ket: # Note index start from the right (sN,...,s3,s2,s1) out = out.reshape((-1, d, ind), order='F') if op[i] == 'X': # Sigma_X out = np.flip(out, 1) elif op[i] == 'Y': # Sigma_Y out = np.flip(out, 1) out[:, 0, :] = -1j * out[:, 0, :] out[:, d - 1, :] = 1j * out[:, d - 1, :] elif op[i] == 'Z': # Sigma_Z out[:, d - 1, :] = -out[:, d - 1, :] out = out.reshape(state.shape, order='F') else: out = out.reshape( (-1, d, d**(state.number_physical_qudits - 1), d, ind), order='F') if op[i] == 'X': # Sigma_X out = np.flip(out, 1) elif op[i] == 'Y': # Sigma_Y out = np.flip(out, axis=1) out[:, 0, :, :, :] = -1j * out[:, 0, :, :, :] out[:, d - 1, :, :, :] = 1j * out[:, d - 1, :, :, :] elif op[i] == 'Z': # Sigma_Z out[:, d - 1, :, :, :] = -out[:, d - 1, :, :, :] out = out.reshape(state.shape, order='F') return State(out, is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code)
for i in range(layout.shape[0] - 1): for j in range(layout.shape[1] - 1): if layout[i, j] == 1: # There is a spin here pass #imperfect_blockade_performance() graph = line_graph(n=3, return_mis=False) phi = np.pi/2 laser = EffectiveOperatorHamiltonian(omega_g=np.cos(phi), omega_r=np.sin(phi), graph=graph) eq = SchrodingerEquation(hamiltonians=[laser]) state = State(np.ones((5, 1), dtype=np.complex128)/np.sqrt(5)) print(np.round(eq.eig(k='all')[1], decimals=3)) def performance_vs_alpha(): # alpha = gamma * omega^2/(delta^2 T) graph = line_graph(n=3) gammas = np.array([1, 5, 10]) times = np.array([1, 5, 10]) deltas = np.array([20, 30]) omegas = np.array([1, 5, 10]) for (g, d, o) in zip(gammas, deltas, omegas): # Find the performance vs alpha laser = EffectiveOperatorHamiltonian(graph=graph) dissipation = EffectiveOperatorDissipation(graph=graph) rydberg_hamiltonian_cost = hamiltonian.HamiltonianMIS(graph, IS_subspace=True, code=qubit) adiabatic = SimulateAdiabatic(hamiltonian=[laser], noise = [dissipation], noise_model='continuous',
def right_multiply(self, state: State): return State(state @ self.hamiltonian.T.conj(), is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code, graph=self.graph)
where_nonzero = np.argwhere(SDP_output != 0) SDP_output = np.zeros(2**8) SDP_output[where_nonzero] = 1 SDP_output = SDP_output * np.random.uniform(-100, 100, size=len(SDP_output)) SDP_output = SDP_output / np.linalg.norm(SDP_output) #SDP_output[23] = 1 np.set_printoptions(threshold=np.inf) print(SDP_output) #print(SDP_output) #SDP_output1 = generate_SDP_output(4, 20) #print(np.linalg.norm(SDP_output1-SDP_output2)) #print(len(SDP_output[np.nonzero(SDP_output)])) #print(SDP_output[rows, cols]) SDP_output = State(SDP_output[np.newaxis, :].T) graph = generate_SDP_graph(3, 1 / 3, visualize=False) cost = hamiltonian.HamiltonianMaxCut(graph, cost_function=True) driver = hamiltonian.HamiltonianDriver() qa = qaoa.SimulateQAOA(graph=graph, hamiltonian=[], cost_hamiltonian=cost) SDP_cf = cost.cost_function(SDP_output) / 8 print(SDP_output) print(SDP_cf) #plt.scatter(-1, cost.cost_function(SDP_output)) vanilla_cf = np.array([ 42.39230484541338, 48.26437904543421, 53.04760524625608, 58.034293475214454, 61.43733741238711, 62.1439072862153 ]) / 64 plt.scatter(np.arange(len(vanilla_cf)), 1 - vanilla_cf) #plt.semilogy()
def left_multiply(self, state: State): return State(self.energies[0] * self._csc_hamiltonian @ state, is_ket=state.is_ket, IS_subspace=state.IS_subspace, code=state.code, graph=self.graph)
def run_stochastic_wavefunction_solver(self, s, t0, tf, num=50, schedule=lambda t: None, times=None, full_output=True, method='trotterize', verbose=False, iterations=None): if iterations is None: iterations = 1 # Compute probability that we have a jump # For the stochastic solver, we have to return a times dictionary assert s.is_ket # Save state properties is_ket = s.is_ket code = s.code IS_subspace = s.IS_subspace state_shape = s.shape graph = s.graph num_jumps = [] jump_times = [] jump_indices = [] if times is None and (method == 'odeint' or method == 'trotterize'): times = np.linspace(0, 1, num=int(num)) * (tf - t0) + t0 if not (method == 'odeint' or method == 'trotterize'): raise NotImplementedError schrodinger_equation = SchrodingerEquation( hamiltonians=self.hamiltonians + self.jump_operators) def f(t, state): if method == 'odeint': t, state = state, t if method != 'odeint': state = np.reshape(np.expand_dims(state, axis=0), state_shape) state = State(state, is_ket=is_ket, code=code, IS_subspace=IS_subspace) return np.asarray( schrodinger_equation.evolution_generator(state)).flatten() assert len(times) > 1 if full_output: outputs = np.zeros( (iterations, len(times), s.shape[0], s.shape[1]), dtype=np.complex128) else: outputs = np.zeros((iterations, s.shape[0], s.shape[1]), dtype=np.complex128) dt = times[1] - times[0] for k in range(iterations): jump_time = [] jump_indices_iter = [] num_jump = 0 out = s.copy() if verbose: print('Iteration', k) for (j, time) in zip(range(times.shape[0]), times): # Update energies schedule(time) for i in range(len(self.jump_operators)): if i == 0: jumped_states, jump_probabilities = self.jump_operators[ i].jump_rate( out, list(range(out.number_physical_qudits))) jump_probabilities = jump_probabilities * dt elif i > 0: js, jp = self.jump_operators[i].jump_rate( out, list(range(out.number_physical_qudits))) jump_probabilities = np.concatenate( [jump_probabilities, jp * dt]) jumped_states = np.concatenate([jumped_states, js]) if len(self.jump_operators) == 0: jump_probability = 0 else: jump_probability = np.sum(jump_probabilities) if np.random.uniform() < jump_probability and len( self.jump_operators) != 0: # Then we should do a jump num_jump += 1 jump_time.append(time) if verbose: print('Jumped with probability', jump_probability, 'at time', time) jump_index = np.random.choice( list(range(len(jump_probabilities))), p=jump_probabilities / np.sum(jump_probabilities)) jump_indices_iter.append(jump_index) out = State(jumped_states[jump_index, ...] * np.sqrt(dt / jump_probabilities[jump_index]), is_ket=is_ket, code=code, IS_subspace=IS_subspace, graph=graph) # Normalization factor else: state_asarray = np.asarray(out) if method == 'odeint': z = odeintw(f, state_asarray, [0, dt], full_output=False) out = State(z[-1], code=code, IS_subspace=IS_subspace, is_ket=is_ket, graph=graph) else: for hamiltonian in self.hamiltonians: out = hamiltonian.evolve(out, dt) for jump_operator in self.jump_operators: if isinstance(jump_operator, LindbladJumpOperator): # Non-hermitian evolve out = jump_operator.nh_evolve(out, dt) elif isinstance(jump_operator, QuantumChannel): out = jump_operator.evolve(out, dt) out = State(out, code=code, IS_subspace=IS_subspace, is_ket=is_ket, graph=graph) # Normalize the output out = out / np.linalg.norm(out) # We don't do np.sqrt(1 - jump_probability) because it is only a first order expansion, # and is often inaccurate. Things will quickly diverge if the state is not normalized if full_output: outputs[k, j, ...] = out jump_times.append(jump_time) jump_indices.append(jump_indices_iter) num_jumps.append(num_jump) if not full_output: outputs[k, ...] = out return outputs, { 't': times, 'jump_times': jump_times, 'num_jumps': num_jumps, 'jump_indices': jump_indices }
def run(self, time, schedule, num=None, initial_state=None, full_output=True, method='RK45', verbose=False, iterations=None): if method == 'odeint' or method == 'trotterize' and num is None: num = self._num_from_time(time, method=method) if initial_state is None: # Begin with all qudits in the ground s initial_state = State(np.zeros((self.cost_hamiltonian.hamiltonian.shape[0], 1)), code=self.code, IS_subspace=self.IS_subspace, graph=self.graph) initial_state[-1, -1] = 1 if self.noise_model is not None and self.noise_model != 'monte_carlo': initial_state = State(outer_product(initial_state, initial_state), IS_subspace=self.IS_subspace, code=self.code, graph=self.graph) if self.noise_model == 'continuous': # Initialize master equation if method == 'trotterize': master_equation = LindbladMasterEquation(hamiltonians=self.hamiltonian, jump_operators=self.noise) results, info = master_equation.run_trotterized_solver(initial_state, 0, time, num=num, schedule=lambda t: schedule(t, time), full_output=full_output, verbose=verbose) else: master_equation = LindbladMasterEquation(hamiltonians=self.hamiltonian, jump_operators=self.noise) results, info = master_equation.run_ode_solver(initial_state, 0, time, num=num, schedule=lambda t: schedule(t, time), method=method, full_output=full_output, verbose=verbose) elif self.noise_model is None: # Noise model is None # Initialize Schrodinger equation schrodinger_equation = SchrodingerEquation(hamiltonians=self.hamiltonian) if method == 'trotterize': results, info = schrodinger_equation.run_trotterized_solver(initial_state, 0, time, num=num, verbose=verbose, full_output=full_output, schedule=lambda t: schedule(t, time)) else: results, info = schrodinger_equation.run_ode_solver(initial_state, 0, time, num=num, verbose=verbose, schedule=lambda t: schedule(t, time), method=method, full_output=full_output) else: assert self.noise_model == 'monte_carlo' # Initialize master equation master_equation = LindbladMasterEquation(hamiltonians=self.hamiltonian, jump_operators=self.noise) results, info = master_equation.run_stochastic_wavefunction_solver(initial_state, 0, time, num=num, full_output=full_output, schedule=lambda t: schedule(t, time), method=method, verbose=verbose, iterations=iterations) if len(results.shape) == 2: # The algorithm has output a single state out = [State(results, IS_subspace=self.IS_subspace, code=self.code, graph=self.graph)] elif len(results.shape) == 3: # The algorithm has output an array of states out = [State(res, IS_subspace=self.IS_subspace, code=self.code, graph=self.graph) for res in results] else: assert len(results.shape) == 4 if self.noise_model != 'monte_carlo': raise Exception('Run output has more dimensions than expected') out = [] for i in range(results.shape[0]): # For all iterations res = [] for j in range(results.shape[1]): # For all times res.append( State(results[i, j, ...], IS_subspace=self.IS_subspace, code=self.code, graph=self.graph)) out.append(res) return out, info
def steady_state(self, state: State, k=6, which='LR', use_initial_guess=False, plot=False, tol=1e-8, verbose=False): """Returns a list of the eigenvalues and the corresponding valid density matrix.""" assert not state.is_ket state_shape = state.shape def f(flattened): s = State(flattened.reshape(state_shape)) res = self.evolution_generator(s) return res.reshape(flattened.shape) state_flattened = state.flatten() lindbladian = LinearOperator(shape=(len(state_flattened), len(state_flattened)), dtype=np.complex128, matvec=f) if not use_initial_guess: v0 = None else: v0 = state_flattened try: eigvals, eigvecs = eigs(lindbladian, k=k, which=which, v0=v0) except ArpackNoConvergence as exception_info: eigvals = exception_info.eigenvalues eigvecs = exception_info.eigenvectors if eigvals.size != 0: # Do some basic reshaping to post process the results and select for only the steady states eigvecs = np.moveaxis(eigvecs, -1, 0) eigvecs = np.reshape( eigvecs, [eigvecs.shape[0], state_shape[0], state_shape[1]]) steady_state_indices = np.argwhere(eigvals.real > -1 * tol).T[0] steady_state_eigvecs = eigvecs[steady_state_indices, :, :] if verbose: print('Number of steady states is ', str(steady_state_eigvecs.shape[0])) # If there is one steady s, then normalize it because it is a valid density matrix if steady_state_eigvecs.shape[0] == 1: steady_state_eigvecs[0, :, :] = steady_state_eigvecs[ 0, :, :] / np.trace(steady_state_eigvecs[0, :, :]) if verbose: print( 'Steady state is a valid density matrix:', tools.is_valid_state(steady_state_eigvecs[0, :, :], verbose=False)) steady_state_eigvals = eigvals[steady_state_indices] if plot: steady_state_eigvals_cc = steady_state_eigvals.conj() steady_state_eigvals_real = np.concatenate( (steady_state_eigvals.real, steady_state_eigvals.real)) steady_state_eigvals_complex = np.concatenate( (steady_state_eigvals_cc.imag, steady_state_eigvals_cc.imag)) plt.hlines(0, xmin=-1, xmax=.1) plt.vlines(0, ymin=-100, ymax=100) plt.scatter(steady_state_eigvals_real, steady_state_eigvals_complex, c='m', s=4) plt.show() return steady_state_eigvals, steady_state_eigvecs else: return None, None
def variational_grad(self, param, initial_state=None): """Calculate the objective function F and its gradient exactly Input: param = parameters of QAOA Output: (F, Fgrad) F = <HamC> for minimization Fgrad = gradient of F with respect to param """ # TODO: make this work for continuous noise models if self.noise_model == 'continuous': raise NotImplementedError( 'Variational gradient does not currently support continuous noise model' ) param = np.asarray(param) # Preallocate space for storing copies of wavefunction - necessary for efficient computation of analytic # gradient if self.code.logical_code and initial_state is None: initial_state = State(tensor_product([self.code.logical_basis[1]] * self.N), code=self.code) elif initial_state is None: initial_state = State(np.zeros( (self.cost_hamiltonian.hamiltonian.shape[0], 1)), code=self.code) initial_state[-1, -1] = 1 if not (self.noise_model is None or self.noise_model == 'monte_carlo'): # Initial s should be a density matrix initial_state = State(outer_product(initial_state, initial_state), code=self.code) psi = initial_state if initial_state.is_ket: memo = np.zeros([psi.shape[0], 2 * self.depth + 2], dtype=np.complex128) memo[:, 0] = np.squeeze(psi.T) tester = psi.copy() else: memo = np.zeros([psi.shape[0], psi.shape[0], self.depth + 1], dtype=np.complex128) memo[..., 0] = np.squeeze(outer_product(psi, psi)) tester = State(outer_product(psi, psi), code=self.code) # Evolving forward for j in range(self.depth): if initial_state.is_ket: tester = self.hamiltonian[j].evolve(tester, param[j]) memo[:, j + 1] = np.squeeze(tester.T) else: self.hamiltonian[j].evolve(tester, param[j]) # Goes through memo, evolves every density matrix in it, and adds one more in the j*m+i+1 position # corresponding to H_i*p s0_prenoise = memo[..., 0] for k in range(j + 1): s = State(memo[..., k], code=self.code) s = self.hamiltonian[j].evolve(s, param[j]) if k == 0: s0_prenoise = s.copy() if self.noise_model is not None: if not (self.noise[j] is None): s = self.noise[j].evolve(s, param[j]) memo[..., k] = s.copy() s0_prenoise = self.hamiltonian[j].left_multiply(s0_prenoise) if self.noise_model is not None: if not (self.noise[j] is None): s0_prenoise = self.noise[j].evolve( s0_prenoise, param[j]) memo[..., j + 1] = s0_prenoise.copy() # Multiply by cost_hamiltonian if initial_state.is_ket: memo[:, self.depth + 1] = self.cost_hamiltonian.hamiltonian @ memo[:, self.depth] s = State(np.array([memo[:, self.depth + 1]]).T, code=self.code) else: for k in range(self.depth + 1): s = memo[..., k] s = State(self.cost_hamiltonian.hamiltonian * s, code=self.code) memo[..., k] = s # Evolving backwards, if ket: if initial_state.is_ket: for k in range(self.depth): s = self.hamiltonian[self.depth - k - 1].evolve( s, -1 * param[self.depth - k - 1]) memo[:, self.depth + k + 2] = np.squeeze(s.T) # Evaluating objective function if initial_state.is_ket: F = np.real(np.vdot(memo[:, self.depth], memo[:, self.depth + 1])) else: F = np.real(np.trace(memo[..., 0])) # Evaluating gradient analytically Fgrad = np.zeros(self.depth) for r in range(self.depth): if initial_state.is_ket: s = State(np.array([memo[:, 2 * self.depth + 1 - r]]).T, code=self.code) s = self.hamiltonian[r].left_multiply(s) Fgrad[r] = -2 * np.imag(np.vdot(memo[:, r], np.squeeze(s.T))) else: Fgrad[r] = 2 * np.imag(np.trace(memo[..., r + 1])) return F, Fgrad