Esempio n. 1
0
class LocalQVMCompiler(AbstractCompiler):
    def __init__(self, endpoint: str, device: AbstractDevice) -> None:
        """
        Client to communicate with a locally executing quilc instance.

        :param endpoint: HTTP endpoint of the quilc instance.
        :param device: PyQuil Device object to use as the compilation target.
        """
        self.endpoint = endpoint
        self.isa = device.get_isa()
        self.specs = device.get_specs()

        self._connection = ForestConnection(sync_endpoint=endpoint)
        self.session = self._connection.session  # backwards compatibility

    def get_version_info(self) -> dict:
        return self._connection._quilc_get_version_info()

    def quil_to_native_quil(self, program: Program) -> Program:
        response = self._connection._quilc_compile(program, self.isa,
                                                   self.specs)

        compiled_program = Program(response['compiled-quil'])
        compiled_program.native_quil_metadata = response['metadata']
        compiled_program.num_shots = program.num_shots

        return compiled_program

    def native_quil_to_executable(self, nq_program: Program):
        return PyQuilExecutableResponse(
            program=nq_program.out(),
            attributes=_extract_attribute_dictionary_from_program(nq_program))
Esempio n. 2
0
    def __init__(self, endpoint: str, device: AbstractDevice) -> None:
        """
        Client to communicate with a locally executing quilc instance.

        :param endpoint: HTTP endpoint of the quilc instance.
        :param device: PyQuil Device object to use as the compilation target.
        """
        self.endpoint = endpoint
        self.isa = device.get_isa()
        self.specs = device.get_specs()

        self._connection = ForestConnection(sync_endpoint=endpoint)
        self.session = self._connection.session  # backwards compatibility
Esempio n. 3
0
def list_devices(connection: ForestConnection = None):
    """
    Query the Forest 2.0 server for a list of underlying QPU devices.

    NOTE: These can't directly be used to manufacture pyQuil Device objects, but this gives a list
          of legal values that can be supplied to list_lattices to filter its (potentially very
          noisy) output.

    :return: A list of device names.
    """
    # For the record, the dictionary stored in "devices" that we're getting back is keyed on device
    # names and has this structure in its values:
    #
    # {
    #   "is_online":   a boolean indicating the availability of the device,
    #   "is_retuning": a boolean indicating whether the device is busy retuning,
    #   "specs":       a Specs object describing the entire device, serialized as a dictionary,
    #   "isa":         an ISA object describing the entire device, serialized as a dictionary,
    #   "noise_model": a NoiseModel object describing the entire device, serialized as a dictionary
    # }
    if connection is None:
        connection = ForestConnection()

    session = connection.session
    url = connection.forest_cloud_endpoint + '/devices'
    return sorted(get_json(session, url)["devices"].keys())
Esempio n. 4
0
 def reset(self):
     """
     Reset the state of the underlying QAM, and the QVM connection information.
     """
     super().reset()
     forest_connection = ForestConnection()
     self.connection = forest_connection
Esempio n. 5
0
    def __init__(self, wires, shots=1000, analytic=False, **kwargs):
        super().__init__(wires, shots)
        self.analytic = analytic
        self.forest_url = kwargs.get("forest_url", pyquil_config.forest_url)
        self.qvm_url = kwargs.get("qvm_url", pyquil_config.qvm_url)
        self.compiler_url = kwargs.get("compiler_url", pyquil_config.quilc_url)

        self.connection = ForestConnection(
            sync_endpoint=self.qvm_url,
            compiler_endpoint=self.compiler_url,
            forest_cloud_endpoint=self.forest_url,
        )

        # The following environment variables are deprecated I think

        # api_key (str): the Forest API key. Can also be set by the environment
        #     variable ``FOREST_API_KEY``, or in the ``~/.qcs_config`` configuration file.
        # user_id (str): the Forest user ID. Can also be set by the environment
        #     variable ``FOREST_USER_ID``, or in the ``~/.qcs_config`` configuration file.
        # qpu_url (str): the QPU server URL. Can also be set by the environment
        #     variable ``QPU_URL``, or in the ``~/.forest_config`` configuration file.

        # if 'api_key' in kwargs:
        #     os.environ['FOREST_API_KEY'] = kwargs['api_key']

        # if 'user_id' in kwargs:
        #     os.environ['FOREST_USER_ID'] = kwargs['user_id']

        # if 'qpu_url' in kwargs:
        #     os.environ['QPU_URL'] = kwargs['qpu_url']

        self.reset()
