Quick Start#

If you want to get started as quickly as possible, this is the right page for you! For in-depth explanations and more complex use cases, see User Guide.

Note

Alternatively, you could also work in our interactive Google Colab Notebook. While the Colab does not require any local installations, it is more extensive than the tutorial below, and takes longer to go through.

Create circuit#

The fermioniq software package integrates seemlessly with Qiskit and Cirq. To emulate a quantum circuit of your choice, start by defining the circuit. In the example below, we also compute expectation values of observables. Also those can be defined via third-party frameworks.

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

# -- Create two qiskit circuits --
circuit = QuantumCircuit(4)
circuit.h(0)
circuit.cx(0, 1)
circuit.z(2)
circuit.cx(2, 3)
circuit.cx(1, 2)
print("My circuit:")
print(circuit)

# -- Setup observables --
# First observable: -1.0 * XIIY + 2 * IIZI
qiskit_obs1 = SparsePauliOp.from_sparse_list(
    [("XY", [0, 3], -1), ("Z", [2], 2)], num_qubits=circuit.num_qubits
)

# Second observable -1.5 * IIIZ
qiskit_obs2 = SparsePauliOp.from_sparse_list(
    [("Z", [3], -1.5)], num_qubits=circuit.num_qubits
)

Setup client#

We also need to instantiate the Fermioniq Client. Here we simply load our access tokens from a file called my-token.json, but it is also possible to use environment variables to set up your credentials (see Setup)

import json
import fermioniq

with open('my-token.json', 'r') as f:
    token_dict = json.load(f)

client = fermioniq.Client(
    access_token_id=token_dict["id"],
    access_token_secret=token_dict["secret"],
)

Configure emulator#

To get the best possible performance and to specify the output, the emulator needs to be configured properly. There are many possible options and parameters to play with (see Emulating Quantum Circuits for more details), but the most important one is the bond dimension D. The emulator approximates the state prepared by the circuit by a Matrix Product State (MPS). A higher bond dimension makes the approximation more accurate (increasing the fidelity), but also increases the computational costs.

In the example below, we specify the bond dimension D=10 and that we want to compute the expectation values of the two previously defined observables. It is also possible to ask for amplitudes of the final quantum state, or sample bitstrings in the computational basis. But both these modes are disabled below.

config = {
    "dmrg": {  # dmrg settings (only required if "mode": "dmrg"), see 'extra dmrg options' for more below
        "D": 10,  # bond dimension (it is possible to specify multiple bond dimensions)
    },
    "output": {  # Enable at least one of four below by setting "enabled": True
        "amplitudes": {
            "enabled": False,
            "bitstrings": "all",  # Either 'all' (for <11 qubits), or a list of bitstrings, e.g. ["0010", "1110"]
        },
        "sampling": {"enabled": False, "n_shots": 1000},
        "expectation_values": {
            "enabled": True,
            "observables": {"obs1": qiskit_obs1, "obs2": qiskit_obs2}  # dictionary of observables
        },
    },
}

If the emulation fidelity is too low, the bond dimension can be increased. For a bond dimension of >100, we recommend using a GPU backend. See 4) Choosing different backends for more details on choosing different backends.

Run emulation#

We can now create and submit our job! In the example below we schedule our job asynchronously, which means we have to retrieve the result at a later time. To do this, we save the job ID.

emulator_job = fermioniq.EmulatorJob(
    circuit = circuit,
    config = config,
)

job = client.schedule_async(emulator_job)
job_id = job.id

We check whether the job has completed or not, and retrieve and print the results if it has terminated:

import rich

job_status = client.status(job_id)
if job_status == 'finished':
    results = client.get_results(job_id)
    rich.print(results)

Noisy quantum circuits#

It is also possible to specify more advanced modes of emulation. This includes simulating noise, choosing different backends (Number of CPU cores and/or GPU), running VQE routines, simulating different quantum trajectories with intermediate measurements and more. All of this can be found in the more extensive section Advanced Emulation.

In the example below we cover one of these: noisy emulations. The Fermioniq NoiseModel is very flexible, but in this example we limit ourselves to depolarizing noise. We define two depolarizing channels, one with p=0.001 which can act on a single qubit and one with p=0.01 which can act on 2-qubits. We then instantiate a NoiseModel, and specify that the two channels should be applied after any gate which acts on 1 or 2 qubits, respectively. We also define a readout error to be applied any time we wish to measure the qubits. We then simply create an EmulatorJob in the same way as before, except for the additional keyword noise_model.

dep_channel_1q = fermioniq.DepolarizingChannel(p=0.001, num_qubits=1)
dep_channel_2q = fermioniq.DepolarizingChannel(p=0.01, num_qubits=2)
noise_model = fermioniq.NoiseModel(qubits=circuit.qubits)
noise_model.add(
    gate_name=fermioniq.ANY,
    gate_qubits=1,
    channel=dep_channel_1q,
    channel_qubits="same",
)
noise_model.add(
    gate_name=fermioniq.ANY,
    gate_qubits=2,
    channel=dep_channel_2q,
    channel_qubits="same",
)
noise_model.add_readout_error(
    fermioniq.ANY, 0.05, 0.07
)  # p=0.05 to read 0 as 1, p=0.07 to read 1 as 0.

emulator_job_noisy = fermioniq.EmulatorJob(
    circuit=circuit,
    config=config,  # using the config from above
    noise_model=noise_model,
)

This job can then be sent in exactly the same way as the previous job! More advanced noise models can encompass customized channels (defined with Kraus operators), different channels for different gates and cross-talk (application of channels to qubit X after a gate acting on qubit Y). For more details on this, see 5) Noisy emulation.