1# Copyright 2018 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Classes for running against Google's Quantum Cloud Service.
15
16As an example, to run a circuit against the xmon simulator on the cloud,
17    engine = cirq_google.Engine(project_id='my-project-id')
18    program = engine.create_program(circuit)
19    result0 = program.run(params=params0, repetitions=10)
20    result1 = program.run(params=params1, repetitions=10)
21
22In order to run on must have access to the Quantum Engine API. Access to this
23API is (as of June 22, 2018) restricted to invitation only.
24"""
25
26import datetime
27import enum
28import os
29import random
30import string
31from typing import Dict, Iterable, List, Optional, Sequence, Set, TypeVar, Union, TYPE_CHECKING
32
33from google.protobuf import any_pb2
34
35import cirq
36from cirq_google.api import v2
37from cirq_google.engine import engine_client
38from cirq_google.engine.client import quantum
39from cirq_google.engine.result_type import ResultType
40from cirq_google.serialization import SerializableGateSet, Serializer
41from cirq_google.serialization.arg_func_langs import arg_to_proto
42from cirq_google.engine import (
43    engine_client,
44    engine_program,
45    engine_job,
46    engine_processor,
47    engine_sampler,
48)
49
50if TYPE_CHECKING:
51    import cirq_google
52    import google.protobuf
53
54TYPE_PREFIX = 'type.googleapis.com/'
55
56_R = TypeVar('_R')
57
58
59class ProtoVersion(enum.Enum):
60    """Protocol buffer version to use for requests to the quantum engine."""
61
62    UNDEFINED = 0
63    V1 = 1
64    V2 = 2
65
66
67def _make_random_id(prefix: str, length: int = 16):
68    random_digits = [random.choice(string.ascii_uppercase + string.digits) for _ in range(length)]
69    suffix = ''.join(random_digits)
70    suffix += datetime.datetime.now().strftime('%y%m%d-%H%M%S')
71    return f'{prefix}{suffix}'
72
73
74@cirq.value_equality
75class EngineContext:
76    """Context for running against the Quantum Engine API. Most users should
77    simply create an Engine object instead of working with one of these
78    directly."""
79
80    # TODO(#3388) Add documentation for Args.
81    # TODO(#3388) Add documentation for Raises.
82    # pylint: disable=missing-param-doc,missing-raises-doc
83    def __init__(
84        self,
85        proto_version: Optional[ProtoVersion] = None,
86        service_args: Optional[Dict] = None,
87        verbose: Optional[bool] = None,
88        client: 'Optional[engine_client.EngineClient]' = None,
89        timeout: Optional[int] = None,
90    ) -> None:
91        """Context and client for using Quantum Engine.
92
93        Args:
94            proto_version: The version of cirq protos to use. If None, then
95                ProtoVersion.V2 will be used.
96            service_args: A dictionary of arguments that can be used to
97                configure options on the underlying client.
98            verbose: Suppresses stderr messages when set to False. Default is
99                true.
100            timeout: Timeout for polling for results, in seconds.  Default is
101                to never timeout.
102        """
103        if (service_args or verbose) and client:
104            raise ValueError('either specify service_args and verbose or client')
105
106        self.proto_version = proto_version or ProtoVersion.V2
107        if self.proto_version == ProtoVersion.V1:
108            raise ValueError('ProtoVersion V1 no longer supported')
109
110        if not client:
111            client = engine_client.EngineClient(service_args=service_args, verbose=verbose)
112        self.client = client
113        self.timeout = timeout
114
115    # pylint: enable=missing-param-doc,missing-raises-doc
116    def copy(self) -> 'EngineContext':
117        return EngineContext(proto_version=self.proto_version, client=self.client)
118
119    def _value_equality_values_(self):
120        return self.proto_version, self.client
121
122
123class Engine:
124    """Runs programs via the Quantum Engine API.
125
126    This class has methods for creating programs and jobs that execute on
127    Quantum Engine:
128        create_program
129        run
130        run_sweep
131        run_batch
132
133    Another set of methods return information about programs and jobs that
134    have been previously created on the Quantum Engine, as well as metadata
135    about available processors:
136        get_program
137        list_processors
138        get_processor
139    """
140
141    # TODO(#3388) Add documentation for Raises.
142    # pylint: disable=missing-raises-doc
143    def __init__(
144        self,
145        project_id: str,
146        proto_version: Optional[ProtoVersion] = None,
147        service_args: Optional[Dict] = None,
148        verbose: Optional[bool] = None,
149        timeout: Optional[int] = None,
150        context: Optional[EngineContext] = None,
151    ) -> None:
152        """Supports creating and running programs against the Quantum Engine.
153
154        Args:
155            project_id: A project_id string of the Google Cloud Project to use.
156                API interactions will be attributed to this project and any
157                resources created will be owned by the project. See
158                https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects
159            proto_version: The version of cirq protos to use. If None, then
160                ProtoVersion.V2 will be used.
161            service_args: A dictionary of arguments that can be used to
162                configure options on the underlying client.
163            verbose: Suppresses stderr messages when set to False. Default is
164                true.
165            timeout: Timeout for polling for results, in seconds.  Default is
166                to never timeout.
167            context: Engine configuration and context to use. For most users
168                this should never be specified.
169        """
170        if context and (proto_version or service_args or verbose):
171            raise ValueError('Either provide context or proto_version, service_args and verbose.')
172
173        self.project_id = project_id
174        if not context:
175            context = EngineContext(
176                proto_version=proto_version,
177                service_args=service_args,
178                verbose=verbose,
179                timeout=timeout,
180            )
181        self.context = context
182
183    # pylint: enable=missing-raises-doc
184    def __str__(self) -> str:
185        return f'Engine(project_id={self.project_id!r})'
186
187    # TODO(#3388) Add documentation for Raises.
188    # pylint: disable=missing-raises-doc
189    def run(
190        self,
191        program: cirq.Circuit,
192        program_id: Optional[str] = None,
193        job_id: Optional[str] = None,
194        param_resolver: cirq.ParamResolver = cirq.ParamResolver({}),
195        repetitions: int = 1,
196        processor_ids: Sequence[str] = ('xmonsim',),
197        gate_set: Optional[Serializer] = None,
198        program_description: Optional[str] = None,
199        program_labels: Optional[Dict[str, str]] = None,
200        job_description: Optional[str] = None,
201        job_labels: Optional[Dict[str, str]] = None,
202    ) -> cirq.Result:
203        """Runs the supplied Circuit via Quantum Engine.
204
205        Args:
206            program: The Circuit to execute. If a circuit is
207                provided, a moment by moment schedule will be used.
208            program_id: A user-provided identifier for the program. This must
209                be unique within the Google Cloud project being used. If this
210                parameter is not provided, a random id of the format
211                'prog-################YYMMDD' will be generated, where # is
212                alphanumeric and YYMMDD is the current year, month, and day.
213            job_id: Job identifier to use. If this is not provided, a random id
214                of the format 'job-################YYMMDD' will be generated,
215                where # is alphanumeric and YYMMDD is the current year, month,
216                and day.
217            param_resolver: Parameters to run with the program.
218            repetitions: The number of repetitions to simulate.
219            processor_ids: The engine processors that should be candidates
220                to run the program. Only one of these will be scheduled for
221                execution.
222            gate_set: The gate set used to serialize the circuit. The gate set
223                must be supported by the selected processor.
224            program_description: An optional description to set on the program.
225            program_labels: Optional set of labels to set on the program.
226            job_description: An optional description to set on the job.
227            job_labels: Optional set of labels to set on the job.
228
229        Returns:
230            A single Result for this run.
231        """
232        if not gate_set:
233            raise ValueError('No gate set provided')
234        return list(
235            self.run_sweep(
236                program=program,
237                program_id=program_id,
238                job_id=job_id,
239                params=[param_resolver],
240                repetitions=repetitions,
241                processor_ids=processor_ids,
242                gate_set=gate_set,
243                program_description=program_description,
244                program_labels=program_labels,
245                job_description=job_description,
246                job_labels=job_labels,
247            )
248        )[0]
249
250    # TODO(#3388) Add documentation for Raises.
251    def run_sweep(
252        self,
253        program: cirq.Circuit,
254        program_id: Optional[str] = None,
255        job_id: Optional[str] = None,
256        params: cirq.Sweepable = None,
257        repetitions: int = 1,
258        processor_ids: Sequence[str] = ('xmonsim',),
259        gate_set: Optional[Serializer] = None,
260        program_description: Optional[str] = None,
261        program_labels: Optional[Dict[str, str]] = None,
262        job_description: Optional[str] = None,
263        job_labels: Optional[Dict[str, str]] = None,
264    ) -> engine_job.EngineJob:
265        """Runs the supplied Circuit via Quantum Engine.Creates
266
267        In contrast to run, this runs across multiple parameter sweeps, and
268        does not block until a result is returned.
269
270        Args:
271            program: The Circuit to execute. If a circuit is
272                provided, a moment by moment schedule will be used.
273            program_id: A user-provided identifier for the program. This must
274                be unique within the Google Cloud project being used. If this
275                parameter is not provided, a random id of the format
276                'prog-################YYMMDD' will be generated, where # is
277                alphanumeric and YYMMDD is the current year, month, and day.
278            job_id: Job identifier to use. If this is not provided, a random id
279                of the format 'job-################YYMMDD' will be generated,
280                where # is alphanumeric and YYMMDD is the current year, month,
281                and day.
282            params: Parameters to run with the program.
283            repetitions: The number of circuit repetitions to run.
284            processor_ids: The engine processors that should be candidates
285                to run the program. Only one of these will be scheduled for
286                execution.
287            gate_set: The gate set used to serialize the circuit. The gate set
288                must be supported by the selected processor.
289            program_description: An optional description to set on the program.
290            program_labels: Optional set of labels to set on the program.
291            job_description: An optional description to set on the job.
292            job_labels: Optional set of labels to set on the job.
293
294        Returns:
295            An EngineJob. If this is iterated over it returns a list of
296            TrialResults, one for each parameter sweep.
297        """
298        if not gate_set:
299            raise ValueError('No gate set provided')
300        engine_program = self.create_program(
301            program, program_id, gate_set, program_description, program_labels
302        )
303        return engine_program.run_sweep(
304            job_id=job_id,
305            params=params,
306            repetitions=repetitions,
307            processor_ids=processor_ids,
308            description=job_description,
309            labels=job_labels,
310        )
311
312    # TODO(#3388) Add documentation for Raises.
313    def run_batch(
314        self,
315        programs: Sequence[cirq.AbstractCircuit],
316        program_id: Optional[str] = None,
317        job_id: Optional[str] = None,
318        params_list: List[cirq.Sweepable] = None,
319        repetitions: int = 1,
320        processor_ids: Sequence[str] = (),
321        gate_set: Optional[Serializer] = None,
322        program_description: Optional[str] = None,
323        program_labels: Optional[Dict[str, str]] = None,
324        job_description: Optional[str] = None,
325        job_labels: Optional[Dict[str, str]] = None,
326    ) -> engine_job.EngineJob:
327        """Runs the supplied Circuits via Quantum Engine.Creates
328
329        This will combine each Circuit provided in `programs` into
330        a BatchProgram.  Each circuit will pair with the associated
331        parameter sweep provided in the `params_list`.  The number of
332        programs is required to match the number of sweeps.
333
334        This method does not block until a result is returned.  However,
335        no results will be available until the entire batch is complete.
336
337        Args:
338            programs: The Circuits to execute as a batch.
339            program_id: A user-provided identifier for the program. This must
340                be unique within the Google Cloud project being used. If this
341                parameter is not provided, a random id of the format
342                'prog-################YYMMDD' will be generated, where # is
343                alphanumeric and YYMMDD is the current year, month, and day.
344            job_id: Job identifier to use. If this is not provided, a random id
345                of the format 'job-################YYMMDD' will be generated,
346                where # is alphanumeric and YYMMDD is the current year, month,
347                and day.
348            params_list: Parameter sweeps to use with the circuits. The number
349                of sweeps should match the number of circuits and will be
350                paired in order with the circuits. If this is None, it is
351                assumed that the circuits are not parameterized and do not
352                require sweeps.
353            repetitions: Number of circuit repetitions to run.  Each sweep value
354                of each circuit in the batch will run with the same repetitions.
355            processor_ids: The engine processors that should be candidates
356                to run the program. Only one of these will be scheduled for
357                execution.
358            gate_set: The gate set used to serialize the circuit. The gate set
359                must be supported by the selected processor.
360            program_description: An optional description to set on the program.
361            program_labels: Optional set of labels to set on the program.
362            job_description: An optional description to set on the job.
363            job_labels: Optional set of labels to set on the job.
364
365        Returns:
366            An EngineJob. If this is iterated over it returns a list of
367            TrialResults. All TrialResults for the first circuit are listed
368            first, then the TrialResults for the second, etc. The TrialResults
369            for a circuit are listed in the order imposed by the associated
370            parameter sweep.
371        """
372        if params_list is None:
373            params_list = [None] * len(programs)
374        elif len(programs) != len(params_list):
375            raise ValueError('Number of circuits and sweeps must match')
376        if not processor_ids:
377            raise ValueError('Processor id must be specified.')
378        engine_program = self.create_batch_program(
379            programs, program_id, gate_set, program_description, program_labels
380        )
381        return engine_program.run_batch(
382            job_id=job_id,
383            params_list=params_list,
384            repetitions=repetitions,
385            processor_ids=processor_ids,
386            description=job_description,
387            labels=job_labels,
388        )
389
390    # TODO(#3388) Add documentation for Raises.
391    def run_calibration(
392        self,
393        layers: List['cirq_google.CalibrationLayer'],
394        program_id: Optional[str] = None,
395        job_id: Optional[str] = None,
396        processor_id: str = None,
397        processor_ids: Sequence[str] = (),
398        gate_set: Optional[Serializer] = None,
399        program_description: Optional[str] = None,
400        program_labels: Optional[Dict[str, str]] = None,
401        job_description: Optional[str] = None,
402        job_labels: Optional[Dict[str, str]] = None,
403    ) -> engine_job.EngineJob:
404        """Runs the specified calibrations via the Calibration API.
405
406        Each calibration will be specified by a `CalibrationLayer`
407        that contains the type of the calibrations to run, a `Circuit`
408        to optimize, and any arguments needed by the calibration routine.
409
410        Arguments and circuits needed for each layer will vary based on the
411        calibration type.  However, the typical calibration routine may
412        require a single moment defining the gates to optimize, for example.
413
414        Note: this is an experimental API and is not yet fully supported
415        for all users.
416
417        Args:
418            layers: The layers of calibration to execute as a batch.
419            program_id: A user-provided identifier for the program. This must
420                be unique within the Google Cloud project being used. If this
421                parameter is not provided, a random id of the format
422                'calibration-################YYMMDD' will be generated,
423                where # is alphanumeric and YYMMDD is the current year, month,
424                and day.
425            job_id: Job identifier to use. If this is not provided, a random id
426                of the format 'calibration-################YYMMDD' will be
427                generated, where # is alphanumeric and YYMMDD is the current
428                year, month, and day.
429            processor_id: The engine processor that should run the calibration.
430                If this is specified, processor_ids should not be specified.
431            processor_ids: The engine processors that should be candidates
432                to run the program. Only one of these will be scheduled for
433                execution.
434            gate_set: The gate set used to serialize the circuit. The gate set
435                must be supported by the selected processor.
436            program_description: An optional description to set on the program.
437            program_labels: Optional set of labels to set on the program.
438            job_description: An optional description to set on the job.
439            job_labels: Optional set of labels to set on the job.  By defauly,
440                this will add a 'calibration' label to the job.
441
442        Returns:
443            An EngineJob whose results can be retrieved by calling
444            calibration_results().
445        """
446        if processor_id and processor_ids:
447            raise ValueError('Only one of processor_id and processor_ids can be specified.')
448        if not processor_ids and not processor_id:
449            raise ValueError('Processor id must be specified.')
450        if processor_id:
451            processor_ids = [processor_id]
452        if job_labels is None:
453            job_labels = {'calibration': ''}
454        engine_program = self.create_calibration_program(
455            layers, program_id, gate_set, program_description, program_labels
456        )
457        return engine_program.run_calibration(
458            job_id=job_id,
459            processor_ids=processor_ids,
460            description=job_description,
461            labels=job_labels,
462        )
463
464    # TODO(#3388) Add documentation for Raises.
465    def create_program(
466        self,
467        program: cirq.Circuit,
468        program_id: Optional[str] = None,
469        gate_set: Optional[Serializer] = None,
470        description: Optional[str] = None,
471        labels: Optional[Dict[str, str]] = None,
472    ) -> engine_program.EngineProgram:
473        """Wraps a Circuit for use with the Quantum Engine.
474
475        Args:
476            program: The Circuit to execute.
477            program_id: A user-provided identifier for the program. This must be
478                unique within the Google Cloud project being used. If this
479                parameter is not provided, a random id of the format
480                'prog-################YYMMDD' will be generated, where # is
481                alphanumeric and YYMMDD is the current year, month, and day.
482            gate_set: The gate set used to serialize the circuit. The gate set
483                must be supported by the selected processor
484            description: An optional description to set on the program.
485            labels: Optional set of labels to set on the program.
486
487        Returns:
488            A EngineProgram for the newly created program.
489        """
490        if not gate_set:
491            raise ValueError('No gate set provided')
492
493        if not program_id:
494            program_id = _make_random_id('prog-')
495
496        new_program_id, new_program = self.context.client.create_program(
497            self.project_id,
498            program_id,
499            code=self._serialize_program(program, gate_set),
500            description=description,
501            labels=labels,
502        )
503
504        return engine_program.EngineProgram(
505            self.project_id, new_program_id, self.context, new_program
506        )
507
508    # TODO(#3388) Add documentation for Raises.
509    def create_batch_program(
510        self,
511        programs: Sequence[cirq.AbstractCircuit],
512        program_id: Optional[str] = None,
513        gate_set: Optional[Serializer] = None,
514        description: Optional[str] = None,
515        labels: Optional[Dict[str, str]] = None,
516    ) -> engine_program.EngineProgram:
517        """Wraps a list of Circuits into a BatchProgram for the Quantum Engine.
518
519        Args:
520            programs: The Circuits to execute within a batch.
521            program_id: A user-provided identifier for the program. This must be
522                unique within the Google Cloud project being used. If this
523                parameter is not provided, a random id of the format
524                'prog-################YYMMDD' will be generated, where # is
525                alphanumeric and YYMMDD is the current year, month, and day.
526            gate_set: The gate set used to serialize the circuit. The gate set
527                must be supported by the selected processor
528            description: An optional description to set on the program.
529            labels: Optional set of labels to set on the program.
530
531        Returns:
532            A EngineProgram for the newly created program.
533        """
534        if not gate_set:
535            raise ValueError('Gate set must be specified.')
536        if not program_id:
537            program_id = _make_random_id('prog-')
538
539        batch = v2.batch_pb2.BatchProgram()
540        for program in programs:
541            gate_set.serialize(program, msg=batch.programs.add())
542
543        new_program_id, new_program = self.context.client.create_program(
544            self.project_id,
545            program_id,
546            code=self._pack_any(batch),
547            description=description,
548            labels=labels,
549        )
550
551        return engine_program.EngineProgram(
552            self.project_id, new_program_id, self.context, new_program, result_type=ResultType.Batch
553        )
554
555    # TODO(#3388) Add documentation for Raises.
556    def create_calibration_program(
557        self,
558        layers: List['cirq_google.CalibrationLayer'],
559        program_id: Optional[str] = None,
560        gate_set: Optional[Serializer] = None,
561        description: Optional[str] = None,
562        labels: Optional[Dict[str, str]] = None,
563    ) -> engine_program.EngineProgram:
564        """Wraps a list of calibration layers into an Any for Quantum Engine.
565
566        Args:
567            layers: The calibration routines to execute.  All layers will be
568                executed within the same API call in the order specified,
569                though some layers may be interleaved together using
570                hardware-specific batching.
571            program_id: A user-provided identifier for the program. This must be
572                unique within the Google Cloud project being used. If this
573                parameter is not provided, a random id of the format
574                'calibration-################YYMMDD' will be generated,
575                where # is alphanumeric and YYMMDD is the current year, month,
576                and day.
577            gate_set: The gate set used to serialize the circuits in each
578                layer.  The gate set must be supported by the processor.
579            description: An optional description to set on the program.
580            labels: Optional set of labels to set on the program.
581
582        Returns:
583            A EngineProgram for the newly created program.
584        """
585        if not gate_set:
586            raise ValueError('Gate set must be specified.')
587        if not program_id:
588            program_id = _make_random_id('calibration-')
589
590        calibration = v2.calibration_pb2.FocusedCalibration()
591        for layer in layers:
592            new_layer = calibration.layers.add()
593            new_layer.calibration_type = layer.calibration_type
594            for arg in layer.args:
595                arg_to_proto(layer.args[arg], out=new_layer.args[arg])
596            gate_set.serialize(layer.program, msg=new_layer.layer)
597
598        new_program_id, new_program = self.context.client.create_program(
599            self.project_id,
600            program_id,
601            code=self._pack_any(calibration),
602            description=description,
603            labels=labels,
604        )
605
606        return engine_program.EngineProgram(
607            self.project_id,
608            new_program_id,
609            self.context,
610            new_program,
611            result_type=ResultType.Calibration,
612        )
613
614    # pylint: enable=missing-raises-doc
615    def _serialize_program(self, program: cirq.Circuit, gate_set: Serializer) -> any_pb2.Any:
616        if not isinstance(program, cirq.Circuit):
617            raise TypeError(f'Unrecognized program type: {type(program)}')
618        program.device.validate_circuit(program)
619
620        if self.context.proto_version == ProtoVersion.V2:
621            program = gate_set.serialize(program)
622            return self._pack_any(program)
623        else:
624            raise ValueError(f'invalid program proto version: {self.context.proto_version}')
625
626    def _pack_any(self, message: 'google.protobuf.Message') -> any_pb2.Any:
627        """Packs a message into an Any proto.
628
629        Returns the packed Any proto.
630        """
631        packed = any_pb2.Any()
632        packed.Pack(message)
633        return packed
634
635    def get_program(self, program_id: str) -> engine_program.EngineProgram:
636        """Returns an EngineProgram for an existing Quantum Engine program.
637
638        Args:
639            program_id: Unique ID of the program within the parent project.
640
641        Returns:
642            A EngineProgram for the program.
643        """
644        return engine_program.EngineProgram(self.project_id, program_id, self.context)
645
646    def list_programs(
647        self,
648        created_before: Optional[Union[datetime.datetime, datetime.date]] = None,
649        created_after: Optional[Union[datetime.datetime, datetime.date]] = None,
650        has_labels: Optional[Dict[str, str]] = None,
651    ) -> List[engine_program.EngineProgram]:
652        """Returns a list of previously executed quantum programs.
653
654        Args:
655            created_after: retrieve programs that were created after this date
656                or time.
657            created_before: retrieve programs that were created after this date
658                or time.
659            has_labels: retrieve programs that have labels on them specified by
660                this dict. If the value is set to `*`, filters having the label
661                regardless of the label value will be filtered. For example, to
662                query programs that have the shape label and have the color
663                label with value red can be queried using
664                `{'color: red', 'shape:*'}`
665        """
666
667        client = self.context.client
668        response = client.list_programs(
669            self.project_id,
670            created_before=created_before,
671            created_after=created_after,
672            has_labels=has_labels,
673        )
674        return [
675            engine_program.EngineProgram(
676                project_id=engine_client._ids_from_program_name(p.name)[0],
677                program_id=engine_client._ids_from_program_name(p.name)[1],
678                _program=p,
679                context=self.context,
680            )
681            for p in response
682        ]
683
684    def list_jobs(
685        self,
686        created_before: Optional[Union[datetime.datetime, datetime.date]] = None,
687        created_after: Optional[Union[datetime.datetime, datetime.date]] = None,
688        has_labels: Optional[Dict[str, str]] = None,
689        execution_states: Optional[Set[quantum.enums.ExecutionStatus.State]] = None,
690    ):
691        """Returns the list of jobs in the project.
692
693        All historical jobs can be retrieved using this method and filtering
694        options are available too, to narrow down the search baesd on:
695          * creation time
696          * job labels
697          * execution states
698
699        Args:
700            created_after: retrieve jobs that were created after this date
701                or time.
702            created_before: retrieve jobs that were created after this date
703                or time.
704            has_labels: retrieve jobs that have labels on them specified by
705                this dict. If the value is set to `*`, filters having the label
706                regardless of the label value will be filtered. For example, to
707                query programs that have the shape label and have the color
708                label with value red can be queried using
709
710                {'color': 'red', 'shape':'*'}
711
712            execution_states: retrieve jobs that have an execution state  that
713                 is contained in `execution_states`. See
714                 `quantum.enums.ExecutionStatus.State` enum for accepted values.
715        """
716        client = self.context.client
717        response = client.list_jobs(
718            self.project_id,
719            None,
720            created_before=created_before,
721            created_after=created_after,
722            has_labels=has_labels,
723            execution_states=execution_states,
724        )
725        return [
726            engine_job.EngineJob(
727                project_id=engine_client._ids_from_job_name(j.name)[0],
728                program_id=engine_client._ids_from_job_name(j.name)[1],
729                job_id=engine_client._ids_from_job_name(j.name)[2],
730                context=self.context,
731                _job=j,
732            )
733            for j in response
734        ]
735
736    def list_processors(self) -> List[engine_processor.EngineProcessor]:
737        """Returns a list of Processors that the user has visibility to in the
738        current Engine project. The names of these processors are used to
739        identify devices when scheduling jobs and gathering calibration metrics.
740
741        Returns:
742            A list of EngineProcessors to access status, device and calibration
743            information.
744        """
745        response = self.context.client.list_processors(self.project_id)
746        return [
747            engine_processor.EngineProcessor(
748                self.project_id,
749                engine_client._ids_from_processor_name(p.name)[1],
750                self.context,
751                p,
752            )
753            for p in response
754        ]
755
756    def get_processor(self, processor_id: str) -> engine_processor.EngineProcessor:
757        """Returns an EngineProcessor for a Quantum Engine processor.
758
759        Args:
760            processor_id: The processor unique identifier.
761
762        Returns:
763            A EngineProcessor for the processor.
764        """
765        return engine_processor.EngineProcessor(self.project_id, processor_id, self.context)
766
767    def sampler(
768        self, processor_id: Union[str, List[str]], gate_set: Serializer
769    ) -> engine_sampler.QuantumEngineSampler:
770        """Returns a sampler backed by the engine.
771
772        Args:
773            processor_id: String identifier, or list of string identifiers,
774                determining which processors may be used when sampling.
775            gate_set: Determines how to serialize circuits when requesting
776                samples.
777        """
778        return engine_sampler.QuantumEngineSampler(
779            engine=self, processor_id=processor_id, gate_set=gate_set
780        )
781
782
783# TODO(#3388) Add documentation for Raises.
784# pylint: disable=missing-raises-doc
785def get_engine(project_id: Optional[str] = None) -> Engine:
786    """Get an Engine instance assuming some sensible defaults.
787
788    This uses the environment variable GOOGLE_CLOUD_PROJECT for the Engine
789    project_id, unless set explicitly. By using an environment variable,
790    you can avoid hard-coding the project_id in shared code.
791
792    If the environment variables are set, but incorrect, an authentication
793    failure will occur when attempting to run jobs on the engine.
794
795    Args:
796        project_id: If set overrides the project id obtained from the
797            environment variable `GOOGLE_CLOUD_PROJECT`.
798
799    Returns:
800        The Engine instance.
801
802    Raises:
803        EnvironmentError: If the environment variable GOOGLE_CLOUD_PROJECT is
804            not set.
805    """
806    env_project_id = 'GOOGLE_CLOUD_PROJECT'
807    if not project_id:
808        project_id = os.environ.get(env_project_id)
809    if not project_id:
810        raise EnvironmentError(f'Environment variable {env_project_id} is not set.')
811
812    return Engine(project_id=project_id)
813
814
815# pylint: enable=missing-raises-doc
816def get_engine_device(
817    processor_id: str,
818    project_id: Optional[str] = None,
819    gatesets: Iterable[SerializableGateSet] = (),
820) -> cirq.Device:
821    """Returns a `Device` object for a given processor.
822
823    This is a short-cut for creating an engine object, getting the
824    processor object, and retrieving the device.  Note that the
825    gateset is required in order to match the serialized specification
826    back into cirq objects.
827    """
828    return get_engine(project_id).get_processor(processor_id).get_device(gatesets)
829
830
831def get_engine_calibration(
832    processor_id: str,
833    project_id: Optional[str] = None,
834) -> Optional['cirq_google.Calibration']:
835    """Returns calibration metrics for a given processor.
836
837    This is a short-cut for creating an engine object, getting the
838    processor object, and retrieving the current calibration.
839    May return None if no calibration metrics exist for the device.
840    """
841    return get_engine(project_id).get_processor(processor_id).get_current_calibration()
842