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 }