Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staged controlled time #100

Merged
merged 39 commits into from
Jan 3, 2022
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e16fcd4
added multi-controls multi-targets, extra gates
JamesB-1qbit Nov 16, 2021
3e76972
reconfigured check for control and target inputs
JamesB-1qbit Nov 26, 2021
0e380e8
improvements from PR comments
JamesB-1qbit Dec 2, 2021
506258c
Create CNAME
ValentinS4t1qbit Oct 2, 2021
b001805
Update CNAME
ValentinS4t1qbit Oct 2, 2021
9191d48
Delete CNAME
ValentinS4t1qbit Oct 2, 2021
71ac2ac
added all previous changes to new branch
JamesB-1qbit Oct 7, 2021
d0be81d
added qite and projective folder
JamesB-1qbit Oct 14, 2021
9a58c5d
separated statevector propagation, style improvements
JamesB-1qbit Oct 14, 2021
1ab2ce8
added decomposed versions of controlled swap
JamesB-1qbit Oct 19, 2021
31f90ee
added docstrings, more gates for qiskit,qulacs
JamesB-1qbit Oct 21, 2021
488bfea
added wheel to github testing
JamesB-1qbit Oct 21, 2021
6bed749
fixed errror in test
JamesB-1qbit Oct 22, 2021
ae21d1a
updated docstrings, added get_resources function
JamesB-1qbit Nov 8, 2021
b9a36f5
fixed spurious change in setup.py
JamesB-1qbit Nov 9, 2021
0f48be6
added qdk support, skipped long test
JamesB-1qbit Nov 9, 2021
9da6eaf
added xx gate
JamesB-1qbit Nov 9, 2021
0e706bc
reverted to pnumpy=1.7.6.post1 for testing purposes
JamesB-1qbit Nov 10, 2021
e5ea2b7
swap decomp for ionq now works, support for cirq backend xx gate
JamesB-1qbit Nov 10, 2021
bba3e76
updated imports, reconfigured qite
JamesB-1qbit Nov 23, 2021
4b7c043
fixed smissing blank line
JamesB-1qbit Nov 23, 2021
9f46abb
reverted spurious update
JamesB-1qbit Nov 24, 2021
18d2c4c
added option to not swap qft registers
JamesB-1qbit Nov 30, 2021
3f6cca8
added multi-controls multi-targets, extra gates
JamesB-1qbit Nov 16, 2021
5d2dccf
syntax fix, added support for inp.integer, fixes #67
JamesB-1qbit Nov 25, 2021
0987b03
reconfigured check for control and target inputs
JamesB-1qbit Nov 26, 2021
49e2309
added other controlled gates to cirq
JamesB-1qbit Dec 2, 2021
18f23bf
fixed rebase issues
JamesB-1qbit Dec 3, 2021
4e00a7d
docstring fixes, codestyle improvements
JamesB-1qbit Dec 17, 2021
694ed18
added license to top of __init__.py files
JamesB-1qbit Dec 17, 2021
289f96b
fixed import error
JamesB-1qbit Dec 17, 2021
129b08e
first run at PR comments
JamesB-1qbit Dec 22, 2021
4f430b6
reverted change that caused failed test, addressed more comments
JamesB-1qbit Dec 22, 2021
2eff3e9
futher improvements
JamesB-1qbit Dec 22, 2021
b3a230c
changed test install to qiskit==0.33.1, style improvement
JamesB-1qbit Dec 23, 2021
99dde01
converted time to float or dictionary input
JamesB-1qbit Dec 23, 2021
d9911f2
convert dict_items to list to be compatible with python 3.7
JamesB-1qbit Dec 23, 2021
8d94167
further PR changes
JamesB-1qbit Jan 1, 2022
78e9f3f
Merge branch 'staging-0.3.0' into staged-controlled-time
ValentinS4t1qbit Jan 3, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/github_actions_automated_testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

- name: Install backends except qsharp/qdk
run: |
pip install qiskit
pip install qiskit==0.33.1
Copy link
Collaborator

@ValentinS4t1qbit ValentinS4t1qbit Dec 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest leaving a short comment here so that users & developers can understand / remember the reason we froze a version number (qiskit moves a lot). Users will wonder why too, or have errors because we're not forcing them to install a particular version number.

A great example: I do not remember why we have a restriction on cirq below. Do you, @JamesB-1qbit @AlexandreF-1qbit ? If yes, leave a comment, or relax this with a recent cirq (e.g cirq >= ....). Even worse, cirq is installed automatically as a dependency of openfermion in setup.py so there's a fair chance people who install Tangelo are not even running in the same conditions as our tests suite, and therefore it means we can't rely on this test suite to guarantee things work for our users. Whatever we do, this needs to be consistent.

