public void TestNotionalToCartesian_Double_double_double_double_double_double()
        {
            Vector3[] cardAxes = CrystalGeometryTools.NotionalToCartesian(1.0, 2.0, 3.0, 90.0, 90.0, 90.0);
            // the a axis
            Assert.AreEqual(1.0, cardAxes[0].X, 0.001);
            Assert.AreEqual(0.0, cardAxes[0].Y, 0.001);
            Assert.AreEqual(0.0, cardAxes[0].Z, 0.001);
            // the b axis
            Assert.AreEqual(0.0, cardAxes[1].X, 0.001);
            Assert.AreEqual(2.0, cardAxes[1].Y, 0.001);
            Assert.AreEqual(0.0, cardAxes[1].Z, 0.001);
            // the c axis
            Assert.AreEqual(0.0, cardAxes[2].X, 0.001);
            Assert.AreEqual(0.0, cardAxes[2].Y, 0.001);
            Assert.AreEqual(3.0, cardAxes[2].Z, 0.001);

            // some sanity checking: roundtripping
            cardAxes = CrystalGeometryTools.NotionalToCartesian(9.3323, 10.1989, 11.2477, 69.043, 74.441, 77.821);
            Vector3 a = cardAxes[0];
            Vector3 b = cardAxes[1];
            Vector3 c = cardAxes[2];

            Assert.AreEqual(69.043, Vectors.RadianToDegree(Vectors.Angle(b, c)), 0.001);
            Assert.AreEqual(74.441, Vectors.RadianToDegree(Vectors.Angle(a, c)), 0.001);
            Assert.AreEqual(77.821, Vectors.RadianToDegree(Vectors.Angle(b, a)), 0.001);
            Assert.AreEqual(9.3323, a.Length(), 0.0001);
            Assert.AreEqual(10.1989, b.Length(), 0.0001);
            Assert.AreEqual(11.2477, c.Length(), 0.0001);
        }
Beispiel #2
0
        private bool DoAngleSnap(IAtom atom, IAtomContainer placedNeighbours)
        {
            if (placedNeighbours.Atoms.Count != 2)
            {
                return(false);
            }
            var b1 = Molecule.GetBond(atom, placedNeighbours.Atoms[0]);

            if (!b1.IsInRing)
            {
                return(false);
            }
            var b2 = Molecule.GetBond(atom, placedNeighbours.Atoms[1]);

            if (!b2.IsInRing)
            {
                return(false);
            }

            var p1 = atom.Point2D.Value;
            var p2 = placedNeighbours.Atoms[0].Point2D.Value;
            var p3 = placedNeighbours.Atoms[1].Point2D.Value;

            var v1 = NewVector(p2, p1);
            var v2 = NewVector(p3, p1);

            return(Math.Abs(Vectors.Angle(v2, v1) - ANGLE_120) < 0.01);
        }
        public void TestCalcAngle3()
        {
            Vector3 b     = new Vector3(4.5, 3.1, 1.7);
            double  angle = Vectors.Angle(b, b) * 180.0 / Math.PI;

            Assert.AreEqual(0.0, angle, 0.001);
        }
        public void TestCalcAngle2()
        {
            Vector3 b     = new Vector3(0.0, 1.0, 1.0);
            Vector3 c     = new Vector3(0.0, 0.0, 1.0);
            double  angle = Vectors.Angle(b, c) * 180.0 / Math.PI;

            Assert.AreEqual(45.0, angle, 0.001);
        }
Beispiel #5
0
            /// <summary>
            /// Determine the angle between two bonds of one atom.
            /// </summary>
            /// <param name="atom">an atom</param>
            /// <param name="bond1">a bond connected to the atom</param>
            /// <param name="bond2">another bond connected to the atom</param>
            /// <returns>the angle (in radians)</returns>
            private static double GetAngle(IAtom atom, IBond bond1, IBond bond2)
            {
                var pA = atom.Point2D.Value;
                var pB = bond1.GetOther(atom).Point2D.Value;
                var pC = bond2.GetOther(atom).Point2D.Value;
                var u  = new Vector2(pB.X - pA.X, pB.Y - pA.Y);
                var v  = new Vector2(pC.X - pA.X, pC.Y - pA.Y);

                return(Vectors.Angle(u, v));
            }
