示例#1
0
def sphericalCoordinates(x, y, z):
    """Returns the r, theta and phi spherical coordinates corresponding to x, y z cartesian coordinates.

    @param x: the cartesian x.
    @type x: float   

    @param y: the cartesian y.
    @type y: float   

    @param z: the cartesian z.
    @type z: float   

    @return: the r, theta and phi spherical coordinates..
    @rtype: a list of three floats
    """

    # The spherical radius is computed
    r = N.sqrt(x**2 + y**2 + z**2)

    # The spherical theta is computed
    theta = N.arccos(z / r)

    # The spherical phi is computed
    phi = N.arctan2(y, x)

    return r, theta, phi
示例#2
0
    def __init__(self, *parameters):
        """
        :param parameters: one of

            1) three lattice vectors or

            2) six numbers: the lengths of the three lattice
               vectors (a, b, c) followed by the three
               angles (alpha, beta, gamma).

        """
        if len(parameters) == 6:
            self.a, self.b, self.c, self.alpha, self.beta, self.gamma = \
                    parameters
            e1 = Vector(self.a, 0, 0)
            e2 = self.b * Vector(N.cos(self.gamma), N.sin(self.gamma), 0.)
            e3_x = N.cos(self.beta)
            e3_y = (N.cos(self.alpha)-N.cos(self.beta)*N.cos(self.gamma)) \
                   / N.sin(self.gamma)
            e3_z = N.sqrt(1. - e3_x**2 - e3_y**2)
            e3 = self.c * Vector(e3_x, e3_y, e3_z)
            self.basis = (e1, e2, e3)
        elif len(parameters) == 3:
            assert isVector(parameters[0])
            assert isVector(parameters[1])
            assert isVector(parameters[2])
            self.basis = list(parameters)
            e1, e2, e3 = self.basis
            self.a = e1.length()
            self.b = e2.length()
            self.c = e3.length()
            self.alpha = N.arccos(e2 * e3 / (self.b * self.c))
            self.beta = N.arccos(e1 * e3 / (self.a * self.c))
            self.gamma = N.arccos(e1 * e2 / (self.a * self.b))
        else:
            raise ValueError("Parameter list incorrect")

        r = LA.inverse(N.transpose([e1, e2, e3]))
        self.reciprocal_basis = [Vector(r[0]), Vector(r[1]), Vector(r[2])]
示例#3
0
 def shouldPass(self, Vector, isVector):
     # Create vector objects
     v1 = Vector(1., -2., 3.)
     v2 = Vector([-2., 1., 0.])
     # Check that vectors are not copied
     v1_copy = copy.copy(v1)
     self.assertTrue(v1 is v1_copy)
     v1_copy = copy.deepcopy(v1)
     self.assertTrue(v1 is v1_copy)
     # check len and element access
     self.assertEqual(len(v1), 3)
     self.assertEqual(v1[0], 1.)
     self.assertEqual(v1[1], -2.)
     self.assertEqual(v1[2], 3.)
     self.assertEqual(v1[-3], 1.)
     self.assertEqual(v1[-2], -2.)
     self.assertEqual(v1[-1], 3.)
     self.assertEqual(v1.x(), 1.)
     self.assertEqual(v1.y(), -2.)
     self.assertEqual(v1.z(), 3.)
     # Check arithmetic
     self.assertEqual(v1 + v2, Vector(-1., -1., 3.))
     self.assertEqual(v1 - v2, Vector(3., -3., 3.))
     self.assertEqual(-v1, Vector(-1., 2., -3.))
     self.assertEqual(v1 * v2, -4.)
     self.assertEqual(2. * v1, Vector(2., -4., 6.))
     self.assertEqual(v1 / 0.5, Vector(2., -4., 6.))
     # Check comparisons
     self.assertTrue(v1 == v1)
     self.assertFalse(v1 == v2)
     self.assertFalse(v1 == None)
     # Check methods
     self.assertAlmostEqual(v1.length(), N.sqrt(14.), 12)
     self.assertAlmostEqual(v1.normal()[0], v1[0] / N.sqrt(14.), 12)
     self.assertAlmostEqual(v1.normal()[1], v1[1] / N.sqrt(14.), 12)
     self.assertAlmostEqual(v1.normal()[2], v1[2] / N.sqrt(14.), 12)
     self.assertAlmostEqual(v1.cross(v1).length(), 0., 12)
     self.assertEqual(v1.cross(v2), Vector(-3., -6., -3.))
     self.assertAlmostEqual(v1.angle(v1), 0., 12)
     self.assertAlmostEqual(v1.angle(v2),
                            N.arccos(v1.normal() * v2.normal()), 12)
     dp = v1.dyadicProduct(v2)
     for i in range(3):
         for j in range(3):
             self.assertEqual(dp[i, j], v1[i] * v2[j])
     self.assertTrue(N.logical_and.reduce(v1.asTensor().array == v1.array))
     # Check isVector
     self.assertTrue(isVector(v1))
     self.assertTrue(isVector(v2))
     self.assertFalse(isVector(0.))
     self.assertFalse(isVector("string"))
