Beispiel #1
0
def latticeVectors(lparms, tag="cubic", radians=False, debug=False):
    """
    Generates direct and reciprocal lattice vector components in a
    crystal-relative RHON basis, X. The convention for fixing X to the
    lattice is such that a || x1 and c* || x3, where a and c* are
    direct and reciprocal lattice vectors, respectively.

    USAGE:

    lattice = LatticeVectors(lparms, <symmTag>)

    INPUTS:

    1) lparms (1 x n float list) is the array of lattice parameters,
       where n depends on the symmetry group (see below).

    2) symTag (string) is a case-insensitive string representing the
       symmetry type of the implied Laue group.  The 11 available choices
       are shown below.  The default value is 'cubic'. Note that each
       group expects a lattice parameter array of the indicated length
       and order.

       latticeType      lparms
       -----------      ------------
       'cubic'          a
       'hexagonal'      a, c
       'trigonal'       a, c
       'rhombohedral'   a, alpha (in degrees)
       'tetragonal'     a, c
       'orthorhombic'   a, b, c
       'monoclinic'     a, b, c, beta (in degrees)
       'triclinic'      a, b, c, alpha, beta, gamma (in degrees)

    OUTPUTS:

    1) lattice is a dictionary containing the following keys/items:

       F         (3, 3) double array    transformation matrix taking
                                        componenents in the direct
                                        lattice (i.e. {uvw}) to the
                                        reference, X

       B         (3, 3) double array    transformation matrix taking
                                        componenents in the reciprocal
                                        lattice (i.e. {hkl}) to X

       BR        (3, 3) double array    transformation matrix taking
                                        componenents in the reciprocal
                                        lattice to the Fable reference
                                        frame (see notes)

       U0        (3, 3) double array    transformation matrix
                                        (orthogonal) taking
                                        componenents in the
                                        Fable reference frame to X

       vol       double                 the unit cell volume


       dparms    (6, ) double list      the direct lattice parameters:
                                        [a b c alpha beta gamma]

       rparms    (6, ) double list      the reciprocal lattice
                                        parameters:
                                        [a* b* c* alpha* beta* gamma*]

    NOTES:

    *) The conventions used for assigning a RHON basis,
       X -> {x1, x2, x3}, to each point group are consistent with
       those published in Appendix B of [1]. Namely: a || x1 and
       c* || x3.  This differs from the convention chosen by the Fable
       group, where a* || x1 and c || x3 [2].

    *) The unit cell angles are defined as follows:
       alpha=acos(b'*c/|b||c|), beta=acos(c'*a/|c||a|), and
       gamma=acos(a'*b/|a||b|).

    *) The reciprocal lattice vectors are calculated using the
       crystallographic convention, where the prefactor of 2*pi is
       omitted. In this convention, the reciprocal lattice volume is
       1/V.

    *) Several relations from [3] were employed in the component
       calculations.

    REFERENCES:

    [1] J. F. Nye, ``Physical Properties of Crystals: Their
        Representation by Tensors and Matrices''. Oxford University
        Press, 1985. ISBN 0198511655

    [2] E. M. Lauridsen, S. Schmidt, R. M. Suter, and H. F. Poulsen,
        ``Tracking: a method for structural characterization of grains
        in powders or polycrystals''. J. Appl. Cryst. (2001). 34,
        744--750

    [3] R. J. Neustadt, F. W. Cagle, Jr., and J. Waser, ``Vector
        algebra and the relations between direct and reciprocal
        lattice quantities''. Acta Cryst. (1968), A24, 247--248


    """

    # build index for sorting out lattice parameters
    lattStrings = [
        "cubic",
        "hexagonal",
        "trigonal",
        "rhombohedral",
        "tetragonal",
        "orthorhombic",
        "monoclinic",
        "triclinic",
    ]

    if radians:
        angConv = 1.0
    else:
        angConv = pi / 180.0  # degToRad
    deg90 = pi / 2.0
    deg120 = 2.0 * pi / 3.0
    #
    if tag == lattStrings[0]:  # cubic
        cellparms = num.r_[num.tile(lparms[0], (3,)), deg90 * num.ones((3,))]
    elif tag == lattStrings[1] or tag == lattStrings[2]:  # hexagonal | trigonal (hex indices)
        cellparms = num.r_[lparms[0], lparms[0], lparms[1], deg90, deg90, deg120]
    elif tag == lattStrings[3]:  # rhombohedral
        cellparms = num.r_[num.tile(lparms[0], (3,)), num.tile(angConv * lparms[1], (3,))]
    elif tag == lattStrings[4]:  # tetragonal
        cellparms = num.r_[lparms[0], lparms[0], lparms[1], deg90, deg90, deg90]
    elif tag == lattStrings[5]:  # orthorhombic
        cellparms = num.r_[lparms[0], lparms[1], lparms[2], deg90, deg90, deg90]
    elif tag == lattStrings[6]:  # monoclinic
        cellparms = num.r_[lparms[0], lparms[1], lparms[2], deg90, angConv * lparms[3], deg90]
    elif tag == lattStrings[7]:  # triclinic
        cellparms = num.r_[
            lparms[0], lparms[1], lparms[2], angConv * lparms[3], angConv * lparms[4], angConv * lparms[5]
        ]  # fixed DP 2/24/16
    else:
        raise RuntimeError("lattice tag '%s' is not recognized" % (tag))

    if debug:
        print str(cellparms[0:3]) + " " + str(r2d * cellparms[3:6])
    alfa = cellparms[3]
    beta = cellparms[4]
    gama = cellparms[5]

    cosalfar, sinalfar = cosineXform(alfa, beta, gama)

    a = cellparms[0] * num.r_[1, 0, 0]
    b = cellparms[1] * num.r_[num.cos(gama), num.sin(gama), 0]
    c = cellparms[2] * num.r_[num.cos(beta), -cosalfar * num.sin(beta), sinalfar * num.sin(beta)]

    ad = num.sqrt(sum(a ** 2))
    bd = num.sqrt(sum(b ** 2))
    cd = num.sqrt(sum(c ** 2))

    # Cell volume
    V = num.dot(a, num.cross(b, c))

    # F takes components in the direct lattice to X
    F = num.c_[a, b, c]

    # Reciprocal lattice vectors
    astar = num.cross(b, c) / V
    bstar = num.cross(c, a) / V
    cstar = num.cross(a, b) / V

    # and parameters
    ar = num.sqrt(sum(astar ** 2))
    br = num.sqrt(sum(bstar ** 2))
    cr = num.sqrt(sum(cstar ** 2))

    alfar = num.arccos(num.dot(bstar, cstar) / br / cr)
    betar = num.arccos(num.dot(cstar, astar) / cr / ar)
    gamar = num.arccos(num.dot(astar, bstar) / ar / br)

    # B takes components in the reciprocal lattice to X
    B = num.c_[astar, bstar, cstar]

    cosalfar2, sinalfar2 = cosineXform(alfar, betar, gamar)

    afable = ar * num.r_[1, 0, 0]
    bfable = br * num.r_[num.cos(gamar), num.sin(gamar), 0]
    cfable = cr * num.r_[num.cos(betar), -cosalfar2 * num.sin(betar), sinalfar2 * num.sin(betar)]

    BR = num.c_[afable, bfable, cfable]
    U0 = num.dot(B, num.linalg.inv(BR))
    if outputDegrees:
        dparms = num.r_[ad, bd, cd, r2d * num.r_[alfa, beta, gama]]
        rparms = num.r_[ar, br, cr, r2d * num.r_[alfar, betar, gamar]]
    else:
        dparms = num.r_[ad, bd, cd, num.r_[alfa, beta, gama]]
        rparms = num.r_[ar, br, cr, num.r_[alfar, betar, gamar]]

    L = {"F": F, "B": B, "BR": BR, "U0": U0, "vol": V, "dparms": dparms, "rparms": rparms}

    return L
