1 module quantities.internal.dimensions; 2 3 package: 4 5 struct Dim 6 { 7 string symbol; 8 int power; 9 10 bool opEquals(Dim other) const pure @safe nothrow 11 { 12 return power == other.power && symbol == other.symbol; 13 } 14 15 int opCmp(Dim other) const pure @safe nothrow 16 { 17 if (symbol < other.symbol) 18 return -1; 19 else if (symbol > other.symbol) 20 return 1; 21 else 22 return 0; 23 } 24 25 string toString() const pure @safe 26 { 27 import std.string : format; 28 29 if (power == 0) 30 return null; 31 else if (power == 1) 32 return symbol; 33 else 34 return "%s^%d".format(symbol, power); 35 } 36 } 37 38 struct DimList 39 { 40 private Dim[] list; 41 42 invariant() 43 { 44 import std.algorithm : isSorted; 45 assert(list.isSorted); 46 } 47 48 DimList dup() const pure @safe nothrow 49 { 50 return DimList(list.dup); 51 } 52 53 void insert(bool invert = false)(string symbol, int power) pure @safe nothrow 54 { 55 import std.algorithm : countUntil, remove; 56 import std.array : insertInPlace; 57 58 if (power == 0) 59 return; 60 61 static if (invert) 62 power = -power; 63 64 if (!list.length) 65 { 66 list = [Dim(symbol, power)]; 67 return; 68 } 69 70 auto pos = list.countUntil!(d => d.symbol == symbol)(); 71 if (pos >= 0) 72 { 73 // Merge the dimensions 74 list[pos].power += power; 75 if (list[pos].power == 0) 76 { 77 try 78 () @trusted { list = list.remove(pos); } (); 79 catch (Exception) 80 // remove only throws when it has multiple arguments 81 assert(false); 82 83 // Necessary to compare dimensionless values 84 if (!list.length) 85 list = null; 86 } 87 } 88 else 89 { 90 // Insert the new dimension 91 pos = list.countUntil!(d => d.symbol > symbol)(); 92 if (pos < 0) 93 pos = list.length; 94 list.insertInPlace(pos, Dim(symbol, power)); 95 } 96 } 97 98 void insert(bool invert = false)(in DimList other) pure @safe nothrow 99 { 100 foreach (dim; other.list) 101 insert!invert(dim.symbol, dim.power); 102 } 103 104 bool opEquals(in DimList other) const pure @safe nothrow 105 { 106 return list == other.list; 107 } 108 109 string toString() const pure @safe 110 { 111 import std.string; 112 return "[%(%s %)]".format(list); 113 } 114 } 115 116 unittest 117 { 118 auto list = DimList([Dim("a", 1), Dim("c", 2), Dim("e", 1)]); 119 list.insert("f", -1); 120 assert(list.toString == "[a c^2 e f^-1]"); 121 list.insert("f", 1); 122 assert(list.toString == "[a c^2 e]"); 123 list.insert("b", 3); 124 assert(list.toString == "[a b^3 c^2 e]"); 125 list.insert("0", 1); 126 assert(list.toString == "[0 a b^3 c^2 e]"); 127 list.insert("0", -1); 128 list.insert("a", -1); 129 list.insert("b", -3); 130 list.insert("c", -2); 131 list.insert("e", -1); 132 assert(list.toString == "[]"); 133 list.insert("x", 0); 134 assert(list.toString == "[]"); 135 list.insert("x", 1); 136 assert(list.toString == "[x]"); 137 } 138 139 unittest // Compile-time 140 { 141 enum list = { 142 DimList list; 143 list.insert("a", 1); 144 return list; 145 }(); 146 } 147 148 struct Dimensions 149 { 150 private: 151 DimList dimList; 152 153 version (unittest) 154 { 155 this(DimList list) pure @safe nothrow 156 { 157 dimList = list; 158 } 159 160 this(int[string] dims) pure @safe 161 { 162 foreach (k, v; dims) 163 dimList.insert(k, v); 164 } 165 } 166 167 public: 168 static Dimensions mono(string symbol) pure @safe nothrow 169 { 170 DimList list; 171 list.insert(symbol, 1); 172 return Dimensions(list); 173 } 174 175 bool empty() const pure @safe nothrow 176 { 177 return !dimList.list.length; 178 } 179 180 Dimensions dup() const pure @safe nothrow 181 { 182 return Dimensions(dimList.dup); 183 } 184 185 Dimensions invert() const pure @safe nothrow 186 { 187 auto list = dimList.dup; 188 foreach (ref dim; list.list) 189 dim.power = -dim.power; 190 return Dimensions(list); 191 } 192 pure @safe unittest 193 { 194 auto dim = Dimensions(["a": 5, "b": -2]); 195 assert(dim.invert == Dimensions(["a": -5, "b": 2])); 196 } 197 198 Dimensions binop(string op)(in Dimensions other) const pure @safe nothrow 199 if (op == "*") 200 { 201 auto list = dimList.dup; 202 list.insert(other.dimList); 203 return Dimensions(list); 204 } 205 pure @safe unittest 206 { 207 auto dim1 = Dimensions(["a": 1, "b": -2]); 208 auto dim2 = Dimensions(["a": -1, "c": 2]); 209 assert(dim1.binop!"*"(dim2) == Dimensions(["b": -2, "c": 2])); 210 } 211 212 Dimensions binop(string op)(in Dimensions other) const pure @safe 213 if (op == "/" || op == "%") 214 { 215 auto list = dimList.dup; 216 list.insert!true(other.dimList); 217 return Dimensions(list); 218 } 219 pure @safe unittest 220 { 221 auto dim1 = Dimensions(["a": 1, "b": -2]); 222 auto dim2 = Dimensions(["a": 1, "c": 2]); 223 assert(dim1.binop!"/"(dim2) == Dimensions(["b": -2, "c": -2])); 224 } 225 226 Dimensions pow(int n) const pure @safe 227 { 228 if (n == 0) 229 return Dimensions.init; 230 231 auto list = dimList.dup; 232 foreach (ref dim; list.list) 233 dim.power = dim.power * n; 234 return Dimensions(list); 235 } 236 pure @safe unittest 237 { 238 auto dim = Dimensions(["a": 5, "b": -2]); 239 assert(dim.pow(2) == Dimensions(["a": 10, "b": -4])); 240 assert(dim.pow(0) == Dimensions.init); 241 } 242 243 Dimensions powinverse(int n) const pure @safe 244 { 245 import std.exception : enforce; 246 import std.string : format; 247 248 auto list = dimList.dup; 249 foreach (ref dim; list.list) 250 { 251 enforce(dim.power % n == 0, 252 "Dimension error: '%s^%s' is not divisible by %s" 253 .format(dim.symbol, dim.power, n)); 254 dim.power = dim.power / n; 255 } 256 return Dimensions(list); 257 } 258 pure @safe unittest 259 { 260 auto dim = Dimensions(["a": 6, "b": -2]); 261 assert(dim.powinverse(2) == Dimensions(["a": 3, "b": -1])); 262 } 263 264 bool opEquals(in Dimensions other) const pure @safe nothrow 265 { 266 return dimList == other.dimList; 267 } 268 pure @safe unittest 269 { 270 assert(Dimensions.init == Dimensions.init); 271 assert(Dimensions(["a": 1, "b": 2]) == Dimensions(["a": 1, "b": 2])); 272 assert(Dimensions(["a": 1, "b": 1]) != Dimensions(["a": 1, "b": 2])); 273 assert(Dimensions(["a": 1]) != Dimensions(["a": 1, "b": 2])); 274 assert(Dimensions(["a": 1, "b": 2]) != Dimensions(["a": 1])); 275 } 276 277 string toString() const pure @safe 278 { 279 return dimList.toString; 280 } 281 unittest 282 { 283 assert(Dimensions(["a": 2, "b": -1, "c": 1, "d": 0]).toString == "[a^2 b^-1 c]"); 284 } 285 } 286 287 unittest // Compile-time 288 { 289 enum dim = { 290 DimList list; 291 list.insert("a", 1); 292 return Dimensions(list); 293 }(); 294 295 int foo(Dimensions d)() { return 0; } 296 enum a = foo!dim(); 297 }