Esempio n. 6
0
def list_lattices(
    device_name: Optional[str] = None,
    num_qubits: Optional[int] = None,
    connection: Optional[ForestConnection] = None,
) -> Dict[str, str]:
    """
    Query the Forest 2.0 server for its knowledge of lattices.  Optionally filters by underlying
    device name and lattice qubit count.

    :return: A dictionary keyed on lattice names and valued in dictionaries of the
        form::

            {
                "device_name": device_name,
                "qubits": num_qubits
            }
    """
    if connection is None:
        connection = ForestConnection()
    session = connection.session
    assert connection.forest_cloud_endpoint is not None
    url = connection.forest_cloud_endpoint + "/lattices"
    try:
        response = get_json(session,
                            url,
                            params={
                                "device_name": device_name,
                                "num_qubits": num_qubits
                            })

        return cast(Dict[str, str], response["lattices"])
    except Exception as e:
        raise ValueError("""
        list_lattices encountered an error when querying the Forest 2.0 endpoint.

        Some common causes for this error include:

        * You don't have valid user authentication information.  Very likely this is because you
          haven't yet been invited to try QCS.  We plan on making our device information publicly
          accessible soon, but in the meanwhile, you'll have to use default QVM configurations and
          to use `list_quantum_computers` with `qpus = False`.

        * You do have user authentication credentials, but they are invalid. You can visit
          https://qcs.rigetti.com/auth/token and save to ~/.qcs/user_auth_token to update your
          authentication credentials. Alternatively, you may provide the path to your credentials in
          your config file or with the USER_AUTH_TOKEN_PATH environment variable::

              [Rigetti Forest]
              user_auth_token_path = ~/.qcs/my_auth_credentials

        * You're missing an address for the Forest 2.0 server endpoint, or the address is invalid.
          This too can be set through the environment variable FOREST_URL or by changing the
          following lines in the QCS config file::

              [Rigetti Forest]
              url = https://forest-server.qcs.rigetti.com

        For the record, here's the original exception: {}
        """.format(repr(e)))
Esempio n. 7
0
def list_lattices(device_name: str = None,
                  num_qubits: int = None,
                  connection: ForestConnection = None):
    """
    Query the Forest 2.0 server for its knowledge of lattices.  Optionally filters by underlying
    device name and lattice qubit count.

    :return: A dictionary keyed on lattice names and valued in dictionaries of the form
             {
               "device_name": device_name,
               "qubits": num_qubits
             }
    """
    if connection is None:
        connection = ForestConnection()
    session = connection.session
    url = connection.forest_cloud_endpoint + "/lattices"
    try:
        response = get_json(session,
                            url,
                            params={
                                "device_name": device_name,
                                "num_qubits": num_qubits
                            })

        return response["lattices"]
    except Exception as e:
        raise ValueError("""
        list_lattices encountered an error when querying the Forest 2.0 endpoint.

        Some common causes for this error include:

        * You don't have valid user authentication information.  Very likely this is because you
          haven't yet been invited to try QCS.  We plan on making our device information publicly
          accessible soon, but in the meanwhile, you'll have to use default QVM configurations and
          to use `list_quantum_computers` with `qpus = False`.

        * You do have user authentication information, but it is missing or modified.  You can find
          this either in the environment variables FOREST_API_KEY and FOREST_USER_ID or in the
          config file (stored by default at ~/.qcs_config, but with location settable through the
          environment variable QCS_CONFIG), which contains the subsection

          [Rigetti Forest]
          user_id = your_user_id
          key = your_api_key

        * You're missing an address for the Forest 2.0 server endpoint, or the address is invalid.
          This too can be set through the environment variable FOREST_URL or by changing the
          following lines in the QCS config file:

          [Rigetti Forest]
          url = https://forest-server.qcs.rigetti.com

        For the record, here's the original exception: {}
        """.format(repr(e)))
