1 // Written in the D programming language
2 /++
3 This module defines the base types for unit and quantity handling.
4 
5 Each  quantity can  be represented  as the  product  of a  number and  a set  of
6 dimensions,  and  the struct  Quantity  has  this  role.  The number  is  stored
7 internally as  a member of type  N, which is  enforced to be a  built-in numeric
8 type  (isNumeric!N is  true). The  dimensions are  stored as  template parameter
9 list (Dim)  in the  form of a  sequence of string  symbols and  integral powers.
10 Dimensionless  quantities have  an  empty  Dim. For  instance  length and  speed
11 quantities can be stored as:
12 ---
13 alias Length = Quantity!(real, "L", 1);
14 alias Speed  = Quantity!(real, "L", 1, "T", -1);
15 ---
16 where "L" is the symbol for the length  dimension, "T" is the symbol of the time
17 dimensions,  and  1   and  -1  are  the  powers  of   those  dimensions  in  the
18 representation of the quantity.
19 
20 The main  quantities compliant with the  international system of units  (SI) are
21 predefined  in  the module  quantities.si.  In  the  same  way, units  are  just
22 instances of  a Quantity struct  where the number is  1 and the  dimensions only
23 contain  one  symbol,  with  the  power  1. For  instance,  the  meter  unit  is
24 predefined as something equivalent to:
25 ---
26 enum meter = Quantity!(real, "L", 1)(1.0);
27 ---
28 (note that  the constructor  used here  has the  package access  protection: new
29 units should be defined with the unit template of this module).
30 
31 Any quantity can be expressed as the product  of a number ($(I n)) and a unit of
32 the right dimensions ($(I U)). For instance:
33 ---
34 auto size = 9.5 * meter;
35 auto time = 120 * milli(second);
36 ---
37 The unit  $(I U)  is not  actually stored along  with the  number in  a Quantity
38 struct,  only the  dimensions are.  This  is because  the same  quantity can  be
39 expressed in an  infinity of different units.  The value of $(I n)  is stored as
40 if the quantity was  expressed in the base units of the  same dimemsions. In the
41 example above,  $(I n) = 9.5  for the variable size  and $(I n) =  0.120 for the
42 variable time.
43 
44 The  value method  can  be used  to  extract the  number  $(I n)  as  if it  was
45 expressed in  any possible  unit. The user  must pass this  unit to  the method.
46 This way, the user makes it clear in which unit the value was expressed.
47 ---
48 auto size = 9.5 * meter;
49 auto valueMeter      = size.value(meter);        // valueMeter == 9.5
50 auto valueCentimeter = size.value(centi(meter)); // valueCentimeter == 950
51 ---
52 Arithmetic operators (+ - * /),  as well as assignment and comparison operators,
53 are  defined when  the  operations are  dimensionally  consistent, otherwise  an
54 error occurs at compile-time:
55 ---
56 auto time = 2 * hour + 17 * minute;
57 auto frequency = time / second;
58 time = time + 2 * meter; // Compilation error
59 ---
60 Any kind  of quantities  and units  can be  defined with  this module,  not just
61 those  from the  SI. The  SI quantities  and units  are in  fact defined  in the
62 module quantities.si.  When a quantity  that is not  predefined has to  be used,
63 instead of instantiating the Quantity template  first, it is preferable to start
64 defining a new base unit (with only  one dimension) using the unit template, and
65 then the quantity type with the typeof operator:
66 ---
67 enum euro = unit!"C"; // C for currency
68 alias Currency = typeof(euro);
69 assert(is(Currency == Quantity!(real, "C", 1)));
70 ---
71 This means that all currencies will be defined with respect to euro.
72 
73 Copyright: Copyright 2013-2014, Nicolas Sicard
74 Authors: Nicolas Sicard
75 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
76 Source: $(LINK https://github.com/biozic/quantities)
77 +/
78 module quantities.base;
79 
80 import std.exception;
81 import std.string;
82 import std.traits;
83 import std.typetuple;
84 
85 version (unittest)
86 {
87     import quantities.math;
88     import quantities.si;
89     import std.math : approxEqual;
90 }
91 
92 template isNumberLike(N)
93 {
94     N n1;
95     N n2;
96     enum isNumberLike = !isQuantity!N
97         && __traits(compiles, { return -n1 + (+n2); })
98         && __traits(compiles, { return n1 + n2; })
99         && __traits(compiles, { return n1 - n2; })
100         && __traits(compiles, { return n1 * n2; })
101         && __traits(compiles, { return n1 / n2; })
102         && (__traits(compiles, { n1 = 1; }) || __traits(compiles, { n1 = N(1); }))
103         && __traits(compiles, { return cast(const) n1 + n2; } );
104 }
105 unittest
106 {
107     static assert(isNumberLike!real);
108     static assert(isNumberLike!int);
109     static assert(!isNumberLike!string);
110 
111     import std.bigint, std.typecons;
112     static assert(isNumberLike!BigInt);
113     static assert(isNumberLike!(RefCounted!real));
114 }
115 
116 template OperatorResultType(T, string op, U)
117 {
118     T t;
119     U u;
120     alias OperatorResultType = typeof(mixin("t" ~ op ~ "u"));
121 }
122 unittest
123 {
124     static assert(is(OperatorResultType!(real, "+", int) == real));
125     static assert(is(OperatorResultType!(int, "*", int) == int));
126 }
127 
128 /++
129 A quantity that can be expressed as the product of a number and a set of dimensions.
130 +/
131 struct Quantity(N, Dim...)
132 {
133     static assert(isNumberLike!N, "Incompatible type: " ~ N.stringof);
134     static assert(Is!Dim.equalTo!(Sort!Dim), "Dimensions are not sorted correctly: "
135                   ~"the right type is " ~ Quantity!(N, Sort!Dim).stringof);
136       
137     /// The type of the underlying numeric value.
138     alias valueType = N;
139     ///
140     unittest
141     {
142         static assert(is(meter.valueType == real));
143     }
144 
145     // The payload
146     private N _value;
147 
148     /// The dimension tuple of the quantity.
149     alias dimensions = Dim;
150 
151     template checkDim(string dim)
152     {
153         enum checkDim =
154             `static assert(Is!(` ~ dim ~ `).equivalentTo!dimensions,
155                 "Dimension error: [%s] is not compatible with [%s]"
156                 .format(dimstr!(` ~ dim ~ `), dimstr!dimensions));`;
157     }
158 
159     template checkValueType(string type)
160     {
161         enum checkValueType =
162             `static assert(is(` ~ type ~ ` : N),
163                 "%s is not implicitly convertible to %s"
164                 .format(` ~ type ~ `.stringof, N.stringof));`;
165     }
166 
167     /// Gets the base unit of this quantity.
168     static @property Quantity baseUnit()
169     {
170         static if (isNumeric!N)
171             return Quantity.make(1);
172         else static if (__traits(compiles, N(1)))
173             return Quantity.make(N(1));
174         else
175             static assert(false, "BUG");
176     }
177 
178     // Creates a new quantity from another one with the same dimensions
179     this(Q)(Q other)
180         if (isQuantity!Q)
181     {
182         mixin(checkDim!"other.dimensions");
183         mixin(checkValueType!"Q.valueType");
184         _value = other._value;
185     }
186 
187     // Creates a new dimensionless quantity from a number
188     this(T)(T value)
189         if (!isQuantity!T && Dim.length == 0)
190     {
191         mixin(checkValueType!"T");
192         _value = value;
193     }
194 
195     // Should be a constructor
196     // Workaround for @@BUG 5770@@
197     // (https://d.puremagic.com/issues/show_bug.cgi?id=5770)
198     // "Template constructor bypass access check"
199     package static Quantity make(T)(T value)
200         if (!isQuantity!T)
201     {
202         mixin(checkValueType!"T");
203         Quantity ret;
204         ret._value = value;
205         return ret;
206     }
207 
208     // Gets the internal number of this quantity.
209     @property N rawValue() const
210     {
211         return _value;
212     }
213     // Implicitly convert a dimensionless value to the value type
214     static if (!Dim.length)
215         alias rawValue this;
216 
217     /++
218     Gets the _value of this quantity expressed in the given target unit.
219     +/
220     N value(Q)(Q target) const
221         if (isQuantity!Q)
222     {
223         mixin(checkDim!"target.dimensions");
224         mixin(checkValueType!"Q.valueType");
225         return _value / target._value;
226     }
227     ///
228     unittest
229     {
230         auto time = 120 * minute;
231         assert(time.value(hour) == 2);
232         assert(time.value(minute) == 120);
233     }
234 
235     /++
236     Tests wheter this quantity has the same dimensions as another one.
237     +/
238     bool isConsistentWith(Q)(Q other) const
239         if (isQuantity!Q)
240     {
241         return AreConsistent!(Quantity, Q);
242     }
243     ///
244     unittest
245     {
246         auto nm = (1.4 * newton) * (0.5 * centi(meter));
247         auto kWh = (4000 * kilo(watt)) * (1200 * hour);
248         assert(nm.isConsistentWith(kWh)); // Energy in both cases
249         assert(!nm.isConsistentWith(second));
250     }
251 
252     /// Cast a quantity to another quantity type with the same dimensions
253     Q opCast(Q)() const
254         if (isQuantity!Q)
255     {
256         mixin(checkDim!"Q.dimensions");
257         mixin(checkValueType!"Q.valueType");
258         return store!(Q.valueType);
259     }
260 
261     /// Cast a dimensionless quantity to a numeric type
262     T opCast(T)() const
263         if (!isQuantity!T)
264     {
265         mixin(checkDim!"");
266         mixin(checkValueType!"T");
267         return _value;
268     }
269     ///
270     unittest
271     {
272         auto proportion = 12 * gram / (4.5 * kilogram);
273         static assert(is(typeof(proportion) == Dimensionless));
274         auto prop = cast(real) proportion;
275 
276         static assert(!__traits(compiles, cast(real) meter));
277     }
278 
279     /// Overloaded operators.
280     /// Only dimensionally correct operations will compile.
281 
282     // Assign from another quantity
283     void opAssign(Q)(Q other)
284         if (isQuantity!Q)
285     {
286         mixin(checkDim!"other.dimensions");
287         mixin(checkValueType!"Q.valueType");
288         _value = other._value;
289     }
290 
291     // Assign from a numeric value if this quantity is dimensionless
292     void opAssign(T)(T other) /// ditto
293         if (!isQuantity!T)
294     {
295         mixin(checkDim!"");
296         mixin(checkValueType!"T");
297         _value = other;
298     }
299 
300     // Unary + and -
301     auto opUnary(string op)() const /// ditto
302         if (op == "+" || op == "-")
303     {
304         return Quantity!(N, dimensions).make(mixin(op ~ "_value"));
305     }
306 
307     // Add (or substract) two quantities if they share the same dimensions
308     auto opBinary(string op, Q)(Q other) const /// ditto
309         if (isQuantity!Q && (op == "+" || op == "-"))
310     {
311         mixin(checkDim!"other.dimensions");
312         mixin(checkValueType!"Q.valueType");
313         return Quantity!(OperatorResultType!(N, "+", Q.valueType), dimensions)
314             .make(mixin("_value" ~ op ~ "other._value"));
315     }
316 
317     // Add (or substract) a dimensionless quantity and a number
318     auto opBinary(string op, T)(T other) const /// ditto
319         if (!isQuantity!T && (op == "+" || op == "-"))
320     {
321         mixin(checkDim!"");
322         mixin(checkValueType!"T");
323         return Quantity!(OperatorResultType(N, "+", T), dimensions)
324             .make(mixin("_value" ~ op ~ "other"));
325     }
326 
327     // ditto
328     auto opBinaryRight(string op, T)(T other) const /// ditto
329         if (!isQuantity!T && (op == "+" || op == "-"))
330     {
331         return opBinary!op(other);
332     }
333 
334     // Multiply or divide two quantities
335     auto opBinary(string op, Q)(Q other) const /// ditto
336         if (isQuantity!Q && (op == "*" || op == "/" || op == "%"))
337     {
338         mixin(checkValueType!"Q.valueType");
339         return Quantity!(OperatorResultType!(N, "*", Q.valueType),
340                          OpBinary!(dimensions, op, other.dimensions))
341             .make(mixin("(_value" ~ op ~ "other._value)"));
342     }
343 
344     // Multiply or divide a quantity by a number
345     auto opBinary(string op, T)(T other) const /// ditto
346         if (!isQuantity!T && (op == "*" || op == "/" || op == "%"))
347     {
348         mixin(checkValueType!"T");
349         return Quantity!(OperatorResultType!(N, "*", T), dimensions)
350             .make(mixin("_value" ~ op ~ "other"));
351     }
352 
353     // ditto
354     auto opBinaryRight(string op, T)(T other) const /// ditto
355         if (!isQuantity!T && op == "*")
356     {
357         mixin(checkValueType!"T");
358         return this * other;
359     }
360 
361     // ditto
362     auto opBinaryRight(string op, T)(T other) const /// ditto
363         if (!isQuantity!T && (op == "/" || op == "%"))
364     {
365         mixin(checkValueType!"T");
366         return Quantity!(OperatorResultType!(T, "/", N), Invert!dimensions)
367             .make(mixin("other" ~ op ~ "_value"));
368     }
369 
370     auto opBinary(string op, T)(T power) const
371         if (op == "^^")
372     {
373         static assert(false, "Unsupporter operator: ^^");
374     }
375 
376     // Add/sub assign with a quantity that shares the same dimensions
377     void opOpAssign(string op, Q)(Q other) /// ditto
378         if (isQuantity!Q && (op == "+" || op == "-"))
379     {
380         mixin(checkDim!"other.dimensions");
381         mixin(checkValueType!"Q.valueType");
382         mixin("_value " ~ op ~ "= other._value;");
383     }
384 
385     // Add/sub assign a number to a dimensionless quantity
386     void opOpAssign(string op, T)(T other) /// ditto
387         if (!isQuantity!T && (op == "+" || op == "-"))
388     {
389         mixin(checkDim!"");
390         mixin(checkValueType!"T");
391         mixin("_value " ~ op ~ "= other;");
392     }
393 
394     // Mul/div assign with a dimensionless quantity
395     void opOpAssign(string op, Q)(Q other) /// ditto
396         if (isQuantity!Q && (op == "*" || op == "/" || op == "%"))
397     {
398         mixin(checkDim!"");
399         mixin(checkValueType!"Q.valueType");
400         mixin("_value" ~ op ~ "= other._value;");
401     }
402 
403     // Mul/div assign with a number
404     void opOpAssign(string op, T)(T other) /// ditto
405         if (!isQuantity!T && (op == "*" || op == "/" || op == "%"))
406     {
407         mixin(checkValueType!"T");
408         mixin("_value" ~ op ~ "= other;");
409     }
410 
411     // Exact equality between quantities
412     bool opEquals(Q)(Q other) const /// ditto
413         if (isQuantity!Q)
414     {
415         mixin(checkDim!"other.dimensions");
416         return _value == other._value;
417     }
418 
419     // Exact equality between a dimensionless quantity and a number
420     bool opEquals(T)(T other) const /// ditto
421         if (!isQuantity!T)
422     {
423         mixin(checkDim!"");
424         return _value == other;
425     }
426 
427     // Comparison between two quantities
428     int opCmp(Q)(Q other) const /// ditto
429         if (isQuantity!Q)
430     {
431         mixin(checkDim!"other.dimensions");
432         if (_value == other._value)
433             return 0;
434         if (_value < other._value)
435             return -1;
436         return 1;
437     }
438 
439     // Comparison between a dimensionless quantity and a number
440     int opCmp(T)(T other) const /// ditto
441         if (!isQuantity!T)
442     {
443         mixin(checkDim!"");
444         if (_value == other)
445             return 0;
446         if (_value < other)
447             return -1;
448         return 1;
449     }
450 
451     /++
452     Returns the default string representation of the quantity.
453 
454     By default, a quantity is represented as a string by a number
455     followed by the set of dimensions between brackets.
456     +/
457     string toString() const
458     {
459         return "%s [%s]".format(_value, dimstr!dimensions);
460     }
461     ///
462     unittest
463     {
464         enum inch = 2.54 * centi(meter);
465         assert(inch.toString == "0.0254 [L]", inch.toString);
466     }
467 }
468 
469 unittest // Quantity.baseUnit
470 {
471     static assert(minute.baseUnit == second);
472 }
473 
474 unittest // Quantity constructor
475 {
476     enum time = typeof(second)(1 * minute);
477     assert(time.value(second) == 60);
478 }
479 
480 unittest // Quantity.value
481 {
482     enum speed = 100 * meter / (5 * second);
483     static assert(speed.value(meter / second) == 20);
484 }
485 
486 unittest // Quantity.opCast
487 {
488     enum angle = 12 * radian;
489     static assert(cast(double) angle == 12);
490 }
491 
492 unittest // Quantity.opAssign Q = Q
493 {
494     auto length = meter;
495     length = 2.54 * centi(meter);
496     assert(length.value(meter).approxEqual(0.0254));
497 }
498 
499 unittest // Quantity.opUnary +Q -Q
500 {
501     enum length = + meter;
502     static assert(length == 1 * meter);
503     enum length2 = - meter;
504     static assert(length2 == -1 * meter);
505 }
506 
507 unittest // Quantity.opBinary Q*N Q/N
508 {
509     enum time = second * 60;
510     static assert(time.value(second) == 60);
511     enum time2 = second / 2;
512     static assert(time2.value(second) == 1.0/2);
513 }
514 
515 unittest // Quantity.opBinary Q+Q Q-Q
516 {
517     enum length = meter + meter;
518     static assert(length.value(meter) == 2);
519     enum length2 = length - meter;
520     static assert(length2.value(meter) == 1);
521 }
522 
523 unittest // Quantity.opBinary Q*Q Q/Q
524 {
525     enum length = meter * 5;
526     enum surface = length * length;
527     static assert(surface.value(square(meter)) == 5*5);
528     enum length2 = surface / length;
529     static assert(length2.value(meter) == 5);
530 
531     enum x = minute / second;
532     static assert(x.rawValue == 60);
533 
534     enum y = minute * hertz;
535     static assert(y.rawValue == 60);
536 }
537 
538 unittest // Quantity.opBinaryRight N*Q
539 {
540     enum length = 100 * meter;
541     static assert(length == meter * 100);
542 }
543 
544 unittest // Quantity.opBinaryRight N/Q
545 {
546     enum x = 1 / (2 * meter);
547     static assert(x.value(1/meter) == 1.0/2);
548 }
549 
550 unittest // Quantity.opBinary Q%Q Q%N N%Q
551 {
552     enum x = 258.1 * meter;
553     enum y1 = x % (5 * deca(meter));
554     static assert((cast(real) y1).approxEqual(8.1));
555     enum y2 = x % 50;
556     static assert(y2.value(meter).approxEqual(8.1));
557 }
558 
559 unittest // Quantity.opOpAssign Q+=Q Q-=Q
560 {
561     auto time = 10 * second;
562     time += 50 * second;
563     assert(time.value(second).approxEqual(60));
564     time -= 40 * second;
565     assert(time.value(second).approxEqual(20));
566 }
567 
568 unittest // Quantity.opOpAssign Q*=N Q/=N Q%=N
569 {
570     auto time = 20 * second;
571     time *= 2;
572     assert(time.value(second).approxEqual(40));
573     time /= 4;
574     assert(time.value(second).approxEqual(10));
575     time %= 3;
576     assert(time.value(second).approxEqual(1));
577 }
578 
579 unittest // Quantity.opEquals
580 {
581     static assert(1 * minute == 60 * second);
582     static assert((1 / second) * meter == meter / second);
583 }
584 
585 unittest // Quantity.opCmp
586 {
587     static assert(second < minute);
588     static assert(minute <= minute);
589     static assert(hour > minute);
590     static assert(hour >= hour);
591 }
592 
593 unittest // Compilation errors for incompatible dimensions
594 {
595     Length m;
596     static assert(!__traits(compiles, m.value(second)));
597     static assert(!__traits(compiles, m = second));
598     static assert(!__traits(compiles, m + second));
599     static assert(!__traits(compiles, m - second));
600     static assert(!__traits(compiles, m + 1));
601     static assert(!__traits(compiles, m - 1));
602     static assert(!__traits(compiles, 1 + m));
603     static assert(!__traits(compiles, 1 - m));
604     static assert(!__traits(compiles, m += second));
605     static assert(!__traits(compiles, m -= second));
606     static assert(!__traits(compiles, m *= second));
607     static assert(!__traits(compiles, m /= second));
608     static assert(!__traits(compiles, m *= meter));
609     static assert(!__traits(compiles, m /= meter));
610     static assert(!__traits(compiles, m += 1));
611     static assert(!__traits(compiles, m -= 1));
612     static assert(!__traits(compiles, m == 1));
613     static assert(!__traits(compiles, m == second));
614     static assert(!__traits(compiles, m < second));
615     static assert(!__traits(compiles, m < 1));
616 }
617 
618 unittest // immutable Quantity
619 {
620     immutable length = 3e5 * kilo(meter);
621     immutable time = 1 * second;
622     immutable speedOfLight = length / time;
623     assert(speedOfLight == 3e5 * kilo(meter) / second);
624     assert(speedOfLight > 1 * meter / minute);
625 }
626 
627 /// Tests whether T is a quantity type
628 template isQuantity(T)
629 {
630     alias U = Unqual!T;
631     static if (is(U == Quantity!X, X...))
632         enum isQuantity = true;
633     else
634         enum isQuantity = false;
635 }
636 ///
637 unittest
638 {
639     static assert(isQuantity!Time);
640     static assert(isQuantity!(typeof(meter)));
641     static assert(!isQuantity!real);
642 }
643 
644 
645 /// Creates a new monodimensional unit.
646 template unit(string symbol, N = real)
647 {
648     static assert(isNumberLike!N, "Incompatible type: " ~ N.stringof);
649     static if (isNumeric!N)
650         enum unit = Quantity!(N, symbol, 1).make(1);
651     else static if (__traits(compiles, N(1)))
652         enum unit = Quantity!(N, symbol, 1).make(N(1));
653     else
654         static assert(false, "BUG");
655 }
656 ///
657 unittest
658 {
659     enum euro = unit!"C"; // C for Currency
660     static assert(isQuantity!(typeof(euro)));
661     enum dollar = euro / 1.35;
662     assert((1.35 * dollar).value(euro).approxEqual(1));
663 }
664 
665 
666 /// Creates a new quantity type where the payload is stored as another numeric type.
667 template Store(Q, N)
668     if (isQuantity!Q && isNumberLike!N)
669 {
670     alias Store = Quantity!(N, Q.dimensions);
671 }
672 ///
673 unittest
674 {
675     alias TimeF = Store!(Time, float);
676 }
677 
678 
679 /++
680 Returns a new quantity where the value is stored in a field of type T.
681 
682 By default, the value is converted to type T using a cast.
683 +/
684 auto store(T, Q)(Q quantity, T delegate(Q.valueType) convertDelegate = x => cast(T) x)
685     if (!isQuantity!T && isQuantity!Q)
686 {
687     static if (is(Q.ValueType : T))
688         return Quantity!(T, Q.dimensions).make(quantity._value);
689     else
690     {
691         if (convertDelegate)
692             return Quantity!(T, Q.dimensions).make(convertDelegate(quantity._value));
693         else
694             assert(false, "%s is not implicitly convertible to %s: provide a conversion delegate)"
695                    .format(Q.valueType.stringof, T.stringof));
696     }
697 }
698 ///
699 unittest
700 {
701     auto sizeF = meter.store!float;
702     static assert(is(sizeF.valueType == float));
703     auto sizeI = meter.store!ulong;
704     static assert(is(sizeI.valueType == ulong));
705 }
706 
707 
708 /// Check that two quantity types are dimensionally consistent.
709 template AreConsistent(Q1, Q2)
710     if (isQuantity!Q1 && isQuantity!Q2)
711 {
712     enum AreConsistent = Is!(Q1.dimensions).equivalentTo!(Q2.dimensions);
713 }
714 ///
715 unittest
716 {
717     alias Speed = typeof(meter/second);
718     alias Velocity = typeof((1/second * meter));
719     static assert(AreConsistent!(Speed, Velocity));
720 }
721 
722 
723 /++
724 Creates a new prefix function that mutlpy a Quantity by _factor factor.
725 +/
726 template prefix(alias factor)
727 {
728     alias N = typeof(factor);
729     static assert(isNumberLike!N, "Incompatible type: " ~ N.stringof);
730 
731     auto prefix(Q)(Q base)
732         if (isQuantity!Q)
733     {
734         return base * factor;
735     }
736 }
737 ///
738 unittest
739 {
740     alias milli = prefix!1e-3;
741     assert(milli(meter).value(meter).approxEqual(1e-3));
742 }
743 
744 
745 /// Exception thrown when operating on two units that are not interconvertible.
746 class DimensionException : Exception
747 {
748     @safe pure nothrow
749     this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
750     {
751         super(msg, file, line, next);
752     }
753 
754     @safe pure nothrow
755     this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__)
756     {
757         super(msg, file, line, next);
758     }
759 }
760 
761 package:
762 
763 // Inspired from std.typetuple.Pack
764 template Is(T...)
765 {
766     static assert(T.length % 2 == 0);
767 
768     template equalTo(U...)
769     {
770         static if (T.length == U.length)
771         {
772             static if (T.length == 0)
773                 enum equalTo = true;
774             else
775                 enum equalTo = IsDim!(T[0..2]).equalTo!(U[0..2]) && Is!(T[2..$]).equalTo!(U[2..$]);
776         }
777         else
778             enum equalTo = false;
779     }
780 
781     template equivalentTo(U...)
782     {
783         alias equivalentTo = Is!(Sort!T).equalTo!(Sort!U);
784     }
785 }
786 unittest
787 {
788     alias T = TypeTuple!("a", 1, "b", -1);
789     alias U = TypeTuple!("a", 1, "b", -1);
790     alias V = TypeTuple!("b", -1, "a", 1);
791     static assert(Is!T.equalTo!U);
792     static assert(!Is!T.equalTo!V);
793     static assert(Is!T.equivalentTo!V);
794 }
795 
796 template IsDim(string d1, int p1)
797 {
798     template equalTo(string d2, int p2)
799     {
800         enum equalTo = (d1 == d2 && p1 == p2);
801     }
802 
803     template dimEqualTo(string d2, int p2)
804     {
805         enum dimEqualTo = (d1 == d2);
806     }
807 
808     template dimLessOrEqual(string d2, int p2)
809     {
810         alias siInOrder = TypeTuple!("L", "M", "T", "I", "Θ", "N", "J");
811         enum id1 = staticIndexOf!(d1, siInOrder);
812         enum id2 = staticIndexOf!(d2, siInOrder);
813 
814         static if (id1 >= 0 && id2 >= 0) // both SI
815             enum dimLessOrEqual = id1 < id2;
816         else static if (id1 >= 0 && id2 == -1) // SI before non-SI
817             enum dimLessOrEqual = true;
818         else static if (id1 == -1 && id2 >= 0) // non-SI after SI
819             enum dimLessOrEqual = false;
820         else
821             enum dimLessOrEqual = d1 <= d2; // Usual comparison
822     }
823 
824     template powEqualTo(string d2, int p2)
825     {
826         enum powEqualTo = (p1 == p2);
827     }
828 }
829 unittest
830 {
831     static assert(IsDim!("a", 0).equalTo!("a", 0));
832     static assert(!IsDim!("a", 0).equalTo!("a", 1));
833     static assert(!IsDim!("a", 0).equalTo!("b", 0));
834     static assert(!IsDim!("a", 0).equalTo!("b", 1));
835 
836     static assert(IsDim!("a", 0).dimEqualTo!("a", 1));
837     static assert(!IsDim!("a", 0).dimEqualTo!("b", 1));
838 
839     static assert(IsDim!("a", 0).powEqualTo!("b", 0));
840     static assert(!IsDim!("a", 0).powEqualTo!("b", 1));
841 
842     static assert(IsDim!("L", 0).dimLessOrEqual!("M", 0));
843     static assert(!IsDim!("M", 0).dimLessOrEqual!("L", 1));
844     static assert(IsDim!("L", 0).dimLessOrEqual!("U", 0));
845     static assert(!IsDim!("U", 0).dimLessOrEqual!("M", 0));
846     static assert(IsDim!("U", 0).dimLessOrEqual!("V", 0));
847 }
848 
849 template FilterPred(alias pred, Dim...)
850 {
851     static assert(Dim.length % 2 == 0);
852 
853     static if (Dim.length == 0)
854         alias FilterPred = Dim;
855     else static if (pred!(Dim[0], Dim[1]))
856         alias FilterPred = TypeTuple!(Dim[0], Dim[1], FilterPred!(pred, Dim[2 .. $]));
857     else
858         alias FilterPred = FilterPred!(pred, Dim[2 .. $]);
859 }
860 
861 template RemoveNull(Dim...)
862 {
863     alias RemoveNull = FilterPred!(templateNot!(IsDim!("_", 0).powEqualTo), Dim);
864 }
865 unittest
866 {
867     alias T = TypeTuple!("a", 1, "b", 0, "c", -1);
868     static assert(Is!(RemoveNull!T).equalTo!("a", 1, "c", -1));
869 }
870 
871 template Filter(string s, Dim...)
872 {
873     alias Filter = FilterPred!(IsDim!(s, 0).dimEqualTo, Dim);
874 }
875 unittest
876 {
877     alias T = TypeTuple!("a", 1, "b", 0, "a", -1, "c", 2);
878     static assert(Is!(Filter!("a", T)).equalTo!("a", 1, "a", -1));
879 }
880 
881 template FilterOut(string s, Dim...)
882 {
883     alias FilterOut = FilterPred!(templateNot!(IsDim!(s, 0).dimEqualTo), Dim);
884 }
885 unittest
886 {
887     alias T = TypeTuple!("a", 1, "b", 0, "a", -1, "c", 2);
888     static assert(Is!(FilterOut!("a", T)).equalTo!("b", 0, "c", 2));
889 }
890 
891 template Reduce(int seed, Dim...)
892 {
893     static assert(Dim.length >= 2);
894     static assert(Dim.length % 2 == 0);
895 
896     static if (Dim.length == 2)
897         alias Reduce = TypeTuple!(Dim[0], seed + Dim[1]);
898     else
899         alias Reduce = Reduce!(seed + Dim[1], Dim[2 .. $]);
900 }
901 unittest
902 {
903     alias T = TypeTuple!("a", 1, "a", 0, "a", -1, "a", 2);
904     static assert(Is!(Reduce!(0, T)).equalTo!("a", 2));
905     alias U = TypeTuple!("a", 1, "a", -1);
906     static assert(Is!(Reduce!(0, U)).equalTo!("a", 0));
907 }
908 
909 template Simplify(Dim...)
910 {
911     static assert(Dim.length % 2 == 0);
912 
913     static if (Dim.length == 0)
914         alias Simplify = Dim;
915     else
916     {
917         alias head = Dim[0 .. 2];
918         alias tail = Dim[2 .. $];
919         alias hret = Reduce!(0, head, Filter!(Dim[0], tail));
920         alias tret = FilterOut!(Dim[0], tail);
921         alias Simplify = TypeTuple!(hret, Simplify!tret);
922     }
923 }
924 unittest
925 {
926     alias T = TypeTuple!("a", 1, "b", 2, "a", -1, "b", 1, "c", 4);
927     static assert(Is!(Simplify!T).equalTo!("a", 0, "b", 3, "c", 4));
928 }
929 
930 template Sort(Dim...)
931 {
932     static assert(Dim.length % 2 == 0);
933 
934     static if (Dim.length <= 2)
935         alias Sort = Dim;
936     else
937     {
938         enum i = (Dim.length / 4) * 2; // Pivot index
939         alias list = TypeTuple!(Dim[0..i], Dim[i+2..$]);
940         alias less = FilterPred!(templateNot!(IsDim!(Dim[i], 0).dimLessOrEqual), list);
941         alias greater = FilterPred!(IsDim!(Dim[i], 0).dimLessOrEqual, list);
942         alias Sort = TypeTuple!(Sort!less, Dim[i], Dim[i+1], Sort!greater);
943     }
944 }
945 unittest
946 {
947     alias T = TypeTuple!("d", -1, "c", 2, "a", 4, "e", 0, "b", -3);
948     static assert(Is!(Sort!T).equalTo!("a", 4, "b", -3, "c", 2, "d", -1, "e", 0));
949 }
950 
951 template OpBinary(Dim...)
952 {
953     static assert(Dim.length % 2 == 1);
954 
955     static if (staticIndexOf!("/", Dim) >= 0)
956     {
957         // Division or modulo
958         enum op = staticIndexOf!("/", Dim);
959         alias numerator = Dim[0 .. op];
960         alias denominator = Dim[op+1 .. $];
961         alias OpBinary = Sort!(RemoveNull!(Simplify!(TypeTuple!(numerator, Invert!(denominator)))));
962     }
963     else static if (staticIndexOf!("%", Dim) >= 0)
964     {
965         // Modulo
966         enum op = staticIndexOf!("%", Dim);
967         alias numerator = Dim[0 .. op];
968         alias denominator = Dim[op+1 .. $];
969         alias OpBinary = Sort!(RemoveNull!(Simplify!(TypeTuple!(numerator, Invert!(denominator)))));
970     }
971     else static if (staticIndexOf!("*", Dim) >= 0)
972     {
973         // Multiplication
974         enum op = staticIndexOf!("*", Dim);
975         alias OpBinary = Sort!(RemoveNull!(Simplify!(TypeTuple!(Dim[0 .. op], Dim[op+1 .. $]))));
976     }
977     else
978         static assert(false, "No valid operator");
979 }
980 unittest
981 {
982     alias T = TypeTuple!("a", 1, "b", 2, "c", -1);
983     alias U = TypeTuple!("a", 1, "b", -2, "c", 2);
984     static assert(Is!(OpBinary!(T, "*", U)).equalTo!("a", 2, "c", 1));
985     static assert(Is!(OpBinary!(T, "/", U)).equalTo!("b", 4, "c", -3));
986 }
987 
988 template Invert(Dim...)
989 {
990     static assert(Dim.length % 2 == 0);
991 
992     static if (Dim.length == 0)
993         alias Invert = Dim;
994     else
995         alias Invert = TypeTuple!(Dim[0], -Dim[1], Invert!(Dim[2 .. $]));
996 }
997 unittest
998 {
999     alias T = TypeTuple!("a", 1, "b", -1);
1000     static assert(Is!(Invert!T).equalTo!("a", -1, "b", 1));
1001 }
1002 
1003 template Pow(int n, Dim...)
1004 {
1005     static assert(Dim.length % 2 == 0);
1006 
1007     static if (Dim.length == 0)
1008         alias Pow = Dim;
1009     else
1010         alias Pow = TypeTuple!(Dim[0], Dim[1] * n, Pow!(n, Dim[2 .. $]));
1011 }
1012 unittest
1013 {
1014     alias T = TypeTuple!("a", 1, "b", -1);
1015     static assert(Is!(Pow!(2, T)).equalTo!("a", 2, "b", -2));
1016 }
1017 
1018 template PowInverse(int n, Dim...)
1019 {
1020     static assert(Dim.length % 2 == 0);
1021 
1022     static if (Dim.length == 0)
1023         alias PowInverse = Dim;
1024     else
1025     {
1026         static assert(Dim[1] % n == 0, "Dimension error: '%s^%s' is not divisible by %s"
1027                                        .format(Dim[0], Dim[1], n));
1028         alias PowInverse = TypeTuple!(Dim[0], Dim[1] / n, PowInverse!(n, Dim[2 .. $]));
1029     }
1030 }
1031 unittest
1032 {
1033     alias T = TypeTuple!("a", 4, "b", -2);
1034     static assert(Is!(PowInverse!(2, T)).equalTo!("a", 2, "b", -1));
1035 }
1036 
1037 int[string] toAA(Dim...)()
1038 {
1039     static if (Dim.length == 0)
1040         return null;
1041     else
1042     {
1043         static assert(Dim.length % 2 == 0);
1044         int[string] ret;
1045         string sym;
1046         foreach (i, d; Dim)
1047         {
1048             static if (i % 2 == 0)
1049                 sym = d;
1050             else
1051                 ret[sym] = d;
1052         }
1053         return ret;
1054     }
1055 }
1056 unittest
1057 {
1058     alias T = TypeTuple!("a", 1, "b", -1);
1059     static assert(toAA!T == ["a":1, "b":-1]);
1060 }
1061 
1062 string dimstr(Dim...)()
1063 {
1064     import std.algorithm : filter;
1065     import std.array : join;
1066     import std.conv : to;
1067 
1068     static string stringize(string base, int power)
1069     {
1070         if (power == 0)
1071             return null;
1072         if (power == 1)
1073             return base;
1074         return base ~ "^" ~ to!string(power);
1075     }
1076 
1077     string[] dimstrs;
1078     string sym;
1079     foreach (i, d; Dim)
1080     {
1081         static if (i % 2 == 0)
1082             sym = d;
1083         else
1084             dimstrs ~= stringize(sym, d);
1085     }
1086     return format("%-(%s %)", dimstrs);
1087 }