Ideal case: we realize everything works without this constraint on cirq version number.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a conversation that can happen independently of the PR if its actually non-trivial

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to a failed test due to the new version of Qiskit-aer. I opened an issue on the qiskit-aer GitHub to explain what is happening or revert to the old behaviour.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not aware for the cirq versioning. The new Qiskit-aer changed a bunch of things and we should look into it.

pip install qulacs
pip install amazon-braket-sdk
pip install cirq==0.12.0
Expand Down
13 changes: 13 additions & 0 deletions tangelo/algorithms/projective/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2021 Good Chemistry Company.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
JamesB-1qbit marked this conversation as resolved.
Show resolved Hide resolved
315 changes: 315 additions & 0 deletions tangelo/algorithms/projective/quantum_imaginary_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
# Copyright 2021 Good Chemistry Company.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module that defines the Quantum Imaginary Time Algorithm (QITE)
"""
from copy import copy
import math
JamesB-1qbit marked this conversation as resolved.
Show resolved Hide resolved

from openfermion import FermionOperator as ofFermionOperator
import numpy as np

from tangelo.toolboxes.ansatz_generator.ansatz_utils import trotterize
from tangelo.toolboxes.operators.operators import FermionOperator, QubitOperator
from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping
from tangelo.toolboxes.ansatz_generator._general_unitary_cc import uccgsd_generator as uccgsd_pool
from tangelo.toolboxes.operators import qubitop_to_qubitham
from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit
from tangelo.linq import Circuit, Simulator


class QITESolver:
"""QITE class. This is an iterative algorithm that obtains a unitary operator
that approximates the imaginary time evolution of an initial state.

