def trotter_step(self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: n_qubits = len(qubits) if not isinstance(control_qubit, cirq.Qid): raise TypeError('Control qudit must be specified.') # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot111(-2 * self.hamiltonian.two_body[p, q] * time).on( cast(cirq.Qid, control_qubit), a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for the full time yield (rot11(rads=-self.orbital_energies[i] * time).on( control_qubit, qubits[i]) for i in range(n_qubits)) # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Apply phase from constant term yield cirq.rz(rads=-self.hamiltonian.constant * time).on(control_qubit)
def test_bogoliubov_transform_quadratic_hamiltonian_inverse_is_dagger( n_qubits, real, particle_conserving, atol): quad_ham = random_quadratic_hamiltonian( n_qubits, real=real, conserves_particle_number=particle_conserving, seed=46533) _, transformation_matrix, _ = quad_ham.diagonalizing_bogoliubov_transform() qubits = cirq.LineQubit.range(n_qubits) if transformation_matrix.shape == (n_qubits, n_qubits): daggered_transformation_matrix = transformation_matrix.T.conj() else: left_block = transformation_matrix[:, :n_qubits] right_block = transformation_matrix[:, n_qubits:] daggered_transformation_matrix = numpy.block( [left_block.T.conj(), right_block.T]) circuit1 = cirq.Circuit( cirq.inverse(bogoliubov_transform(qubits, transformation_matrix))) circuit2 = cirq.Circuit( bogoliubov_transform(qubits, daggered_transformation_matrix)) cirq.testing.assert_allclose_up_to_global_phase(circuit1.unitary(), circuit2.unitary(), atol=atol)
def trotter_step( self, qubits: Sequence[cirq.QubitId], time: float, control_qubit: Optional[cirq.QubitId] = None) -> cirq.OP_TREE: n_qubits = len(qubits) # Simulate the one-body terms for half of the full time yield (cirq.Rz(rads=-0.5 * self.orbital_energies[i] * time).on( qubits[i]) for i in range(n_qubits)) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot11(rads=-2 * self.hamiltonian.two_body[p, q] * time).on( a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate back to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for half of the full time yield (cirq.Rz(rads=-0.5 * self.orbital_energies[i] * time).on( qubits[i]) for i in range(n_qubits))
def trotter_step( self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: if not isinstance(control_qubit, cirq.Qid): raise NotImplementedError('Control qudit must be specified.') n_qubits = len(qubits) # Change to the basis in which the one-body term is diagonal yield bogoliubov_transform( qubits, self.one_body_basis_change_matrix.T.conj()) # Simulate the one-body terms. for p in range(n_qubits): yield rot11(rads= -self.one_body_energies[p] * time ).on(control_qubit, qubits[p]) # Simulate each singular vector of the two-body terms. prior_basis_matrix = self.one_body_basis_change_matrix for j in range(len(self.eigenvalues)): # Get the two-body coefficients and basis change matrix. two_body_coefficients = self.scaled_density_density_matrices[j] basis_change_matrix = self.basis_change_matrices[j] # Merge previous basis change matrix with the inverse of the # current one merged_basis_change_matrix = numpy.dot(prior_basis_matrix, basis_change_matrix.T.conj()) yield bogoliubov_transform(qubits, merged_basis_change_matrix) # Simulate the off-diagonal two-body terms. yield swap_network( qubits, lambda p, q, a, b: rot111( -2 * two_body_coefficients[p, q] * time).on( control_qubit, a, b)) qubits = qubits[::-1] # Simulate the diagonal two-body terms. yield (rot11(rads= -two_body_coefficients[k, k] * time).on( control_qubit, qubits[k]) for k in range(n_qubits)) # Update prior basis change matrix. prior_basis_matrix = basis_change_matrix # Undo final basis transformation. yield bogoliubov_transform(qubits, prior_basis_matrix) # Apply phase from constant term yield cirq.rz(rads= -self.hamiltonian.constant * time).on(control_qubit)
def test_bogoliubov_transform_quadratic_hamiltonian(n_qubits, conserves_particle_number, atol=5e-5): qubits = LineQubit.range(n_qubits) # Initialize a random quadratic Hamiltonian quad_ham = random_quadratic_hamiltonian(n_qubits, conserves_particle_number, real=False) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the orbital energies and circuit orbital_energies, constant = quad_ham.orbital_energies() transformation_matrix = quad_ham.diagonalizing_bogoliubov_transform() circuit = cirq.Circuit.from_ops( bogoliubov_transform(qubits, transformation_matrix)) # Pick some random eigenstates to prepare, which correspond to random # subsets of [0 ... n_qubits - 1] n_eigenstates = min(2**n_qubits, 5) subsets = [ numpy.random.choice(range(n_qubits), numpy.random.randint(1, n_qubits + 1), False) for _ in range(n_eigenstates) ] # Also test empty subset subsets += [()] for occupied_orbitals in subsets: # Compute the energy of this eigenstate energy = (sum(orbital_energies[i] for i in occupied_orbitals) + constant) # Construct initial state initial_state = sum(2**(n_qubits - 1 - int(i)) for i in occupied_orbitals) # Get the state using a circuit simulation state1 = circuit.apply_unitary_effect_to_state(initial_state) # Also test the option to start with a computational basis state special_circuit = cirq.Circuit.from_ops( bogoliubov_transform(qubits, transformation_matrix, initial_state=initial_state)) state2 = special_circuit.apply_unitary_effect_to_state( initial_state, qubits_that_should_be_present=qubits) # Check that the result is an eigenstate with the correct eigenvalue numpy.testing.assert_allclose(quad_ham_sparse.dot(state1), energy * state1, atol=atol) numpy.testing.assert_allclose(quad_ham_sparse.dot(state2), energy * state2, atol=atol)
def test_bogoliubov_transform_fourier_transform_inverse_is_dagger( n_qubits, atol): u = fourier_transform_matrix(n_qubits) qubits = cirq.LineQubit.range(n_qubits) circuit1 = cirq.Circuit(cirq.inverse(bogoliubov_transform(qubits, u))) circuit2 = cirq.Circuit(bogoliubov_transform(qubits, u.T.conj())) cirq.testing.assert_allclose_up_to_global_phase( circuit1.to_unitary_matrix(), circuit2.to_unitary_matrix(), atol=atol)
def operations(self, qubits: Sequence[cirq.Qid]) -> cirq.OP_TREE: """Produce the operations of the ansatz circuit.""" n_qubits = len(qubits) param_set = set(self.params()) for i in range(self.iterations): # Change to the basis in which the one-body term is diagonal yield bogoliubov_transform( qubits, self.one_body_basis_change_matrix.T.conj()) # Simulate the one-body terms. for p in range(n_qubits): u_symbol = LetterWithSubscripts('U', p, i) if u_symbol in param_set: yield cirq.ZPowGate(exponent=u_symbol).on(qubits[p]) # Simulate each singular vector of the two-body terms. prior_basis_matrix = self.one_body_basis_change_matrix for j in range(len(self.eigenvalues)): # Get the basis change matrix. basis_change_matrix = self.basis_change_matrices[j] # Merge previous basis change matrix with the inverse of the # current one merged_basis_change_matrix = numpy.dot( prior_basis_matrix, basis_change_matrix.T.conj()) yield bogoliubov_transform(qubits, merged_basis_change_matrix) # Simulate the off-diagonal two-body terms. def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: v_symbol = LetterWithSubscripts('V', p, q, j, i) if v_symbol in param_set: yield cirq.CZPowGate(exponent=v_symbol).on(a, b) yield swap_network(qubits, two_body_interaction) qubits = qubits[::-1] # Simulate the diagonal two-body terms. for p in range(n_qubits): u_symbol = LetterWithSubscripts('U', p, j, i) if u_symbol in param_set: yield cirq.ZPowGate(exponent=u_symbol).on(qubits[p]) # Update prior basis change matrix. prior_basis_matrix = basis_change_matrix # Undo final basis transformation. yield bogoliubov_transform(qubits, prior_basis_matrix)
def trotter_step( self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: n_qubits = len(qubits) # Change to the basis in which the one-body term is diagonal yield bogoliubov_transform( qubits, self.one_body_basis_change_matrix.T.conj()) # Simulate the one-body terms. for p in range(n_qubits): yield cirq.Rz(rads= -self.one_body_energies[p] * time ).on(qubits[p]) # Simulate each singular vector of the two-body terms. prior_basis_matrix = self.one_body_basis_change_matrix for j in range(len(self.eigenvalues)): # Get the two-body coefficients and basis change matrix. two_body_coefficients = self.scaled_density_density_matrices[j] basis_change_matrix = self.basis_change_matrices[j] # Merge previous basis change matrix with the inverse of the # current one merged_basis_change_matrix = numpy.dot(prior_basis_matrix, basis_change_matrix.T.conj()) yield bogoliubov_transform(qubits, merged_basis_change_matrix) # Simulate the off-diagonal two-body terms. yield swap_network( qubits, lambda p, q, a, b: rot11(rads= -2 * two_body_coefficients[p, q] * time).on(a, b)) qubits = qubits[::-1] # Simulate the diagonal two-body terms. for p in range(n_qubits): yield cirq.Rz(rads= -two_body_coefficients[p, p] * time ).on(qubits[p]) # Update prior basis change matrix prior_basis_matrix = basis_change_matrix # Undo final basis transformation yield bogoliubov_transform(qubits, prior_basis_matrix)
def test_bogoliubov_transform_compose(n_qubits, atol): u = random_unitary_matrix(n_qubits, seed=24964) v = random_unitary_matrix(n_qubits, seed=33656) qubits = cirq.LineQubit.range(n_qubits) circuit1 = cirq.Circuit(bogoliubov_transform(qubits, u), bogoliubov_transform(qubits, v)) circuit2 = cirq.Circuit(bogoliubov_transform(qubits, u.dot(v))) cirq.testing.assert_allclose_up_to_global_phase( circuit1.to_unitary_matrix(), circuit2.to_unitary_matrix(), atol=atol)
def finish(self, qubits: Sequence[cirq.QubitId], n_steps: int, control_qubit: Optional[cirq.QubitId] = None, omit_final_swaps: bool = False) -> cirq.OP_TREE: # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits)
def operations(self, qubits: Sequence[cirq.QubitId]) -> cirq.OP_TREE: """Produce the operations of the ansatz circuit.""" # TODO implement asymmetric ansatz # Change to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) for i in range(self.iterations): suffix = '-{}'.format(i) if self.iterations > 1 else '' # Simulate one-body terms yield (cirq.RotZGate(half_turns=self.params['U{}'.format(p) + suffix]).on(qubits[p]) for p in range(len(qubits)) if 'U{}'.format(p) + suffix in self.params) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Simulate the two-body terms def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: if 'V{}_{}'.format(p, q) + suffix in self.params: yield cirq.Rot11Gate( half_turns=self.params['V{}_{}'.format(p, q) + suffix]).on(a, b) yield swap_network(qubits, two_body_interaction) qubits = qubits[::-1] # Rotate back to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate one-body terms again yield (cirq.RotZGate(half_turns=self.params['U{}'.format(p) + suffix]).on(qubits[p]) for p in range(len(qubits)) if 'U{}'.format(p) + suffix in self.params) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix)
def operations(self, qubits: Sequence[cirq.QubitId]) -> cirq.OP_TREE: """Produce the operations of the ansatz circuit.""" # TODO implement asymmetric ansatz param_set = set(self.params()) # Change to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) for i in range(self.iterations): # Simulate one-body terms for p in range(len(qubits)): u_symbol = LetterWithSubscripts('U', p, i) if u_symbol in param_set: yield cirq.RotZGate(half_turns=u_symbol).on(qubits[p]) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Simulate the two-body terms def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: v_symbol = LetterWithSubscripts('V', p, q, i) if v_symbol in param_set: yield cirq.Rot11Gate(half_turns=v_symbol).on(a, b) yield swap_network(qubits, two_body_interaction) qubits = qubits[::-1] # Rotate back to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate one-body terms again for p in range(len(qubits)): u_symbol = LetterWithSubscripts('U', p, i) if u_symbol in param_set: yield cirq.RotZGate(half_turns=u_symbol).on(qubits[p]) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix)
def test_spin_symmetric_bogoliubov_transform( n_spatial_orbitals, conserves_particle_number, atol=5e-5): n_qubits = 2*n_spatial_orbitals qubits = LineQubit.range(n_qubits) # Initialize a random quadratic Hamiltonian quad_ham = random_quadratic_hamiltonian( n_spatial_orbitals, conserves_particle_number, real=True, expand_spin=True, seed=28166) # Reorder the Hamiltonian and get sparse matrix quad_ham = openfermion.get_quadratic_hamiltonian( openfermion.reorder( openfermion.get_fermion_operator(quad_ham), openfermion.up_then_down) ) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the orbital energies and transformation_matrix up_orbital_energies, _, _ = ( quad_ham.diagonalizing_bogoliubov_transform(spin_sector=0)) down_orbital_energies, _, _ = ( quad_ham.diagonalizing_bogoliubov_transform(spin_sector=1)) _, transformation_matrix, _ = ( quad_ham.diagonalizing_bogoliubov_transform()) # Pick some orbitals to occupy up_orbitals = list(range(2)) down_orbitals = [0, 2, 3] energy = sum(up_orbital_energies[up_orbitals]) + sum( down_orbital_energies[down_orbitals]) + quad_ham.constant # Construct initial state initial_state = ( sum(2**(n_qubits - 1 - int(i)) for i in up_orbitals) + sum(2**(n_qubits - 1 - int(i+n_spatial_orbitals)) for i in down_orbitals) ) # Apply the circuit circuit = cirq.Circuit.from_ops( bogoliubov_transform( qubits, transformation_matrix, initial_state=initial_state)) state = circuit.apply_unitary_effect_to_state(initial_state) # Check that the result is an eigenstate with the correct eigenvalue numpy.testing.assert_allclose( quad_ham_sparse.dot(state), energy * state, atol=atol)
def test_bogoliubov_transform_fourier_transform(transformation_matrix, initial_state, correct_state, atol=5e-6): n_qubits = transformation_matrix.shape[0] qubits = LineQubit.range(n_qubits) if isinstance(initial_state, Container): initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) circuit = cirq.Circuit.from_ops(bogoliubov_transform( qubits, transformation_matrix, initial_state=initial_state)) state = circuit.apply_unitary_effect_to_state(initial_state) cirq.testing.assert_allclose_up_to_global_phase( state, correct_state, atol=atol)
def _create_quadratic_hamiltonian_circuit( qubits: List[cirq.Qid], particles: int, hamiltonian: openfermion.QuadraticHamiltonian) -> cirq.Circuit: """Creates circuit that realizes network of givens rotation.""" circuit = cirq.Circuit() # Prepare ground state in diagonal basis and add initial circuits. circuit.append([cirq.X.on(q) for q in qubits[:particles]]) # Create Givens network circuit. _, transform, _ = hamiltonian.diagonalizing_bogoliubov_transform() circuit += cirq.Circuit( openfermioncirq.bogoliubov_transform(qubits, transform, range(particles))) return circuit
def test_ffft_equal_to_bogoliubov(size): def fourier_transform_matrix(): root_of_unity = np.exp(-2j * np.pi / size) return np.array([[root_of_unity**(j * k) for k in range(size)] for j in range(size)]) / np.sqrt(size) qubits = LineQubit.range(size) ffft_circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) ffft_matrix = ffft_circuit.unitary(qubits_that_should_be_present=qubits) bogoliubov_circuit = cirq.Circuit(bogoliubov_transform( qubits, fourier_transform_matrix()), strategy=cirq.InsertStrategy.EARLIEST) bogoliubov_matrix = bogoliubov_circuit.unitary( qubits_that_should_be_present=qubits) cirq.testing.assert_allclose_up_to_global_phase(ffft_matrix, bogoliubov_matrix, atol=1e-8)
def test_bogoliubov_transform_fourier_transform(transformation_matrix, initial_state, correct_state, atol=5e-6): simulator = cirq.google.XmonSimulator() n_qubits = transformation_matrix.shape[0] qubits = LineQubit.range(n_qubits) circuit = cirq.Circuit.from_ops( bogoliubov_transform(qubits, transformation_matrix, initial_state=initial_state)) if isinstance(initial_state, Container): initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) result = simulator.simulate(circuit, initial_state=initial_state) state = result.final_state cirq.testing.assert_allclose_up_to_global_phase(state, correct_state, atol=atol)
def prepare(self, qubits: Sequence[cirq.QubitId], control_qubits: Optional[cirq.QubitId] = None) -> cirq.OP_TREE: # Change to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix))
for q in range(n_qubits): term = ((p, 1), (q, 0)) H += openfermion.FermionOperator(term, T[p, q]) print("\nFermion operator:") print(H) # Diagonalize T and obtain basis transformation matrix (aka "u") eigenvalues, eigenvectors = numpy.linalg.eigh(T) basis_transformation_matrix = eigenvectors.transpose() # Initialize the qubit register qubits = cirq.LineQubit.range(n_qubits) # Rotate to the eigenbasis inverse_basis_rotation = cirq.inverse( openfermioncirq.bogoliubov_transform(qubits, basis_transformation_matrix) ) circuit = cirq.Circuit.from_ops(inverse_basis_rotation) # Add diagonal phase rotations to circuit for k, eigenvalue in enumerate(eigenvalues): phase = -eigenvalue * simulation_time circuit.append(cirq.Rz(rads=phase).on(qubits[k])) # Finally, change back to the computational basis basis_rotation = openfermioncirq.bogoliubov_transform( qubits, basis_transformation_matrix ) circuit.append(basis_rotation) # Initialize a random initial state
def test_bogoliubov_transform_bad_shape_raises_error(): with pytest.raises(ValueError): _ = next( bogoliubov_transform(cirq.LineQubit.range(4), numpy.zeros((4, 7))))
def trotter_step( self, qubits: Sequence[cirq.QubitId], time: float, control_qubit: Optional[cirq.QubitId]=None ) -> cirq.OP_TREE: n_qubits = len(qubits) # Simulate the off-diagonal one-body terms. yield swap_network( qubits, lambda p, q, a, b: ControlledXXYYGate(duration= self.one_body_coefficients[p, q].real * time).on( control_qubit, a, b), fermionic=True) qubits = qubits[::-1] # Simulate the diagonal one-body terms. yield (cirq.Rot11Gate(rads= -self.one_body_coefficients[j, j].real * time).on( control_qubit, qubits[j]) for j in range(n_qubits)) # Simulate each singular vector of the two-body terms. prior_basis_matrix = numpy.identity(n_qubits, complex) for j in range(len(self.eigenvalues)): # Get the basis change matrix and its inverse. two_body_coefficients = self.scaled_density_density_matrices[j] basis_change_matrix = self.basis_change_matrices[j] # If the basis change matrix is unitary, merge rotations. # Otherwise, you must simulate two basis rotations. is_unitary = cirq.is_unitary(basis_change_matrix) if is_unitary: inverse_basis_matrix = numpy.conjugate( numpy.transpose(basis_change_matrix)) merged_basis_matrix = numpy.dot(prior_basis_matrix, inverse_basis_matrix) yield bogoliubov_transform(qubits, merged_basis_matrix) else: # TODO add LiH test to cover this. # coverage: ignore if j: yield bogoliubov_transform(qubits, prior_basis_matrix) yield cirq.inverse( bogoliubov_transform(qubits, basis_change_matrix)) # Simulate the off-diagonal two-body terms. yield swap_network( qubits, lambda p, q, a, b: Rot111Gate(rads= -2 * two_body_coefficients[p, q] * time).on( control_qubit, a, b)) qubits = qubits[::-1] # Simulate the diagonal two-body terms. yield (cirq.Rot11Gate(rads= -two_body_coefficients[k, k] * time).on( control_qubit, qubits[k]) for k in range(n_qubits)) # Undo basis transformation non-unitary case. # Else, set basis change matrix to prior matrix for merging. if is_unitary: prior_basis_matrix = basis_change_matrix else: # TODO add LiH test to cover this. # coverage: ignore yield bogoliubov_transform(qubits, basis_change_matrix) prior_basis_matrix = numpy.identity(n_qubits, complex) # Undo final basis transformation in unitary case. if is_unitary: yield bogoliubov_transform(qubits, prior_basis_matrix) # Apply phase from constant term yield cirq.RotZGate(rads=-self.constant * time).on(control_qubit)