Esempio n. 8
0
    def _get_connection(**kwargs):
        forest_url = kwargs.get("forest_url", pyquil_config.forest_url)
        qvm_url = kwargs.get("qvm_url", pyquil_config.qvm_url)
        compiler_url = kwargs.get("compiler_url", pyquil_config.quilc_url)

        connection = ForestConnection(
            sync_endpoint=qvm_url,
            compiler_endpoint=compiler_url,
            forest_cloud_endpoint=forest_url,
        )

        return connection
Esempio n. 9
0
    def __init__(self, connection: ForestConnection = None, random_seed=None):
        """
        A simulator that propagates a wavefunction representation of a quantum state.

        :param connection: A connection to the Forest web API.
        :param random_seed: A seed for the simulator's random number generators. Either None (for
            an automatically generated seed) or a non-negative integer.
        """
        if connection is None:
            connection = ForestConnection()

        self.connection = connection

        if random_seed is None:
            self.random_seed = None
        elif isinstance(random_seed, integer_types) and random_seed >= 0:
            self.random_seed = random_seed
        else:
            raise TypeError("random_seed should be None or a non-negative int")
Esempio n. 10
0
    def __init__(self, device=None, endpoint=None,
                 gate_noise=None, measurement_noise=None, random_seed=None,
                 compiler_endpoint=None):
        """
        Constructor for QVMConnection. Sets up any necessary security, and establishes the noise
        model to use.

        :param Device device: The optional device, from which noise will be added by default to all
                              programs run on this instance.
        :param endpoint: The endpoint of the server for running small jobs
        :param gate_noise: A list of three numbers [Px, Py, Pz] indicating the probability of an X,
                           Y, or Z gate getting applied to each qubit after a gate application or
                           reset. (default None)
        :param measurement_noise: A list of three numbers [Px, Py, Pz] indicating the probability of
                                  an X, Y, or Z gate getting applied before a a measurement.
                                  (default None)
        :param random_seed: A seed for the QVM's random number generators. Either None (for an
                            automatically generated seed) or a non-negative integer.
        """
        if endpoint is None:
            pyquil_config = PyquilConfig()
            endpoint = pyquil_config.qvm_url

        if compiler_endpoint is None:
            pyquil_config = PyquilConfig()
            compiler_endpoint = pyquil_config.compiler_url

        if (device is not None and device.noise_model is not None) and \
                (gate_noise is not None or measurement_noise is not None):
            raise ValueError("""
You have attempted to supply the QVM with both a device noise model
(by having supplied a device argument), as well as either gate_noise
or measurement_noise. At this time, only one may be supplied.

To read more about supplying noise to the QVM, see http://pyquil.readthedocs.io/en/latest/noise_models.html#support-for-noisy-gates-on-the-rigetti-qvm.
""")

        if device is not None and device.noise_model is None:
            warnings.warn("""
You have supplied the QVM with a device that does not have a noise model. No noise will be added to
programs run on this QVM.
""")

        self.noise_model = device.noise_model if device else None
        self.compiler = LocalQVMCompiler(endpoint=compiler_endpoint, device=device) if device \
            else None

        self.sync_endpoint = endpoint

        validate_noise_probabilities(gate_noise)
        validate_noise_probabilities(measurement_noise)
        self.gate_noise = gate_noise
        self.measurement_noise = measurement_noise

        if random_seed is None:
            self.random_seed = None
        elif isinstance(random_seed, integer_types) and random_seed >= 0:
            self.random_seed = random_seed
        else:
            raise TypeError("random_seed should be None or a non-negative int")

        self._connection = ForestConnection(sync_endpoint=endpoint)
        self.session = self._connection.session  # backwards compatibility
