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