NUNAME = 'gam' class HklCalculatorBase(object):

    def __init__(self, ubcalc,
                 raiseExceptionsIfAnglesDoNotMapBackToHkl=False):

        self._ubcalc = ubcalc  # to get the UBMatrix, tau and sigma
        self.raiseExceptionsIfAnglesDoNotMapBackToHkl = \
            raiseExceptionsIfAnglesDoNotMapBackToHkl

    def anglesToHkl(self, pos, wavelength):
        """
        Return hkl tuple and dictionary of all virtual angles in degrees from
        Position in degrees and wavelength in Angstroms.
        """

        h, k, l = self._anglesToHkl(pos.inRadians(), wavelength)
        paramDict = self.anglesToVirtualAngles(pos, wavelength)
        return ((h, k, l), paramDict)

    def anglesToVirtualAngles(self, pos, wavelength):
        """
        Return dictionary of all virtual angles in degrees from Position object
        in degrees and wavelength in Angstroms.
        """
        anglesDict = self._anglesToVirtualAngles(pos.inRadians(), wavelength)
        for name in anglesDict:
            anglesDict[name] = anglesDict[name] * TODEG
        return anglesDict

    def hklToAngles(self, h, k, l, wavelength):
        """
        Return verified Position and all virtual angles in degrees from
        h, k & l and wavelength in Angstroms.

        The calculated Position is verified by checking that it maps back using
        anglesToHkl() to the requested hkl value.

        Those virtual angles fixed or generated while calculating the position
        are verified by by checking that they map back using
        anglesToVirtualAngles to the virtual angles for the given position.

        Throws a DiffcalcException if either check fails and
        raiseExceptionsIfAnglesDoNotMapBackToHkl is True, otherwise displays a
        warning.
        """

        # Update tracked parameters. During this calculation parameter values
        # will be read directly from self._parameters instead of via
        # self.getParameter which would trigger another potentially time-costly
        # position update.
        self.parameter_manager.update_tracked()

        pos, virtualAngles = self._hklToAngles(h, k, l, wavelength)  # in rad

        # to degrees:
        pos.changeToDegrees()
        
        for key, val in virtualAngles.items():
            if val is not None:
                virtualAngles[key] = val * TODEG

        self._verify_pos_map_to_hkl(h, k, l, wavelength, pos)

        virtualAnglesReadback = self._verify_virtual_angles(h, k, l, wavelength, pos, virtualAngles)

        return pos, virtualAnglesReadback

    def _verify_pos_map_to_hkl(self, h, k, l, wavelength, pos):
        hkl, _ = self.anglesToHkl(pos, wavelength)
        e = 0.001
        if ((abs(hkl[0] - h) > e) or (abs(hkl[1] - k) > e) or 
            (abs(hkl[2] - l) > e)):
            s = "ERROR: The angles calculated for hkl=(%f,%f,%f) were %s.\n" % (h, k, l, str(pos))
            s += "Converting these angles back to hkl resulted in hkl="\
            "(%f,%f,%f)" % (hkl[0], hkl[1], hkl[2])
            if self.raiseExceptionsIfAnglesDoNotMapBackToHkl:
                raise DiffcalcException(s)
            else:
                print s

    def _verify_virtual_angles(self, h, k, l, wavelength, pos, virtualAngles):
        # Check that the virtual angles calculated/fixed during the hklToAngles
    # those read back from pos using anglesToVirtualAngles
        virtualAnglesReadback = self.anglesToVirtualAngles(pos, wavelength)
        for key, val in virtualAngles.items():
            if val != None: # Some values calculated in some mode_selector
                r = virtualAnglesReadback[key]
                if ((differ(val, r, .00001) and differ(val, r + 360, .00001) and differ(val, r - 360, .00001))):
                    s = "ERROR: The angles calculated for hkl=(%f,%f,%f) with"\
                    " mode=%s were %s.\n" % (h, k, l, self.repr_mode(), str(pos))
                    s += "During verification the virtual angle %s resulting "\
                    "from (or set for) this calculation of %f" % (key, val)
                    s += "did not match that calculated by "\
                    "anglesToVirtualAngles of %f" % virtualAnglesReadback[key]
                    if self.raiseExceptionsIfAnglesDoNotMapBackToHkl:
                        raise DiffcalcException(s)
                    else:
                        print s
        
        return virtualAnglesReadback

    def repr_mode(self):
        pass