示例#4
0
def _intersectCirclePlane(circle, plane):
    if abs(abs(circle.normal*plane.normal)-1.) < eps:
	if plane.hasPoint(circle.center):
	    return circle
	else:
	    return None
    else:
	line = plane.intersectWith(Plane(circle.center, circle.normal))
	x = line.distanceFrom(circle.center)
	if x > circle.radius:
	    return None
	else:
	    angle = N.arccos(x/circle.radius)
	    along_line = N.sin(angle)*circle.radius
	    normal = circle.normal.cross(line.direction)
	    if line.distanceFrom(circle.center+normal) > x:
		normal = -normal
	    return (circle.center+x*normal-along_line*line.direction,
		    circle.center+x*normal+along_line*line.direction)
示例#5
0
def _intersectCirclePlane(circle, plane):
    if abs(abs(circle.normal * plane.normal) - 1.) < eps:
        if plane.hasPoint(circle.center):
            return circle
        else:
            return None
    else:
        line = plane.intersectWith(Plane(circle.center, circle.normal))
        x = line.distanceFrom(circle.center)
        if x > circle.radius:
            return None
        else:
            angle = N.arccos(x / circle.radius)
            along_line = N.sin(angle) * circle.radius
            normal = circle.normal.cross(line.direction)
            if line.distanceFrom(circle.center + normal) > x:
                normal = -normal
            return (circle.center + x * normal - along_line * line.direction,
                    circle.center + x * normal + along_line * line.direction)
示例#6
0
 def _tetrahedralH(self, atom, known, unknown, bond):
     r = atom.position()
     n = (known[0].position() - r).normal()
     cone = Objects3D.Cone(r, n, Numeric.arccos(-1. / 3.))
     sphere = Objects3D.Sphere(r, bond)
     circle = sphere.intersectWith(cone)
     others = filter(lambda a: a.symbol != 'H', known[0].bondedTo())
     others.remove(atom)
     other = others[0]
     ref = (Objects3D.Plane(circle.center, circle.normal) \
            .projectionOf(other.position())-circle.center).normal()
     p0 = circle.center + ref * circle.radius
     p0 = Objects3D.rotatePoint(
         p0, Objects3D.Line(circle.center, circle.normal), 60. * Units.deg)
     p1 = Objects3D.rotatePoint(
         p0, Objects3D.Line(circle.center, circle.normal), 120. * Units.deg)
     p2 = Objects3D.rotatePoint(
         p1, Objects3D.Line(circle.center, circle.normal), 120. * Units.deg)
     unknown[0].setPosition(p0)
     unknown[1].setPosition(p1)
     unknown[2].setPosition(p2)
示例#7
0
 def _tetrahedralH(self, atom, known, unknown, bond):
     r = atom.position()
     n = (known[0].position()-r).normal()
     cone = Objects3D.Cone(r, n, N.arccos(-1./3.))
     sphere = Objects3D.Sphere(r, bond)
     circle = sphere.intersectWith(cone)
     others = filter(lambda a: a.symbol != 'H', known[0].bondedTo())
     others.remove(atom)
     other = others[0]
     ref = (Objects3D.Plane(circle.center, circle.normal) \
            .projectionOf(other.position())-circle.center).normal()
     p0 = circle.center + ref*circle.radius
     p0 = Objects3D.rotatePoint(p0,
                               Objects3D.Line(circle.center, circle.normal),
                               60.*Units.deg)
     p1 = Objects3D.rotatePoint(p0,
                               Objects3D.Line(circle.center, circle.normal),
                               120.*Units.deg)
     p2 = Objects3D.rotatePoint(p1,
                               Objects3D.Line(circle.center, circle.normal),
                               120.*Units.deg)
     unknown[0].setPosition(p0)
     unknown[1].setPosition(p1)
     unknown[2].setPosition(p2)
