def test_generate_randomized_benchmarking_sequence__returns_benchmarking_sequence( mocker: MockerFixture, ): client_configuration = QCSClientConfiguration.load() compiler_client = CompilerClient(client_configuration=client_configuration) rpcq_client = patch_rpcq_client( mocker=mocker, return_value=rpcq.messages.RandomizedBenchmarkingResponse( sequence=[[3, 1, 4], [1, 6, 1]])) request = GenerateRandomizedBenchmarkingSequenceRequest( depth=42, num_qubits=3, gateset=["some", "gate", "set"], seed=314, interleaver="some-interleaver", ) assert compiler_client.generate_randomized_benchmarking_sequence( request) == GenerateRandomizedBenchmarkingSequenceResponse( sequence=[[3, 1, 4], [1, 6, 1]]) rpcq_client.call.assert_called_once_with( "generate_rb_sequence", rpcq.messages.RandomizedBenchmarkingRequest( depth=42, qubits=3, gateset=["some", "gate", "set"], seed=314, interleaver="some-interleaver", ))
def test_conjugate_pauli_by_clifford__returns_conjugation_result( mocker: MockerFixture): client_configuration = QCSClientConfiguration.load() compiler_client = CompilerClient(client_configuration=client_configuration) rpcq_client = patch_rpcq_client( mocker=mocker, return_value=rpcq.messages.ConjugateByCliffordResponse(phase=42, pauli="pauli")) request = ConjugatePauliByCliffordRequest( pauli_indices=[0, 1, 2], pauli_symbols=["x", "y", "z"], clifford="cliff", ) assert compiler_client.conjugate_pauli_by_clifford( request) == ConjugatePauliByCliffordResponse( phase_factor=42, pauli="pauli", ) rpcq_client.call.assert_called_once_with( "conjugate_pauli_by_clifford", rpcq.messages.ConjugateByCliffordRequest( pauli=rpcq.messages.PauliTerm(indices=[0, 1, 2], symbols=["x", "y", "z"]), clifford="cliff", ))
def test_get_version__returns_version(mocker: MockerFixture): client_configuration = QCSClientConfiguration.load() compiler_client = CompilerClient(client_configuration=client_configuration) rpcq_client = patch_rpcq_client(mocker=mocker, return_value={"quilc": "1.2.3"}) assert compiler_client.get_version() == "1.2.3" rpcq_client.call.assert_called_once_with("get_version_info")
def test_sets_timeout_on_requests(mocker: MockerFixture): client_configuration = QCSClientConfiguration.load() compiler_client = CompilerClient(client_configuration=client_configuration, request_timeout=0.1) patch_rpcq_client(mocker=mocker, return_value={}) with compiler_client._rpcq_client() as client: assert client.timeout == compiler_client.timeout
def __init__(self, *, timeout: float = 10.0, client_configuration: Optional[QCSClientConfiguration] = None): """ Client to communicate with the benchmarking data endpoint. :param timeout: Time limit for requests, in seconds. :param client_configuration: Optional client configuration. If none is provided, a default one will be loaded. """ self._compiler_client = CompilerClient( client_configuration=client_configuration or QCSClientConfiguration.load(), request_timeout=timeout, )
def test_compile_to_native_quil__returns_native_quil( aspen8_compiler_isa: CompilerISA, mocker: MockerFixture, ): client_configuration = QCSClientConfiguration.load() compiler_client = CompilerClient(client_configuration=client_configuration) rpcq_client = patch_rpcq_client( mocker=mocker, return_value=rpcq.messages.NativeQuilResponse( quil="native-program", metadata=rpcq.messages.NativeQuilMetadata( final_rewiring=[0, 1, 2], gate_depth=10, gate_volume=42, multiqubit_gate_depth=5, program_duration=3.14, program_fidelity=0.99, topological_swaps=3, qpu_runtime_estimation=0.1618, ), )) request = CompileToNativeQuilRequest( program="some-program", target_quantum_processor=compiler_isa_to_target_quantum_processor( aspen8_compiler_isa), protoquil=True, ) assert compiler_client.compile_to_native_quil( request) == CompileToNativeQuilResponse( native_program="native-program", metadata=NativeQuilMetadataResponse( final_rewiring=[0, 1, 2], gate_depth=10, gate_volume=42, multiqubit_gate_depth=5, program_duration=3.14, program_fidelity=0.99, topological_swaps=3, qpu_runtime_estimation=0.1618, ), ) rpcq_client.call.assert_called_once_with( "quil_to_native_quil", rpcq.messages.NativeQuilRequest( quil="some-program", target_device=compiler_isa_to_target_quantum_processor( aspen8_compiler_isa), ), protoquil=True, )
def __init__( self, *, quantum_processor: AbstractQuantumProcessor, timeout: float, client_configuration: Optional[QCSClientConfiguration], ) -> None: self.quantum_processor = quantum_processor self._timeout = timeout self._client_configuration = client_configuration or QCSClientConfiguration.load( ) self._compiler_client = CompilerClient( client_configuration=self._client_configuration, request_timeout=timeout)
def test_init__sets_base_url_and_timeout(monkeypatch: MonkeyPatch): host = "tcp://localhost:1234" monkeypatch.setenv("QCS_SETTINGS_APPLICATIONS_PYQUIL_QUILC_URL", host) client_configuration = QCSClientConfiguration.load() compiler_client = CompilerClient(client_configuration=client_configuration, request_timeout=3.14) assert compiler_client.base_url == host assert compiler_client.timeout == 3.14
def test_init__validates_compiler_url(monkeypatch: MonkeyPatch): monkeypatch.setenv("QCS_SETTINGS_APPLICATIONS_PYQUIL_QUILC_URL", "not-http-or-tcp://example.com") client_configuration = QCSClientConfiguration.load() with raises( ValueError, match= "Expected compiler URL 'not-http-or-tcp://example.com' to start with 'tcp://'", ): CompilerClient(client_configuration=client_configuration)
class AbstractCompiler(ABC): """The abstract interface for a compiler.""" def __init__( self, *, quantum_processor: AbstractQuantumProcessor, timeout: float, client_configuration: Optional[QCSClientConfiguration], ) -> None: self.quantum_processor = quantum_processor self._timeout = timeout self._client_configuration = client_configuration or QCSClientConfiguration.load( ) self._compiler_client = CompilerClient( client_configuration=self._client_configuration, request_timeout=timeout) def get_version_info(self) -> Dict[str, Any]: """ Return version information for this compiler and its dependencies. :return: Dictionary of version information. """ return {"quilc": self._compiler_client.get_version()} def quil_to_native_quil(self, program: Program, *, protoquil: Optional[bool] = None) -> Program: """ Compile an arbitrary quil program according to the ISA of ``self.quantum_processor``. :param program: Arbitrary quil to compile :param protoquil: Whether to restrict to protoquil (``None`` means defer to server) :return: Native quil and compiler metadata """ self._connect() compiler_isa = self.quantum_processor.to_compiler_isa() request = CompileToNativeQuilRequest( program=program.out(calibrations=False), target_quantum_processor=compiler_isa_to_target_quantum_processor( compiler_isa), protoquil=protoquil, ) response = self._compiler_client.compile_to_native_quil(request) nq_program = parse_program(response.native_program) nq_program.native_quil_metadata = ( None if response.metadata is None else NativeQuilMetadata( final_rewiring=response.metadata.final_rewiring, gate_depth=response.metadata.gate_depth, gate_volume=response.metadata.gate_volume, multiqubit_gate_depth=response.metadata.multiqubit_gate_depth, program_duration=response.metadata.program_duration, program_fidelity=response.metadata.program_fidelity, topological_swaps=response.metadata.topological_swaps, qpu_runtime_estimation=response.metadata. qpu_runtime_estimation, )) nq_program.num_shots = program.num_shots nq_program._calibrations = program.calibrations nq_program._memory = program._memory.copy() return nq_program def _connect(self) -> None: try: _check_quilc_version(self._compiler_client.get_version()) except TimeoutError: raise QuilcNotRunning( f"Request to quilc at {self._compiler_client.base_url} timed out. " "This could mean that quilc is not running, is not reachable, or is " "responding slowly.") @abstractmethod def native_quil_to_executable(self, nq_program: Program) -> QuantumExecutable: """ Compile a native quil program to a binary executable. :param nq_program: Native quil to compile :return: An (opaque) binary executable """ def reset(self) -> None: """ Reset the state of the this compiler. """ pass
class BenchmarkConnection(AbstractBenchmarker): """ Represents a connection to a server that generates benchmarking data. """ def __init__(self, *, timeout: float = 10.0, client_configuration: Optional[QCSClientConfiguration] = None): """ Client to communicate with the benchmarking data endpoint. :param timeout: Time limit for requests, in seconds. :param client_configuration: Optional client configuration. If none is provided, a default one will be loaded. """ self._compiler_client = CompilerClient( client_configuration=client_configuration or QCSClientConfiguration.load(), request_timeout=timeout, ) def apply_clifford_to_pauli(self, clifford: Program, pauli_in: PauliTerm) -> PauliTerm: r""" Given a circuit that consists only of elements of the Clifford group, return its action on a PauliTerm. In particular, for Clifford C, and Pauli P, this returns the PauliTerm representing CPC^{\dagger}. :param clifford: A Program that consists only of Clifford operations. :param pauli_in: A PauliTerm to be acted on by clifford via conjugation. :return: A PauliTerm corresponding to clifford * pauli_in * clifford^{\dagger} """ # do nothing if `pauli_in` is the identity if is_identity(pauli_in): return pauli_in indices_and_terms = list(zip(*list(pauli_in.operations_as_set()))) request = ConjugatePauliByCliffordRequest( pauli_indices=list(indices_and_terms[0]), pauli_symbols=list(indices_and_terms[1]), clifford=clifford.out(calibrations=False), ) response = self._compiler_client.conjugate_pauli_by_clifford(request) phase_factor, paulis = response.phase_factor, response.pauli pauli_out = PauliTerm("I", 0, 1.0j ** phase_factor) clifford_qubits = clifford.get_qubits() pauli_qubits = pauli_in.get_qubits() all_qubits = sorted(set(cast(List[int], pauli_qubits)).union(set(cast(List[int], clifford_qubits)))) # The returned pauli will have specified its value on all_qubits, sorted by index. # This is maximal set of qubits that can be affected by this conjugation. for i, pauli in enumerate(paulis): pauli_out = cast(PauliTerm, pauli_out * PauliTerm(pauli, all_qubits[i])) return cast(PauliTerm, pauli_out * pauli_in.coefficient) def generate_rb_sequence( self, depth: int, gateset: Sequence[Gate], seed: Optional[int] = None, interleaver: Optional[Program] = None, ) -> List[Program]: """ Construct a randomized benchmarking experiment on the given qubits, decomposing into gateset. If interleaver is not provided, the returned sequence will have the form C_1 C_2 ... C_(depth-1) C_inv , where each C is a Clifford element drawn from gateset, C_{< depth} are randomly selected, and C_inv is selected so that the entire sequence composes to the identity. If an interleaver G (which must be a Clifford, and which will be decomposed into the native gateset) is provided, then the sequence instead takes the form C_1 G C_2 G ... C_(depth-1) G C_inv . The JSON response is a list of lists of indices, or Nones. In the former case, they are the index of the gate in the gateset. :param depth: The number of Clifford gates to include in the randomized benchmarking experiment. This is different than the number of gates in the resulting experiment. :param gateset: A list of pyquil gates to decompose the Clifford elements into. These must generate the clifford group on the qubits of interest. e.g. for one qubit [RZ(np.pi/2), RX(np.pi/2)]. :param seed: A positive integer used to seed the PRNG. :param interleaver: A Program object that encodes a Clifford element. :return: A list of pyquil programs. Each pyquil program is a circuit that represents an element of the Clifford group. When these programs are composed, the resulting Program will be the randomized benchmarking experiment of the desired depth. e.g. if the return programs are called cliffords then `sum(cliffords, Program())` will give the randomized benchmarking experiment, which will compose to the identity program. """ # Support QubitPlaceholders: we temporarily index to arbitrary integers. # `generate_rb_sequence` handles mapping back to the original gateset gates. gateset_as_program = address_qubits(sum(gateset, Program())) qubits = len(gateset_as_program.get_qubits()) gateset_for_api = gateset_as_program.out().splitlines() interleaver_out: Optional[str] = None if interleaver: assert isinstance(interleaver, Program) interleaver_out = interleaver.out(calibrations=False) depth = int(depth) # needs to be jsonable, no np.int64 please! request = GenerateRandomizedBenchmarkingSequenceRequest( depth=depth, num_qubits=qubits, gateset=gateset_for_api, seed=seed, interleaver=interleaver_out, ) response = self._compiler_client.generate_randomized_benchmarking_sequence(request) programs = [] for clifford in response.sequence: clifford_program = Program() if interleaver: clifford_program._calibrations = interleaver.calibrations # Like below, we reversed the order because the API currently hands back the Clifford # decomposition right-to-left. for index in reversed(clifford): clifford_program.inst(gateset[index]) programs.append(clifford_program) # The programs are returned in "textbook style" right-to-left order. To compose them into # the correct pyquil program, we reverse the order. return list(reversed(programs))