Attributes:
molecule (SecondQuantizedMolecule): The molecular system.
dt (float): The imaginary time step size
min_de (float): Maximum energy change allowed before convergence.
max_cycles (int): Maximum number of iterations of QITE.
pool (func): Function that returns a list of FermionOperator. Each
element represents an excitation/operator that has an effect on the
total energy.
pool_args (tuple) : The arguments for the pool function given as a
tuple.
qubit_mapping (str): One of the supported qubit mapping identifiers.
qubit_hamiltonian (QubitOperator-like): Self-explanatory.
up_then_down (bool): Spin orbitals ordering.
n_spinorbitals (int): Self-explanatory.
n_electrons (int): Self-explanatory.
backend_options (dict): Backend options for the underlying QITE propagation
verbose (bool): Flag for verbosity of QITE.
"""

def __init__(self, opt_dict):

default_backend_options = {"target": None, "n_shots": None, "noise_model": None}
default_options = {"molecule": None,
"dt": 0.5, "max_cycles": 100,
"min_de": 1.e-7,
"pool": uccgsd_pool,
"pool_args": None,
"frozen_orbitals": "frozen_core",
"qubit_mapping": "jw",
"qubit_hamiltonian": None,
"up_then_down": False,
"n_spinorbitals": None,
"n_electrons": None,
"backend_options": default_backend_options,
"verbose": True}

# Initialize with default values
self.__dict__ = default_options
# Overwrite default values with user-provided ones, if they correspond to a valid keyword
for k, v in opt_dict.items():
if k in default_options:
setattr(self, k, v)
else:
raise KeyError(f"Keyword :: {k}, not available in {self.__class__.__name__}")

# Raise error/warnings if input is not as expected. Only a single input
# must be provided to avoid conflicts.
if not (bool(self.molecule) ^ bool(self.qubit_hamiltonian)):
raise ValueError("A molecule OR qubit Hamiltonian object must be provided when instantiating "
f"{self.__class__.__name__}.")

if self.qubit_hamiltonian:
if not (self.n_spinorbitals and self.n_electrons):
raise ValueError("Expecting the number of spin-orbitals (n_spinorbitals) and the number of "
"electrons (n_electrons) with a qubit_hamiltonian.")

self.iteration = 0
self.energies = list()

self.circuit_list = list()
self.final_energy = None
self.final_circuit = None
self.final_statevector = None

self.backend = None

def prepare_reference_state(self):
"""Returns circuit preparing the reference state of the ansatz (e.g
prepare reference wavefunction with HF, multi-reference state, etc).
These preparations must be consistent with the transform used to obtain
the qubit operator.
"""

return get_reference_circuit(n_spinorbitals=self.n_spinorbitals,
n_electrons=self.n_electrons,
mapping=self.qubit_mapping,
up_then_down=self.up_then_down,
spin=0)

def build(self):
"""Builds the underlying objects required to run the QITE algorithm."""

# Building molecule data with a pyscf molecule.
if self.molecule:

self.n_spinorbitals = self.molecule.n_active_sos
self.n_electrons = self.molecule.n_active_electrons

# Compute qubit hamiltonian for the input molecular system
qubit_op = fermion_to_qubit_mapping(fermion_operator=self.molecule.fermionic_hamiltonian,
mapping=self.qubit_mapping,
n_spinorbitals=self.n_spinorbitals,
n_electrons=self.n_electrons,
up_then_down=self.up_then_down,
spin=0)

self.qubit_hamiltonian = qubitop_to_qubitham(qubit_op, self.qubit_mapping, self.up_then_down)

# Getting the pool of operators for the ansatz. If more functionalities
# are added, this part must be modified and generalized.
if self.pool_args is None:
if self.pool == uccgsd_pool:
self.pool_args = (self.n_spinorbitals,)
else:
raise KeyError("pool_args must be defined if using own pool function")
# Check if pool function returns a QubitOperator or FermionOperator and populate variables
pool_list = self.pool(*self.pool_args)
if isinstance(pool_list[0], QubitOperator):
self.pool_type = "qubit"
self.full_pool_operators = pool_list
elif isinstance(pool_list[0], (FermionOperator, ofFermionOperator)):
self.pool_type = "fermion"
self.fermionic_operators = pool_list
self.full_pool_operators = [fermion_to_qubit_mapping(fermion_operator=fi,
mapping=self.qubit_mapping,
n_spinorbitals=self.n_spinorbitals,
n_electrons=self.n_electrons,
up_then_down=self.up_then_down) for fi in self.fermionic_operators]
else:
raise ValueError("pool function must return either QubitOperator or FermionOperator")

# Cast all coefs to floats (rotations angles are real).
for qubit_op in self.full_pool_operators:
for term, coeff in qubit_op.terms.items():
qubit_op.terms[term] = math.copysign(1., coeff.imag)

# Remove duplicates and only select terms with odd number of Y gates for all mappings except JKMN
if self.qubit_mapping.upper() != "JKMN":
reduced_pool_terms = set()
for qubit_op in self.full_pool_operators:
for term in qubit_op.terms:
count_y = str(term).count("Y")
if count_y % 2 == 1:
reduced_pool_terms.add(term)
else:
reduced_pool_terms = set()
for qubit_op in self.full_pool_operators:
for term in qubit_op.terms.keys():
if term:
reduced_pool_terms.add(term)

# Generated list of pool_operators and full pool operator.
self.pool_operators = [QubitOperator(term) for term in reduced_pool_terms]
self.pool_qubit_op = QubitOperator()
JamesB-1qbit marked this conversation as resolved.
Show resolved Hide resolved
for term in self.pool_operators:
self.pool_qubit_op += term

self.qubit_operator = self.qubit_hamiltonian.to_qubitoperator()

# Obtain all qubit terms that need to be measured
self.pool_h = [element*self.qubit_operator for element in self.pool_operators]
self.pool_pool = [[element1*element2 for element2 in self.pool_operators] for element1 in self.pool_operators]

# Obtain initial state preparation circuit
self.circuit_list.append(self.prepare_reference_state())
self.final_circuit = copy(self.circuit_list[0])

# Quantum circuit simulation backend options
self.backend = Simulator(target=self.backend_options["target"], n_shots=self.backend_options["n_shots"],
noise_model=self.backend_options["noise_model"])

self.use_statevector = self.backend.statevector_available and self.backend._noise_model is None

def simulate(self):
"""Performs the QITE cycles. Each iteration, a linear system is
solved to obtain the next unitary. The system to be solved can be found in
section 3.5 of https://arxiv.org/pdf/2108.04413.pdf