Beispiel #2
0
def getFriedelPair(tth0, eta0, *ome0, **kwargs):
    """
    Get the diffractometer angular coordinates in degrees for
    the Friedel pair of a given reflection (min angular distance).

    AUTHORS:

    J. V. Bernier -- 10 Nov 2009

    USAGE:

    ome1, eta1 = getFriedelPair(tth0, eta0, *ome0,
                                display=False,
                                units='degrees',
                                convention='hexrd')

    INPUTS:

    1) tth0 is a list (or ndarray) of 1 or n the bragg angles (2theta) for
       the n reflections (tiled to match eta0 if only 1 is given).

    2) eta0 is a list (or ndarray) of 1 or n azimuthal coordinates for the n
       reflections  (tiled to match tth0 if only 1 is given).

    3) ome0 is a list (or ndarray) of 1 or n reference oscillation
       angles for the n reflections (denoted omega in [1]).  This argument
       is optional.

    4) Keyword arguments may be one of the following:

    Keyword             Values|{default}        Action
    --------------      --------------          --------------
    'display'           True|{False}            toggles display info to cmd line
    'units'             'radians'|{'degrees'}   sets units for input angles
    'convention'        'fable'|{'hexrd'}         sets conventions defining
                                                the angles (see below)
    'chiTilt'           None                    the inclination (about Xlab) of
                                                the oscillation axis

    OUTPUTS:

    1) ome1 contains the oscialltion angle coordinates of the
       Friedel pairs associated with the n input reflections, relative to ome0
       (i.e. ome1 = <result> + ome0).  Output is in DEGREES!

    2) eta1 contains the azimuthal coordinates of the Friedel
       pairs associated with the n input reflections.  Output units are
       controlled via the module variable 'outputDegrees'

    NOTES:

    JVB) The ouputs ome1, eta1 are written using the selected convention, but the
         units are alway degrees.  May change this to work with Nathan's global...

    JVB) In the 'fable' convention [1], {XYZ} form a RHON basis where X is
         downstream, Z is vertical, and eta is CCW with +Z defining eta = 0.

    JVB) In the 'hexrd' convention [2], {XYZ} form a RHON basis where Z is upstream,
         Y is vertical, and eta is CCW with +X defining eta = 0.

    REFERENCES:

    [1] E. M. Lauridsen, S. Schmidt, R. M. Suter, and H. F. Poulsen,
        ``Tracking: a method for structural characterization of grains in
        powders or polycrystals''. J. Appl. Cryst. (2001). 34, 744--750

    [2] J. V. Bernier, M. P. Miller, J. -S. Park, and U. Lienert,
        ``Quantitative Stress Analysis of Recrystallized OFHC Cu Subject
        to Deformed In Situ'', J. Eng. Mater. Technol. (2008). 130.
        DOI:10.1115/1.2870234
    """

    dispFlag = False
    fableFlag = False
    chi = None
    c1 = 1.0
    c2 = pi / 180.0
    zTol = 1.0e-7

    # cast to arrays (in case they aren't)
    if num.isscalar(eta0):
        eta0 = [eta0]

    if num.isscalar(tth0):
        tth0 = [tth0]

    if num.isscalar(ome0):
        ome0 = [ome0]

    eta0 = num.asarray(eta0)
    tth0 = num.asarray(tth0)
    ome0 = num.asarray(ome0)

    if eta0.ndim != 1:
        raise RuntimeError, "your azimuthal input was not 1-D, so I do not know what you expect me to do"

    npts = len(eta0)

    if tth0.ndim != 1:
        raise RuntimeError, "your Bragg angle input was not 1-D, so I do not know what you expect me to do"
    else:
        if len(tth0) != npts:
            if len(tth0) == 1:
                tth0 = tth0 * num.ones(npts)
            elif npts == 1:
                npts = len(tth0)
                eta0 = eta0 * num.ones(npts)
            else:
                raise RuntimeError, "the azimuthal and Bragg angle inputs are inconsistent"

    if len(ome0) == 0:
        ome0 = num.zeros(npts)  # dummy ome0
    elif len(ome0) == 1 and npts > 1:
        ome0 = ome0 * num.ones(npts)
    else:
        if len(ome0) != npts:
            raise RuntimeError(
                "your oscialltion angle input is inconsistent; "
                + "it has length %d while it should be %d" % (len(ome0), npts)
            )

    # keyword args processing
    kwarglen = len(kwargs)
    if kwarglen > 0:
        argkeys = kwargs.keys()
        for i in range(kwarglen):
            if argkeys[i] == "display":
                dispFlag = kwargs[argkeys[i]]
            elif argkeys[i] == "convention":
                if kwargs[argkeys[i]].lower() == "fable":
                    fableFlag = True
            elif argkeys[i] == "units":
                if kwargs[argkeys[i]] == "radians":
                    c1 = 180.0 / pi
                    c2 = 1.0
            elif argkeys[i] == "chiTilt":
                if kwargs[argkeys[i]] is not None:
                    chi = kwargs[argkeys[i]]

    # a little talkback...
    if dispFlag:
        if fableFlag:
            print "\nUsing Fable angle convention\n"
        else:
            print "\nUsing image-based angle convention\n"

    # mapped eta input
    #   - in DEGREES, thanks to c1
    eta0 = mapAngle(c1 * eta0, [-180, 180], units="degrees")
    if fableFlag:
        eta0 = 90 - eta0

    # must put args into RADIANS
    #   - eta0 is in DEGREES,
    #   - the others are in whatever was entered, hence c2
    eta0 = d2r * eta0
    tht0 = c2 * tth0 / 2
    if chi is not None:
        chi = c2 * chi
    else:
        chi = 0

    # ---------------------
    # SYSTEM SOLVE
    #
    #
    # cos(chi)cos(eta)cos(theta)sin(x) - cos(chi)sin(theta)cos(x) = sin(theta) - sin(chi)sin(eta)cos(theta)
    #
    #
    # Identity: a sin x + b cos x = sqrt(a**2 + b**2) sin (x + alfa)
    #
    #       /
    #       |      atan(b/a) for a > 0
    # alfa <
    #       | pi + atan(b/a) for a < 0
    #       \
    #
    # => sin (x + alfa) = c / sqrt(a**2 + b**2)
    #
    # must use both branches for sin(x) = n: x = u (+ 2k*pi) | x = pi - u (+ 2k*pi)
    #
    cchi = num.cos(chi)
    schi = num.sin(chi)
    ceta = num.cos(eta0)
    seta = num.sin(eta0)
    ctht = num.cos(tht0)
    stht = num.sin(tht0)

    nchi = num.c_[0.0, cchi, schi].T

    gHat0_l = -num.vstack([ceta * ctht, seta * ctht, stht])

    a = cchi * ceta * ctht
    b = -cchi * stht
    c = stht + schi * seta * ctht

    # form solution
    abMag = num.sqrt(a * a + b * b)
    assert num.all(abMag > 0), "Beam vector specification is infeasible!"
    phaseAng = num.arctan2(b, a)
    rhs = c / abMag
    rhs[abs(rhs) > 1.0] = num.nan
    rhsAng = num.arcsin(rhs)

    # write ome angle output arrays (NaNs persist here)
    ome1 = rhsAng - phaseAng
    ome2 = num.pi - rhsAng - phaseAng

    ome1 = mapAngle(ome1, [-num.pi, num.pi], units="radians")
    ome2 = mapAngle(ome2, [-num.pi, num.pi], units="radians")

    ome_stack = num.vstack([ome1, ome2])

    min_idx = num.argmin(abs(ome_stack), axis=0)

    ome_min = ome_stack[min_idx, range(len(ome1))]
    eta_min = num.nan * num.ones_like(ome_min)

    # mark feasible reflections
    goodOnes = -num.isnan(ome_min)

    numGood = sum(goodOnes)
    tmp_eta = num.empty(numGood)
    tmp_gvec = gHat0_l[:, goodOnes]
    for i in range(numGood):
        come = num.cos(ome_min[goodOnes][i])
        some = num.sin(ome_min[goodOnes][i])
        rchi = rotMatOfExpMap(num.tile(ome_min[goodOnes][i], (3, 1)) * nchi)
        gHat_l = num.dot(rchi, tmp_gvec[:, i].reshape(3, 1))
        tmp_eta[i] = num.arctan2(gHat_l[1], gHat_l[0])
        pass
    eta_min[goodOnes] = tmp_eta

    # everybody back to DEGREES!
    #     - ome1 is in RADIANS here
    #     - convert and put into [-180, 180]
    ome1 = mapAngle(mapAngle(r2d * ome_min, [-180, 180], units="degrees") + c1 * ome0, [-180, 180], units="degrees")

    # put eta1 in [-180, 180]
    eta1 = mapAngle(r2d * eta_min, [-180, 180], units="degrees")

    if not outputDegrees:
        ome1 = d2r * ome1
        eta1 = d2r * eta1

    return ome1, eta1
