Asynchronous Optimization with Bluesky Queueserver#
In this tutorial, you will learn how to run Blop optimization against a remote Bluesky Queueserver. This architecture is used when:
The experiment hardware is controlled by a shared instrument server
You want the optimizer to run in a separate process from the RunEngine
You need asynchronous, non-blocking optimization (the agent submits plans and reacts to completions)
We will optimize the same Himmelblau function from the simple experiment tutorial, but now the devices live inside a remote queueserver process rather than in the same Python session as the agent.
Architecture#
The distributed system has five components:
flowchart
subgraph docker["Docker Compose Stack"]
redis["Redis"]
rem["RE Manager<br/>(devices + plans)"]
zmqp["ZMQ Proxy<br/>(pub/sub)"]
bridge["ZMQ-Tiled Bridge<br/>(persists docs to Tiled)"]
tiled["Tiled Server<br/>(data storage)"]
redis <-->|state| rem
rem -->|publishes documents| zmqp
zmqp -->|document stream| bridge
bridge -->|writes| tiled
end
agent["Blop QueueserverAgent<br/>(suggests points, listens for completions, evaluates data)"]
agent -->|submit plans via REManagerAPI| rem
zmqp -->|document stream| agent
tiled -->|read data| agent
Data flow:
The agent suggests parameter values and submits an acquisition plan to the RE Manager
The RE Manager executes the plan (moves motors, reads detectors)
Bluesky documents are published via ZMQ to the proxy
The ZMQ-Tiled bridge persists documents to the Tiled server (using bluesky-tiled-plugins’s
TiledWritercallback)The agent’s ZMQ listener detects plan completion
The agent’s evaluation function reads results from Tiled and computes objectives
The optimizer ingests outcomes and suggests the next point
Prerequisites#
Docker and Docker Compose installed
The
blopPython package installed (withbluesky-queueserver-apiandtiled)The
blopGitHub repository cloned (for the service definitions underdocs/source/tutorials/queueserver/)
Starting the Infrastructure#
All services are defined in a docker-compose.yml in the docs/source/tutorials/queueserver/ directory. Before running this tutorial, start the stack in a separate terminal:
cd docs/source/tutorials/queueserver
docker compose up -d --build
Wait until all containers are healthy:
docker compose ps
You should see all services in a “healthy” or “running” state. The services expose the following ports on localhost:
Service |
Port |
Purpose |
|---|---|---|
RE Manager |
60615 |
ZMQ control channel (REManagerAPI connects here) |
ZMQ Proxy (out) |
5578 |
Document stream (agent listens for plan completions) |
Tiled |
8000 |
Data access (evaluation function reads results) |
Redis |
6379 |
Internal message broker for queueserver |
Once the containers are up, proceed with the tutorial below.
import time
from bluesky_queueserver_api.zmq import REManagerAPI
RM = REManagerAPI(zmq_control_addr="tcp://localhost:60615")
RM.environment_open()
RM.wait_for_idle(timeout=30)
status = RM.status()
print(f"RE Manager state: {status['manager_state']}")
print(f"Worker environment exists: {status['worker_environment_exists']}")
assert status["worker_environment_exists"], "Open the RE environment before continuing (see instructions above)"
RE Manager state: idle
Worker environment exists: True
The Queueserver Environment#
The queueserver startup script (shown below for reference) defines the devices and plans available in the remote environment. This script runs inside the RE Manager process — not in your notebook:
# startup.py (runs inside the RE Manager)
from bluesky import RunEngine
from bluesky_queueserver import is_re_worker_active
from ophyd.sim import SynAxis, SynSignal
RE = RunEngine({})
# Publish documents to ZMQ so external subscribers can react
if is_re_worker_active():
import os
from bluesky.callbacks.zmq import Publisher as ZmqPublisher
addr = os.environ.get("BLUESKY_ZMQ_PROXY_IN_ADDR", "tcp://zmq-proxy:5577")
host, port = addr.replace("tcp://", "").split(":")
publisher = ZmqPublisher((host, int(port)))
RE.subscribe(publisher)
# Simulated motors
motor1 = SynAxis(name="motor1", labels={"motors"})
motor2 = SynAxis(name="motor2", labels={"motors"})
# Simulated detector that computes the Himmelblau function
def _compute_himmelblau():
x = motor1.read()["motor1"]["value"]
y = motor2.read()["motor2"]["value"]
return float((x**2 + y - 11) ** 2 + (x + y**2 - 7) ** 2)
himmel_det = SynSignal(name="himmel_det", func=_compute_himmelblau, labels={"detectors"})
# Acquisition plan — moves actuators to suggested positions and reads sensors
import bluesky.plans as bp
def default_acquire(suggestions, actuators, sensors, *, md=None):
plan_args = []
for actuator in actuators:
values = [s[actuator.name] for s in suggestions]
plan_args.append(actuator)
plan_args.append(values)
_md = {"blop_suggestions": suggestions}
if md:
_md.update(md)
yield from bp.list_scan(list(sensors), *plan_args, md=_md)
Key points:
is_re_worker_active()gates code that should only run inside the queueserver workerThe ZMQ publisher sends all run documents to the proxy for external consumption
default_acquireis a simple plan that wraps Bluesky’slist_scan— it moves actuators to each suggested position and reads sensors
Connecting to Tiled#
The evaluation function will read experimental data from the Tiled server:
from tiled.client import from_uri
tiled_client = from_uri("http://localhost:8000", api_key="tutorialkey")
Defining the Optimization Problem#
Just as in the simple experiment tutorial, we define DOFs and objectives. The key difference: since devices exist only in the remote queueserver environment, DOFs reference device names as strings (no actuator objects).
from blop.ax import QueueserverAgent, RangeDOF, Objective
dofs = [
RangeDOF(actuator="motor1", bounds=(-5.0, 5.0), parameter_type="float"),
RangeDOF(actuator="motor2", bounds=(-5.0, 5.0), parameter_type="float"),
]
objectives = [
Objective(name="himmelblau", minimize=True),
]
# Sensors are referenced by name — these are the detectors in the queueserver environment
sensors = ["himmel_det"]
[WARNING 05-19 23:15:31] ax.storage.sqa_store.with_db_settings_base: Ax currently requires a sqlalchemy version below 2.0. This will be addressed in a future release. Disabling SQL storage in Ax for now, if you would like to use SQL storage please install Ax with mysql extras via `pip install ax-platform[mysql]`.
Writing the Evaluation Function#
The evaluation function is called each time a plan completes. It reads data from Tiled and computes the objective values. The function receives:
uid: the Bluesky run UID (use this to look up data in Tiled)suggestions: the list of parameter dicts that were evaluated
It must return a list of outcome dicts, each containing the objective value(s) and an _id matching the suggestion.
Because the agent and the ZMQ-Tiled bridge are separate subscribers to the same ZMQ stream, there is a race condition: the agent may receive the stop document before the bridge has finished writing data to Tiled. The evaluation function should poll Tiled until both the run and the detector data are available.
import numpy as np
from tiled.client.container import Container
class HimmelblauEvaluation:
"""Reads detector data from Tiled and computes the Himmelblau objective."""
def __init__(self, tiled_client: Container, timeout: float = 30.0, poll_interval: float = 0.5):
self.tiled_client = tiled_client
self.timeout = timeout
self.poll_interval = poll_interval
def _wait_for_run(self, uid: str):
"""Poll Tiled until the run with the given UID is available."""
deadline = time.time() + self.timeout
while time.time() < deadline:
try:
return self.tiled_client[uid]
except KeyError:
time.sleep(self.poll_interval)
raise TimeoutError(
f"Run '{uid}' not found in Tiled after {self.timeout}s. "
"The ZMQ-Tiled bridge may not be running."
)
def _wait_for_detector_data(self, run, path: str):
"""Poll Tiled until the detector data path is readable."""
deadline = time.time() + self.timeout
while time.time() < deadline:
try:
return run[path].read()
except KeyError:
time.sleep(self.poll_interval)
raise TimeoutError(
f"Data path '{path}' for run '{run.metadata['start']['uid']}' was not readable after {self.timeout}s. "
"The ZMQ-Tiled bridge may still be writing the run."
)
def __call__(self, uid: str, suggestions: list[dict]) -> list[dict]:
run = self._wait_for_run(uid)
# Read the detector values from the primary data stream
himmel_values = self._wait_for_detector_data(run, "primary/himmel_det")
outcomes = []
for idx, suggestion in enumerate(suggestions):
outcomes.append({
"_id": suggestion["_id"],
"himmelblau": float(himmel_values[idx]),
})
return outcomes
Creating the Queueserver Agent#
Now we bring everything together. The QueueserverAgent needs:
re_manager_api: how to communicate with the queueserver (submit plans, check status)zmq_consumer_addr: where to listen for document completion eventsThe DOFs, objectives, sensors, and evaluation function
agent = QueueserverAgent(
re_manager_api=RM,
zmq_consumer_addr=("localhost", 5578),
sensors=sensors,
dofs=dofs,
objectives=objectives,
evaluation_function=HimmelblauEvaluation(tiled_client),
acquisition_plan="default_acquire",
)
Note
The re_manager_api argument also accepts an HTTP-based client
(bluesky_queueserver_api.http.REManagerAPI) for deployments that expose the
queueserver over HTTP rather than ZMQ. The zmq_consumer_addr points to the
ZMQ proxy output port (5578) where Bluesky documents are published.
Running the Optimization#
The run() method is non-blocking — it submits the first plan and returns immediately. The agent reacts asynchronously to plan completions via ZMQ callbacks.
future = agent.run(iterations=10, n_points=1)
[INFO 05-19 23:15:31] ax.api.client: GenerationStrategy(name='Center+Sobol+MBM:fast', nodes=[CenterGenerationNode(next_node_name='Sobol'), GenerationNode(name='Sobol', generator_specs=[GeneratorSpec(generator_enum=Sobol, generator_key_override=None)], transition_criteria=[MinTrials(transition_to='MBM'), MinTrials(transition_to='MBM')], suggested_experiment_status=ExperimentStatus.INITIALIZATION, pausing_criteria=[MaxTrialsAwaitingData(threshold=5)]), GenerationNode(name='MBM', generator_specs=[GeneratorSpec(generator_enum=BoTorch, generator_key_override=None)], transition_criteria=None, suggested_experiment_status=ExperimentStatus.OPTIMIZATION, pausing_criteria=None)]) chosen based on user input and problem structure.
[INFO 05-19 23:15:31] ax.api.client: Generated new trial 0 with parameters {'motor1': 0.0, 'motor2': 0.0} using GenerationNode CenterOfSearchSpace.
Wait for the optimization to complete and inspect the result:
result = future.result()
print(f"Iterations completed : {result.iterations_completed}")
print(f"Points per iteration : {result.num_points}")
print(f"Total acquisitions : {len(result.uids)}")
print()
print("Run UIDs:")
for uid in result.uids:
print(f" {uid}")
Iterations completed : 10
Points per iteration : 1
Total acquisitions : 10
Run UIDs:
32fbc221-fe74-4290-8da4-c8bf1359084b
612b07fd-042d-4625-bddf-a1ecd84ddcd4
072b67f4-7ee1-45fe-82f2-6175b507ca0a
5f0fd053-c721-4acc-8e9a-78124675963c
c16252be-e383-4e8a-9a8e-3864c7ed2bf1
c0297074-5ce3-4a70-903c-96a177d868c7
50ddd420-2732-45ba-9abe-7a22d6863289
a7245027-9bb4-401c-940c-7a7a34e1b3d9
68871038-21e2-4fd0-a802-a1b2d8053659
f79e615d-c550-457a-9733-266ad813c5e0
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:32] ax.api.client: Trial 0 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:32] ax.api.client: Generated new trial 1 with parameters {'motor1': -4.074273, 'motor2': 4.15749} using GenerationNode Sobol.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:33] ax.api.client: Trial 1 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:33] ax.api.client: Generated new trial 2 with parameters {'motor1': 1.839365, 'motor2': -3.059355} using GenerationNode Sobol.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:34] ax.api.client: Trial 2 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:34] ax.api.client: Generated new trial 3 with parameters {'motor1': 4.839857, 'motor2': 1.682082} using GenerationNode Sobol.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:35] ax.api.client: Trial 3 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:35] ax.api.client: Generated new trial 4 with parameters {'motor1': -2.372609, 'motor2': -0.593413} using GenerationNode Sobol.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:36] ax.api.client: Trial 4 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:37] ax.api.client: Generated new trial 5 with parameters {'motor1': -3.23326, 'motor2': 0.083762} using GenerationNode MBM.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:38] ax.api.client: Trial 5 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:38] ax.api.client: Generated new trial 6 with parameters {'motor1': -4.672127, 'motor2': -0.882074} using GenerationNode MBM.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:39] ax.api.client: Trial 6 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:40] ax.api.client: Generated new trial 7 with parameters {'motor1': -2.952319, 'motor2': 1.833646} using GenerationNode MBM.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:41] ax.api.client: Trial 7 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:42] ax.api.client: Generated new trial 8 with parameters {'motor1': -2.390378, 'motor2': 1.966985} using GenerationNode MBM.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:42] ax.api.client: Trial 8 marked COMPLETED.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:43] ax.api.client: Generated new trial 9 with parameters {'motor1': -2.560377, 'motor2': 2.648524} using GenerationNode MBM.
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
[INFO 05-19 23:15:43] ax.api.client: Trial 9 marked COMPLETED.
Viewing Results#
Since QueueserverAgent uses the same Ax optimizer backend as the local Agent, all the familiar analysis methods are available:
agent.ax_client.summarize()
/home/runner/work/blop/blop/.pixi/envs/docs/lib/python3.13/site-packages/ax/core/data.py:365: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
self.full_df.groupby(self.DEDUPLICATE_BY_COLUMNS).apply(
| trial_index | arm_name | trial_status | generation_node | himmelblau | motor1 | motor2 | |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0_0 | COMPLETED | CenterOfSearchSpace | 170.000000 | 0.000000 | 0.000000 |
| 1 | 1 | 1_0 | COMPLETED | Sobol | 133.772427 | -4.074273 | 4.157490 |
| 2 | 2 | 2_0 | COMPLETED | Sobol | 131.610719 | 1.839365 | -3.059355 |
| 3 | 3 | 3_0 | COMPLETED | Sobol | 199.435458 | 4.839857 | 1.682082 |
| 4 | 4 | 4_0 | COMPLETED | Sobol | 116.939819 | -2.372609 | -0.593413 |
| 5 | 5 | 5_0 | COMPLETED | MBM | 104.789759 | -3.233260 | 0.083762 |
| 6 | 6 | 6_0 | COMPLETED | MBM | 217.617593 | -4.672127 | -0.882074 |
| 7 | 7 | 7_0 | COMPLETED | MBM | 43.631569 | -2.952319 | 1.833646 |
| 8 | 8 | 8_0 | COMPLETED | MBM | 41.501757 | -2.390378 | 1.966985 |
| 9 | 9 | 9_0 | COMPLETED | MBM | 9.705999 | -2.560377 | 2.648524 |
The Himmelblau function has four global minima (all with value 0). The optimizer should have made some progress toward these optima.
(3.0, 2.0)
(-2.805, 3.131)
(-3.779, -3.283)
(3.584, -1.848)
Cleanup#
When you’re done, close the RE environment and stop the Docker services:
RM.environment_close()
RM.wait_for_idle(timeout=30)
RM.close()
cd docs/source/tutorials/queueserver
docker compose down
What You Learned#
Distributed architecture: The queueserver separates experiment execution from optimization logic, connected via ZMQ and Tiled
String-based device references: Since devices live in the remote process, DOFs, sensors, and plans are referenced by name
Asynchronous operation:
agent.run()is non-blocking; the agent reacts to events via ZMQ callbacksEvaluation function: Reads from Tiled (not direct device access) to compute objectives after each plan completes
Next Steps#
Add multiple objectives for multi-objective optimization (see KB Mirrors tutorial)
Use
agent.submit_suggestions()to manually evaluate specific parameter combinations (see Manual Point Injection)Implement
outcome_constraintsto constrain the optimization (see Set outcome constraints)Add a
checkpoint_pathto persist optimizer state across restarts (see Agent)