Note

This page was generated from docs/tutorials/sea_with_sampler.ipynb.

# Spectroscopic Eigensolver Algorithm with Sampler¶

This tutorial demonstrates the ability to send flexible circuits to the `Sampler`

primitive by performing a simple example of the spectroscopic eigensolver algorithm (arXiv:2202.12910). The SEA is used for quantum simulation of model Hamiltonians, and works by interacting a “probe” auxiliary qubit with a simulation register. The energy of the probe qubit is swept and eigenvalues of the simulation Hamiltonian are observed as peaks or dips in the response,
akin to the experimental tool of spectroscopy. Because each point (i.e., energy) is a different quantum circuit, this technique is expensive with respect to the number of circuits required. The `Sampler`

provides the flexibility to send just a single circuit with the needed `Parameter`

s passed.

## Set up your local development environment¶

This tutorial requires a Qiskit Runtime service instance. If you haven’t done so already, follow these steps to set one up.

```
[1]:
```

```
# load necessary Runtime libraries
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session
backend = "ibmq_qasm_simulator" # use the simulator
```

## Simple Hamiltonian Model¶

Let’s consider a Pauli-X matrix acting on a qubit,

where we can set \(\mu\) later, or even sweep its values as well. The SEA works by taking the model Pauli (i.e., qubit) Hamiltonian and building a larger “resonance” Hamiltonian that includes both the simulation register and probe qubit `q0`

via

where the angular frequency \(\omega\) corresponds to the energy of the probe qubit, and \(c\) is the coupling between the probe qubit and a qubit in the simulation register (`q1`

in this case). The letters \(I\), \(X\), and \(Z\) correspond to the Pauli spin matrices and their order reflect which qubit they operate on (note that this notation is little-endian). We will set \(\hbar \equiv 1\) in the following code.

We can construct the SEA circuits with tools from Qiskit Opflow.

```
[2]:
```

```
from qiskit.circuit import Parameter
from qiskit.opflow import I, X, Z
mu = Parameter('$\\mu$')
ham_pauli = mu * X
```

```
[3]:
```

```
cc = Parameter('$c$')
ww = Parameter('$\\omega$')
ham_res = -(1/2)*ww*(I^Z) + cc*(X^X) + (ham_pauli^I)
```

Time evolve the resonance Hamiltonian.

```
[4]:
```

```
tt = Parameter('$t$')
U_ham = (tt*ham_res).exp_i()
```

From the time-evolution operator \(U_{\rm ham}\), we use the Suzuki-Trotter expansion to convert this operator into quantum circuits that implement discrete time steps of the simulation. The smaller the time steps (more Trotter steps), the more accurate the quantum circuit, but also longer depth, which could introduce errors when executing on noisy quantum hardware. We then transpile the circuits to IBM backend basis gates and measure only the probe qubit `q0`

.

```
[5]:
```

```
from qiskit import transpile
from qiskit.circuit import ClassicalRegister
from qiskit.opflow import PauliTrotterEvolution, Suzuki
import numpy as np
num_trot_steps = 5
total_time = 10
cr = ClassicalRegister(1, 'c')
spec_op = PauliTrotterEvolution(trotter_mode=Suzuki(order=2, reps=num_trot_steps)).convert(U_ham)
spec_circ = spec_op.to_circuit()
spec_circ_t = transpile(spec_circ, basis_gates=['sx', 'rz', 'cx'])
spec_circ_t.add_register(cr)
spec_circ_t.measure(0, cr[0])
```

```
[5]:
```

```
<qiskit.circuit.instructionset.InstructionSet at 0x1273c9a00>
```

```
[6]:
```

```
spec_circ_t.draw('mpl')
```

```
[6]:
```

Now let’s fix our parameters and sweep over frequency with a number of points `num_pts`

. The eigenvalues of our model Hamiltonian \(H_{\rm Pauli}\) are \(\pm \mu\), so we need to choose a range that includes those numbers.

Note that the `Parameter`

s’ keys and values must be separated and converted into a `List`

of `List`

s. The keys give us the `Parameter`

s inside each circuit. In this case, we only have a single circuit, so the `List`

of keys contains a single `List`

. For the `Parameter`

values, there is a `List`

for each value of `ww`

.

```
[7]:
```

```
# fixed Parameters
fixed_params = {
cc: 0.3,
mu: 0.7,
tt: total_time
}
# Parameter value for single circuit
param_keys = list(spec_circ_t.parameters)
# run through all the ww values to create a List of Lists of Parameter value
num_pts = 101
wvals = np.linspace(-2, 2, num_pts)
param_vals = []
for wval in wvals:
all_params = {**fixed_params, **{ww: wval}}
param_vals.append([all_params[key] for key in param_keys])
```

When calling the `sampler`

, we specify a list of `circuits`

pointing to the circuits desired to be run and the parameter values for each circuit.

```
[8]:
```

```
with Session(backend=backend):
sampler = Sampler()
job = sampler.run(
circuits=[spec_circ_t]*num_pts,
parameter_values=param_vals,
shots=1e5
)
result = job.result()
```

Build the \(Z\) expectations by converting quasi-probabilities to \(\langle Z \rangle\).

```
[9]:
```

```
Zexps = []
for dist in result.quasi_dists:
if 1 in dist:
Zexps.append(1 - 2*dist[1])
else:
Zexps.append(1)
```

As a sanity check, we’ll calculate the exact expectation values with Qiskit Opflow.

```
[10]:
```

```
from qiskit.opflow import PauliExpectation, Zero
param_bind = {
cc: 0.3,
mu: 0.7,
tt: total_time
}
init_state = Zero^2
obsv = I^Z
Zexp_exact = (U_ham @ init_state).adjoint() @ obsv @ (U_ham @ init_state)
diag_meas_op = PauliExpectation().convert(Zexp_exact)
Zexact_values = []
for w_set in wvals:
param_bind[ww] = w_set
Zexact_values.append(np.real(diag_meas_op.bind_parameters(param_bind).eval()))
```

And plotting everything together shows that the energy at which our peaks occurs to be \(\pm \mu\).

```
[11]:
```

```
import matplotlib.pyplot as plt
plt.style.use('dark_background')
fig, ax = plt.subplots(dpi=100)
ax.plot([-param_bind[mu], -param_bind[mu]], [0, 1], ls='--', color='purple')
ax.plot([param_bind[mu], param_bind[mu]], [0, 1], ls='--', color='purple')
ax.plot(wvals, Zexact_values, label='Exact')
ax.plot(wvals, Zexps, label=f"{backend}")
ax.set_xlabel(r'$\omega$ (arb)')
ax.set_ylabel(r'$\langle Z \rangle$ Expectation')
ax.legend()
```

```
[11]:
```

```
<matplotlib.legend.Legend at 0x12c286cd0>
```

```
[12]:
```

```
import qiskit_ibm_runtime
qiskit_ibm_runtime.version.get_version_info()
```

```
[12]:
```

```
'0.7.0'
```

```
[13]:
```

```
from qiskit.tools.jupyter import *
%qiskit_version_table
```

### Version Information

Qiskit Software | Version |
---|---|

`qiskit-terra` | 0.20.0 |

`qiskit-aer` | 0.10.3 |

`qiskit-ignis` | 0.7.0 |

`qiskit-ibmq-provider` | 0.18.3 |

`qiskit` | 0.35.0 |

System information | |

Python version | 3.9.10 |

Python compiler | Clang 11.1.0 |

Python build | main, Feb 1 2022 21:27:48 |

OS | Darwin |

CPUs | 2 |

Memory (Gb) | 16.0 |

Mon Apr 11 16:17:45 2022 EDT |