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
class AMI430_3D(Instrument): def __init__(self, name, instrument_x, instrument_y, instrument_z, field_limit, **kwargs): super().__init__(name, **kwargs) if not isinstance(name, str): raise ValueError("Name should be a string") instruments = [instrument_x, instrument_y, instrument_z] if not all( [isinstance(instrument, AMI430) for instrument in instruments]): raise ValueError("Instruments need to be instances " "of the class AMI430") self._instrument_x = instrument_x self._instrument_y = instrument_y self._instrument_z = instrument_z if repr(field_limit).isnumeric() or isinstance(field_limit, collections.Iterable): self._field_limit = field_limit else: raise ValueError("field limit should either be" " a number or an iterable") self._set_point = FieldVector(x=self._instrument_x.field(), y=self._instrument_y.field(), z=self._instrument_z.field()) # Get-only parameters that return a measured value self.add_parameter('cartesian_measured', get_cmd=partial(self._get_measured, 'x', 'y', 'z'), unit='T') self.add_parameter('x_measured', get_cmd=partial(self._get_measured, 'x'), unit='T') self.add_parameter('y_measured', get_cmd=partial(self._get_measured, 'y'), unit='T') self.add_parameter('z_measured', get_cmd=partial(self._get_measured, 'z'), unit='T') self.add_parameter('spherical_measured', get_cmd=partial(self._get_measured, 'r', 'theta', 'phi'), unit='T') self.add_parameter('phi_measured', get_cmd=partial(self._get_measured, 'phi'), unit='deg') self.add_parameter('theta_measured', get_cmd=partial(self._get_measured, 'theta'), unit='deg') self.add_parameter('field_measured', get_cmd=partial(self._get_measured, 'r'), unit='T') self.add_parameter('cylindrical_measured', get_cmd=partial(self._get_measured, 'rho', 'phi', 'z'), unit='T') self.add_parameter('rho_measured', get_cmd=partial(self._get_measured, 'rho'), unit='T') # Get and set parameters for the set points of the coordinates self.add_parameter('cartesian', get_cmd=partial(self._get_setpoints, 'x', 'y', 'z'), set_cmd=self._set_cartesian, unit='T', vals=Anything()) self.add_parameter('x', get_cmd=partial(self._get_setpoints, 'x'), set_cmd=self._set_x, unit='T', vals=Numbers()) self.add_parameter('y', get_cmd=partial(self._get_setpoints, 'y'), set_cmd=self._set_y, unit='T', vals=Numbers()) self.add_parameter('z', get_cmd=partial(self._get_setpoints, 'z'), set_cmd=self._set_z, unit='T', vals=Numbers()) self.add_parameter('spherical', get_cmd=partial(self._get_setpoints, 'r', 'theta', 'phi'), set_cmd=self._set_spherical, unit='tuple?', vals=Anything()) self.add_parameter('phi', get_cmd=partial(self._get_setpoints, 'phi'), set_cmd=self._set_phi, unit='deg', vals=Numbers()) self.add_parameter('theta', get_cmd=partial(self._get_setpoints, 'theta'), set_cmd=self._set_theta, unit='deg', vals=Numbers()) self.add_parameter('field', get_cmd=partial(self._get_setpoints, 'r'), set_cmd=self._set_r, unit='T', vals=Numbers()) self.add_parameter('cylindrical', get_cmd=partial(self._get_setpoints, 'rho', 'phi', 'z'), set_cmd=self._set_cylindrical, unit='tuple?', vals=Anything()) self.add_parameter('rho', get_cmd=partial(self._get_setpoints, 'rho'), set_cmd=self._set_rho, unit='T', vals=Numbers()) def _verify_safe_setpoint(self, setpoint_values): if repr(self._field_limit).isnumeric(): return np.linalg.norm(setpoint_values) < self._field_limit answer = any([ limit_function(*setpoint_values) for limit_function in self._field_limit ]) return answer def _set_fields(self, values): """ Set the fields of the x/y/z magnets. This function is called whenever the field is changed and performs several safety checks to make sure no limits are exceeded. Args: values (tuple): a tuple of cartesian coordinates (x, y, z). """ log.debug("Checking whether fields can be set") # Check if exceeding the global field limit if not self._verify_safe_setpoint(values): raise ValueError("_set_fields aborted; field would exceed limit") # Check if the individual instruments are ready for name, value in zip(["x", "y", "z"], values): instrument = getattr(self, "_instrument_{}".format(name)) if instrument.ramping_state() == "ramping": msg = '_set_fields aborted; magnet {} is already ramping' raise AMI430Exception(msg.format(instrument)) # Now that we know we can proceed, call the individual instruments log.debug("Field values OK, proceeding") for operator in [np.less, np.greater]: # First ramp the coils that are decreasing in field strength. # This will ensure that we are always in a safe region as # far as the quenching of the magnets is concerned for name, value in zip(["x", "y", "z"], values): instrument = getattr(self, "_instrument_{}".format(name)) current_actual = instrument.field() # If the new set point is practically equal to the # current one then do nothing if np.isclose(value, current_actual, rtol=0, atol=1e-8): continue # evaluate if the new set point is smaller or larger # than the current value if not operator(abs(value), abs(current_actual)): continue instrument.set_field(value, perform_safety_check=False) def _request_field_change(self, instrument, value): """ This method is called by the child x/y/z magnets if they are set individually. It results in additional safety checks being performed by this 3D driver. """ if instrument is self._instrument_x: self._set_x(value) elif instrument is self._instrument_y: self._set_y(value) elif instrument is self._instrument_z: self._set_z(value) else: msg = 'This magnet doesnt belong to its specified parent {}' raise NameError(msg.format(self)) def _get_measured(self, *names): x = self._instrument_x.field() y = self._instrument_y.field() z = self._instrument_z.field() measured_values = FieldVector(x=x, y=y, z=z).get_components(*names) # Convert angles from radians to degrees d = dict(zip(names, measured_values)) # Do not do "return list(d.values())", because then there is # no guaranty that the order in which the values are returned # is the same as the original intention return_value = [d[name] for name in names] if len(names) == 1: return_value = return_value[0] return return_value def _get_setpoints(self, *names): measured_values = self._set_point.get_components(*names) # Convert angles from radians to degrees d = dict(zip(names, measured_values)) return_value = [d[name] for name in names] # Do not do "return list(d.values())", because then there is # no guarantee that the order in which the values are returned # is the same as the original intention if len(names) == 1: return_value = return_value[0] return return_value def _set_cartesian(self, values): x, y, z = values self._set_point.set_vector(x=x, y=y, z=z) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_x(self, x): self._set_point.set_component(x=x) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_y(self, y): self._set_point.set_component(y=y) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_z(self, z): self._set_point.set_component(z=z) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_spherical(self, values): r, theta, phi = values self._set_point.set_vector(r=r, theta=theta, phi=phi) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_r(self, r): self._set_point.set_component(r=r) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_theta(self, theta): self._set_point.set_component(theta=theta) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_phi(self, phi): self._set_point.set_component(phi=phi) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_cylindrical(self, values): rho, phi, z = values self._set_point.set_vector(rho=rho, phi=phi, z=z) self._set_fields(self._set_point.get_components("x", "y", "z")) def _set_rho(self, rho): self._set_point.set_component(rho=rho) self._set_fields(self._set_point.get_components("x", "y", "z"))