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
15"""Tests for engine."""
16import os
17from unittest import mock
18import time
19import numpy as np
20import pytest
21
22from google.protobuf import any_pb2
23from google.protobuf.text_format import Merge
24
25import cirq
26import cirq_google
27import cirq_google as cg
28from cirq_google.api import v1, v2
29from cirq_google.engine.engine import EngineContext
30from cirq_google.engine.client.quantum_v1alpha1 import types as qtypes
31
32_CIRCUIT = cirq.Circuit(
33    cirq.X(cirq.GridQubit(5, 2)) ** 0.5, cirq.measure(cirq.GridQubit(5, 2), key='result')
34)
35
36
37_CIRCUIT2 = cirq.Circuit(
38    cirq.Y(cirq.GridQubit(5, 2)) ** 0.5, cirq.measure(cirq.GridQubit(5, 2), key='result')
39)
40
41
42def _to_any(proto):
43    any_proto = qtypes.any_pb2.Any()
44    any_proto.Pack(proto)
45    return any_proto
46
47
48def _to_timestamp(json_string):
49    timestamp_proto = qtypes.timestamp_pb2.Timestamp()
50    timestamp_proto.FromJsonString(json_string)
51    return timestamp_proto
52
53
54_A_RESULT = _to_any(
55    Merge(
56        """
57sweep_results: [{
58        repetitions: 1,
59        measurement_keys: [{
60            key: 'q',
61            qubits: [{
62                row: 1,
63                col: 1
64            }]
65        }],
66        parameterized_results: [{
67            params: {
68                assignments: {
69                    key: 'a'
70                    value: 1
71                }
72            },
73            measurement_results: '\000\001'
74        }]
75    }]
76""",
77        v1.program_pb2.Result(),
78    )
79)
80
81_RESULTS = _to_any(
82    Merge(
83        """
84sweep_results: [{
85        repetitions: 1,
86        measurement_keys: [{
87            key: 'q',
88            qubits: [{
89                row: 1,
90                col: 1
91            }]
92        }],
93        parameterized_results: [{
94            params: {
95                assignments: {
96                    key: 'a'
97                    value: 1
98                }
99            },
100            measurement_results: '\000\001'
101        },{
102            params: {
103                assignments: {
104                    key: 'a'
105                    value: 2
106                }
107            },
108            measurement_results: '\000\001'
109        }]
110    }]
111""",
112        v1.program_pb2.Result(),
113    )
114)
115
116_RESULTS_V2 = _to_any(
117    Merge(
118        """
119sweep_results: [{
120        repetitions: 1,
121        parameterized_results: [{
122            params: {
123                assignments: {
124                    key: 'a'
125                    value: 1
126                }
127            },
128            measurement_results: {
129                key: 'q'
130                qubit_measurement_results: [{
131                  qubit: {
132                    id: '1_1'
133                  }
134                  results: '\000\001'
135                }]
136            }
137        },{
138            params: {
139                assignments: {
140                    key: 'a'
141                    value: 2
142                }
143            },
144            measurement_results: {
145                key: 'q'
146                qubit_measurement_results: [{
147                  qubit: {
148                    id: '1_1'
149                  }
150                  results: '\000\001'
151                }]
152            }
153        }]
154    }]
155""",
156        v2.result_pb2.Result(),
157    )
158)
159
160_BATCH_RESULTS_V2 = _to_any(
161    Merge(
162        """
163results: [{
164    sweep_results: [{
165        repetitions: 1,
166        parameterized_results: [{
167            params: {
168                assignments: {
169                    key: 'a'
170                    value: 1
171                }
172            },
173            measurement_results: {
174                key: 'q'
175                qubit_measurement_results: [{
176                  qubit: {
177                    id: '1_1'
178                  }
179                  results: '\000\001'
180                }]
181            }
182        },{
183            params: {
184                assignments: {
185                    key: 'a'
186                    value: 2
187                }
188            },
189            measurement_results: {
190                key: 'q'
191                qubit_measurement_results: [{
192                  qubit: {
193                    id: '1_1'
194                  }
195                  results: '\000\001'
196                }]
197            }
198        }]
199    }],
200    },{
201    sweep_results: [{
202        repetitions: 1,
203        parameterized_results: [{
204            params: {
205                assignments: {
206                    key: 'a'
207                    value: 3
208                }
209            },
210            measurement_results: {
211                key: 'q'
212                qubit_measurement_results: [{
213                  qubit: {
214                    id: '1_1'
215                  }
216                  results: '\000\001'
217                }]
218            }
219        },{
220            params: {
221                assignments: {
222                    key: 'a'
223                    value: 4
224                }
225            },
226            measurement_results: {
227                key: 'q'
228                qubit_measurement_results: [{
229                  qubit: {
230                    id: '1_1'
231                  }
232                  results: '\000\001'
233                }]
234            }
235        }]
236    }]
237}]
238""",
239        v2.batch_pb2.BatchResult(),
240    )
241)
242
243
244_CALIBRATION_RESULTS_V2 = _to_any(
245    Merge(
246        """
247results: [{
248    code: 1
249    error_message: 'First success'
250    token: 'abc123'
251    metrics: {
252      metrics: [{
253        name: 'fidelity'
254        targets: ['q2_3','q2_4']
255        values: [{
256            double_val: 0.75
257    }]
258    }]}
259    },{
260    code: 1
261    error_message: 'Second success'
262}]
263""",
264        v2.calibration_pb2.FocusedCalibrationResult(),
265    )
266)
267
268
269def test_make_random_id():
270    with mock.patch('random.choice', return_value='A'):
271        random_id = cg.engine.engine._make_random_id('prefix-', length=4)
272        assert random_id[:11] == 'prefix-AAAA'
273    random_id = cg.engine.engine._make_random_id('prefix-')
274    time.sleep(1)
275    random_id2 = cg.engine.engine._make_random_id('prefix-')
276    # Verify %H%M%S is populated
277    assert random_id[-7:] != '-000000' or random_id2[-7:] != '-000000'
278    # Verify program id generate distinct even if random is seeded
279    assert random_id != random_id2
280
281
282@pytest.fixture(scope='session', autouse=True)
283def mock_grpc_client():
284    with mock.patch(
285        'cirq_google.engine.engine_client.quantum.QuantumEngineServiceClient'
286    ) as _fixture:
287        yield _fixture
288
289
290@mock.patch('cirq_google.engine.engine_client.EngineClient')
291def test_create_context(client):
292    with pytest.raises(ValueError, match='specify service_args and verbose or client'):
293        EngineContext(cg.engine.engine.ProtoVersion.V1, {'args': 'test'}, True, mock.Mock())
294    with pytest.raises(ValueError, match='no longer supported'):
295        _ = EngineContext(cg.engine.engine.ProtoVersion.V1, {'args': 'test'}, True)
296
297    context = EngineContext(cg.engine.engine.ProtoVersion.V2, {'args': 'test'}, True)
298    assert context.proto_version == cg.engine.engine.ProtoVersion.V2
299    assert client.called_with({'args': 'test'}, True)
300
301    assert context.copy().proto_version == context.proto_version
302    assert context.copy().client == context.client
303    assert context.copy() == context
304
305
306@mock.patch('cirq_google.engine.engine_client.EngineClient')
307def test_create_engine(client):
308    with pytest.raises(
309        ValueError, match='provide context or proto_version, service_args and verbose'
310    ):
311        cg.Engine(
312            'proj',
313            proto_version=cg.engine.engine.ProtoVersion.V2,
314            service_args={'args': 'test'},
315            verbose=True,
316            context=mock.Mock(),
317        )
318
319    assert (
320        cg.Engine(
321            'proj',
322            proto_version=cg.engine.engine.ProtoVersion.V2,
323            service_args={'args': 'test'},
324            verbose=True,
325        ).context.proto_version
326        == cg.engine.engine.ProtoVersion.V2
327    )
328    assert client.called_with({'args': 'test'}, True)
329
330
331def test_engine_str():
332    engine = cg.Engine(
333        'proj',
334        proto_version=cg.engine.engine.ProtoVersion.V2,
335        service_args={'args': 'test'},
336        verbose=True,
337    )
338    assert str(engine) == 'Engine(project_id=\'proj\')'
339
340
341def setup_run_circuit_with_result_(client, result):
342    client().create_program.return_value = (
343        'prog',
344        qtypes.QuantumProgram(name='projects/proj/programs/prog'),
345    )
346    client().create_job.return_value = (
347        'job-id',
348        qtypes.QuantumJob(
349            name='projects/proj/programs/prog/jobs/job-id', execution_status={'state': 'READY'}
350        ),
351    )
352    client().get_job.return_value = qtypes.QuantumJob(execution_status={'state': 'SUCCESS'})
353    client().get_job_results.return_value = qtypes.QuantumResult(result=result)
354
355
356@mock.patch('cirq_google.engine.engine_client.EngineClient')
357def test_run_circuit(client):
358    setup_run_circuit_with_result_(client, _A_RESULT)
359
360    engine = cg.Engine(project_id='proj', service_args={'client_info': 1})
361    result = engine.run(
362        program=_CIRCUIT,
363        program_id='prog',
364        job_id='job-id',
365        processor_ids=['mysim'],
366        gate_set=cg.XMON,
367    )
368
369    assert result.repetitions == 1
370    assert result.params.param_dict == {'a': 1}
371    assert result.measurements == {'q': np.array([[0]], dtype='uint8')}
372    client.assert_called_with(service_args={'client_info': 1}, verbose=None)
373    client.create_program.called_once_with()
374    client.create_job.called_once_with(
375        'projects/project-id/programs/test',
376        qtypes.QuantumJob(
377            name='projects/project-id/programs/test/jobs/job-id',
378            scheduling_config={
379                'priority': 50,
380                'processor_selector': {'processor_names': ['projects/project-id/processors/mysim']},
381            },
382            run_context=_to_any(
383                v2.run_context_pb2.RunContext(
384                    parameter_sweeps=[v2.run_context_pb2.ParameterSweep(repetitions=1)]
385                )
386            ),
387        ),
388        False,
389    )
390
391    client.get_job.called_once_with('proj', 'prog')
392    client.get_job_result.called_once_with()
393
394
395def test_circuit_device_validation_fails():
396    circuit = cirq.Circuit(device=cg.Foxtail)
397
398    # Purposefully create an invalid Circuit by fiddling with internal bits.
399    # This simulates a failure in the incremental checks.
400    circuit._moments.append(cirq.Moment([cirq.Z(cirq.NamedQubit("dorothy"))]))
401    engine = cg.Engine(project_id='project-id')
402    with pytest.raises(ValueError, match='Unsupported qubit type'):
403        engine.run_sweep(program=circuit, gate_set=cg.XMON)
404    with pytest.raises(ValueError, match='Unsupported qubit type'):
405        engine.create_program(circuit, gate_set=cg.XMON)
406
407
408def test_no_gate_set():
409    circuit = cirq.Circuit(device=cg.Sycamore)
410    engine = cg.Engine(project_id='project-id')
411    with pytest.raises(ValueError, match='No gate set'):
412        engine.run(program=circuit)
413    with pytest.raises(ValueError, match='No gate set'):
414        engine.run_sweep(program=circuit)
415    with pytest.raises(ValueError, match='No gate set'):
416        engine.create_program(program=circuit)
417
418
419def test_unsupported_program_type():
420    engine = cg.Engine(project_id='project-id')
421    with pytest.raises(TypeError, match='program'):
422        engine.run(program="this isn't even the right type of thing!", gate_set=cg.XMON)
423
424
425@mock.patch('cirq_google.engine.engine_client.EngineClient')
426def test_run_circuit_failed(client):
427    client().create_program.return_value = (
428        'prog',
429        qtypes.QuantumProgram(name='projects/proj/programs/prog'),
430    )
431    client().create_job.return_value = (
432        'job-id',
433        qtypes.QuantumJob(
434            name='projects/proj/programs/prog/jobs/job-id', execution_status={'state': 'READY'}
435        ),
436    )
437    client().get_job.return_value = qtypes.QuantumJob(
438        name='projects/proj/programs/prog/jobs/job-id',
439        execution_status={
440            'state': 'FAILURE',
441            'processor_name': 'myqc',
442            'failure': {'error_code': 'SYSTEM_ERROR', 'error_message': 'Not good'},
443        },
444    )
445
446    engine = cg.Engine(project_id='proj')
447    with pytest.raises(
448        RuntimeError,
449        match='Job projects/proj/programs/prog/jobs/job-id on processor'
450        ' myqc failed. SYSTEM_ERROR: Not good',
451    ):
452        engine.run(program=_CIRCUIT, gate_set=cg.XMON)
453
454
455@mock.patch('cirq_google.engine.engine_client.EngineClient')
456def test_run_circuit_failed_missing_processor_name(client):
457    client().create_program.return_value = (
458        'prog',
459        qtypes.QuantumProgram(name='projects/proj/programs/prog'),
460    )
461    client().create_job.return_value = (
462        'job-id',
463        qtypes.QuantumJob(
464            name='projects/proj/programs/prog/jobs/job-id', execution_status={'state': 'READY'}
465        ),
466    )
467    client().get_job.return_value = qtypes.QuantumJob(
468        name='projects/proj/programs/prog/jobs/job-id',
469        execution_status={
470            'state': 'FAILURE',
471            'failure': {'error_code': 'SYSTEM_ERROR', 'error_message': 'Not good'},
472        },
473    )
474
475    engine = cg.Engine(project_id='proj')
476    with pytest.raises(
477        RuntimeError,
478        match='Job projects/proj/programs/prog/jobs/job-id on processor'
479        ' UNKNOWN failed. SYSTEM_ERROR: Not good',
480    ):
481        engine.run(program=_CIRCUIT, gate_set=cg.XMON)
482
483
484@mock.patch('cirq_google.engine.engine_client.EngineClient')
485def test_run_circuit_cancelled(client):
486    client().create_program.return_value = (
487        'prog',
488        qtypes.QuantumProgram(name='projects/proj/programs/prog'),
489    )
490    client().create_job.return_value = (
491        'job-id',
492        qtypes.QuantumJob(
493            name='projects/proj/programs/prog/jobs/job-id', execution_status={'state': 'READY'}
494        ),
495    )
496    client().get_job.return_value = qtypes.QuantumJob(
497        name='projects/proj/programs/prog/jobs/job-id',
498        execution_status={
499            'state': 'CANCELLED',
500        },
501    )
502
503    engine = cg.Engine(project_id='proj')
504    with pytest.raises(
505        RuntimeError,
506        match='Job projects/proj/programs/prog/jobs/job-id failed in state CANCELLED.',
507    ):
508        engine.run(program=_CIRCUIT, gate_set=cg.XMON)
509
510
511@mock.patch('cirq_google.engine.engine_client.EngineClient')
512@mock.patch('time.sleep', return_value=None)
513def test_run_circuit_timeout(patched_time_sleep, client):
514    client().create_program.return_value = (
515        'prog',
516        qtypes.QuantumProgram(name='projects/proj/programs/prog'),
517    )
518    client().create_job.return_value = (
519        'job-id',
520        qtypes.QuantumJob(
521            name='projects/proj/programs/prog/jobs/job-id', execution_status={'state': 'READY'}
522        ),
523    )
524    client().get_job.return_value = qtypes.QuantumJob(
525        name='projects/proj/programs/prog/jobs/job-id',
526        execution_status={
527            'state': 'RUNNING',
528        },
529    )
530
531    engine = cg.Engine(project_id='project-id', timeout=600)
532    with pytest.raises(RuntimeError, match='Timed out'):
533        engine.run(program=_CIRCUIT, gate_set=cg.XMON)
534
535
536@mock.patch('cirq_google.engine.engine_client.EngineClient')
537def test_run_sweep_params(client):
538    setup_run_circuit_with_result_(client, _RESULTS)
539
540    engine = cg.Engine(project_id='proj')
541    job = engine.run_sweep(
542        program=_CIRCUIT,
543        params=[cirq.ParamResolver({'a': 1}), cirq.ParamResolver({'a': 2})],
544        gate_set=cg.XMON,
545    )
546    results = job.results()
547    assert len(results) == 2
548    for i, v in enumerate([1, 2]):
549        assert results[i].repetitions == 1
550        assert results[i].params.param_dict == {'a': v}
551        assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')}
552
553    client().create_program.assert_called_once()
554    client().create_job.assert_called_once()
555
556    run_context = v2.run_context_pb2.RunContext()
557    client().create_job.call_args[1]['run_context'].Unpack(run_context)
558    sweeps = run_context.parameter_sweeps
559    assert len(sweeps) == 2
560    for i, v in enumerate([1.0, 2.0]):
561        assert sweeps[i].repetitions == 1
562        assert sweeps[i].sweep.sweep_function.sweeps[0].single_sweep.points.points == [v]
563    client().get_job.assert_called_once()
564    client().get_job_results.assert_called_once()
565
566
567@mock.patch('cirq_google.engine.engine_client.EngineClient')
568def test_run_multiple_times(client):
569    setup_run_circuit_with_result_(client, _RESULTS)
570
571    engine = cg.Engine(project_id='proj', proto_version=cg.engine.engine.ProtoVersion.V2)
572    program = engine.create_program(program=_CIRCUIT, gate_set=cg.XMON)
573    program.run(param_resolver=cirq.ParamResolver({'a': 1}))
574    run_context = v2.run_context_pb2.RunContext()
575    client().create_job.call_args[1]['run_context'].Unpack(run_context)
576    sweeps1 = run_context.parameter_sweeps
577    job2 = program.run_sweep(repetitions=2, params=cirq.Points('a', [3, 4]))
578    client().create_job.call_args[1]['run_context'].Unpack(run_context)
579    sweeps2 = run_context.parameter_sweeps
580    results = job2.results()
581    assert engine.context.proto_version == cg.engine.engine.ProtoVersion.V2
582    assert len(results) == 2
583    for i, v in enumerate([1, 2]):
584        assert results[i].repetitions == 1
585        assert results[i].params.param_dict == {'a': v}
586        assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')}
587    assert len(sweeps1) == 1
588    assert sweeps1[0].repetitions == 1
589    points1 = sweeps1[0].sweep.sweep_function.sweeps[0].single_sweep.points
590    assert points1.points == [1]
591    assert len(sweeps2) == 1
592    assert sweeps2[0].repetitions == 2
593    assert sweeps2[0].sweep.single_sweep.points.points == [3, 4]
594    assert client().get_job.call_count == 2
595    assert client().get_job_results.call_count == 2
596
597
598@mock.patch('cirq_google.engine.engine_client.EngineClient')
599def test_run_sweep_v2(client):
600    setup_run_circuit_with_result_(client, _RESULTS_V2)
601
602    engine = cg.Engine(
603        project_id='proj',
604        proto_version=cg.engine.engine.ProtoVersion.V2,
605    )
606    job = engine.run_sweep(
607        program=_CIRCUIT, job_id='job-id', params=cirq.Points('a', [1, 2]), gate_set=cg.XMON
608    )
609    results = job.results()
610    assert len(results) == 2
611    for i, v in enumerate([1, 2]):
612        assert results[i].repetitions == 1
613        assert results[i].params.param_dict == {'a': v}
614        assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')}
615    client().create_program.assert_called_once()
616    client().create_job.assert_called_once()
617    run_context = v2.run_context_pb2.RunContext()
618    client().create_job.call_args[1]['run_context'].Unpack(run_context)
619    sweeps = run_context.parameter_sweeps
620    assert len(sweeps) == 1
621    assert sweeps[0].repetitions == 1
622    assert sweeps[0].sweep.single_sweep.points.points == [1, 2]
623    client().get_job.assert_called_once()
624    client().get_job_results.assert_called_once()
625
626
627@mock.patch('cirq_google.engine.engine_client.EngineClient')
628def test_run_batch(client):
629    setup_run_circuit_with_result_(client, _BATCH_RESULTS_V2)
630
631    engine = cg.Engine(
632        project_id='proj',
633        proto_version=cg.engine.engine.ProtoVersion.V2,
634    )
635    job = engine.run_batch(
636        gate_set=cg.XMON,
637        programs=[_CIRCUIT, _CIRCUIT2],
638        job_id='job-id',
639        params_list=[cirq.Points('a', [1, 2]), cirq.Points('a', [3, 4])],
640        processor_ids=['mysim'],
641    )
642    results = job.results()
643    assert len(results) == 4
644    for i, v in enumerate([1, 2, 3, 4]):
645        assert results[i].repetitions == 1
646        assert results[i].params.param_dict == {'a': v}
647        assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')}
648    client().create_program.assert_called_once()
649    client().create_job.assert_called_once()
650    run_context = v2.batch_pb2.BatchRunContext()
651    client().create_job.call_args[1]['run_context'].Unpack(run_context)
652    assert len(run_context.run_contexts) == 2
653    for idx, rc in enumerate(run_context.run_contexts):
654        sweeps = rc.parameter_sweeps
655        assert len(sweeps) == 1
656        assert sweeps[0].repetitions == 1
657        if idx == 0:
658            assert sweeps[0].sweep.single_sweep.points.points == [1.0, 2.0]
659        if idx == 1:
660            assert sweeps[0].sweep.single_sweep.points.points == [3.0, 4.0]
661    client().get_job.assert_called_once()
662    client().get_job_results.assert_called_once()
663
664
665@mock.patch('cirq_google.engine.engine_client.EngineClient')
666def test_run_batch_no_params(client):
667    # OK to run with no params, it should use empty sweeps for each
668    # circuit.
669    setup_run_circuit_with_result_(client, _BATCH_RESULTS_V2)
670    engine = cg.Engine(
671        project_id='proj',
672        proto_version=cg.engine.engine.ProtoVersion.V2,
673    )
674    engine.run_batch(
675        programs=[_CIRCUIT, _CIRCUIT2], gate_set=cg.XMON, job_id='job-id', processor_ids=['mysim']
676    )
677    # Validate correct number of params have been created and that they
678    # are empty sweeps.
679    run_context = v2.batch_pb2.BatchRunContext()
680    client().create_job.call_args[1]['run_context'].Unpack(run_context)
681    assert len(run_context.run_contexts) == 2
682    for rc in run_context.run_contexts:
683        sweeps = rc.parameter_sweeps
684        assert len(sweeps) == 1
685        assert sweeps[0].repetitions == 1
686        assert sweeps[0].sweep == v2.run_context_pb2.Sweep()
687
688
689def test_batch_size_validation_fails():
690    engine = cg.Engine(
691        project_id='proj',
692        proto_version=cg.engine.engine.ProtoVersion.V2,
693    )
694
695    with pytest.raises(ValueError, match='Number of circuits and sweeps'):
696        _ = engine.run_batch(
697            programs=[_CIRCUIT, _CIRCUIT2],
698            gate_set=cg.XMON,
699            job_id='job-id',
700            params_list=[
701                cirq.Points('a', [1, 2]),
702                cirq.Points('a', [3, 4]),
703                cirq.Points('a', [5, 6]),
704            ],
705            processor_ids=['mysim'],
706        )
707
708    with pytest.raises(ValueError, match='Processor id must be specified'):
709        _ = engine.run_batch(
710            programs=[_CIRCUIT, _CIRCUIT2],
711            gate_set=cg.XMON,
712            job_id='job-id',
713            params_list=[cirq.Points('a', [1, 2]), cirq.Points('a', [3, 4])],
714        )
715
716    with pytest.raises(ValueError, match='Gate set must be specified'):
717        _ = engine.run_batch(
718            programs=[_CIRCUIT, _CIRCUIT2],
719            job_id='job-id',
720            params_list=[cirq.Points('a', [1, 2]), cirq.Points('a', [3, 4])],
721            processor_ids=['mysim'],
722        )
723
724
725def test_bad_sweep_proto():
726    engine = cg.Engine(project_id='project-id', proto_version=cg.ProtoVersion.UNDEFINED)
727    program = cg.EngineProgram('proj', 'prog', engine.context)
728    with pytest.raises(ValueError, match='invalid run context proto version'):
729        program.run_sweep()
730
731
732@mock.patch('cirq_google.engine.engine_client.EngineClient')
733def test_run_calibration(client):
734    setup_run_circuit_with_result_(client, _CALIBRATION_RESULTS_V2)
735
736    engine = cg.Engine(
737        project_id='proj',
738        proto_version=cg.engine.engine.ProtoVersion.V2,
739    )
740    q1 = cirq.GridQubit(2, 3)
741    q2 = cirq.GridQubit(2, 4)
742    layer1 = cg.CalibrationLayer('xeb', cirq.Circuit(cirq.CZ(q1, q2)), {'num_layers': 42})
743    layer2 = cg.CalibrationLayer(
744        'readout', cirq.Circuit(cirq.measure(q1, q2)), {'num_samples': 4242}
745    )
746    job = engine.run_calibration(
747        gate_set=cg.FSIM_GATESET, layers=[layer1, layer2], job_id='job-id', processor_id='mysim'
748    )
749    results = job.calibration_results()
750    assert len(results) == 2
751    assert results[0].code == v2.calibration_pb2.SUCCESS
752    assert results[0].error_message == 'First success'
753    assert results[0].token == 'abc123'
754    assert len(results[0].metrics) == 1
755    assert len(results[0].metrics['fidelity']) == 1
756    assert results[0].metrics['fidelity'][(q1, q2)] == [0.75]
757    assert results[1].code == v2.calibration_pb2.SUCCESS
758    assert results[1].error_message == 'Second success'
759
760    # assert label is correct
761    client().create_job.assert_called_once_with(
762        project_id='proj',
763        program_id='prog',
764        job_id='job-id',
765        processor_ids=['mysim'],
766        run_context=_to_any(v2.run_context_pb2.RunContext()),
767        description=None,
768        labels={'calibration': ''},
769    )
770
771
772def test_run_calibration_validation_fails():
773    engine = cg.Engine(
774        project_id='proj',
775        proto_version=cg.engine.engine.ProtoVersion.V2,
776    )
777    q1 = cirq.GridQubit(2, 3)
778    q2 = cirq.GridQubit(2, 4)
779    layer1 = cg.CalibrationLayer('xeb', cirq.Circuit(cirq.CZ(q1, q2)), {'num_layers': 42})
780    layer2 = cg.CalibrationLayer(
781        'readout', cirq.Circuit(cirq.measure(q1, q2)), {'num_samples': 4242}
782    )
783
784    with pytest.raises(ValueError, match='Processor id must be specified'):
785        _ = engine.run_calibration(layers=[layer1, layer2], gate_set=cg.XMON, job_id='job-id')
786
787    with pytest.raises(ValueError, match='Gate set must be specified'):
788        _ = engine.run_calibration(
789            layers=[layer1, layer2], processor_ids=['mysim'], job_id='job-id'
790        )
791    with pytest.raises(ValueError, match='processor_id and processor_ids'):
792        _ = engine.run_calibration(
793            layers=[layer1, layer2],
794            processor_ids=['mysim'],
795            processor_id='mysim',
796            gate_set=cg.XMON,
797            job_id='job-id',
798        )
799
800
801@mock.patch('cirq_google.engine.engine_client.EngineClient')
802def test_bad_result_proto(client):
803    result = any_pb2.Any()
804    result.CopyFrom(_RESULTS_V2)
805    result.type_url = 'type.googleapis.com/unknown'
806    setup_run_circuit_with_result_(client, result)
807
808    engine = cg.Engine(project_id='project-id', proto_version=cg.engine.engine.ProtoVersion.V2)
809    job = engine.run_sweep(
810        program=_CIRCUIT, job_id='job-id', params=cirq.Points('a', [1, 2]), gate_set=cg.XMON
811    )
812    with pytest.raises(ValueError, match='invalid result proto version'):
813        job.results()
814
815
816def test_bad_program_proto():
817    engine = cg.Engine(
818        project_id='project-id', proto_version=cg.engine.engine.ProtoVersion.UNDEFINED
819    )
820    with pytest.raises(ValueError, match='invalid program proto version'):
821        engine.run_sweep(program=_CIRCUIT, gate_set=cg.XMON)
822    with pytest.raises(ValueError, match='invalid program proto version'):
823        engine.create_program(_CIRCUIT, gate_set=cg.XMON)
824
825
826def test_get_program():
827    assert cg.Engine(project_id='proj').get_program('prog').program_id == 'prog'
828
829
830@mock.patch('cirq_google.engine.engine_client.EngineClient.list_programs')
831def test_list_programs(list_programs):
832    prog1 = qtypes.QuantumProgram(name='projects/proj/programs/prog-YBGR48THF3JHERZW200804')
833    prog2 = qtypes.QuantumProgram(name='projects/otherproj/programs/prog-V3ZRTV6TTAFNTYJV200804')
834    list_programs.return_value = [prog1, prog2]
835
836    result = cg.Engine(project_id='proj').list_programs()
837    list_programs.assert_called_once_with(
838        'proj', created_after=None, created_before=None, has_labels=None
839    )
840    assert [(p.program_id, p.project_id, p._program) for p in result] == [
841        ('prog-YBGR48THF3JHERZW200804', 'proj', prog1),
842        ('prog-V3ZRTV6TTAFNTYJV200804', 'otherproj', prog2),
843    ]
844
845
846@mock.patch('cirq_google.engine.engine_client.EngineClient')
847def test_create_program(client):
848    client().create_program.return_value = ('prog', qtypes.QuantumProgram())
849    result = cg.Engine(project_id='proj').create_program(_CIRCUIT, 'prog', gate_set=cg.XMON)
850    client().create_program.assert_called_once()
851    assert result.program_id == 'prog'
852
853
854@mock.patch('cirq_google.engine.engine_client.EngineClient.list_jobs')
855def test_list_jobs(list_jobs):
856    job1 = qtypes.QuantumJob(name='projects/proj/programs/prog1/jobs/job1')
857    job2 = qtypes.QuantumJob(name='projects/proj/programs/prog2/jobs/job2')
858    list_jobs.return_value = [job1, job2]
859
860    ctx = EngineContext()
861    result = cg.Engine(project_id='proj', context=ctx).list_jobs()
862    list_jobs.assert_called_once_with(
863        'proj',
864        None,
865        created_after=None,
866        created_before=None,
867        has_labels=None,
868        execution_states=None,
869    )
870    assert [(j.project_id, j.program_id, j.job_id, j.context, j._job) for j in result] == [
871        ('proj', 'prog1', 'job1', ctx, job1),
872        ('proj', 'prog2', 'job2', ctx, job2),
873    ]
874
875
876@mock.patch('cirq_google.engine.engine_client.EngineClient.list_processors')
877def test_list_processors(list_processors):
878    processor1 = qtypes.QuantumProcessor(name='projects/proj/processors/xmonsim')
879    processor2 = qtypes.QuantumProcessor(name='projects/proj/processors/gmonsim')
880    list_processors.return_value = [processor1, processor2]
881
882    result = cg.Engine(project_id='proj').list_processors()
883    list_processors.assert_called_once_with('proj')
884    assert [p.processor_id for p in result] == ['xmonsim', 'gmonsim']
885
886
887def test_get_processor():
888    assert cg.Engine(project_id='proj').get_processor('xmonsim').processor_id == 'xmonsim'
889
890
891@mock.patch('cirq_google.engine.engine_client.EngineClient')
892def test_sampler(client):
893    setup_run_circuit_with_result_(client, _RESULTS)
894
895    engine = cg.Engine(project_id='proj')
896    sampler = engine.sampler(processor_id='tmp', gate_set=cg.XMON)
897    results = sampler.run_sweep(
898        program=_CIRCUIT, params=[cirq.ParamResolver({'a': 1}), cirq.ParamResolver({'a': 2})]
899    )
900    assert len(results) == 2
901    for i, v in enumerate([1, 2]):
902        assert results[i].repetitions == 1
903        assert results[i].params.param_dict == {'a': v}
904        assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')}
905    assert client().create_program.call_args[0][0] == 'proj'
906
907
908@mock.patch('cirq_google.engine.client.quantum.QuantumEngineServiceClient')
909def test_get_engine(build):
910    # Default project id present.
911    with mock.patch.dict(
912        os.environ,
913        {
914            'GOOGLE_CLOUD_PROJECT': 'project!',
915        },
916        clear=True,
917    ):
918        eng = cirq_google.get_engine()
919        assert eng.project_id == 'project!'
920
921    # Nothing present.
922    with mock.patch.dict(os.environ, {}, clear=True):
923        with pytest.raises(EnvironmentError, match='GOOGLE_CLOUD_PROJECT'):
924            _ = cirq_google.get_engine()
925        _ = cirq_google.get_engine('project!')
926
927
928@mock.patch('cirq_google.engine.engine_client.EngineClient.get_processor')
929def test_get_engine_device(get_processor):
930    device_spec = _to_any(
931        Merge(
932            """
933valid_gate_sets: [{
934    name: 'test_set',
935    valid_gates: [{
936        id: 'x',
937        number_of_qubits: 1,
938        gate_duration_picos: 1000,
939        valid_targets: ['1q_targets']
940    }]
941}],
942valid_qubits: ['0_0', '1_1'],
943valid_targets: [{
944    name: '1q_targets',
945    target_ordering: SYMMETRIC,
946    targets: [{
947        ids: ['0_0']
948    }]
949}]
950""",
951            v2.device_pb2.DeviceSpecification(),
952        )
953    )
954
955    gate_set = cg.SerializableGateSet(
956        gate_set_name='x_gate_set',
957        serializers=[cg.GateOpSerializer(gate_type=cirq.XPowGate, serialized_gate_id='x', args=[])],
958        deserializers=[
959            cg.GateOpDeserializer(serialized_gate_id='x', gate_constructor=cirq.XPowGate, args=[])
960        ],
961    )
962
963    get_processor.return_value = qtypes.QuantumProcessor(device_spec=device_spec)
964    device = cirq_google.get_engine_device('rainbow', 'project', gatesets=[gate_set])
965    assert set(device.qubits) == {cirq.GridQubit(0, 0), cirq.GridQubit(1, 1)}
966    device.validate_operation(cirq.X(cirq.GridQubit(0, 0)))
967    with pytest.raises(ValueError):
968        device.validate_operation(cirq.X(cirq.GridQubit(1, 2)))
969    with pytest.raises(ValueError):
970        device.validate_operation(cirq.Y(cirq.GridQubit(0, 0)))
971
972
973_CALIBRATION = qtypes.QuantumCalibration(
974    name='projects/a/processors/p/calibrations/1562715599',
975    timestamp=_to_timestamp('2019-07-09T23:39:59Z'),
976    data=_to_any(
977        Merge(
978            """
979    timestamp_ms: 1562544000021,
980    metrics: [
981    {
982        name: 't1',
983        targets: ['0_0'],
984        values: [{
985            double_val: 321
986        }]
987    }, {
988        name: 'globalMetric',
989        values: [{
990            int32_val: 12300
991        }]
992    }]
993""",
994            v2.metrics_pb2.MetricsSnapshot(),
995        )
996    ),
997)
998
999
1000@mock.patch('cirq_google.engine.engine_client.EngineClient.get_current_calibration')
1001def test_get_engine_calibration(get_current_calibration):
1002    get_current_calibration.return_value = _CALIBRATION
1003    calibration = cirq_google.get_engine_calibration('rainbow', 'project')
1004    assert calibration.timestamp == 1562544000021
1005    assert set(calibration.keys()) == {'t1', 'globalMetric'}
1006    assert calibration['t1'][(cirq.GridQubit(0, 0),)] == [321.0]
1007    get_current_calibration.assert_called_once_with('project', 'rainbow')
1008