Beispiel #6
0
 // @cdk.dictref  blue-obelisk:convertCartesianIntoNotionalCoordinates
 public static double[] CartesianToNotional(Vector3 aAxis, Vector3 bAxis, Vector3 cAxis)
 {
     double[] notionalCoords = new double[6];
     notionalCoords[0] = aAxis.Length();
     notionalCoords[1] = bAxis.Length();
     notionalCoords[2] = cAxis.Length();
     notionalCoords[3] = Vectors.Angle(bAxis, cAxis) * 180.0 / Math.PI;
     notionalCoords[4] = Vectors.Angle(aAxis, cAxis) * 180.0 / Math.PI;
     notionalCoords[5] = Vectors.Angle(aAxis, bAxis) * 180.0 / Math.PI;
     return(notionalCoords);
 }
Beispiel #7
0
 private static double Angle(Vector2 pBeg, Vector2 pEnd)
 {
     // TODO inline to allow generic Tuple2ds
     return(Vectors.Angle(pBeg, pEnd));
 }
Beispiel #8
0
 /// <summary>
 /// Given vectors for the hypotenuse and adjacent side of a right angled
 /// triangle and the length of the opposite side, determine how long the
 /// adjacent side size.
 /// </summary>
 /// <param name="hypotenuse">vector for the hypotenuse</param>
 /// <param name="adjacent">vector for the adjacent side</param>
 /// <param name="oppositeLength">length of the opposite side of a triangle</param>
 /// <returns>length of the adjacent side</returns>
 public static double AdjacentLength(Vector2 hypotenuse, Vector2 adjacent, double oppositeLength)
 {
     return(Math.Tan(Vectors.Angle(hypotenuse, adjacent)) * oppositeLength);
 }
Beispiel #9
0
        /// <summary>
        /// Generate a new annotation vector for an atom using the connected bonds and any other occupied
        /// space (auxiliary vectors). The fall back method is to use the largest available space but
        /// some common cases are handled differently. For example, when the number of bonds is two
        /// the annotation is placed in the acute angle of the bonds (providing there is space). This
        /// improves labelling of atoms saturated rings. When there are three bonds and two are 'plain'
        /// the label is again placed in the acute section of the plain bonds.
        /// </summary>
        /// <param name="atom">the atom having an annotation</param>
        /// <param name="bonds">the bonds connected to the atom</param>
        /// <param name="auxVectors">additional vectors to avoid (filled spaced)</param>
        /// <returns>unit vector along which the annotation should be placed.</returns>
        /// <seealso cref="IsPlainBond(IBond)"/>
        /// <seealso cref="VecmathUtil.NewVectorInLargestGap(IList{Vector2})"/>
        internal static Vector2 NewAtomAnnotationVector(IAtom atom, IEnumerable <IBond> bonds, List <Vector2> auxVectors)
        {
            var vectors = new List <Vector2>();

            foreach (var bond in bonds)
            {
                vectors.Add(VecmathUtil.NewUnitVector(atom, bond));
            }

            if (vectors.Count == 0)
            {
                // no bonds, place below
                if (auxVectors.Count == 0)
                {
                    return(new Vector2(0, -1));
                }
                if (auxVectors.Count == 1)
                {
                    return(VecmathUtil.Negate(auxVectors[0]));
                }
                return(VecmathUtil.NewVectorInLargestGap(auxVectors));
            }
            else if (vectors.Count == 1)
            {
                // 1 bond connected
                // H0, then label simply appears on the opposite side
                if (auxVectors.Count == 0)
                {
                    return(VecmathUtil.Negate(vectors[0]));
                }
                // !H0, then place it in the largest gap
                vectors.AddRange(auxVectors);
                return(VecmathUtil.NewVectorInLargestGap(vectors));
            }
            else if (vectors.Count == 2 && auxVectors.Count == 0)
            {
                // 2 bonds connected to an atom with no hydrogen labels

                // sum the vectors such that the label appears in the acute/nook of the two bonds
                var combined = VecmathUtil.Sum(vectors[0], vectors[1]);

                // shallow angle (< 30 deg) means the label probably won't fit
                if (Vectors.Angle(vectors[0], vectors[1]) < Vectors.DegreeToRadian(65))
                {
                    combined = Vector2.Negate(combined);
                }
                else
                {
                    // flip vector if either bond is a non-single bond or a wedge, this will
                    // place the label in the largest space.
                    // However - when both bonds are wedged (consider a bridging system) to
                    // keep the label in the nook of the wedges
                    var bonds_ = bonds.ToList();
                    if ((!IsPlainBond(bonds_[0]) || !IsPlainBond(bonds_[1])) &&
                        !(IsWedged(bonds_[0]) && IsWedged(bonds_[1])))
                    {
                        combined = Vector2.Negate(combined);
                    }
                }

                combined = Vector2.Normalize(combined);

                // did we divide by 0? whoops - this happens when the bonds are collinear
                if (double.IsNaN(combined.Length()))
                {
                    return(VecmathUtil.NewVectorInLargestGap(vectors));
                }

                return(combined);
            }
            else
            {
                if (vectors.Count == 3 && auxVectors.Count == 0)
                {
                    // 3 bonds connected to an atom with no hydrogen label

                    // the easy and common case is to check when two bonds are plain
                    // (i.e. non-stereo sigma bonds) and use those. This gives good
                    // placement for fused conjugated rings

                    var plainVectors = new List <Vector2>();
                    var wedgeVectors = new List <Vector2>();

                    foreach (var bond in bonds)
                    {
                        if (IsPlainBond(bond))
                        {
                            plainVectors.Add(VecmathUtil.NewUnitVector(atom, bond));
                        }
                        if (IsWedged(bond))
                        {
                            wedgeVectors.Add(VecmathUtil.NewUnitVector(atom, bond));
                        }
                    }

                    if (plainVectors.Count == 2)
                    {
                        return(VecmathUtil.Sum(plainVectors[0], plainVectors[1]));
                    }
                    else if (plainVectors.Count + wedgeVectors.Count == 2)
                    {
                        plainVectors.AddRange(wedgeVectors);
                        return(VecmathUtil.Sum(plainVectors[0], plainVectors[1]));
                    }
                }

                // the default option is to find the largest gap
                if (auxVectors.Count > 0)
                {
                    vectors.AddRange(auxVectors);
                }
                return(VecmathUtil.NewVectorInLargestGap(vectors));
            }
        }
