1 /++
2 This module defines dimensionally variant quantities, for use mainly at run time.
3 
4 Copyright: Copyright 2013-2018, Nicolas Sicard
5 Authors: Nicolas Sicard
6 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 Source: $(LINK https://github.com/biozic/quantities)
8 +/
9 module quantities.runtime.qvariant;
10 
11 import quantities.compiletime.quantity : isQuantity;
12 import quantities.internal.dimensions;
13 
14 import std.conv;
15 import std.exception;
16 import std.format;
17 import std.math;
18 import std.string;
19 import std.traits;
20 
21 /++
22 Exception thrown when operating on two units that are not interconvertible.
23 +/
24 class DimensionException : Exception
25 {
26     /// Holds the dimensions of the quantity currently operated on
27     Dimensions thisDim;
28     /// Holds the dimensions of the eventual other operand
29     Dimensions otherDim;
30 
31     mixin basicExceptionCtors;
32 
33     this(string msg, Dimensions thisDim, Dimensions otherDim,
34             string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow
35     {
36         super(msg, file, line, next);
37         this.thisDim = thisDim;
38         this.otherDim = otherDim;
39     }
40 }
41 ///
42 unittest
43 {
44     import std.exception : assertThrown;
45 
46     enum meter = unit!double("L");
47     enum second = unit!double("T");
48 
49 }
50 
51 /++
52 A dimensionnaly variant quantity.
53 
54 Params:
55     N = the numeric type of the quantity.
56 
57 See_Also:
58     QVariant has the same public members and overloaded operators as Quantity.
59 +/
60 struct QVariant(N)
61 {
62     static assert(isNumeric!N, "Incompatible type: " ~ N.stringof);
63 
64 private:
65     N _value;
66     Dimensions _dimensions;
67 
68     void checkDim(Dimensions dim) @safe pure const
69     {
70         enforce(_dimensions == dim,
71                 new DimensionException("Incompatible dimensions", _dimensions, dim));
72     }
73 
74     void checkDimensionless() @safe pure const
75     {
76         enforce(_dimensions.empty, new DimensionException("Not dimensionless",
77                 _dimensions, Dimensions.init));
78     }
79 
80 package(quantities):
81     alias valueType = N;
82 
83     N rawValue() const
84     {
85         return _value;
86     }
87 
88 public:
89     // Creates a new quantity with non-empty dimensions
90     this(T)(T scalar, const Dimensions dim)
91             if (isNumeric!T)
92     {
93         _value = scalar;
94         _dimensions = dim;
95     }
96 
97     /// Creates a new quantity from another one with the same dimensions
98     this(Q)(auto ref const Q qty)
99             if (isQVariant!Q)
100     {
101         _value = qty._value;
102         _dimensions = qty._dimensions;
103     }
104 
105     /// Ditto
106     this(Q)(auto ref const Q qty)
107             if (isQuantity!Q)
108     {
109         import quantities.compiletime.quantity : qVariant;
110 
111         this = qty.qVariant;
112     }
113 
114     /// Creates a new dimensionless quantity from a number
115     this(T)(T scalar)
116             if (isNumeric!T)
117     {
118         _dimensions = Dimensions.init;
119         _value = scalar;
120     }
121 
122     /// Returns the dimensions of the quantity
123     Dimensions dimensions() @property const
124     {
125         return _dimensions;
126     }
127 
128     /++
129     Implicitly convert a dimensionless value to the value type.
130 
131     Calling get will throw DimensionException if the quantity is not
132     dimensionless.
133     +/
134     N get() const
135     {
136         checkDimensionless;
137         return _value;
138     }
139 
140     alias get this;
141 
142     /++
143     Gets the _value of this quantity when expressed in the given target unit.
144     +/
145     N value(Q)(auto ref const Q target) const 
146             if (isQVariantOrQuantity!Q)
147     {
148         checkDim(target.dimensions);
149         return _value / target.rawValue;
150     }
151     ///
152     @safe pure unittest
153     {
154         auto minute = unit!int("T");
155         auto hour = 60 * minute;
156 
157         QVariant!int time = 120 * minute;
158         assert(time.value(hour) == 2);
159         assert(time.value(minute) == 120);
160     }
161 
162     /++
163     Test whether this quantity is dimensionless
164     +/
165     bool isDimensionless() @property const
166     {
167         return _dimensions.empty;
168     }
169 
170     /++
171     Tests wheter this quantity has the same dimensions as another one.
172     +/
173     bool isConsistentWith(Q)(auto ref const Q qty) const 
174             if (isQVariantOrQuantity!Q)
175     {
176         return _dimensions == qty.dimensions;
177     }
178     ///
179     @safe pure unittest
180     {
181         auto second = unit!double("T");
182         auto minute = 60 * second;
183         auto meter = unit!double("L");
184 
185         assert(minute.isConsistentWith(second));
186         assert(!meter.isConsistentWith(second));
187     }
188 
189     /++
190     Cast a dimensionless quantity to a numeric type.
191 
192     The cast operation will throw DimensionException if the quantity is not
193     dimensionless.
194     +/
195     T opCast(T)() const 
196             if (isNumeric!T)
197     {
198         checkDimensionless;
199         return _value;
200     }
201 
202     // Assign from another quantity
203     /// Operator overloading
204     ref QVariant opAssign(Q)(auto ref const Q qty)
205             if (isQVariantOrQuantity!Q)
206     {
207         _dimensions = qty.dimensions;
208         _value = qty.rawValue;
209         return this;
210     }
211 
212     // Assign from a numeric value if this quantity is dimensionless
213     /// ditto
214     ref QVariant opAssign(T)(T scalar)
215             if (isNumeric!T)
216     {
217         _dimensions = Dimensions.init;
218         _value = scalar;
219         return this;
220     }
221 
222     // Unary + and -
223     /// ditto
224     QVariant!N opUnary(string op)() const 
225             if (op == "+" || op == "-")
226     {
227         return QVariant(mixin(op ~ "_value"), _dimensions);
228     }
229 
230     // Unary ++ and --
231     /// ditto
232     QVariant!N opUnary(string op)()
233             if (op == "++" || op == "--")
234     {
235         mixin(op ~ "_value;");
236         return this;
237     }
238 
239     // Add (or substract) two quantities if they share the same dimensions
240     /// ditto
241     QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 
242             if (isQVariantOrQuantity!Q && (op == "+" || op == "-"))
243     {
244         checkDim(qty.dimensions);
245         return QVariant(mixin("_value" ~ op ~ "qty.rawValue"), _dimensions);
246     }
247 
248     /// ditto
249     QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 
250             if (isQVariantOrQuantity!Q && (op == "+" || op == "-"))
251     {
252         checkDim(qty.dimensions);
253         return QVariant(mixin("qty.rawValue" ~ op ~ "_value"), _dimensions);
254     }
255 
256     // Add (or substract) a dimensionless quantity and a number
257     /// ditto
258     QVariant!N opBinary(string op, T)(T scalar) const 
259             if (isNumeric!T && (op == "+" || op == "-"))
260     {
261         checkDimensionless;
262         return QVariant(mixin("_value" ~ op ~ "scalar"), _dimensions);
263     }
264 
265     /// ditto
266     QVariant!N opBinaryRight(string op, T)(T scalar) const 
267             if (isNumeric!T && (op == "+" || op == "-"))
268     {
269         checkDimensionless;
270         return QVariant(mixin("scalar" ~ op ~ "_value"), _dimensions);
271     }
272 
273     // Multiply or divide a quantity by a number
274     /// ditto
275     QVariant!N opBinary(string op, T)(T scalar) const 
276             if (isNumeric!T && (op == "*" || op == "/" || op == "%"))
277     {
278         return QVariant(mixin("_value" ~ op ~ "scalar"), _dimensions);
279     }
280 
281     /// ditto
282     QVariant!N opBinaryRight(string op, T)(T scalar) const 
283             if (isNumeric!T && op == "*")
284     {
285         return QVariant(mixin("scalar" ~ op ~ "_value"), _dimensions);
286     }
287 
288     /// ditto
289     QVariant!N opBinaryRight(string op, T)(T scalar) const 
290             if (isNumeric!T && (op == "/" || op == "%"))
291     {
292         return QVariant(mixin("scalar" ~ op ~ "_value"), ~_dimensions);
293     }
294 
295     // Multiply or divide two quantities
296     /// ditto
297     QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 
298             if (isQVariantOrQuantity!Q && (op == "*" || op == "/"))
299     {
300         return QVariant(mixin("(_value" ~ op ~ "qty.rawValue)"),
301                 mixin("_dimensions" ~ op ~ "qty.dimensions"));
302     }
303 
304     /// ditto
305     QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 
306             if (isQVariantOrQuantity!Q && (op == "*" || op == "/"))
307     {
308         return QVariant(mixin("(qty.rawValue" ~ op ~ "_value)"),
309                 mixin("qty.dimensions" ~ op ~ "_dimensions"));
310     }
311 
312     /// ditto
313     QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 
314             if (isQVariantOrQuantity!Q && (op == "%"))
315     {
316         checkDim(qty.dimensions);
317         return QVariant(_value % qty.rawValue, _dimensions);
318     }
319 
320     /// ditto
321     QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 
322             if (isQVariantOrQuantity!Q && (op == "%"))
323     {
324         checkDim(qty.dimensions);
325         return QVariant(qty.rawValue % _value, _dimensions);
326     }
327 
328     /// ditto
329     QVariant!N opBinary(string op, T)(T power) const 
330             if (isIntegral!T && op == "^^")
331     {
332         return QVariant(_value ^^ power, _dimensions.pow(Rational(power)));
333     }
334 
335     /// ditto
336     QVariant!N opBinary(string op)(Rational power) const 
337             if (op == "^^")
338     {
339         return QVariant(std.math.pow(_value, cast(N) power), _dimensions.pow(power));
340     }
341 
342     // Add/sub assign with a quantity that shares the same dimensions
343     /// ditto
344     void opOpAssign(string op, Q)(auto ref const Q qty)
345             if (isQVariantOrQuantity!Q && (op == "+" || op == "-"))
346     {
347         checkDim(qty.dimensions);
348         mixin("_value " ~ op ~ "= qty.rawValue;");
349     }
350 
351     // Add/sub assign a number to a dimensionless quantity
352     /// ditto
353     void opOpAssign(string op, T)(T scalar)
354             if (isNumeric!T && (op == "+" || op == "-"))
355     {
356         checkDimensionless;
357         mixin("_value " ~ op ~ "= scalar;");
358     }
359 
360     // Mul/div assign another quantity to a quantity
361     /// ditto
362     void opOpAssign(string op, Q)(auto ref const Q qty)
363             if (isQVariantOrQuantity!Q && (op == "*" || op == "/" || op == "%"))
364     {
365         mixin("_value" ~ op ~ "= qty.rawValue;");
366         static if (op == "*")
367             _dimensions = _dimensions * qty.dimensions;
368         else
369             _dimensions = _dimensions / qty.dimensions;
370     }
371 
372     // Mul/div assign a number to a quantity
373     /// ditto
374     void opOpAssign(string op, T)(T scalar)
375             if (isNumeric!T && (op == "*" || op == "/"))
376     {
377         mixin("_value" ~ op ~ "= scalar;");
378     }
379 
380     /// ditto
381     void opOpAssign(string op, T)(T scalar)
382             if (isNumeric!T && op == "%")
383     {
384         checkDimensionless;
385         mixin("_value" ~ op ~ "= scalar;");
386     }
387 
388     // Exact equality between quantities
389     /// ditto
390     bool opEquals(Q)(auto ref const Q qty) const 
391             if (isQVariantOrQuantity!Q)
392     {
393         checkDim(qty.dimensions);
394         return _value == qty.rawValue;
395     }
396 
397     // Exact equality between a dimensionless quantity and a number
398     /// ditto
399     bool opEquals(T)(T scalar) const 
400             if (isNumeric!T)
401     {
402         checkDimensionless;
403         return _value == scalar;
404     }
405 
406     // Comparison between two quantities
407     /// ditto
408     int opCmp(Q)(auto ref const Q qty) const 
409             if (isQVariantOrQuantity!Q)
410     {
411         checkDim(qty.dimensions);
412         if (_value == qty.rawValue)
413             return 0;
414         if (_value < qty.rawValue)
415             return -1;
416         return 1;
417     }
418 
419     // Comparison between a dimensionless quantity and a number
420     /// ditto
421     int opCmp(T)(T scalar) const 
422             if (isNumeric!T)
423     {
424         checkDimensionless;
425         if (_value < scalar)
426             return -1;
427         if (_value > scalar)
428             return 1;
429         return 0;
430     }
431 
432     void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const
433     {
434         sink.formatValue(_value, fmt);
435         sink(" ");
436         sink.formattedWrite!"%s"(_dimensions);
437     }
438 }
439 
440 /// Creates a new monodimensional unit as a QVariant
441 QVariant!N unit(N)(string symbol)
442 {
443     return QVariant!N(N(1), Dimensions.mono(symbol));
444 }
445 
446 // Tests whether T is a quantity type
447 template isQVariant(T)
448 {
449     alias U = Unqual!T;
450     static if (is(U == QVariant!X, X...))
451         enum isQVariant = true;
452     else
453         enum isQVariant = false;
454 }
455 
456 enum isQVariantOrQuantity(T) = isQVariant!T || isQuantity!T;
457 
458 /// Turns a Quantity into a QVariant
459 auto qVariant(Q)(auto ref const Q qty)
460         if (isQuantity!Q)
461 {
462     return QVariant!(Q.valueType)(qty.rawValue, qty.dimensions);
463 }
464 
465 /// Turns a scalar into a dimensionless QVariant
466 auto qVariant(N)(N scalar)
467         if (isNumeric!N)
468 {
469     return QVariant!N(scalar, Dimensions.init);
470 }
471 
472 /++
473 Creates a new prefix function that multiplies a QVariant by a _factor.
474 +/
475 template prefix(alias factor)
476 {
477     alias N = typeof(factor);
478     static assert(isNumeric!N, "Incompatible type: " ~ N.stringof);
479 
480     auto prefix(Q)(auto ref const Q base)
481             if (isQVariantOrQuantity!Q)
482     {
483         return base * factor;
484     }
485 }
486 ///
487 @safe pure unittest
488 {
489     import std.math : approxEqual;
490 
491     auto meter = unit!double("L");
492     alias milli = prefix!1e-3;
493     assert(milli(meter).value(meter).approxEqual(1e-3));
494 }
495 
496 /// Basic math functions that work with QVariant.
497 auto square(Q)(auto ref const Q quantity)
498         if (isQVariant!Q)
499 {
500     return Q(quantity._value ^^ 2, quantity._dimensions.pow(Rational(2)));
501 }
502 
503 /// ditto
504 auto sqrt(Q)(auto ref const Q quantity)
505         if (isQVariant!Q)
506 {
507     return Q(std.math.sqrt(quantity._value), quantity._dimensions.powinverse(Rational(2)));
508 }
509 
510 /// ditto
511 auto cubic(Q)(auto ref const Q quantity)
512         if (isQVariant!Q)
513 {
514     return Q(quantity._value ^^ 3, quantity._dimensions.pow(Rational(3)));
515 }
516 
517 /// ditto
518 auto cbrt(Q)(auto ref const Q quantity)
519         if (isQVariant!Q)
520 {
521     return Q(std.math.cbrt(quantity._value), quantity._dimensions.powinverse(Rational(3)));
522 }
523 
524 /// ditto
525 auto pow(int n, Q)(auto ref const Q quantity)
526         if (isQVariant!Q)
527 {
528     return Q(std.math.pow(quantity._value, n), quantity._dimensions.pow(Rational(n)));
529 }
530 
531 /// ditto
532 auto nthRoot(int n, Q)(auto ref const Q quantity)
533         if (isQVariant!Q)
534 {
535     return Q(std.math.pow(quantity._value, 1.0 / n), quantity._dimensions.powinverse(Rational(n)));
536 }
537 
538 /// ditto
539 Q abs(Q)(auto ref const Q quantity)
540         if (isQVariant!Q)
541 {
542     return Q(std.math.fabs(quantity._value), quantity._dimensions);
543 }