|
1 | | -import importlib.util |
2 | 1 | import logging |
3 | 2 | from collections.abc import Mapping, Sequence |
4 | | -from concurrent.futures import Future |
5 | 3 | from typing import Any, cast |
6 | 4 |
|
| 5 | +import bluesky.preprocessors as bpp |
7 | 6 | from ax import Client, TOutcome, TParameterization |
8 | 7 | from ax.analysis.plotly.surface.contour import ContourPlot |
| 8 | +from ax.core.analysis_card import AnalysisCardBase |
9 | 9 | from ax.core.types import TParamValue |
10 | | - |
11 | | -# =============================== |
12 | | -# TODO: Remove when Python 3.10 is no longer supported |
13 | | -if importlib.util.find_spec("ax.core.analysis_card") is not None: |
14 | | - from ax.core.analysis_card import AnalysisCardBase |
15 | | -else: |
16 | | - from ax.analysis.analysis_card import AnalysisCardBase # type: ignore[import-untyped] |
17 | | -# =============================== |
18 | | -import bluesky.preprocessors as bpp |
19 | 10 | from bluesky.callbacks import CallbackBase |
20 | | -from bluesky.callbacks.zmq import RemoteDispatcher |
21 | 11 | from bluesky.utils import MsgGenerator |
22 | | -from bluesky_queueserver_api.zmq import REManagerAPI |
23 | 12 |
|
24 | 13 | from ..callbacks.logger import OptimizationLogger |
25 | 14 | from ..callbacks.router import OptimizationCallbackRouter |
|
30 | 19 | Actuator, |
31 | 20 | EvaluationFunction, |
32 | 21 | OptimizationProblem, |
33 | | - QueueserverOptimizationProblem, |
34 | 22 | Sensor, |
35 | 23 | ) |
36 | | -from ..queueserver import OptimizationResult, QueueserverClient, QueueserverOptimizationRunner |
37 | 24 | from ..utils import InferredReadable |
38 | 25 | from .dof import DOF, DOFConstraint |
39 | 26 | from .objective import Objective, OutcomeConstraint, ScalarizedObjective, to_ax_objective_str |
@@ -561,182 +548,3 @@ def navigate_to_best(self, parameterization: Mapping | None = None) -> MsgGenera |
561 | 548 | parameterization, |
562 | 549 | ) |
563 | 550 | ) |
564 | | - |
565 | | - |
566 | | -class QueueserverAgent(_AxAgentMixin): |
567 | | - """ |
568 | | - An asynchronous interface that uses Ax as the backend for optimization and experiment tracking |
569 | | - and the bluesky-queueserver-api for scheduling plan execution. |
570 | | -
|
571 | | - .. warning:: |
572 | | -
|
573 | | - This class is **experimental**. The API is not yet stable and may change |
574 | | - in future releases without a deprecation period. It is not recommended for |
575 | | - production use. |
576 | | -
|
577 | | - Parameters |
578 | | - ---------- |
579 | | - re_manager_api : REManagerAPI |
580 | | - The manager API for interaction with Bluesky queueserver. |
581 | | - document_dispatcher : RemoteDispatcher |
582 | | - Dispatcher for consuming Bluesky documents from the remote server. |
583 | | - sensors : Sequence[str] |
584 | | - The sensors to use for acquisition. These should be the minimal set |
585 | | - of sensors that are needed to compute the objectives. |
586 | | - dofs : Sequence[DOF] |
587 | | - The degrees of freedom that the agent can control, which determine the search space. |
588 | | - objectives : Sequence[Objective] |
589 | | - The objectives which the agent will try to optimize. |
590 | | - evaluation_function : EvaluationFunction |
591 | | - The function to evaluate acquired data and produce outcomes. |
592 | | - acquisition_plan : str | None, optional |
593 | | - The acquisition plan to use for acquiring data from the beamline. If not provided, |
594 | | - :func:`blop.plans.default_acquire` will be assumed. |
595 | | - dof_constraints : Sequence[DOFConstraint] | None, optional |
596 | | - Constraints on DOFs to refine the search space. |
597 | | - outcome_constraints : Sequence[OutcomeConstraint] | None, optional |
598 | | - Constraints on outcomes to be satisfied during optimization. |
599 | | - checkpoint_path : str | None, optional |
600 | | - The path to the checkpoint file to save the optimizer's state to. |
601 | | - **kwargs : Any |
602 | | - Additional keyword arguments to configure the Ax experiment. |
603 | | -
|
604 | | - See Also |
605 | | - -------- |
606 | | - blop.protocols.Sensor : The protocol for sensors. |
607 | | - blop.ax.dof.RangeDOF : For continuous parameters. |
608 | | - blop.ax.dof.ChoiceDOF : For discrete parameters. |
609 | | - blop.ax.objective.Objective : For defining objectives. |
610 | | - blop.ax.optimizer.AxOptimizer : The optimizer used internally. |
611 | | - blop.queueserver.QueueserverOptimizatonRunner : Runner that handles interaction with bluesky-queueserver. |
612 | | - """ |
613 | | - |
614 | | - def __init__( |
615 | | - self, |
616 | | - re_manager_api: REManagerAPI, |
617 | | - document_dispatcher: RemoteDispatcher, |
618 | | - sensors: Sequence[str], |
619 | | - dofs: Sequence[DOF], |
620 | | - objectives: Sequence[Objective], |
621 | | - evaluation_function: EvaluationFunction, |
622 | | - acquisition_plan: str | None = None, |
623 | | - dof_constraints: Sequence[DOFConstraint] | None = None, |
624 | | - outcome_constraints: Sequence[OutcomeConstraint] | None = None, |
625 | | - checkpoint_path: str | None = None, |
626 | | - acquisition_plan_kwargs: Mapping[str, Any] | None = None, |
627 | | - **kwargs: Any, |
628 | | - ): |
629 | | - self._sensors = sensors |
630 | | - self._actuators: Sequence[str] = [] |
631 | | - for dof in dofs: |
632 | | - if dof.actuator is not None: |
633 | | - if isinstance(dof.actuator, Actuator): |
634 | | - self._actuators.append(dof.actuator.name) |
635 | | - else: |
636 | | - self._actuators.append(dof.actuator) |
637 | | - self._evaluation_function = evaluation_function |
638 | | - self._acquisition_plan = acquisition_plan |
639 | | - self._acquisition_plan_kwargs = acquisition_plan_kwargs or {} |
640 | | - self._optimizer = AxOptimizer( |
641 | | - parameters=[dof.to_ax_parameter_config() for dof in dofs], |
642 | | - objective=to_ax_objective_str(objectives), |
643 | | - parameter_constraints=[constraint.ax_constraint for constraint in dof_constraints] if dof_constraints else None, |
644 | | - outcome_constraints=[constraint.ax_constraint for constraint in outcome_constraints] |
645 | | - if outcome_constraints |
646 | | - else None, |
647 | | - checkpoint_path=checkpoint_path, |
648 | | - **kwargs, |
649 | | - ) |
650 | | - self._runner = QueueserverOptimizationRunner( |
651 | | - self.to_optimization_problem(), |
652 | | - QueueserverClient(re_manager_api, document_dispatcher), |
653 | | - ) |
654 | | - |
655 | | - @property |
656 | | - def evaluation_function(self) -> EvaluationFunction: |
657 | | - return self._evaluation_function |
658 | | - |
659 | | - @property |
660 | | - def actuators(self) -> Sequence[str]: |
661 | | - return self._actuators |
662 | | - |
663 | | - @property |
664 | | - def sensors(self) -> Sequence[str]: |
665 | | - return self._sensors |
666 | | - |
667 | | - @property |
668 | | - def acquisition_plan(self) -> str | None: |
669 | | - return self._acquisition_plan |
670 | | - |
671 | | - def stop(self) -> None: |
672 | | - self._runner.stop() |
673 | | - |
674 | | - @property |
675 | | - def current_iteration(self) -> int: |
676 | | - return self._runner.current_iteration |
677 | | - |
678 | | - def to_optimization_problem(self) -> QueueserverOptimizationProblem: |
679 | | - return QueueserverOptimizationProblem( |
680 | | - optimizer=self._optimizer, |
681 | | - actuators=self._actuators, |
682 | | - sensors=self._sensors, |
683 | | - evaluation_function=self._evaluation_function, |
684 | | - acquisition_plan=self._acquisition_plan, |
685 | | - acquisition_plan_kwargs=self._acquisition_plan_kwargs, |
686 | | - ) |
687 | | - |
688 | | - def run(self, iterations: int = 1, n_points: int = 1) -> Future[OptimizationResult]: |
689 | | - """ |
690 | | - Start the optimization loop. |
691 | | -
|
692 | | - Validates the queueserver state, then begins the suggest -> acquire -> ingest |
693 | | - cycle. This method returns immediately; the optimization runs asynchronously |
694 | | - via callbacks. |
695 | | -
|
696 | | - Parameters |
697 | | - ---------- |
698 | | - iterations : int |
699 | | - Number of optimization iterations to run. |
700 | | - n_points : int |
701 | | - Number of points to suggest per iteration. |
702 | | -
|
703 | | - Returns |
704 | | - ------- |
705 | | - concurrent.futures.Future[OptimizationResult] |
706 | | - A future that resolves to an :class:`~blop.queueserver.OptimizationResult` |
707 | | - when all iterations complete or when :meth:`stop` is called. If an |
708 | | - unhandled exception occurs the future will hold it and re-raise on |
709 | | - ``.result()``. |
710 | | -
|
711 | | - Raises |
712 | | - ------ |
713 | | - RuntimeError |
714 | | - If the queueserver environment is not ready. |
715 | | - ValueError |
716 | | - If required devices or plans are not available. |
717 | | - """ |
718 | | - return self._runner.run(iterations, n_points) |
719 | | - |
720 | | - def submit_suggestions(self, suggestions: list[dict]) -> Future[OptimizationResult]: |
721 | | - """ |
722 | | - Evaluate specific parameter combinations. |
723 | | -
|
724 | | - Acquires data for given suggestions and ingests results. Supports both |
725 | | - optimizer suggestions and manual points. |
726 | | -
|
727 | | - Parameters |
728 | | - ---------- |
729 | | - suggestions : list[dict] |
730 | | - Either optimizer suggestions (with "_id") or manual points (without "_id"). |
731 | | -
|
732 | | - Returns |
733 | | - ------- |
734 | | - concurrent.futures.Future[OptimizationResult] |
735 | | - A future that resolves to an :class:`~blop.queueserver.OptimizationResult` |
736 | | - when the acquisition completes. |
737 | | -
|
738 | | - See Also |
739 | | - -------- |
740 | | - run : Run the full optimization loop. |
741 | | - """ |
742 | | - return self._runner.submit_suggestions(suggestions) |
0 commit comments