1 module vivaldi.node; 2 3 import vivaldi.coordinate; 4 5 /** 6 * A node is a point in the coordinate system with an estimated 7 * position. 8 * 9 * Node positions may either be represented Vivaldi coordinate, or by 10 * "hybrid" coordinates. In the hybrid model, an additional 11 * non-Euclidean adjustment term is added to each coordinate. 12 * 13 * Adjustment terms improve the performance of the Euclidean 14 * embedding, and therefore can match the performance of 15 * high-dimension embeddings with fewer dimensions (plus the 16 * adjustment term). 17 * 18 * See "On Suitability of Euclidean Embedding forHost-based Network 19 * Coordinate System" by Lee et al. 20 * 21 * Params: 22 * T = The type of an instantiation of Coordinate. 23 * window = The number of samples used to compute each adjustment term. 24 */ 25 struct Node(T, ushort window = 0) 26 { 27 /** 28 * Given a round-trip time observation for another node at 29 * `other`, updates the estimated position of this Coordinate. 30 */ 31 void update(const ref Node other, const double rtt) nothrow @safe @nogc { 32 static if (window > 0) { 33 import std.algorithm : sum; 34 35 coordinate.update(other.coordinate, rtt, adjustment, other.adjustment); 36 const dist = coordinate.distanceTo(other.coordinate); 37 38 // NOTE: Rather than choosing landmarks as described in 39 // "On Suitability", sample all nodes. In a passive 40 // system, this is feasible. 41 samples[index] = rtt - dist; 42 index = (index + 1) % window; 43 44 adjustment = sum(samples[]) / (2.0 * window); 45 } else { 46 coordinate.update(other.coordinate, rtt); 47 } 48 } 49 50 /** 51 * Returns the distance to `other` in estimated round-trip time. 52 */ 53 double distanceTo(const ref Node other) nothrow @safe @nogc { 54 auto dist = coordinate.distanceTo(other.coordinate); 55 56 static if (window > 0) { 57 import std.algorithm : max; 58 dist = max(dist, dist + adjustment + other.adjustment); 59 } 60 61 return dist; 62 } 63 64 /** 65 * The Vivaldi coordinate of this Node. 66 */ 67 T coordinate; 68 69 private: 70 71 static if (window > 0) { 72 /** 73 * The adjustment term in a hybrid coordinate system. See "On 74 * Suitability" Sec. VI-A. 75 */ 76 double adjustment = 0.0; 77 78 /** 79 * The insertion index for the next sample in the sample window. 80 */ 81 size_t index = 0; 82 83 /** 84 * Samples is a ringbuffer of error terms used to compute an adjustment. 85 */ 86 double[window] samples = 0.0; 87 } 88 } 89 90 @("no adjustment") 91 nothrow @safe @nogc unittest { 92 alias C4 = Coordinate!4; 93 94 auto a = Node!C4(); 95 auto b = Node!C4(); 96 97 a.update(b, 0.2); 98 assert(a.distanceTo(b) > 0); 99 } 100 101 @("adjustment") 102 nothrow @safe @nogc unittest { 103 version (DigitalMars) { 104 import std.math : isClose; 105 } else version (LDC) { 106 import std.math : approxEqual; 107 alias isClose = approxEqual; 108 } 109 110 alias C3 = Coordinate!(3, 1.5, 0); 111 112 auto a = Node!(C3, 20)(); 113 auto b = Node!(C3, 20)(); 114 115 a.coordinate.vector = [ -0.5, 1.3, 2.4 ]; 116 b.coordinate.vector = [ 1.2, -2.3, 3.4 ]; 117 118 assert(a.distanceTo(a) == 0); 119 assert(a.distanceTo(b) == b.distanceTo(a)); 120 assert(isClose(a.distanceTo(b), 4.104875150354758)); 121 122 a.adjustment = -1.0e6; 123 assert(isClose(a.distanceTo(b), 4.104875150354758)); 124 125 a.adjustment = 0.1; 126 b.adjustment = 0.2; 127 128 assert(isClose(a.distanceTo(b), 4.104875150354758 + 0.3)); 129 }