Spanish
Idiomas
English
Japanese
Spanish

Nota

Esta página fue generada a partir de docs/tutorials/how-to-getting-started-with-estimator.ipynb.

Primeros pasos con la primitiva Estimator

En este tutorial, te mostraremos cómo configurar la primitiva Estimator de Qiskit Runtime, explorar las diferentes opciones que puedes usar para configurarlo e invocar la primitiva de manera eficiente dentro de una sesión.

Primitivas

Primitives are core functions that make it easier to build modular algorithms and applications.

La versión inicial de Qiskit Runtime incluye dos primitivas:

Sampler: Generates quasi-probability distribution from input circuits.

Estimator: Calcula los valores esperados de los circuitos de entrada y observables.

En este tutorial nos centraremos en la primitiva Estimator. Hay un tutorial separado sobre los Primeros pasos con la primitiva Sampler.

Utilizar la primitiva Estimator

Similar a la clase base Backend, hay una clase base Estimator definida en Qiskit Terra que estandariza la forma en que los usuarios interactúan con todas las implementaciones de Estimator. Esto permite a los usuarios cambiar fácilmente su elección de simulador o dispositivo para realizar cálculos de valor esperado, incluso si la implementación subyacente es diferente.

En esta sección, utilizaremos la implementación predeterminada en Qiskit Terra, que utiliza un simulador de vector de estado local.

1. Crear un circuito

Para un cálculo básico del valor esperado, necesitarás al menos un circuito cuántico para preparar nuestro sistema en un estado cuántico preciso para el estudio. Todos nuestros ejemplos tienen circuitos, pero puedes usar Qiskit para crear el tuyo propio. Para aprender a crear circuitos con Qiskit, consulta el Tutorial de aspectos básicos de un circuito.

[1]:
from qiskit.circuit.random import random_circuit

circuit = random_circuit(2, 2, seed=0).decompose(reps=1)
display(circuit.draw("mpl"))
../_images/tutorials_how-to-getting-started-with-estimator_9_0.png

2. Crear un observable para medir

También necesitarás al menos un observable para medir. Los observables representan propiedades físicas de un sistema cuántico (como la energía o el espín) y permiten medir dichas propiedades (como sus valores esperados) para un estado dado de nuestro sistema. Para simplificar, puedes usar la clase SparsePauliOp en Qiskit para definirlos, como se ilustra en el siguiente ejemplo.

[2]:
from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp("XZ")
print(f">>> Observable: {observable.paulis}")
>>> Observable: ['XZ']

3. Inicializar una clase Estimator

The next step is to create an instance of an Estimator class, which can be any of the subclasses that comply with the base specification. For simplicity, we will use Qiskit Terra’s qiskit.primitives.Estimator class, based on the Statevector construct (algebraic simulation).

[3]:
from qiskit.primitives import Estimator

estimator = Estimator()

4. Invocar el Estimator y obtener resultados

Para calcular los valores esperados, invoca al método run() de la instancia Estimator que acabas de crear y pasa el circuito y el observable como parámetros de entrada. Esta llamada de método es asíncrona y obtendrás un objeto Job. Puedes usar este objeto para consultar información como job_id() y status().

[4]:
job = estimator.run(circuit, observable)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
>>> Job ID: 85a54b84-7ce2-49f8-b614-cc39ef8eafcc
>>> Job Status: JobStatus.DONE

El método result() del trabajo (job) devolverá el EstimatorResult, que incluye tanto los valores esperados como los metadatos del trabajo.

[5]:
result = job.result()
print(f">>> {result}")
print(f"  > Expectation value: {result.values[0]}")
>>> EstimatorResult(values=array([0.85347811]), metadata=[{}])
  > Expectation value: 0.8534781134132173

Puedes seguir invocando al método run() nuevamente con las diferentes entradas:

[6]:
circuit = random_circuit(2, 2, seed=1).decompose(reps=1)
observable = SparsePauliOp("IY")

job = estimator.run(circuit, observable)
result = job.result()

display(circuit.draw("mpl"))
print(f">>> Observable: {observable.paulis}")
print(f">>> Expectation value: {result.values[0]}")
../_images/tutorials_how-to-getting-started-with-estimator_22_0.png
>>> Observable: ['IY']
>>> Expectation value: -1.582539029327245e-16

También puedes proporcionar entradas compuestas al método run():

