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