### Collect all math access to context here

    def _getUBMatrix(self):
        return self._ubcalc.UB

    def _getMode(self):
        return self.mode_selector.getMode()

    def _getSigma(self):
        return self._ubcalc.sigma

    def _getTau(self):
        return self._ubcalc.tau

    def _getParameter(self, name):
        # Does not use context.getParameter as this will trigger a costly
        # parameter collection
        pm = self.parameter_manager
        return pm.getParameterWithoutUpdatingTrackedParemeters(name)

    def _getGammaParameterName(self):
        return self._gammaParameterName class TransformCInRadians(Transform):
    '''
    Flip omega, invert chi and flip phi. Using radians and keeping
    -pi<omega<=pi and 0<=phi<=360
    '''
    def transform(self, pos):
        pos = pos.clone()
        if pos.omega > 0:
            pos.omega -= pi
        else:
            pos.omega += pi
        pos.chi *= -1
        pos.phi += pi
        return pos def dot3(x, y):
    """z = dot3(x ,y) -- where x, y are 3*1 Jama matrices"""
    return x[0, 0] * y[0, 0] + x[1, 0] * y[1, 0] + x[2, 0] * y[2, 0] def cross3(x, y):
    """z = cross3(x ,y) -- where x, y & z are 3*1 Jama matrices"""
    [[x1], [x2], [x3]] = x.tolist()
    [[y1], [y2], [y3]] = y.tolist()
    return matrix([[x2 * y3 - x3 * y2],
                   [x3 * y1 - x1 * y3],
                   [x1 * y2 - x2 * y1]]) def bound(x):
    """
    moves x between -1 and 1. Used to correct for rounding errors which may
    have moved the sin or cosine of a value outside this range.
    """
    if abs(x) > (1 + SMALL):
        raise AssertionError(
            "The value (%f) was unexpectedly too far outside -1 or 1 to "
            "safely bound. Please report this." % x)
    if x > 1:
        return 1
    if x < -1:
        return -1
    return x def differ(first, second, tolerance):
    """Returns error message if the norm of the difference between two arrays
    or numbers is greater than the given tolerance. Else returns False.
    """
    # TODO: Fix spaghetti
    nonArray = False
    if type(first) in (int, float):
        if type(second) not in (int, float):
            raise TypeError(
                "If first is an int or float, so must second. "
                "first=%s, second=%s" & (repr(first), repr(second)))
        first = [first]
        second = [second]
        nonArray = True
    if not isinstance(first, matrix):
        first = matrix([list(first)])
    if not isinstance(second, matrix):
        second = matrix([list(second)])
    diff = first - second
    if norm(diff) >= tolerance:
        if nonArray:
            return ('%s!=%s' %
                    (repr(first.tolist()[0][0]), repr(second.tolist()[0][0])))
        return ('%s!=%s' %
                (repr(tuple(first.tolist()[0])),
                repr(tuple(second.tolist()[0]))))
    return False def createVliegMatrices(alpha=None, delta=None, gamma=None, omega=None,
                        chi=None, phi=None):

    ALPHA = None if alpha is None else calcALPHA(alpha)
    DELTA = None if delta is None else calcDELTA(delta)
    GAMMA = None if gamma is None else calcGAMMA(gamma)
    OMEGA = None if omega is None else calcOMEGA(omega)
    CHI = None if chi is None else calcCHI(chi)
    PHI = None if phi is None else calcPHI(phi)
    return ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI def createVliegsPsiTransformationMatrix(psi):
    """PSI = createPsiTransformationMatrices(psi)
    angles in radians
    """
    return matrix([[1, 0, 0],
                   [0, cos(psi), sin(psi)],
                   [0, -sin(psi), cos(psi)]]) def createVliegsSurfaceTransformationMatrices(sigma, tau):
    """[SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(sigma, tau)
    angles in radians
    """
    SIGMA = matrix([[cos(sigma), 0, sin(sigma)],
                    [0, 1, 0], \
                    [-sin(sigma), 0, cos(sigma)]])

    TAU = matrix([[cos(tau), sin(tau), 0],
                  [-sin(tau), cos(tau), 0],
                  [0, 0, 1]])
    return(SIGMA, TAU) def calcPHI(phi):
    return z_rotation(-phi) class VliegPosition(AbstractPosition):
    """The position of all six diffractometer axis"""
    def __init__(self, alpha=None, delta=None, gamma=None, omega=None,
                 chi=None, phi=None):
        self.alpha = alpha
        self.delta = delta
        self.gamma = gamma
        self.omega = omega
        self.chi = chi
        self.phi = phi

    def clone(self):
        return VliegPosition(self.alpha, self.delta, self.gamma, self.omega,
                             self.chi, self.phi)

    def changeToRadians(self):
        self.alpha *= TORAD
        self.delta *= TORAD
        self.gamma *= TORAD
        self.omega *= TORAD
        self.chi *= TORAD
        self.phi *= TORAD

    def changeToDegrees(self):
        self.alpha *= TODEG
        self.delta *= TODEG
        self.gamma *= TODEG
        self.omega *= TODEG
        self.chi *= TODEG
        self.phi *= TODEG

    def inRadians(self):
        pos = self.clone()
        pos.changeToRadians()
        return pos

    def inDegrees(self):
        pos = self.clone()
        pos.changeToDegrees()
        return pos

    def nearlyEquals(self, pos2, maxnorm):
        for a, b in zip(self.totuple(), pos2.totuple()):
            if abs(a - b) > maxnorm:
                return False
        return True

    def totuple(self):
        return (self.alpha, self.delta, self.gamma, self.omega,
                self.chi, self.phi)

    def __str__(self):
        return ("VliegPosition(alpha %r delta: %r gamma: %r omega: %r chi: %r"
                "  phi: %r)" % self.totuple())

    def __repr__(self):
        return self.__str__()

    def __eq__(self, b):
        return self.nearlyEquals(b, .001) class VliegParameterManager(object):

    def __init__(self, geometry, hardware, modeSelector,
                 gammaParameterName='gamma'):
        self._geometry = geometry
        self._hardware = hardware
        self._modeSelector = modeSelector
        self._gammaParameterName = gammaParameterName
        self._parameters = {}
        self._defineParameters()

    def _defineParameters(self):
        # Set default fixed values (In degrees if angles)
        self._parameters = {}
        self._parameters['alpha'] = 0
        self._parameters[self._gammaParameterName] = 0
        self._parameters['blw'] = None  # Busing and Levi omega!
        self._parameters['betain'] = None
        self._parameters['betaout'] = None
        self._parameters['azimuth'] = None
        self._parameters['phi'] = None

        self._parameterDisplayOrder = (
            'alpha', self._gammaParameterName, 'betain', 'betaout', 'azimuth',
            'phi', 'blw')
        self._trackableParameters = ('alpha', self._gammaParameterName, 'phi')
        self._trackedParameters = []

        # Overide parameters that are unchangable for this diffractometer
        for (name, value) in self._geometry.fixed_parameters.items():
            if name not in self._parameters:
                raise RuntimeError(
                    "The %s diffractometer geometry specifies a fixed "
                    "parameter %s that is not used by the diffractometer "
                    "calculator" % (self._geometry.getName, name))
            self._parameters[name] = value

    def reportAllParameters(self):
        self.update_tracked()
        result = ''
        for name in self._parameterDisplayOrder:
            flags = ""
            if not self._modeSelector.getMode().usesParameter(name):
                flags += '(not relevant in this mode)'
            if self._geometry.parameter_fixed(name):
                flags += ' (fixed by this diffractometer)'
            if self.isParameterTracked(name):
                flags += ' (tracking hardware)'
            value = self._parameters[name]
            if value is None:
                value = '---'
            else:
                value = float(value)
            result += '%s: %s %s\n' % (name.rjust(8), value, flags)
        return result

    def reportParametersUsedInCurrentMode(self):
        self.update_tracked()
        result = ''
        for name in self.getUserChangableParametersForMode(
            self._modeSelector.getMode()):
            flags = ""
            value = self._parameters[name]
            if value is None:
                value = '---'
            else:
                value = float(value)
            if self.isParameterTracked(name):
                flags += ' (tracking hardware)'
            result += '%s: %s %s\n' % (name.rjust(8), value, flags)
        return result

    def getUserChangableParametersForMode(self, mode=None):
        """
        (p1,p2...p3) = getUserChangableParametersForMode(mode) returns a list
        of parameters names used in this mode for this diffractometer geometry.
        Checks current mode if no mode specified.
        """
        if mode is None:
            mode = self._mode
        result = []
        for name in self._parameterDisplayOrder:
            if self._isParameterChangeable(name, mode):
                result += [name]
        return result

