From c62b6bd10ab1a434e72ac5076f8095b0d583f809 Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Mon, 13 Dec 2021 05:45:56 -0800 Subject: [PATCH 1/6] Add classes to aid in providing simulated versions of devices - Add a validator that checks (some of) the common errors occuring when sending circuits to quantum engine. This likely needs more checks, but these can be added over time to match engine behavior. - Adds convenience functions to create SimulatedLocalEngine instances from a device or from a device specification proto. - Adds proto device specifications for example Sycamore devices that can be loaded without API access. --- .../rainbow_12_10_2021_device_spec.proto.txt | 610 +++++++++++++ .../weber_12_10_2021_device_spec.proto.txt | 856 ++++++++++++++++++ .../cirq_google/engine/engine_validator.py | 115 +++ .../engine/engine_validator_test.py | 75 ++ .../engine/simulated_local_processor.py | 18 +- .../engine/virtual_engine_factory.py | 156 ++++ .../engine/virtual_engine_factory_test.py | 56 ++ 7 files changed, 1885 insertions(+), 1 deletion(-) create mode 100644 cirq-google/cirq_google/devices/specifications/rainbow_12_10_2021_device_spec.proto.txt create mode 100644 cirq-google/cirq_google/devices/specifications/weber_12_10_2021_device_spec.proto.txt create mode 100644 cirq-google/cirq_google/engine/engine_validator.py create mode 100644 cirq-google/cirq_google/engine/engine_validator_test.py create mode 100644 cirq-google/cirq_google/engine/virtual_engine_factory.py create mode 100644 cirq-google/cirq_google/engine/virtual_engine_factory_test.py diff --git a/cirq-google/cirq_google/devices/specifications/rainbow_12_10_2021_device_spec.proto.txt b/cirq-google/cirq_google/devices/specifications/rainbow_12_10_2021_device_spec.proto.txt new file mode 100644 index 00000000000..1a57f35ecb0 --- /dev/null +++ b/cirq-google/cirq_google/devices/specifications/rainbow_12_10_2021_device_spec.proto.txt @@ -0,0 +1,610 @@ +valid_gate_sets { + name: "sycamore" + valid_gates { + id: "syc" + number_of_qubits: 2 + valid_args { + name: "phase_match" + type: STRING + } + gate_duration_picos: 12000 + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "xy_pi" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "xy_half_pi" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_gate_sets { + name: "sqrt_iswap" + valid_gates { + id: "fsim_pi_4" + number_of_qubits: 2 + valid_args { + name: "phase_match" + type: STRING + } + gate_duration_picos: 32000 + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "inv_fsim_pi_4" + number_of_qubits: 2 + valid_args { + name: "phase_match" + type: STRING + } + gate_duration_picos: 32000 + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_gate_sets { + name: "fsim" + valid_gates { + id: "fsim" + number_of_qubits: 2 + valid_args { + name: "theta" + type: FLOAT + } + valid_args { + name: "phi" + type: FLOAT + } + valid_args { + name: "phase_match" + type: STRING + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_gate_sets { + name: "xmon" + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "cz" + number_of_qubits: 2 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "phase_match" + type: STRING + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } +} +valid_gate_sets { + name: "pulse" + valid_gates { + id: "coupler_pulse" + number_of_qubits: 2 + valid_args { + name: "coupling_mhz" + type: FLOAT + } + valid_args { + name: "hold_time_ns" + type: FLOAT + } + valid_args { + name: "rise_time_ns" + type: FLOAT + } + valid_args { + name: "padding_time_ns" + type: FLOAT + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "fsim" + number_of_qubits: 2 + valid_args { + name: "theta" + type: FLOAT + } + valid_args { + name: "phi" + type: FLOAT + } + valid_args { + name: "phase_match" + type: STRING + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_qubits: "3_2" +valid_qubits: "4_1" +valid_qubits: "4_2" +valid_qubits: "4_3" +valid_qubits: "5_0" +valid_qubits: "5_1" +valid_qubits: "5_2" +valid_qubits: "5_3" +valid_qubits: "5_4" +valid_qubits: "6_1" +valid_qubits: "6_2" +valid_qubits: "6_3" +valid_qubits: "6_4" +valid_qubits: "6_5" +valid_qubits: "7_2" +valid_qubits: "7_3" +valid_qubits: "7_4" +valid_qubits: "7_5" +valid_qubits: "7_6" +valid_qubits: "8_3" +valid_qubits: "8_4" +valid_qubits: "8_5" +valid_qubits: "9_4" +valid_targets { + name: "meas_targets" + target_ordering: SUBSET_PERMUTATION +} +valid_targets { + name: "2_qubit_targets" + target_ordering: SYMMETRIC + targets { + ids: "3_2" + ids: "4_2" + } + targets { + ids: "4_1" + ids: "4_2" + } + targets { + ids: "4_1" + ids: "5_1" + } + targets { + ids: "4_2" + ids: "4_3" + } + targets { + ids: "4_2" + ids: "5_2" + } + targets { + ids: "4_3" + ids: "5_3" + } + targets { + ids: "5_0" + ids: "5_1" + } + targets { + ids: "5_1" + ids: "5_2" + } + targets { + ids: "5_1" + ids: "6_1" + } + targets { + ids: "5_2" + ids: "5_3" + } + targets { + ids: "5_2" + ids: "6_2" + } + targets { + ids: "5_3" + ids: "5_4" + } + targets { + ids: "5_3" + ids: "6_3" + } + targets { + ids: "5_4" + ids: "6_4" + } + targets { + ids: "6_1" + ids: "6_2" + } + targets { + ids: "6_2" + ids: "6_3" + } + targets { + ids: "6_2" + ids: "7_2" + } + targets { + ids: "6_3" + ids: "6_4" + } + targets { + ids: "6_3" + ids: "7_3" + } + targets { + ids: "6_4" + ids: "6_5" + } + targets { + ids: "6_4" + ids: "7_4" + } + targets { + ids: "6_5" + ids: "7_5" + } + targets { + ids: "7_2" + ids: "7_3" + } + targets { + ids: "7_3" + ids: "7_4" + } + targets { + ids: "7_3" + ids: "8_3" + } + targets { + ids: "7_4" + ids: "7_5" + } + targets { + ids: "7_4" + ids: "8_4" + } + targets { + ids: "7_5" + ids: "7_6" + } + targets { + ids: "7_5" + ids: "8_5" + } + targets { + ids: "8_3" + ids: "8_4" + } + targets { + ids: "8_4" + ids: "8_5" + } + targets { + ids: "8_4" + ids: "9_4" + } +} + diff --git a/cirq-google/cirq_google/devices/specifications/weber_12_10_2021_device_spec.proto.txt b/cirq-google/cirq_google/devices/specifications/weber_12_10_2021_device_spec.proto.txt new file mode 100644 index 00000000000..b9aa1a9ee67 --- /dev/null +++ b/cirq-google/cirq_google/devices/specifications/weber_12_10_2021_device_spec.proto.txt @@ -0,0 +1,856 @@ +valid_gate_sets { + name: "sycamore" + valid_gates { + id: "syc" + number_of_qubits: 2 + valid_args { + name: "phase_match" + type: STRING + } + gate_duration_picos: 12000 + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "xy_pi" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "xy_half_pi" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_gate_sets { + name: "sqrt_iswap" + valid_gates { + id: "fsim_pi_4" + number_of_qubits: 2 + valid_args { + name: "phase_match" + type: STRING + } + gate_duration_picos: 32000 + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "inv_fsim_pi_4" + number_of_qubits: 2 + valid_args { + name: "phase_match" + type: STRING + } + gate_duration_picos: 32000 + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_gate_sets { + name: "fsim" + valid_gates { + id: "fsim" + number_of_qubits: 2 + valid_args { + name: "theta" + type: FLOAT + } + valid_args { + name: "phi" + type: FLOAT + } + valid_args { + name: "phase_match" + type: STRING + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_gate_sets { + name: "xmon" + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "cz" + number_of_qubits: 2 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "phase_match" + type: STRING + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } +} +valid_gate_sets { + name: "pulse" + valid_gates { + id: "coupler_pulse" + number_of_qubits: 2 + valid_args { + name: "coupling_mhz" + type: FLOAT + } + valid_args { + name: "hold_time_ns" + type: FLOAT + } + valid_args { + name: "rise_time_ns" + type: FLOAT + } + valid_args { + name: "padding_time_ns" + type: FLOAT + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "fsim" + number_of_qubits: 2 + valid_args { + name: "theta" + type: FLOAT + } + valid_args { + name: "phi" + type: FLOAT + } + valid_args { + name: "phase_match" + type: STRING + } + valid_targets: "2_qubit_targets" + } + valid_gates { + id: "xy" + number_of_qubits: 1 + valid_args { + name: "axis_half_turns" + type: FLOAT + } + valid_args { + name: "half_turns" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "z" + number_of_qubits: 1 + valid_args { + name: "half_turns" + type: FLOAT + } + valid_args { + name: "type" + type: STRING + } + } + valid_gates { + id: "xyz" + number_of_qubits: 1 + valid_args { + name: "x_exponent" + type: FLOAT + } + valid_args { + name: "z_exponent" + type: FLOAT + } + valid_args { + name: "axis_phase_exponent" + type: FLOAT + } + gate_duration_picos: 25000 + } + valid_gates { + id: "meas" + valid_args { + name: "key" + type: STRING + } + valid_args { + name: "invert_mask" + type: REPEATED_BOOLEAN + } + gate_duration_picos: 4000000 + valid_targets: "meas_targets" + } + valid_gates { + id: "wait" + number_of_qubits: 1 + valid_args { + name: "nanos" + type: FLOAT + } + } + valid_gates { + id: "circuit" + } +} +valid_qubits: "0_5" +valid_qubits: "0_6" +valid_qubits: "1_4" +valid_qubits: "1_5" +valid_qubits: "1_6" +valid_qubits: "1_7" +valid_qubits: "2_4" +valid_qubits: "2_5" +valid_qubits: "2_6" +valid_qubits: "2_7" +valid_qubits: "2_8" +valid_qubits: "3_2" +valid_qubits: "3_3" +valid_qubits: "3_4" +valid_qubits: "3_5" +valid_qubits: "3_6" +valid_qubits: "3_7" +valid_qubits: "3_8" +valid_qubits: "3_9" +valid_qubits: "4_1" +valid_qubits: "4_2" +valid_qubits: "4_3" +valid_qubits: "4_4" +valid_qubits: "4_5" +valid_qubits: "4_6" +valid_qubits: "4_7" +valid_qubits: "4_8" +valid_qubits: "4_9" +valid_qubits: "5_0" +valid_qubits: "5_1" +valid_qubits: "5_2" +valid_qubits: "5_3" +valid_qubits: "5_4" +valid_qubits: "5_5" +valid_qubits: "5_6" +valid_qubits: "5_7" +valid_qubits: "5_8" +valid_qubits: "6_1" +valid_qubits: "6_2" +valid_qubits: "6_3" +valid_qubits: "6_4" +valid_qubits: "6_5" +valid_qubits: "6_6" +valid_qubits: "6_7" +valid_qubits: "7_2" +valid_qubits: "7_3" +valid_qubits: "7_4" +valid_qubits: "7_5" +valid_qubits: "7_6" +valid_qubits: "8_3" +valid_qubits: "8_4" +valid_qubits: "8_5" +valid_qubits: "9_4" +valid_targets { + name: "meas_targets" + target_ordering: SUBSET_PERMUTATION +} +valid_targets { + name: "2_qubit_targets" + target_ordering: SYMMETRIC + targets { + ids: "0_5" + ids: "0_6" + } + targets { + ids: "0_5" + ids: "1_5" + } + targets { + ids: "0_6" + ids: "1_6" + } + targets { + ids: "1_4" + ids: "1_5" + } + targets { + ids: "1_4" + ids: "2_4" + } + targets { + ids: "1_5" + ids: "1_6" + } + targets { + ids: "1_5" + ids: "2_5" + } + targets { + ids: "1_6" + ids: "1_7" + } + targets { + ids: "1_6" + ids: "2_6" + } + targets { + ids: "1_7" + ids: "2_7" + } + targets { + ids: "2_4" + ids: "2_5" + } + targets { + ids: "2_4" + ids: "3_4" + } + targets { + ids: "2_5" + ids: "2_6" + } + targets { + ids: "2_5" + ids: "3_5" + } + targets { + ids: "2_6" + ids: "2_7" + } + targets { + ids: "2_6" + ids: "3_6" + } + targets { + ids: "2_7" + ids: "2_8" + } + targets { + ids: "2_7" + ids: "3_7" + } + targets { + ids: "2_8" + ids: "3_8" + } + targets { + ids: "3_2" + ids: "3_3" + } + targets { + ids: "3_2" + ids: "4_2" + } + targets { + ids: "3_3" + ids: "3_4" + } + targets { + ids: "3_3" + ids: "4_3" + } + targets { + ids: "3_4" + ids: "3_5" + } + targets { + ids: "3_4" + ids: "4_4" + } + targets { + ids: "3_5" + ids: "3_6" + } + targets { + ids: "3_5" + ids: "4_5" + } + targets { + ids: "3_6" + ids: "3_7" + } + targets { + ids: "3_6" + ids: "4_6" + } + targets { + ids: "3_7" + ids: "3_8" + } + targets { + ids: "3_7" + ids: "4_7" + } + targets { + ids: "3_8" + ids: "3_9" + } + targets { + ids: "3_8" + ids: "4_8" + } + targets { + ids: "3_9" + ids: "4_9" + } + targets { + ids: "4_1" + ids: "4_2" + } + targets { + ids: "4_1" + ids: "5_1" + } + targets { + ids: "4_2" + ids: "4_3" + } + targets { + ids: "4_2" + ids: "5_2" + } + targets { + ids: "4_3" + ids: "4_4" + } + targets { + ids: "4_3" + ids: "5_3" + } + targets { + ids: "4_4" + ids: "4_5" + } + targets { + ids: "4_4" + ids: "5_4" + } + targets { + ids: "4_5" + ids: "4_6" + } + targets { + ids: "4_5" + ids: "5_5" + } + targets { + ids: "4_6" + ids: "4_7" + } + targets { + ids: "4_6" + ids: "5_6" + } + targets { + ids: "4_7" + ids: "4_8" + } + targets { + ids: "4_7" + ids: "5_7" + } + targets { + ids: "4_8" + ids: "4_9" + } + targets { + ids: "4_8" + ids: "5_8" + } + targets { + ids: "5_0" + ids: "5_1" + } + targets { + ids: "5_1" + ids: "5_2" + } + targets { + ids: "5_1" + ids: "6_1" + } + targets { + ids: "5_2" + ids: "5_3" + } + targets { + ids: "5_2" + ids: "6_2" + } + targets { + ids: "5_3" + ids: "5_4" + } + targets { + ids: "5_3" + ids: "6_3" + } + targets { + ids: "5_4" + ids: "5_5" + } + targets { + ids: "5_4" + ids: "6_4" + } + targets { + ids: "5_5" + ids: "5_6" + } + targets { + ids: "5_5" + ids: "6_5" + } + targets { + ids: "5_6" + ids: "5_7" + } + targets { + ids: "5_6" + ids: "6_6" + } + targets { + ids: "5_7" + ids: "5_8" + } + targets { + ids: "5_7" + ids: "6_7" + } + targets { + ids: "6_1" + ids: "6_2" + } + targets { + ids: "6_2" + ids: "6_3" + } + targets { + ids: "6_2" + ids: "7_2" + } + targets { + ids: "6_3" + ids: "6_4" + } + targets { + ids: "6_3" + ids: "7_3" + } + targets { + ids: "6_4" + ids: "6_5" + } + targets { + ids: "6_4" + ids: "7_4" + } + targets { + ids: "6_5" + ids: "6_6" + } + targets { + ids: "6_5" + ids: "7_5" + } + targets { + ids: "6_6" + ids: "6_7" + } + targets { + ids: "6_6" + ids: "7_6" + } + targets { + ids: "7_2" + ids: "7_3" + } + targets { + ids: "7_3" + ids: "7_4" + } + targets { + ids: "7_3" + ids: "8_3" + } + targets { + ids: "7_4" + ids: "7_5" + } + targets { + ids: "7_4" + ids: "8_4" + } + targets { + ids: "7_5" + ids: "7_6" + } + targets { + ids: "7_5" + ids: "8_5" + } + targets { + ids: "8_3" + ids: "8_4" + } + targets { + ids: "8_4" + ids: "8_5" + } + targets { + ids: "8_4" + ids: "9_4" + } +} + diff --git a/cirq-google/cirq_google/engine/engine_validator.py b/cirq-google/cirq_google/engine/engine_validator.py new file mode 100644 index 00000000000..fb62b626985 --- /dev/null +++ b/cirq-google/cirq_google/engine/engine_validator.py @@ -0,0 +1,115 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from typing import Callable, List, Sequence, Union +from google.protobuf import any_pb2 + +import cirq +from cirq_google.engine.validating_sampler import VALIDATOR_TYPE +from cirq_google.serialization.serializer import Serializer +from cirq_google.api import v2 + +MAX_MESSAGE_SIZE = 10_000_000 +MAX_MOMENTS = 10000 +MAX_TOTAL_REPETITIONS = 5_000_000 + +GATE_SET_VALIDATOR_TYPE = Callable[ + [Sequence[cirq.AbstractCircuit], List[cirq.Sweepable], int, 'Serializer'], + None, +] + + +def _validate_depth( + circuits: Sequence[cirq.AbstractCircuit], + max_moments: int = MAX_MOMENTS, +) -> None: + """Validate that the depth of the circuit is not too long (too many moments).""" + for circuit in circuits: + if len(circuit) > max_moments: + raise RuntimeError(f'Provided circuit exceeds the limit of {max_moments} moments.') + + +def _verify_reps( + sweeps: Sequence[cirq.Sweepable], + repetitions: Union[int, List[int]], + max_repetitions: int = MAX_TOTAL_REPETITIONS, +) -> None: + """Verify that the total number of repetitions is under the limit.""" + total_reps = 0 + for idx, sweep in enumerate(sweeps): + if isinstance(repetitions, List): + total_reps += len(list(cirq.to_resolvers(sweep))) * repetitions[idx] + else: + total_reps += len(list(cirq.to_resolvers(sweep))) * repetitions + if total_reps > max_repetitions: + raise RuntimeError( + f'No requested processors currently support the number of requested total repetitions.' + ) + + +def _verify_measurements(circuits): + """Verify the circuit has appropriate measurements.""" + for circuit in circuits: + has_measurement = any( + isinstance(op.gate, cirq.MeasurementGate) for moment in circuit for op in moment + ) + if not has_measurement: + raise RuntimeError('Code must measure at least one qubit.') + + +def validate_gate_set( + circuits: Sequence[cirq.AbstractCircuit], + sweeps: List[cirq.Sweepable], + repetitions: int, + gate_set: Serializer, + max_size: int = MAX_MESSAGE_SIZE, +) -> None: + """Validate that the message size is below the maximum size limit.""" + batch = v2.batch_pb2.BatchProgram() + packed = any_pb2.Any() + for circuit in circuits: + gate_set.serialize(circuit, msg=batch.programs.add()) + packed.Pack(batch) + message_size = len(packed.SerializeToString()) + if message_size > max_size: + raise RuntimeError("INVALID_PROGRAM: Program too long.") + + +def create_gate_set_validator(max_size: int = MAX_MESSAGE_SIZE) -> GATE_SET_VALIDATOR_TYPE: + """Creates a Callanle gate set validator with a set message size.""" + return lambda c, s, r, g: validate_gate_set(c, s, r, g, max_size) + + +def validate_for_engine( + circuits: Sequence[cirq.AbstractCircuit], + sweeps: Sequence[cirq.Sweepable], + repetitions: Union[int, List[int]], + max_moments: int = MAX_MOMENTS, + max_repetitions: int = MAX_TOTAL_REPETITIONS, + max_duration_ns: int = 55000, +) -> None: + """Validate a circuit and sweeps for sending to the Quantum Engine API.""" + _verify_reps(sweeps, repetitions, max_repetitions) + _validate_depth(circuits, max_moments) + _verify_measurements(circuits) + + +def create_engine_validator( + max_moments: int = MAX_MOMENTS, + max_repetitions: int = MAX_TOTAL_REPETITIONS, + max_duration_ns: int = 55000, +) -> VALIDATOR_TYPE: + """Creates a Callanle gate set validator with a set message size.""" + return lambda c, s, r: validate_for_engine( + c, s, r, max_moments, max_repetitions, max_duration_ns + ) diff --git a/cirq-google/cirq_google/engine/engine_validator_test.py b/cirq-google/cirq_google/engine/engine_validator_test.py new file mode 100644 index 00000000000..020923e7847 --- /dev/null +++ b/cirq-google/cirq_google/engine/engine_validator_test.py @@ -0,0 +1,75 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + +import cirq +import cirq_google as cg +import cirq_google.engine.engine_validator as engine_validator + +SERIALIZABLE_GATE_DOMAIN = { + cirq.X: 1, + cirq.Y: 1, + cirq.Z: 1, + cirq.S: 1, + cirq.T: 1, + cirq.CZ: 2, +} + + +def test_validate_gate_set(): + circuit = cirq.testing.random_circuit( + cirq.GridQubit.rect(5, 5), + n_moments=10, + op_density=1.0, + gate_domain=SERIALIZABLE_GATE_DOMAIN, + ) + + engine_validator.validate_gate_set( + [circuit] * 5, [{}] * 5, 1000, cg.FSIM_GATESET, max_size=100000 + ) + + with pytest.raises(RuntimeError, match='Program too long'): + engine_validator.validate_gate_set( + [circuit] * 10, [{}] * 10, 1000, cg.FSIM_GATESET, max_size=100000 + ) + + +def test_validate_for_engine(): + circuit = cirq.testing.random_circuit( + cirq.GridQubit.rect(5, 5), + n_moments=10, + op_density=1.0, + gate_domain=SERIALIZABLE_GATE_DOMAIN, + ) + long_circuit = cirq.Circuit([cirq.X(cirq.GridQubit(0, 0))] * 10001) + + with pytest.raises(RuntimeError, match='Provided circuit exceeds the limit'): + engine_validator.validate_for_engine([long_circuit], [{}], 1000) + + with pytest.raises(RuntimeError, match='the number of requested total repetitions'): + engine_validator.validate_for_engine([circuit], [{}], 10_000_000) + + with pytest.raises(RuntimeError, match='the number of requested total repetitions'): + engine_validator.validate_for_engine([circuit] * 6, [{}] * 6, 1_000_000) + + with pytest.raises(RuntimeError, match='the number of requested total repetitions'): + engine_validator.validate_for_engine( + [circuit] * 6, [{}] * 6, [4_000_000, 2_000_000, 1, 1, 1, 1] + ) + + +def test_validate_for_engine_no_meas(): + circuit = cirq.Circuit(cirq.X(cirq.GridQubit(0, 0))) + with pytest.raises(RuntimeError, match='Code must measure at least one qubit.'): + engine_validator.validate_for_engine([circuit] * 6, [{}] * 6, 1_000) diff --git a/cirq-google/cirq_google/engine/simulated_local_processor.py b/cirq-google/cirq_google/engine/simulated_local_processor.py index 454e4b94169..4bdaef84581 100644 --- a/cirq-google/cirq_google/engine/simulated_local_processor.py +++ b/cirq-google/cirq_google/engine/simulated_local_processor.py @@ -13,7 +13,7 @@ # limitations under the License. import datetime -from typing import Dict, Iterable, List, Optional, Sequence, TYPE_CHECKING, Union +from typing import Callable, Dict, Iterable, List, Optional, Sequence, TYPE_CHECKING, Union import cirq @@ -25,6 +25,7 @@ from cirq_google.engine.local_simulation_type import LocalSimulationType from cirq_google.engine.simulated_local_job import SimulatedLocalJob from cirq_google.engine.simulated_local_program import SimulatedLocalProgram +from cirq_google.serialization.circuit_serializer import CIRCUIT_SERIALIZER import cirq_google.engine.validating_sampler as validating_sampler @@ -36,6 +37,11 @@ 'type.googleapis.com/cirq.google.api.v2.BatchProgram', ] +GATE_SET_VALIDATOR_TYPE = Callable[ + [Sequence[cirq.AbstractCircuit], List[cirq.Sweepable], int, 'Serializer'], + None, +] + def _date_to_timestamp( union_time: Optional[Union[datetime.datetime, datetime.date, int]] @@ -69,6 +75,8 @@ class SimulatedLocalProcessor(AbstractLocalProcessor): device: An optional device, for validation of qubit connectivity. validator: A Callable that can validate additional characteristics beyond the device, such as serialization, repetition limits, etc. + gate_set_validator: A callable that can validate a circuit and sweeps + based on the given serializer. calibrations: A dictionary of calibration metrics keyed by epoch seconds that can be returned by the processor. """ @@ -79,6 +87,7 @@ def __init__( sampler: cirq.Sampler = cirq.Simulator(), device: cirq.Device = cirq.UNCONSTRAINED_DEVICE, validator: validating_sampler.VALIDATOR_TYPE = None, + gate_set_validator: GATE_SET_VALIDATOR_TYPE = None, simulation_type: LocalSimulationType = LocalSimulationType.SYNCHRONOUS, calibrations: Optional[Dict[int, calibration.Calibration]] = None, **kwargs, @@ -87,6 +96,7 @@ def __init__( self._calibrations = calibrations or {} self._device = device self._simulation_type = simulation_type + self._gate_set_validator = gate_set_validator or (lambda a, b, c, d: None) self._validator = validator self._sampler = validating_sampler.ValidatingSampler( device=self._device, validator=self._validator, sampler=sampler @@ -200,6 +210,9 @@ def run_batch( program_id = self._create_id(id_type='program') if job_id is None: job_id = self._create_id(id_type='job') + if gate_set is None: + gate_set = CIRCUIT_SERIALIZER + self._gate_set_validator(programs, params_list or [{}], repetitions, gate_set) self._programs[program_id] = SimulatedLocalProgram( program_id=program_id, simulation_type=self._simulation_type, @@ -287,6 +300,9 @@ def run_sweep( program_id = self._create_id(id_type='program') if job_id is None: job_id = self._create_id(id_type='job') + if gate_set is None: + gate_set = CIRCUIT_SERIALIZER + self._gate_set_validator([program], [params], repetitions, gate_set) self._programs[program_id] = SimulatedLocalProgram( program_id=program_id, simulation_type=self._simulation_type, diff --git a/cirq-google/cirq_google/engine/virtual_engine_factory.py b/cirq-google/cirq_google/engine/virtual_engine_factory.py new file mode 100644 index 00000000000..0882940061e --- /dev/null +++ b/cirq-google/cirq_google/engine/virtual_engine_factory.py @@ -0,0 +1,156 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Functions to instantiate SimulatedLocalEngines to simulate various Google Devices.""" +from typing import cast, Iterable, Optional +import pathlib +import time + +import google.protobuf.text_format as text_format +import cirq +from cirq_google.api import v2 +from cirq_google.engine import ( + calibration, + engine_validator, + simulated_local_engine, + simulated_local_processor, +) +from cirq_google.devices import serializable_device +from cirq_google.serialization.gate_sets import FSIM_GATESET +from cirq_google.serialization import serializable_gate_set + +METRICS_1Q = [ + 'single_qubit_p00_error', + 'single_qubit_p11_error', + 'single_qubit_readout_separation_error', + 'parallel_p00_error', + 'parallel_p11_error', + 'single_qubit_rb_average_error_per_gate', + 'single_qubit_rb_incoherent_error_per_gate', + 'single_qubit_rb_pauli_error_per_gate', +] + +METRICS_2Q = [ + 'two_qubit_sycamore_gate_xeb_average_error_per_cycle', + 'two_qubit_sycamore_gate_xeb_pauli_error_per_cycle', + 'two_qubit_sycamore_gate_xeb_incoherent_error_per_cycle', + 'two_qubit_sqrt_iswap_gate_xeb_average_error_per_cycle', + 'two_qubit_sqrt_iswap_gate_xeb_pauli_error_per_cycle', + 'two_qubit_sqrt_iswap_gate_xeb_incoherent_error_per_cycle', + 'two_qubit_parallel_sycamore_gate_xeb_average_error_per_cycle', + 'two_qubit_parallel_sycamore_gate_xeb_pauli_error_per_cycle', + 'two_qubit_parallel_sycamore_gate_xeb_incoherent_error_per_cycle', + 'two_qubit_parallel_sqrt_iswap_gate_xeb_average_error_per_cycle', + 'two_qubit_parallel_sqrt_iswap_gate_xeb_pauli_error_per_cycle', + 'two_qubit_parallel_sqrt_iswap_gate_xeb_incoherent_error_per_cycle', +] +# Technically, T1 for a noiseless simulation should be infinite, +# but we will set it to a very high value. +PERFECT_T1_VALUE = 1_000_000 +T1_METRIC_NAME = 'single_qubit_idle_t1_micros' + + +def _create_perfect_calibration(device: cirq.Device): + all_metrics: calibration.ALL_METRICS = {} + qubit_set = device.qubit_set() + if qubit_set is None: + raise ValueError('Devices for noiseless Virtual Engine must have qubits') + qubits = [cast(cirq.GridQubit, q) for q in qubit_set] + for name in METRICS_1Q: + all_metrics[name] = {(q,): [0.0] for q in qubits} + for name in METRICS_2Q: + metric_dict: calibration.METRIC_DICT = {} + pairs = device.qid_pairs() + if pairs is not None: + for pair in pairs: + qubits = [cast(cirq.GridQubit, q) for q in pair.qids] + metric_dict[(qubits[0], qubits[1])] = [0.0] + all_metrics[name] = metric_dict + all_metrics[T1_METRIC_NAME] = {(q,): [PERFECT_T1_VALUE] for q in qubits} + snapshot = v2.metrics_pb2.MetricsSnapshot() + snapshot.timestamp_ms = int(time.time() * 1000) + return calibration.Calibration(calibration=snapshot, metrics=all_metrics) + + +def create_noiseless_virtual_engine_from_device(processor_id: str, device: cirq.Device): + """Creates an Engine object that is backed by a noiseless simulator. + + Creates a noiseless engine object based on the cirq simulator, + a default validator, and a provided device. + + Args: + processor_id: name of the processor to simulate. This is an arbitrary + string identifier and does not have to match the processor's name + in QCS. + device: A `cirq.Device` to validate circuits against. + """ + calibration = _create_perfect_calibration(device) + processor = simulated_local_processor.SimulatedLocalProcessor( + processor_id=processor_id, + device=device, + validator=engine_validator.create_engine_validator(), + gate_set_validator=engine_validator.create_gate_set_validator(), + calibrations={calibration.timestamp // 1000: calibration}, + ) + return simulated_local_engine.SimulatedLocalEngine([processor]) + + +def create_noiseless_virtual_engine_from_proto( + processor_id: str, + device_specification: v2.device_pb2.DeviceSpecification, + gate_sets: Optional[Iterable[serializable_gate_set.SerializableGateSet]] = None, +): + """Creates a noiseless virtual engine object from a device specification proto.a + + The device specification protocol buffer specifies qubits and gates on the device + and can be retrieved from a stored "proto.txt" file or from the QCS API. + + Args: + processor_id: name of the processor to simulate. This is an arbitrary + string identifier and does not have to match the processor's name + in QCS. + device_specification: `v2.device_pb2.DeviceSpecification` proto to create + a validating device from. + gate_sets: Iterable of serializers to use in the processor. Defaults + to the FSIM_GATESET. + """ + if gate_sets is None: + gate_sets = [FSIM_GATESET] + device = serializable_device.SerializableDevice.from_proto(device_specification, gate_sets) + return create_noiseless_virtual_engine_from_device(processor_id, device) + + +def create_noiseless_virtual_engine_from_template( + processor_id: str, + template_name: str, + gate_sets: Optional[Iterable[serializable_gate_set.SerializableGateSet]] = None, +): + """Creates a noiseless virtual engine object from a device specification template. + + Args: + processor_id: name of the processor to simulate. This is an arbitrary + string identifier and does not have to match the processor's name + in QCS. + template_name: File name of the device specification template, see + cirq_google/devices/specifications for valid templates. + gate_sets: Iterable of serializers to use in the processor. Defaults + to the FSIM_GATESET. + """ + path = pathlib.Path(__file__).parent.parent.resolve() + f = open(path.joinpath('devices', 'specifications', template_name)) + proto_txt = f.read() + f.close() + device_spec = v2.device_pb2.DeviceSpecification() + text_format.Parse(proto_txt, device_spec) + return create_noiseless_virtual_engine_from_proto(processor_id, device_spec, gate_sets) diff --git a/cirq-google/cirq_google/engine/virtual_engine_factory_test.py b/cirq-google/cirq_google/engine/virtual_engine_factory_test.py new file mode 100644 index 00000000000..8d3bd3c345f --- /dev/null +++ b/cirq-google/cirq_google/engine/virtual_engine_factory_test.py @@ -0,0 +1,56 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest +import numpy as np +import cirq +import cirq_google as cg +import cirq_google.engine.virtual_engine_factory as factory + + +def _test_processor(processor: cg.engine.abstract_processor.AbstractProcessor): + """Tests an engine instance with some standard commands. + Also tests the non-Sycamore qubits and gates fail.""" + good_qubit = cirq.GridQubit(5, 5) + circuit = cirq.Circuit(cirq.X(good_qubit), cirq.measure(good_qubit)) + results = processor.run(circuit, repetitions=100) + assert np.all(results.measurements[str(good_qubit)] == 1) + with pytest.raises(RuntimeError, match='requested total repetitions'): + _ = processor.run(circuit, repetitions=100_000_000) + + bad_qubit = cirq.GridQubit(10, 10) + circuit = cirq.Circuit(cirq.X(bad_qubit), cirq.measure(bad_qubit)) + with pytest.raises(ValueError, match='Qubit not on device'): + _ = processor.run(circuit, repetitions=100) + circuit = cirq.Circuit(cirq.H(good_qubit), cirq.measure(good_qubit)) + with pytest.raises(ValueError, match='Cannot serialize op'): + _ = processor.run(circuit, repetitions=100) + + +def test_create_from_device(): + engine = factory.create_noiseless_virtual_engine_from_device('sycamore', cg.Sycamore) + _test_processor(engine.get_processor('sycamore')) + + +def test_create_from_proto(): + engine = factory.create_noiseless_virtual_engine_from_template( + 'sycamore', 'weber_12_10_2021_device_spec.proto.txt' + ) + _test_processor(engine.get_processor('sycamore')) + + +def test_create_from_proto_no_qubits(): + with pytest.raises(ValueError, match='must have qubits'): + _ = factory.create_noiseless_virtual_engine_from_device( + 'sycamore', cirq.UNCONSTRAINED_DEVICE + ) From 3fbafd40cf245a10754f11404aa80d48e589ca61 Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Mon, 13 Dec 2021 14:39:40 -0800 Subject: [PATCH 2/6] Fix typing. --- cirq-google/cirq_google/engine/engine_validator.py | 4 ++-- cirq-google/cirq_google/engine/simulated_local_processor.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cirq-google/cirq_google/engine/engine_validator.py b/cirq-google/cirq_google/engine/engine_validator.py index fb62b626985..ba0e7ec226b 100644 --- a/cirq-google/cirq_google/engine/engine_validator.py +++ b/cirq-google/cirq_google/engine/engine_validator.py @@ -24,7 +24,7 @@ MAX_TOTAL_REPETITIONS = 5_000_000 GATE_SET_VALIDATOR_TYPE = Callable[ - [Sequence[cirq.AbstractCircuit], List[cirq.Sweepable], int, 'Serializer'], + [Sequence[cirq.AbstractCircuit], Sequence[cirq.Sweepable], int, 'Serializer'], None, ] @@ -69,7 +69,7 @@ def _verify_measurements(circuits): def validate_gate_set( circuits: Sequence[cirq.AbstractCircuit], - sweeps: List[cirq.Sweepable], + sweeps: Sequence[cirq.Sweepable], repetitions: int, gate_set: Serializer, max_size: int = MAX_MESSAGE_SIZE, diff --git a/cirq-google/cirq_google/engine/simulated_local_processor.py b/cirq-google/cirq_google/engine/simulated_local_processor.py index 49bc96e7f5d..c80dd0e5b64 100644 --- a/cirq-google/cirq_google/engine/simulated_local_processor.py +++ b/cirq-google/cirq_google/engine/simulated_local_processor.py @@ -38,7 +38,7 @@ ] GATE_SET_VALIDATOR_TYPE = Callable[ - [Sequence[cirq.AbstractCircuit], List[cirq.Sweepable], int, 'Serializer'], + [Sequence[cirq.AbstractCircuit], Sequence[cirq.Sweepable], int, 'Serializer'], None, ] From 06d58f2ed00d0d4015e1071d2843c3a16aeb17fb Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Tue, 14 Dec 2021 16:44:25 -0800 Subject: [PATCH 3/6] Add ability to have multiple processors and create a default factory. --- .../engine/virtual_engine_factory.py | 130 ++++++++++++++---- .../engine/virtual_engine_factory_test.py | 38 ++++- 2 files changed, 136 insertions(+), 32 deletions(-) diff --git a/cirq-google/cirq_google/engine/virtual_engine_factory.py b/cirq-google/cirq_google/engine/virtual_engine_factory.py index 0882940061e..8205229815c 100644 --- a/cirq-google/cirq_google/engine/virtual_engine_factory.py +++ b/cirq-google/cirq_google/engine/virtual_engine_factory.py @@ -13,7 +13,7 @@ # limitations under the License. """Functions to instantiate SimulatedLocalEngines to simulate various Google Devices.""" -from typing import cast, Iterable, Optional +from typing import cast, Iterable, List, Optional, Union import pathlib import time @@ -83,10 +83,10 @@ def _create_perfect_calibration(device: cirq.Device): return calibration.Calibration(calibration=snapshot, metrics=all_metrics) -def create_noiseless_virtual_engine_from_device(processor_id: str, device: cirq.Device): - """Creates an Engine object that is backed by a noiseless simulator. +def _create_virtual_processor_from_device(processor_id: str, device: cirq.Device): + """Creates a Processor object that is backed by a noiseless simulator. - Creates a noiseless engine object based on the cirq simulator, + Creates a noiseless `AbstractProcessor` object based on the cirq simulator, a default validator, and a provided device. Args: @@ -96,19 +96,37 @@ def create_noiseless_virtual_engine_from_device(processor_id: str, device: cirq. device: A `cirq.Device` to validate circuits against. """ calibration = _create_perfect_calibration(device) - processor = simulated_local_processor.SimulatedLocalProcessor( + return simulated_local_processor.SimulatedLocalProcessor( processor_id=processor_id, device=device, validator=engine_validator.create_engine_validator(), gate_set_validator=engine_validator.create_gate_set_validator(), calibrations={calibration.timestamp // 1000: calibration}, ) - return simulated_local_engine.SimulatedLocalEngine([processor]) + + +def create_noiseless_virtual_engine_from_device(processor_id: str, device: cirq.Device): + """Creates an Engine object with a single processor backed by a noiseless simulator. + + Creates a noiseless engine object based on the cirq simulator, + a default validator, and a provided device. + + Args: + processor_id: name of the processor to simulate. This is an arbitrary + string identifier and does not have to match the processor's name + in QCS. + device: A `cirq.Device` to validate circuits against. + """ + return simulated_local_engine.SimulatedLocalEngine( + [_create_virtual_processor_from_device(processor_id, device)] + ) def create_noiseless_virtual_engine_from_proto( - processor_id: str, - device_specification: v2.device_pb2.DeviceSpecification, + processor_ids: Union[str, List[str]], + device_specifications: Union[ + v2.device_pb2.DeviceSpecification, List[v2.device_pb2.DeviceSpecification] + ], gate_sets: Optional[Iterable[serializable_gate_set.SerializableGateSet]] = None, ): """Creates a noiseless virtual engine object from a device specification proto.a @@ -117,40 +135,92 @@ def create_noiseless_virtual_engine_from_proto( and can be retrieved from a stored "proto.txt" file or from the QCS API. Args: - processor_id: name of the processor to simulate. This is an arbitrary + processor_ids: name of the processor to simulate. This is an arbitrary string identifier and does not have to match the processor's name - in QCS. - device_specification: `v2.device_pb2.DeviceSpecification` proto to create - a validating device from. + in QCS. This can be a single string or list of strings. + device_specifications: `v2.device_pb2.DeviceSpecification` proto to create + a validating device from. This can be a single DeviceSpecification + or a list of them. There should be one DeviceSpecification for each + processor_id. gate_sets: Iterable of serializers to use in the processor. Defaults to the FSIM_GATESET. + + Raises: + ValueError: if processor_ids and device_specifications are not the same length. """ if gate_sets is None: gate_sets = [FSIM_GATESET] - device = serializable_device.SerializableDevice.from_proto(device_specification, gate_sets) - return create_noiseless_virtual_engine_from_device(processor_id, device) - - -def create_noiseless_virtual_engine_from_template( - processor_id: str, - template_name: str, + if isinstance(processor_ids, str): + processor_ids = [processor_ids] + if isinstance(device_specifications, v2.device_pb2.DeviceSpecification): + device_specifications = [device_specifications] + if len(processor_ids) != len(device_specifications): + raise ValueError('Must provide equal numbers of processor ids and device specifications.') + + processors = [] + for idx in range(len(processor_ids)): + device = serializable_device.SerializableDevice.from_proto( + device_specifications[idx], gate_sets + ) + processors.append(_create_virtual_processor_from_device(processor_ids[idx], device)) + return simulated_local_engine.SimulatedLocalEngine(processors) + + +def create_noiseless_virtual_engine_from_templates( + processor_ids: Union[str, List[str]], + template_names: Union[str, List[str]], gate_sets: Optional[Iterable[serializable_gate_set.SerializableGateSet]] = None, ): """Creates a noiseless virtual engine object from a device specification template. Args: - processor_id: name of the processor to simulate. This is an arbitrary + processor_ids: name of the processor to simulate. This is an arbitrary string identifier and does not have to match the processor's name - in QCS. - template_name: File name of the device specification template, see - cirq_google/devices/specifications for valid templates. + in QCS. There can be a single string or a list of strings for multiple + processors. + template_names: File name of the device specification template, see + cirq_google/devices/specifications for valid templates. There can + be a single str for a template name or a list of strings. Each + template name should be matched to a single processor id. gate_sets: Iterable of serializers to use in the processor. Defaults to the FSIM_GATESET. + + Raises: + ValueError: if processor_ids and template_names are not the same length. + """ + if isinstance(processor_ids, str): + processor_ids = [processor_ids] + if isinstance(template_names, str): + template_names = [template_names] + if len(processor_ids) != len(template_names): + raise ValueError('Must provide equal numbers of processor ids and template names.') + + specifications = [] + for idx in range(len(processor_ids)): + path = pathlib.Path(__file__).parent.parent.resolve() + f = open(path.joinpath('devices', 'specifications', template_names[idx])) + proto_txt = f.read() + f.close() + device_spec = v2.device_pb2.DeviceSpecification() + text_format.Parse(proto_txt, device_spec) + specifications.append(device_spec) + return create_noiseless_virtual_engine_from_proto(processor_ids, specifications, gate_sets) + + +def create_noiseless_virtual_engine_from_latest_templates(): + """Creates a noiseless virtual engine based on current templates. + + This uses the most recent templates to create a reasonable facsimile of + a simulated Quantum Computing Service (QCS). + + Note: this will use the most recent templates to match the service. + While not expected to change frequently, this function may change the + templates (processors) that are included in the "service" as the actual + hardware evolves. The processors returned from this function should not + be considered stable from version to version and are not guaranteed to be + backwards compatible. """ - path = pathlib.Path(__file__).parent.parent.resolve() - f = open(path.joinpath('devices', 'specifications', template_name)) - proto_txt = f.read() - f.close() - device_spec = v2.device_pb2.DeviceSpecification() - text_format.Parse(proto_txt, device_spec) - return create_noiseless_virtual_engine_from_proto(processor_id, device_spec, gate_sets) + return create_noiseless_virtual_engine_from_templates( + ['rainbow', 'weber'], + ['rainbow_12_10_2021_device_spec.proto.txt', 'weber_12_10_2021_device_spec.proto.txt'], + ) diff --git a/cirq-google/cirq_google/engine/virtual_engine_factory_test.py b/cirq-google/cirq_google/engine/virtual_engine_factory_test.py index 8d3bd3c345f..4fe020c8e0f 100644 --- a/cirq-google/cirq_google/engine/virtual_engine_factory_test.py +++ b/cirq-google/cirq_google/engine/virtual_engine_factory_test.py @@ -15,13 +15,14 @@ import numpy as np import cirq import cirq_google as cg +import cirq_google.api.v2 as v2 import cirq_google.engine.virtual_engine_factory as factory def _test_processor(processor: cg.engine.abstract_processor.AbstractProcessor): """Tests an engine instance with some standard commands. Also tests the non-Sycamore qubits and gates fail.""" - good_qubit = cirq.GridQubit(5, 5) + good_qubit = cirq.GridQubit(5, 4) circuit = cirq.Circuit(cirq.X(good_qubit), cirq.measure(good_qubit)) results = processor.run(circuit, repetitions=100) assert np.all(results.measurements[str(good_qubit)] == 1) @@ -43,12 +44,45 @@ def test_create_from_device(): def test_create_from_proto(): - engine = factory.create_noiseless_virtual_engine_from_template( + + # Create a minimal gate specification that can handle the test. + device_spec = v2.device_pb2.DeviceSpecification() + device_spec.valid_qubits.extend(['5_4']) + gs = device_spec.valid_gate_sets.add() + gs.name = 'fsim' + gs.valid_gates.add().id = 'fsim' + gs.valid_gates.add().id = 'xyz' + gs.valid_gates.add().id = 'xy' + gs.valid_gates.add().id = 'z' + gs.valid_gates.add().id = 'meas' + gs.valid_gates.add().id = 'wait' + gs.valid_gates.add().id = 'circuit' + engine = factory.create_noiseless_virtual_engine_from_proto('sycamore', device_spec) + _test_processor(engine.get_processor('sycamore')) + + +def test_create_from_template(): + engine = factory.create_noiseless_virtual_engine_from_templates( 'sycamore', 'weber_12_10_2021_device_spec.proto.txt' ) _test_processor(engine.get_processor('sycamore')) +def test_default_creation(): + engine = factory.create_noiseless_virtual_engine_from_latest_templates() + _test_processor(engine.get_processor('weber')) + _test_processor(engine.get_processor('rainbow')) + + +def test_create_from_template_wrong_args(): + with pytest.raises(ValueError, match='equal numbers of processor ids'): + _ = factory.create_noiseless_virtual_engine_from_templates( + ['sycamore', 'sycamore2'], 'weber_12_10_2021_device_spec.proto.txt' + ) + with pytest.raises(ValueError, match='equal numbers of processor ids'): + _ = factory.create_noiseless_virtual_engine_from_proto('sycamore', []) + + def test_create_from_proto_no_qubits(): with pytest.raises(ValueError, match='must have qubits'): _ = factory.create_noiseless_virtual_engine_from_device( From 450112fdac432218b9108214c9268c53c01d0282 Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Thu, 16 Dec 2021 06:45:08 -0800 Subject: [PATCH 4/6] Address review comments. --- .../cirq_google/engine/engine_validator.py | 73 +++++++++++++++++-- .../engine/virtual_engine_factory.py | 27 ++++--- 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/cirq-google/cirq_google/engine/engine_validator.py b/cirq-google/cirq_google/engine/engine_validator.py index ba0e7ec226b..4f98e2e5819 100644 --- a/cirq-google/cirq_google/engine/engine_validator.py +++ b/cirq-google/cirq_google/engine/engine_validator.py @@ -74,7 +74,21 @@ def validate_gate_set( gate_set: Serializer, max_size: int = MAX_MESSAGE_SIZE, ) -> None: - """Validate that the message size is below the maximum size limit.""" + """Validate that the message size is below the maximum size limit. + + Args: + circuits: A sequence of `cirq.Circuit` objects to validate. For + sweeps and runs, this will be a single circuit. For batches, + this will be a list of circuits. + sweeps: Parameters to run with each circuit. The length of the + sweeps sequence should be the same as the circuits argument. + repetitions: Number of repetitions to run with each sweep. + gate_set: Serializer to use to serialize the circuits and sweeps. + max_size: proto size limit to check against. + + Raises: + RuntimeError: if compiled proto is above the maximum size. + """ batch = v2.batch_pb2.BatchProgram() packed = any_pb2.Any() for circuit in circuits: @@ -86,8 +100,23 @@ def validate_gate_set( def create_gate_set_validator(max_size: int = MAX_MESSAGE_SIZE) -> GATE_SET_VALIDATOR_TYPE: - """Creates a Callanle gate set validator with a set message size.""" - return lambda c, s, r, g: validate_gate_set(c, s, r, g, max_size) + """Creates a Callable gate set validator with a set message size. + + Args: + max_size: proto size limit to check against. + + Returns: Callable to use in validation with the max_size already set. + """ + + def _validator( + circuits: Sequence[cirq.AbstractCircuit], + sweeps: Sequence[cirq.Sweepable], + repetitions: int, + gate_set: Serializer, + ): + return validate_gate_set(circuits, sweeps, repetitions, gate_set) + + return _validator def validate_for_engine( @@ -98,7 +127,20 @@ def validate_for_engine( max_repetitions: int = MAX_TOTAL_REPETITIONS, max_duration_ns: int = 55000, ) -> None: - """Validate a circuit and sweeps for sending to the Quantum Engine API.""" + """Validate a circuit and sweeps for sending to the Quantum Engine API. + + Args: + circuits: A sequence of `cirq.Circuit` objects to validate. For + sweeps and runs, this will be a single circuit. For batches, + this will be a list of circuits. + sweeps: Parameters to run with each circuit. The length of the + sweeps sequence should be the same as the circuits argument. + repetitions: Number of repetitions to run with each sweep. + max_moments: Maximum number of moments to allow. + max_repetitions: Maximum number of parameter sweep values allowed + when summed across all sweeps and all batches. + max_duration_ns: Maximum duration of the circuit, in nanoseconds. + """ _verify_reps(sweeps, repetitions, max_repetitions) _validate_depth(circuits, max_moments) _verify_measurements(circuits) @@ -109,7 +151,22 @@ def create_engine_validator( max_repetitions: int = MAX_TOTAL_REPETITIONS, max_duration_ns: int = 55000, ) -> VALIDATOR_TYPE: - """Creates a Callanle gate set validator with a set message size.""" - return lambda c, s, r: validate_for_engine( - c, s, r, max_moments, max_repetitions, max_duration_ns - ) + """Creates a Callanle gate set validator with a set message size. + + Args: + max_moments: Maximum number of moments to allow. + max_repetitions: Maximum number of parameter sweep values allowed + when summed across all sweeps and all batches. + max_duration_ns: Maximum duration of the circuit, in nanoseconds. + """ + + def _validator( + circuits: Sequence[cirq.AbstractCircuit], + sweeps: Sequence[cirq.Sweepable], + repetitions: Union[int, List[int]], + ): + return validate_for_engine( + circuits, sweeps, repetitions, max_moments, max_repetitions, max_duration_ns + ) + + return _validator diff --git a/cirq-google/cirq_google/engine/virtual_engine_factory.py b/cirq-google/cirq_google/engine/virtual_engine_factory.py index 8205229815c..8868cd82b7b 100644 --- a/cirq-google/cirq_google/engine/virtual_engine_factory.py +++ b/cirq-google/cirq_google/engine/virtual_engine_factory.py @@ -21,14 +21,15 @@ import cirq from cirq_google.api import v2 from cirq_google.engine import ( + abstract_local_processor, calibration, engine_validator, - simulated_local_engine, simulated_local_processor, ) from cirq_google.devices import serializable_device from cirq_google.serialization.gate_sets import FSIM_GATESET from cirq_google.serialization import serializable_gate_set +from cirq_google.engine.simulated_local_engine import SimulatedLocalEngine METRICS_1Q = [ 'single_qubit_p00_error', @@ -61,7 +62,7 @@ T1_METRIC_NAME = 'single_qubit_idle_t1_micros' -def _create_perfect_calibration(device: cirq.Device): +def _create_perfect_calibration(device: cirq.Device) -> calibration.Calibration: all_metrics: calibration.ALL_METRICS = {} qubit_set = device.qubit_set() if qubit_set is None: @@ -83,7 +84,9 @@ def _create_perfect_calibration(device: cirq.Device): return calibration.Calibration(calibration=snapshot, metrics=all_metrics) -def _create_virtual_processor_from_device(processor_id: str, device: cirq.Device): +def _create_virtual_processor_from_device( + processor_id: str, device: cirq.Device +) -> simulated_local_processor.SimulatedLocalProcessor: """Creates a Processor object that is backed by a noiseless simulator. Creates a noiseless `AbstractProcessor` object based on the cirq simulator, @@ -105,7 +108,9 @@ def _create_virtual_processor_from_device(processor_id: str, device: cirq.Device ) -def create_noiseless_virtual_engine_from_device(processor_id: str, device: cirq.Device): +def create_noiseless_virtual_engine_from_device( + processor_id: str, device: cirq.Device +) -> SimulatedLocalEngine: """Creates an Engine object with a single processor backed by a noiseless simulator. Creates a noiseless engine object based on the cirq simulator, @@ -117,9 +122,7 @@ def create_noiseless_virtual_engine_from_device(processor_id: str, device: cirq. in QCS. device: A `cirq.Device` to validate circuits against. """ - return simulated_local_engine.SimulatedLocalEngine( - [_create_virtual_processor_from_device(processor_id, device)] - ) + return SimulatedLocalEngine([_create_virtual_processor_from_device(processor_id, device)]) def create_noiseless_virtual_engine_from_proto( @@ -128,7 +131,7 @@ def create_noiseless_virtual_engine_from_proto( v2.device_pb2.DeviceSpecification, List[v2.device_pb2.DeviceSpecification] ], gate_sets: Optional[Iterable[serializable_gate_set.SerializableGateSet]] = None, -): +) -> SimulatedLocalEngine: """Creates a noiseless virtual engine object from a device specification proto.a The device specification protocol buffer specifies qubits and gates on the device @@ -157,20 +160,20 @@ def create_noiseless_virtual_engine_from_proto( if len(processor_ids) != len(device_specifications): raise ValueError('Must provide equal numbers of processor ids and device specifications.') - processors = [] + processors: List[abstract_local_processor.AbstractLocalProcessor] = [] for idx in range(len(processor_ids)): device = serializable_device.SerializableDevice.from_proto( device_specifications[idx], gate_sets ) processors.append(_create_virtual_processor_from_device(processor_ids[idx], device)) - return simulated_local_engine.SimulatedLocalEngine(processors) + return SimulatedLocalEngine(processors) def create_noiseless_virtual_engine_from_templates( processor_ids: Union[str, List[str]], template_names: Union[str, List[str]], gate_sets: Optional[Iterable[serializable_gate_set.SerializableGateSet]] = None, -): +) -> SimulatedLocalEngine: """Creates a noiseless virtual engine object from a device specification template. Args: @@ -207,7 +210,7 @@ def create_noiseless_virtual_engine_from_templates( return create_noiseless_virtual_engine_from_proto(processor_ids, specifications, gate_sets) -def create_noiseless_virtual_engine_from_latest_templates(): +def create_noiseless_virtual_engine_from_latest_templates() -> SimulatedLocalEngine: """Creates a noiseless virtual engine based on current templates. This uses the most recent templates to create a reasonable facsimile of From 86f4feae9d026f43c45f7104981ec803e99a3ed2 Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Thu, 23 Dec 2021 07:08:08 -0800 Subject: [PATCH 5/6] Address mpharrigan review commnents. --- .../cirq_google/engine/engine_validator.py | 15 ++++++++----- .../engine/engine_validator_test.py | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/cirq-google/cirq_google/engine/engine_validator.py b/cirq-google/cirq_google/engine/engine_validator.py index 4f98e2e5819..444dff755f7 100644 --- a/cirq-google/cirq_google/engine/engine_validator.py +++ b/cirq-google/cirq_google/engine/engine_validator.py @@ -102,6 +102,10 @@ def validate_gate_set( def create_gate_set_validator(max_size: int = MAX_MESSAGE_SIZE) -> GATE_SET_VALIDATOR_TYPE: """Creates a Callable gate set validator with a set message size. + This validator can be used for a validator in `cg.ValidatingSampler` + and can also be useful in generating 'engine emulators' by using + `cg.SimulatedLocalProcessor` with this callable as a gate_set_validator. + Args: max_size: proto size limit to check against. @@ -114,7 +118,7 @@ def _validator( repetitions: int, gate_set: Serializer, ): - return validate_gate_set(circuits, sweeps, repetitions, gate_set) + return validate_gate_set(circuits, sweeps, repetitions, gate_set, max_size) return _validator @@ -125,7 +129,6 @@ def validate_for_engine( repetitions: Union[int, List[int]], max_moments: int = MAX_MOMENTS, max_repetitions: int = MAX_TOTAL_REPETITIONS, - max_duration_ns: int = 55000, ) -> None: """Validate a circuit and sweeps for sending to the Quantum Engine API. @@ -153,6 +156,10 @@ def create_engine_validator( ) -> VALIDATOR_TYPE: """Creates a Callanle gate set validator with a set message size. + This validator can be used for a validator in `cg.ValidatingSampler` + and can also be useful in generating 'engine emulators' by using + `cg.SimulatedLocalProcessor` with this callable as a validator. + Args: max_moments: Maximum number of moments to allow. max_repetitions: Maximum number of parameter sweep values allowed @@ -165,8 +172,6 @@ def _validator( sweeps: Sequence[cirq.Sweepable], repetitions: Union[int, List[int]], ): - return validate_for_engine( - circuits, sweeps, repetitions, max_moments, max_repetitions, max_duration_ns - ) + return validate_for_engine(circuits, sweeps, repetitions, max_moments, max_repetitions) return _validator diff --git a/cirq-google/cirq_google/engine/engine_validator_test.py b/cirq-google/cirq_google/engine/engine_validator_test.py index 020923e7847..2dae0988bc9 100644 --- a/cirq-google/cirq_google/engine/engine_validator_test.py +++ b/cirq-google/cirq_google/engine/engine_validator_test.py @@ -44,6 +44,27 @@ def test_validate_gate_set(): [circuit] * 10, [{}] * 10, 1000, cg.FSIM_GATESET, max_size=100000 ) + with pytest.raises(RuntimeError, match='Program too long'): + engine_validator.validate_gate_set( + [circuit] * 5, [{}] * 5, 1000, cg.FSIM_GATESET, max_size=10000 + ) + + +def test_create_gate_set_validator(): + circuit = cirq.testing.random_circuit( + cirq.GridQubit.rect(4, 4), + n_moments=10, + op_density=1.0, + gate_domain=SERIALIZABLE_GATE_DOMAIN, + ) + + smaller_size_validator = engine_validator.create_gate_set_validator(max_size=20000) + smaller_size_validator([circuit] * 2, [{}] * 2, 1000, cg.FSIM_GATESET) + with pytest.raises(RuntimeError, match='Program too long'): + smaller_size_validator([circuit] * 5, [{}] * 5, 1000, cg.FSIM_GATESET) + larger_size_validator = engine_validator.create_gate_set_validator(max_size=50000) + larger_size_validator([circuit] * 5, [{}] * 5, 1000, cg.FSIM_GATESET) + def test_validate_for_engine(): circuit = cirq.testing.random_circuit( From 8027f543596ec4fcf58173eec1daeaf2c0270835 Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Thu, 23 Dec 2021 07:09:43 -0800 Subject: [PATCH 6/6] fix typo --- cirq-google/cirq_google/engine/engine_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-google/cirq_google/engine/engine_validator.py b/cirq-google/cirq_google/engine/engine_validator.py index 444dff755f7..84f5b16e13b 100644 --- a/cirq-google/cirq_google/engine/engine_validator.py +++ b/cirq-google/cirq_google/engine/engine_validator.py @@ -154,7 +154,7 @@ def create_engine_validator( max_repetitions: int = MAX_TOTAL_REPETITIONS, max_duration_ns: int = 55000, ) -> VALIDATOR_TYPE: - """Creates a Callanle gate set validator with a set message size. + """Creates a Callable gate set validator with a set message size. This validator can be used for a validator in `cg.ValidatingSampler` and can also be useful in generating 'engine emulators' by using