How to use Monte Carlo integration with Fire Opal
Learn the basics of estimating an expectation value with integrate_monte_carlo
Fire Opal's integrate_monte_carlo function provides an end-to-end workflow for Monte Carlo integration on quantum hardware. There are two ways to define the problem:
- Let Fire Opal construct the required circuits from a supported probability distribution and objective function.
- Provide a custom preparation circuit that already encodes the quantity to estimate.
This guide uses one small example for each workflow. The goal is to focus on how to use the function rather than on a detailed financial model or a complex circuit construction.
Summary workflow
The general workflow for using integrate_monte_carlo is:
- Import Fire Opal and configure your credentials.
- Choose a supported quantum backend.
- Define the Monte Carlo problem using either:
- a supported probability distribution and objective function, or
- a custom preparation circuit.
- Submit the problem with
integrate_monte_carlo. - Retrieve and inspect the estimated value.
The first example uses a European call delta objective. The second uses a small custom circuit that represents four equally likely outcomes and marks one of them as a successful outcome.
1. Imports and initialization
Import Fire Opal.
import fireopal as fo
import qctrlvisualizer as qv
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.qasm2 import dumps
plt.style.use(qv.get_qctrl_style())If you experience an error while attempting to import the above libraries, please run the following command using the terminal. Please note that after installing new packages, you will need to restart your notebook session or kernel for the newly installed packages to be recognized.
%pip install fire-opal matplotlib qiskit-ibm-runtime qctrl-visualizer qiskit qiskit_qasm3_importAuthenticate your Q-CTRL account and create the credentials for your hardware provider. This example uses IBM Quantum.
# Authenticate your Q-CTRL account.
api_key = "YOUR_QCTRL_API_KEY"
fo.authenticate_qctrl_account(api_key=api_key)
# Create IBM Quantum credentials.
token = "YOUR_IBM_CLOUD_API_KEY"
instance = "YOUR_IBM_CRN"
credentials = fo.credentials.make_credentials_for_ibm_cloud(
token=token, instance=instance
)Q-CTRL authentication successful!
2. Choose a backend
Use show_supported_devices to list the backends available through the credentials you provided. Select one of the returned backend names for the integration.
supported_devices = fo.show_supported_devices(credentials=credentials)[
"supported_devices"
]
for device in supported_devices:
print(device)
backend_name = "your_desired_backend"ibm_kingston
ibm_boston
ibm_marrakesh
ibm_phoenix
ibm_miami
ibm_pittsburgh
ibm_fez
3. Example 1: European call delta using the high-level workflow
Fire Opal constructs the required quantum circuits from a supported probability distribution and objective function. Currently it provides the following financial objective-function constructors:
make_european_call_delta_objectivemake_european_call_pricing_objectivemake_fixed_income_pricing_objective
See the Fire Opal Monte Carlo integration overview for the supported distributions and objective functions.
For this example, consider a uniformly distributed variable represented by eight discrete points between $0$ and $1$. You use a strike price of $0.5$ and the European call delta objective.
The quantity of interest is
\begin{equation} \mathbb{E}[f(X)], \end{equation}
where
\begin{equation} f(X) = \begin{cases} 1, & X > 0.5, \\ 0, & X \leq 0.5. \end{cases} \end{equation}
This small example demonstrates the high-level interface without reproducing the complete financial model from the European options application guide.
point_count = 8
bounds = (0.0, 1.0)
strike_price = 0.5
distribution = fo.make_uniform_distribution(uncertainty=point_count)
objective = fo.make_european_call_delta_objective(
strike_price=strike_price, bounds=bounds
)
problem = fo.make_monte_carlo_problem(
objective_function_parameters=objective, distribution_parameters=distribution
)
problem{'objective_function_parameters': {'objective_function_name': 'european_call_delta',
'strike_price': 0.5,
'bounds': (0.0, 1.0)},
'distribution_parameters': {'distribution_name': 'uniform', 'uncertainty': 8}}
The eight discrete values have equal probability. Four are above the strike price and four are not, so the exact expectation value for this reference problem is
\begin{equation} \mathbb{E}[f(X)] = \frac{4}{8} = 0.5. \end{equation}
4. Run the Monte Carlo integration
Pass the assembled problem and execution settings directly to integrate_monte_carlo. Fire Opal constructs the required state-preparation and amplitude-estimation circuits and executes them on the selected backend.
For a starter example, let's limit the integration to 10 iterations.
job = fo.integrate_monte_carlo(
problem=problem,
credentials=credentials,
backend_name=backend_name,
integrator_options={"max_iteration_count": 10},
)
print("Job submitted.")This function performs multiple consecutive runs. Wait time may vary depending on hardware queues.
Job submitted.
5. Retrieve the result
integrate_monte_carlo returns a Fire Opal job. Call result() to retrieve the completed integration output.
result = job.result()
estimate = result["estimate"]
variance = result["variance"]
iteration_count = result["iteration_count"]
metadata = result["metadata"]
print(f"Estimate: {estimate:.6f}")
print(f"Variance: {variance:.6f}")
print(f"Standard deviation: {variance**0.5:.6f}")
print(f"Iterations: {iteration_count}")Estimate: 0.487435
Variance: 0.000070
Standard deviation: 0.008392
Iterations: 10
6. Compare with the reference value
In this example, the probability distribution contains eight discrete values, which can be encoded using only three qubits. Even with this small problem and a limited number of Monte Carlo iterations, the hardware estimate is already close to the target value. Increasing the number of iterations gives the integration procedure additional opportunities to refine the estimate and can improve its precision. The exact value for this simple problem is $0.5$. Comparing the hardware estimate with the reference value provides a quick check of the completed workflow.
reference_value = 0.5
absolute_difference = abs(estimate - reference_value)
print(f"Reference value: {reference_value:.6f}")
print(f"Absolute difference: {absolute_difference:.6f}")Reference value: 0.500000
Absolute difference: 0.012565
7. Example 2: submit a custom preparation circuit
You can also use integrate_monte_carlo when you already have a quantum circuit that encodes the Monte Carlo problem. The preparation circuit must encode the probability to estimate in a single-qubit register named target or output. Consider four equally likely outcomes. You want to estimate the probability that one particular outcome occurs.
Two data qubits represent the four possible outcomes:
\begin{equation} |00\rangle,\ |01\rangle,\ |10\rangle,\ |11\rangle. \end{equation}
Hadamard gates prepare them with equal probability. You then mark only $|11\rangle$ by flipping the output qubit. The preparation circuit therefore encodes
\begin{equation} p = \Pr(\text{marked outcome}) = \frac{1}{4}. \end{equation}
This is a small Monte Carlo integration problem: the circuit loads a discrete distribution and evaluates a binary objective function, while keeping the circuit construction easy to follow.
data = QuantumRegister(2, "data")
output = QuantumRegister(1, "output")
preparation_circuit = QuantumCircuit(data, output)
# Load four equally likely outcomes.
preparation_circuit.h(data[0])
preparation_circuit.h(data[1])
# Mark the |11> outcome.
preparation_circuit.ccx(data[0], data[1], output[0])
preparation_circuit.draw("text") ┌───┐
data_0: ┤ H ├──■──
├───┤ │
data_1: ┤ H ├──■──
└───┘┌─┴─┐
output: ─────┤ X ├
└───┘Serialize the circuit as OpenQASM 2 and provide it through the preparation_circuit field. Fire Opal uses this circuit as the state-preparation operator and manages the amplitude-estimation workflow, hardware execution, error suppression, and classical post-processing.
circuit_job = fo.integrate_monte_carlo(
problem={"preparation_circuit": dumps(preparation_circuit)},
credentials=credentials,
backend_name=backend_name,
integrator_options={"max_iteration_count": 10},
)
circuit_result = circuit_job.result()
circuit_estimate = circuit_result["estimate"]
circuit_reference = 0.25
print(f"Estimate: {circuit_estimate:.6f}")
print(f"Reference value: {circuit_reference:.6f}")
print(f"Absolute difference: {abs(circuit_estimate - circuit_reference):.6f}")This function performs multiple consecutive runs. Wait time may vary depending on hardware queues.
Estimate: 0.245243
Reference value: 0.250000
Absolute difference: 0.004757
Next steps
The two examples use the same integrate_monte_carlo function but start from different levels of abstraction:
- Use the high-level workflow when the problem can be described with one of Fire Opal's supported distributions and objective functions.
- Use the custom-circuit workflow when you already have a circuit that performs distribution loading and objective-function encoding.
The Fire Opal Monte Carlo integration overview lists the currently supported constructors and explains both workflows.
For a complete finance application, continue with Price European options using Monte Carlo integration.
For more detailed integrations, integrator_options can be used to configure settings such as max_iteration_count, target_variance, and full_output.
