1 /**
2  * International System of Units (SI) units and prefixes for use with
3  * $(D std.units).
4  *
5  * The definitions have been taken from the NIST Special Publication 330,
6  * $(WEB http://physics.nist.gov/Pubs/SP330/sp330.pdf, The International
7  * System of Units), 2008 edition.
8  *
9  * Todo: $(UL
10  *  $(LI Do something about the derived unit types being expanded in the
11  *   generated documentation.)
12  * )
13  *
14  * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
15  * Authors: $(WEB klickverbot.at, David Nadlinger)
16  */
17 module experimental.units.si;
18 
19 import experimental.units;
20 
21 /**
22  * The full $(XREF units, PrefixSystem) of SI prefixes.
23  *
24  * For each prefix, a helper template like $(D kilo!()) for prefixing units
25  * is provided (see $(XREF units, prefixTemplate)).
26  */
27 alias SiPrefixSystem = PrefixSystem!(10, {
28         return [Prefix(-24, "yocto", "y"),
29                 Prefix(-21, "zepto", "z"),
30                 Prefix(-18, "atto", "a"),
31                 Prefix(-15, "femto", "f"),
32                 Prefix(-12, "pico", "p"),
33                 Prefix(-9, "nano", "n"),
34                 Prefix(-6, "micro", "µ"),
35                 Prefix(-3, "milli", "m"),
36                 Prefix(-2, "centi", "c"),
37                 Prefix(-1, "deci", "d"),
38                 Prefix(1, "deka", "da"),
39                 Prefix(2, "hecto", "h"),
40                 Prefix(3, "kilo", "k"),
41                 Prefix(6, "mega", "M"),
42                 Prefix(9, "giga", "G"),
43                 Prefix(12, "tera", "T"),
44                 Prefix(15, "peta", "P"),
45                 Prefix(18, "exa", "E"),
46                 Prefix(21, "zetta", "Z"),
47                 Prefix(24, "yotta", "Y")];
48 });
49 
50 // TODO Add binary byte units
51 
52 mixin DefinePrefixSystem!(SiPrefixSystem);
53 
54 /**
55  * SI base units.
56  */
57 alias Ampere = BaseUnit!("Ampere", "A");
58 alias Candela = BaseUnit!("candela", "cd");
59 alias Gram = BaseUnit!("gram", "g");
60 alias Kelvin = BaseUnit!("Kelvin", "K");
61 alias Metre = BaseUnit!("metre", "m");
62 alias Mole = BaseUnit!("mole", "mol");
63 alias Second = BaseUnit!("second", "s");
64 alias Radian = BaseUnit!("radian", "rad");
65 alias Steradian = BaseUnit!("steradian", "sr");
66 
67 enum ampere = Ampere.init;
68 enum candela = Candela.init; /// ditto
69 enum gram = Gram.init; /// ditto
70 enum kilogram = kilo!gram; /// ditto
71 enum kelvin = Kelvin.init; /// ditto
72 enum metre = Metre.init; /// ditto
73 alias meter = metre; /// ditto
74 enum mole = Mole.init; /// ditto
75 enum second = Second.init; /// ditto
76 
77 /**
78  * SI supplementary units for angles.
79  */
80 enum radian = Radian.init;
81 enum steradian = Steradian.init; /// ditto
82 
83 import std.math : PI;
84 
85 enum PI_OVER_180 = PI/180;
86 enum _180_OVER_PI = 180/PI;
87 
88 /**
89  * SI scaled units for angles.
90  */
91 // enum degree = PI_OVER_180 * radian; // TODO Use own convertible type:
92 enum degree = scale!(radian, PI/180, "degree");
93 
94 // TODO Celsius: Use AffineUnit
95 // TODO Fahrenheit: Add and use LinearUnit
96 
97 auto cos(Q)(Q angle)
98     if (Q.init.isConvertibleTo!radian) // TODO Fix and use ConvertibleTo?
99 {
100     import std.math : cos;
101     return cos(angle.convert!radian.toValue);
102 }
103 
104 auto sin(Q)(Q angle)
105     if (Q.init.isConvertibleTo!radian)
106 {
107     import std.math : sin;
108     return sin(angle.convert!radian.toValue);
109 }
110 
111 auto tan(Q)(Q angle)
112     if (Q.init.isConvertibleTo!radian)
113 {
114     import std.math : tan;
115     return tan(angle.convert!radian.toValue);
116 }
117 
118 auto expi(Q)(Q angle)
119     if (Q.init.isConvertibleTo!radian)
120 {
121     import std.math : expi;
122     return expi(angle.convert!radian.toValue);
123 }
124 
125 ///
126 @safe pure nothrow @nogc unittest
127 {
128     import std.math : isClose;
129 
130     assert(0.0*radian < 1.0*radian);
131     // TODO fix Quantitiy.opCmp to allow: assert(0.0*radian < 1.0*degree);
132 
133     assert(isClose(cos(0.0*radian), 1));
134     assert(isClose(cos(PI*radian), -1));
135     assert(isClose(cos(2*PI*radian), 1));
136 
137     enum d = (180*degree);
138     // pragma(msg, d.stringof ~ " : " ~ typeof(d).stringof);
139 
140     enum r = d.convert!radian;
141     // pragma(msg, r.stringof ~ " : " ~ typeof(r).stringof);
142 
143     // TODO enable when cast in ScaledUnit.{to|from}Base have been removed:
144     // TODO assert(isClose(cos(180*degree), -1));
145 
146     assert(isClose(sin(0.0*radian), 0));
147     assert(isClose(sin(PI*radian), 0));
148     assert(isClose(sin(2*PI*radian), 0));
149     // TODO enable when cast in ScaledUnit.{to|from}Base have been removed:
150     // TODO assert(isClose(sin(360*degree), 0));
151     assert(isClose(sin(PI*radian), 0));
152 
153     // assert(isClose(expi(0.0*radian)!0.toValue, 0));
154 }
155 
156 /**
157  * SI derived units.
158  */
159 enum hertz = dimensionless / second;
160 enum newton = kilogram * metre / pow!2(second); /// ditto
161 enum pascal = newton / pow!2(metre); /// ditto
162 enum joule = newton * metre; /// ditto
163 enum watt = joule / second; /// ditto
164 enum coulomb = ampere * second; /// ditto
165 enum volt = watt / ampere; /// ditto
166 enum farad = coulomb / volt; /// ditto
167 enum ohm = volt / ampere; /// ditto
168 enum siemens = ampere / volt; /// ditto
169 enum weber = volt * second; /// ditto
170 enum tesla = weber / pow!2(metre); /// ditto
171 enum henry = weber / ampere; /// ditto
172 enum lumen = candela * steradian; /// ditto
173 enum lux = lumen / pow!2(metre); /// ditto
174 enum becquerel = dimensionless / second; /// ditto
175 enum gray = joule / kilogram; /// ditto
176 enum sievert = joule / kilogram; /// ditto
177 enum katal = mole / second; /// ditto
178 
179 ///
180 @safe pure nothrow @nogc unittest
181 {
182     auto work(Quantity!newton force, Quantity!metre displacement)
183     {
184         return force * displacement;
185     }
186 
187     Quantity!(mole, V) idealGasAmount(V)(Quantity!(pascal, V) pressure,
188         Quantity!(pow!3(meter), V) volume, Quantity!(kelvin, V) temperature)
189     {
190         enum R = 8.314471 * joule / (kelvin * mole);
191         return (pressure * volume) / (temperature * R);
192     }
193 
194     enum forcef = 1.0f * newton;
195     enum force = 1.0 * newton;
196 
197     // compare quantities with different value types
198     assert(forcef == force);
199     static assert(forcef == force);
200 
201     enum displacement = 1.0 * metre;
202     enum Quantity!joule e = work(force, displacement);
203     static assert(e == 1.0 * joule);
204 
205     enum T = (273. + 37.) * kelvin;
206     enum p = 1.01325e5 * pascal;
207     enum r = 0.5e-6 * meter;
208     enum V = (4.0 / 3.0) * 3.141592 * r.pow!3;
209     enum n = idealGasAmount!double(p, V, T); // Need to explicitly specify double due to @@BUG5801@@.
210     // TODO is this needed: static assert(n == 0xb.dd95ef4ddcb82f7p-59 * mole);
211 
212     static assert((2 * kilogram).convert!gram == 2000 * gram);
213     static assert((2000 * gram).convert!kilogram == 2 * kilogram);
214     static assert((1000 * newton).convert!(milli!newton) == 1000000 * milli!newton);
215     static assert((2000000 * gram * meter / second.pow!2).convert!(kilo!newton) == 2 * kilo!newton);
216     static assert((1234.0 * micro!newton / milli!metre.pow!2).convert!pascal == 1234.0 * pascal);
217 }