1 // Written in the D programming language
2 /++
3 This module defines the SI units and prefixes.
4 
5 All the quantities and units defined in this module store a value
6 of a numeric type that defaults to `double`. To change this default,
7 compile the module with one of these version flags:
8 `-version=SIReal`,
9 `-version=SIDouble`, or
10 `-version=SIFloat`.
11 
12 Copyright: Copyright 2013-2014, Nicolas Sicard
13 Authors: Nicolas Sicard
14 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
15 Standards: $(LINK http://www.bipm.org/en/si/si_brochure/)
16 Source: $(LINK https://github.com/biozic/quantities)
17 +/
18 module quantities.si;
19 
20 import quantities.internal.dimensions;
21 import quantities.base;
22 import quantities.math;
23 import quantities.parsing;
24 import quantities.qvariant;
25 
26 import std.array : appender;
27 import std.conv;
28 import std.math : PI;
29 import std.format;
30 import std.string;
31 import std.typetuple;
32 import core.time : Duration, dur;
33 
34 version (unittest) import std.math : approxEqual;
35 
36 version (SIReal)
37     alias Numeric = real;
38 else version (SIDouble)
39     alias Numeric = double;
40 else version (SIFloat)
41     alias Numeric = float;
42 else
43     alias Numeric = double;
44  
45 /++
46 Predefined SI units.
47 +/
48 enum meter = unit!(Numeric, "L");
49 alias metre = meter; /// ditto
50 enum kilogram = unit!(Numeric, "M"); /// ditto
51 enum second = unit!(Numeric, "T"); /// ditto
52 enum ampere = unit!(Numeric, "I"); /// ditto
53 enum kelvin = unit!(Numeric, "Θ"); /// ditto
54 enum mole = unit!(Numeric, "N"); /// ditto
55 enum candela = unit!(Numeric, "J"); /// ditto
56 
57 enum radian = meter / meter; // ditto
58 enum steradian = square(meter) / square(meter); /// ditto
59 enum hertz = 1 / second; /// ditto
60 enum newton = kilogram * meter / square(second); /// ditto
61 enum pascal = newton / square(meter); /// ditto
62 enum joule = newton * meter; /// ditto
63 enum watt = joule / second; /// ditto
64 enum coulomb = second * ampere; /// ditto
65 enum volt = watt / ampere; /// ditto
66 enum farad = coulomb / volt; /// ditto
67 enum ohm = volt / ampere; /// ditto
68 enum siemens = ampere / volt; /// ditto
69 enum weber = volt * second; /// ditto
70 enum tesla = weber / square(meter); /// ditto
71 enum henry = weber / ampere; /// ditto
72 enum celsius = kelvin; /// ditto
73 enum lumen = candela / steradian; /// ditto
74 enum lux = lumen / square(meter); /// ditto
75 enum becquerel = 1 / second; /// ditto
76 enum gray = joule / kilogram; /// ditto
77 enum sievert = joule / kilogram; /// ditto
78 enum katal = mole / second; /// ditto
79 
80 enum gram = 1e-3 * kilogram; /// ditto
81 enum minute = 60 * second; /// ditto
82 enum hour = 60 * minute; /// ditto
83 enum day = 24 * hour; /// ditto
84 enum degreeOfAngle = PI / 180 * radian; /// ditto
85 enum minuteOfAngle = degreeOfAngle / 60; /// ditto
86 enum secondOfAngle = minuteOfAngle / 60; /// ditto
87 enum hectare = 1e4 * square(meter); /// ditto
88 enum liter = 1e-3 * cubic(meter); /// ditto
89 alias litre = liter; /// ditto
90 enum ton = 1e3 * kilogram; /// ditto
91 enum electronVolt = 1.60217653e-19 * joule; /// ditto
92 enum dalton = 1.66053886e-27 * kilogram; /// ditto
93 
94 //enum one = Quantity!(Numeric, Dimensions.init)(1); /// The dimensionless unit 'one'
95 
96 alias Length = typeof(meter); /// Predefined quantity type templates for SI quantities
97 alias Mass = typeof(kilogram); /// ditto
98 alias Time = typeof(second); /// ditto
99 alias ElectricCurrent = typeof(ampere); /// ditto
100 alias Temperature = typeof(kelvin); /// ditto
101 alias AmountOfSubstance = typeof(mole); /// ditto
102 alias LuminousIntensity = typeof(candela); /// ditto
103 
104 alias Area = typeof(square(meter)); /// ditto
105 alias Surface = Area;
106 alias Volume = typeof(cubic(meter)); /// ditto
107 alias Speed = typeof(meter/second); /// ditto
108 alias Acceleration = typeof(meter/square(second)); /// ditto
109 alias MassDensity = typeof(kilogram/cubic(meter)); /// ditto
110 alias CurrentDensity = typeof(ampere/square(meter)); /// ditto
111 alias MagneticFieldStrength = typeof(ampere/meter); /// ditto
112 alias Concentration = typeof(mole/cubic(meter)); /// ditto
113 alias MolarConcentration = Concentration; /// ditto
114 alias MassicConcentration = typeof(kilogram/cubic(meter)); /// ditto
115 alias Luminance = typeof(candela/square(meter)); /// ditto
116 alias RefractiveIndex = typeof(kilogram); /// ditto
117 
118 alias Angle = typeof(radian); /// ditto
119 alias SolidAngle = typeof(steradian); /// ditto
120 alias Frequency = typeof(hertz); /// ditto
121 alias Force = typeof(newton); /// ditto
122 alias Pressure = typeof(pascal); /// ditto
123 alias Energy = typeof(joule); /// ditto
124 alias Work = Energy; /// ditto
125 alias Heat = Energy; /// ditto
126 alias Power = typeof(watt); /// ditto
127 alias ElectricCharge = typeof(coulomb); /// ditto
128 alias ElectricPotential = typeof(volt); /// ditto
129 alias Capacitance = typeof(farad); /// ditto
130 alias ElectricResistance = typeof(ohm); /// ditto
131 alias ElectricConductance = typeof(siemens); /// ditto
132 alias MagneticFlux = typeof(weber); /// ditto
133 alias MagneticFluxDensity = typeof(tesla); /// ditto
134 alias Inductance = typeof(henry); /// ditto
135 alias LuminousFlux = typeof(lumen); /// ditto
136 alias Illuminance = typeof(lux); /// ditto
137 alias CelsiusTemperature = typeof(celsius); /// ditto
138 alias Radioactivity = typeof(becquerel); /// ditto
139 alias AbsorbedDose = typeof(gray); /// ditto
140 alias DoseEquivalent = typeof(sievert); /// ditto
141 alias CatalyticActivity = typeof(katal); /// ditto
142 
143 alias Dimensionless = typeof(meter/meter); /// The type of dimensionless quantities
144 
145 /// SI prefixes.
146 alias yotta = prefix!1e24;
147 alias zetta = prefix!1e21; /// ditto
148 alias exa = prefix!1e18; /// ditto
149 alias peta = prefix!1e15; /// ditto
150 alias tera = prefix!1e12; /// ditto
151 alias giga = prefix!1e9; /// ditto
152 alias mega = prefix!1e6; /// ditto
153 alias kilo = prefix!1e3; /// ditto
154 alias hecto = prefix!1e2; /// ditto
155 alias deca = prefix!1e1; /// ditto
156 alias deci = prefix!1e-1; /// ditto
157 alias centi = prefix!1e-2; /// ditto
158 alias milli = prefix!1e-3; /// ditto
159 alias micro = prefix!1e-6; /// ditto
160 alias nano = prefix!1e-9; /// ditto
161 alias pico = prefix!1e-12; /// ditto
162 alias femto = prefix!1e-15; /// ditto
163 alias atto = prefix!1e-18; /// ditto
164 alias zepto = prefix!1e-21; /// ditto
165 alias yocto = prefix!1e-24; /// ditto
166 
167 /// A list of common SI symbols and prefixes
168 enum siSymbols = SymbolList!Numeric()
169     .addUnit("m", meter)
170     .addUnit("kg", kilogram)
171     .addUnit("s", second)
172     .addUnit("A", ampere)
173     .addUnit("K", kelvin)
174     .addUnit("mol", mole)
175     .addUnit("cd", candela)
176     .addUnit("rad", radian)
177     .addUnit("sr", steradian)
178     .addUnit("Hz", hertz)
179     .addUnit("N", newton)
180     .addUnit("Pa", pascal)
181     .addUnit("J", joule)
182     .addUnit("W", watt)
183     .addUnit("C", coulomb)
184     .addUnit("V", volt)
185     .addUnit("F", farad)
186     .addUnit("Ω", ohm)
187     .addUnit("S", siemens)
188     .addUnit("Wb", weber)
189     .addUnit("T", tesla)
190     .addUnit("H", henry)
191     .addUnit("lm", lumen)
192     .addUnit("lx", lux)
193     .addUnit("Bq", becquerel)
194     .addUnit("Gy", gray)
195     .addUnit("Sv", sievert)
196     .addUnit("kat", katal)
197     .addUnit("g", gram)
198     .addUnit("min", minute)
199     .addUnit("h", hour)
200     .addUnit("d", day)
201     .addUnit("l", liter)
202     .addUnit("L", liter)
203     .addUnit("t", ton)
204     .addUnit("eV", electronVolt)
205     .addUnit("Da", dalton)
206     .addPrefix("Y", 1e24)
207     .addPrefix("Z", 1e21)
208     .addPrefix("E", 1e18)
209     .addPrefix("P", 1e15)
210     .addPrefix("T", 1e12)
211     .addPrefix("G", 1e9)
212     .addPrefix("M", 1e6)
213     .addPrefix("k", 1e3)
214     .addPrefix("h", 1e2)
215     .addPrefix("da", 1e1)
216     .addPrefix("d", 1e-1)
217     .addPrefix("c", 1e-2)
218     .addPrefix("m", 1e-3)
219     .addPrefix("µ", 1e-6)
220     .addPrefix("n", 1e-9)
221     .addPrefix("p", 1e-12)
222     .addPrefix("f", 1e-15)
223     .addPrefix("a", 1e-18)
224     .addPrefix("z", 1e-21)
225     .addPrefix("y", 1e-24)
226     .addPrefix("Yi", 1024.0^^8)
227     .addPrefix("Zi", 1024.0^^7)
228     .addPrefix("Ei", 1024.0^^6)
229     .addPrefix("Pi", 1024.0^^5)
230     .addPrefix("Ti", 1024.0^^4)
231     .addPrefix("Gi", 1024.0^^3)
232     .addPrefix("Mi", 1024.0^^2)
233     .addPrefix("Ki", 1024.0);
234 
235 private static SymbolList!Numeric _siSymbols;
236 private static Parser!Numeric _siParser;
237 static this()
238 {
239     _siSymbols = siSymbols;
240     _siParser = Parser!Numeric(_siSymbols, &std.conv.parse!(Numeric, string));
241 }
242 
243 /// Parses a string for a quantity of type Q at runtime
244 Q parseSI(Q)(string str)
245     if (isQuantity!Q)
246 {
247     return _siParser.parse!Q(str);
248 }
249 ///
250 @safe unittest
251 {
252     auto t = parseSI!Time("90 min");
253     assert(t == 90 * minute);
254     t = parseSI!Time("h");
255     assert(t == 1 * hour);
256 
257     auto v = parseSI!Dimensionless("2");
258     assert(v == (2 * meter) / meter);
259 }
260 
261 /// A compile-time parser with automatic type deduction for SI quantities.
262 alias si = compileTimeParser!(Numeric, siSymbols, std.conv.parse!(Numeric, string));
263 ///
264 pure @safe unittest
265 {
266     enum min = si!"min";
267     enum inch = si!"2.54 cm";
268     auto conc = si!"1 µmol/L";
269     auto speed = si!"m s^-1";
270     auto value = si!"0.5";
271     
272     static assert(is(typeof(inch) == Length));
273     static assert(is(typeof(conc) == Concentration));
274     static assert(is(typeof(speed) == Speed));
275     static assert(is(typeof(value) == Dimensionless));
276 }
277 
278 /++
279 Helper struct that formats a SI quantity.
280 +/
281 struct SIFormatWrapper(Q)
282     if (isQuantity!Q || isQVariant!Q)
283 {
284     private string fmt;
285 
286     /++
287     Creates a new formatter from a format string.
288     +/
289     this(string fmt)
290     {
291         this.fmt = fmt;
292     }
293 
294     /++
295     Returns a wrapper struct that can be formatted by `std.string.format` or
296     `std.format` functions.
297     +/
298     auto opCall(Q quantity) const
299     {
300         auto _fmt = fmt;
301 
302         ///
303         struct Wrapper
304         {
305             private Q quantity;
306             
307             void toString(scope void delegate(const(char)[]) sink) const
308             {
309                 auto spec = FormatSpec!char(_fmt);
310                 spec.writeUpToNextSpec(sink);
311                 auto target = spec.trailing.idup;
312                 auto unit = parseSI!Q(target);
313                 sink.formatValue(quantity.value(unit), spec);
314                 sink(target);
315             }
316 
317             string toString() const
318             {
319                 return format("%s", this);
320             }
321         }
322 
323         return Wrapper(quantity);
324     }
325 }
326 ///
327 unittest
328 {
329     import std.string;
330 
331     auto sf = SIFormatWrapper!Speed("%.1f km/h");
332     auto speed = 343.4 * meter/second;
333     assert("Speed: %s".format(sf(speed)) == "Speed: 1236.2 km/h");
334 }
335 
336 unittest
337 {
338     auto sf = SIFormatWrapper!Speed("%.1f km/h");
339     assert(text(sf(si!"343.4 m/s")) == "1236.2 km/h");
340     assert(text(sf(parseSI!Speed("343.4 m/s"))) == "1236.2 km/h");
341 }
342 
343 /++
344 Convenience function that returns a SIFormatter when the format string is
345 known at compile-time.
346 +/
347 auto siFormatWrapper(string fmt)()
348 {
349     enum unit = si!({
350         auto spec = FormatSpec!char(fmt);
351         auto dummy = appender!string;
352         spec.writeUpToNextSpec(dummy);
353         return spec.trailing.idup;
354     }());
355 
356     return SIFormatWrapper!(typeof(unit))(fmt);
357 }
358 ///
359 unittest
360 {
361     import std.string;
362 
363     auto sf = siFormatWrapper!"%.1f km/h";
364     auto speed = 343.4 * meter/second;
365     assert("Speed: %s".format(sf(speed)) == "Speed: 1236.2 km/h");
366 }
367 
368 /// Converts a quantity of time to or from a core.time.Duration
369 Time fromDuration(Duration d) pure @safe
370 {
371     return d.total!"hnsecs" * hecto(nano(second));
372 }
373 
374 /// ditto
375 Duration toDuration(Q)(Q quantity) 
376     if (isQuantity!Q)
377 {
378     import std.conv;
379     auto hns = quantity.value(hecto(nano(second)));
380     return dur!"hnsecs"(hns.roundTo!long);
381 }
382 
383 ///
384 @safe unittest // Durations
385 {
386     auto d = 4.dur!"msecs";
387     auto t = fromDuration(d);
388     assert(t.value(milli(second)).approxEqual(4));
389     
390     auto t2 = 3.5 * minute;
391     auto d2 = t2.toDuration;
392     auto s = d2.split!("minutes", "seconds")();
393     assert(s.minutes == 3 && s.seconds == 30);
394 }