[7]:
circuits = (
    random_circuit(2, 2, seed=0).decompose(reps=1),
    random_circuit(2, 2, seed=1).decompose(reps=1),
)
observables = (
    SparsePauliOp("XZ"),
    SparsePauliOp("IY"),
)

job = estimator.run(circuits, observables)
result = job.result()

[display(cir.draw("mpl")) for cir in circuits]
print(f">>> Observables: {[obs.paulis for obs in observables]}")
print(f">>> Expectation values: {result.values.tolist()}")
../_images/tutorials_how-to-getting-started-with-estimator_24_0.png
../_images/tutorials_how-to-getting-started-with-estimator_24_1.png
>>> Observables: [PauliList(['XZ']), PauliList(['IY'])]
>>> Expectation values: [0.8534781134132173, -1.582539029327245e-16]

O usar circuitos parametrizados:

[8]:
from qiskit.circuit.library import RealAmplitudes

circuit = RealAmplitudes(num_qubits=2, reps=2).decompose(reps=1)
observable = SparsePauliOp("ZI")
parameter_values = [0, 1, 2, 3, 4, 5]

job = estimator.run(circuit, observable, parameter_values)
result = job.result()

display(circuit.draw("mpl"))
print(f">>> Observable: {observable.paulis}")
print(f">>> Parameter values: {parameter_values}")
print(f">>> Expectation value: {result.values[0]}")
../_images/tutorials_how-to-getting-started-with-estimator_26_0.png
>>> Observable: ['ZI']
>>> Parameter values: [0, 1, 2, 3, 4, 5]
>>> Expectation value: -0.6485568434766461

Usar Estimator de Qiskit Runtime

En esta sección, veremos cómo usar la implementación de Qiskit Runtime de la primitiva Estimator.

1. Inicializar la cuenta

Dado que Estimator de Qiskit Runtime es un servicio administrado, primero deberás inicializar tu cuenta. Luego puedes seleccionar el simulador o el backend real que deseas usar para calcular el valor esperado.

Sigue la guía de primeros pasos si aún no tienes una cuenta configurada.

[9]:
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService(channel="ibm_quantum")
backend = service.backend("ibmq_qasm_simulator")

2. Crear un circuito y un observable

Al igual que en la sección anterior, necesitarás al menos un circuito y un observable como entradas para la primitiva Estimator.

[10]:
from qiskit.circuit.random import random_circuit
from qiskit.quantum_info import SparsePauliOp

circuit = random_circuit(2, 2, seed=0).decompose(reps=1)
display(circuit.draw("mpl"))

observable = SparsePauliOp("XZ")
print(f">>> Observable: {observable.paulis}")
../_images/tutorials_how-to-getting-started-with-estimator_34_0.png
>>> Observable: ['XZ']

3. Inicializar Estimator de Qiskit Runtime

Aquí estamos inicializando una instancia de qiskit_ibm_runtime.Estimator en lugar de qiskit.primitives.Estimator para usar la implementación de Estimator de Qiskit Runtime.

When you initialize the Estimator, you’ll need to pass in the backend you previously selected as the target device (or simulator), using the backend parameter.

[11]:
from qiskit_ibm_runtime import Estimator

estimator = Estimator(backend=backend)

4. Invocar el Estimator y obtener resultados

Luego puedes invocar al método run() para calcular los valores esperados para los circuitos de entrada y los observables.

[12]:
job = estimator.run(circuit, observable)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
>>> Job ID: cdkrlhian60ka16e78dg
>>> Job Status: JobStatus.RUNNING
[13]:
result = job.result()
print(f">>> {result}")
print(f"  > Expectation value: {result.values[0]}")
print(f"  > Metadata: {result.metadata[0]}")
>>> EstimatorResult(values=array([0.859]), metadata=[{'variance': 0.262119, 'shots': 4000}])
  > Expectation value: 0.859
  > Metadata: {'variance': 0.262119, 'shots': 4000}

Opciones

Las primitivas vienen con varias opciones que se agrupan en diferentes categorías. Las opciones de uso común, como resilience_level, se encuentran en el primer nivel.

options

Puedes utilizar la clase Options para especificar distintas opciones.

En el siguiente ejemplo, creamos una instancia de la clase Options. optimization_level es una opción de primer nivel y se puede pasar como un parámetro de entrada. Las opciones relacionadas con el entorno de ejecución se pasan mediante el parámetro environment.

