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 }