using System; using System.Collections.Generic; using System.Linq; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Layout.MDS; using Microsoft.Msagl.Core; namespace Microsoft.Msagl.Layout.Incremental { /// /// /// public class ProcrustesCircleConstraint : IConstraint { Node[] V; int n; Point[] X; Point[] Y; /// /// Create a circle constraint with variable radius /// /// public ProcrustesCircleConstraint(IEnumerable nodes) { V = nodes.ToArray(); n = V.Length; X = new Point[n]; Y = new Point[n]; double angle = 2.0 * Math.PI / (double)n; double r = 10*(double)n; // we make our circle a reasonable size to aid numerical precision for (int i = 0; i < n; ++i) { double theta = angle * (double)i; Y[i] = new Point(r * Math.Cos(theta), r * Math.Sin(theta)); } } /// /// A procrustes constraint with arbitrary target shape /// /// /// public ProcrustesCircleConstraint(IEnumerable nodes, IEnumerable targetConfiguration) { ValidateArg.IsNotNull(nodes, "nodes"); ValidateArg.IsNotNull(targetConfiguration, "targetConfiguration"); V = nodes.ToArray(); n = V.Length; X = new Point[n]; Y = new Point[n]; int i = 0; Point yc = new Point(); foreach (var p in targetConfiguration) { Y[i++] = p; yc += p; } yc /= n; for (i = 0; i < n; ++i) { Y[i] -= yc; } } #region IConstraint Members /// /// matrix product: A'B /// /// /// /// 2X2 matrix private static Point[] matrixProduct(Point[] A, Point[] B) { Point[] R = new Point[2]; double x0 = 0, y0 = 0, x1 = 0, y1 = 0; for (int i = 0; i < A.Length; ++i) { x0 += A[i].X * B[i].X; y0 += A[i].X * B[i].Y; x1 += A[i].Y * B[i].X; y1 += A[i].Y * B[i].Y; } R[0] = new Point(x0, y0); R[1] = new Point(x1, y1); return R; } /// /// matrix product of two 2x2 matrices with no transpose, i.e.: AB /// /// /// /// 2X2 matrix private static Point[] matrixProductNoTranspose(Point[] A, Point[] B) { Point[] R = new Point[2]; R[0] = new Point(A[0].X*B[0].X+A[0].Y*B[1].X, A[0].X*B[0].Y + A[0].Y*B[1].Y); R[1] = new Point(A[1].X*B[0].X+A[1].Y*B[1].X, A[1].X*B[0].Y + A[1].Y*B[1].Y); return R; } private static Point MatrixTimesVector(Point[] A, Point v) { double a = A[0].X, b = A[0].Y, c = A[1].X, d = A[1].Y; return new Point(a * v.X + b * v.Y, c * v.X + d * v.Y); } private static Point eigenSystem2(Point[] B, out Point[] Q) { double[][] b = new double[B.Length][]; for (int i = 0; i < B.Length; i++) { b[i] = new double[2]; b[i][0] = B[i].X; b[i][1] = B[i].Y; } double lambda1; double[] q1; double lambda2; double[] q2; MultidimensionalScaling.SpectralDecomposition(b, out q1, out lambda1, out q2, out lambda2,300,1e-8); Q = new Point[] { new Point(q1[0], q1[1]), new Point(q2[0], q2[1]) }; return new Point(lambda1, lambda2); } /// /// Compute singular value decomposition of a 2X2 matrix X=PSQ' /// /// input 2x2 matrix /// left singular vectors /// right singular vectors (eigenvectors of X'X) /// Singular values (Sqrt of eigenvalues of X'X) private static Point SingularValueDecomposition(Point[] X, out Point[] P, out Point[] Q) { Point[] XX = matrixProduct(X, X); Point l = eigenSystem2(XX, out Q); /* Point[] Q0; Point l0 = eigenSystem3(XX, out Q0); Q0 = new Point[] { Q0[1], Q0[0] }; Q0 = Transpose(Q0); Q0 = new Point[] { -Q0[0], -Q0[1] }; double tmp = l0.X; l0.X = l0.Y; l0.Y = tmp; if (!cmpEigenSystem(Q0, Q, l0, l)) { System.Console.Write("XX="); printMatrix(XX); System.Console.Write("Q0="); printMatrix(Q0); System.Console.WriteLine("l0=" + l0 + ";"); System.Console.Write("Q="); printMatrix(Q); System.Console.WriteLine("l=" + l + ";"); } l = l0; Q = Q0; System.Console.Write("Q="); printMatrix(Q); System.Console.WriteLine("l=" + l + ";"); */ Point s = new Point(Math.Sqrt(l.X), Math.Sqrt(l.Y)); P = matrixProductNoTranspose(X, Q); P = matrixProductNoTranspose(P,new Point[]{new Point(1.0/s.X,0), new Point(0,1.0/s.Y)}); return s; } #if OLDPROJECT /// /// /// /// public double Project() { Point barycenter = Barycenter(); Point[] X = new Point[ni]; Point[] Y = new Point[ni]; double angle = 2.0 * Math.PI / nd; double r = 0; int i = 0; foreach(var v in nodes) { X[i] = v.Center - barycenter; r += X[i].LengthSquared; ++i; } r = Math.Sqrt(r / nd); i = 0; foreach (var v in nodes) { double theta = angle * (double)i; Y[i] = new Point(r * Math.Cos(theta), r * Math.Sin(theta)); ++i; } Point[] C = matrixProduct(Y, X); Point[] P, Q; SingularValueDecomposition(C, out P, out Q); Point[] T = matrixProductNoTranspose(Q, Transpose(P)); /* Point[] XY = matrixProduct(X, Y); Point[] XYT = matrixProductNoTranspose(XY, T); Point[] YY = matrixProduct(Y, Y); double s = XYT[0].X + XYT[1].Y; s /= YY[0].X + YY[1].Y; for (int i = 0; i < nodes.Count; ++i) { Y[i] = Y[i] * s; } */ i = 0; foreach (var v in nodes) { v.Center = barycenter + MatrixTimesVector(T, Y[i++]); } return 0; } #endif /// /// /// /// public double Project() { for (int i = 0; i < n; ++i) { X[i] = V[i].Center; } Point[] T; double s; Point t; FindTransform(X, Y, out T, out s, out t); //System.Console.WriteLine("X="); //printMatrix(X); //System.Console.WriteLine("Y="); //printMatrix(Y); //System.Console.WriteLine("T="); //printMatrix(T); //System.Console.WriteLine("s="+s); //System.Console.WriteLine("t="+t); Point[] TT = Transpose(T); double displacement = 0; for (int i = 0; i < n; ++i) { var v = V[i]; v.Center = double.IsNaN(s) ? Y[i] : s * MatrixTimesVector(TT, Y[i]) + t; displacement += (v.Center - X[i]).Length; } return displacement; } /// /// output an n*2 point matrix in Mathematica format /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.Write(System.String,System.Object,System.Object)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.Write(System.String)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String)")] public static void PrintMatrix(Point[] points) { ValidateArg.IsNotNull(points, "points"); System.Console.Write("{"); for (int i = 0; i < points.Length;) { System.Console.Write("{{{0},{1}}}", points[i].X, points[i].Y); if (++i != points.Length) { System.Console.Write(","); } } System.Console.WriteLine("};"); } private static Point[] Transpose(Point[] X) { return new Point[] { new Point(X[0].X, X[1].X), new Point(X[0].Y, X[1].Y) }; } //// //// //// //public static void Test1() { // Random rand = new Random(); // Point[] X = new Point[] { new Point(rand.NextDouble(), rand.NextDouble()), new Point(rand.NextDouble(), rand.NextDouble()) }; // System.Console.Write("X="); printMatrix(X); // Point[] P, Q; // Point s = SingularValueDecomposition(X, out P, out Q); // Point[] S = new Point[] { new Point(s.X, 0), new Point(0, s.Y) }; // System.Console.Write("S="); printMatrix(S); // System.Console.Write("Q="); printMatrix(Q); // System.Console.Write("P="); printMatrix(P); // Point[] R = matrixProductNoTranspose(P, S); // System.Console.Write("PS="); printMatrix(R); // R = matrixProductNoTranspose(R, Transpose(Q)); // System.Console.Write("R="); printMatrix(R); // Debug.Assert(Math.Abs(R[0].X - X[0].X) < 0.01); // Debug.Assert(Math.Abs(R[0].Y - X[0].Y) < 0.01); // Debug.Assert(Math.Abs(R[1].X - X[1].X) < 0.01); // Debug.Assert(Math.Abs(R[1].Y - X[1].Y) < 0.01); //} //// //// //// // public static void Test() { // double[,] XX = {{-0.342439, -0.815696}, {-0.772753, -0.807363}, {-0.264356, //0.847908}, {-0.524064, 0.826169}, {0.615021, -0.762655}}; // List vs = new List(); // for (int i = 0; i < XX.Length/2; ++i) { // var v = new Microsoft.Msagl.Node(); // v.Center = new Point(XX[i,0], XX[i,1]); // vs.Add(v); // } // ProcrustesCircleConstraint c = new ProcrustesCircleConstraint(vs); // c.Project(); // } /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Body"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.Write(System.String)")] public static void Test() { #if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=340 double[,] XX = null; throw new InvalidOperationException(); #else double[,] XX = { { 1, 2 }, { -1, 2 }, { -1, -2 }, { 1, -2 } }, YY = { { 0.07, 2.62 }, { 0.93, 3.12 }, { 1.93, 1.38 }, { 1.07, 0.88 } }; int ni = 4; Point[] X = new Point[ni]; Point[] Y = new Point[ni]; for (int i = 0; i < ni; ++i) { X[i] = new Point(XX[i, 0], XX[i, 1]); Y[i] = new Point(YY[i, 0], YY[i, 1]); } Point[] XY = matrixProduct(X, Y); System.Console.Write("XY="); PrintMatrix(XY); Point[] P, Q; SingularValueDecomposition(XY, out P, out Q); PrintMatrix(P); PrintMatrix(Q); Point[] T; double s; Point t; FindTransform(X, Y, out T, out s, out t); PrintMatrix(T); System.Console.WriteLine("s={0}", s); System.Console.WriteLine("t=({0},{1})", t.X,t.Y); #endif } private static void FindTransform(Point[] X, Point[] Y, out Point[] T, out double s, out Point t) { int ni = X.Length; Point[] C = matrixProduct(X, Y); Point[] P, Q; SingularValueDecomposition(C, out P, out Q); T = matrixProductNoTranspose(Q, Transpose(P)); Point[] XYT = matrixProductNoTranspose(C, T); Point[] Y2 = matrixProduct(Y, Y); s = XYT[0].X + XYT[1].Y; s /= Y2[0].X + Y2[1].Y; t = new Point(); for (int i = 0; i < ni; ++i) { t += X[i] - s * MatrixTimesVector(T, Y[i]); } t /= ni; } /// /// /// public int Level { get { return 1; } } /// /// Get the list of nodes involved in the constraint /// public IEnumerable Nodes { get { return V.AsEnumerable(); } } #endregion } }