Example: Expectation values with FiQCIEstimator#

For configuration options, mitigation levels, and result methods, see the FiQCIEstimator guide.

Import everything needed#

from fiqci.ems import FiQCIEstimator
from iqm.qiskit_iqm import IQMProvider, transpile_to_IQM
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

Initialise FiQCIEstimator#

url = None
quantum_computer = None

# Connect to an IQM quantum computer using the provider
if url is not None and quantum_computer is not None:
	provider = IQMProvider(url=url, quantum_computer=quantum_computer)
	backend = provider.get_backend()
else:
	# Or using a noisy simulator
	from iqm.qiskit_iqm import IQMFakeAdonis

	backend = IQMFakeAdonis()

# Initialise FiQCI estimator with mitigation level 1 (readout error mitigation)
estimator = FiQCIEstimator(backend=backend, mitigation_level=1)

# We can view the default settings enabled for mitigation level 1
estimator.mitigator_options
{'zne': {'enabled': False,
  'fold_gates': None,
  'scale_factors': [1, 3, 5],
  'folding_method': 'local',
  'extrapolation_method': 'exponential',
  'extrapolation_degree': None,
  'seed': None},
 'rem': {'enabled': True,
  'calibration_shots': 1000,
  'calibration_file': None,
  'mitigator': <fiqci.ems.mitigators.rem.M3IQM at 0x75ac9ed24050>},
 'dd': {'enabled': False, 'gate_sequences': []},
 'pauli_twirl': {'enabled': False,
  'num_twirls': 0,
  'gates_to_twirl': None,
  'seed': None}}

Bell State expectation values with REM#

# Create a Bell state circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

# Transpile for backend
tr_qc = transpile_to_IQM(qc, backend, remove_final_rzs=False, optimization_level=3)

tr_qc.draw("mpl")
../_images/7ff317aeec09be9075adda5cbcb25d84dbd8924ff2ea7c5f34bf62abf5dd8388.png
# Define observables to calculate expectation values for. Each SparsePauliOp is one observable, so
# SparsePauliOp(["ZZ", "IX"]) would give the single value <ZZ> + <IX>.
observables = [SparsePauliOp("ZZ"), SparsePauliOp("IX")]

# Map observables to the layout of the transpiled circuit
observables_device = [obs.apply_layout(tr_qc.layout) for obs in observables]

# Execute on FiQCI Estimator with specified observables and shots
job = estimator.run(tr_qc, observables=observables_device, shots=2**10)

# Retrieve mitigated expectation values
job.expectation_values()
/home/runner/work/fiqci-ems/fiqci-ems/src/fiqci/ems/backend/core.py:733: UserWarning: M3 produced negative quasi-probabilities for 1 of 2 circuit(s); projecting onto the nearest physical distribution clipped them to zero. Mitigated counts from those circuits are biased towards the physical boundary, so expectation values computed from them can be pinned to +-1 with an understated shot error (FiQCIEstimator avoids this by using the unprojected quasi-probabilities from the result header). Raising calibration_shots reduces this.
  warnings.warn(
[[0.9769239161020429, -0.024754643440246582]]

Access job metadata#

# Get the underlying backend job
backend_job = job.job()

# Access raw counts for the first circuit in the job
raw_counts = backend_job.result().results[0].header["fiqci_ems"]["raw_counts"]

print(raw_counts)
{'01': 42, '11': 492, '10': 53, '00': 437}

Manually configure mitigation options#

# Initialise FiQCI sampler
estimator = FiQCIEstimator(backend=backend, mitigation_level=0)

# Config rem
estimator.rem(enabled=True, calibration_shots=2**10)

# Print current mitigator options
estimator.mitigator_options
{'zne': {'enabled': False,
  'fold_gates': None,
  'scale_factors': [1, 3, 5],
  'folding_method': 'local',
  'extrapolation_method': 'exponential',
  'extrapolation_degree': None,
  'seed': None},
 'rem': {'enabled': True,
  'calibration_shots': 1024,
  'calibration_file': None,
  'mitigator': <fiqci.ems.mitigators.rem.M3IQM at 0x75aca1077b90>},
 'dd': {'enabled': False, 'gate_sequences': []},
 'pauli_twirl': {'enabled': False,
  'num_twirls': 0,
  'gates_to_twirl': None,
  'seed': None}}

Access Job Information and Metadata#

# All measurement-basis subcircuits the estimator runs are flattened into a single backend job. Access it via job.job().
backend_job = job.job()

# Get the counts for every circuit in the job
print(backend_job.result().get_counts())

# Get raw counts for the first circuit in the job
print(backend_job.result().results[0].header)

raw_counts = backend_job.result().results[0].header["fiqci_ems"]["raw_counts"]

print(raw_counts)
[{'10': 14, '00': 477, '11': 533}, {'0': 499, '1': 525}]
{'creg_sizes': [['meas', 2]], 'global_phase': 1.5707963267948966, 'memory_slots': 2, 'n_qubits': 5, 'name': 'circuit-13855', 'qreg_sizes': [['q', 5]], 'metadata': {}, 'fiqci_ems': {'mitigation_level': 1, 'mitigation_method': 'M3', 'calibration_shots': 1000, 'raw_counts': {'01': 42, '11': 492, '10': 53, '00': 437}, 'quasi_probabilities': {'00': 0.46711423993110657, '01': -0.0031726714223623276, '10': 0.014710713177919388, '11': 0.52134770154953}, 'mitigation_overhead': np.float64(1.4698414066795584), 'clipped_outcomes': 1}}
{'01': 42, '11': 492, '10': 53, '00': 437}