# How to manage automated closed-loop hardware optimization with M-LOOP

Use external data management package for simple closed-loop optimizations

Boulder Opal contains automated closed-loop optimization tools that allow complex control optimization without requiring a complete understanding of the workings of your quantum system. These tools allow you to run optimizations incorporating input data rather than building a detailed model of all aspects of your system. However data management must be handled manually in the optimization procedure. This notebook will show how you can use the M-LOOP package to simplify management of optimization workflows.

## Closed-loop optimization framework

You can use the automated closed-loop optimizers to create a closed optimization loop where the optimizer communicates with the experimental apparatus without your direct involvement. In this kind of setting, your experimental apparatus produces an initial set of results, which it sends to the optimizer. Using this information, the optimizer produces a set of improved test points that it recommends back to the experimental apparatus. The results corresponding to these test points are resent to the optimizer, and the cycle repeats itself until any of the results has a sufficiently low cost function value, or until it meets any other ending condition that you imposed. In simple cases the construction and management of the optimization loop can be handled with the open-source M-LOOP package.

## Summary workflow

### 1. Installation

Install the Q-CTRL M-LOOP Python package (qctrlmloop), which acts as an interface between M-LOOP and Boulder Opal, with pip install qctrl-mloop.

### 2. Define QctrlController for use in optimization

The Q-CTRL M-LOOP Python package provides the QctrlController class, a controller that accepts an initial instance of the Optimizer object and then handles the subsequent calls to the experimental interface, the next steps of the automated closed-loop optimization, and the updates to the state of the Optimizer.

Using the QctrlController, you don't need to set up the optimization loop by yourself, and the optimization will run until it meets the conditions that you defined in the arguments of the controller (see the documentation of the Controller base class for a description of the options available). In this framework you can still select the optimization engine from Boulder Opal and simply pass this information to QctrlController. Note that this framework does not support experimental batching inside the optimization loop.

You can then use an Interface object from M-LOOP to call your experimental apparatus, and the QctrlController to send the retrieved data to Boulder Opal. A simple setup would look like the following (where the settings passed to Interface and Optimizer depend on the optimization that you want to perform):

interface = Interface(<interface settings>)
optimizer = qctrl.types.closed_loop_optimization_step.Optimizer(<optimizer settings>)
controller = QctrlController(
interface, qctrl=qctrl, optimizer=optimizer, target_cost=0.01, num_params=1,
)
controller.optimize()

## Example: Optimization over a simulated 2D landscape with M-LOOP

In practice, you would use an interface to an actual experiment to obtain the costs that correspond to each test point that the automated closed-loop optimizer requests. To keep this example simple, consider that the cost function is just a 2D parabola with a minimum at $(0, 0.5)$, and the objective of the optimization is to find this minimum.

import numpy as np
from qctrl import Qctrl

# Start a Boulder Opal session.
qctrl = Qctrl()
import logging

# Import objects from M-LOOP and the Q-CTRL M-LOOP Python package.
from mloop.interfaces import Interface
from qctrlmloop import QctrlController

# Define the experimental interface (in this case, a parabolic cost function).
minimum = np.array([0, 0.5])

class CustomInterface(Interface):
def get_next_cost_dict(self, params_dict):
params = params_dict["params"]
cost = sum((params - minimum) ** 2)
return {"cost": cost}

interface = CustomInterface()

# Define initialization object for the automated closed-loop optimization.
# The test points are constrained to the interval (-1, 1).
initializer = qctrl.types.closed_loop_optimization_step.CrossEntropyInitializer(
elite_fraction=0.2,
bounds=[
qctrl.types.closed_loop_optimization_step.BoxConstraint(
lower_bound=-1, upper_bound=1
),
qctrl.types.closed_loop_optimization_step.BoxConstraint(
lower_bound=-1, upper_bound=1
),
],
)

# Define state object for the automated closed-loop optimization.
optimizer = qctrl.types.closed_loop_optimization_step.Optimizer(
cross_entropy_initializer=initializer
)

# Define the controller that handles the calls to Boulder Opal.
controller = QctrlController(
interface,
qctrl=qctrl,
optimizer=optimizer,
target_cost=0.001,
num_params=2,
test_point_count=200,
)

# Set verbosity to the lowest possible.
# (See: https://docs.python.org/3/library/logging.html#logging-levels)
interface.log.setLevel(logging.CRITICAL)
controller.log.setLevel(logging.CRITICAL)

# Run the optimization.
controller.optimize()

# Print results.
print(f"Best cost: {controller.best_cost}")
print(f"Best parameter set: {controller.best_params}")
print(f"Minimum of the cost function: {minimum}")
INFO     M-LOOP version 3.3.1

Best cost: 0.0009857826491870274
Best parameter set: [-0.02764526  0.48511637]
Minimum of the cost function: [0.  0.5]


This notebook was run using the following package versions. It should also be compatible with newer versions of the Q-CTRL Python package.

PackageVersion
Python3.9.16
M-LOOP3.3.1
numpy1.24.2
scipy1.10.0
qctrl20.1.1
qctrl-commons17.9.1
qctrl-mloop2.1.1
boulder-opal-toolkits2.0.0-beta.3