96 lines
3.9 KiB
Python
96 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# *****************************************************************************
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation; either version 2 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Module authors:
|
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
|
# *****************************************************************************
|
|
"""vector field"""
|
|
|
|
import math
|
|
from secop.core import Drivable, Done, BUSY, IDLE, ERROR, Parameter, TupleOf, ArrayOf, FloatRange
|
|
from secop.errors import BadValueError
|
|
from secop_psi.vector import Vector
|
|
from secop.states import HasStates, Retry
|
|
|
|
|
|
DECREASE = 1
|
|
INCREASE = 2
|
|
|
|
|
|
class VectorField(HasStates, Vector, Drivable):
|
|
sphere_radius = Parameter('max. sphere', datatype=FloatRange(0, 0.7, unit='T'), readonly=True, default=0.6)
|
|
cylinders = Parameter('allowed cylinders (list of radius and height)',
|
|
datatype=ArrayOf(TupleOf(FloatRange(0, 0.6, unit='T'), FloatRange(0, 5.2, unit='T')), 1, 9),
|
|
readonly=True, default=((0.23, 5.2), (0.45, 0.8)))
|
|
|
|
def initModule(self):
|
|
super().initModule()
|
|
# override check_limits of the components with a check for restrictions on the vector
|
|
for idx, component in enumerate(self.components):
|
|
|
|
def outer_check(target, vector=self, i=idx, inner_check=component.check_limits):
|
|
inner_check(target)
|
|
if vector:
|
|
value = [c.value - math.copysign(c.tolerance, c.value)
|
|
for c in vector.components]
|
|
value[i] = target
|
|
vector.check_limits(value)
|
|
|
|
component.check_limits = outer_check
|
|
|
|
def merge_status(self):
|
|
return self.status
|
|
|
|
def check_limits(self, value):
|
|
"""check if value is within one of the safe shapes"""
|
|
if sum((v ** 2 for v in value)) <= self.sphere_radius ** 2:
|
|
return
|
|
for r, h in self.cylinders:
|
|
if sum(v ** 2 for v in value[0:2]) <= r ** 2 and abs(value[2]) <= h:
|
|
return
|
|
raise BadValueError('vector %s does not fit in any limiting shape' % repr(value))
|
|
|
|
def write_target(self, value):
|
|
"""initiate target change"""
|
|
# check limits first
|
|
for target, component in zip(value, self.components):
|
|
# check against limits of individual components
|
|
component.check_limits(target, vector=None) # no outer check here!
|
|
self.check_limits(value)
|
|
for target, component in zip(value, self.components):
|
|
if target * component.value < 0:
|
|
# change sign: drive to zero first
|
|
target = 0
|
|
if abs(target) > abs(component.value):
|
|
target = component.value
|
|
component.write_target(target)
|
|
self.start_state(self.ramp_down, target=value)
|
|
return value
|
|
|
|
def ramp_down(self, state):
|
|
for target, component in zip(state.target, self.components):
|
|
if component.isDriving():
|
|
return Retry()
|
|
for target, component in zip(state.target, self.components):
|
|
component.write_target(target)
|
|
return self.final_ramp
|
|
|
|
def final_ramp(self, state):
|
|
for component in self.components:
|
|
if component.isDriving():
|
|
return Retry()
|
|
return self.final_status()
|