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

[unitaryHACK](#1) Cirq Support #20

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
.venv
coverage*
dist
.vscode
62 changes: 55 additions & 7 deletions qrand/platforms/cirq/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,71 @@
## 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.

from cirq_google.engine import Engine
Copy link
Owner

Choose a reason for hiding this comment

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

I the dependency cirq, cirq_google, both, or are they the same? I am saying this to add list it appropriately. Also, did you include any extra depencies?

Copy link
Author

Choose a reason for hiding this comment

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

They are different packages and no other extra dependency was used.

from ..backend import QuantumBackend


###############################################################################
## CIRQ BACKEND (DECORATOR)
###############################################################################
class CirqBackend(QuantumBackend):
def __init__(self) -> None:
self.ERROR_MSG = f"{self.__class__.__name__}" # TODO
raise NotImplementedError(self.ERROR_MSG)



Comment on lines +29 to +31
Copy link
Owner

Choose a reason for hiding this comment

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

Generally speaking, adhere to the code style in other files. Here, remove unnecessary empty lines.

class CirqBackend(QuantumBackend,Engine):
"""

Encapsulating Google Engine API with Quantum Backend

Since API and Cirq is still in Alpha,Changes are expected

methods from Engine class:

create_program()->Wraps a Circuit for use with the Quantum Engine. i.e return engineprogram

run()->Runs the supplied Circuit via Quantum Engine. i.e return study.result
"""
Comment on lines +33 to +44
Copy link
Owner

Choose a reason for hiding this comment

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

This does not adhere to NumPy's docstring style. See the contributing guidelines.

def __init__(
self,
engine: Engine,
Copy link
Owner

Choose a reason for hiding this comment

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

Why are you inheriting from Engine and also passing in and storing an Engine object as an attribute? Not necessarily wrong, but definitely not ideal, so just wondering the motivation behind it.

Copy link
Author

Choose a reason for hiding this comment

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

I thought maybe an Engine object had to be instantiated before gcloud authentication.But I think it's not necessary, will fix it in the fresh PR.

shots:int =1,
max_experiments:int=1
Comment on lines +48 to +49
Copy link
Owner

Choose a reason for hiding this comment

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

Does Cirq differentiate between shots and experiments? I thought they only used measurements. In any case, max_shots and max_experiments must be determined by the hardware you are connecting to, while the num_shots, num_experiments, or num_measurements has to be defined in CirqJob.

Copy link
Author

Choose a reason for hiding this comment

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

They use repetitions mainly.

) -> None:
self._backend: Engine = engine
super(QuantumBackend, self).__init__( )
self._options = engine.service_args
self._shots:int= shots,
Copy link
Owner

Choose a reason for hiding this comment

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

shots is not an attribute of the backend, it belongs (if anything) in job.

self._max_experiments = max_experiments
self.max_shots=shots #Google API is still private

############################### PUBLIC API ###############################
@property
def max_measurements(self) -> int:
raise NotImplementedError(self.ERROR_MSG)
return (self._max_experiments*self._shots )

@property
def max_experiments(self) -> int:
return self._max_experiments

@max_experiments.setter
def max_experiments(self,max_experiments:int)->None:
self._max_experiments:int=max_experiments

"""
@property
def max_qubits(self) -> int:
raise NotImplementedError(self.ERROR_MSG)
return (

)
"""
Comment on lines +71 to +77
Copy link
Author

Choose a reason for hiding this comment

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

I couldn't figure out the maximum qubits part. Should we define this explicitly...??

Copy link
Owner

Choose a reason for hiding this comment

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

I do not think that would be a good approach. We have to run the programs on physical backends/simulators, and they will have different capabilities.

Also, have you tested your code? It should not have let you instantiate this class without defining this property.


#Using Consitent Qiskit analogy
Copy link
Owner

Choose a reason for hiding this comment

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

Do not add line comments.

@property
def shots(self) -> int:
return self._shots

@shots.setter
def shots(self,shots:int) -> None:
self._shots:int = shots
Comment on lines +80 to +86
Copy link
Owner

Choose a reason for hiding this comment

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

shots is not an attribute of the backend, it belongs (if anything) in job.


def run(self, run_input, **options):
return self._backend.run(run_input, self._shots,**options)
Comment on lines +88 to +89
Copy link
Owner

Choose a reason for hiding this comment

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

Why do you implement this method?

116 changes: 74 additions & 42 deletions qrand/platforms/cirq/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,85 +19,117 @@
## 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.

import cirq
from cirq.circuits import Circuit as CirqQuantumCircuit
from ..circuit import QuantumCircuit

from ...helpers import validate_natural_number, validate_type
Copy link
Owner

Choose a reason for hiding this comment

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

I believe that you are not using validate_type in this module.


###############################################################################
## CIRQ CIRCUIT
###############################################################################
class CirqCircuit(QuantumCircuit):

def __init__(self, num_qubits: int) -> None:
self.ERROR_MSG = f"{self.__class__.__name__}" # TODO
raise NotImplementedError(self.ERROR_MSG)

self.qubits=cirq.LineQubit.range(num_qubits)
self._num_qubits=num_qubits
self.moment_list:list=[]

def _validate_qubit_index(self, qubit_index: int) -> None:
if qubit_index >= self.num_qubits:
raise ValueError(
f"Qubit index out of range {qubit_index} >= {self.num_qubits}."
)



Comment on lines +37 to +44
Copy link
Owner

Choose a reason for hiding this comment

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

This has been added as a QuantumCircuit method. You can get rid of its declaration here. Keep using it though!

@property
def num_qubits(self) -> int:
raise NotImplementedError(self.ERROR_MSG)
return self._num_qubits

@num_qubits.setter
def num_qubits(self,num_qubits:int) -> None:
self._num_qubits=num_qubits

############################## SPECIAL GATES ##############################
def measure(self, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)

self.moment_list.append(cirq.ops.measure(self.qubits[target_qubit]))
########################### SINGLE QUBIT GATES ###########################
def h(self, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.H(self.qubits[target_qubit]))


def rx(self, radians: float, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.Rx(radians, self.qubits[target_qubit]))

def ry(self, radians: float, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.Ry(radians,self.qubits[target_qubit]))

def rz(self, radians: float, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.Rz(radians, self.qubits[target_qubit]))

def s(self, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.S(self.qubits[target_qubit]))

def t(self, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)

def u1(self, theta: float, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)

def u2(self, phi: float, lam: float, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)

def u3(
self, theta: float, phi: float, lam: float, target_qubit: int
) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.T(self.qubits[target_qubit]))

