def _set_setpoints(self, names, values): kwargs = dict(zip(names, np.atleast_1d(values))) set_point = FieldVector() set_point.copy(self._set_point) if len(kwargs) == 3: set_point.set_vector(**kwargs) else: set_point.set_component(**kwargs) self._adjust_child_instruments(set_point.get_components("x", "y", "z")) self._set_point = set_point
def _set_target(self, coordinate: str, target: float) -> None: """ The function to set a target value for a coordinate, i.e. the set_cmd for the XXX_target parameters """ # first validate the new target valid_vec = FieldVector() valid_vec.copy(self._target_vector) valid_vec.set_component(**{coordinate: target}) components = valid_vec.get_components('x', 'y', 'z') if not self._field_limits(*components): raise ValueError(f'Cannot set {coordinate} target to {target}, ' 'that would violate the field_limits. ') # update our internal target cache self._target_vector.set_component(**{coordinate: target}) # actually assign the target on the slaves cartesian_targ = self._target_vector.get_components('x', 'y', 'z') for targ, slave in zip(cartesian_targ, self.submodules.values()): slave.field_target(targ)
def observe(current_field: FieldVector): new_loc = FieldVector() new_loc.copy(current_field) locations.append(new_loc) objectives_meas.append(self.objective())
def set_field(self, target_field: FieldVector, n_steps: int = 10, absolute: bool = True, ramp_rate: float = 1e-3, observer_fn: Optional[Callable[[FieldVector], None]] = None, verbose: bool = False, threshold: float = 1e-6) -> None: """ Sets the field controlled by this problem's instrument to a given target field by taking small steps, measuring, and then updating the target accordingly. Args: target_field: The value of the field that should be set, or the difference between the current and target field if `absolute=True`. n_steps: The number of steps that should be taken in order to reach the given target. absolute: Indicates whether `target_field` is the target field, or a difference from the current field to the target. ramp_rate: A rate at which the field can be safely swept between points. observer_fn: A callable which gets called after each small step. threshold: If the norm of the difference between the current and target field is smaller than this value, no further steps will be taken. """ if IPYTHON and ipw is not None: status = ipw.Label() def update(field: FieldVector): status.value = field.repr_spherical() def finish(): status.value = "Move complete." display(status) else: def update(field: FieldVector): print( f'Magnet reached (r, phi, theta) = ({field.r}, {field.phi}, {field.theta})', end='\r') def finish(): print("Move complete.") target = FieldVector() if absolute: target.copy(target_field) else: initial = self.instrument.field_measured() target = target_field + initial with temporary_setting( self.instrument.field_ramp_rate, FieldVector(x=ramp_rate, y=ramp_rate, z=ramp_rate)): for step_amount in np.linspace(0, 1, n_steps + 1)[1:]: current = self.instrument.field_measured() intermediate_target = step_amount * (target - current) + current if (intermediate_target - current).norm() <= threshold: print("Step threshold met, stopping move early.") break if verbose: print( f"Setting field target to {intermediate_target.repr_spherical()}" ) self.instrument.field_target(intermediate_target) self.instrument.ramp() time.sleep(0.1) current = self.instrument.field_measured() if observer_fn is not None: observer_fn(current) update(current) time.sleep(0.1) finish()
def optimize_at_fixed_magnitude( self, r: float, phi_range: Interval[float], n_phi: int, theta_range: Interval[float], n_theta: int, return_extra: bool = False, n_steps: int = 5, plot: bool = False, verbose: bool = False, ramp_rate: float = 1.5e-3) -> OptionalExtra[FieldVector]: """ Given the magnitude of a magnetic field, maximizes the objective over the spherical coordinates phi and theta by an exhaustive search. Args: r: The magnitude of the magnetic field to be optimized over angles. phi_range: The interval over which phi will be searched. n_phi: The number of distinct values of phi to be evaluated. theta_range: The interval over which theta will be searched. n_theta: The number of distinct values of theta to be evaluated. return_extra: If `True`, this method will return additional data as a dictionary. plot: If `True`, produces a plot of the path that this method took to find the optimal objective value. Returns: The optimal field found by an exhaustive seach. If `return_extra=True`, this method returns a tuple of the optimal field and a dictionary containing diagnostic data. """ locations = [] objectives_meas = [] still_to_visit = [ FieldVector(r=r, phi=phi, theta=theta) for phi in np.linspace(phi_range[0], phi_range[1], n_phi) for theta in np.linspace(theta_range[0], theta_range[1], n_theta) ] def observe(current_field: FieldVector): new_loc = FieldVector() new_loc.copy(current_field) locations.append(new_loc) objectives_meas.append(self.objective()) while still_to_visit: # Find the nearest point. current = self.instrument.field_measured() nearest = min(still_to_visit, key=current.distance) still_to_visit.remove(nearest) print( f"Evaluating at phi = {nearest.phi}, theta = {nearest.theta}") self.set_field(nearest, absolute=True, n_steps=n_steps, ramp_rate=ramp_rate, observer_fn=observe, verbose=verbose) observe(self.instrument.field_measured()) extra = {'objectives': objectives_meas, 'field_vectors': locations} idx_flat_best = np.argmax(objectives_meas) optimum = FieldVector() optimum.copy(locations[idx_flat_best]) # Renormalize to desired B. optimum['r'] = r # Move the field before returning. print( f"Found optimum for |B| = {r} at ({optimum.phi}, {optimum.theta})." ) self.set_field(optimum, absolute=True) if plot: plt_xs = [vec.phi for vec in extra['field_vectors']] plt_ys = [vec.theta for vec in extra['field_vectors']] plt.figure() plt.plot( plt_xs, plt_ys, ) plt.scatter(plt_xs, plt_ys, c=extra['objectives']) plt.colorbar() if return_extra: return optimum, extra else: return optimum