Returns:
float: final energy after obtaining running QITE
"""

# Construction of the circuit. self.max_cycles terms are added, unless
# the energy change is less than self.min_de.
if self.use_statevector:
self.update_statevector(self.backend, self.circuit_list[0])
self.final_energy = self.energy_expectation(self.backend)
self.energies.append(self.final_energy)
while self.iteration < self.max_cycles:
self.iteration += 1
if self.verbose:
print(f"Iteration {self.iteration} of QITE with starting energy {self.final_energy}")

suv, bu = self.calculate_matrices(self.backend, self.final_energy)

alphas_array = np.linalg.solve(suv.real, bu.real)
# convert to dictionary with key as first (only) term of each pool_operator and value self.dt * alphas_array[i]
alphas_dict = {next(iter(qu_op.terms)): self.dt * alphas_array[i] for i, qu_op in enumerate(self.pool_operators)}
next_circuit = trotterize(self.pool_qubit_op, alphas_dict, trotter_order=1, n_trotter_steps=1)

self.circuit_list.append(next_circuit)
self.final_circuit += next_circuit

if self.use_statevector:
self.update_statevector(self.backend, self.circuit_list[self.iteration])

new_energy = self.energy_expectation(self.backend)
self.energies.append(new_energy)

if abs(new_energy - self.final_energy) < self.min_de and self.iteration > 1:
self.final_energy = new_energy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take this statement out of the branches: it is not subject to the conditional statement. You then realize the else is unnecessary

self.final_energy = new_energy
if abs(new_energy - self.final_energy) < self.min_de and self.iteration > 1:
   break

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are doing self.final_energy = new_energy regardless of your conditional statement: just do it outside first. Clean, clear. You're making the reader (me) making a double take and squinting eyes to understand why you're doing something contrived and if i'm missing something. I believe that if your conditional statement is true, then the assignment is actually done twice in your current code.

The code snippet above is clearer to me, and the current code is unsatisfying... unless I'm missing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do the declaration above, it will exit the loop immediately after two loops as new_energy-self.final_energy=0 Is there a standard way to break a loop with two conditions?

break
self.final_energy = new_energy

if self.verbose:
print(f"Final energy of QITE is {self.final_energy}")

return self.energies[-1]

def update_statevector(self, backend: Simulator, next_circuit: Circuit):
r"""Update self.final_statevector by propagating with next_circuit using backend

Args:
Simulator: the backend to use for the statevector update
Circuit: The circuit to apply to the statevector
"""
_, self.final_statevector = backend.simulate(next_circuit,
return_statevector=True,
initial_statevector=self.final_statevector)

def calculate_matrices(self, backend: Simulator, new_energy: float):
r"""Calculated matrix elements for imaginary time evolution.
The matrices are defined in section 3.5 of https://arxiv.org/pdf/2108.04413.pdf

Args:
backend (Simulator): the backend from which the matrices are generated
new_energy (float): the current energy_expectation of the Hamiltonian

Returns:
matrix float: The expectation values <\psi| pu^+ pv |\psi>
array float: The expecation values <\psi| pu^+ H |\psi>
"""

circuit = Circuit(n_qubits=self.final_circuit.width) if self.use_statevector else self.final_circuit

ndeltab = np.sqrt(1 - 2 * self.dt * new_energy)
prefac = -1j/ndeltab
bu = [prefac*backend.get_expectation_value(element, circuit, initial_statevector=self.final_statevector)
for element in self.pool_h]
bu = np.array(bu)
pool_size = len(self.pool_h)
suv = np.zeros((pool_size, pool_size), dtype=complex)
for u in range(pool_size):
for v in range(u+1, pool_size):
suv[u, v] = backend.get_expectation_value(self.pool_pool[u][v],
circuit,
initial_statevector=self.final_statevector)
suv[v, u] = suv[u, v]

return suv, bu

def energy_expectation(self, backend: Simulator):
"""Estimate energy using the self.final_circuit, qubit hamiltonian and compute
backend.

Args:
backend (Simulator): the backend one computes the energy expectation with

Returns:
float: energy computed by the backend
"""
circuit = Circuit(n_qubits=self.final_circuit.width) if self.use_statevector else self.final_circuit
energy = backend.get_expectation_value(self.qubit_hamiltonian.to_qubitoperator(),
circuit,
initial_statevector=self.final_statevector)
return energy

def get_resources(self):
"""Returns resources currently used in underlying state preparation i.e. self.final_circuit
the number of pool operators, and the size of qubit_hamiltonian

Returns:
dict: Dictionary of various quantum resources required"""
resources = dict()
resources["qubit_hamiltonian_terms"] = len(self.qubit_hamiltonian.terms)
resources["pool_size"] = len(self.pool_operators)
resources["circuit_width"] = self.final_circuit.width
resources["circuit_gates"] = self.final_circuit.size
resources["circuit_2qubit_gates"] = self.final_circuit.counts.get("CNOT", 0)
ValentinS4t1qbit marked this conversation as resolved.
Show resolved Hide resolved
return resources
13 changes: 13 additions & 0 deletions tangelo/algorithms/projective/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2021 Good Chemistry Company.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Loading