### Fixed parameters stuff ###

    def set_constraint(self, name, value):
        if not name in self._parameters:
            raise DiffcalcException("No fixed parameter %s is used by the "
                                    "diffraction calculator" % name)
        if self._geometry.parameter_fixed(name):
            raise DiffcalcException(
                "The parameter %s cannot be changed: It has been fixed by the "
                "%s diffractometer geometry"
                % (name, self._geometry.name))
        if self.isParameterTracked(name):
            # for safety and to avoid confusion:
            raise DiffcalcException(
                "Cannot change parameter %s as it is set to track an axis.\n"
                "To turn this off use a command like 'trackalpha 0'." % name)

        if not self.isParameterUsedInSelectedMode(name):
            print ("WARNING: The parameter %s is not used in mode %i" %
                   (name, self._modeSelector.getMode().index))
        self._parameters[name] = value

    def isParameterUsedInSelectedMode(self, name):
        return self._modeSelector.getMode().usesParameter(name)

    def getParameterWithoutUpdatingTrackedParemeters(self, name):
        try:
            return self._parameters[name]
        except KeyError:
            raise DiffcalcException("No fixed parameter %s is used by the "
                                    "diffraction calculator" % name)

    def get_constraint(self, name):
        self.update_tracked()
        return self.getParameterWithoutUpdatingTrackedParemeters(name)

    def getParameterDict(self):
        self.update_tracked()
        return copy(self._parameters)

    @property
    def settable_constraint_names(self):
        """list of all available constraints that have settable values"""
        return sorted(self.getParameterDict().keys())

    def setTrackParameter(self, name, switch):
        if not name in self._parameters.keys():
            raise DiffcalcException("No fixed parameter %s is used by the "
                                    "diffraction calculator" % name)
        if not name in self._trackableParameters:
            raise DiffcalcException("Parameter %s is not trackable" % name)
        if not self._isParameterChangeable(name):
            print ("WARNING: Parameter %s is not used in mode %i" %
                   (name, self._mode.index))
        if switch:
            if name not in self._trackedParameters:
                self._trackedParameters.append(name)
        else:
            if name in self._trackedParameters:
                self._trackedParameters.remove(name)

    def isParameterTracked(self, name):
        return (name in self._trackedParameters)

    def update_tracked(self):
        """Note that the name of a tracked parameter MUST map into the name of
        an external diffractometer angle
        """
        if self._trackedParameters:
            externalAnglePositionArray = self._hardware.get_position()
            externalAngleNames = list(self._hardware.get_axes_names())
            for name in self._trackedParameters:
                self._parameters[name] = \
                    externalAnglePositionArray[externalAngleNames.index(name)]

    def _isParameterChangeable(self, name, mode=None):
        """
        Returns true if parameter is used in a mode (current mode if none
        specified), AND if it is not locked by the diffractometer geometry
        """
        if mode is None:
            mode = self._modeSelector.getMode()
        return (mode.usesParameter(name) and
                not self._geometry.parameter_fixed(name)) class ModeSelector(object):

    def __init__(self, geometry, parameterManager=None,
                 gammaParameterName='gamma'):
        self.parameter_manager = parameterManager
        self._geometry = geometry
        self._gammaParameterName = gammaParameterName
        self._modelist = {}  # indexed by non-contiguous mode number
        self._configureAvailableModes()
        self._selectedIndex = 1

    def setParameterManager(self, manager):
        """
        Required as a ParameterManager and ModelSelector are mutually tied
        together in practice
        """
        self.parameter_manager = manager

    def _configureAvailableModes(self):

        gammaName = self._gammaParameterName

        ml = self._modelist

        ml[0] = Mode(0, '4cFixedw', 'fourc', 'fourc fixed-bandlw',
                     ['alpha', gammaName, 'blw'], False)

        ml[1] = Mode(1, '4cBeq', 'fourc', 'fourc bisecting',
                     ['alpha', gammaName])

        ml[2] = Mode(2, '4cBin', 'fourc', 'fourc incoming',
                     ['alpha', gammaName, 'betain'])

        ml[3] = Mode(3, '4cBout', 'fourc', 'fourc outgoing',
                     ['alpha', gammaName, 'betaout'])

        ml[4] = Mode(4, '4cAzimuth', 'fourc', 'fourc azimuth',
                     ['alpha', gammaName, 'azimuth'], False)

        ml[5] = Mode(5, '4cPhi', 'fourc', 'fourc fixed-phi',
                     ['alpha', gammaName, 'phi'])

        ml[10] = Mode(10, '5cgBeq', 'fivecFixedGamma', 'fivec bisecting',
                      [gammaName])

        ml[11] = Mode(11, '5cgBin', 'fivecFixedGamma', 'fivec incoming',
                      [gammaName, 'betain'])

        ml[12] = Mode(12, '5cgBout', 'fivecFixedGamma', 'fivec outgoing',
                      [gammaName, 'betaout'])

        ml[13] = Mode(13, '5caBeq', 'fivecFixedAlpha', 'fivec bisecting',
                      ['alpha'])

        ml[14] = Mode(14, '5caBin', 'fivecFixedAlpha', 'fivec incoming',
                      ['alpha', 'betain'])

        ml[15] = Mode(15, '5caBout', 'fivecFixedAlpha', 'fivec outgoing',
                      ['alpha', 'betaout'])

        ml[20] = Mode(20, '6czBeq', 'zaxis', 'zaxis bisecting',
                      [])

        ml[21] = Mode(21, '6czBin', 'zaxis', 'zaxis incoming',
                                  ['betain'])

        ml[22] = Mode(22, '6czBout', 'zaxis', 'zaxiz outgoing',
                                  ['betaout'])

    def setModeByIndex(self, index):
        if index in self._modelist:
            self._selectedIndex = index
        else:
            raise DiffcalcException("mode %r is not defined" % index)

    def setModeByName(self, name):
        def findModeWithName(name):
            for index, mode in self._modelist.items():
                if mode.name == name:
                    return index, mode
            raise ValueError

        try:
            index, mode = findModeWithName(name)
        except ValueError:
            raise DiffcalcException(
                'Unknown mode. The diffraction calculator supports these '
                'modeSelector: %s' % self._supportedModes.keys())
        if  self._geometry.supports_mode_group(mode.group):
            self._selectedIndex = index
        else:
            raise DiffcalcException(
                "Mode %s not supported for this diffractometer (%s)." %
                (name, self._geometry.name))

    def getMode(self):
        return self._modelist[self._selectedIndex]

    def reportCurrentMode(self):
        return self.getMode().__str__()

    def reportAvailableModes(self):
        result = ''
        indecis = self._modelist.keys()
        indecis.sort()
        for index in indecis:
            mode = self._modelist[index]
            if self._geometry.supports_mode_group(mode.group):
                paramString = ''
                flags = ''
                pm = self.parameter_manager
                for paramName in pm.getUserChangableParametersForMode(mode):
                    paramString += paramName + ", "
                if paramString:
                    paramString = paramString[:-2]  # remove trailing commas
                if not mode.implemented:
                    flags += "(Not impl.)"
                result += ('%2i) %-15s (%s) %s\n' % (mode.index,
                           mode.description, paramString, flags))
        return result class PaperSpecificUbCalcStrategy(object):
    """Base class defining interface for scattering vector calculation
    for the given diffractometer position.
    """

    def calculate_q_phi(self, pos):
        """Calculate scattering vector in laboratory frame.
        
        Calculate hkl in the phi frame in units of 2 * pi / lambda.
        
        Parameters
        ----------
        pos: object
            Diffractometer angles in radians.
        
        Returns
        -------
        matrix:
            Scattering vector coordinates corresponding to the input position."""
        raise NotImplementedError()