Beispiel #3
0
def latticeVectors(lparms, tag='cubic', radians=False, debug=False):
    """
    Generates direct and reciprocal lattice vector components in a
    crystal-relative RHON basis, X. The convention for fixing X to the
    lattice is such that a || x1 and c* || x3, where a and c* are
    direct and reciprocal lattice vectors, respectively.

    USAGE:

    lattice = LatticeVectors(lparms, <symmTag>)

    INPUTS:

    1) lparms (1 x n float list) is the array of lattice parameters,
       where n depends on the symmetry group (see below).

    2) symTag (string) is a case-insensitive string representing the
       symmetry type of the implied Laue group.  The 11 available choices
       are shown below.  The default value is 'cubic'. Note that each
       group expects a lattice parameter array of the indicated length
       and order.

       latticeType      lparms
       -----------      ------------
       'cubic'          a
       'hexagonal'      a, c
       'trigonal'       a, c
       'rhombohedral'   a, alpha (in degrees)
       'tetragonal'     a, c
       'orthorhombic'   a, b, c
       'monoclinic'     a, b, c, beta (in degrees)
       'triclinic'      a, b, c, alpha, beta, gamma (in degrees)

    OUTPUTS:

    1) lattice is a dictionary containing the following keys/items:

       F         (3, 3) double array    transformation matrix taking
                                        componenents in the direct
                                        lattice (i.e. {uvw}) to the
                                        reference, X

       B         (3, 3) double array    transformation matrix taking
                                        componenents in the reciprocal
                                        lattice (i.e. {hkl}) to X

       BR        (3, 3) double array    transformation matrix taking
                                        componenents in the reciprocal
                                        lattice to the Fable reference
                                        frame (see notes)

       U0        (3, 3) double array    transformation matrix
                                        (orthogonal) taking
                                        componenents in the
                                        Fable reference frame to X

       vol       double                 the unit cell volume


       dparms    (6, ) double list      the direct lattice parameters:
                                        [a b c alpha beta gamma]

       rparms    (6, ) double list      the reciprocal lattice
                                        parameters:
                                        [a* b* c* alpha* beta* gamma*]

    NOTES:

    *) The conventions used for assigning a RHON basis,
       X -> {x1, x2, x3}, to each point group are consistent with
       those published in Appendix B of [1]. Namely: a || x1 and
       c* || x3.  This differs from the convention chosen by the Fable
       group, where a* || x1 and c || x3 [2].

    *) The unit cell angles are defined as follows:
       alpha=acos(b'*c/|b||c|), beta=acos(c'*a/|c||a|), and
       gamma=acos(a'*b/|a||b|).

    *) The reciprocal lattice vectors are calculated using the
       crystallographic convention, where the prefactor of 2*pi is
       omitted. In this convention, the reciprocal lattice volume is
       1/V.

    *) Several relations from [3] were employed in the component
       calculations.

    REFERENCES:

    [1] J. F. Nye, ``Physical Properties of Crystals: Their
        Representation by Tensors and Matrices''. Oxford University
        Press, 1985. ISBN 0198511655

    [2] E. M. Lauridsen, S. Schmidt, R. M. Suter, and H. F. Poulsen,
        ``Tracking: a method for structural characterization of grains
        in powders or polycrystals''. J. Appl. Cryst. (2001). 34,
        744--750

    [3] R. J. Neustadt, F. W. Cagle, Jr., and J. Waser, ``Vector
        algebra and the relations between direct and reciprocal
        lattice quantities''. Acta Cryst. (1968), A24, 247--248


    """

    # build index for sorting out lattice parameters
    lattStrings = [
        'cubic'       ,
        'hexagonal'   ,
        'trigonal'    ,
        'rhombohedral',
        'tetragonal'  ,
        'orthorhombic',
        'monoclinic'  ,
        'triclinic'
        ]

    if radians:
        angConv = 1.
    else:
        angConv = pi/180. # degToRad
    deg90  = pi/2.
    deg120 = 2.*pi/3.
    #
    if tag == lattStrings[0]:   # cubic
        cellparms = num.r_[num.tile(lparms[0], (3,)), deg90*num.ones((3,))]
    elif tag == lattStrings[1] or tag == lattStrings[2]: # hexagonal | trigonal (hex indices)
        cellparms = num.r_[lparms[0], lparms[0], lparms[1], deg90, deg90, deg120]
    elif tag == lattStrings[3]: # rhombohedral
        cellparms = num.r_[num.tile(lparms[0], (3,)), num.tile(angConv*lparms[1], (3,))]
    elif tag == lattStrings[4]: # tetragonal
        cellparms = num.r_[lparms[0], lparms[0], lparms[1], deg90, deg90, deg90]
    elif tag == lattStrings[5]: # orthorhombic
        cellparms = num.r_[lparms[0], lparms[1], lparms[2], deg90, deg90, deg90]
    elif tag == lattStrings[6]: # monoclinic
        cellparms = num.r_[lparms[0], lparms[1], lparms[2], deg90, angConv*lparms[3], deg90]
    elif tag == lattStrings[7]: # triclinic
        cellparms = lparms
    else:
        raise RuntimeError('lattice tag \'%s\' is not recognized' % (tag))

    if debug:
        print str(cellparms[0:3]) + ' ' + str(r2d*cellparms[3:6])
    alfa = cellparms[3]
    beta = cellparms[4]
    gama = cellparms[5]

    cosalfar, sinalfar = cosineXform(alfa, beta, gama)

    a = cellparms[0]*num.r_[1, 0, 0]
    b = cellparms[1]*num.r_[num.cos(gama), num.sin(gama), 0]
    c = cellparms[2]*num.r_[num.cos(beta), -cosalfar*num.sin(beta), sinalfar*num.sin(beta)]

    ad = num.sqrt(sum(a**2))
    bd = num.sqrt(sum(b**2))
    cd = num.sqrt(sum(c**2))

    # Cell volume
    V = num.dot(a, num.cross(b, c))

    # F takes components in the direct lattice to X
    F = num.c_[a, b, c]

    # Reciprocal lattice vectors
    astar = num.cross(b, c)/V
    bstar = num.cross(c, a)/V
    cstar = num.cross(a, b)/V

    # and parameters
    ar = num.sqrt(sum(astar**2))
    br = num.sqrt(sum(bstar**2))
    cr = num.sqrt(sum(cstar**2))

    alfar = num.arccos(num.dot(bstar, cstar)/br/cr)
    betar = num.arccos(num.dot(cstar, astar)/cr/ar)
    gamar = num.arccos(num.dot(astar, bstar)/ar/br)

    # B takes components in the reciprocal lattice to X
    B = num.c_[astar, bstar, cstar]

    cosalfar2, sinalfar2 = cosineXform(alfar, betar, gamar)

    afable = ar*num.r_[1, 0, 0]
    bfable = br*num.r_[num.cos(gamar), num.sin(gamar), 0]
    cfable = cr*num.r_[num.cos(betar), -cosalfar2*num.sin(betar), sinalfar2*num.sin(betar)]

    BR = num.c_[afable, bfable, cfable]
    U0 = num.dot(B, num.linalg.inv(BR))
    if outputDegrees:
        dparms = num.r_[ad, bd, cd, r2d*num.r_[ alfa,  beta,  gama]]
        rparms = num.r_[ar, br, cr, r2d*num.r_[alfar, betar, gamar]]
    else:
        dparms = num.r_[ad, bd, cd, num.r_[ alfa,  beta,  gama]]
        rparms = num.r_[ar, br, cr, num.r_[alfar, betar, gamar]]

    L = {'F':F,
         'B':B,
         'BR':BR,
         'U0':U0,
         'vol':V,

         'dparms':dparms,
         'rparms':rparms}

    return L