Beispiel #10
0
        // Private procedures

        private void WriteCrystal(ICrystal crystal)
        {
            var title = crystal.Title;

            if (title != null && title.Trim().Length > 0)
            {
                Writeln($"TITL {title.Trim()}");
            }
            else
            {
                Writeln("TITL Produced with CDK (http://cdk.sf.net/)");
            }
            Vector3 a       = crystal.A;
            Vector3 b       = crystal.B;
            Vector3 c       = crystal.C;
            double  alength = a.Length();
            double  blength = b.Length();
            double  clength = c.Length();
            double  alpha   = Vectors.RadianToDegree(Vectors.Angle(b, c));
            double  beta    = Vectors.RadianToDegree(Vectors.Angle(a, c));
            double  gamma   = Vectors.RadianToDegree(Vectors.Angle(a, b));

            Write("CELL " + 1.54184.ToString("F5", NumberFormatInfo.InvariantInfo) + "   ");
            Write(alength.ToString("F5", NumberFormatInfo.InvariantInfo) + "  ");
            Write(blength.ToString("F5", NumberFormatInfo.InvariantInfo) + "  ");
            Write(clength.ToString("F5", NumberFormatInfo.InvariantInfo) + "  ");
            Write(alpha.ToString("F4", NumberFormatInfo.InvariantInfo) + "  ");
            Write(beta.ToString("F4", NumberFormatInfo.InvariantInfo) + "  ");
            Write(gamma.ToString("F4", NumberFormatInfo.InvariantInfo) + "  ");
            Writeln("ZERR " + ((double)crystal.Z).ToString("F5", NumberFormatInfo.InvariantInfo)
                    + "    0.01000  0.01000   0.01000   0.0100   0.0100   0.0100");
            string spaceGroup = crystal.SpaceGroup;

            if (string.Equals("P1", spaceGroup, StringComparison.Ordinal))
            {
                Writeln("LATT  -1");
            }
            else if (string.Equals("P 2_1 2_1 2_1", spaceGroup, StringComparison.Ordinal))
            {
                Writeln("LATT  -1");
                Writeln("SYMM  1/2+X   , 1/2-Y   ,    -Z");
                Writeln("SYMM     -X   , 1/2+Y   , 1/2-Z");
                Writeln("SYMM  1/2-X   ,    -Y   , 1/2+Z");
            }
            string            elemNames  = "";
            string            elemCounts = "";
            IMolecularFormula formula    = MolecularFormulaManipulator.GetMolecularFormula(crystal);
            var asortedElements          = MolecularFormulaManipulator.Elements(formula).ToReadOnlyList();

            foreach (var element in asortedElements)
            {
                string symbol = element.Symbol;
                elemNames += symbol + "    ".Substring(symbol.Length);
                string countS = MolecularFormulaManipulator.GetElementCount(formula, element).ToString(NumberFormatInfo.InvariantInfo);
                elemCounts += countS + "    ".Substring(countS.Length);
            }
            Writeln("SFAC  " + elemNames);
            Writeln("UNIT  " + elemCounts);
            /* write atoms */
            for (int i = 0; i < crystal.Atoms.Count; i++)
            {
                IAtom   atom      = crystal.Atoms[i];
                Vector3 cartCoord = atom.Point3D.Value;
                Vector3 fracCoord = CrystalGeometryTools.CartesianToFractional(a, b, c, cartCoord);
                string  symbol    = atom.Symbol;
                string  output    = symbol + (i + 1);
                Write(output);
                for (int j = 1; j < 5 - output.Length; j++)
                {
                    Write(" ");
                }
                Write("     ");
                string elemID = null;
                for (int elemidx = 0; elemidx < asortedElements.Count; elemidx++)
                {
                    var elem = asortedElements[elemidx];
                    if (elem.Symbol.Equals(symbol, StringComparison.Ordinal))
                    {
                        elemID = (elemidx + 1).ToString(NumberFormatInfo.InvariantInfo);
                        break;
                    }
                }
                Write(elemID);
                Write("    ".Substring(elemID.Length));
                Write(fracCoord.X.ToString("F5", NumberFormatInfo.InvariantInfo) + "   ");
                Write(fracCoord.Y.ToString("F5", NumberFormatInfo.InvariantInfo) + "   ");
                Writeln(fracCoord.Y.ToString("F5", NumberFormatInfo.InvariantInfo) + "    11.00000    0.05000");
            }
            Writeln("END");
        }
