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 }