Beispiel #4
0
    def makeScatteringVectors(hkls, rMat_c, bMat, wavelength, chiTilt=None):
        """
        modeled after QFromU.m
        """

        # basis vectors
        bHat_l = num.c_[ 0.,  0., -1.].T
        eHat_l = num.c_[ 1.,  0.,  0.].T

        zTol = 1.0e-7                       # zero tolerance for checking vectors

        gVec_s = []
        oangs0 = []
        oangs1 = []

        # these are the reciprocal lattice vectors in the CRYSTAL FRAME
        # ** NOTE **
        #   if strained, assumes that you handed it a bMat calculated from
        #   strained [a, b, c]
        gVec_c = num.dot( bMat, hkls )
        gHat_c = unitVector(gVec_c)

        dim0, nRefl = gVec_c.shape
        assert dim0 == 3, "Looks like something is wrong with your lattice plane normals son!"

        # extract 1/dspacing and sin of bragg angle
        dSpacingi = columnNorm(gVec_c).flatten()
        sintht    = 0.5 * wavelength * dSpacingi

        # move reciprocal lattice vectors to sample frame
        gHat_s = num.dot(rMat_c.squeeze(), gHat_c)

        if chiTilt is None:
            cchi = 1.
            schi = 0.
            rchi = num.eye(3)
        else:
            cchi = num.cos(chiTilt)
            schi = num.sin(chiTilt)
            rchi = num.array([[   1.,    0.,    0.],
                              [   0.,  cchi, -schi],
                              [   0.,  schi,  cchi]])
            pass

        a =  cchi * gHat_s[0, :]
        b = -cchi * gHat_s[2, :]
        c =  schi * gHat_s[1, :] - sintht

        # form solution
        abMag    = num.sqrt(a*a + b*b); assert num.all(abMag > 0), "Beam vector specification is infealible!"
        phaseAng = num.arctan2(b, a)
        rhs      = c / abMag; rhs[abs(rhs) > 1.] = num.nan
        rhsAng   = num.arcsin(rhs)

        # write ome angle output arrays (NaNs persist here)
        ome0 =          rhsAng - phaseAng
        ome1 = num.pi - rhsAng - phaseAng

        goodOnes_s = -num.isnan(ome0)

        eta0 = num.nan * num.ones_like(ome0)
        eta1 = num.nan * num.ones_like(ome1)

        # mark feasible reflections
        goodOnes   = num.tile(goodOnes_s, (1, 2)).flatten()

        numGood_s  = sum(goodOnes_s)
        numGood    = 2 * numGood_s
        tmp_eta    = num.empty(numGood)
        tmp_gvec   = num.tile(gHat_c, (1, 2))[:, goodOnes]
        allome     = num.hstack([ome0, ome1])

        for i in range(numGood):
            come = num.cos(allome[goodOnes][i])
            some = num.sin(allome[goodOnes][i])
            rome = num.array([[ come,    0.,  some],
                              [   0.,    1.,    0.],
                              [-some,    0.,  come]])
            rMat_s = num.dot(rchi, rome)
    	    gVec_l = num.dot(rMat_s,
                       num.dot(rMat_c, tmp_gvec[:, i].reshape(3, 1)
                       ) )
            tmp_eta[i] = num.arctan2(gVec_l[1], gVec_l[0])
            pass
        eta0[goodOnes_s] = tmp_eta[:numGood_s]
        eta1[goodOnes_s] = tmp_eta[numGood_s:]

        # make assoc tTh array
        tTh  = 2.*num.arcsin(sintht).flatten()
        tTh0 = tTh; tTh0[-goodOnes_s] = num.nan

        gVec_s = num.tile(dSpacingi, (3, 1)) * gHat_s
        oangs0 = num.vstack([tTh0.flatten(), eta0.flatten(), ome0.flatten()])
        oangs1 = num.vstack([tTh0.flatten(), eta1.flatten(), ome1.flatten()])

        return gVec_s, oangs0, oangs1