def x(self, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.X(self.qubits[target_qubit]))


def y(self, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.Y(self.qubits[target_qubit]))


def z(self, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
self._validate_qubit_index(target_qubit)
self.moment_list.append(cirq.ops.Z(self.qubits[target_qubit]))

############################# TWO QUBIT GATES #############################
def cs(self, control_qubit: int, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)

def cx(self, control_qubit: int, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)

Copy link
Author

@harry-stark harry-stark May 31, 2021

Choose a reason for hiding this comment

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

Official Implementation of CX, CS are WIP. Also some three-qubit gates too. We can wait until the CIRQ ships them officially.

def cz(self, control_qubit: int, target_qubit: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit, True)
validate_natural_number(control_qubit, True)
self._validate_qubit_index(target_qubit)
self._validate_qubit_index(control_qubit)
self.moment_list.append(cirq.ops.CZ(self.qubits[control_qubit], self.qubits[target_qubit]))

def swap(self, target_qubit_1: int, target_qubit_2: int) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit_1, True)
self._validate_qubit_index(target_qubit_1)
self._validate_qubit_index(target_qubit_2)
self.moment_list.append(cirq.ops.SWAP(self.qubits[target_qubit_1], self.qubits[target_qubit_2]))

############################ THREE QUBIT GATES ############################
def ccx(
self, control_qubit_1: int, control_qubit_2: int, target_qubit: int
) -> None:
raise NotImplementedError(self.ERROR_MSG)