[14]:
from qiskit_ibm_runtime import Options

options = Options(optimization_level=3, environment={"log_level": "INFO"})

Options soporta el autocompletado. Una vez que creas una instancia de la clase Options, puedes usar la función de autocompletar para ver qué opciones están disponibles. Si eliges una de las categorías, puedes usar la función de autocompletar nuevamente para ver qué opciones están disponibles en esa categoría.

[15]:
from qiskit_ibm_runtime import Options

options = Options()
options.resilience_level = 1
options.execution.shots = 2048

Al crear una instancia de la clase Estimator, puedes pasar las options que acabas de crear. Esas opciones se aplicarán cuando uses run() para realizar el cálculo.

[16]:
estimator = Estimator(backend=backend, options=options)
result = estimator.run(circuit, observable).result()
print(f">>> Metadata: {result.metadata[0]}")
>>> Metadata: {'variance': 0.2847862243652344, 'shots': 2048, 'readout_mitigation_num_twirled_circuits': 16, 'readout_mitigation_shots_calibration': 8192}

También puedes pasar opciones al método run(). Esto sobrescribirá las opciones que especificaste al crear la instancia de Estimator para esa ejecución en particular.

Dado que la mayoría de los usuarios solo sobrescribirán un puñado de opciones a nivel del trabajo (job), no es necesario especificar la categoría en la que se encuentran las opciones. El siguiente código, por ejemplo, especifica shots=1024 en lugar de execution={"shots": 1024} (que también es válido).

[17]:
estimator = Estimator(backend=backend, options=options)
result = estimator.run(circuit, observable, shots=1024).result()
print(f">>> Metadata: {result.metadata[0]}")
>>> Metadata: {'variance': 0.2814788818359375, 'shots': 1024, 'readout_mitigation_num_twirled_circuits': 16, 'readout_mitigation_shots_calibration': 8192}

Supresión y mitigación de errores

optimization_level y resilience_level se utilizan para configurar la supresión y mitigación de errores.

Estimator admite optimization_level 0-3 y resilience_level 0-3.

Las opciones de mitigación avanzadas se pueden especificar en resilience.

[18]:
from qiskit_ibm_runtime import Options

# optimization_level=3 adds dynamical decoupling
# resilience_level=1 adds readout error mitigation
options = Options(optimization_level=3, resilience_level=1)
[19]:
estimator = Estimator(backend=backend, options=options)
result = estimator.run(circuit, observable).result()
print(f">>> Expectation value: {result.values[0]}")
print(f">>> Metadata: {result.metadata[0]}")
>>> Expectation value: 0.8485
>>> Metadata: {'variance': 0.28004774999999993, 'shots': 4000, 'readout_mitigation_num_twirled_circuits': 16, 'readout_mitigation_shots_calibration': 8192}

Sesión

Una sesión de Qiskit Runtime te permite agrupar una colección de llamadas iterativas a la computadora cuántica. Una sesión se inicia cuando se inicia el primer trabajo (job) dentro de la sesión. Si la sesión está activa, el planificador prioriza los trabajos posteriores dentro de la sesión para minimizar el retraso artificial dentro de un algoritmo iterativo. Los datos utilizados dentro de una sesión, como los circuitos transpilados, también se almacenan en caché para evitar una sobrecarga innecesaria.

Duración de la sesión

Cuando se inicia una sesión, se le asigna un valor máximo de tiempo de espera de sesión. Puedes establecer este valor utilizando el parámetro max_time.

Si no especificas un valor de tiempo de espera, se establece en el tiempo de ejecución máximo del trabajo inicial y es el menor de estos valores:

Una vez alcanzado este límite de tiempo, la sesión se cierra de forma permanente.

Una sesión también tiene un valor de tiempo de espera interactivo. Si no hay trabajos (jobs) de sesión encolados dentro de esa ventana, la sesión se desactiva temporalmente y se reanuda la selección normal de trabajos. Este valor de tiempo de espera interactivo lo establece el sistema y no se puede sobrescribir.

Invocar Estimator.run dentro de una sesión

Puedes crear una sesión de Qiskit Runtime usando el administrador de contexto (with ...:), que automáticamente abre y cierra la sesión por ti. Puedes invocar Estimator.run una o más veces dentro de una sesión:

[20]:
from qiskit_ibm_runtime import Session, Estimator