from math import pi, asin, acos, sin, cos, sqrt, atan2, fabs, atan
from diffcalc import settings
    from numpy import matrix
    from numpy.linalg import norm
    from numjy import matrix
    from numjy.linalg import norm
from diffcalc.hkl.calcbase import HklCalculatorBase
from diffcalc.hkl.vlieg.transform import TransformCInRadians
from diffcalc.util import dot3, cross3, bound, differ
from diffcalc.hkl.vlieg.geometry import createVliegMatrices, \
    createVliegsPsiTransformationMatrix, \
    createVliegsSurfaceTransformationMatrices, calcPHI
from diffcalc.hkl.vlieg.geometry import VliegPosition
from diffcalc.hkl.vlieg.constraints import VliegParameterManager
from diffcalc.hkl.vlieg.constraints import ModeSelector
from diffcalc.ub.calc import PaperSpecificUbCalcStrategy
            pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi)
        [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
            self._getSigma() * TORAD, self._getTau() * TORAD)

        S = TAU * SIGMA
        y_vector = matrix([[0], [1], [0]])

        # Calculate Bin from equation 15:
        surfacenormal_alpha = OMEGA * CHI * PHI * S * matrix([[0], [0], [1]])
        incoming_alpha = ALPHA.I * y_vector
        minusSinBetaIn = dot3(surfacenormal_alpha, incoming_alpha)
        Bin = asin(bound(-minusSinBetaIn))

        # Calculate Bout from equation 16:
        #  surfacenormal_alpha has just ben calculated
        outgoing_alpha = DELTA * GAMMA * y_vector
        sinBetaOut = dot3(surfacenormal_alpha, outgoing_alpha)
        Bout = asin(bound(sinBetaOut))

        # Calculate 2theta from equation 25:

        cosTwoTheta = dot3(ALPHA * DELTA * GAMMA * y_vector, y_vector)
        twotheta = acos(bound(cosTwoTheta))
        psi = self._anglesToPsi(pos, wavelength)

        return {'Bin': Bin, 'Bout': Bout, 'azimuth': psi, '2theta': twotheta}

    def _hklToAngles(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstroms. The virtual angles are those fixed or
        generated while calculating the position: Bin, Bout and 2theta; and
        azimuth in four and five circle modes.
        """

        if self._getMode().group in ("fourc", "fivecFixedGamma",
                                     "fivecFixedAlpha"):
            return self._hklToAnglesFourAndFiveCirclesModes(h, k, l,
                                                            wavelength)
        elif self._getMode().group == "zaxis":
            return self._hklToAnglesZaxisModes(h, k, l, wavelength)
        else:
            raise RuntimeError(
                'The current mode (%s) has an unrecognised group: %s.'
                 % (self._getMode().name, self._getMode().group))

    def _hklToAnglesFourAndFiveCirclesModes(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstrom for four and five circle modes. The virtual
        angles are those fixed or generated while calculating the position:
        Bin, Bout, 2theta and azimuth.
        """

        # Results in radians during calculations, returned in degreess
        pos = VliegPosition(None, None, None, None, None, None)

        # Normalise hkl
        wavevector = 2 * pi / wavelength
        hklNorm = matrix([[h], [k], [l]]) / wavevector

        # Compute hkl in phi axis coordinate frame
        hklPhiNorm = self._getUBMatrix() * hklNorm

        # Determine Bin and Bout
        if self._getMode().name == '4cPhi':
            Bin = Bout = None
        else:
            Bin, Bout = self._determineBinAndBoutInFourAndFiveCirclesModes(
                                                                    hklNorm)

        # Determine alpha and gamma
        if self._getMode().group == 'fourc':
            pos.alpha, pos.gamma = \
                self._determineAlphaAndGammaForFourCircleModes(hklPhiNorm)
        else:
            pos.alpha, pos.gamma = \
                self._determineAlphaAndGammaForFiveCircleModes(Bin, hklPhiNorm)
        if pos.alpha < -pi:
            pos.alpha += 2 * pi
        if pos.alpha > pi:
            pos.alpha -= 2 * pi

        # Determine delta
        (pos.delta, twotheta) = self._determineDelta(hklPhiNorm, pos.alpha,
                                                     pos.gamma)

        # Determine omega, chi & phi
        pos.omega, pos.chi, pos.phi, psi = \
            self._determineSampleAnglesInFourAndFiveCircleModes(
                hklPhiNorm, pos.alpha, pos.delta, pos.gamma, Bin)
        # (psi will be None in fixed phi mode)

        # Ensure that by default omega is between -90 and 90, by possibly
        # transforming the sample angles
        if self._getMode().name != '4cPhi':  # not in fixed-phi mode
            if pos.omega < -pi / 2 or pos.omega > pi / 2:
                pos = transformC.transform(pos)

        # Gather up the virtual angles calculated along the way...
        #   -pi<psi<=pi
        if psi is not None:
            if psi > pi:
                psi -= 2 * pi
            if psi < (-1 * pi):
                psi += 2 * pi

        v = {'2theta': twotheta, 'Bin': Bin, 'Bout': Bout, 'azimuth': psi}
        return pos, v

    def _hklToAnglesZaxisModes(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstroms for z-axis modes. The virtual angles are those
        fixed or generated while calculating the position: Bin, Bout, and
        2theta.
        """
        # Section 6:

        # Results in radians during calculations, returned in degreess

        pos = VliegPosition(None, None, None, None, None, None)