def cswap(
self, control_qubit_1: int, control_qubit_2: int, target_qubit: int
self, control_qubit: int, target_qubit_1: int, target_qubit_2: int
) -> None:
raise NotImplementedError(self.ERROR_MSG)
validate_natural_number(target_qubit_1, True)
validate_natural_number(target_qubit_2, True)
validate_natural_number(control_qubit, True)
self._validate_qubit_index(control_qubit)
self._validate_qubit_index(target_qubit_1)
self._validate_qubit_index(target_qubit_2)
self.moment_list.append(cirq.ops.CSWAP(self.qubits[control_qubit], self.qubits[target_qubit_1], self.qubits[target_qubit_2]))

def createcircuit(self):
return CirqQuantumCircuit(self.moment_list)
Comment on lines +134 to +135
Copy link
Owner

Choose a reason for hiding this comment

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

It is better to inherit from CirqQuantumCircuit and apply the gates to itself: just as it is done in QiskitCircuit. This other technique should be avoided. Therefore you do not need the moment_list attribute.

Copy link
Author

Choose a reason for hiding this comment

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

Will do it.

52 changes: 42 additions & 10 deletions qrand/platforms/cirq/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@
## See the License for the specific language governing permissions and
## limitations under the License.


from typing import List, Optional
from warnings import warn



from ...helpers import compute_bounded_factorization, reverse_endian
from ..job import QuantumJob
from .backend import CirqBackend
from .circuit import CirqCircuit
Expand All @@ -31,34 +36,61 @@
## CIRQ JOB
###############################################################################
class CirqJob(QuantumJob):
def __init__(self) -> None:
self.ERROR_MSG = f"{self.__class__.__name__}" # TODO
raise NotImplementedError(self.ERROR_MSG)
def __init__(
self,
circuit:CirqCircuit,
backend:CirqBackend,
num_measurements:Optional[int]=None
) -> None:
self.backend:CirqBackend = backend
self.circuit:CirqCircuit = circuit
self.num_measurements:int = num_measurements



############################### PUBLIC API ###############################
@property
def backend(self) -> CirqBackend:
raise NotImplementedError(self.ERROR_MSG)
return self._backend

@backend.setter
def backend(self, backend: CirqBackend) -> None:
raise NotImplementedError(self.ERROR_MSG)
self._backend: CirqBackend = backend

@property
def circuit(self) -> CirqCircuit:
raise NotImplementedError(self.ERROR_MSG)
return self._circuit

@circuit.setter
def circuit(self, circuit: CirqCircuit) -> None:
raise NotImplementedError(self.ERROR_MSG)
self._circuit: CirqCircuit = circuit

@property
def num_measurements(self) -> int:
raise NotImplementedError(self.ERROR_MSG)
return self._shots

@num_measurements.setter
def num_measurements(self, num_measurements: Optional[int]) -> None:
raise NotImplementedError(self.ERROR_MSG)
#Shots == Reptition....Used because of Qiskit analogy in whole qrand
num_measurements = (
num_measurements
if isinstance(num_measurements, int) and 0 < num_measurements
else self.backend.max_measurements
)
if self.backend.max_measurements < num_measurements:
warn(
f"Number of measurements unsupported by the job's Backend: \
{self.backend.max_measurements}<{num_measurements}. \
Using max_measurements instead.",
UserWarning,
)
num_measurements=self._backend.max_measurements
self._shots, self._experiments = compute_bounded_factorization(
num_measurements,
self.backend.max_shots,
self.backend.max_experiments,
)

def execute(self) -> List[str]:
raise NotImplementedError(self.ERROR_MSG)
self.cirqcirc=self._circuit.createcircuit()
return self._backend.run(self.cirqcirc)
Loading