Beispiel #11
0
        /// <summary>
        ///  Distribute the bonded atoms (neighbours) of an atom such that they fill the
        ///  remaining space around an atom in a geometrically nice way.
        ///  IMPORTANT: This method is not supposed to handle the
        ///  case of one or no place neighbor. In the case of
        ///  one placed neighbor, the chain placement methods
        ///  should be used.
        /// </summary>
        /// <param name="atom">The atom whose partners are to be placed</param>
        /// <param name="placedNeighbours">The atoms which are already placed</param>
        /// <param name="unplacedNeighbours">The partners to be placed</param>
        /// <param name="bondLength">The standard bond length for the newly placed atoms</param>
        /// <param name="sharedAtomsCenter">The 2D centre of the placed atoms</param>
        public void DistributePartners(IAtom atom, IAtomContainer placedNeighbours, Vector2 sharedAtomsCenter, IAtomContainer unplacedNeighbours, double bondLength)
        {
            double occupiedAngle = 0;

            IAtom[] sortedAtoms    = null;
            double  startAngle     = 0.0;
            double  addAngle       = 0.0;
            double  radius         = 0.0;
            double  remainingAngle = 0.0;
            // calculate the direction away from the already placed partners of atom
            var sharedAtomsCenterVector = sharedAtomsCenter;

            var newDirection      = atom.Point2D.Value;
            var occupiedDirection = sharedAtomsCenter;

            occupiedDirection = Vector2.Subtract(occupiedDirection, newDirection);
            // if the placing on the centre atom we get NaNs just give a arbitrary direction the
            // rest works it's self out
            if (Math.Abs(occupiedDirection.Length()) < 0.001)
            {
                occupiedDirection = new Vector2(0, 1);
            }
            Debug.WriteLine("distributePartners->occupiedDirection.Lenght(): " + occupiedDirection.Length());
            var atomsToDraw = new List <IAtom>();

            Debug.WriteLine($"Number of shared atoms: {placedNeighbours.Atoms.Count}");

            // IMPORTANT: This method is not supposed to handle the case of one or
            // no place neighbor. In the case of one placed neighbor, the chain
            // placement methods should be used.
            if (placedNeighbours.Atoms.Count == 1)
            {
                Debug.WriteLine("Only one neighbour...");
                for (int f = 0; f < unplacedNeighbours.Atoms.Count; f++)
                {
                    atomsToDraw.Add(unplacedNeighbours.Atoms[f]);
                }

                addAngle = Math.PI * 2 / (unplacedNeighbours.Atoms.Count + placedNeighbours.Atoms.Count);
                // IMPORTANT: At this point we need a calculation of the start
                // angle. Not done yet.
                IAtom  placedAtom = placedNeighbours.Atoms[0];
                double xDiff      = placedAtom.Point2D.Value.X - atom.Point2D.Value.X;
                double yDiff      = placedAtom.Point2D.Value.Y - atom.Point2D.Value.Y;

                Debug.WriteLine("distributePartners->xdiff: " + Vectors.RadianToDegree(xDiff));
                Debug.WriteLine("distributePartners->ydiff: " + Vectors.RadianToDegree(yDiff));
                startAngle = GeometryUtil.GetAngle(xDiff, yDiff);
                Debug.WriteLine("distributePartners->angle: " + Vectors.RadianToDegree(startAngle));

                PopulatePolygonCorners(atomsToDraw, atom.Point2D.Value, startAngle, addAngle, bondLength);
                return;
            }
            else if (placedNeighbours.Atoms.Count == 0)
            {
                Debug.WriteLine("First atom...");
                for (int f = 0; f < unplacedNeighbours.Atoms.Count; f++)
                {
                    atomsToDraw.Add(unplacedNeighbours.Atoms[f]);
                }

                addAngle = Math.PI * 2.0 / unplacedNeighbours.Atoms.Count;

                // IMPORTANT: At this point we need a calculation of the start
                // angle. Not done yet.
                startAngle = 0.0;
                PopulatePolygonCorners(atomsToDraw, atom.Point2D.Value, startAngle, addAngle, bondLength);
                return;
            }

            if (DoAngleSnap(atom, placedNeighbours))
            {
                int numTerminal = 0;
                foreach (var unplaced in unplacedNeighbours.Atoms)
                {
                    if (Molecule.GetConnectedBonds(unplaced).Count() == 1)
                    {
                        numTerminal++;
                    }
                }

                if (numTerminal == unplacedNeighbours.Atoms.Count)
                {
                    var a     = NewVector(placedNeighbours.Atoms[0].Point2D.Value, atom.Point2D.Value);
                    var b     = NewVector(placedNeighbours.Atoms[1].Point2D.Value, atom.Point2D.Value);
                    var d1    = GeometryUtil.GetAngle(a.X, a.Y);
                    var d2    = GeometryUtil.GetAngle(b.X, b.Y);
                    var sweep = Vectors.Angle(a, b);
                    if (sweep < Math.PI)
                    {
                        sweep = 2 * Math.PI - sweep;
                    }
                    startAngle = d2;
                    if (d1 > d2 && d1 - d2 < Math.PI || d2 - d1 >= Math.PI)
                    {
                        startAngle = d1;
                    }
                    sweep /= (1 + unplacedNeighbours.Atoms.Count);
                    PopulatePolygonCorners(unplacedNeighbours.Atoms,
                                           atom.Point2D.Value, startAngle, sweep, bondLength);

                    MarkPlaced(unplacedNeighbours);
                    return;
                }
                else
                {
                    atom.RemoveProperty(MacroCycleLayout.MACROCYCLE_ATOM_HINT);
                }
            }

            // if the least hindered side of the atom is clearly defined (bondLength / 10 is an arbitrary value that seemed reasonable)
            //newDirection.Sub(sharedAtomsCenterVector);
            sharedAtomsCenterVector -= newDirection;
            newDirection             = sharedAtomsCenterVector;
            newDirection             = Vector2.Normalize(newDirection);
            newDirection             = newDirection * bondLength;
            newDirection             = -newDirection;
            Debug.WriteLine($"distributePartners->newDirection.Lenght(): {newDirection.Length()}");
            var distanceMeasure = atom.Point2D.Value;

            distanceMeasure += newDirection;

            // get the two sharedAtom partners with the smallest distance to the new center
            sortedAtoms = placedNeighbours.Atoms.ToArray();
            GeometryUtil.SortBy2DDistance(sortedAtoms, distanceMeasure);
            var closestPoint1 = sortedAtoms[0].Point2D.Value;
            var closestPoint2 = sortedAtoms[1].Point2D.Value;

            closestPoint1 -= atom.Point2D.Value;
            closestPoint2 -= atom.Point2D.Value;
            occupiedAngle  = Vectors.Angle(closestPoint1, occupiedDirection);
            occupiedAngle += Vectors.Angle(closestPoint2, occupiedDirection);

            var angle1 = GeometryUtil.GetAngle(
                sortedAtoms[0].Point2D.Value.X - atom.Point2D.Value.X,
                sortedAtoms[0].Point2D.Value.Y - atom.Point2D.Value.Y);
            var angle2 = GeometryUtil.GetAngle(
                sortedAtoms[1].Point2D.Value.X - atom.Point2D.Value.X,
                sortedAtoms[1].Point2D.Value.Y - atom.Point2D.Value.Y);
            var angle3 = GeometryUtil.GetAngle(
                distanceMeasure.X - atom.Point2D.Value.X,
                distanceMeasure.Y - atom.Point2D.Value.Y);

            if (debug)
            {
                try
                {
                    Debug.WriteLine($"distributePartners->sortedAtoms[0]: {(Molecule.Atoms.IndexOf(sortedAtoms[0]) + 1)}");
                    Debug.WriteLine($"distributePartners->sortedAtoms[1]: {(Molecule.Atoms.IndexOf(sortedAtoms[1]) + 1)}");
                    Debug.WriteLine($"distributePartners->angle1: {Vectors.RadianToDegree(angle1)}");
                    Debug.WriteLine($"distributePartners->angle2: {Vectors.RadianToDegree(angle2)}");
                }
                catch (Exception exc)
                {
                    Debug.WriteLine(exc);
                }
            }
            IAtom startAtom = null;

            if (angle1 > angle3)
            {
                if (angle1 - angle3 < Math.PI)
                {
                    startAtom = sortedAtoms[1];
                }
                else
                {
                    // 12 o'clock is between the two vectors
                    startAtom = sortedAtoms[0];
                }
            }
            else
            {
                if (angle3 - angle1 < Math.PI)
                {
                    startAtom = sortedAtoms[0];
                }
                else
                {
                    // 12 o'clock is between the two vectors
                    startAtom = sortedAtoms[1];
                }
            }
            remainingAngle = (2 * Math.PI) - occupiedAngle;
            addAngle       = remainingAngle / (unplacedNeighbours.Atoms.Count + 1);
            if (debug)
            {
                try
                {
                    Debug.WriteLine($"distributePartners->startAtom: {(Molecule.Atoms.IndexOf(startAtom) + 1)}");
                    Debug.WriteLine($"distributePartners->remainingAngle: {Vectors.RadianToDegree(remainingAngle)}");
                    Debug.WriteLine($"distributePartners->addAngle: {Vectors.RadianToDegree(addAngle)}");
                    Debug.WriteLine($"distributePartners-> partners.Atoms.Count: {unplacedNeighbours.Atoms.Count}");
                }
                catch (Exception exc)
                {
                    Debug.WriteLine(exc);
                }
            }
            for (int f = 0; f < unplacedNeighbours.Atoms.Count; f++)
            {
                atomsToDraw.Add(unplacedNeighbours.Atoms[f]);
            }
            radius     = bondLength;
            startAngle = GeometryUtil.GetAngle(startAtom.Point2D.Value.X - atom.Point2D.Value.X, startAtom.Point2D.Value.Y - atom.Point2D.Value.Y);
            Debug.WriteLine($"Before check: distributePartners->startAngle: {startAngle}");
            Debug.WriteLine($"After check: distributePartners->startAngle: {startAngle}");
            PopulatePolygonCorners(atomsToDraw, atom.Point2D.Value, startAngle, addAngle, radius);
        }