def Q(eq: SchrodingerEquation, dissipation): # Diagonalize Hamiltonian # Compute Q matrix elements eigval, eigvec = eq.eig() q = np.zeros((len(eigval), len(eigval))) jump_operators = np.array(dissipation.jump_operators) # For each pair of eigenvectors for pair in itertools.product(range(eigvec.shape[0]), repeat=2): # Compute the rate if pair[0] != pair[1]: eigvec1 = State(tools.outer_product(eigvec[pair[0]].T, eigvec[pair[0]].T), is_ket=False, IS_subspace=True, graph=graph) eigvec2 = State(tools.outer_product(eigvec[pair[1]].T, eigvec[pair[1]].T), is_ket=False, IS_subspace=True, graph=graph) rates = np.sum([ np.trace(eigvec2 @ jump_operators[i] @ eigvec1 @ jump_operators[i].conj().T) for i in range(len(jump_operators)) ]) q[pair[1], pair[0]] = rates.real**2 q[pair[0], pair[0]] = q[pair[0], pair[0]] - rates.real**2 return q
def run(self, param, initial_state=None): 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: if isinstance(self.cost_hamiltonian, HamiltonianMIS): initial_state = State(np.zeros( (self.cost_hamiltonian.hamiltonian.shape[0], 1)), code=self.code) initial_state[-1, -1] = 1 else: initial_state = State( np.ones((self.cost_hamiltonian.hamiltonian.shape[0], 1)) / np.sqrt(self.cost_hamiltonian.hamiltonian.shape[0]), code=self.code) 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) s = initial_state for j in range(self.depth): s = self.hamiltonian[j].evolve(s, param[j]) if self.noise_model is not None: if self.noise[j] is not None: s = self.noise[j].evolve(s, param[j]) # Return the expected value of the cost function # Note that the codes's defined expectation function won't work here due to the shape of C return self.cost_hamiltonian.cost_function(s)
def k_beta(delta_r_bar): dt = 0.001 overlap = 0 for time in [0, 1-2*dt]: def normalize_phase(eig): where_nonzero = np.argwhere(np.absolute(eig) > 1e-9)[0] eig = np.e**(-1j*np.angle(eig[where_nonzero[0], where_nonzero[1]])) * eig # We can take the eigenvalues to be real for this Hamiltonian eig = eig.real return eig / np.linalg.norm(eig) schedule(time, 1, delta_r_bar=delta_r_bar) # Construct the first order transition matrix ground_energy, ground_state = SchrodingerEquation(hamiltonians=eq.hamiltonians).ground_state() ground_state = normalize_phase(ground_state) ham = scipy.sparse.csr_matrix((-ground_energy * np.ones(graph.num_independent_sets), (range(graph.num_independent_sets), range(graph.num_independent_sets))), shape=(graph.num_independent_sets, graph.num_independent_sets)) for h in eq.hamiltonians: ham = ham + h.hamiltonian schedule(time+dt, 1, delta_r_bar=delta_r_bar) # Construct the first order transition matrix ground_energy_dt, ground_state_dt = SchrodingerEquation(hamiltonians=eq.hamiltonians).ground_state() ground_state_dt = normalize_phase(ground_state_dt) d_ground_state_dt = (ground_state_dt - ground_state) / dt # Construct a projector out of the ground subspace proj = np.identity(graph.num_independent_sets) proj = proj - tools.outer_product(ground_state, ground_state) if graph.degeneracy>1 and time == 0: # Project out of the ground subspace energies, states = SchrodingerEquation(hamiltonians=eq.hamiltonians).eig(k=graph.degeneracy) for i in range(graph.degeneracy-1): proj = proj - tools.outer_product(states[i+1, np.newaxis].T, states[i+1, np.newaxis].T) d_ground_state_dt = proj @ d_ground_state_dt # Multiply by 1/(H-E_0) # If at the end, remove the rows corresponding to degeneracies if time != 0: d_ground_state_dt = d_ground_state_dt[graph.degeneracy:] ham = ham[graph.degeneracy:, graph.degeneracy:] else: d_ground_state_dt = d_ground_state_dt[:-1] ham = ham[:-1, :-1] res = scipy.sparse.linalg.spsolve(ham, d_ground_state_dt).real overlap += np.linalg.norm(res)**2 return overlap
def channel(self, state: State, p: tuple, apply_to: Union[int, list] = None): """ Perform general Pauli channel on the i-th qubit of an input density matrix Input: rho = input density matrix (as numpy.ndarray) i = zero-based index of qubit location to apply pauli ps = a 3-tuple (px, py, pz) indicating the probability of applying each Pauli operator Returns: (1-px-py-pz) * rho + px * Xi * rho * Xi + py * Yi * rho * Yi + pz * Zi * rho * Zi """ try: assert len(p) == 3 except AssertionError: print('Length of tuple must be 3.') # If the input s is a ket, convert it to a density matrix if state.is_ket: print('Converting ket to density matrix.') state = State(tools.outer_product(state, state)) if apply_to is None: apply_to = list(range(state.number_physical_qudits)) # Assume that apply_to is a list of integers if isinstance(apply_to, int): apply_to = [apply_to] if state.code.logical_code: # Assume that logical codes are composed of qubits code = self.code else: code = state.code # Handle apply_to recursively # Only apply to one qudit if len(apply_to) == 1: return state * (1 - sum(p)) + ( p[0] * code.multiply(state, apply_to, ['X']) + p[1] * code.multiply(state, apply_to, ['Y']) + p[2] * code.multiply(state, apply_to, ['Z'])) else: last_element = apply_to.pop() recursive_solution = self.channel(state, p, apply_to=apply_to) return recursive_solution * (1 - sum(p)) + p[0] * code.multiply(recursive_solution, [last_element], ['X']) \ + p[1] * code.multiply(recursive_solution, [last_element], ['Y']) + p[2] * \ code.multiply(recursive_solution, [last_element], ['Z'])
def channel(self, state: State, p: float, apply_to: Union[int, list] = None): """ Perform depolarizing channel on the i-th qubit of an input density matrix Input: rho = input density matrix (as numpy.ndarray) i = zero-based index of qubit location to apply pauli p = probability of depolarization, between 0 and 1 """ # If the input s is a ket, convert it to a density matrix if state.is_ket: print('Converting ket to density matrix.') state = State(tools.outer_product(state, state)) if apply_to is None: apply_to = list(range(state.number_physical_qudits)) # Assume that apply_to is a list of integers if isinstance(apply_to, int): apply_to = [apply_to] if state.code.logical_code: # Assume that logical codes are composed of qubits code = self.code else: code = state.code # Handle apply_to recursively # Only apply to one qudit if len(apply_to) == 1: return state * ( 1 - p) + p / 3 * (code.multiply(state, apply_to, ['X']) + code.multiply(state, apply_to, ['Y']) + code.multiply(state, apply_to, ['Z'])) else: last_element = apply_to.pop() recursive_solution = self.channel(state, p, apply_to=apply_to) return recursive_solution * (1 - p) + p / 3 * ( code.multiply(recursive_solution, [last_element], ['X']) + code.multiply(recursive_solution, [last_element], ['Y']) + code.multiply(recursive_solution, [last_element], ['Z']))
def evolve(self, state: State, time, threshold=.05, apply_to: Union[int, list] = None): if state.is_ket: print('Converting ket to density matrix.') state = State(tools.outer_product(state, state)) if apply_to is None: apply_to = list(range(state.number_physical_qudits)) # Assume that apply_to is a list of integers if isinstance(apply_to, int): apply_to = [apply_to] n = 1 # Find a number of repetitions n small enough so that channel evolution is well approximated while (self.rates[0] * time)**2 / n > threshold: n += 1 p = self.rates[0] * time / n s = state.copy() # Apply channel n times for i in range(n): s = self.channel(s, p, apply_to=apply_to) return s
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: if isinstance(self.cost_hamiltonian, HamiltonianMIS): initial_state = State(tensor_product( [self.code.logical_basis[1]] * self.N), code=self.code) elif initial_state is None: if isinstance(self.cost_hamiltonian, HamiltonianMIS): initial_state = State(np.zeros( (self.cost_hamiltonian.hamiltonian.shape[0], 1)), code=self.code) initial_state[-1, -1] = 1 else: initial_state = State( np.ones((self.cost_hamiltonian.hamiltonian.shape[0], 1)) / np.sqrt(self.cost_hamiltonian.hamiltonian.shape[0]), code=self.code) 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
def channel(self, state: State, p: float, apply_to: Union[int, list] = None): """ Applies ``povm`` homogeneously to the qudits identified in apply_to. :param p: :param apply_to: :param state: State to operate on. :type state: np.ndarray :return: """ # If the input s is a ket, convert it to a density matrix if state.is_ket: print('Converting ket to density matrix.') state = State(tools.outer_product(state, state)) if apply_to is None: apply_to = list(range(state.number_physical_qudits)) # Assume that apply_to is a list of integers if isinstance(apply_to, int): apply_to = [apply_to] if state.code.logical_code: # Assume that logical codes are composed of qubits code = self.code else: code = state.code if self.IS_subspace: povm = self.povm(p) temp = state.copy() out = None for i in apply_to: out = State(np.zeros_like(state), is_ket=state.is_ket, code=state.code, IS_subspace=state.IS_subspace, graph=state.graph) for j in range(len(povm[i])): out = out + povm[i][j] @ temp @ povm[i][j].conj().T temp = out return out # Handle apply_to recursively # Only apply to one qudit else: # Empty s to store the output out = State(np.zeros_like(state), is_ket=state.is_ket, code=state.code, IS_subspace=state.IS_subspace, graph=state.graph) if len(apply_to) == 1: povm = self.povm(p) for j in range(len(povm)): out = out + code.multiply(state, apply_to, povm[j]) return out else: last_element = apply_to.pop() recursive_solution = self.channel(state, p, apply_to=apply_to) povm = self.povm(p) for j in range(len(povm)): out = out + code.multiply(recursive_solution, [last_element], povm[j]) return out
def k_beta(delta_r_bar, graph: Graph, verbose=False, mode='hybrid'): def schedule_hybrid(t, tf, delta_r_bar=0): phi = (tf - t) / tf * np.pi / 2 energy_shift.energies = (delta_r_bar * np.sin(phi) ** 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) def schedule_adiabatic(t, tf, delta_r_bar=0): phi = (tf - t) / tf * np.pi / 2 energy_shift.energies = ((delta_r_bar+1) * np.sin(phi) ** 2-np.cos(phi) ** 2,) laser.omega_g = np.sqrt(np.abs(np.cos(phi)*np.sin(phi))) laser.omega_r = np.sqrt(np.abs(np.cos(phi)*np.sin(phi))) dissipation.omega_g = np.sqrt(np.abs(np.cos(phi)*np.sin(phi))) dissipation.omega_r = np.sqrt(np.abs(np.cos(phi)*np.sin(phi))) if mode == 'hybrid': schedule = schedule_hybrid elif mode=='adiabatic': schedule = schedule_adiabatic laser = EffectiveOperatorHamiltonian(graph=graph, IS_subspace=True, energies=(1,), omega_g=1, omega_r=1) energy_shift = hamiltonian.HamiltonianEnergyShift(IS_subspace=True, graph=graph, index=0) dissipation = EffectiveOperatorDissipation(graph=graph, omega_r=1, omega_g=1, rates=(1,)) eq = LindbladMasterEquation(hamiltonians=[laser, energy_shift], jump_operators=[dissipation]) dt = 0.001 overlap = 0 for time in [0, 1-2*dt]: def normalize_phase(eig): where_nonzero = np.argwhere(np.absolute(eig) > 1e-9)[0] eig = np.e**(-1j*np.angle(eig[where_nonzero[0], where_nonzero[1]])) * eig # We can take the eigenvalues to be real for this Hamiltonian eig = eig.real return eig / np.linalg.norm(eig) schedule(time, 1, delta_r_bar=delta_r_bar) # Construct the first order transition matrix ground_energy, ground_state = SchrodingerEquation(hamiltonians=eq.hamiltonians).ground_state() ground_state = normalize_phase(ground_state) ham = scipy.sparse.csr_matrix((-ground_energy * np.ones(graph.num_independent_sets), (range(graph.num_independent_sets), range(graph.num_independent_sets))), shape=(graph.num_independent_sets, graph.num_independent_sets)) for h in eq.hamiltonians: ham = ham + h.hamiltonian schedule(time+dt, 1, delta_r_bar=delta_r_bar) # Construct the first order transition matrix ground_energy_dt, ground_state_dt = SchrodingerEquation(hamiltonians=eq.hamiltonians).ground_state() ground_state_dt = normalize_phase(ground_state_dt) d_ground_state_dt = (ground_state_dt - ground_state) / dt # Construct a projector out of the ground subspace proj = np.identity(graph.num_independent_sets) proj = proj - tools.outer_product(ground_state, ground_state) if graph.degeneracy>1 and time == 0: # Project out of the ground subspace energies, states = SchrodingerEquation(hamiltonians=eq.hamiltonians).eig(k=graph.degeneracy) for i in range(graph.degeneracy-1): proj = proj - tools.outer_product(states[i+1, np.newaxis].T, states[i+1, np.newaxis].T) d_ground_state_dt = proj @ d_ground_state_dt # Multiply by 1/(H-E_0) # If at the end, remove the rows corresponding to degeneracies if time != 0: d_ground_state_dt = d_ground_state_dt[graph.degeneracy:] ham = ham[graph.degeneracy:, graph.degeneracy:] else: d_ground_state_dt = d_ground_state_dt[:-1] ham = ham[:-1, :-1] res = scipy.sparse.linalg.spsolve(ham, d_ground_state_dt).real overlap += np.linalg.norm(res)**2 return overlap
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
from scipy.linalg import expm from qsim.codes.quantum_state import State from typing import Union from qsim.tools.tools import int_to_nary __all__ = ['multiply', 'right_multiply', 'left_multiply', 'rotation'] logical_code = False # Define Pauli matrices X = X() Y = Y() Z = Z() n = 1 d = 2 logical_basis = np.array([[[1], [0]], [[0], [1]]]).astype(np.complex128) Q = outer_product(logical_basis[0], logical_basis[0]) P = outer_product(logical_basis[1], logical_basis[1]) code_space_projector = outer_product(logical_basis[0], logical_basis[0]) + outer_product( logical_basis[1], logical_basis[1]) def rotation(state: State, apply_to: Union[int, list], angle: float, op, is_involutary=False, is_idempotent=False): """ Apply a single qubit rotation :math:`e^{-i \\alpha A}` to the input ``codes``.
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( np.all(