with Session(backend=backend, max_time="1h"):
    estimator = Estimator()

    result = estimator.run(circuit, observable).result()
    print(f">>> Expectation value from the first run: {result.values[0]}")

    result = estimator.run(circuit, observable).result()
    print(f">>> Expectation value from the second run: {result.values[0]}")
>>> Expectation value from the first run: 0.858
>>> Expectation value from the second run: 0.85

Invocar múltiples primitivas en una sesión

No estás restringido a una sola función primitiva dentro de una sesión. En esta sección te mostraremos un ejemplo del uso de múltiples primitivas.

Primero preparamos un circuito para la primitiva sampler.

[21]:
from qiskit.circuit.random import random_circuit

sampler_circuit = random_circuit(2, 2, seed=0).decompose(reps=1)
sampler_circuit.measure_all()
display(circuit.draw("mpl"))
../_images/tutorials_how-to-getting-started-with-estimator_68_0.png

El siguiente ejemplo muestra cómo puedes crear tanto una instancia de la clase Sampler como una de la clase Estimator e invocar sus métodos run() dentro de una sesión.

[22]:
from qiskit_ibm_runtime import Session, Sampler, Estimator

with Session(backend=backend):
    sampler = Sampler()
    estimator = Estimator()

    result = sampler.run(sampler_circuit).result()
    print(f">>> Quasi-probability distribution from the sampler job: {result.quasi_dists[0]}")

    result = estimator.run(circuit, observable).result()
    print(f">>> Expectation value from the estimator job: {result.values[0]}")
>>> Quasi-probability distribution from the sampler job: {2: 0.50125, 0: 0.49875}
>>> Expectation value from the estimator job: 0.8525

Las llamadas también pueden ser asíncronas. No necesitas esperar el resultado de un trabajo anterior para enviar otro.

[23]:
from qiskit_ibm_runtime import Session, Sampler, Estimator

with Session(backend=backend):
    sampler = Sampler()
    estimator = Estimator()

    sampler_job = sampler.run(sampler_circuit)
    estimator_job = estimator.run(circuit, observable)

    print(
        f">>> Quasi-probability distribution from the sampler job: {sampler_job.result().quasi_dists[0]}"
    )
    print(f">>> Expectation value from the estimator job: {estimator_job.result().values[0]}")
>>> Quasi-probability distribution from the sampler job: {2: 0.49025, 0: 0.50975}
>>> Expectation value from the estimator job: 0.8475

Resumen

El siguiente código resume el uso de las primitivas, las opciones y la sesión de Qiskit Runtime.

[24]:
from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    Session,
    Sampler,
    Estimator,
    Options,
)

# 1. Initialize account
service = QiskitRuntimeService(channel="ibm_quantum")

# 2. Specify options, such as enabling error mitigation
options = Options(resilience_level=1)

# 3. Select a backend.
backend = service.backend("ibmq_qasm_simulator")

# 4. Create a session
with Session(backend=backend):
    # 5. Create primitive instances
    sampler = Sampler(options=options)
    estimator = Estimator(options=options)

    # 6. Submit jobs
    sampler_job = sampler.run(sampler_circuit)
    estimator_job = estimator.run(circuit, observable)

    # 7. Get results
    print(
        f">>> Quasi-probability distribution from the sampler job: {sampler_job.result().quasi_dists[0]}"
    )
    print(f">>> Expectation value from the estimator job: {estimator_job.result().values[0]}")
>>> Quasi-probability distribution from the sampler job: {0: 0.49575, 2: 0.50425}
>>> Expectation value from the estimator job: 0.8315

Referencia

Puedes encontrar más detalles sobre los métodos de Estimator en la referencia de la API de Estimator.

Y todas las opciones disponibles en la referencia de la API de Options.

[25]:
import qiskit_ibm_runtime

qiskit_ibm_runtime.version.get_version_info()
[25]:
'0.8.0'
[26]:
from qiskit.tools.jupyter import *

%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.22.2
qiskit-aer0.11.0
qiskit-ibmq-provider0.19.2
qiskit-nature0.5.0
System information
Python version3.8.1
Python compilerClang 11.0.3 (clang-1103.0.32.62)
Python builddefault, Jul 15 2020 18:48:27
OSDarwin
CPUs8
Memory (Gb)16.0
Mon Nov 07 21:11:54 2022 EST

This code is a part of Qiskit

© Copyright IBM 2017, 2022.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.