Esempio n. 11
0
class QVMConnection(object):
    """
    Represents a connection to the QVM.
    """

    @_record_call
    def __init__(self, device=None, endpoint=None,
                 gate_noise=None, measurement_noise=None, random_seed=None,
                 compiler_endpoint=None):
        """
        Constructor for QVMConnection. Sets up any necessary security, and establishes the noise
        model to use.

        :param Device device: The optional device, from which noise will be added by default to all
                              programs run on this instance.
        :param endpoint: The endpoint of the server for running small jobs
        :param gate_noise: A list of three numbers [Px, Py, Pz] indicating the probability of an X,
                           Y, or Z gate getting applied to each qubit after a gate application or
                           reset. (default None)
        :param measurement_noise: A list of three numbers [Px, Py, Pz] indicating the probability of
                                  an X, Y, or Z gate getting applied before a a measurement.
                                  (default None)
        :param random_seed: A seed for the QVM's random number generators. Either None (for an
                            automatically generated seed) or a non-negative integer.
        """
        if endpoint is None:
            pyquil_config = PyquilConfig()
            endpoint = pyquil_config.qvm_url

        if compiler_endpoint is None:
            pyquil_config = PyquilConfig()
            compiler_endpoint = pyquil_config.compiler_url

        if (device is not None and device.noise_model is not None) and \
                (gate_noise is not None or measurement_noise is not None):
            raise ValueError("""
You have attempted to supply the QVM with both a device noise model
(by having supplied a device argument), as well as either gate_noise
or measurement_noise. At this time, only one may be supplied.

To read more about supplying noise to the QVM, see http://pyquil.readthedocs.io/en/latest/noise_models.html#support-for-noisy-gates-on-the-rigetti-qvm.
""")

        if device is not None and device.noise_model is None:
            warnings.warn("""
You have supplied the QVM with a device that does not have a noise model. No noise will be added to
programs run on this QVM.
""")

        self.noise_model = device.noise_model if device else None
        self.compiler = LocalQVMCompiler(endpoint=compiler_endpoint, device=device) if device \
            else None

        self.sync_endpoint = endpoint

        validate_noise_probabilities(gate_noise)
        validate_noise_probabilities(measurement_noise)
        self.gate_noise = gate_noise
        self.measurement_noise = measurement_noise

        if random_seed is None:
            self.random_seed = None
        elif isinstance(random_seed, integer_types) and random_seed >= 0:
            self.random_seed = random_seed
        else:
            raise TypeError("random_seed should be None or a non-negative int")

        self._connection = ForestConnection(sync_endpoint=endpoint)
        self.session = self._connection.session  # backwards compatibility

    @_record_call
    def get_version_info(self):
        """
        Return version information for the QVM.

        :return: Dictionary with version information
        """
        return self._connection._qvm_get_version_info()

    @_record_call
    def run(self, quil_program, classical_addresses: List[int] = None,
            trials=1):
        """
        Run a Quil program multiple times, accumulating the values deposited in
        a list of classical addresses.

        :param Program quil_program: A Quil program.
        :param classical_addresses: The classical memory to retrieve. Specified as a list of
            integers that index into a readout register named ``ro``. This function--and
            particularly this argument--are included for backwards compatibility and will
            be removed in the future.
        :param int trials: Number of shots to collect.
        :return: A list of dictionaries of bits. Each dictionary corresponds to the values in
            `classical_addresses`.
        :rtype: list
        """
        if classical_addresses is None:
            caddresses = get_classical_addresses_from_program(quil_program)

        else:
            caddresses = {'ro': classical_addresses}

        buffers = self._connection._qvm_run(quil_program, caddresses, trials,
                                            self.measurement_noise, self.gate_noise,
                                            self.random_seed)

        if len(buffers) == 0:
            return []
        if 'ro' in buffers:
            return buffers['ro'].tolist()

        raise ValueError("You are using QVMConnection.run with multiple readout registers not "
                         "named `ro`. Please use the new `QuantumComputer` abstraction.")

    @_record_call
    def run_and_measure(self, quil_program, qubits, trials=1):
        """
        Run a Quil program once to determine the final wavefunction, and measure multiple times.

        :note: If the execution of ``quil_program`` is **non-deterministic**, i.e., if it includes
            measurements and/or noisy quantum gates, then the final wavefunction from which the
            returned bitstrings are sampled itself only represents a stochastically generated sample
            and the outcomes sampled from *different* ``run_and_measure`` calls *generally sample
            different bitstring distributions*.

        :param Program quil_program: A Quil program.
        :param list|range qubits: A list of qubits.
        :param int trials: Number of shots to collect.
        :return: A list of a list of bits.
        :rtype: list
        """
        # Developer note: This code is for backwards compatibility. It can't be replaced with
        # ForestConnection._run_and_measure because we've turned off the ability to set
        # `needs_compilation` (that usually indicates the user is doing something iffy like
        # using a noise model with this function)

        payload = self._run_and_measure_payload(quil_program, qubits, trials)
        response = post_json(self.session, self.sync_endpoint + "/qvm", payload)
        return response.json()

    @_record_call
    def _run_and_measure_payload(self, quil_program, qubits, trials):
        if not quil_program:
            raise ValueError("You have attempted to run an empty program."
                             " Please provide gates or measure instructions to your program.")

        if not isinstance(quil_program, Program):
            raise TypeError("quil_program must be a Quil program object")
        qubits = validate_qubit_list(qubits)
        if not isinstance(trials, integer_types):
            raise TypeError("trials must be an integer")

        if self.noise_model is not None:
            compiled_program = self.compiler.quil_to_native_quil(quil_program)
            quil_program = apply_noise_model(compiled_program, self.noise_model)

        payload = {"type": TYPE_MULTISHOT_MEASURE,
                   "qubits": list(qubits),
                   "trials": trials,
                   "compiled-quil": quil_program.out()}

        self._maybe_add_noise_to_payload(payload)
        self._add_rng_seed_to_payload(payload)

        return payload

    @_record_call
    def wavefunction(self, quil_program):
        """
        Simulate a Quil program and get the wavefunction back.

        :note: If the execution of ``quil_program`` is **non-deterministic**, i.e., if it includes
            measurements and/or noisy quantum gates, then the final wavefunction from which the
            returned bitstrings are sampled itself only represents a stochastically generated sample
            and the wavefunctions returned by *different* ``wavefunction`` calls *will generally be
            different*.

        :param Program quil_program: A Quil program.
        :return: A Wavefunction object representing the state of the QVM.
        :rtype: Wavefunction
        """
        # Developer note: This code is for backwards compatibility. It can't be replaced with
        # ForestConnection._wavefunction because we've turned off the ability to set
        # `needs_compilation` (that usually indicates the user is doing something iffy like
        # using a noise model with this function)

        payload = self._wavefunction_payload(quil_program)
        response = post_json(self.session, self.sync_endpoint + "/qvm", payload)
        return Wavefunction.from_bit_packed_string(response.content)

    @_record_call
    def _wavefunction_payload(self, quil_program):
        # Developer note: This code is for backwards compatibility. It can't be replaced with
        # _base_connection._wavefunction_payload because we've turned off the ability to set
        # `needs_compilation` (that usually indicates the user is doing something iffy like
        # using a noise model with this function)
        if not isinstance(quil_program, Program):
            raise TypeError("quil_program must be a Quil program object")

        payload = {'type': TYPE_WAVEFUNCTION,
                   'compiled-quil': quil_program.out()}

        self._maybe_add_noise_to_payload(payload)
        self._add_rng_seed_to_payload(payload)

        return payload

    @_record_call
    def expectation(self, prep_prog, operator_programs=None):
        """
        Calculate the expectation value of operators given a state prepared by
        prep_program.

        :note: If the execution of ``quil_program`` is **non-deterministic**, i.e., if it includes
            measurements and/or noisy quantum gates, then the final wavefunction from which the
            expectation values are computed itself only represents a stochastically generated
            sample. The expectations returned from *different* ``expectation`` calls *will then
            generally be different*.

        To measure the expectation of a PauliSum, you probably want to
        do something like this::

                progs, coefs = hamiltonian.get_programs()
                expect_coeffs = np.array(cxn.expectation(prep_program, operator_programs=progs))
                return np.real_if_close(np.dot(coefs, expect_coeffs))

        :param Program prep_prog: Quil program for state preparation.
        :param list operator_programs: A list of Programs, each specifying an operator whose expectation to compute.
            Default is a list containing only the empty Program.
        :return: Expectation values of the operators.
        :rtype: List[float]
        """
        # Developer note: This code is for backwards compatibility. It can't be replaced with
        # ForestConnection._expectation because we've turned off the ability to set
        # `needs_compilation` (that usually indicates the user is doing something iffy like
        # using a noise model with this function)

        if isinstance(operator_programs, Program):
            warnings.warn(
                "You have provided a Program rather than a list of Programs. The results from expectation "
                "will be line-wise expectation values of the operator_programs.", SyntaxWarning)

        payload = self._expectation_payload(prep_prog, operator_programs)
        response = post_json(self.session, self.sync_endpoint + "/qvm", payload)
        return response.json()

    @_record_call
    def pauli_expectation(self, prep_prog, pauli_terms):
        """
        Calculate the expectation value of Pauli operators given a state prepared by prep_program.

        If ``pauli_terms`` is a ``PauliSum`` then the returned value is a single ``float``,
        otherwise the returned value is a list of ``float``s, one for each ``PauliTerm`` in the
        list.

        :note: If the execution of ``quil_program`` is **non-deterministic**, i.e., if it includes
            measurements and/or noisy quantum gates, then the final wavefunction from which the
            expectation values are computed itself only represents a stochastically generated
            sample. The expectations returned from *different* ``expectation`` calls *will then
            generally be different*.

        :param Program prep_prog: Quil program for state preparation.
        :param Sequence[PauliTerm]|PauliSum pauli_terms: A list of PauliTerms or a PauliSum.
        :return: If ``pauli_terms`` is a PauliSum return its expectation value. Otherwise return
          a list of expectation values.
        :rtype: float|List[float]
        """

        is_pauli_sum = False
        if isinstance(pauli_terms, PauliSum):
            progs, coeffs = pauli_terms.get_programs()
            is_pauli_sum = True
        else:
            coeffs = [pt.coefficient for pt in pauli_terms]
            progs = [pt.program for pt in pauli_terms]

        bare_results = self.expectation(prep_prog, progs)
        results = [c * r for c, r in zip(coeffs, bare_results)]
        if is_pauli_sum:
            return sum(results)
        return results

    def _expectation_payload(self, prep_prog, operator_programs):
        if operator_programs is None:
            operator_programs = [Program()]

        if not isinstance(prep_prog, Program):
            raise TypeError("prep_prog variable must be a Quil program object")

        payload = {'type': TYPE_EXPECTATION,
                   'state-preparation': prep_prog.out(),
                   'operators': [x.out() for x in operator_programs]}

        self._add_rng_seed_to_payload(payload)

        return payload

    def _maybe_add_noise_to_payload(self, payload):
        """
        Set the gate noise and measurement noise of a payload.
        """
        if self.measurement_noise is not None:
            payload["measurement-noise"] = self.measurement_noise
        if self.gate_noise is not None:
            payload["gate-noise"] = self.gate_noise

    def _add_rng_seed_to_payload(self, payload):
        """
        Add a random seed to the payload.
        """
        if self.random_seed is not None:
            payload['rng-seed'] = self.random_seed