Beispiel #5
0
def getFriedelPair(tth0, eta0, *ome0, **kwargs):
    """
    Get the diffractometer angular coordinates in degrees for
    the Friedel pair of a given reflection (min angular distance).

    AUTHORS:

    J. V. Bernier -- 10 Nov 2009

    USAGE:

    ome1, eta1 = getFriedelPair(tth0, eta0, *ome0,
                                display=False,
                                units='degrees',
                                convention='hexrd')

    INPUTS:

    1) tth0 is a list (or ndarray) of 1 or n the bragg angles (2theta) for
       the n reflections (tiled to match eta0 if only 1 is given).

    2) eta0 is a list (or ndarray) of 1 or n azimuthal coordinates for the n
       reflections  (tiled to match tth0 if only 1 is given).

    3) ome0 is a list (or ndarray) of 1 or n reference oscillation
       angles for the n reflections (denoted omega in [1]).  This argument
       is optional.

    4) Keyword arguments may be one of the following:

    Keyword             Values|{default}        Action
    --------------      --------------          --------------
    'display'           True|{False}            toggles display info to cmd line
    'units'             'radians'|{'degrees'}   sets units for input angles
    'convention'        'fable'|{'hexrd'}         sets conventions defining
                                                the angles (see below)
    'chiTilt'           None                    the inclination (about Xlab) of
                                                the oscillation axis

    OUTPUTS:

    1) ome1 contains the oscialltion angle coordinates of the
       Friedel pairs associated with the n input reflections, relative to ome0
       (i.e. ome1 = <result> + ome0).  Output is in DEGREES!

    2) eta1 contains the azimuthal coordinates of the Friedel
       pairs associated with the n input reflections.  Output units are
       controlled via the module variable 'outputDegrees'

    NOTES:

    JVB) The ouputs ome1, eta1 are written using the selected convention, but the
         units are alway degrees.  May change this to work with Nathan's global...

    JVB) In the 'fable' convention [1], {XYZ} form a RHON basis where X is
         downstream, Z is vertical, and eta is CCW with +Z defining eta = 0.

    JVB) In the 'hexrd' convention [2], {XYZ} form a RHON basis where Z is upstream,
         Y is vertical, and eta is CCW with +X defining eta = 0.

    REFERENCES:

    [1] E. M. Lauridsen, S. Schmidt, R. M. Suter, and H. F. Poulsen,
        ``Tracking: a method for structural characterization of grains in
        powders or polycrystals''. J. Appl. Cryst. (2001). 34, 744--750

    [2] J. V. Bernier, M. P. Miller, J. -S. Park, and U. Lienert,
        ``Quantitative Stress Analysis of Recrystallized OFHC Cu Subject
        to Deformed In Situ'', J. Eng. Mater. Technol. (2008). 130.
        DOI:10.1115/1.2870234
    """

    dispFlag  = False
    fableFlag = False
    chi       = None
    c1        = 1.
    c2        = pi/180.
    zTol      = 1.e-7

    # cast to arrays (in case they aren't)
    if num.isscalar(eta0):
        eta0 = [eta0]

    if num.isscalar(tth0):
        tth0 = [tth0]

    if num.isscalar(ome0):
        ome0 = [ome0]

    eta0 = num.asarray(eta0)
    tth0 = num.asarray(tth0)
    ome0 = num.asarray(ome0)

    if eta0.ndim != 1:
        raise RuntimeError, 'your azimuthal input was not 1-D, so I do not know what you expect me to do'

    npts = len(eta0)

    if tth0.ndim != 1:
        raise RuntimeError, 'your Bragg angle input was not 1-D, so I do not know what you expect me to do'
    else:
        if len(tth0) != npts:
            if len(tth0) == 1:
                tth0 = tth0*num.ones(npts)
            elif npts == 1:
                npts = len(tth0)
                eta0 = eta0*num.ones(npts)
            else:
                raise RuntimeError, 'the azimuthal and Bragg angle inputs are inconsistent'

    if len(ome0) == 0:
        ome0 = num.zeros(npts)                  # dummy ome0
    elif len(ome0) == 1 and npts > 1:
        ome0 = ome0*num.ones(npts)
    else:
        if len(ome0) != npts:
            raise RuntimeError('your oscialltion angle input is inconsistent; ' \
                               + 'it has length %d while it should be %d' % (len(ome0), npts) )

    # keyword args processing
    kwarglen = len(kwargs)
    if kwarglen > 0:
        argkeys = kwargs.keys()
        for i in range(kwarglen):
            if argkeys[i] == 'display':
                dispFlag = kwargs[argkeys[i]]
            elif argkeys[i] == 'convention':
                if kwargs[argkeys[i]].lower() == 'fable':
                    fableFlag = True
            elif argkeys[i] == 'units':
                if kwargs[argkeys[i]] == 'radians':
                    c1 = 180./pi
                    c2 = 1.
            elif argkeys[i] == 'chiTilt':
                if kwargs[argkeys[i]] is not None:
                    chi = kwargs[argkeys[i]]

    # a little talkback...
    if dispFlag:
        if fableFlag:
            print '\nUsing Fable angle convention\n'
        else:
            print '\nUsing image-based angle convention\n'

    # mapped eta input
    #   - in DEGREES, thanks to c1
    eta0 = mapAngle(c1*eta0, [-180, 180], units='degrees')
    if fableFlag:
        eta0  = 90 - eta0

    # must put args into RADIANS
    #   - eta0 is in DEGREES,
    #   - the others are in whatever was entered, hence c2
    eta0  = d2r*eta0
    tht0  = c2*tth0/2
    if chi is not None:
        chi = c2*chi
    else:
        chi = 0

    # ---------------------
    # SYSTEM SOLVE
    #
    #
    # cos(chi)cos(eta)cos(theta)sin(x) - cos(chi)sin(theta)cos(x) = sin(theta) - sin(chi)sin(eta)cos(theta)
    #
    #
    # Identity: a sin x + b cos x = sqrt(a**2 + b**2) sin (x + alfa)
    #
    #       /
    #       |      atan(b/a) for a > 0
    # alfa <
    #       | pi + atan(b/a) for a < 0
    #       \
    #
    # => sin (x + alfa) = c / sqrt(a**2 + b**2)
    #
    # must use both branches for sin(x) = n: x = u (+ 2k*pi) | x = pi - u (+ 2k*pi)
    #
    cchi = num.cos(chi);     schi = num.sin(chi)
    ceta = num.cos(eta0);    seta = num.sin(eta0)
    ctht = num.cos(tht0);    stht = num.sin(tht0)

    nchi = num.c_[0., cchi, schi].T

    gHat0_l = num.vstack([ceta * ctht,
                          seta * ctht,
                          stht])

    a = cchi*ceta*ctht
    b = cchi*schi*seta*ctht + schi*schi*stht - stht
    c = stht + cchi*schi*seta*ctht + schi*schi*stht

    # form solution
    abMag    = num.sqrt(a*a + b*b); assert num.all(abMag > 0), "Beam vector specification is infealible!"
    phaseAng = num.arctan2(b, a)
    rhs      = c / abMag; rhs[abs(rhs) > 1.] = num.nan
    rhsAng   = num.arcsin(rhs)

    # write ome angle output arrays (NaNs persist here)
    ome1 =          rhsAng - phaseAng
    ome2 = num.pi - rhsAng - phaseAng

    ome1 = mapAngle(ome1, [-num.pi, num.pi], units='radians')
    ome2 = mapAngle(ome2, [-num.pi, num.pi], units='radians')

    ome_stack = num.vstack([ome1, ome2])

    min_idx = num.argmin(abs(ome_stack), axis=0)

    ome_min = ome_stack[min_idx, range(len(ome1))]
    eta_min = num.nan * num.ones_like(ome_min)

    # mark feasible reflections
    goodOnes = -num.isnan(ome_min)

    numGood  = sum(goodOnes)
    tmp_eta  = num.empty(numGood)
    tmp_gvec = gHat0_l[:, goodOnes]
    for i in range(numGood):
        come = num.cos(ome_min[goodOnes][i])
        some = num.sin(ome_min[goodOnes][i])
        rchi = rotMatOfExpMap( num.tile(ome_min[goodOnes][i], (3, 1)) * nchi )
        gHat_l = num.dot(rchi, tmp_gvec[:, i].reshape(3, 1))
        tmp_eta[i] = num.arctan2(gHat_l[1], gHat_l[0])
        pass
    eta_min[goodOnes] = tmp_eta

    # everybody back to DEGREES!
    #     - ome1 is in RADIANS here
    #     - convert and put into [-180, 180]
    ome1 = mapAngle( mapAngle(r2d*ome_min, [-180, 180], units='degrees') + c1*ome0, [-180, 180], units='degrees')

    # put eta1 in [-180, 180]
    eta1 = mapAngle(r2d*eta_min, [-180, 180], units='degrees')

    if not outputDegrees:
        ome1 = d2r * ome1
        eta1 = d2r * eta1

    return ome1, eta1