PageRenderTime 57ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/GraphLayout/MSAGL/Core/ProjectionSolver/Variable.cs

https://gitlab.com/hoseinyeganloo/automatic-graph-layout
C# | 270 lines | 156 code | 30 blank | 84 comment | 15 complexity | b043e3c95a0ae80b9811bc86f8e42a37 MD5 | raw file
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="Variable.cs" company="Microsoft">
  3. // (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <summary>
  6. // MSAGL class for Variables for Projection Solver.
  7. // </summary>
  8. // --------------------------------------------------------------------------------------------------------------------
  9. // Remove this from project build and uncomment here to selectively enable per-class.
  10. //#define VERBOSE
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Diagnostics;
  14. using System.Globalization;
  15. namespace Microsoft.Msagl.Core.ProjectionSolver
  16. {
  17. /// <summary>
  18. /// A Variable is essentially a wrapper around a node, containing the node's initial and
  19. /// current (Actual) positions along the current axis and a collection of Constraints.
  20. /// </summary>
  21. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes")]
  22. public class Variable : IComparable<Variable>
  23. {
  24. /// <summary>
  25. /// Passed through as a convenience to the caller; it is not used by ProjectionSolver directly
  26. /// (except in VERIFY/VERBOSE where it uses ToString()), but if the variable list returned by
  27. /// Solver.Variables is sorted, then UserData must implement IComparable. When Solve() is
  28. /// complete, the caller should copy the Variable's ActualPos property into whatever property
  29. /// the class specialization for this has.
  30. /// </summary>
  31. public Object UserData { get; set; }
  32. // These properties are initialized by caller before being passed to Solver.AddVariable.
  33. /// <summary>
  34. /// This holds the desired position of the node (the position we'd like it to have, initially
  35. /// calculated before any constraint application). This may change during the process of
  36. /// solution; currently that only happens if there are neighbors. Each iteration of the
  37. /// solution keeps block reference-position calculation as close as possible to this position.
  38. /// </summary>
  39. public double DesiredPos { get; set; }
  40. // Variable has no Size member. We use only the DesiredPos and (Scaled)ActualPos and
  41. // assume only a point (in a single dimension). OverlapRemoval takes care of generating
  42. // constraints using size information. It would not make sense to incorporate Size into
  43. // the violation calculation at the ProjectionSolver level for two reasons:
  44. // - It would not take into account the potential deferral from horizontal to vertical
  45. // when the vertical movement could be much less.
  46. // - It would not automatically ensure that all overlap constraints were even calculated;
  47. // it would only enforce constraints added by the caller, which would either use
  48. // OverlapRemoval or have constraint-generation logic optimized for its own scenario.
  49. /// <summary>
  50. /// The weight of the node; a variable with a higher weight than others in its block will
  51. /// move less than it would if all weights were equal.
  52. /// </summary>
  53. public double Weight { get; set; }
  54. /// <summary>
  55. /// The scale of the variable. May be set by the application. For Qpsc this is computed
  56. /// from the Hessian diagonal and replaces any application-set value during Solve().
  57. /// </summary>
  58. public double Scale { get; set; }
  59. /// <summary>
  60. /// The current position of the variable; s[i]y[i] in the scaling paper. It is updated on each
  61. /// iteration inside Solve(), then unscaled to contain the final position when Solve() completes.
  62. /// </summary>
  63. public double ActualPos { get; set; }
  64. /// <summary>
  65. /// The derivative value - essentially the weighted difference in position.
  66. /// </summary>
  67. internal double DfDv { get { return (2 * Weight * (ActualPos - DesiredPos)) / this.Scale; } }
  68. // Updated through Solve().
  69. internal double OffsetInBlock { get; set; }
  70. internal Block Block { get; set; }
  71. // For Qpsc
  72. internal uint Ordinal { get; private set; }
  73. // Use an array[] for Constraints for performance. Their membership in the Variable doesn't change after
  74. // Solve() initializes, so we can use the fixed-size array and gain performance (at some up-front cost due
  75. // to buffering in AddVariable/AddConstraint, but the tradeoff is a great improvement). This cannot be done
  76. // for Variables (whose membership in a Block changes) or Blocks (whose membership in the block list changes).
  77. // Constraints where 'this' is constraint.Left
  78. internal Constraint[] LeftConstraints { get; private set; }
  79. // Constraints where 'this' is constraint.Right
  80. internal Constraint[] RightConstraints { get; private set; }
  81. internal int ActiveConstraintCount
  82. {
  83. get { return activeConstraintCount; }
  84. set
  85. {
  86. Debug.Assert(value >= 0, "ActiveConstraintCount must be >= 0");
  87. activeConstraintCount = value;
  88. }
  89. }
  90. private int activeConstraintCount;
  91. internal struct NeighborAndWeight
  92. {
  93. internal Variable Neighbor { get; private set; }
  94. internal double Weight { get; private set; }
  95. internal NeighborAndWeight(Variable neighbor, double weight) : this()
  96. {
  97. this.Neighbor = neighbor;
  98. this.Weight = weight;
  99. }
  100. }
  101. // The (x1-x2)^2 neighbor relationships: Key == NeighborVar, Value == Weight of relationship
  102. internal List<NeighborAndWeight> Neighbors { get; private set; }
  103. internal Variable(uint ordinal, Object userData, double desiredPos, double weight, double scale)
  104. {
  105. if (weight <= 0)
  106. {
  107. throw new ArgumentOutOfRangeException("weight"
  108. #if DEBUG
  109. , "Variable Weight must be greater than zero"
  110. #endif // DEBUG
  111. );
  112. }
  113. if (scale <= 0)
  114. {
  115. throw new ArgumentOutOfRangeException("scale"
  116. #if DEBUG
  117. , "Variable Scale must be greater than zero"
  118. #endif // DEBUG
  119. );
  120. }
  121. double check = desiredPos * weight;
  122. if (double.IsInfinity(check) || double.IsNaN(check))
  123. {
  124. throw new ArgumentOutOfRangeException("desiredPos"
  125. #if DEBUG
  126. , "Invalid Variable DesiredPosition * Weight"
  127. #endif // DEBUG
  128. );
  129. }
  130. check = desiredPos * scale;
  131. if (double.IsInfinity(check) || double.IsNaN(check))
  132. {
  133. throw new ArgumentOutOfRangeException("desiredPos"
  134. #if DEBUG
  135. , "Invalid Variable DesiredPosition * Scale"
  136. #endif // DEBUG
  137. );
  138. }
  139. this.Ordinal = ordinal;
  140. this.UserData = userData;
  141. this.DesiredPos = desiredPos;
  142. this.Weight = weight;
  143. this.Scale = scale;
  144. this.OffsetInBlock = 0.0;
  145. this.ActualPos = this.DesiredPos;
  146. }
  147. internal void Reinitialize()
  148. {
  149. // // Called by Qpsc or equivalence-constraint-regapping initial block restructuring.
  150. this.ActiveConstraintCount = 0;
  151. this.OffsetInBlock = 0.0;
  152. // If we are in Qpsc, this simply repeats (in the opposite direction) what
  153. // Qpsc.VariablesComplete did after (possibly) scaling. If we're not in Qpsc,
  154. // then we've reset all the blocks because we could not incrementally re-Solve
  155. // due to changes to equality constraints, so this restores the initial state.
  156. this.ActualPos = this.DesiredPos;
  157. }
  158. internal void AddNeighbor(Variable neighbor, double weight)
  159. {
  160. if (null == this.Neighbors)
  161. {
  162. this.Neighbors = new List<NeighborAndWeight>();
  163. }
  164. this.Neighbors.Add(new NeighborAndWeight(neighbor, weight));
  165. }
  166. /// <summary>
  167. /// Gets a string representation of the Variable; calls UserData.ToString as part of this.
  168. /// </summary>
  169. /// <returns>A string representation of the variable.</returns>
  170. public override string ToString()
  171. {
  172. return string.Format(CultureInfo.InvariantCulture,
  173. #if VERBOSE
  174. "Var: '{0}' a {1:F5} d {2:F5} o {3:F5} w {4:F5} s {5:F5}",
  175. this.Name, ActualPos, DesiredPos, OffsetInBlock, Weight, Scale);
  176. #else // VERBOSE
  177. "{0} {1:F5} ({2:F5}) {3:F5} {4:F5}",
  178. this.Name, ActualPos, DesiredPos, Weight, Scale);
  179. #endif // VERBOSE
  180. }
  181. /// <summary>
  182. /// Gets the string representation of UserData.
  183. /// </summary>
  184. /// <returns>A string representation of Node.Object.</returns>
  185. public string Name
  186. {
  187. get { return (null == this.UserData) ? "-0-" : this.UserData.ToString(); }
  188. }
  189. internal void SetConstraints(Constraint[] leftConstraints, Constraint[] rightConstraints)
  190. {
  191. this.LeftConstraints = leftConstraints;
  192. this.RightConstraints = rightConstraints;
  193. }
  194. #region IComparable<Variable> Members
  195. /// <summary>
  196. /// Compare the Variables by their ordinals, in ascending order (this == lhs, other == rhs).
  197. /// </summary>
  198. /// <param name="other">The object being compared to.</param>
  199. /// <returns>-1 if this.Ordinal is "less"; +1 if this.Ordinal is "greater"; 0 if this.Ordinal
  200. /// and rhs are equal.</returns>
  201. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.CompareTo(System.String)")]
  202. public int CompareTo(Variable other)
  203. {
  204. ValidateArg.IsNotNull(other, "other");
  205. return this.Ordinal.CompareTo(other.Ordinal);
  206. }
  207. #endregion // IComparer<Node> Members
  208. #if NOTNEEDED_FXCOP // This entails a perf hit due to ==/!= becoming a non-inlined function call in some cases.
  209. // We only create one Variable object per variable so do not need anything but reference
  210. // ==/!=, so we suppress:1036 above. Add UnitTests for these if they're enabled.
  211. #region RequiredOverridesForIComparable
  212. // Omitting getHashCode violates rule: OverrideGetHashCodeOnOverridingEquals.
  213. public override int GetHashCode() {
  214. return Ordinal.GetHashCode();
  215. }
  216. // Omitting any of the following violates rule: OverrideMethodsOnComparableTypes.
  217. public override bool Equals(Object obj) {
  218. if (!(obj is Variable))
  219. return false;
  220. return (this.CompareTo((Variable)obj) == 0);
  221. }
  222. public static bool operator ==(Variable lhs, Variable rhs) {
  223. if (null == (object)lhs) { // Cast to object to avoid recursive op==
  224. return (null == (object)rhs);
  225. }
  226. return lhs.Equals(rhs);
  227. }
  228. public static bool operator !=(Variable lhs, Variable rhs) {
  229. return !(lhs == rhs);
  230. }
  231. public static bool operator <(Variable lhs, Variable rhs) {
  232. return (lhs.CompareTo(rhs) < 0);
  233. }
  234. public static bool operator >(Variable lhs, Variable rhs) {
  235. return (lhs.CompareTo(rhs) > 0);
  236. }
  237. #endregion // RequiredOverridesForIcomparable
  238. #endif // NOTNEEDED_FXCOP
  239. }
  240. }