Source code for atomiq.components.optimizers

from __future__ import annotations

from atomiq.components.primitives import Component, Parametrizable, Measurable, Switchable
from atomiq.helper import replace_member

from artiq.experiment import kernel, delay
from artiq.language.types import TStr, TFloat, TBool
from artiq.language.units import us


[docs] class Optimizer(Component): kernel_invariants = {"actor", "monitor", "actor_name"} def __init__(self, actor_component: Parametrizable, actor_name: TStr, monitor_component: Measurable, monitor_name: TStr, *args, **kwargs): Component.__init__(self, *args, **kwargs) self.actor = actor_component self.monitor = monitor_component self.actor_name = actor_name # TODO: This is bit hacky since we modify the objects we act on. Better solutions welcome.. replace_member(self.monitor, "_measure_for_optimizer", f"get_{monitor_name}") replace_member(self.actor, "_set_for_optimizer", f"set_{actor_name}")
[docs] def optimize(self): raise NotImplementedError("Implement the optimzer")
[docs] class BoundOptimizer(Optimizer): """An Optimizer with restrictions on the values of the actor. Args: actor_min: The minumum value the actor is allowed to take during optimization actor_max: The maxiumum value the actor is allowed to take during optimization """ kernel_invariants = {"actor_min", "actor_max"} def __init__(self, actor_min: TFloat, actor_max: TFloat, *args, **kwargs): Optimizer.__init__(self, *args, **kwargs) self.actor_min = actor_min self.actor_max = actor_max
[docs] class BisectionOptimizer(BoundOptimizer): """An optimizer that uses bisection to achieve the optimization target on the monitor Args: timestep: The time the individual steps in the algorithm take epsilon: Acceptable relative difference from the target value (default 0.01, i.e. 1% of the target value) switch_actor: If True, the actor (needs to be Switchable) is switched on before the measurement of the monitor and switched off after (default False) """ kernel_invariants = {"timestep", "epsilon", "max_steps", "switch_actor"} def __init__(self, timestep=325*us, epsilon=0.01, max_steps=20, switch_actor: TBool = False, *args, **kwargs): BoundOptimizer.__init__(self, *args, **kwargs) self.timestep = timestep self.epsilon = epsilon self.max_steps = max_steps @kernel def dummy_function(self): self.experiment.log.warning(self.identifier + ": cannot switch actor "+self.actor.identifier) self.switch_actor = switch_actor if not isinstance(self.actor, Switchable): self._actor_on = dummy_function.__get__(self) self._actor_off = dummy_function.__get__(self) @kernel def _actor_on(self): self.actor.on() @kernel def _actor_off(self): self.actor.off()
[docs] @kernel(flags={"fast-math"}) def optimize(self, target: TFloat) -> TFloat: soft_max = self.actor_max soft_min = self.actor_min # initial values for the bisection trial = (self.actor_max - self.actor_min) / 2 measurement = 0.0 # Do the bisection iteration for i in range(self.max_steps): delay(self.timestep / 2) # calculate and set trial = soft_min / 2 + soft_max / 2 if trial < self.actor_min: trial = self.actor_min if trial > self.actor_max: trial = self.actor_max # self.log.info(trial) self.actor._set_for_optimizer(trial) delay(self.timestep) # measure if self.switch_actor: self._actor_on() delay(self.timestep) measurement = self.monitor._measure_for_optimizer() delay(self.timestep) if self.switch_actor: self._actor_off() self.experiment.log.debug("Bisection measurement result: {0}V ", [measurement]) # compare difference = measurement - target if abs(difference / target) < self.epsilon: # self.log.info('eps') break # change borders for next iteration if difference > 0.0: soft_max = trial else: soft_min = trial delay(self.timestep / 2) # delay to avoid collisions delay(0.03*us) return trial