示例#8
0
class Molecule(CompositeChemicalObject, ChemicalObject):
    """Molecule

    A Glossary:Subclass of Class:MMTK.ChemicalObjects.ChemicalObject.

    Molecules consist of atoms and groups linked by bonds.

    Constructor: Molecule(|species|, **|properties|)

    Arguments:

    |species| -- a string (not case sensitive) that specifies the molecule
                 name in the chemical database

    |properties| -- optional keyword properties:

      * position: the center-of-mass position (a vector)
      * configuration: the name of a configuration listed in the database
                       definition of the molecule, which is used to
                       initialize the atom positions. If no configuration
                       is specified, the configuration named "default" will
                       be used, if it exists. Otherwise the atom positions
                       are undefined.
      * name: the atom name (a string)
    """
    def __init__(self, blueprint, _memo=None, **properties):
        if blueprint is not None:
            # blueprint is None when called from MoleculeFactory
            ChemicalObject.__init__(self, blueprint, _memo)
            properties = copy.copy(properties)
            CompositeChemicalObject.__init__(self, properties)
            self.bonds = Bonds.BondList(self.bonds)

    blueprintclass = Database.BlueprintMolecule

    def bondedTo(self, atom):
        return self.bonds.bondedTo(atom)

    def setBondAttributes(self):
        self.bonds.setBondAttributes()

    def clearBondAttributes(self):
        for a in self.atoms:
            a.clearBondAttribute()

    def _subunits(self):
        return self.groups

    def _descriptionSpec(self):
        return "M", None

    def addGroup(self, group, bond_atom_pairs):
        for a1, a2 in bond_atom_pairs:
            o1 = a1.topLevelChemicalObject()
            o2 = a2.topLevelChemicalObject()
            if not (o1 == self and o2 == group) \
               and not(o2 == self and o1 == group):
                raise ValueError("bond %s-%s outside object" %
                                 (str(a1), str(a2)))
        self.groups.append(group)
        self.atoms = self.atoms + group.atoms
        group.parent = self
        self.clearBondAttributes()
        for a1, a2 in bond_atom_pairs:
            self.bonds.append(Bonds.Bond((a1, a2)))
        for b in group.bonds:
            self.bonds.append(b)

    # construct positions of missing hydrogens
    def findHydrogenPositions(self):
        """Find reasonable positions for hydrogen atoms that have no
        position assigned.

        This method uses a heuristic approach based on standard geometry
        data. It was developed for proteins and DNA and may not give
        good results for other molecules. It raises an exception
        if presented with a topology it cannot handle."""
        self.setBondAttributes()
        try:
            unknown = DictWithDefault([])
            for a in self.atoms:
                if a.position() is None:
                    if a.symbol != 'H':
                        raise ValueError('position of ' + a.fullName() + \
                                          ' is undefined')
                    bonded = a.bondedTo()[0]
                    unknown[bonded].append(a)
            for a, list in unknown.items():
                bonded = a.bondedTo()
                n = len(bonded)
                known = []
                for b in bonded:
                    if b.position() is not None:
                        known.append(b)
                nb = len(list)
                if a.symbol == 'C':
                    if n == 4:
                        if nb == 1:
                            self._C4oneH(a, known, list)
                        elif nb == 2:
                            self._C4twoH(a, known, list)
                        elif nb == 3:
                            self._C4threeH(a, known, list)
                    elif n == 3:
                        if nb == 1:
                            self._C3oneH(a, known, list)
                        else:
                            self._C3twoH(a, known, list)
                    elif n == 2:
                        self._C2oneH(a, known, list)
                    else:
                        print a
                        raise ValueError("Can't handle C with " + ` n ` +
                                         " bonds")
                elif a.symbol == 'N':
                    if n == 4:
                        if nb == 3:
                            self._N4threeH(a, known, list)
                        elif nb == 2:
                            self._N4twoH(a, known, list)
                        elif nb == 1:
                            self._N4oneH(a, known, list)
                    elif n == 3:
                        if nb == 1:
                            self._N3oneH(a, known, list)
                        elif nb == 2:
                            self._N3twoH(a, known, list)
                    elif n == 2:
                        self._N2oneH(a, known, list)
                    else:
                        print a
                        raise ValueError("Can't handle N with " + ` n ` +
                                         " bonds")
                elif a.symbol == 'O' and n == 2:
                    self._O2(a, known, list)
                elif a.symbol == 'S' and n == 2:
                    self._S2(a, known, list)
                else:
                    print a
                    raise ValueError("Can't handle this yet: " + a.symbol +
                                     ' with ' + ` n ` + ' bonds (' +
                                     a.fullName() + ').')
        finally:
            self.clearBondAttributes()

    # default C-H bond length and X-C-H angle
    _ch_bond = 1.09 * Units.Ang
    _hch_angle = Numeric.arccos(-1. / 3.) * Units.rad
    _nh_bond = 1.03 * Units.Ang
    _hnh_angle = 120. * Units.deg
    _oh_bond = 0.95 * Units.Ang
    _coh_angle = 114.9 * Units.deg
    _sh_bond = 1.007 * Units.Ang
    _csh_angle = 96.5 * Units.deg

    def _C4oneH(self, atom, known, unknown):
        r = atom.position()
        n0 = (known[0].position() - r).normal()
        n1 = (known[1].position() - r).normal()
        n2 = (known[2].position() - r).normal()
        n3 = (n0 + n1 + n2).normal()
        unknown[0].setPosition(r - self._ch_bond * n3)

    def _C4twoH(self, atom, known, unknown):
        r = atom.position()
        r1 = known[0].position()
        r2 = known[1].position()
        plane = Objects3D.Plane(r, r1, r2)
        axis = -((r1 - r) + (r2 - r)).normal()
        plane = plane.rotate(Objects3D.Line(r, axis), 90. * Units.deg)
        cone = Objects3D.Cone(r, axis, 0.5 * self._hch_angle)
        sphere = Objects3D.Sphere(r, self._ch_bond)
        circle = sphere.intersectWith(cone)
        points = circle.intersectWith(plane)
        unknown[0].setPosition(points[0])
        unknown[1].setPosition(points[1])

    def _C4threeH(self, atom, known, unknown):
        self._tetrahedralH(atom, known, unknown, self._ch_bond)

    def _C3oneH(self, atom, known, unknown):
        r = atom.position()
        n1 = (known[0].position() - r).normal()
        n2 = (known[1].position() - r).normal()
        n3 = -(n1 + n2).normal()
        unknown[0].setPosition(r + self._ch_bond * n3)

    def _C3twoH(self, atom, known, unknown):
        r = atom.position()
        r1 = known[0].position()
        others = filter(lambda a: a.symbol != 'H', known[0].bondedTo())
        r2 = others[0].position()
        try:
            plane = Objects3D.Plane(r, r1, r2)
        except ZeroDivisionError:
            # We get here if all three points are colinear.
            # Add a small random displacement as a fix.
            from MMTK.Random import randomPointInSphere
            plane = Objects3D.Plane(r, r1, r2 + randomPointInSphere(0.001))
        axis = (r - r1).normal()
        cone = Objects3D.Cone(r, axis, 0.5 * self._hch_angle)
        sphere = Objects3D.Sphere(r, self._ch_bond)
        circle = sphere.intersectWith(cone)
        points = circle.intersectWith(plane)
        unknown[0].setPosition(points[0])
        unknown[1].setPosition(points[1])

    def _C2oneH(self, atom, known, unknown):
        r = atom.position()
        r1 = known[0].position()
        x = r + self._ch_bond * (r - r1).normal()
        unknown[0].setPosition(x)

    def _N2oneH(self, atom, known, unknown):
        r = atom.position()
        r1 = known[0].position()
        others = filter(lambda a: a.symbol != 'H', known[0].bondedTo())
        r2 = others[0].position()
        try:
            plane = Objects3D.Plane(r, r1, r2)
        except ZeroDivisionError:
            # We get here when all three points are colinear.
            # Add a small random displacement as a fix.
            from MMTK.Random import randomPointInSphere
            plane = Objects3D.Plane(r, r1, r2 + randomPointInSphere(0.001))
        axis = (r - r1).normal()
        cone = Objects3D.Cone(r, axis, 0.5 * self._hch_angle)
        sphere = Objects3D.Sphere(r, self._nh_bond)
        circle = sphere.intersectWith(cone)
        points = circle.intersectWith(plane)
        unknown[0].setPosition(points[0])

    def _N3oneH(self, atom, known, unknown):
        r = atom.position()
        n1 = (known[0].position() - r).normal()
        n2 = (known[1].position() - r).normal()
        n3 = -(n1 + n2).normal()
        unknown[0].setPosition(r + self._nh_bond * n3)

    def _N3twoH(self, atom, known, unknown):
        r = atom.position()
        r1 = known[0].position()
        others = filter(lambda a: a.symbol != 'H', known[0].bondedTo())
        r2 = others[0].position()
        plane = Objects3D.Plane(r, r1, r2)
        axis = (r - r1).normal()
        cone = Objects3D.Cone(r, axis, 0.5 * self._hnh_angle)
        sphere = Objects3D.Sphere(r, self._nh_bond)
        circle = sphere.intersectWith(cone)
        points = circle.intersectWith(plane)
        unknown[0].setPosition(points[0])
        unknown[1].setPosition(points[1])

    def _N4threeH(self, atom, known, unknown):
        self._tetrahedralH(atom, known, unknown, self._nh_bond)

    def _N4twoH(self, atom, known, unknown):
        r = atom.position()
        r1 = known[0].position()
        r2 = known[1].position()
        plane = Objects3D.Plane(r, r1, r2)
        axis = -((r1 - r) + (r2 - r)).normal()
        plane = plane.rotate(Objects3D.Line(r, axis), 90. * Units.deg)
        cone = Objects3D.Cone(r, axis, 0.5 * self._hnh_angle)
        sphere = Objects3D.Sphere(r, self._nh_bond)
        circle = sphere.intersectWith(cone)
        points = circle.intersectWith(plane)
        unknown[0].setPosition(points[0])
        unknown[1].setPosition(points[1])

    def _N4oneH(self, atom, known, unknown):
        r = atom.position()
        n0 = (known[0].position() - r).normal()
        n1 = (known[1].position() - r).normal()
        n2 = (known[2].position() - r).normal()
        n3 = (n0 + n1 + n2).normal()
        unknown[0].setPosition(r - self._nh_bond * n3)

    def _O2(self, atom, known, unknown):
        others = known[0].bondedTo()
        for a in others:
            r = a.position()
            if a != atom and r is not None: break
        dihedral = 180. * Units.deg
        self._findPosition(unknown[0], atom.position(), known[0].position(), r,
                           self._oh_bond, self._coh_angle, dihedral)

    def _S2(self, atom, known, unknown):
        c2 = filter(lambda a: a.symbol == 'C', known[0].bondedTo())[0]
        self._findPosition(unknown[0], atom.position(), known[0].position(),
                           c2.position(), self._sh_bond, self._csh_angle,
                           180. * Units.deg)

    def _tetrahedralH(self, atom, known, unknown, bond):
        r = atom.position()
        n = (known[0].position() - r).normal()
        cone = Objects3D.Cone(r, n, Numeric.arccos(-1. / 3.))
        sphere = Objects3D.Sphere(r, bond)
        circle = sphere.intersectWith(cone)
        others = filter(lambda a: a.symbol != 'H', known[0].bondedTo())
        others.remove(atom)
        other = others[0]
        ref = (Objects3D.Plane(circle.center, circle.normal) \
               .projectionOf(other.position())-circle.center).normal()
        p0 = circle.center + ref * circle.radius
        p0 = Objects3D.rotatePoint(
            p0, Objects3D.Line(circle.center, circle.normal), 60. * Units.deg)
        p1 = Objects3D.rotatePoint(
            p0, Objects3D.Line(circle.center, circle.normal), 120. * Units.deg)
        p2 = Objects3D.rotatePoint(
            p1, Objects3D.Line(circle.center, circle.normal), 120. * Units.deg)
        unknown[0].setPosition(p0)
        unknown[1].setPosition(p1)
        unknown[2].setPosition(p2)

    def _findPosition(self, unknown, a1, a2, a3, bond, angle, dihedral):
        sphere = Objects3D.Sphere(a1, bond)
        cone = Objects3D.Cone(a1, a2 - a1, angle)
        plane = Objects3D.Plane(a3, a2, a1)
        plane = plane.rotate(Objects3D.Line(a1, a2 - a1), dihedral)
        points = sphere.intersectWith(cone).intersectWith(plane)
        for p in points:
            if (a1 - a2).cross(p - a1) * (plane.normal) > 0:
                unknown.setPosition(p)
                break
    def threeAngles(self, e1, e2, e3, tolerance=1e-7):
        """
        Find three angles a1, a2, a3 such that
        Rotation(a1*e1)*Rotation(a2*e2)*Rotation(a3*e3)
        is equal to the rotation object. e1, e2, and
        e3 are non-zero vectors. There are two solutions, both of which
        are computed.

        @param e1: a rotation axis
        @type e1: L{Scientific.Geometry.Vector}
        @param e2: a rotation axis
        @type e2: L{Scientific.Geometry.Vector}
        @param e3: a rotation axis
        @type e3: L{Scientific.Geometry.Vector}
        @returns: a list containing two arrays of shape (3,),
                  each containing the three angles of one solution
        @rtype: C{list} of C{N.array}
        @raise ValueError: if two consecutive axes are parallel
        """

        # Written by Pierre Legrand ([email protected])
        #
        # Basically this is a reimplementation of the David
        # Thomas's algorithm [1] described by Gerard Bricogne in [2]:
        #
        # [1] "Modern Equations of Diffractometry. Goniometry." D.J. Thomas
        # Acta Cryst. (1990) A46 Page 321-343.
        #
        # [2] "The ECC Cooperative Programming Workshop on Position-Sensitive
        # Detector Software." G. Bricogne,
        # Computational aspect of Protein Crystal Data Analysis,
        # Proceedings of the Daresbury Study Weekend (23-24/01/1987)
        # Page 122-126

        e1 = e1.normal()
        e2 = e2.normal()
        e3 = e3.normal()

        # We are searching for the three angles a1, a2, a3
        # If 2 consecutive axes are parallel: decomposition is not meaningful
        if (e1.cross(e2)).length() < tolerance or \
           (e2.cross(e3)).length() < tolerance :
            raise ValueError('Consecutive parallel axes. Too many solutions')
        w = self(e3)

        # Solve the equation : _a.cosx + _b.sinx = _c
        _a = e1 * e3 - (e1 * e2) * (e2 * e3)
        _b = e1 * (e2.cross(e3))
        _c = e1 * w - (e1 * e2) * (e2 * e3)
        _norm = (_a**2 + _b**2)**0.5

        # Checking for possible errors in initial Rot matrix
        if _norm == 0:
            raise ValueError('FAILURE 1, norm = 0')
        if abs(_c / _norm) > 1 + tolerance:
            raise ValueError(
                'FAILURE 2' +
                'malformed rotation Tensor (non orthogonal?) %.8f' %
                (_c / _norm))
        #if _c/_norm > 1: raise ValueError('Step1: No solution')
        _th = angleFromSineAndCosine(_b / _norm, _a / _norm)
        _xmth = N.arccos(_c / _norm)

        # a2a and a2b are the two possible solutions to the equation.
        a2a = mod_angle((_th + _xmth), 2 * N.pi)
        a2b = mod_angle((_th - _xmth), 2 * N.pi)

        solutions = []
        # for each solution, find the two other angles (a1, a3).
        for a2 in (a2a, a2b):
            R2 = Rotation(e2, a2)
            v = R2(e3)
            v1 = v - (v * e1) * e1
            w1 = w - (w * e1) * e1
            norm = ((v1 * v1) * (w1 * w1))**0.5
            if norm == 0:
                # in that case rotation 1 and 3 are about the same axis
                # so any solution for rotation 1 is OK
                a1 = 0.
            else:
                cosa1 = (v1 * w1) / norm
                sina1 = v1 * (w1.cross(e1)) / norm
                a1 = mod_angle(angleFromSineAndCosine(sina1, cosa1), 2 * N.pi)

            R3 = Rotation(e2, -1 * a2) * Rotation(e1, -1 * a1) * self
            # u = normalized test vector perpendicular to e3
            # if e2 and e3 are // we have an exception before.
            # if we take u = e1^e3 then it will not work for
            # Euler and Kappa axes.
            u = (e2.cross(e3)).normal()
            cosa3 = u * R3(u)
            sina3 = u * (R3(u).cross(e3))
            a3 = mod_angle(angleFromSineAndCosine(sina3, cosa3), 2 * N.pi)

            solutions.append(N.array([a1, a2, a3]))

        # Gives the closest solution to 0,0,0 first
        if N.add.reduce(solutions[0]**2) > \
               N.add.reduce(solutions[1]**2):
            solutions = [solutions[1], solutions[0]]
        return solutions