.circleci/config.yml .github/workflows/ci.yml .github/workflows/compilers.json .gitignore .gitmodules LICENSE NOTICE README.md bigint_benchmark/dub.sdl bigint_benchmark/source/app.d cpp_example/eye.d cpp_example/init_rcarray.d cpp_example/main.cpp cpp_example/meson.build dub.sdl images/kaleidic.jpeg images/symmetry.png include/mir/interpolate.h include/mir/ndslice.h include/mir/numeric.h include/mir/rcarray.h include/mir/rcptr.h include/mir/series.h include/mir/slim_rcptr.h include/mir/small_string.h index.d meson.build ndslice.graffle ndslice.svg source/mir/algebraic_alias/ion.d source/mir/algebraic_alias/json.d source/mir/algebraic_alias/transform.d source/mir/algorithm/iteration.d source/mir/algorithm/setops.d source/mir/annotated.d source/mir/appender.d source/mir/array/allocation.d source/mir/base64.d source/mir/bignum/decimal.d source/mir/bignum/fixed.d source/mir/bignum/fp.d source/mir/bignum/integer.d source/mir/bignum/internal/dec2float.d source/mir/bignum/internal/dec2float_table.d source/mir/bignum/internal/kernel.d source/mir/bignum/internal/parse.d source/mir/bignum/internal/phobos_kernel.d source/mir/bignum/internal/ryu/generic_128.d source/mir/bignum/internal/ryu/table.d source/mir/bignum/low_level_view.d source/mir/combinatorics/package.d source/mir/container/binaryheap.d source/mir/cpp_export/numeric.d source/mir/date.d source/mir/ediff.d source/mir/format.d source/mir/format_impl.d source/mir/graph/package.d source/mir/graph/tarjan.d source/mir/interpolate/constant.d source/mir/interpolate/extrapolate.d source/mir/interpolate/generic.d source/mir/interpolate/linear.d source/mir/interpolate/mod.d source/mir/interpolate/package.d source/mir/interpolate/polynomial.d source/mir/interpolate/spline.d source/mir/interpolate/utility.d source/mir/lob.d source/mir/math/func/expdigamma.d source/mir/math/func/hermite.d source/mir/math/func/normal.d source/mir/math/numeric.d source/mir/math/stat.d source/mir/math/sum.d source/mir/ndslice/allocation.d source/mir/ndslice/chunks.d source/mir/ndslice/concatenation.d source/mir/ndslice/connect/cpython.d source/mir/ndslice/dynamic.d source/mir/ndslice/field.d source/mir/ndslice/filling.d source/mir/ndslice/fuse.d source/mir/ndslice/internal.d source/mir/ndslice/iterator.d source/mir/ndslice/mutation.d source/mir/ndslice/ndfield.d source/mir/ndslice/package.d source/mir/ndslice/slice.d source/mir/ndslice/sorting.d source/mir/ndslice/topology.d source/mir/ndslice/traits.d source/mir/numeric.d source/mir/parse.d source/mir/polynomial.d source/mir/range.d source/mir/rc/array.d source/mir/rc/context.d source/mir/rc/package.d source/mir/rc/ptr.d source/mir/rc/slim_ptr.d source/mir/serde.d source/mir/series.d source/mir/small_array.d source/mir/small_string.d source/mir/stdio.d source/mir/string.d source/mir/string_map.d source/mir/test.d source/mir/timestamp.d source/mir/type_info.d subprojects/mir-core.wrap <<<<<< network # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-algebraic.lst |/++ |$(H2 Variant and Nullable types) | |This module implements a |$(HTTP erdani.org/publications/cuj-04-2002.php.html,discriminated union) |type (a.k.a. |$(HTTP en.wikipedia.org/wiki/Tagged_union,tagged union), |$(HTTP en.wikipedia.org/wiki/Algebraic_data_type,algebraic type)). |Such types are useful |for type-uniform binary interfaces, interfacing with scripting |languages, and comfortable exploratory programming. | |The module defines generic $(LREF Algebraic) type that contains a payload. |The allowed types of the paylad are defined by the unordered $(LREF TypeSet). | |$(LREF Algebraic) template accepts two arguments: self type set id and a list of type sets. | |$(BOOKTABLE $(H3 $(LREF Algebraic) Aliases), |$(TR $(TH Name) $(TH Description)) |$(T2 Variant, an algebraic type) |$(T2 TaggedVariant, a tagged algebraic type) |$(T2 Nullable, an algebraic type with at least `typeof(null)`) |) | |$(BOOKTABLE $(H3 Visitor Handlers), |$(TR $(TH Name) $(TH Ensures can match) $(TH Throws if no match) $(TH Returns $(LREF Nullable)) $(TH Multiple dispatch) $(TH Argumments count) $(TH Fuses Algebraic types on return)) |$(LEADINGROWN 8, Classic handlers) |$(T8 visit, Yes, N/A, No, No, 1+, No) |$(T8 optionalVisit, No, No, Yes, No, 1+, No) |$(T8 autoVisit, No, No, auto, No, 1+, No) |$(T8 tryVisit, No, Yes, No, No, 1+, No) |$(LEADINGROWN 8, Multiple dispatch and algebraic fusion on return) |$(T8 match, Yes, N/A, No, Yes, 0+, Yes) |$(T8 optionalMatch, No, No, Yes, Yes, 0+, Yes) |$(T8 autoMatch, No, No, auto, Yes, 0+, Yes) |$(T8 tryMatch, No, Yes, No, Yes, 0+, Yes) |$(LEADINGROWN 8, Inner handlers. Multiple dispatch and algebraic fusion on return.) |$(T8 suit, N/A(Yes), N/A, No, Yes, ?, Yes) |$(T8 some, N/A(Yes), N/A, No, Yes, 0+, Yes) |$(T8 none, N/A(Yes), N/A, No, Yes, 1+, Yes) |$(T8 assumeOk, Yes(No), No(Yes), No(Yes), Yes(No), 0+, Yes(No)) |$(LEADINGROWN 8, Member access) |$(T8 getMember, Yes, N/A, No, No, 1+, No) |$(T8 optionalGetMember, No, No, Yes, No, 1+, No) |$(T8 autoGetMember, No, No, auto, No, 1+, No) |$(T8 tryGetMember, No, Yes, No, No, 1+, No) |$(LEADINGROWN 8, Member access with algebraic fusion on return) |$(T8 matchMember, Yes, N/A, No, No, 1+, Yes) |$(T8 optionalMatchMember, No, No, Yes, No, 1+, Yes) |$(T8 autoMatchMember, No, No, auto, No, 1+, Yes) |$(T8 tryMatchMember, No, Yes, No, No, 1+, Yes) |) | |$(BOOKTABLE $(H3 Special Types), |$(TR $(TH Name) $(TH Description)) |$(T2plain `void`, It is usefull to indicate a possible return type of the visitor. Can't be accesed by reference. ) |$(T2plain `typeof(null)`, It is usefull for nullable types. Also, it is used to indicate that a visitor can't match the current value of the algebraic. Can't be accesed by reference. ) |$(T2 This, Dummy structure that is used to construct self-referencing algebraic types. Example: `Variant!(int, double, string, This*[2])`) |$(T2plain $(LREF SetAlias)`!setId`, Dummy structure that is used to construct cyclic-referencing lists of algebraic types. ) |$(T2 Err, Wrapper to denote an error value type. ) |$(T2 reflectErr, Attribute that denotes that the type is an error value type. ) |) | |$(BOOKTABLE $(H3 $(LREF Algebraic) Traits), |$(TR $(TH Name) $(TH Description)) |$(T2 isVariant, Checks if the type is instance of $(LREF Algebraic).) |$(T2 isNullable, Checks if the type is instance of $(LREF Algebraic) with a self $(LREF TypeSet) that contains `typeof(null)`. ) |$(T2 isTypeSet, Checks if the types are the same as $(LREF TypeSet) of them. ) |$(T2 ValueTypeOfNullable, Gets type of $(LI $(LREF .Algebraic.get.2)) method. ) |$(T2 SomeVariant, Gets subtype of algebraic without types for which $(LREF isErr) is true.) |$(T2 NoneVariant, Gets subtype of algebraic with types for which $(LREF isErr) is true.) |$(T2 isErr, Checks if T is a instance of $(LREF Err) or if it is annotated with $(LREF reflectErr).) |$(T2 isResultVariant, Checks if T is a Variant with at least one allowed type that satisfy $(LREF isErr) traits.) | |) | | |$(H3 Type Set) |$(UL |$(LI Type set is unordered. Example:`TypeSet!(int, double)` and `TypeSet!(double, int)` are the same. ) |$(LI Duplicats are ignored. Example: `TypeSet!(float, int, float)` and `TypeSet!(int, float)` are the same. ) |$(LI Types are automatically unqualified if this operation can be performed implicitly. Example: `TypeSet!(const int) and `TypeSet!int` are the same. ) |$(LI Non trivial `TypeSet!(A, B, ..., etc)` is allowed.) |$(LI Trivial `TypeSet!T` is allowed.) |$(LI Empty `TypeSet!()` is allowed.) |) | |$(H3 Visitors) |$(UL |$(LI Visitors are allowed to return values of different types If there are more then one return type then the an $(LREF Algebraic) type is returned. ) |$(LI Visitors are allowed to accept additional arguments. The arguments can be passed to the visitor handler. ) |$(LI Multiple visitors can be passes to the visitor handler. ) |$(LI Visitors are matched according to the common $(HTTPS dlang.org/spec/function.html#function-overloading, Dlang Function Overloading) rules. ) |$(LI Visitors are allowed accept algebraic value by reference except the value of `typeof(null)`. ) |$(LI Visitors are called without algebraic value if its algebraic type is `void`. ) |$(LI If the visitors arguments has known types, then such visitors should be passed to a visitor handler before others to make the compiler happy. This includes visitors with no arguments, which is used to match `void` type. ) |) | |$(H3 Implementation Features) |$(UL |$(LI BetterC support. Runtime `TypeInfo` is not used.) |$(LI Copy-constructors and postblit constructors are supported. ) |$(LI `toHash`, `opCmp`. `opEquals`, and `toString` support. ) |$(LI No string or template mixins are used. ) |$(LI Optimised for fast execution. ) |$(LI $(LREF some) / $(LREF none) idiom. ) |) | |See_also: $(HTTPS en.wikipedia.org/wiki/Algebra_of_sets, Algebra of sets). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki | |Macros: |T2plain=$(TR $(TDNW $1) $(TD $+)) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) |T8=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4) $(TD $5) $(TD $6) $(TD $7) $(TD $8)) | |+/ |module mir.algebraic; | |import mir.internal.meta; |import mir.functional: naryFun; | |/++ |The attribute is used to define a permanent member field in an anlgebraic type. |Should applied to a field of the union passed to $(LREF TaggedVariant). |+/ |enum algMeta; |/++ |The attribute is used in pair with $(LREF algMeta) to exclude the field |from compression in `toHash`, `opEquals`, and `opCmp` methods. |+/ |enum algTransp; |/++ |The attribute is used in pair with $(LREF algMeta) to use the field |as an error infomration. Usually it is a position marker in a file. |The type should have `scope const` `toString` method. |+/ |enum algVerbose; | |private static immutable variantExceptionMsg = "mir.algebraic: the algebraic stores other type then requested."; |private static immutable variantNullExceptionMsg = "mir.algebraic: the algebraic is empty and doesn't store any value."; |private static immutable variantMemberExceptionMsg = "mir.algebraic: the algebraic stores a type that isn't compatible with the user provided visitor and arguments."; | |version (D_Exceptions) |{ | private static immutable variantException = new Exception(variantExceptionMsg); | private static immutable variantNullException = new Exception(variantNullExceptionMsg); | private static immutable variantMemberException = new Exception(variantMemberExceptionMsg); |} | |private static struct _Null() |{ |@safe pure nothrow @nogc const: | int opCmp(_Null) { return 0; } | this(typeof(null)) inout {} | string toString() { return "null"; } |} | |private static struct _Void() |{ | @safe pure nothrow @nogc const: | int opCmp(_Void) { return 0; } | string toString() { return "void"; } |} | |/++ |Checks if the type is instance of $(LREF Algebraic). |+/ |enum bool isVariant(T) = is(immutable T == immutable Algebraic!Types, Types...); | |/// |@safe pure version(mir_core_test) unittest |{ | static assert(isVariant!(Variant!(int, string))); | static assert(isVariant!(const Variant!(int[], string))); | static assert(isVariant!(Nullable!(int, string))); | static assert(!isVariant!int); |} | |/++ |Same as $(LREF isVariant), but matches for `alias this` variant types (requires |DMD FE 2.100.0 or later) |+/ |enum bool isLikeVariant(T) = !is(immutable T == immutable noreturn) | && is(immutable T : immutable Algebraic!Types, Types...); | | |static if (__VERSION__ >= 2_100) |{ | /// | @safe pure version(mir_core_test) unittest | { | static struct CustomVariant | { | Variant!(int, string) data; | alias data this; | this(T)(T v) { data = v; } | ref typeof(this) opAssign(T)(T v) | { | data = v; | return this; | } | } | | static assert(isLikeVariant!(Variant!(int, string))); | static assert(isLikeVariant!(const Variant!(int[], string))); | static assert(isLikeVariant!(Nullable!(int, string))); | static assert(!isLikeVariant!int); | | static assert(!isVariant!CustomVariant); | static assert(isLikeVariant!CustomVariant); | | CustomVariant customVariant = 5; | assert(customVariant.match!( | (string s) => false, | (int n) => true | )); | } |} | |/++ |Checks if the type is instance of tagged $(LREF Algebraic). | |Tagged algebraics can be defined with $(LREF TaggedVariant). |+/ |enum bool isTaggedVariant(T) = is(immutable T == immutable Algebraic!U, U) && is(U == union); | |/// |@safe pure version(mir_core_test) unittest |{ | static union MyUnion | { | int integer; | immutable(char)[] string; | } | | alias MyAlgebraic = Algebraic!MyUnion; | static assert(isTaggedVariant!MyAlgebraic); | | static assert(!isTaggedVariant!int); | static assert(!isTaggedVariant!(Variant!(int, string))); |} | |/++ |Same as $(LREF isTaggedVariant), but with support for custom `alias this` |variants. | |Only works since DMD FE 2.100, see $(LREF isLikeVariant). |+/ |enum bool isLikeTaggedVariant(T) = isLikeVariant!T && is(T.Kind == enum); | |/++ |Checks if the type is instance of $(LREF Algebraic) with a self $(LREF TypeSet) that contains `typeof(null)`. |+/ |enum bool isNullable(T) = is(immutable T == immutable Algebraic!(typeof(null), Types), Types...); | |/// |@safe pure version(mir_core_test) unittest |{ | static assert(isNullable!(const Nullable!(int, string))); | static assert(isNullable!(Nullable!())); | | static assert(!isNullable!(Variant!())); | static assert(!isNullable!(Variant!string)); | static assert(!isNullable!int); | static assert(!isNullable!string); |} | |/++ |Same as $(LREF isNullable), but with support for custom `alias this` variants. | |Only works since DMD FE 2.100, see $(LREF isLikeVariant). |+/ |enum bool isLikeNullable(T) = !is(immutable T == immutable noreturn) | && is(immutable T : immutable Algebraic!(typeof(null), Types), Types...); | |/++ |Gets type of $(LI $(LREF .Algebraic.get.2)) method. |+/ |template ValueTypeOfNullable(T : Algebraic!(typeof(null), Types), Types...) |{ | static if (Types.length == 1) | alias ValueTypeOfNullable = Types[0]; | else | alias ValueTypeOfNullable = Algebraic!Types; |} | |/// |@safe pure version(mir_core_test) unittest |{ | static assert(is(ValueTypeOfNullable!(const Nullable!(int, string)) == Algebraic!(int, string))); | static assert(is(ValueTypeOfNullable!(Nullable!()) == Algebraic!())); | static assert(is(typeof(Nullable!().get()) == Algebraic!())); |} | |/++ |Dummy type for $(LREF Variant) and $(LREF Nullable) self-referencing. |+/ |struct This |{ |@safe pure nothrow @nogc scope const: 0000000| int opCmp(typeof(this)) { return 0; } 0000000| string toString() { return typeof(this).stringof; } |} | |private template TagInfo(T, string name, udas...) | if (udas.length <= 3) |{ | import std.meta: staticIndexOf; | alias Type = T; | enum tag = name; | enum meta = staticIndexOf!(algMeta, udas) >= 0; | enum transparent = staticIndexOf!(algTransp, udas) >= 0; | enum verbose = staticIndexOf!(algVerbose, udas) >= 0; |} | |// example from std.variant |/++ |$(H4 Self-Referential Types) |A useful and popular use of algebraic data structures is for defining |$(LUCKY self-referential data structures), i.e. structures that embed references to |values of their own type within. |This is achieved with $(LREF Variant) by using $(LREF This) as a placeholder whenever a |reference to the type being defined is needed. The $(LREF Variant) instantiation |will perform |$(LINK2 https://en.wikipedia.org/wiki/Name_resolution_(programming_languages)#Alpha_renaming_to_make_name_resolution_trivial, |alpha renaming) on its constituent types, replacing $(LREF This) |with the self-referenced type. The structure of the type involving $(LREF This) may |be arbitrarily complex. |+/ |@safe pure version(mir_core_test) unittest |{ | import mir.functional: Tuple; | | // A tree is either a leaf or a branch of two others | alias Tree(Leaf) = Variant!(Leaf, Tuple!(This*, This*)); | alias Leafs = Tuple!(Tree!int*, Tree!int*); | | Tree!int tree = Leafs(new Tree!int(41), new Tree!int(43)); | Tree!int* right = tree.get!Leafs[1]; | assert(*right == 43); |} | |/// |@safe pure version(mir_core_test) unittest |{ | // An object is a double, a string, or a hash of objects | alias Obj = Variant!(double, string, This[string], This[]); | alias Map = Obj[string]; | | Obj obj = "hello"; | assert(obj._is!string); | assert(obj.trustedGet!string == "hello"); | obj = 42.0; | assert(obj.get!double == 42); | obj = ["customer": Obj("John"), "paid": Obj(23.95)]; | assert(obj.get!Map["customer"] == "John"); |} | |/++ |Type set resolution template used to construct $(LREF Algebraic) . |+/ |template TypeSet(T...) |{ | import std.meta: staticSort, staticMap, allSatisfy, anySatisfy; | // sort types by sizeof and them mangleof | // but typeof(null) goes first | static if (is(staticMap!(TryRemoveConst, T) == T)) | static if (is(NoDuplicates!T == T)) | static if (staticIsSorted!(TypeCmp, T)) | { | alias TypeSet = T; | } | else | alias TypeSet = .TypeSet!(staticSort!(TypeCmp, T)); | else | alias TypeSet = TypeSet!(NoDuplicates!T); | else | alias TypeSet = TypeSet!(staticMap!(TryRemoveConst, T)); |} | |// IonNull goes first as well |private template isIonNull(T) |{ | static if (is(T == TagInfo!(U, name), U, string name)) | enum isIonNull = .isIonNull!U; | else | enum isIonNull = T.stringof == "IonNull"; |} | |private template TypeCmp(A, B) |{ | enum bool TypeCmp = is(A == B) ? false: | is(A == typeof(null)) ? true: | is(B == typeof(null)) ? false: | isIonNull!A ? true: | isIonNull!B ? false: | is(A == void) || is(A == TagInfo!(void, vaname), string vaname) ? true: | is(B == void) || is(A == TagInfo!(void, vbname), string vbname) ? false: | A.sizeof < B.sizeof ? true: | A.sizeof > B.sizeof ? false: | A.mangleof < B.mangleof; |} | |/// |version(mir_core_test) unittest |{ | static struct S {} | alias C = S; | alias Int = int; | static assert(is(TypeSet!(S, int) == TypeSet!(Int, C))); | static assert(is(TypeSet!(S, int, int) == TypeSet!(Int, C))); | static assert(!is(TypeSet!(uint, S) == TypeSet!(int, S))); |} | |private template applyTags(string[] tagNames, T...) | if (tagNames.length == T.length) |{ | import std.meta: AliasSeq; | static if (tagNames.length == 0) | alias applyTags = AliasSeq!(); | else | alias applyTags = AliasSeq!(TagInfo!(T[0], tagNames[0]), .applyTags!(tagNames[1 .. $], T[1 .. $])); |} | |/++ |Checks if the type list is $(LREF TypeSet). |+/ |enum bool isTypeSet(T...) = is(T == TypeSet!T); | |/// |@safe pure version(mir_core_test) unittest |{ | static assert(isTypeSet!(TypeSet!())); | static assert(isTypeSet!(TypeSet!void)); | static assert(isTypeSet!(TypeSet!(void, int, typeof(null)))); |} | |/++ |Variant Type (aka Algebraic Type). | |The impllementation is defined as |---- |alias Variant(T...) = Algebraic!(TypeSet!T); |---- | |Compatible with BetterC mode. |+/ |alias Variant(T...) = Algebraic!(TypeSet!T); | |/// |@safe pure @nogc |version(mir_core_test) unittest |{ | Variant!(int, double, string) v = 5; | assert(v.get!int == 5); | v = 3.14; | assert(v == 3.14); | // auto x = v.get!long; // won't compile, type long not allowed | // v = '1'; // won't compile, type char not allowed |} | |/// Single argument Variant |// and Type with copy constructor |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | static struct S | { | int n; | this(ref return scope inout S rhs) inout | { | this.n = rhs.n + 1; | } | } | | Variant!S a = S(); | auto b = a; | | import mir.conv; | assert(a.get!S.n == 0); | assert(b.n == 1); //direct access of a member in case of all algebraic types has this member |} | |/// Empty type set |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | Variant!() a; | auto b = a; | assert(a.toHash == 0); | assert(a == b); | assert(a <= b && b >= a); | static assert(typeof(a).sizeof == 1); |} | |/// Small types |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | static struct S { ubyte d; } | static assert(Nullable!(byte, char, S).sizeof == 2); |} | |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | static struct S { ubyte[3] d; } | static assert(Nullable!(ushort, wchar, S).sizeof == 6); |} | |// /// opPostMove support |// @safe pure @nogc nothrow |// version(mir_core_test) unittest |// { |// import std.algorithm.mutation: move; | |// static struct S |// { |// uint s; | |// void opPostMove(const ref S old) nothrow |// { |// this.s = old.s + 1; |// } |// } | |// Variant!S a; | |// auto b = a.move; |// assert(b.s == 1); |// } | |/++ |Tagged Variant Type (aka Tagged Algebraic Type). | |Compatible with BetterC mode. | |Template has two declarations: |---- |// and |template TaggedVariant(T) | if (is(T == union)) |{ | ... |} |---- | |See_also: $(LREF Variant), $(LREF isTaggedVariant). |+/ |deprecated ("Use Algebraic!Union instead") |template TaggedVariant(T) | if (is(T == union)) |{ | alias TaggedVariant = Algebraic!T; |} | |/// Json Value with styles |@safe pure |version(mir_core_test) unittest |{ | enum Style { block, flow } | | static struct SomeMetadata { | int a; | @safe pure nothrow @nogc scope | int opCmp(scope const SomeMetadata rhs) const { return a - rhs.a; } | } | | static struct ParsePosition | { | string file, line, column; | | void toString()(scope ref W w) scope const | { | w.put(file); | if (line) { | w.put("("); w.put(line); | if (column) { w.put(","); w.put(column); } | w.put(")"); | } | } | } | | static union Json_ | { | typeof(null) null_; | bool boolean; | long integer; | double floating; | // Not, that `string` is't builtin type but an alias in `object.d` | // So we can use `string` as a name of the string field | immutable(char)[] string; | This[] array; | // commented out to test `opCmp` primitive | // This[immutable(char)[]] object; | | @algMeta: | bool active; | SomeMetadata metadata; | @algTransp: | Style style; | @algVerbose ParsePosition position; | } | | alias JsonAlgebraic = Algebraic!Json_; | | // typeof(null) has priority | static assert(JsonAlgebraic.Kind.init == JsonAlgebraic.Kind.null_); | static assert(JsonAlgebraic.Kind.null_ == 0); | | // Kind and AllowedTypes has the same order | static assert (is(JsonAlgebraic.AllowedTypes[JsonAlgebraic.Kind.array] == JsonAlgebraic[])); | static assert (is(JsonAlgebraic.AllowedTypes[JsonAlgebraic.Kind.boolean] == bool)); | static assert (is(JsonAlgebraic.AllowedTypes[JsonAlgebraic.Kind.floating] == double)); | static assert (is(JsonAlgebraic.AllowedTypes[JsonAlgebraic.Kind.integer] == long)); | static assert (is(JsonAlgebraic.AllowedTypes[JsonAlgebraic.Kind.null_] == typeof(null))); | // static assert (is(JsonAlgebraic.AllowedTypes[JsonAlgebraic.Kind.object] == JsonAlgebraic[string])); | | JsonAlgebraic v; | assert(v.kind == JsonAlgebraic.Kind.null_); | | v = 1; | assert(v.kind == JsonAlgebraic.Kind.integer); | assert(v == 1); | v = JsonAlgebraic(1); | assert(v == 1); | v = v.get!(long, double); | | v = "Tagged!"; | // member-based access. Simple! | assert(v.string == "Tagged!"); | // type-based access | assert(v.get!string == "Tagged!"); | assert(v.trustedGet!string == "Tagged!"); | | assert(v.kind == JsonAlgebraic.Kind.string); | | assert(v.get!"string" == "Tagged!"); // string-based get | assert(v.trustedGet!"string" == "Tagged!"); // string-based trustedGet | | assert(v.get!(JsonAlgebraic.Kind.string) == "Tagged!"); // Kind-based get | assert(v.trustedGet!(JsonAlgebraic.Kind.string) == "Tagged!"); // Kind-based trustedGet | | // checks | assert(v._is!string); // type based | assert(v._is!"string"); // string based | assert(v._is!(JsonAlgebraic.Kind.string)); // | | v = null; | assert(v.kind == JsonAlgebraic.Kind.null_); | | v = [JsonAlgebraic("str"), JsonAlgebraic(4.3)]; | | assert(v.kind == JsonAlgebraic.Kind.array); | assert(v.trustedGet!(JsonAlgebraic[])[1].kind == JsonAlgebraic.Kind.floating); | | JsonAlgebraic w = v; | w.style = Style.flow; | assert(v.style != w.style); | assert(v == w); | assert(v <= w); | assert(v >= w); | assert(v.toHash == w.toHash); | w.active = true; | assert(v != w); | assert(v.toHash != w.toHash); | assert(v.get!"array" == w.get!"array"); | assert(v < w); |} | |/// Wrapped algebraic with propogated primitives |@safe pure |version(mir_core_test) unittest |{ | static struct Response | { | private union Response_ | { | double float_; | immutable(char)[] string; | Response[] array; | Response[immutable(char)[]] table; | } | | alias ResponseAlgebraic = Algebraic!Response_; | | ResponseAlgebraic data; | alias Tag = ResponseAlgebraic.Kind; | | // propogates opEquals, opAssign, and other primitives | alias data this; | | static foreach (T; ResponseAlgebraic.AllowedTypes) | this(T v) @safe pure nothrow @nogc { data = v; } | } | | Response v = 3.0; | assert(v.kind == Response.Tag.float_); | v = "str"; | assert(v == "str"); |} | |/++ |Nullable $(LREF Variant) Type (aka Algebraic Type). | |The impllementation is defined as |---- |alias Nullable(T...) = Variant!(typeof(null), T); |---- | |In additional to common algebraic API the following members can be accesssed: |$(UL |$(LI $(LREF .Algebraic.isNull)) |$(LI $(LREF .Algebraic.nullify)) |$(LI $(LREF .Algebraic.get.2)) |) | |Compatible with BetterC mode. |+/ |alias Nullable(T...) = Variant!(typeof(null), T); | |/// ditto |Nullable!T nullable(T)(T t) |{ | import core.lifetime: forward; | return Nullable!T(forward!t); |} | |/++ |Single type `Nullable` |+/ |@safe pure @nogc |version(mir_core_test) unittest |{ | static assert(is(Nullable!int == Variant!(typeof(null), int))); | | Nullable!int a = 5; | assert(a.get!int == 5); | | a.nullify; | assert(a.isNull); | | a = 4; | assert(!a.isNull); | assert(a.get == 4); | assert(a == 4); | a = 4; | | a = null; | assert(a == null); |} | |/// Empty nullable type set support |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | Nullable!() a; | auto b = a; | assert(a.toHash == 0); | assert(a == b); | assert(a <= b && b >= a); | static assert(typeof(a).sizeof == 1); |} | |private bool contains(scope const char[][] names, scope const char[] member) |@safe pure nothrow @nogc |{ 0000000| foreach (name; names) 0000000| if (name == member) 0000000| return true; 0000000| return false; |} | |/++ |Algebraic implementation. |For more portable code, it is higly recommeded to don't use this template directly. |Instead, please use of $(LREF Variant) and $(LREF Nullable), which sort types. |+/ |struct Algebraic(T__...) |{ | import mir.internal.meta: getUDAs; | import core.lifetime: moveEmplace; | import mir.conv: emplaceRef; | import mir.reflection: isPublic, hasField, isProperty; | import std.meta: Filter, AliasSeq, ApplyRight, anySatisfy, allSatisfy, staticMap, templateOr, templateNot, templateAnd; | import std.traits: | hasElaborateAssign, | hasElaborateCopyConstructor, | hasElaborateDestructor, | hasMember, | hasUDA, | isAggregateType, | isAssociativeArray, | isDynamicArray, | isEqualityComparable, | isOrderingComparable, | Largest, | Unqual | ; | | static if (T__.length != 1) | { | private alias Types__ = T__; | private alias MetaInfo__ = T__[0 .. 0]; | enum immutable(char[][]) metaFieldNames__ = null; | enum immutable(char[][]) typeFieldNames__ = null; | } | else | static if (!is(T__[0] == union)) | { | private alias Types__ = T__; | private alias MetaInfo__ = T__[0 .. 0]; | enum immutable(char[][]) metaFieldNames__ = null; | enum immutable(char[][]) typeFieldNames__ = null; | } | else | { | private alias UMTypeInfoOf__(immutable(char)[] member) = TagInfo!( | typeof(__traits(getMember, T__[0], member)), | member, | getUDAs!(T__[0], member, algMeta), | getUDAs!(T__[0], member, algTransp), | getUDAs!(T__[0], member, algVerbose), | ); | | private alias UMGetType__(alias TI) = TI.Type; | private enum bool UMGetMeta(alias TI) = TI.meta; | | private alias AllInfo__ = staticMap!(UMTypeInfoOf__, __traits(allMembers, T__[0])); | private alias TypesInfo__ = Filter!(templateNot!UMGetMeta, AllInfo__); | private alias MetaInfo__ = Filter!(UMGetMeta, AllInfo__); | private alias Types__ = staticMap!(UMGetType__, TypesInfo__); | | /++ | +/ | static immutable char[][] metaFieldNames__ = () { | immutable(char)[][] ret; | foreach (T; MetaInfo__) | ret ~= T.tag; | return ret; | } (); | | /++ | +/ | static immutable char[][] typeFieldNames__ = () { | immutable(char)[][] ret; | foreach (T; TypesInfo__) | ret ~= T.tag; | return ret; | } (); | } | | private enum bool variant_test__ = is(Types__ == AliasSeq!(typeof(null), double)); | | /++ | Allowed types list | See_also: $(LREF TypeSet) | +/ | alias AllowedTypes = AliasSeq!(ReplaceTypeUnless!(.isVariant, .This, Algebraic!T__, Types__)); | | version(mir_core_test) | static if (variant_test__) | /// | unittest | { | import std.meta: AliasSeq; | | alias V = Nullable! | ( | This*, | string, | double, | bool, | ); | | static assert(is(V.AllowedTypes == TypeSet!( | typeof(null), | bool, | string, | double, | V*))); | } | | static foreach (i, T; MetaInfo__) | mixin ("MetaInfo__[" ~ i.stringof ~ "].Type " ~ T.tag ~";"); | | private alias _Payload = Replace!(void, _Void!(), Replace!(typeof(null), _Null!(), AllowedTypes)); | | private static union Storage__ | { | _Payload payload; | | static foreach (int i, P; _Payload) | mixin(`alias _member_` ~ i.stringof ~ ` = payload[` ~ i.stringof ~ `];`); | | static if (AllowedTypes.length == 0 || is(AllowedTypes == AliasSeq!(typeof(null))) || is(AllowedTypes == AliasSeq!void)) | ubyte[0] bytes; | else | ubyte[Largest!_Payload.sizeof] bytes; | } | | private Storage__ storage__; | | static if (AllowedTypes.length > 1) | { | static if ((Storage__.alignof & 1) && _Payload.length <= ubyte.max) | private alias ID__ = ubyte; | else | static if ((Storage__.alignof & 2) && _Payload.length <= ushort.max) | private alias ID__ = ushort; | else | // static if (Storage__.alignof & 3) | private alias ID__ = uint; | // else | // private alias ID__ = ulong; | | ID__ identifier__; | } | else | { | private alias ID__ = uint; | enum ID__ identifier__ = 0; | } | | version (D_Ddoc) | { | /++ | Algebraic Kind. | | Defined as enum for tagged algebraics and as unsigned for common algebraics. | | The Kind enum contains the members defined using tag names. | | If the algebraic type is $(LREF Nullable) then the default Kind enum member has zero value and corresponds to `typeof(null)`. | | See_also: $(LREF TaggedVariant). | +/ | enum Kind { _not_me_but_tags_name_list_ } | } | | static if (typeFieldNames__.length) | { | version (D_Ddoc){} | else | { | mixin(enumKindText(typeFieldNames__)); | | } | } | else | { | version (D_Ddoc){} | else | { | alias Kind = ID__; | } | } | | /++ | Returns: $(LREF .Algebraic.Kind). | | Defined as enum for tagged algebraics and as unsigned for common algebraics. | See_also: $(LREF TaggedVariant). | +/ | Kind kind() const @safe pure nothrow @nogc @property | { | assert(identifier__ <= Kind.max); | return cast(Kind) identifier__; | } | | static if (anySatisfy!(hasElaborateDestructor, _Payload)) | ~this() @trusted | { | S: switch (identifier__) | { | static foreach (i, T; AllowedTypes) | static if (hasElaborateDestructor!T) | { | case i: | (*cast(Unqual!(_Payload[i])*)&storage__.payload[i]).__xdtor; | break S; | } | default: | } | version(mir_secure_memory) | storage__.bytes = 0xCC; | } | | // static if (anySatisfy!(hasOpPostMove, _Payload)) | // void opPostMove(const ref typeof(this) old) | // { | // S: switch (identifier__) | // { | // static foreach (i, T; AllowedTypes) | // static if (hasOpPostMove!T) | // { | // case i: | // this.storage__.payload[i].opPostMove(old.storage__.payload[i]); | // return; | // } | // default: return; | // } | // } | | static if (AllowedTypes.length) | { | static if (!__traits(compiles, (){ _Payload[0] arg; })) | { | @disable this(); | } | | static if (allSatisfy!(isDynamicArray, AllowedTypes)) | { | auto length()() const @property | { | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | return trustedGet!T().length; | } | default: assert(0); | } | } | | auto length()(size_t length) @property | { | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | return trustedGet!T().length = length; | } | default: assert(0); | } | } | | alias opDollar(size_t pos : 0) = length; | | /// Returns: slice type of `Slice!(IotaIterator!size_t)` | size_t[2] opSlice(size_t dimension)(size_t i, size_t j) @safe scope const | if (dimension == 0) | in(i <= j, "Algebraic.opSlice: the left opSlice boundary must be less than or equal to the right bound.") | { | return [i, j]; | } | | auto opIndex()(size_t index) | { | return this.visit!(a => a[index]); | } | | auto opIndex()(size_t index) const | { | return this.visit!(a => a[index]); | } | | auto opIndex()(size_t[2] index) | { | auto ret = this; | S: switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | ret.trustedGet!T() = ret.trustedGet!T()[index[0] .. index[1]]; | break S; | } | default: assert(0); | } | return ret; | } | | auto opIndexAssign(T)(T value, size_t index) | { | return this.tryMatch!((ref array, ref value) => array[index] = value)(value); | } | } | } | | /// Construct an algebraic type from its subset. | this(RhsTypes...)(Algebraic!RhsTypes rhs) | if (allSatisfy!(Contains!AllowedTypes, Algebraic!RhsTypes.AllowedTypes)) | { | import core.lifetime: move; | static if (is(RhsTypes == Types__)) | this = move(rhs); | else | { | static foreach (member; metaFieldNames__) | static if (Algebraic!RhsTypes.metaFieldNames__.contains(member)) | __traits(getMember, this, member) = move(__traits(getMember, rhs, member)); | | switch (rhs.identifier__) | { | static foreach (i, T; Algebraic!RhsTypes.AllowedTypes) | { | case i: | static if (__traits(compiles, __ctor(move(rhs.trustedGet!T)))) | __ctor(move(rhs.trustedGet!T)); | else | __ctor(rhs.trustedGet!T); | return; | } | default: | assert(0, variantMemberExceptionMsg); | } | } | } | | version(mir_core_test) | static if (variant_test__) | /// | unittest | { | alias Float = Variant!(float, double); | alias Int = Variant!(long, int); | alias Number = Variant!(Float.AllowedTypes, Int.AllowedTypes); | | Float fp = 3.0; | Number number = fp; // constructor call | assert(number == 3.0); | | Int integer = 12L; | number = Number(integer); | assert(number == 12L); | } | | static if (!allSatisfy!(isCopyable, AllowedTypes)) | @disable this(this); | else | static if (anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) | { | import std.meta: Filter; | private alias CC_AllowedTypes = Filter!(hasElaborateCopyConstructor, AllowedTypes); | // private enum _allCanImplicitlyRemoveConst = allSatisfy!(canImplicitlyRemoveConst, AllowedTypes); | // private enum _allCanRemoveConst = allSatisfy!(canRemoveConst, AllowedTypes); | // private enum _allHaveImplicitSemiMutableConstruction = _allCanImplicitlyRemoveConst && _allHaveMutableConstruction; | | static if (__VERSION__ < 2094) | private static union _StorageI(uint i) | { | _Payload[i] payload; | ubyte[Storage__.bytes.length] bytes; | } | | static if (allSatisfy!(hasInoutConstruction, CC_AllowedTypes)) | this(return ref scope inout Algebraic rhs) inout | { | static foreach (member; metaFieldNames__) | __traits(getMember, this, member) = __traits(getMember, rhs, member); | | static if (AllowedTypes.length > 1) this.identifier__ = rhs.identifier__; | static foreach (int i, T; AllowedTypes) | static if (!is(T == typeof(null)) && !is(T == void)) | { | if (identifier__ == i) | { | static if (__VERSION__ < 2094) | { | storage__.bytes = () inout @trusted { | auto ret = inout _StorageI!i(rhs.trustedGet!T); | return ret.bytes; | } (); | return; | } | else | { | storage__ = () inout { | mixin(`inout Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs.trustedGet!T };`); | return ret; | } (); | return; | } | } | } | } | else | { | static if (allSatisfy!(hasMutableConstruction, CC_AllowedTypes)) | this(return ref scope Algebraic rhs) | { | static foreach (member; metaFieldNames__) | __traits(getMember, this, member) = __traits(getMember, rhs, member); | | static if (AllowedTypes.length > 1) this.identifier__ = rhs.identifier__; | static foreach (int i, T; AllowedTypes) | static if (!is(T == typeof(null)) && !is(T == void)) | { | if (identifier__ == i) | { | storage__ = () { | mixin(`Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs.trustedGet!T };`); | return ret; | } (); | return; | } | } | } | | static if (allSatisfy!(hasConstConstruction, CC_AllowedTypes)) | this(return ref scope const Algebraic rhs) const | { | static foreach (member; metaFieldNames__) | __traits(getMember, this, member) = __traits(getMember, rhs, member); | | static if (AllowedTypes.length > 1) this.identifier__ = rhs.identifier__; | static foreach (int i, T; AllowedTypes) | static if (!is(T == typeof(null)) && !is(T == void)) | { | if (identifier__ == i) | { | storage__ = () const { | mixin(`const Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs.trustedGet!T };`); | return ret; | } (); | return; | } | } | } | | static if (allSatisfy!(hasImmutableConstruction, CC_AllowedTypes)) | this(return ref scope immutable Algebraic rhs) immutable | { | static foreach (member; metaFieldNames__) | __traits(getMember, this, member) = __traits(getMember, rhs, member); | | static if (AllowedTypes.length > 1) this.identifier__ = rhs.identifier__; | static foreach (int i, T; AllowedTypes) | static if (!is(T == typeof(null)) && !is(T == void)) | { | if (identifier__ == i) | { | storage__ = () immutable { | mixin(`immutable Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs.trustedGet!T };`); | return ret; | } (); | return; | } | } | } | | static if (allSatisfy!(hasSemiImmutableConstruction, CC_AllowedTypes)) | this(return ref scope const Algebraic rhs) immutable | { | static foreach (member; metaFieldNames__) | __traits(getMember, this, member) = __traits(getMember, rhs, member); | | static if (AllowedTypes.length > 1) this.identifier__ = rhs.identifier__; | static foreach (int i, T; AllowedTypes) | static if (!is(T == typeof(null)) && !is(T == void)) | { | if (identifier__ == i) | { | storage__ = () const { | mixin(`immutable Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs.trustedGet!T };`); | return ret; | } (); | return; | } | } | } | | static if (allSatisfy!(hasSemiMutableConstruction, CC_AllowedTypes)) | this(return ref scope const Algebraic rhs) | { | static foreach (member; metaFieldNames__) | __traits(getMember, this, member) = __traits(getMember, rhs, member); | | static if (AllowedTypes.length > 1) this.identifier__ = rhs.identifier__; | static foreach (int i, T; AllowedTypes) | static if (!is(T == typeof(null)) && !is(T == void)) | { | if (identifier__ == i) | { | storage__ = () const { | mixin(`const Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs.trustedGet!T };`); | return ret; | } (); | return; | } | } | } | } | } | | /++ | +/ | size_t toHash() scope @trusted const pure nothrow @nogc | { | size_t hash; | | | static if (AllowedTypes.length == 0 || is(AllowedTypes == AliasSeq!(typeof(null)))) | { | } | else{S: | switch (identifier__) | { | import std.traits: isArray; | static foreach (i, T; AllowedTypes) | { | case i: { | static if (is(T == void)) | hash = i; | else | static if (is(T == typeof(null))) | hash = i; | else | static if (typeFieldNames__.length) // force for tagged types | { | static if (__traits(hasMember, T, "toHash")) | hash = trustedGet!T.toHash; | else | static if (isArray!T) | foreach (ref e; trustedGet!T) | static if (__traits(hasMember, typeof(e), "toHash")) | hash = hashOf(e.toHash, hash); | else | hash = hashOf(e, hash); | else | hash = hashOf(trustedGet!T); | } | else | static if (__traits(compiles, hashOf(trustedGet!T.hashOf, i ^ hash))) | hash = hashOf(trustedGet!T.hashOf, i ^ hash); | else | { | debug pragma(msg, "Mir warning: can't compute hash. Expexted `size_t toHash() scope @safe const pure nothrow @nogc` method for " ~ T.stringof); | hash = i; | } | break S; | } | } | default: assert(0); | }} | | static foreach (i, T; MetaInfo__) | static if (!T.transparent) | { | static if (is(MetaFieldsTypes[i] == class) || is(MetaFieldsTypes[i] == interface)) | {{ | scope eqfun = delegate() { | hash = hashOf(__traits(getMember, this, T.tag), hash); | }; | trustedAllAttr(eqfun)(); | }} | else | hash = hashOf(__traits(getMember, this, T.tag), hash); | } | return hash; | } | | /// | bool opEquals()(scope const Algebraic rhs) scope @trusted const pure nothrow @nogc | { | return opEquals(rhs); | } | | /// ditto | bool opEquals()(scope ref const Algebraic rhs) scope @trusted const pure nothrow @nogc | { | static foreach (i, T; MetaInfo__) | static if (!T.transparent) | { | static if (is(MetaFieldsTypes[i] == class) || is(MetaFieldsTypes[i] == interface)) | {{ | scope eqfun = delegate() { | return __traits(getMember, this, T.tag) != __traits(getMember, rhs, T.tag); | }; | if (trustedAllAttr(eqfun)()) | return false; | }} | else | if (__traits(getMember, this, T.tag) != __traits(getMember, rhs, T.tag)) | return false; | } | | static if (AllowedTypes.length == 0) | { | return true; | } | else | { | if (this.identifier__ != rhs.identifier__) | return false; | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (is(T == void)) | return rhs._is!void; | else | static if (is(T == class) || is(T == interface)) | {{ | scope eqfun = delegate() { | return this.trustedGet!T == rhs.trustedGet!T; | }; | return trustedAllAttr(eqfun)(); | }} | else | static if (__traits(isFloating, T)) | return this.trustedGet!T == rhs.trustedGet!T || (this.trustedGet!T != this.trustedGet!T && rhs.trustedGet!T != rhs.trustedGet!T); | else | return this.trustedGet!T == rhs.trustedGet!T; | } | default: assert(0); | } | } | } | | /++ | +/ | static if (!anySatisfy!(templateOr!(isAssociativeArray, templateAnd!(isAggregateType, templateNot!hasOpCmp)), staticMap!(basicElementType, AllowedTypes))) | int opCmp()(auto ref scope const typeof(this) rhs) scope @trusted const pure nothrow @nogc | { | static foreach (i, T; MetaInfo__) | static if (!T.transparent) | { | static if (__traits(compiles, __cmp(__traits(getMember, this, T.tag), __traits(getMember, rhs, T.tag)))) | { | if (auto d = __cmp(__traits(getMember, this, T.tag), __traits(getMember, rhs, T.tag))) | return d; | } | else | static if (__traits(hasMember, __traits(getMember, this, T.tag), "opCmp") && !is(MetaFieldsTypes[i] == U*, U)) | { | if (auto d = __traits(getMember, this, T.tag).opCmp(__traits(getMember, rhs, T.tag))) | return d; | } | else | { | if (auto d = __traits(getMember, this, T.tag) < __traits(getMember, rhs, T.tag) ? -1 : __traits(getMember, this, T.tag) > __traits(getMember, rhs, T.tag) ? +1 : 0) | return d; | } | } | | | static if (AllowedTypes.length == 0) | { | return 0; | } | else | { | import std.traits: isArray; | if (auto d = int(this.identifier__) - int(rhs.identifier__)) | return d; | import std.traits: isArray, isPointer; | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (__traits(hasMember, T, "opCmp") && !isPointer!T) | {{ | auto ret = this.trustedGet!T.opCmp(rhs.trustedGet!T); | static if (is(typeof(ret) == int)) | return ret; | else | return ret < 0 ? -1 : ret > 0 ? 1 : 0; | }} | else | static if (!isArray!T) | return this.trustedGet!T < rhs.trustedGet!T ? -1 : | this.trustedGet!T > rhs.trustedGet!T ? +1 : 0; | else | return __cmp(trustedGet!T, rhs.trustedGet!T); | } | default: assert(0); | } | } | } | | /// Requires mir-algorithm package | immutable(char)[] toString()() @trusted pure scope const | { | static if (AllowedTypes.length == 0) | { | return "Algebraic"; | } | else | { | import mir.conv: to; | immutable(char)[] ret; | static foreach (i, member; metaFieldNames__) | { | static if (__traits(compiles, { auto s = to!(immutable(char)[])(__traits(getMember, this, member));})) | // should be passed by value to workaround compiler bug | ret ~= to!(immutable(char)[])(__traits(getMember, this, member)); | else | ret ~= AllowedTypes[i].stringof; | ret ~= ", "; | } | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (is(T == void)) | ret ~= "void"; | else | static if (is(T == typeof(null))) | ret ~= "null"; | else | static if (__traits(compiles, { auto s = to!(immutable(char)[])(trustedGet!T);})) | // should be passed by value to workaround compiler bug | ret ~= to!(immutable(char)[])(trustedGet!T); | else | ret ~= AllowedTypes[i].stringof; | return ret; | } | default: assert(0); | } | } | } | | ///ditto | void toString(W)(ref scope W w) scope const @trusted pure | if (__traits(compiles, ()pure{ w.put("Algebraic"); })) | { | if (false) | return w.put("Algebraic"); | static if (AllowedTypes.length == 0) | { | return w.put("Algebraic"); | } | else | { | import mir.format: print; | static foreach (i, member; metaFieldNames__) | { | static if (__traits(compiles, { import mir.format: print; print(w, __traits(getMember, this, member)); })) | { import mir.format: print; print(w, __traits(getMember, this, member)); } | else | w.put(AllowedTypes[i].stringof); | w.put(", "); | } | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (is(T == void)) | w.put("void"); | else | static if (is(T == typeof(null))) | w.put("null"); | else | static if (__traits(compiles, { import mir.format: print; print(w, trustedGet!T); })) | toStringImpl!T(w); | else | w.put(AllowedTypes[i].stringof); | return; | } | default: assert(0); | } | } | } | | ///ditto | void toString(W)(ref scope W w) scope const @trusted | if (!__traits(compiles, ()pure{ w.put("Algebraic"); })) | { | if (false) | return w.put("Algebraic"); | static if (AllowedTypes.length == 0) | { | return w.put("Algebraic"); | } | else | { | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (is(T == void)) | return w.put("void"); | else | static if (is(T == typeof(null))) | return w.put("null"); | else | static if (__traits(compiles, { import mir.format: print; print(w, trustedGet!T); })) | return toStringImpl!T(w); | else | return w.put(AllowedTypes[i].stringof); | } | default: assert(0); | } | } | } | | private void toStringImpl(T, W)(ref scope W w) @safe scope const pure nothrow @nogc | { | import mir.format: print; | scope pfun = delegate() { | print(w, trustedGet!T); | }; | trustedAllAttr(pfun)(); | } | | static if (is(AllowedTypes[0] == typeof(null))) | { | /// | bool opCast(C)() const | if (is(C == bool)) | { | return identifier__ != 0; | } | | /// | Algebraic opCast(C)() const | if (is(C == Algebraic)) | { | return this; | } | | /// Defined if the first type is `typeof(null)` | bool isNull() const @property { return identifier__ == 0; } | /// ditto | void nullify() { this = null; } | | /// ditto | auto get()() | if (allSatisfy!(isCopyable, AllowedTypes[1 .. $]) && AllowedTypes.length != 2 && is(AllowedTypes[0] == typeof(null))) | { | import mir.utility: _expect; | if (_expect(!identifier__, false)) | { | throw variantNullException; | } | static if (AllowedTypes.length != 2) | { | Algebraic!(AllowedTypes[1 .. $]) ret; | S: switch (identifier__) | { | static foreach (i, T; AllowedTypes[1 .. $]) | { | { | case i + 1: | if (!hasElaborateCopyConstructor!T && !__ctfe) | goto default; | static if (is(T == void)) | ret = ret._void; | else | ret = this.trustedGet!T; | break S; | } | } | default: | ret.storage__.bytes = this.storage__.bytes; | static if (ret.AllowedTypes.length > 1) | ret.identifier__ = cast(typeof(ret.identifier__))(this.identifier__ - 1); | } | return ret; | } | } | | static if (AllowedTypes.length == 2) | { | /++ | Gets the value if not null. If `this` is in the null state, and the optional | parameter `fallback` was provided, it will be returned. Without `fallback`, | calling `get` with a null state is invalid. | | When the fallback type is different from the Nullable type, `get(T)` returns | the common type. | | Params: | fallback = the value to return in case the `Nullable` is null. | | Returns: | The value held internally by this `Nullable`. | +/ | auto ref inout(AllowedTypes[1]) get() return inout | { | assert(identifier__, "Called `get' on null Nullable!(" ~ AllowedTypes[1].stringof ~ ")."); | return trustedGet!(AllowedTypes[1]); | } | | version(mir_core_test) | static if (variant_test__) | /// | @safe pure nothrow @nogc | unittest | { | enum E { a = "a", b = "b" } | Nullable!E f = E.a; | auto e = f.get(); | static assert(is(typeof(e) == E), Nullable!E.AllowedTypes.stringof); | assert(e == E.a); | | assert(f.get(E.b) == E.a); | | f = null; | assert(f.get(E.b) == E.b); | } | | /// ditto | @property auto ref inout(AllowedTypes[1]) get()(auto ref inout(AllowedTypes[1]) fallback) return inout | { | return isNull ? fallback : get(); | } | } | } | | /++ | Checks if the underlaying type is an element of a user provided type set. | +/ | bool _is(R : Algebraic!RetTypes, RetTypes...)() @safe pure nothrow @nogc const @property | if (allSatisfy!(Contains!AllowedTypes, Algebraic!RetTypes.AllowedTypes)) | { | static if (is(RetTypes == Types__)) | return true; | else | { | import std.meta: staticIndexOf; | import std.traits: CopyTypeQualifiers; | alias RhsAllowedTypes = Algebraic!RetTypes.AllowedTypes; | alias Ret = CopyTypeQualifiers!(This, Algebraic!RetTypes); | // uint rhsTypeId; | switch (identifier__) | { | foreach (i, T; AllowedTypes) | static if (staticIndexOf!(T, RhsAllowedTypes) >= 0) | { | case i: | return true; | } | default: | return false; | } | } | } | | /// ditto | bool _is(RetTypes...)() @safe pure nothrow @nogc const @property | if (RetTypes.length > 1) | { | return this._is!(Variant!RetTypes); | } | | /++ | `nothrow` $(LREF .Algebraic.get) alternative that returns an algebraic subset. | +/ | auto ref trustedGet(R : Algebraic!RetTypes, this This, RetTypes...)() return @property | if (allSatisfy!(Contains!AllowedTypes, Algebraic!RetTypes.AllowedTypes)) | { | static if (is(RetTypes == Types__)) | return this; | else | { | import std.meta: staticIndexOf; | import std.traits: CopyTypeQualifiers; | alias RhsAllowedTypes = Algebraic!RetTypes.AllowedTypes; | alias Ret = CopyTypeQualifiers!(This, Algebraic!RetTypes); | // uint rhsTypeId; | switch (identifier__) | { | foreach (i, T; AllowedTypes) | static if (staticIndexOf!(T, RhsAllowedTypes) >= 0) | { | case i: | static if (is(T == void)) | return (()@trusted => cast(Ret) Ret._void)(); | else | return Ret(trustedGet!T); | } | default: | assert(0, variantMemberExceptionMsg); | } | } | } | | /// ditto | template trustedGet(RetTypes...) | if (RetTypes.length > 1) | { | /// | auto ref trustedGet(this This)() return | { | return this.trustedGet!(Variant!RetTypes); | } | } | | version(mir_core_test) | static if (variant_test__) | /// | @safe pure nothrow @nogc | unittest | { | alias Float = Variant!(float, double); | alias Int = Variant!(long, int); | alias Number = Variant!(Float.AllowedTypes, Int.AllowedTypes); | | Number number = 3.0; | assert(number._is!Float); | auto fp = number.trustedGet!Float; | static assert(is(typeof(fp) == Float)); | assert(fp == 3.0); | | // type list overload | number = 12L; | assert(number._is!(int, long)); | auto integer = number.trustedGet!(int, long); | static assert(is(typeof(integer) == Int)); | assert(integer == 12L); | } | | static if (typeFieldNames__.length) | { | /// `trustedGet` overload that accept $(LREF .Algebraic.Kind). | alias trustedGet(Kind kind) = trustedGet!(AllowedTypes[kind]); | /// ditto | alias trustedGet(immutable(char)[] kind) = trustedGet!(__traits(getMember, Kind, kind)); | } | | /++ | Gets an algebraic subset. | | Throws: Exception if the storage contains value of the type that isn't represented in the allowed type set of the requested algebraic. | +/ | auto ref get(R : Algebraic!RetTypes, this This, RetTypes...)() return @property | if (allSatisfy!(Contains!AllowedTypes, Algebraic!RetTypes.AllowedTypes)) | { | static if (is(RetTypes == Types__)) | return this; | else | { | import std.meta: staticIndexOf; | import std.traits: CopyTypeQualifiers; | alias RhsAllowedTypes = Algebraic!RetTypes.AllowedTypes; | alias Ret = CopyTypeQualifiers!(This, Algebraic!RetTypes); | // uint rhsTypeId; | switch (identifier__) | { | foreach (i, T; AllowedTypes) | static if (staticIndexOf!(T, RhsAllowedTypes) >= 0) | { | case i: | static if (is(T == void)) | return (()@trusted => cast(Ret) Ret._void)(); | else | return Ret(trustedGet!T); | } | default: | throw variantMemberException; | } | } | } | | /// ditto | template get(RetTypes...) | if (RetTypes.length > 1) | { | /// | auto ref get(this This)() return | { | return this.get!(Variant!RetTypes); | } | } | | version(mir_core_test) | static if (variant_test__) | /// | @safe pure @nogc | unittest | { | alias Float = Variant!(float, double); | alias Int = Variant!(long, int); | alias Number = Variant!(Float.AllowedTypes, Int.AllowedTypes); | | Number number = 3.0; | auto fp = number.get!Float; | static assert(is(typeof(fp) == Float)); | assert(fp == 3.0); | | // type list overload | number = 12L; | auto integer = number.get!(int, long); | static assert(is(typeof(integer) == Int)); | assert(integer == 12L); | } | | static if (typeFieldNames__.length) | { | /// `get` overload that accept $(LREF .Algebraic.Kind). | alias get(Kind kind) = get!(AllowedTypes[kind]); | /// ditto | alias get(immutable(char)[] kind) = get!(__traits(getMember, Kind, kind)); | | /// `_is` overload that accept $(LREF .Algebraic.Kind). | alias _is(Kind kind) = _is!(AllowedTypes[kind]); | /// ditto | alias _is(immutable(char)[] kind) = _is!(__traits(getMember, Kind, kind)); | | static foreach (member; typeFieldNames__) | mixin ("alias " ~ member ~ `() = get!"` ~ member ~ `";`); | } | | private alias _ReflectionTypes = AllowedTypes[is(AllowedTypes[0] == typeof(null)) .. $]; | | static if (_ReflectionTypes.length) | this(this This, Args...)(auto ref Args args) | if (Args.length && (Args.length > 1 || !isLikeVariant!(Args[0]))) | { | import std.traits: CopyTypeQualifiers; | import core.lifetime: forward; | | template CanCompile(T) | { | alias Q = CopyTypeQualifiers!(This, T); | enum CanCompile = __traits(compiles, new Q(forward!args)); | } | | alias TargetType = Filter!(CanCompile, _ReflectionTypes); | static if (TargetType.length == 0) | static assert(0, typeof(this).stringof ~ ".this: no types can be constructed with arguments " ~ Args.stringof); | static assert(TargetType.length == 1, typeof(this).stringof ~ ".this: multiple types " ~ TargetType.stringof ~ " can be constructed with arguments " ~ Args.stringof); | alias TT = TargetType[0]; | static if (is(TT == struct) || is(TT == union)) | this(CopyTypeQualifiers!(This, TT)(forward!args)); | else | this(new CopyTypeQualifiers!(This, TT)(forward!args)); | } | | static if (_ReflectionTypes.length && allSatisfy!(isSimpleAggregateType, _ReflectionTypes)) | { | static foreach (member; AllMembersRec!(_ReflectionTypes[0])) | static if ( | !.algebraicMembers.contains(member) && | !metaFieldNames__.contains(member) && | !typeFieldNames__.contains(member) && | !(member.length >= 2 && (member[0 .. 2] == "__" || member[$ - 2 .. $] == "__"))) | static if (allSatisfy!(ApplyRight!(hasMember, member), _ReflectionTypes)) | static if (!anySatisfy!(ApplyRight!(isMemberType, member), _ReflectionTypes)) | static if (allSatisfy!(ApplyRight!(isSingleMember, member), _ReflectionTypes)) | static if (allSatisfy!(ApplyRight!(isPublic, member), _ReflectionTypes)) | { | static if (allSatisfy!(ApplyRight!(hasField, member), _ReflectionTypes) && NoDuplicates!(staticMap!(ApplyRight!(memberTypeOf, member), _ReflectionTypes)).length == 1) | { | mixin(`ref ` ~ member ~q{()() inout return @trusted pure nothrow @nogc @property { return this.getMember!member; }}); | } | else | static if (allSatisfy!(ApplyRight!(templateOr!(hasField, isProperty), member), _ReflectionTypes)) | { | mixin(`auto ref ` ~ member ~q{(this This, Args...)(auto ref Args args) @property { static if (args.length) { import core.lifetime: forward; return this.getMember!member = forward!args; } else return this.getMember!member; }}); | } | static if (allSatisfy!(ApplyRight!(templateNot!(templateOr!(hasField, isProperty)), member), _ReflectionTypes)) | { | mixin(`template ` ~ member ~`(TArgs...) { auto ref ` ~ member ~q{(this This, Args...)(auto ref Args args) { static if (args.length) { import core.lifetime: forward; return this.getMember!(member, TArgs)(forward!args); } else return this.getMember!(member, TArgs); }} ~ `}`); | } | } | } | | /// | ref opAssign(RhsTypes...)(Algebraic!RhsTypes rhs) return @trusted | if (allSatisfy!(Contains!AllowedTypes, Algebraic!RhsTypes.AllowedTypes) && !is(Algebraic == Algebraic!RhsTypes)) | { | import core.lifetime: forward; | this = this.init; | __ctor(forward!rhs); | return this; | } | | // pragma(msg, AllowedTypes); | | static foreach (int i, T; AllowedTypes) | { | /// Zero cost always nothrow `get` alternative | auto ref trustedGet(E)() @trusted @property return inout nothrow | if (is(E == T)) | { | assert (i == identifier__); | static if (is(T == typeof(null))) | return null; | else | static if (is(T == void)) | return; | else | return storage__.payload[i]; | } | | /++ | Throws: Exception if the storage contains value of other type | +/ | auto ref get(E)() @property return inout | if (is(E == T)) | { | import mir.utility: _expect; | static if (AllowedTypes.length > 1) | { | if (_expect(i != identifier__, false)) | { | throw variantException; | } | } | return trustedGet!T; | } | | /++ | Checks if the storage stores an allowed type. | +/ | bool _is(E)() const @property nothrow @nogc | if (is(E == T)) | { | return identifier__ == i; | } | | static if (is(T == void)) | { | /// Defined if `AllowedTypes` contains `void` | static Algebraic _void() | { | Algebraic ret; | ret.storage__ = () { | import core.lifetime: forward; | mixin(`Storage__ ret = { _member_` ~ i.stringof ~ ` : _Void!().init };`); | return ret; | } (); | ret.identifier__ = i; | return ret; | } | } | else | { | /// | static if (isCopyable!(const T) || is(Unqual!T == T)) | this(T value) | { | import core.lifetime: forward; | static if (is(T == typeof(null))) | auto rhs = _Null!()(); | else | alias rhs = forward!value; | | static if (__VERSION__ < 2094 && anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) | { | storage__.bytes = () @trusted { | auto ret = _StorageI!i(rhs); | return ret.bytes; | } (); | } | else | { | storage__ = () { | mixin(`Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs };`); | return ret; | } (); | } | static if (_Payload.length > 1) | identifier__ = i; | } | | /// ditto | static if (isCopyable!(const T)) | this(const T value) const | { | static if (is(T == typeof(null))) | auto rhs = _Null!()(); | else | alias rhs = value; | static if (__VERSION__ < 2094 && anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) | { | storage__.bytes = () const @trusted { | auto ret = const _StorageI!i(rhs); | return ret.bytes; | } (); | } | else | { | storage__ = () { | mixin(`const Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs };`); | return ret; | } (); | } | static if (_Payload.length > 1) | identifier__ = i; | } | | /// ditto | static if (isCopyable!(immutable T)) | this(immutable T value) immutable | { | static if (is(T == typeof(null))) | auto rhs = _Null!()(); | else | alias rhs = value; | static if (__VERSION__ < 2094 && anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) | { | storage__.bytes = () const @trusted { | auto ret = immutable _StorageI!i(rhs); | return ret.bytes; | } (); | } | else | { | storage__ = () { | mixin(`immutable Storage__ ret = { _member_` ~ i.stringof ~ ` : rhs };`); | return ret; | } (); | } | static if (_Payload.length > 1) | identifier__ = i; | } | | static if (__traits(compiles, (ref T a, ref T b) { moveEmplace(a, b); })) | /// | ref opAssign(T rhs) return @trusted | { | static foreach (T; MetaInfo__) | __traits(getMember, this, T.tag) = T.Type.init; | | import core.lifetime: forward; | this = this.init; | __ctor(forward!rhs); | return this; | } | | /++ | +/ | bool opEquals()(scope ref const UnqualRec!T rhs) scope @trusted const //pure nothrow @nogc | { | static if (AllowedTypes.length > 1) | if (identifier__ != i) | return false; | return trustedGet!T == rhs; | } | | ///ditto | bool opEquals()(scope const UnqualRec!T rhs) scope @trusted const //pure nothrow @nogc | { | return opEquals(rhs); | } | | /++ | +/ | auto opCmp()(auto ref scope const UnqualRec!T rhs) scope @trusted const pure nothrow @nogc | { | static if (AllowedTypes.length > 1) | if (auto d = int(identifier__) - int(i)) | return d; | static if (__traits(compiles, __cmp(trustedGet!T, rhs))) | return __cmp(trustedGet!T, rhs); | else | static if (__traits(hasMember, T, "opCmp") && !is(T == U*, U)) | return trustedGet!T.opCmp(rhs); | else | return trustedGet!T < rhs ? -1 : trustedGet!T > rhs ? +1 : 0; | } | | static if (is(Unqual!T == bool)) | { | private alias contains = Contains!AllowedTypes; | static if (contains!long && !contains!int) | { | this(int value) | { | this(long(value)); | } | | this(int value) const | { | this(long(value)); | } | | this(int value) immutable | { | this(long(value)); | } | | ref opAssign(int rhs) return @trusted | { | return opAssign(long(rhs)); | } | | auto opEquals()(int rhs) const | { | return opEquals(long(rhs)); | } | | auto opCmp()(int rhs) const | { | return opCmp(long(rhs)); | } | } | | static if (contains!ulong && !contains!uint) | { | this(uint value) | { | this(ulong(value)); | } | | this(uint value) const | { | this(ulong(value)); | } | | this(uint value) immutable | { | this(ulong(value)); | } | | ref opAssign(uint rhs) return @trusted | { | return opAssign(ulong(rhs)); | } | | auto opEquals()(uint rhs) const | { | return opEquals(ulong(rhs)); | } | | auto opCmp()(uint rhs) const | { | return opCmp(ulong(rhs)); | } | } | } | } | } | | static if (anySatisfy!(isErr, AllowedTypes)) | { | /++ | Determines if the variant holds value of some none-$(LREF isVariant) type. | The property is avaliable only for $(ResultVariant) | +/ | bool isOk() @safe pure nothrow @nogc const @property | { | switch (identifier__) | { | static foreach (i, T; AllowedTypes) | { | case i: | return !.isErr!T; | } | default: assert(0); | } | } | } |} | |/++ |Constructor and methods propagation. |+/ |version(mir_core_test) |unittest |{ | static struct Base | { | double d; | } | | static class Cc | { | // alias this members are supported | Base base; | alias base this; | | int a; | private string _b; | | @safe pure nothrow @nogc: | | override size_t toHash() scope const { return hashOf(_b) ^ a; } | | string b() const @property { return _b; } | void b(string b) @property { _b = b; } | | int retArg(int v) { return v; } | string retArgT(TArgs...)(int v) { return TArgs.stringof; } | | this(int a, string b) | { | this.a = a; | this._b = b; | } | } | | static struct S | { | string b; | int a; | | double retArg(double v) { return v; } | double retArgT(TArgs...)(int v) { return v * TArgs.length; } | | // alias this members are supported | Base base; | alias base this; | } | | static void inc(ref int a) { a++; } | | alias V = Nullable!(Cc, S); // or Variant! | | auto v = V(2, "str"); | assert(v._is!Cc); | assert(v.a == 2); | assert(v.b == "str"); | // members are returned by reference if possible | inc(v.a); | assert(v.a == 3); | v.b = "s"; | assert(v.b == "s"); | // alias this members are supported | v.d = 10; | assert(v.d == 10); | // method call support | assert(v.retArg(100)._is!int); | assert(v.retArg(100) == 100); | | // method with template args support | assert(v.retArgT!dchar(100)._is!string); | assert(v.retArgT!dchar(100) == "(dchar)"); | | v = V("S", 5); | assert(v._is!S); | assert(v.a == 5); | assert(v.b == "S"); | // members are returned by reference if possible | inc(v.a); | assert(v.a == 6); | v.b = "s"; | assert(v.b == "s"); | // alias this members are supported | v.d = 15; | assert(v.d == 15); | // method call support | assert(v.retArg(300)._is!double); | assert(v.retArg(300) == 300.0); |} | |// test CTFE |unittest |{ | static struct S { string s;} | alias V = Nullable!(double, S); | enum a = V(1.9); | static assert (a == 1.9); | enum b = V(S("str")); | static assert(b == S("str")); | static auto foo(int r) | { | auto s = V(S("str")); | s = r; | return s; | } | | static assert(foo(3) == 3); | static auto bar(int r) | { | auto s = V(S("str")); | s = r; | return s.visit!((double d) => d, (_)=> 0.0)(); | } | assert(bar(3) == 3); | static assert(bar(3) == 3); | | static auto bar3(int r) | { | auto s = V(S("str")); | s = r; | return s.match!((double d) => d, (_)=> "3")(); | } | assert(bar(3) == 3); | static assert(bar(3) == 3); |} | |@safe pure @nogc nothrow |version(mir_core_test) unittest |{ | import core.stdc.string: memcmp; | | static struct C(ubyte payloadSize, bool isPOD, bool hasToHash = true, bool hasOpEquals = true) | { | ubyte[payloadSize] _payload; | | const: | | static if (!isPOD) | { | this(this) {} | ~this() {} | } | | @safe pure nothrow @nogc: | | | static if (hasToHash) | size_t toHash() scope { return hashOf(_payload); } | | static if (hasOpEquals) | auto opEquals(ref const scope typeof(this) rhs) scope { return _payload == rhs._payload; } | auto opCmp(ref const scope typeof(this) rhs) @trusted scope { return memcmp(_payload.ptr, rhs._payload.ptr, _payload.length); } | } | | static foreach (size1; [1, 2, 4, 8, 10, 16, 20]) | static foreach (size2; [1, 2, 4, 8, 10, 16, 20]) | static if (size1 != size2) | static foreach (isPOD; [true, false]) | static foreach (hasToHash; [true, false]) | static foreach (hasOpEquals; [true, false]) | {{ | alias T = Variant!( | double, | C!(size1, isPOD, hasToHash, hasOpEquals), | C!(size2, isPOD, hasToHash, hasOpEquals)); | // static assert (__traits(compiles, T.init <= T.init)); | }} |} | |// const propogation |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | static struct S1 { immutable(ubyte)* value; } | static struct C1 { immutable(uint)* value; } | | alias V = Variant!(S1, C1); | const V v = S1(); | assert(v._is!S1); | V w = v; | w = v; | | immutable f = V(S1()); | auto t = immutable V(S1()); | // auto j = immutable V(t); | // auto i = const V(t); |} | |// ditto |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | static struct S2 { | uint* value; | this(return ref scope const typeof(this) rhs) {} | ref opAssign(typeof(this) rhs) return { return this; } | } | static struct C2 { const(uint)* value; } | | alias V = Variant!(S2, C2); | const V v = S2(); | V w = v; | w = S2(); | w = v; | w = cast(const) V.init; | | const f = V(S2()); | auto t = const V(f); |} | |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | static struct S3 { | uint* value; | this(return ref scope typeof(this) rhs) {} | this(return ref scope const typeof(this) rhs) const {} | this(return ref scope immutable typeof(this) rhs) immutable {} | } | static struct C3 { immutable(uint)* value; } | | S3 s; | S3 r = s; | r = s; | r = S3.init; | | alias V = Variant!(S3, C3); | V v = S3(); | V w = v; | w = S3(); | w = V.init; | w = v; | | immutable V e = S3(); | auto t = immutable V(S3()); | auto j = const V(t); | auto h = t; | | immutable V l = C3(); | auto g = immutable V(C3()); |} | |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | static struct S4 { | uint* value; | this(return ref scope const typeof(this) rhs) pure immutable {} | } | static struct C4 { immutable(uint)* value; } | | | S4 s; | S4 r = s; | r = s; | r = S4.init; | | alias V = Variant!(S4, C4); | V v = S4(); | V w = v; | w = S4(); | w = V.init; | w = v; | | { | const V e = S4(); | const k = w; | auto t = const V(k); | auto j = immutable V(k); | } | | immutable V e = S4(); | immutable k = w; | auto t = immutable V(S4()); | auto j = const V(t); | auto h = t; | | immutable V l = C4(); | import core.lifetime; | auto g = immutable V(C4()); | immutable b = immutable V(s); |} | |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | import core.lifetime: move; | | static struct S5 { | immutable(uint)* value; | this(return ref scope typeof(this) rhs) {} | this(return ref scope const typeof(this) rhs) immutable {} | } | static struct C5 { immutable(uint)* value; } | | S5 s; | S5 r = s; | r = s; | r = S5.init; | | alias V = Variant!(S5, C5); | V v = S5(); | V w = v; | w = S5(); | w = V.init; | w = v; | | immutable V e = S5(); | immutable f = V(S5()); | immutable k = w; | auto t = immutable V(S5()); | auto j = const V(t); | auto h = t; | | immutable V l = C5(); | import core.lifetime; | immutable n = w.move; | auto g = immutable V(C5()); | immutable b = immutable V(s); |} | |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | static struct S { | uint* value; | this(this) @safe pure nothrow @nogc {} | // void opAssign(typeof(this) rhs) {} | } | static struct C1 { const(uint)* value; } | | S s; | S r = s; | r = s; | r = S.init; | | alias V = Variant!(S, C1); | V v = S(); | V w = v; | w = S(); | w = V.init; | w = v; |} | |/++ |Applies a delegate or function to the given Variant depending on the held type, |ensuring that all types are handled by the visiting functions. |+/ |alias visit(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.compileTime, false); | |/// |@safe pure @nogc nothrow |version(mir_core_test) unittest |{ | alias Number = Variant!(int, double); | | Number x = 23; | Number y = 1.0; | | assert(x.visit!((int v) => true, (float v) => false)); | assert(y.visit!((int v) => false, (float v) => true)); |} | |/// |@safe pure @nogc |version(mir_core_test) unittest |{ | alias Number = Nullable!(int, double); | | Number z = null; // default | Number x = 23; | Number y = 1.0; | | () nothrow { | assert(x.visit!((int v) => true, (float v) => false)); | assert(y.visit!((int v) => false, (v) => true)); | assert(z.visit!((typeof(null) v) => true, (v) => false)); | } (); | | auto xx = x.get; | static assert (is(typeof(xx) == Variant!(int, double))); | assert(xx.visit!((int v) => v, (float v) => 0) == 23); | assert(xx.visit!((ref v) => v) == 23); | | x = null; | y.nullify; | | assert(x.isNull); | assert(y.isNull); | assert(z.isNull); | assert(z == y); |} | |/// Array primitives propagation |@safe pure version(mir_core_test) unittest |{ | Variant!(long[], double[]) array; | array = new long[3]; | array[2] = 100; | assert(array == [0L, 0, 100]); | assert(array.length == 3); | assert(array[2] == 100); | array.length = 4; | assert(array == [0L, 0, 100, 0]); | array = array[2 .. 3]; | assert(array.length == 1); | assert(array[0] == 100); | array[0] = 10.Variant!(long, double); | assert(array[0] == 10); |} | |/++ |Checks $(LREF .Algebraic.toString) and `void` |$(LREF Algerbraic)`.toString` requries `mir-algorithm` package |+/ |@safe pure nothrow version(mir_core_test) unittest |{ | import mir.conv: to; | enum MIR_ALGORITHM = __traits(compiles, { import mir.format; }); | | alias visitorHandler = visit!( | (typeof(null)) => "NULL", | () => "VOID", | (ref r) {r += 1;}, // returns void | ); | | alias secondOrderVisitorHandler = visit!( | () => "SO VOID", // void => to "RV VOID" | (str) => str, // string to => it self | ); | | alias V = Nullable!(void, int); | static assert(is(V == Variant!(typeof(null), void, int))); | | V variant; | | assert(secondOrderVisitorHandler(visitorHandler(variant)) == "NULL"); | assert(variant.toString == "null"); | | variant = V._void; | assert(variant._is!void); | assert(is(typeof(variant.get!void()) == void)); | | assert(secondOrderVisitorHandler(visitorHandler(variant)) == "VOID"); | assert(variant.toString == "void"); | | variant = 5; | | assert(secondOrderVisitorHandler(visitorHandler(variant)) == "SO VOID"); | assert(variant == 6); | assert(variant.toString == (MIR_ALGORITHM ? "6" : "int")); |} | |version(mir_core_test) |unittest |{ | Nullable!() value; | alias visitHandler = visit!((typeof(null)) => null, err); | auto d = visitHandler(value); | assert(d == value); |} | |/++ |Behaves as $(LREF visit) but doesn't enforce at compile time that all types can be handled by the visiting functions. |Throws: Exception if `naryFun!visitors` can't be called with provided arguments |+/ |alias tryVisit(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.exception, false); | |/// |@safe pure @nogc |version(mir_core_test) unittest |{ | alias Number = Variant!(int, double); | | Number x = 23; | | assert(x.tryVisit!((int v) => true)); |} | |/++ |Behaves as $(LREF visit) but doesn't enforce at compile time that all types can be handled by the visiting functions. |Returns: nullable variant, null value is used if `naryFun!visitors` can't be called with provided arguments. |+/ |alias optionalVisit(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.nullable, false); | |/// |@safe pure @nogc nothrow |version(mir_core_test) unittest |{ | static struct S { int a; } | | Variant!(S, double) variant; | | alias optionalVisitInst = optionalVisit!((ref value) => value + 0); | | // do nothing because of variant isn't initialized | Nullable!double result = optionalVisitInst(variant); | assert(result.isNull); | | variant = S(2); | // do nothing because of lambda can't compile | result = optionalVisitInst(variant); | assert(result == null); | | variant = 3.0; | result = optionalVisitInst(variant); | assert (result == 3.0); |} | |/++ |Behaves as $(LREF visit) but doesn't enforce at compile time that all types can be handled by the visiting functions. |Returns: optionally nullable type, null value is used if `naryFun!visitors` can't be called with provided arguments. |+/ |alias autoVisit(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.auto_, false); | | |/++ |Applies a delegate or function to the given arguments depending on the held type, |ensuring that all types are handled by the visiting functions. | |The handler supports multiple dispatch or multimethods: a feature of handler in which |a function or method can be dynamically dispatched based on the run time (dynamic) type or, |in the more general case, some other attribute of more than one of its arguments. | |Fuses algebraic types on return. | |See_also: $(HTTPS en.wikipedia.org/wiki/Multiple_dispatch, Multiple dispatch) |+/ |alias match(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.compileTime, true); | |/// |version(mir_core_test) |unittest |{ | static struct Asteroid { uint size; } | static struct Spaceship { uint size; } | alias SpaceObject = Variant!(Asteroid, Spaceship); | | alias collideWith = match!( | (Asteroid x, Asteroid y) => "a/a", | (Asteroid x, Spaceship y) => "a/s", | (Spaceship x, Asteroid y) => "s/a", | (Spaceship x, Spaceship y) => "s/s", | ); | | import mir.utility: min; | | // Direct access of a member in case of all algebraic types has this member | alias oops = (a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1; | | alias collide = (x, y) => oops(x, y) ? "big-boom" : collideWith(x, y); | | auto ea = Asteroid(1); | auto es = Spaceship(2); | auto oa = SpaceObject(ea); | auto os = SpaceObject(es); | | // Asteroid-Asteroid | assert(collide(ea, ea) == "a/a"); | assert(collide(ea, oa) == "a/a"); | assert(collide(oa, ea) == "a/a"); | assert(collide(oa, oa) == "a/a"); | | // Asteroid-Spaceship | assert(collide(ea, es) == "a/s"); | assert(collide(ea, os) == "a/s"); | assert(collide(oa, es) == "a/s"); | assert(collide(oa, os) == "a/s"); | | // Spaceship-Asteroid | assert(collide(es, ea) == "s/a"); | assert(collide(es, oa) == "s/a"); | assert(collide(os, ea) == "s/a"); | assert(collide(os, oa) == "s/a"); | | // Spaceship-Spaceship | assert(collide(es, es) == "big-boom"); | assert(collide(es, os) == "big-boom"); | assert(collide(os, es) == "big-boom"); | assert(collide(os, os) == "big-boom"); |} | |/++ |Behaves as $(LREF match) but doesn't enforce at compile time that all types can be handled by the visiting functions. |Throws: Exception if `naryFun!visitors` can't be called with provided arguments | |Fuses algebraic types on return. |+/ |alias tryMatch(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.exception, true); | |/// |version(mir_core_test) |unittest |{ | import std.exception: assertThrown; | static struct Asteroid { uint size; } | static struct Spaceship { uint size; } | alias SpaceObject = Variant!(Asteroid, Spaceship); | | alias collideWith = tryMatch!( | (Asteroid x, Asteroid y) => "a/a", | // No visitor for A/S pair | // (Asteroid x, Spaceship y) => "a/s", | (Spaceship x, Asteroid y) => "s/a", | (Spaceship x, Spaceship y) => "s/s", | ); | | import mir.utility: min; | // Direct access of a member in case of all algebraic types has this member | alias oops = (a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1; | | alias collide = (x, y) => oops(x, y) ? "big-boom" : collideWith(x, y); | | auto ea = Asteroid(1); | auto es = Spaceship(2); | auto oa = SpaceObject(ea); | auto os = SpaceObject(es); | | // Asteroid-Asteroid | assert(collide(ea, ea) == "a/a"); | assert(collide(ea, oa) == "a/a"); | assert(collide(oa, ea) == "a/a"); | assert(collide(oa, oa) == "a/a"); | | // Asteroid-Spaceship | assertThrown!Exception(collide(ea, es)); | assertThrown!Exception(collide(ea, os)); | assertThrown!Exception(collide(oa, es)); | assertThrown!Exception(collide(oa, os)); | | // can deduce the type based on other return values | static assert(is(typeof(collide(ea, os)) == string)); | static assert(is(typeof(collide(oa, es)) == string)); | static assert(is(typeof(collide(oa, os)) == string)); | | // Also allows newer compilers to detect combinations which always throw an exception | static if (is(typeof(collideWith(ea, es)) == noreturn)) | { | static assert(is(typeof(collide(ea, es)) == string)); | } | else | { | // not enough information to deduce the type from (ea, es) pair | static assert(is(typeof(collide(ea, es)) == void)); | } | | // Spaceship-Asteroid | assert(collide(es, ea) == "s/a"); | assert(collide(es, oa) == "s/a"); | assert(collide(os, ea) == "s/a"); | assert(collide(os, oa) == "s/a"); | | // Spaceship-Spaceship | assert(collide(es, es) == "big-boom"); | assert(collide(es, os) == "big-boom"); | assert(collide(os, es) == "big-boom"); | assert(collide(os, os) == "big-boom"); |} | |/++ |Behaves as $(LREF match) but doesn't enforce at compile time that all types can be handled by the visiting functions. |Returns: nullable variant, null value is used if `naryFun!visitors` can't be called with provided arguments. | |Fuses algebraic types on return. |+/ |alias optionalMatch(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.nullable, true); | |/// |version(mir_core_test) |unittest |{ | static struct Asteroid { uint size; } | static struct Spaceship { uint size; } | alias SpaceObject = Variant!(Asteroid, Spaceship); | | alias collideWith = optionalMatch!( | (Asteroid x, Asteroid y) => "a/a", | // No visitor for A/S pair | // (Asteroid x, Spaceship y) => "a/s", | (Spaceship x, Asteroid y) => "s/a", | (Spaceship x, Spaceship y) => "s/s", | ); | | import mir.utility: min; | // Direct access of a member in case of all algebraic types has this member | alias oops = (a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1; | | alias collide = (x, y) => oops(x, y) ? "big-boom".nullable : collideWith(x, y); | | auto ea = Asteroid(1); | auto es = Spaceship(2); | auto oa = SpaceObject(ea); | auto os = SpaceObject(es); | | // Asteroid-Asteroid | assert(collide(ea, ea) == "a/a"); | assert(collide(ea, oa) == "a/a"); | assert(collide(oa, ea) == "a/a"); | assert(collide(oa, oa) == "a/a"); | | // Asteroid-Spaceship | // assert(collide(ea, es).isNull); // Compiler error: incompatible types | assert(collideWith(ea, es).isNull); // OK | assert(collide(ea, os).isNull); | assert(collide(oa, es).isNull); | assert(collide(oa, os).isNull); | | | // Spaceship-Asteroid | assert(collide(es, ea) == "s/a"); | assert(collide(es, oa) == "s/a"); | assert(collide(os, ea) == "s/a"); | assert(collide(os, oa) == "s/a"); | | // Spaceship-Spaceship | assert(collide(es, es) == "big-boom"); | assert(collide(es, os) == "big-boom"); | assert(collide(os, es) == "big-boom"); | assert(collide(os, os) == "big-boom"); | | // check types | | static assert(!__traits(compiles, collide(Asteroid.init, Spaceship.init))); | static assert(is(typeof(collideWith(Asteroid.init, Spaceship.init)) == Nullable!())); | | static assert(is(typeof(collide(Asteroid.init, Asteroid.init)) == Nullable!string)); | static assert(is(typeof(collide(Asteroid.init, SpaceObject.init)) == Nullable!string)); | static assert(is(typeof(collide(SpaceObject.init, Asteroid.init)) == Nullable!string)); | static assert(is(typeof(collide(SpaceObject.init, SpaceObject.init)) == Nullable!string)); | static assert(is(typeof(collide(SpaceObject.init, Spaceship.init)) == Nullable!string)); | static assert(is(typeof(collide(Spaceship.init, Asteroid.init)) == Nullable!string)); | static assert(is(typeof(collide(Spaceship.init, SpaceObject.init)) == Nullable!string)); | static assert(is(typeof(collide(Spaceship.init, Spaceship.init)) == Nullable!string)); |} | |/++ |Behaves as $(LREF match) but doesn't enforce at compile time that all types can be handled by the visiting functions. |Returns: optionally nullable type, null value is used if `naryFun!visitors` can't be called with provided arguments. | |Fuses algebraic types on return. |+/ |alias autoMatch(visitors...) = visitImpl!(naryFun!visitors, Exhaustive.auto_, true); | |/// |version(mir_core_test) |unittest |{ | static struct Asteroid { uint size; } | static struct Spaceship { uint size; } | alias SpaceObject = Variant!(Asteroid, Spaceship); | | alias collideWith = autoMatch!( | (Asteroid x, Asteroid y) => "a/a", | // No visitor for A/S pair | // (Asteroid x, Spaceship y) => "a/s", | (Spaceship x, Asteroid y) => "s/a", | (Spaceship x, Spaceship y) => "s/s", | ); | | import mir.utility: min; | // Direct access of a member in case of all algebraic types has this member | alias oops = (a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1; | | import mir.conv: to; | alias collide = (x, y) => oops(x, y) ? "big-boom".to!(typeof(collideWith(x, y))) : collideWith(x, y); | | auto ea = Asteroid(1); | auto es = Spaceship(2); | auto oa = SpaceObject(ea); | auto os = SpaceObject(es); | | // Asteroid-Asteroid | assert(collide(ea, ea) == "a/a"); | assert(collide(ea, oa) == "a/a"); | assert(collide(oa, ea) == "a/a"); | assert(collide(oa, oa) == "a/a"); | | // Asteroid-Spaceship | // assert(collide(ea, es).isNull); // Compiler error: incompatible types | assert(collideWith(ea, es).isNull); // OK | assert(collide(ea, os).isNull); | assert(collide(oa, es).isNull); | assert(collide(oa, os).isNull); | | // Spaceship-Asteroid | assert(collide(es, ea) == "s/a"); | assert(collide(es, oa) == "s/a"); | assert(collide(os, ea) == "s/a"); | assert(collide(os, oa) == "s/a"); | | // Spaceship-Spaceship | assert(collide(es, es) == "big-boom"); | assert(collide(es, os) == "big-boom"); | assert(collide(os, es) == "big-boom"); | assert(collide(os, os) == "big-boom"); | | // check types | | static assert(!__traits(compiles, collide(Asteroid.init, Spaceship.init))); | static assert(is(typeof(collideWith(Asteroid.init, Spaceship.init)) == Nullable!())); | | static assert(is(typeof(collide(Asteroid.init, Asteroid.init)) == string)); | static assert(is(typeof(collide(SpaceObject.init, Asteroid.init)) == string)); | static assert(is(typeof(collide(Spaceship.init, Asteroid.init)) == string)); | static assert(is(typeof(collide(Spaceship.init, SpaceObject.init)) == string)); | static assert(is(typeof(collide(Spaceship.init, Spaceship.init)) == string)); | | static assert(is(typeof(collide(Asteroid.init, SpaceObject.init)) == Nullable!string)); | static assert(is(typeof(collide(SpaceObject.init, SpaceObject.init)) == Nullable!string)); | static assert(is(typeof(collide(SpaceObject.init, Spaceship.init)) == Nullable!string)); |} | |/++ |Applies a member handler to the given Variant depending on the held type, |ensuring that all types are handled by the visiting handler. |+/ |alias getMember(string member, TArgs...) = visitImpl!(getMemberHandler!(member, TArgs), Exhaustive.compileTime, false); | |/// |@safe pure @nogc nothrow |version(mir_core_test) unittest |{ | static struct S { auto bar(int a) { return a; } enum boolean = true; } | static struct C2 { alias bar = (double a) => a * 2; enum boolean = false; } | | alias V = Variant!(S, C2); | | V x = S(); | V y = C2(); | | static assert(is(typeof(x.getMember!"bar"(2)) == Variant!(int, double))); | assert(x.getMember!"bar"(2) == 2); | assert(y.getMember!"bar"(2) != 4); | assert(y.getMember!"bar"(2) == 4.0); | | // direct implementation | assert(x.bar(2) == 2); | assert(y.bar(2) != 4); | assert(y.bar(2) == 4.0); | assert(x.boolean); | assert(!y.boolean); |} | |/++ |Applies a member handler to the given Variant depending on the held type, |ensuring that all types are handled by the visiting handler. | |Fuses algebraic types on return. |+/ |alias matchMember(string member, TArgs...) = visitImpl!(getMemberHandler!(member, TArgs), Exhaustive.compileTime, true); | |/// |@safe pure @nogc nothrow |version(mir_core_test) unittest |{ | static struct S | { | Nullable!int m; | } | | static struct C1 | { | Variant!(float, double) m; | } | | alias V = Variant!(S, C1); | | V x = S(2.nullable); | V y = C1(Variant!(float, double)(4.0)); | | // getMember returns an algebraic of algebaics | static assert(is(typeof(x.getMember!"m") == Variant!(Variant!(float, double), Nullable!int))); | // matchMember returns a fused algebraic | static assert(is(typeof(x.matchMember!"m") == Nullable!(int, float, double))); | assert(x.matchMember!"m" == 2); | assert(y.matchMember!"m" != 4); | assert(y.matchMember!"m" == 4.0); |} | |/++ |Behaves as $(LREF getMember) but doesn't enforce at compile time that all types can be handled by the member visitor. |Throws: Exception if member can't be accessed with provided arguments |+/ |alias tryGetMember(string member) = visitImpl!(getMemberHandler!member, Exhaustive.exception, false); | |/// |@safe pure @nogc |version(mir_core_test) unittest |{ | static struct S { int bar(int a) { return a; }} | static struct C3 { alias Bar = (double a) => a * 2; } | | alias V = Variant!(S, C3); | | V x = S(); | V y = C3(); | | static assert(is(typeof(x.tryGetMember!"bar"(2)) == int)); | static assert(is(typeof(y.tryGetMember!"Bar"(2)) == double)); | assert(x.tryGetMember!"bar"(2) == 2); | assert(y.tryGetMember!"Bar"(2) == 4.0); |} | |/// |@safe pure @nogc nothrow |version(mir_core_test) unittest |{ | alias Number = Variant!(int, double); | | Number x = Number(23); | Number y = Number(1.0); | | assert(x.visit!((int v) => true, (float v) => false)); | assert(y.visit!((int v) => false, (float v) => true)); |} | |/++ |Behaves as $(LREF matchMember) but doesn't enforce at compile time that all types can be handled by the member visitor. |Throws: Exception if member can't be accessed with provided arguments | |Fuses algebraic types on return. |+/ |alias tryMatchMember(string member, TArgs...) = visitImpl!(getMemberHandler!(member, TArgs), Exhaustive.exception, true); | |/++ |Behaves as $(LREF getMember) but doesn't enforce at compile time that all types can be handled by the member visitor. |Returns: nullable variant, null value is used if the member can't be called with provided arguments. |+/ |alias optionalGetMember(string member, TArgs...) = visitImpl!(getMemberHandler!(member, TArgs), Exhaustive.nullable, false); | |/++ |Behaves as $(LREF matchMember) but doesn't enforce at compile time that all types can be handled by the member visitor. |Returns: nullable variant, null value is used if the member can't be called with provided arguments. | |Fuses algebraic types on return. |+/ |alias optionalMatchMember(string member, TArgs...) = visitImpl!(getMemberHandler!(member, TArgs), Exhaustive.nullable, true); | |/++ |Behaves as $(LREF getMember) but doesn't enforce at compile time that all types can be handled by the member visitor. |Returns: optionally nullable type, null value is used if the member can't be called with provided arguments. |+/ |alias autoGetMember(string member, TArgs...) = visitImpl!(getMemberHandler!(member, TArgs), Exhaustive.auto_, false); | |/++ |Behaves as $(LREF matchMember) but doesn't enforce at compile time that all types can be handled by the member visitor. |Returns: optionally nullable type, null value is used if the member can't be called with provided arguments. | |Fuses algebraic types on return. |+/ |alias autoMatchMember(string member, TArgs...) = visitImpl!(getMemberHandler!(member, TArgs), Exhaustive.auto_, true); | |private template getMemberHandler(string member, TArgs...) |{ | /// | auto ref getMemberHandler(V, Args...)(ref V value, auto ref Args args) | { | static if (Args.length == 0) | { | static if (TArgs.length) | { | return mixin(`value.` ~ member ~ `!TArgs`); | } | else | { | return __traits(getMember, value, member); | } | } | else | { | import core.lifetime: forward; | import mir.reflection: hasField; | static if (TArgs.length) | { | static if (hasField!(V, member) && Args.length == 1) | return mixin(`value.` ~ member ~ `!TArgs`) = forward!args; | else | return mixin(`value.` ~ member ~ `!TArgs(forward!args)`); | } | else | { | static if (hasField!(V, member) && Args.length == 1) | return __traits(getMember, value, member) = forward!args; | else | return __traits(getMember, value, member)(forward!args); | } | } | } |} | |private template VariantReturnTypes(T...) |{ | import std.meta: staticMap; | | alias VariantReturnTypes = NoDuplicates!(staticMap!(TryRemoveConst, T)); |} | |private enum Exhaustive |{ | compileTime, | exception, | nullable, | auto_, |} | |private template nextVisitor(T, alias visitor, alias arg) |{ | static if (is(T == void)) | { | alias nextVisitor = visitor; | } | else | auto ref nextVisitor(NextArgs...)(auto ref NextArgs nextArgs) | { | import core.lifetime: forward; | | static if (__traits(isRef, arg)) | return visitor(arg.trustedGet!T, forward!nextArgs); | else | static if (is(typeof(move(arg.trustedGet!T)))) | return visitor(move(arg.trustedGet!T), forward!nextArgs); | else | return visitor((() => arg.trustedGet!T)(), forward!nextArgs); | } |} | |private template nextVisitor(alias visitor, alias arg) |{ | auto ref nextVisitor(NextArgs...)(auto ref NextArgs nextArgs) | { | import core.lifetime: forward; | return visitor(forward!arg, forward!nextArgs); | } |} | |private template visitThis(alias visitor, Exhaustive nextExhaustive) |{ | template visitThis(T) | { | auto ref visitThis(Args...)(auto ref Args args) | { | import core.lifetime: forward; | return .visitImpl!(nextVisitor!(T, visitor, forward!(args[0])), nextExhaustive, true)(forward!(args[1 .. $])); | } | } |} | |private template visitLast(alias visitor) |{ | template visitLast(T) | { | static if (is(T == void)) | { | auto ref visitLast(Args...)(auto ref Args args) | { | import core.lifetime: forward; | return visitor(forward!(args[1 .. $])); | } | } | else | { | auto ref visitLast(Args...)(auto ref Args args) | { | import core.lifetime: forward, move; | static if (__traits(isRef, args[0])) | return visitor(args[0].trustedGet!T, forward!(args[1 .. $])); | else | static if (is(typeof(move(args[0].trustedGet!T)))) | return visitor(move(args[0].trustedGet!T), forward!(args[1 .. $])); | else | return visitor((() => args[0].trustedGet!T)(), forward!(args[1 .. $])); | } | } | } |} | |private enum _AcceptAll(Args...) = true; | |template visitImpl(alias visitor, Exhaustive exhaustive, bool fused, alias Filter = _AcceptAll) |{ | /// | auto ref visitImpl(Args...)(auto ref Args args) | if (Filter!Args) | { | import std.meta: anySatisfy, staticMap, AliasSeq; | import core.lifetime: forward; | | static if (!anySatisfy!(isLikeVariant, Args)) | { | static if (exhaustive == Exhaustive.compileTime) | { | return visitor(forward!args); | } | else | static if (exhaustive == Exhaustive.exception) | { | static if (__traits(compiles, visitor(forward!args))) | return visitor(forward!args); | else | return throwMe(variantMemberException); | } | else | static if (exhaustive == Exhaustive.nullable) | { | static if (__traits(compiles, visitor(forward!args))) | return Nullable!(typeof(visitor(forward!args)))(visitor(forward!args)); | else | return Nullable!().init; | } | else | static if (exhaustive == Exhaustive.auto_) | { | static if (__traits(compiles, visitor(forward!args))) | return visitor(forward!args); | else | return Nullable!().init; | } | else | static assert(0, "not implemented"); | } | else | static if (!isLikeVariant!(Args[0])) | { | return .visitImpl!(nextVisitor!(visitor, args[0]), exhaustive, fused)(forward!(args[1 .. $])); | } | else | { | static if (fused && anySatisfy!(isLikeVariant, Args[1 .. $])) | { | alias fun = visitThis!(visitor, exhaustive); | } | else | { | static assert (isLikeVariant!(Args[0]), "First argument should be a Mir Algebraic type"); | alias fun = visitLast!visitor; | } | | template VariantReturnTypesImpl(T) | { | static if (__traits(compiles, fun!T(forward!args))) | { | alias R = typeof(fun!T(forward!args)); | static if (fused && isLikeVariant!R) | alias VariantReturnTypesImpl = staticMap!(TryRemoveConst, R.AllowedTypes); | else | static if (is(immutable R == immutable noreturn)) | alias VariantReturnTypesImpl = AliasSeq!(); | else | alias VariantReturnTypesImpl = AliasSeq!(TryRemoveConst!R); | } | else | static if (exhaustive == Exhaustive.auto_) | alias VariantReturnTypesImpl = AliasSeq!(typeof(null)); | else | alias VariantReturnTypesImpl = AliasSeq!(); | } | | static if (exhaustive == Exhaustive.nullable) | alias AllReturnTypes = NoDuplicates!(typeof(null), staticMap!(VariantReturnTypesImpl, Args[0].AllowedTypes)); | else | alias AllReturnTypes = NoDuplicates!(staticMap!(VariantReturnTypesImpl, Args[0].AllowedTypes)); | | switch (args[0].identifier__) | { | static foreach (i, T; Args[0].AllowedTypes) | { | case i: | static if (__traits(compiles, fun!T(forward!args)) || exhaustive == Exhaustive.compileTime && !is(T == typeof(null))) | { | static if (AllReturnTypes.length == 1) | { | return fun!T(forward!args); | } | else | static if (is(VariantReturnTypesImpl!T == AliasSeq!void)) | { | fun!T(forward!args); | return Variant!AllReturnTypes._void; | } | else | static if (is(typeof(fun!T(forward!args)) == Variant!AllReturnTypes)) | { | return fun!T(forward!args); | } | else | { | return Variant!AllReturnTypes(fun!T(forward!args)); | } | } | else | static if (exhaustive == Exhaustive.compileTime && is(T == typeof(null))) | { | assert(0, "Null " ~ Args[0].stringof); | } | else | static if (exhaustive == Exhaustive.nullable || exhaustive == Exhaustive.auto_) | { | return Variant!AllReturnTypes(null); | } | else | { | return throwMe(variantMemberException); | } | } | default: assert(0); | } | } | } |} | |private string enumKindText()(scope const char[][] strs) |{ | auto r = "enum Kind {"; | foreach (s; strs) | { | r ~= s; | r ~= ", "; | } | r ~= "}"; | return r; |} | |@safe pure @nogc |version(mir_core_test) unittest |{ | static struct S { int a; } | | Variant!(S, double) variant; | variant = 1.0; | variant.tryVisit!((ref value, b) => value += b)(2); | assert (variant.get!double == 3); | | alias fun = (ref value) { | static if (is(typeof(value) == S)) | value.a += 2; | else | value += 2; | }; | | variant.tryVisit!fun; | assert (variant.get!double == 5); | | variant = S(4); | variant.tryVisit!fun; | assert (variant.get!S.a == 6); | | alias D = Variant!(Variant!(S, double)); |} | |@safe pure @nogc |version(mir_core_test) unittest |{ | import std.meta: AliasSeq; | | static struct PODWithLongPointer { | long* x; | this(long l) pure | { | x = new long(l); | } | | @property: | long a() const { | return x ? *x : 0; | } | | void a(long l) { | if (x) { | *x = l; | } else { | x = new long(l); | } | } | } | static assert(is(TypeSet!(byte, immutable PODWithLongPointer) == AliasSeq!(byte, immutable PODWithLongPointer))); |} | |private enum isSimpleAggregateType(T) = is(T == class) || is(T == struct) || is(T == union) || is(T == interface); | |unittest |{ | static struct Test | { | alias Value = void; | } | | alias B = Nullable!Test; |} | |/++ |Wrapper to denote an error value type. | |The wrapper is autostripped by $(LREF none). | |See_also: $(LREF reflectErr). |+/ |template Err(T) |{ | static if (!isErr!T) | { | /// | struct Err | { | /// | T value; | } | } | else | { | alias Err = T; | } |} | |/// ditto |auto err(T)(T value) { | import core.lifetime: move; | static if (isErr!T) | return move(value); | else | return Err!T(move(value)); |} | |/// |unittest |{ | @reflectErr static struct E {} | | static assert(is(Err!string == Err!string)); | static assert(is(Err!(Err!string) == Err!string)); | static assert(is(Err!E == E)); | static assert(is(Err!Exception == Exception)); | | static assert(is(typeof("str".err) == Err!string)); | static assert(is(typeof(E().err) == E)); | static assert(is(typeof(new Exception("str").err) == Exception)); |} | |/// Strips out $(LREF Err) wrapper from the type. |template stripErr(T) |{ | static if (is(immutable T : immutable Err!U, U)) | alias stripErr = U; | else | alias stripErr = T; |} | |/// |version(mir_core_test) |unittest |{ | static assert(is(stripErr!Exception == Exception)); | static assert(is(stripErr!string == string)); | static assert(is(stripErr!(Err!string) == string)); |} | |/++ | |See_also: $(LREF some) and $(LREF none). | |Params: | visitors = visitors to $(LREF match) with. |+/ |alias suit(alias filter, visitors...) = visitImpl!(naryFun!visitors, Exhaustive.compileTime, true, filter); | |/// |version(mir_core_test) |@safe pure nothrow @nogc unittest |{ | import std.traits: isDynamicArray, Unqual; | import std.meta: templateNot; | alias V = Variant!(long, int, string, long[], int[]); | alias autoGetElementType = match!( | (string s) => "string", // we override the suit handler below for string | suit!(isDynamicArray, a => Unqual!(typeof(a[0])).stringof), | suit!(templateNot!isDynamicArray, a => Unqual!(typeof(a)).stringof), | ); | assert(autoGetElementType(V(string.init)) == "string"); | assert(autoGetElementType(V((long[]).init)) == "long"); | assert(autoGetElementType(V((int[]).init)) == "int"); | assert(autoGetElementType(V(long.init)) == "long"); | assert(autoGetElementType(V(int.init)) == "int"); |} | |/// |version(mir_core_test) |@safe pure nothrow @nogc unittest |{ | import std.traits: allSameType; | import std.meta: templateNot; | | static struct Asteroid { uint size; } | static struct Spaceship { uint size; } | alias SpaceObject = Variant!(Asteroid, Spaceship); | | auto errorMsg = "can't unite an asteroid with a spaceship".err; | | alias unite = match!( | suit!(allSameType, (a, b) => typeof(a)(a.size + b.size)), | suit!(templateNot!allSameType, (a, b) => errorMsg), | ); | | auto ea = Asteroid(10); | auto es = Spaceship(1); | auto oa = SpaceObject(ea); | auto os = SpaceObject(es); | | static assert(is(typeof(unite(oa, oa)) == Variant!(Err!string, Asteroid, Spaceship))); | | // Asteroid-Asteroid | assert(unite(ea, ea) == Asteroid(20)); | assert(unite(ea, oa) == Asteroid(20)); | assert(unite(oa, ea) == Asteroid(20)); | assert(unite(oa, oa) == Asteroid(20)); | | // Asteroid-Spaceship | assert(unite(ea, es) == errorMsg); | assert(unite(ea, os) == errorMsg); | assert(unite(oa, es) == errorMsg); | assert(unite(oa, os) == errorMsg); | | // Spaceship-Asteroid | assert(unite(es, ea) == errorMsg); | assert(unite(es, oa) == errorMsg); | assert(unite(os, ea) == errorMsg); | assert(unite(os, oa) == errorMsg); | | // Spaceship-Spaceship | assert(unite(es, es) == Spaceship(2)); | assert(unite(es, os) == Spaceship(2)); | assert(unite(os, es) == Spaceship(2)); | assert(unite(os, os) == Spaceship(2)); |} | |private template unwrapErrImpl(alias arg) |{ | static if (is(immutable typeof(arg) == immutable Err!V, V)) | auto ref unwrapErrImpl() @property { return arg.value; } | else | alias unwrapErrImpl = arg; |} | |private template unwrapErr(alias fun) |{ | auto ref unwrapErr(Args...)(auto ref return Args args) | { | import std.meta: staticMap; | import std.format: format; | enum expr = () { | string ret = `fun(`; | foreach(i, T; Args) | { | static if (is(immutable T == immutable Err!V, V)) | ret ~= `args[` ~ i.stringof ~ `].value, `; | else | ret ~= `args[` ~ i.stringof ~ `], `; | } | ret ~= `)`; | return ret; | }(); | return mixin(expr); | } |} | |/++ |$(LREF some) is a variant of $(LREF suit) that forces that type of any argument doesn't satisfy $(LREF isErr) template. | |$(LREF none) is a variant of $(LREF suit) that forces that type of all arguments satisfy $(LREF isErr) template. The handler automatically strips the $(LREF Err) wrapper. | |See_also: $(LREF suit), $(LREF Err), $(LREF isErr), $(LREF isResultVariant), and $(LREF reflectErr). | |Params: | visitors = visitors to $(LREF match) with. |+/ |alias some(visitors...) = suit!(allArgumentsIsNotInstanceOfErr, naryFun!visitors); | |/// ditto |alias none(visitors...) = suit!(anyArgumentIsInstanceOfErr, unwrapErr!(naryFun!visitors)); | |/// |version(mir_core_test) |unittest |{ | import mir.conv: to; | | alias orElse(alias fun) = visit!(some!"a", none!fun); | alias errToString = orElse!(to!string); | | // can any other type including integer enum | @reflectErr | static struct ErrorInfo { | string msg; | auto toString() const { return msg; } | } | | alias V = Variant!(Err!string, ErrorInfo, Exception, long, double); | alias R = typeof(errToString(V.init)); | | static assert(is(R == Variant!(string, long, double)), R.stringof); | | { | V v = 1; | assert(v.isOk); | assert(errToString(v) == 1); | } | | { | V v = 1.0; | assert(v.isOk); | assert(errToString(v) == 1.0); | } | | { | V v = ErrorInfo("msg"); | assert(!v.isOk); | assert(errToString(v) == "msg"); | } | | { | V v = "msg".err; | assert(!v.isOk); | assert(errToString(v) == "msg"); | } | | { | V v = new Exception("msg"); enum line = __LINE__; | assert(!v.isOk); | assert(errToString(v) == "object.Exception@" ~ __FILE__ ~ "(" ~ line.stringof ~ "): msg"); | } |} | |/++ |Attribute that denotes an error type. Can be used with $(LREF some) and $(LREF none). | |See_also: $(LREF Err). |+/ |enum reflectErr; | |/++ |Checks if T is a instance of $(LREF Err) or if it is annotated with $(LREF reflectErr). |+/ |template isErr(T) |{ | import std.traits: isAggregateType, hasUDA; | static if (is(T == enum) || isAggregateType!T) | { | static if (is(immutable T == immutable Err!V, V)) | { | enum isErr = true; | } | else | static if (hasUDA!(T, reflectErr)) | { | enum isErr = true; | } | else | version (D_Exceptions) | { | enum isErr = is(immutable T : immutable Throwable); | } | else | { | enum isErr = false; | } | } | else | { | enum isErr = false; | } |} | |/++ |Checks if T is a Variant with at least one allowed type that satisfy $(LREF isErr) traits. |+/ |template isResultVariant(T) |{ | static if (is(immutable T == immutable Algebraic!Types, Types...)) | { | import std.meta: anySatisfy; | enum isResultVariant = anySatisfy!(isErr, Types); | } | else | { | enum isResultVariant = false; | } |} | |deprecated("Use isResultVariant instead") alias isErrVariant = isResultVariant; | |private template anyArgumentIsInstanceOfErr(Args...) |{ | import std.meta: anySatisfy; | enum anyArgumentIsInstanceOfErr = anySatisfy!(isErr, Args); |} | |private template allArgumentsIsNotInstanceOfErr(Args...) |{ | import std.meta: anySatisfy; | enum allArgumentsIsNotInstanceOfErr = !anySatisfy!(isErr, Args); |} | |/++ |Gets subtype of algebraic without types for which $(LREF isErr) is true. |+/ |template SomeVariant(T : Algebraic!Types, Types...) |{ | import std.meta: Filter, templateNot; | alias SomeVariant = Algebraic!(Filter!(templateNot!isErr, Types)); |} | |/// |@safe pure version(mir_core_test) unittest |{ | @reflectErr static struct ErrorS { } | alias V = Variant!(ErrorS, Err!string, long, double, This[]); | static assert(is(SomeVariant!V == Variant!(long, double, This[]))); |} | |/++ |Gets subtype of algebraic with types for which $(LREF isErr) is true. |+/ |template NoneVariant(T : Algebraic!Types, Types...) |{ | import std.meta: Filter; | alias NoneVariant = Algebraic!(Filter!(isErr, Types)); |} | |/// |@safe pure version(mir_core_test) unittest |{ | @reflectErr static struct ErrorS { } | alias V = Variant!(ErrorS, Err!string, long, double, This[]); | static assert(is(NoneVariant!V == Variant!(ErrorS, Err!string))); |} | |private template withNewLine(alias arg) |{ | import std.meta: AliasSeq; | alias withNewLine = AliasSeq!("\n", arg); |} | |private noreturn throwMe(T...)(auto ref T args) { | static if (T.length == 1) | enum simpleThrow = is(immutable T[0] : immutable Throwable); | else | enum simpleThrow = false; | static if (simpleThrow) | { | throw args[0]; | } | else | { | import mir.exception: MirException; | static if (__traits(compiles, { import mir.format: print; })) | { | import std.meta: staticMap; | throw new MirException("assumeOk failure:", staticMap!(withNewLine, args)); | } | else | { | import mir.conv: to; | auto msg = "assumeOk failure:"; | foreach(ref arg; args) | { | msg ~= "\n"; | msg ~= arg.to!string; | } | throw new MirException(msg); | } | } |} | |version(D_Exceptions) |/++ |Validates that the result doesn't contain an error value. | |Params: | visitor = (compiletime) visitor function. Default value is `naryFun!("", "a")`. | handler = (compiletime) visitor handler to use. Default value is $(LREF match). |Throws: | Throws an exception if at least one parameter passed to | `visitor` satisfies $(LREF isErr) traits. | If there is only one paramter (common case) and its value is `Throwable`, throws it. | Otherwise, _all_ paramters will be printed to the exception message using `mir.format.print`. |+/ |alias assumeOk(alias visitor = naryFun!("", "a"), alias handler = .match) = handler!(some!visitor, none!throwMe); | |/// |version(mir_core_test) version(D_Exceptions) |unittest |{ | import std.exception: collectExceptionMsg; | import mir.exception: MirException; | | alias SingleTypeValue = typeof(assumeOk(Variant!(Exception, long).init)); | static assert(is(SingleTypeValue == long), SingleTypeValue.stringof); | | | // can any other type including integer enum | @reflectErr | static struct ErrorInfo { | string msg; | auto toString() const { return msg; } | } | | alias V = Variant!(Err!string, ErrorInfo, Exception, long, double); | alias R = typeof(assumeOk(V.init)); | | static assert(is(R == Variant!(long, double)), R.stringof); | | { | V v = 1; | assert(v.isOk); | assert(v.assumeOk == 1); | } | | { | V v = 1.0; | assert(v.isOk); | assert(v.assumeOk == 1.0); | } | | { | V v = ErrorInfo("msg"); | assert(!v.isOk); | assert(v.assumeOk.collectExceptionMsg == "assumeOk failure:\nmsg"); | } | | { | V v = "msg".err; | assert(!v.isOk); | assert(v.assumeOk.collectExceptionMsg == "assumeOk failure:\nmsg"); | } | | { | V v = new Exception("msg"); | assert(!v.isOk); | assert(v.assumeOk.collectExceptionMsg == "msg"); | } |} | |version(mir_core_test) |unittest |{ | static struct RequestToken | { | Variant!(long, string) value; | alias value this; | | this(T)(T v) | { | value = typeof(value)(v); | } | } | | Variant!(int, RequestToken) v = RequestToken.init; | | auto r = v.match!( | (int v) { | return assert(false); | }, | ret => ret | ); | | static assert(is(typeof(r) == Variant!(long, string))); |} | |package auto trustedAllAttr(T)(scope T t) @trusted |{ | import std.traits; | enum attrs = (functionAttributes!T & ~FunctionAttribute.system) | | FunctionAttribute.pure_ | | FunctionAttribute.safe | | FunctionAttribute.nogc | | FunctionAttribute.nothrow_; | return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; |} | |private static immutable algebraicMembers = [ | "_is", | "_void", | "AllowedTypes", | "MetaFieldsTypes", | "get", | "isNull", | "kind", | "Kind", | "nullify", | "opAssign", | "opCast", | "opCmp", | "opEquals", | "opPostMove", | "toHash", | "toString", | "trustedGet", | "deserializeFromAsdf", | "deserializeFromIon", |]; | |private template UnqualRec(T) |{ | import std.traits: Unqual, isDynamicArray, ForeachType; | static if (isDynamicArray!T) | alias UnqualRec = UnqualRec!(ForeachType!T)[]; | else | alias UnqualRec = Unqual!T; |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/algebraic.d is 0% covered <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-complex-math.lst | |/++ |Complex math | |Copyright: Ilia Ki; 2010, Lars T. Kyllingstad (original Phobos code) |Authors: Ilia Ki, Lars Tandle Kyllingstad, Don Clugston |+/ |module mir.complex.math; | |public import mir.complex; | |/++ |Params: z = A complex number. |Returns: The square root of `z`. |+/ |Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc |{ | import mir.math.common: fabs, fmin, fmax, sqrt; | | if (z == 0) | return typeof(return)(0, 0); | auto x = fabs(z.re); | auto y = fabs(z.im); | auto n = fmin(x, y); | auto m = fmax(x, y); | auto r = n / m; | auto w = sqrt(m) * sqrt(0.5f * ((x >= y ? 1 : r) + sqrt(1 + r * r))); | auto s = typeof(return)(w, z.im / (w + w)); | if (z.re < 0) | { | s = typeof(return)(s.im, s.re); | if (z.im < 0) | s = -s; | } | return s; |} | |/// |@safe pure nothrow unittest |{ | assert(sqrt(complex(0.0)) == 0.0); | assert(sqrt(complex(1.0, 0)) == 1.0); | assert(sqrt(complex(-1.0, 0)) == complex(0, 1.0)); | assert(sqrt(complex(-8.0, -6.0)) == complex(1.0, -3.0)); |} | |@safe pure nothrow unittest |{ | assert(complex(1.0, 1.0).sqrt.approxEqual(complex(1.098684113467809966, 0.455089860562227341))); | assert(complex(0.5, 2.0).sqrt.approxEqual(complex(1.131713924277869410, 0.883615530875513265))); |} | |/** | * Calculate the natural logarithm of x. | * The branch cut is along the negative axis. | * Params: | * x = A complex number | * Returns: | * The complex natural logarithm of `x` | * | * $(TABLE_SV | * $(TR $(TH x) $(TH log(x))) | * $(TR $(TD (-0, +0)) $(TD (-$(INFIN), $(PI)))) | * $(TR $(TD (+0, +0)) $(TD (-$(INFIN), +0))) | * $(TR $(TD (any, +$(INFIN))) $(TD (+$(INFIN), $(PI)/2))) | * $(TR $(TD (any, $(NAN))) $(TD ($(NAN), $(NAN)))) | * $(TR $(TD (-$(INFIN), any)) $(TD (+$(INFIN), $(PI)))) | * $(TR $(TD (+$(INFIN), any)) $(TD (+$(INFIN), +0))) | * $(TR $(TD (-$(INFIN), +$(INFIN))) $(TD (+$(INFIN), 3$(PI)/4))) | * $(TR $(TD (+$(INFIN), +$(INFIN))) $(TD (+$(INFIN), $(PI)/4))) | * $(TR $(TD ($(PLUSMN)$(INFIN), $(NAN))) $(TD (+$(INFIN), $(NAN)))) | * $(TR $(TD ($(NAN), any)) $(TD ($(NAN), $(NAN)))) | * $(TR $(TD ($(NAN), +$(INFIN))) $(TD (+$(INFIN), $(NAN)))) | * $(TR $(TD ($(NAN), $(NAN))) $(TD ($(NAN), $(NAN)))) | * ) | */ |Complex!T log(T)(Complex!T x) @safe pure nothrow @nogc |{ | import mir.math.constant: PI, PI_4, PI_2; | import mir.math.common: log, fabs, copysign; | alias isNaN = x => x != x; | alias isInfinity = x => x.fabs == T.infinity; | | // Handle special cases explicitly here for better accuracy. | // The order here is important, so that the correct path is chosen. | if (isNaN(x.re)) | { | if (isInfinity(x.im)) | return Complex!T(T.infinity, T.nan); | else | return Complex!T(T.nan, T.nan); | } | if (isInfinity(x.re)) | { | if (isNaN(x.im)) | return Complex!T(T.infinity, T.nan); | else if (isInfinity(x.im)) | { | if (copysign(1, x.re) < 0) | return Complex!T(T.infinity, copysign(3.0 * PI_4, x.im)); | else | return Complex!T(T.infinity, copysign(PI_4, x.im)); | } | else | { | if (copysign(1, x.re) < 0) | return Complex!T(T.infinity, copysign(PI, x.im)); | else | return Complex!T(T.infinity, copysign(0.0, x.im)); | } | } | if (isNaN(x.im)) | return Complex!T(T.nan, T.nan); | if (isInfinity(x.im)) | return Complex!T(T.infinity, copysign(PI_2, x.im)); | if (x.re == 0.0 && x.im == 0.0) | { | if (copysign(1, x.re) < 0) | return Complex!T(-T.infinity, copysign(PI, x.im)); | else | return Complex!T(-T.infinity, copysign(0.0, x.im)); | } | | return Complex!T(log(cabs(x)), arg(x)); |} | |/// |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.math.common: sqrt; | import mir.math.constant: PI; | import mir.math.common: approxEqual; | | auto a = complex(2.0, 1.0); | assert(log(conj(a)) == conj(log(a))); | | assert(log(complex(-1.0L, 0.0L)) == complex(0.0L, PI)); | assert(log(complex(-1.0L, -0.0L)) == complex(0.0L, -PI)); |} | |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.math.common: fabs; | import mir.math.constant: PI, PI_2, PI_4; | alias isNaN = x => x != x; | alias isInfinity = x => x.fabs == x.infinity; | | auto a = log(complex(-0.0L, 0.0L)); | assert(a == complex(-real.infinity, PI)); | auto b = log(complex(0.0L, 0.0L)); | assert(b == complex(-real.infinity, +0.0L)); | auto c = log(complex(1.0L, real.infinity)); | assert(c == complex(real.infinity, PI_2)); | auto d = log(complex(1.0L, real.nan)); | assert(isNaN(d.re) && isNaN(d.im)); | | auto e = log(complex(-real.infinity, 1.0L)); | assert(e == complex(real.infinity, PI)); | auto f = log(complex(real.infinity, 1.0L)); | assert(f == complex(real.infinity, 0.0L)); | auto g = log(complex(-real.infinity, real.infinity)); | assert(g == complex(real.infinity, 3.0 * PI_4)); | auto h = log(complex(real.infinity, real.infinity)); | assert(h == complex(real.infinity, PI_4)); | auto i = log(complex(real.infinity, real.nan)); | assert(isInfinity(i.re) && isNaN(i.im)); | | auto j = log(complex(real.nan, 1.0L)); | assert(isNaN(j.re) && isNaN(j.im)); | auto k = log(complex(real.nan, real.infinity)); | assert(isInfinity(k.re) && isNaN(k.im)); | auto l = log(complex(real.nan, real.nan)); | assert(isNaN(l.re) && isNaN(l.im)); |} | |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.math.constant: PI; | | auto a = log(fromPolar(1.0, PI / 6.0)); | assert(approxEqual(a, complex(0.0L, 0.523598775598298873077L), 0.0, 1e-15)); | | auto b = log(fromPolar(1.0, PI / 3.0)); | assert(approxEqual(b, complex(0.0L, 1.04719755119659774615L), 0.0, 1e-15)); | | auto c = log(fromPolar(1.0, PI / 2.0)); | assert(approxEqual(c, complex(0.0L, 1.57079632679489661923L), 0.0, 1e-15)); | | auto d = log(fromPolar(1.0, 2.0 * PI / 3.0)); | assert(approxEqual(d, complex(0.0L, 2.09439510239319549230L), 0.0, 1e-15)); | | auto e = log(fromPolar(1.0, 5.0 * PI / 6.0)); | assert(approxEqual(e, complex(0.0L, 2.61799387799149436538L), 0.0, 1e-15)); | | auto f = log(complex(-1.0L, 0.0L)); | assert(approxEqual(f, complex(0.0L, PI), 0.0, 1e-15)); |} | |/++ |Calculates e$(SUPERSCRIPT x). |Params: | x = A complex number |Returns: | The complex base e exponential of `x` | $(TABLE_SV | $(TR $(TH x) $(TH exp(x))) | $(TR $(TD ($(PLUSMN)0, +0)) $(TD (1, +0))) | $(TR $(TD (any, +$(INFIN))) $(TD ($(NAN), $(NAN)))) | $(TR $(TD (any, $(NAN)) $(TD ($(NAN), $(NAN))))) | $(TR $(TD (+$(INFIN), +0)) $(TD (+$(INFIN), +0))) | $(TR $(TD (-$(INFIN), any)) $(TD ($(PLUSMN)0, cis(x.im)))) | $(TR $(TD (+$(INFIN), any)) $(TD ($(PLUSMN)$(INFIN), cis(x.im)))) | $(TR $(TD (-$(INFIN), +$(INFIN))) $(TD ($(PLUSMN)0, $(PLUSMN)0))) | $(TR $(TD (+$(INFIN), +$(INFIN))) $(TD ($(PLUSMN)$(INFIN), $(NAN)))) | $(TR $(TD (-$(INFIN), $(NAN))) $(TD ($(PLUSMN)0, $(PLUSMN)0))) | $(TR $(TD (+$(INFIN), $(NAN))) $(TD ($(PLUSMN)$(INFIN), $(NAN)))) | $(TR $(TD ($(NAN), +0)) $(TD ($(NAN), +0))) | $(TR $(TD ($(NAN), any)) $(TD ($(NAN), $(NAN)))) | $(TR $(TD ($(NAN), $(NAN))) $(TD ($(NAN), $(NAN)))) | ) |+/ |Complex!T exp(T)(Complex!T x) @trusted pure nothrow @nogc // TODO: @safe |{ | import mir.math.common: exp, fabs, copysign; | alias isNaN = x => x != x; | alias isInfinity = x => x.fabs == T.infinity; | | // Handle special cases explicitly here, as fromPolar will otherwise | // cause them to return Complex!T(NaN, NaN), or with the wrong sign. | if (isInfinity(x.re)) | { | if (isNaN(x.im)) | { | if (copysign(1, x.re) < 0) | return Complex!T(0, copysign(0, x.im)); | else | return x; | } | if (isInfinity(x.im)) | { | if (copysign(1, x.re) < 0) | return Complex!T(0, copysign(0, x.im)); | else | return Complex!T(T.infinity, -T.nan); | } | if (x.im == 0) | { | if (copysign(1, x.re) < 0) | return Complex!T(0); | else | return Complex!T(T.infinity); | } | } | if (isNaN(x.re)) | { | if (isNaN(x.im) || isInfinity(x.im)) | return Complex!T(T.nan, T.nan); | if (x.im == 0) | return x; | } | if (x.re == 0) | { | if (isNaN(x.im) || isInfinity(x.im)) | return Complex!T(T.nan, T.nan); | if (x.im == 0) | return Complex!T(1, 0); | } | | return fromPolar!T(exp(x.re), x.im); |} | |/// |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.math.constant: PI; | | assert(exp(complex(0.0, 0.0)) == complex(1.0, 0.0)); | | auto a = complex(2.0, 1.0); | assert(exp(conj(a)) == conj(exp(a))); | | auto b = exp(complex(0.0, 1.0) * double(PI)); | assert(approxEqual(b, complex(-1.0), 0.0, 1e-15)); |} | |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.math.common: fabs; | | alias isNaN = x => x != x; | alias isInfinity = x => x.fabs == x.infinity; | | auto a = exp(complex(0.0, double.infinity)); | assert(isNaN(a.re) && isNaN(a.im)); | auto b = exp(complex(0.0, double.infinity)); | assert(isNaN(b.re) && isNaN(b.im)); | auto c = exp(complex(0.0, double.nan)); | assert(isNaN(c.re) && isNaN(c.im)); | | auto d = exp(complex(+double.infinity, 0.0)); | assert(d == complex(double.infinity, 0.0)); | auto e = exp(complex(-double.infinity, 0.0)); | assert(e == complex(0.0)); | auto f = exp(complex(-double.infinity, 1.0)); | assert(f == complex(0.0)); | auto g = exp(complex(+double.infinity, 1.0)); | assert(g == complex(double.infinity, double.infinity)); | auto h = exp(complex(-double.infinity, +double.infinity)); | assert(h == complex(0.0)); | auto i = exp(complex(+double.infinity, +double.infinity)); | assert(isInfinity(i.re) && isNaN(i.im)); | auto j = exp(complex(-double.infinity, double.nan)); | assert(j == complex(0.0)); | auto k = exp(complex(+double.infinity, double.nan)); | assert(isInfinity(k.re) && isNaN(k.im)); | | auto l = exp(complex(double.nan, 0)); | assert(isNaN(l.re) && l.im == 0.0); | auto m = exp(complex(double.nan, 1)); | assert(isNaN(m.re) && isNaN(m.im)); | auto n = exp(complex(double.nan, double.nan)); | assert(isNaN(n.re) && isNaN(n.im)); |} | |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.math.constant : PI; | | auto a = exp(complex(0.0, -PI)); | assert(approxEqual(a, complex(-1.0L), 0.0, 1e-15)); | | auto b = exp(complex(0.0, -2.0 * PI / 3.0)); | assert(approxEqual(b, complex(-0.5L, -0.866025403784438646763L))); | | auto c = exp(complex(0.0, PI / 3.0)); | assert(approxEqual(c, complex(0.5L, 0.866025403784438646763L))); | | auto d = exp(complex(0.0, 2.0 * PI / 3.0)); | assert(approxEqual(d, complex(-0.5L, 0.866025403784438646763L))); | | auto e = exp(complex(0.0, PI)); | assert(approxEqual(e, complex(-1.0L), 0.0, 1e-15)); |} | |/++ |Computes whether two values are approximately equal, admitting a maximum |relative difference, and a maximum absolute difference. |Params: | lhs = First item to compare. | rhs = Second item to compare. | maxRelDiff = Maximum allowable difference relative to `rhs`. Defaults to `0.5 ^^ 20`. | maxAbsDiff = Maximum absolute difference. Defaults to `0.5 ^^ 20`. | |Returns: | `true` if the two items are equal or approximately equal under either criterium. |+/ |bool approxEqual(T)(Complex!T lhs, Complex!T rhs, const T maxRelDiff = 0x1p-20f, const T maxAbsDiff = 0x1p-20f) |{ | import mir.math.common: approxEqual; | return approxEqual(lhs.re, rhs.re, maxRelDiff, maxAbsDiff) | && approxEqual(lhs.im, rhs.im, maxRelDiff, maxAbsDiff); |} | |/// Complex types works as `approxEqual(l.re, r.re) && approxEqual(l.im, r.im)` |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | assert(approxEqual(complex(1.0, 1), complex(1.0000001, 1), 1.0000001)); | assert(!approxEqual(complex(100000.0, 0), complex(100001.0, 0))); |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/complex/math.d has no code <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-complex-package.lst |/++ |Complex numbers | |Copyright: Ilia Ki; 2010, Lars T. Kyllingstad (original Phobos code) |Authors: Ilia Ki, Lars Tandle Kyllingstad, Don Clugston |+/ |module mir.complex; | |import mir.math.common: optmath; | |private alias CommonType(A, B) = typeof(A.init + B.init); | |@optmath: | |/++ |Generic complex number type |+/ |struct Complex(T) | if (is(T == float) || is(T == double) || is(T == real)) |{ | import mir.internal.utility: isComplex; | import std.traits: isNumeric; | |@optmath: | | /++ | Real part. Default value is zero. | +/ | T re = 0; | /++ | Imaginary part. Default value is zero. | +/ | T im = 0; | | /// | ref Complex opAssign(R)(Complex!R rhs) | if (!is(R == T)) | { | this.re = rhs.re; | this.im = rhs.im; | return this; | } | | /// | ref Complex opAssign(F)(const F rhs) | if (isNumeric!F) | { | this.re = rhs; | this.im = 0; | return this; | } | | /// | ref Complex opOpAssign(string op : "+", R)(Complex!R rhs) return | { | re += rhs.re; | im += rhs.im; | return this; | } | | /// | ref Complex opOpAssign(string op : "-", R)(Complex!R rhs) return | { | re -= rhs.re; | im -= rhs.im; | return this; | } | | /// | ref Complex opOpAssign(string op, R)(Complex!R rhs) return | if (op == "*" || op == "/") | { | return this = this.opBinary!op(rhs); | } | | /// | ref Complex opOpAssign(string op : "+", R)(const R rhs) return | if (isNumeric!R) | { | re += rhs; | return this; | } | | /// | ref Complex opOpAssign(string op : "-", R)(const R rhs) return | if (isNumeric!R) | { | re -= rhs; | return this; | } | | /// | ref Complex opOpAssign(string op : "*", R)(const R rhs) return | if (isNumeric!R) | { | re *= rhs; | return this; | } | | /// | ref Complex opOpAssign(string op : "/", R)(const R rhs) return | if (isNumeric!R) | { | re /= rhs; | return this; | } | |scope const: | | /// | bool opEquals(const Complex rhs) | { 0000000| return re == rhs.re && im == rhs.im; | } | | /// | size_t toHash() | { 0000000| T[2] val = [re, im]; 0000000| return hashOf(val) ; | } | | /// | bool opEquals(R)(Complex!R rhs) | if (!is(R == T)) | { | return re == rhs.re && im == rhs.im; | } | | /// | bool opEquals(F)(const F rhs) | if (isNumeric!F) | { | return re == rhs && im == 0; | } | | /// | Complex opUnary(string op : "+")() | { | return this; | } | | /// | Complex opUnary(string op : "-")() | { | return typeof(return)(-re, -im); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "+", R)(Complex!R rhs) | { | return typeof(return)(re + rhs.re, im + rhs.im); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "-", R)(Complex!R rhs) | { | return typeof(return)(re - rhs.re, im - rhs.im); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "*", R)(Complex!R rhs) | { | return typeof(return)(re * rhs.re - im * rhs.im, re * rhs.im + im * rhs.re); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "/", R)(Complex!R rhs) | { | // TODO: use more precise algorithm | auto norm = rhs.re * rhs.re + rhs.im * rhs.im; | return typeof(return)( | (re * rhs.re + im * rhs.im) / norm, | (im * rhs.re - re * rhs.im) / norm, | ); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "+", R)(const R rhs) | if (isNumeric!R) | { | return typeof(return)(re + rhs, im); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "-", R)(const R rhs) | if (isNumeric!R) | { | return typeof(return)(re - rhs, im); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "*", R)(const R rhs) | if (isNumeric!R) | { | return typeof(return)(re * rhs, im * rhs); | } | | /// | Complex!(CommonType!(T, R)) opBinary(string op : "/", R)(const R rhs) | if (isNumeric!R) | { | return typeof(return)(re / rhs, im / rhs); | } | | | /// | Complex!(CommonType!(T, R)) opBinaryRight(string op : "+", R)(const R rhs) | if (isNumeric!R) | { | return typeof(return)(rhs + re, im); | } | | /// | Complex!(CommonType!(T, R)) opBinaryRight(string op : "-", R)(const R rhs) | if (isNumeric!R) | { | return typeof(return)(rhs - re, -im); | } | | /// | Complex!(CommonType!(T, R)) opBinaryRight(string op : "*", R)(const R rhs) | if (isNumeric!R) | { | return typeof(return)(rhs * re, rhs * im); | } | | /// | Complex!(CommonType!(T, R)) opBinaryRight(string op : "/", R)(const R rhs) | if (isNumeric!R) | { | // TODO: use more precise algorithm | auto norm = this.re * this.re + this.im * this.im; | return typeof(return)( | rhs * (this.re / norm), | -rhs * (this.im / norm), | ); | } | | /// | R opCast(R)() | if (isNumeric!R || isComplex!R) | { | static if (isNumeric!R) | return cast(R) re; | else | return R(re, im); | } |} | |/// ditto |Complex!T complex(T)(const T re, const T im = 0) | if (is(T == float) || is(T == double) || is(T == real)) |{ | return typeof(return)(re, im); |} | |private alias _cdouble_ = Complex!double; |private alias _cfloat_ = Complex!float; |private alias _creal_ = Complex!real; | |/// |unittest |{ | auto a = complex(1.0, 3); | auto b = a; | b.re += 3; | a = b; | assert(a == b); | | a = Complex!float(5, 6); | assert(a == Complex!real(5, 6)); | | a += b; | a -= b; | a *= b; | a /= b; | | a = a + b; | a = a - b; | a = a * b; | a = a / b; | | a += 2; | a -= 2; | a *= 2; | a /= 2; | | a = a + 2; | a = a - 2; | a = a * 2; | a = a / 2; | | a = 2 + a; | a = 2 - a; | a = 2 * a; | a = 2 / a; | | a = -a; | a = +a; | | assert(a != 4.0); | a = 4; | assert(a == 4); | assert(cast(int)a == 4); | assert(cast(Complex!float)a == 4); | | import std.complex : StdComplex = Complex; | assert(cast(StdComplex!double)a == StdComplex!double(4, 0)); |} | |/** | Constructs a complex number given its absolute value and argument. | Params: | modulus = The modulus | argument = The argument | Returns: The complex number with the given modulus and argument. |*/ |Complex!T fromPolar(T)(const T modulus, const T argument) | @safe pure nothrow @nogc | if (__traits(isFloating, T)) |{ | import mir.math.common: sin, cos; | return typeof(return)(modulus * cos(argument), modulus * sin(argument)); |} | |/// |@safe pure nothrow version(mir_core_test) unittest |{ | import mir.math : approxEqual, PI, sqrt; | auto z = fromPolar(sqrt(2.0), double(PI / 4)); | assert(approxEqual(z.re, 1.0)); | assert(approxEqual(z.im, 1.0)); |} | |/++ |Params: z = A complex number. |Returns: The complex conjugate of `z`. |+/ |Complex!T conj(T)(Complex!T z) @safe pure nothrow @nogc |{ | return Complex!T(z.re, -z.im); |} | |/// |@safe pure nothrow version(mir_core_test) unittest |{ | assert(conj(complex(1.0)) == complex(1.0)); | assert(conj(complex(1.0, 2.0)) == complex(1.0, -2.0)); |} | |/++ |Params: z = A complex number. |Returns: The argument (or phase) of `z`. |+/ |T arg(T)(Complex!T z) @safe pure nothrow @nogc |{ | import std.math.trigonometry : atan2; | return atan2(z.im, z.re); |} | |/// |@safe pure nothrow version(mir_core_test) unittest |{ | import mir.math.constant: PI_2, PI_4; | assert(arg(complex(1.0)) == 0.0); | assert(arg(complex(0.0L, 1.0L)) == PI_2); | assert(arg(complex(1.0L, 1.0L)) == PI_4); |} | | |/** |Params: z = A complex number. |Returns: The absolute value (or modulus) of `z`. |*/ |T cabs(T)(Complex!T z) @safe pure nothrow @nogc |{ | import std.math.algebraic : hypot; | return hypot(z.re, z.im); |} | |/// |@safe pure nothrow version(mir_core_test) unittest |{ | import mir.math.common: sqrt; | assert(cabs(complex(1.0)) == 1.0); | assert(cabs(complex(0.0, 1.0)) == 1.0); | assert(cabs(complex(1.0L, -2.0L)) == sqrt(5.0L)); |} | |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.math.common: sqrt; | assert(cabs(complex(0.0L, -3.2L)) == 3.2L); | assert(cabs(complex(0.0L, 71.6L)) == 71.6L); | assert(cabs(complex(-1.0L, 1.0L)) == sqrt(2.0L)); |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/complex/package.d is 0% covered <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-conv.lst |/++ |Conversion utilities. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |+/ |module mir.conv; | |public import core.lifetime: emplace; | |import std.traits; | |private template isMirString(T) |{ | static if (isSomeString!T) | { | enum isMirString = true; | } | else | { | static if (__traits(compiles, {import mir.small_string;})) | { | import mir.small_string; | enum isMirString = is(T : SmallString!size, size_t size); | } | else | { | enum isMirString = false; | } | } |} | |/++ |The `to` template converts a value from one type _to another. |The source type is deduced and the target type must be specified, for example the |expression `to!int(42.0)` converts the number 42 from |`double` _to `int`. The conversion is "unsafe", i.e., |it does not check for overflow. |+/ |template to(T) |{ | /// | auto ref T to(A...)(auto ref A args) | if (A.length > 0) | { | import mir.utility; | import mir.functional: forward; | static if (A.length == 1 && isImplicitlyConvertible!(A[0], T)) | return args[0]; | else | static if (is(T == class) && is(typeof(new T(forward!args)))) | return new T(forward!args); | else | static if (is(typeof(T(args)))) | return T(forward!args); | else | static if (A.length == 1) | { | alias I = A[0]; | alias arg = args[0]; | static if (is(typeof(cast(T) arg)) && !(isDynamicArray!T && isDynamicArray!I) && !isSomeString!T) | return cast(T) forward!arg; | else | static if (isSomeString!I && is(T == enum)) | { | import mir.enums; | uint index = void; | if (getEnumIndexFromKey!T(arg, index)._expect(true)) | return index.unsafeEnumFromIndex!T; | static immutable msg = "Can not convert string to the enum " ~ T.stringof; | version (D_Exceptions) | { | static immutable Exception exc = new Exception(msg); | throw exc; | } | else | { | assert(0, msg); | } | } | else | static if (is(I == enum) && isSomeString!T) | { | import mir.enums; | uint id = void; | if (getEnumIndex(arg, id)._expect(true)) | return enumStrings!I[id]; | assert(0); | } | else | static if (isMirString!I && !isSomeString!T) | { | static assert (__traits(compiles, { import mir.parse: fromString; })); | import mir.parse: fromString; | return fromString!(Unqual!T)(arg[]); | } | else | static if (!isSomeString!I && isMirString!T) | { | // static if (is(Unqual!I == typeof(null))) | // { | // enum immutable(T) ret = "null"; | // static if (isImplicitlyConvertible!(immutable T, T)) | // return ret; | // else | // return .to!T(ret[]); | // } | // else | static if (is(Unqual!I == bool)) | { | enum immutable(T) t = "true"; | enum immutable(T) f = "false"; | auto ret = arg ? t : f; | static if (isImplicitlyConvertible!(immutable T, T)) | return ret; | else | return .to!T(ret[]); | } | else | { | static if (isImplicitlyConvertible!(T, string) && __traits(compiles, () {const(char)[] s = arg.toString; return s;})) | { | auto ret = arg.toString; | static if (is(typeof(ret) == string)) | return ret; | else | return ret.idup; | } | else | { | static assert (__traits(compiles, { import mir.format: print; })); | import mir.format: print; | static if (isSomeString!T) | { | static if (isNumeric!I) | { | import mir.appender: UnsafeArrayBuffer; | alias C = Unqual!(ForeachType!T); | C[64] array = '\0'; | auto buffer = UnsafeArrayBuffer!C(array); | } | else | { | import mir.appender: scopedBuffer; | auto buffer = scopedBuffer!(Unqual!(ForeachType!T)); | } | buffer.print(arg); | static if (isMutable!(ForeachType!(T))) | return buffer.data.dup; | else | return buffer.data.idup; | } | else | { | Unqual!T buffer; | buffer.print(arg); | return buffer; | } | } | } | } | else | static if (is(I : const(C)[], C) && is(T : immutable(C)[])) | { | static if (is(I : immutable(C)[])) | return arg; | else | return idup(arg); | } | else | static if (is(I : const(D)[], D) && is(T : D[])) | { | static if (is(I : D[])) | return arg; | else | return dup(arg); | } | else | static assert(0, T.stringof); | } | else | static assert(0, T.stringof); | } |} | |/// |@safe pure @nogc |version(mir_core_test) unittest |{ | enum E | { | A, | B, | C, | } | | assert(to!E("B") == E.B); | assert(to!string(E.B) == "B"); | assert(to!string(null) is null); | assert(to!string(true) == "true"); | assert(to!string(false) == "false"); | | enum S : wstring | { | a = "A", | b = "B", | } | | assert(to!wstring(S.b) == "B"w); | assert(to!S("B"w) == S.b); |} | |/++ |Emplace helper function. |+/ |void emplaceInitializer(T)(scope ref T chunk) @trusted pure nothrow | |{ | // Emplace T.init. | // Previously, an immutable static and memcpy were used to hold an initializer. | // With improved unions, this is no longer needed. | union UntypedInit | { | T dummy; | } | static struct UntypedStorage | { | align(T.alignof) void[T.sizeof] dummy; | } | | () @trusted { | *cast(UntypedStorage*) &chunk = cast(UntypedStorage) UntypedInit.init; | } (); |} | |/++ |+/ |T[] uninitializedFillDefault(T)(return scope T[] array) nothrow @nogc |{ | static if (__VERSION__ < 2083) | { | static if (__traits(isIntegral, T) && 0 == cast(T) (T.init + 1)) | { | import core.stdc.string : memset; | memset(array.ptr, 0xff, T.sizeof * array.length); | return array; | } | else | { | pragma(inline, false); | foreach(ref e; array) | emplaceInitializer(e); | return array; | } | } | else | { | static if (__traits(isZeroInit, T)) | { | import core.stdc.string : memset; | memset(array.ptr, 0, T.sizeof * array.length); | return array; | } | else static if (__traits(isIntegral, T) && 0 == cast(T) (T.init + 1)) | { | import core.stdc.string : memset; | memset(array.ptr, 0xff, T.sizeof * array.length); | return array; | } | else | { | pragma(inline, false); | foreach(ref e; array) | emplaceInitializer(e); | return array; | } | } |} | |/// |pure nothrow @nogc @system |version(mir_core_test) unittest |{ | static struct S { int x = 42; @disable this(this); } | | int[5] expected = [42, 42, 42, 42, 42]; | S[5] arr = void; | uninitializedFillDefault(arr); | assert((cast(int*) arr.ptr)[0 .. arr.length] == expected); |} | |/// |@system |version(mir_core_test) unittest |{ | int[] a = [1, 2, 4]; | uninitializedFillDefault(a); | assert(a == [0, 0, 0]); |} | |/++ |Destroy structs and unions usnig `__xdtor` member if any. |Do nothing for other types. |+/ |void xdestroy(T)(scope T[] ar) |{ | static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) | { | static if (__traits(isSame, T, __traits(parent, ar[0].__xdtor))) | { | pragma(inline, false); | foreach_reverse (ref e; ar) | e.__xdtor(); | } | } |} | |/// |nothrow @nogc version(mir_core_test) unittest |{ | __gshared int d; | __gshared int c; | struct D { ~this() nothrow @nogc {d++;} } | extern(C++) | struct C { ~this() nothrow @nogc {c++;} } | C[2] carray; | D[2] darray; | carray.xdestroy; | darray.xdestroy; | assert(c == 2); | assert(d == 2); | c = 0; | d = 0; |} | | |template emplaceRef(T) |{ | void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args) | { | import core.lifetime: forward; | import core.internal.lifetime: emplaceRef; | return emplaceRef!T(chunk, forward!args); | } |} | |void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args) |if (is(UT == Unqual!UT)) |{ | import core.lifetime: forward; | emplaceRef!UT(chunk, forward!args); |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/conv.d has no code <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-enums.lst |/++ |Enum utilities. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Macros: |+/ |module mir.enums; | |private bool hasSeqGrow(T)(T[] elems) | if (__traits(isIntegral, T)) |{ | assert(elems.length); | auto min = elems[0]; | foreach (i, e; elems) | if (i != e - min) | return false; | return true; |} | |/++ |Enum index that corresponds of the list returned by `std.traits.EnumMembers`. |Returns: | enum member position index in the enum definition that corresponds the `value`. |+/ |bool getEnumIndex(T)(const T value, ref uint index) | @safe pure nothrow @nogc | if (is(T == enum)) |{ | import std.traits: EnumMembers, isSomeString; | import mir.utility: _expect; | | static if (__traits(isFloating, T)) | { | // TODO: index based binary searach | foreach (i, member; enumMembers!T) | { | if (value == member) | { | index = cast(uint) i; | return true; | } | } | return false; | } | else | static if (!__traits(isIntegral, T)) //strings | { | enum string[1] stringEnumValue(alias symbol) = [symbol]; | return getEnumIndexFromKey!(T, false, stringEnumValue)(value, index); | } | else | static if (hasSeqGrow(enumMembers!T)) | { | import std.traits: Unsigned; | const shifted = cast(Unsigned!(typeof(value - T.min)))(value - T.min); | if (_expect(shifted < enumMembers!T.length, true)) | { | index = cast(uint) shifted; | return true; | } | return false; | } | else | static if (is(T : bool)) | { | index = !value; | return true; | } | else | { | import std.traits: Unsigned; | alias U = Unsigned!(typeof(T.max - T.min + 1)); | | enum length = cast(size_t)cast(U)(T.max - T.min + 1); | | const shifted = cast(size_t)cast(U)(value - T.min); | | static if (length <= 255) | { | static immutable ubyte[length] table = (){ | ubyte[length] ret; | foreach (i, member; enumMembers!T) | { | ret[member - T.min] = cast(ubyte)(i + 1); | } | return ret; | }(); | | if (_expect(shifted < length, true)) | { | int id = table[shifted] - 1; | if (_expect(id >= 0, true)) | { | index = id; | return true; | } | } | return false; | } | else | { | switch (value) | { | foreach (i, member; EnumMembers!T) | { | case member: | index = i; | return true; | } | default: return false; | } | } | } |} | |/// |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | import std.meta: AliasSeq; | | enum Common { a, b, c } | enum Reversed { a = 1, b = 0, c = -1 } | enum Shifted { a = -4, b, c } | enum Small { a = -4, b, c = 10 } | enum Big { a = -4, b, c = 1000 } | enum InverseBool { True = true, False = false } | enum FP : float { a = -4, b, c } | enum S : string { a = "а", b = "б", c = "ц" } | | uint index = -1; | foreach (E; AliasSeq!(Common, Reversed, Shifted, Small, Big, FP, S)) | { | assert(getEnumIndex(E.a, index) && index == 0); | assert(getEnumIndex(E.b, index) && index == 1); | assert(getEnumIndex(E.c, index) && index == 2); | } | | assert(getEnumIndex(InverseBool.True, index) && index == 0); | assert(getEnumIndex(InverseBool.False, index) && index == 1); |} | |/++ |Static immutable instance of `[std.traits.EnumMembers!T]`. |+/ |template enumMembers(T) | if (is(T == enum)) |{ | import std.traits: EnumMembers; | /// | static immutable T[EnumMembers!T.length] enumMembers = [EnumMembers!T]; |} | |/// |version(mir_core_test) unittest |{ | enum E {a = 1, b = -1, c} | static assert(enumMembers!E == [E.a, E.b, E.c]); |} | |/++ |Static immutable instance of Enum Identifiers. |+/ |template enumIdentifiers(T) | if (is(T == enum)) |{ | import std.traits: EnumMembers; | static immutable string[EnumMembers!T.length] enumIdentifiers = () { | string[EnumMembers!T.length] identifiers; | static foreach(i, member; EnumMembers!T) | identifiers[i] = __traits(identifier, EnumMembers!T[i]); | return identifiers; | } (); |} | |/// |version(mir_core_test) unittest |{ | enum E {z = 1, b = -1, c} | static assert(enumIdentifiers!E == ["z", "b", "c"]); |} | |/++ |Aliases itself to $(LREF enumMembers) for string enums and |$(LREF enumIdentifiers) for integral and floating point enums. |+/ |template enumStrings(T) | if (is(T == enum)) |{ | static if (is(T : C[], C)) | alias enumStrings = enumMembers!T; | else | alias enumStrings = enumIdentifiers!T; |} | |/// |version(mir_core_test) unittest |{ | enum E {z = 1, b = -1, c} | static assert(enumStrings!E == ["z", "b", "c"]); | | enum S {a = "A", b = "B", c = ""} | static assert(enumStrings!S == [S.a, S.b, S.c]); |} | |/++ |Params: | index = enum index `std.traits.EnumMembers!T` |Returns: | A enum value that corresponds to the index. |Note: | The function doesn't check that index is less then `EnumMembers!T.length`. |+/ |T unsafeEnumFromIndex(T)(size_t index) | @trusted pure nothrow @nogc | if (is(T == enum)) |{ | static if (__traits(isIntegral, T)) | enum bool fastConv = hasSeqGrow(enumMembers!T); | else | enum bool fastConv = false; | | assert(index < enumMembers!T.length); | | static if (fastConv) | { | return cast(T) (index + enumMembers!T[0]); | } | else | { | return enumMembers!T[index]; | } |} | |/// |version(mir_core_test) |unittest |{ | enum Linear | { | one = 1, | two = 2 | } | | static assert(is(typeof(unsafeEnumFromIndex!Linear(0)) == Linear)); | assert(unsafeEnumFromIndex!Linear(0) == Linear.one); | assert(unsafeEnumFromIndex!Linear(1) == Linear.two); | | enum Mixed | { | one = 1, | oneAgain = 1, | two = 2 | } | | assert(unsafeEnumFromIndex!Mixed(0) == Mixed.one); | assert(unsafeEnumFromIndex!Mixed(1) == Mixed.one); | assert(unsafeEnumFromIndex!Mixed(2) == Mixed.two); |} | |/++ |Params: | T = enum type to introspect | key = some string that corresponds to some key name of the given enum | index = resulting enum index if this method returns true. |Returns: | boolean whether the key was found in the enum keys and if so, index is set. |+/ |template getEnumIndexFromKey(T, bool caseInsensitive = true, getKeysTemplate...) | if (is(T == enum) && getKeysTemplate.length <= 1) |{ | /// | bool getEnumIndexFromKey(C)(scope const(C)[] key, ref uint index) | @safe pure nothrow @nogc | if (is(C == char) || is(C == wchar) || is(C == dchar)) | { | import mir.string_table; | import mir.utility: simpleSort, _expect; | import std.traits: EnumMembers; | import std.meta: staticIndexOf; | | alias String = immutable(C)[]; | | static if (getKeysTemplate.length) | { | alias keysOfImpl = getKeysTemplate[0]; | enum String[] keysOf(alias symbol) = keysOfImpl!symbol; | } | else | static if (is(T : W[], W)) | enum String[1] keysOf(alias symbol) = [cast(String)symbol]; | else | enum String[1] keysOf(alias symbol) = [__traits(identifier, symbol)]; | | enum keys = () { | String[] keys; | foreach(i, member; EnumMembers!T) | keys ~= keysOf!(EnumMembers!T[i]); | return keys; | } (); | | static if (keys.length == 0) | { | return false; | } | else | { | enum indexLength = keys.length + 1; | alias ct = createTable!C; | static immutable table = ct!(keys, caseInsensitive); | static immutable indices = () | { | minimalSignedIndexType!indexLength[indexLength] indices; | | foreach (i, member; EnumMembers!T) | foreach (key; keysOf!(EnumMembers!T[i])) | { | static if (caseInsensitive) | { | key = key.dup.fastToUpperInPlace; | } | indices[table[key]] = i; | } | | return indices; | } (); | | uint stringId = void; | if (_expect(table.get(key, stringId), true)) | { | index = indices[stringId]; | return true; | } | return false; | } | } |} | |/// |unittest |{ | enum Short | { | hello, | world | } | | enum Long | { | This, | Is, | An, | Enum, | With, | Lots, | Of, | Very, | Long, | EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tm | } | | uint i; | assert(getEnumIndexFromKey!Short("hello", i)); | assert(i == 0); | assert(getEnumIndexFromKey!Short("world", i)); | assert(i == 1); | assert(!getEnumIndexFromKey!Short("foo", i)); | | assert(getEnumIndexFromKey!Short("HeLlO", i)); | assert(i == 0); | assert(getEnumIndexFromKey!Short("WoRLd", i)); | assert(i == 1); | | assert(!getEnumIndexFromKey!(Short, false)("HeLlO", i)); | assert(!getEnumIndexFromKey!(Short, false)("WoRLd", i)); | | assert(getEnumIndexFromKey!Long("Is", i)); | assert(i == 1); | assert(getEnumIndexFromKey!Long("Long", i)); | assert(i == 8); | assert(getEnumIndexFromKey!Long("EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tm", i)); | assert(i == 9); | assert(!getEnumIndexFromKey!Long("EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCodeatm", i)); | | assert(!getEnumIndexFromKey!(Long, false)("EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tM", i)); | assert(!getEnumIndexFromKey!(Long, false)("entriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tm", i)); |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/enums.d has no code <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-exception.lst |/++ |`@nogc` exceptions and errors definitions. | |Most of the API Requires DIP1008. |+/ |module mir.exception; | |version(D_Exceptions): | |version(D_Ddoc) | private enum _version_D_Ddoc = true; |else | private enum _version_D_Ddoc = false; | |private enum NOGCEXP = __traits(compiles, (()@nogc {throw new Exception("");})()); |private enum HASFORMAT = __traits(compiles, (()@nogc {import mir.format;})()); | |package template staticException(string fmt, string file, int line) |{ | static immutable staticException = new Exception(fmt, file, line); |} | |/// |auto ref enforce(string fmt, string file = __FILE__, int line = __LINE__, Expr)(scope auto return ref Expr arg) @trusted |{ | version(LDC) pragma(inline, true); | import core.lifetime: forward; | import mir.utility: _expect; | static if (__traits(compiles, arg !is null)) | { | if (_expect(arg !is null, true)) | return forward!arg; | } | else | { | if (_expect(cast(bool)arg, true)) | return forward!arg; | } | throw staticException!(fmt, file, line); |} | |/// |@safe pure nothrow @nogc |version (mir_core_test) unittest |{ | import mir.exception; | try enforce!"Msg"(false); | catch(Exception e) assert(e.msg == "Msg"); |} | |/++ |+/ |class MirException : Exception |{ | /// | mixin MirThrowableImpl; |} | |/// Generic style |version (mir_test) static if (NOGCEXP && HASFORMAT) |@safe pure nothrow @nogc |unittest |{ | static if (__traits(compiles, (()@nogc {import mir.format;})())) | { | import mir.exception; | try throw new MirException("Hi D", 2, "!"); | catch(Exception e) assert(e.msg == "Hi D2!"); | } |} | |/// C++ style |version (mir_test) static if (NOGCEXP && HASFORMAT) |@safe pure nothrow @nogc |unittest |{ | static if (__traits(compiles, (()@nogc {import mir.format;})())) | { | import mir.exception; | import mir.format; | try throw new MirException(stringBuf() << "Hi D" << 2 << "!" << getData); | catch(Exception e) assert(e.msg == "Hi D2!"); | } |} | |/// Low-level style |version (mir_test) static if (NOGCEXP && HASFORMAT) |@safe pure nothrow @nogc |unittest |{ | static if (__traits(compiles, (()@nogc {import mir.format;})())) | { | import mir.exception; | import mir.format; | auto buffer = stringBuf(); | try throw new MirException(buf.print( "Hi D", 2, "!").data); | catch(Exception e) assert(e.msg == "Hi D2!"); | } |} | |/// |version (mir_core_test) static if (NOGCEXP) |@safe pure nothrow @nogc |unittest |{ | @safe pure nothrow @nogc | bool func(scope const(char)[] msg) | { | /// scope messages are copied | try throw new MirException(msg); | catch(Exception e) assert(e.msg == msg); | | /// immutable strings are not copied | static immutable char[] gmsg = "global msg"; | try throw new MirException(gmsg); | catch(Exception e) assert(e.msg is gmsg); | | return __ctfe; | } | | assert(func("runtime-time check") == 0); | | static assert(func("compile-time check") == 1); |} | |// /// |// auto ref enforce(T, Args...)(scope auto return ref T arg, lazy @nogc Args args, string file = __FILE__, int line = __LINE__) @nogc |// if (Args.length) |// { |// import mir.utility: _expect; |// static if (__traits(compiles, arg !is null)) |// { |// if (_expect(arg !is null, true)) |// return arg; |// } |// else |// { |// if (_expect(cast(bool)arg, true)) |// return arg; |// } |// import mir.format; |// stringBuf buf; |// throw new MirException(buf.print(args).data, file, line); |// } | |// /// |// @safe pure nothrow @nogc |// version (mir_core_test) unittest static if (NOGCEXP && HASFORMAT) |// { |// import mir.exception; |// try enforce(false, "Hi D", 2, "!"); |// catch(Exception e) assert(e.msg == "Hi D2!"); |// } | |// /// |// auto ref enforce(T)(scope auto return ref T arg, lazy scope const(char)[] msg, string file = __FILE__, int line = __LINE__) @nogc |// { |// import core.lifetime: forward; |// import mir.utility: _expect; |// static if (__traits(compiles, arg !is null)) |// { |// if (_expect(arg !is null, true)) |// return forward!arg[0]; |// } |// else |// { |// if (_expect(cast(bool)arg, true)) |// return forward!arg[0]; |// } |// throw new MirException(msg, file, line); |// } | |// /// |// @safe pure nothrow @nogc |// version (mir_core_test) unittest static if (NOGCEXP && HASFORMAT) |// { |// import mir.exception; |// try enforce(false, "Msg"); |// catch(Exception e) assert(e.msg == "Msg"); |// } | | |/++ |+/ |class MirError : Error |{ | /// | mixin MirThrowableImpl; |} | |/// |@system pure nothrow @nogc |version (mir_test) static if (NOGCEXP) |unittest |{ | @system pure nothrow @nogc | bool func(scope const(char)[] msg) | { | /// scope messages are copied | try throw new MirException(msg); | catch(Exception e) assert(e.msg == msg); | | /// immutable strings are not copied | static immutable char[] gmsg = "global msg"; | try throw new MirError(gmsg); | catch(Error e) assert(e.msg is gmsg); | | return __ctfe; | } | | assert(func("runtime-time check") == 0); | | static assert(func("compile-time check") == 1); |} | |/++ |+/ |mixin template MirThrowableImpl() |{ | private bool _global; | private char[maxMirExceptionMsgLen] _payload = void; | import mir.exception: maxMirExceptionMsgLen, mirExceptionInitilizePayloadImpl; | | /++ | Params: | msg = message. No-scope `msg` is assumed to have the same lifetime as the throwable. scope strings are copied to internal buffer. | file = file name, zero terminated global string | line = line number | nextInChain = next exception in the chain (optional) | +/ 0000000| @nogc @safe pure nothrow this(scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) | { 0000000| super((() @trusted => cast(immutable) mirExceptionInitilizePayloadImpl(_payload, msg))(), file, line, nextInChain); | } | | /// ditto 0000000| @nogc @safe pure nothrow this(scope const(char)[] msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) | { 0000000| super((() @trusted => cast(immutable) mirExceptionInitilizePayloadImpl(_payload, msg))(), file, line, nextInChain); | } | | /// ditto 0000000| @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) | { 0000000| this._global = true; 0000000| super(msg, file, line, nextInChain); | } | | /// ditto 0000000| @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) | { 0000000| this._global = true; 0000000| super(msg, file, line, nextInChain); | } | | /// | ~this() @trusted | { | import mir.internal.memory: free; 0000000| if (!_global && msg.ptr != _payload.ptr) 0000000| free(cast(void*)msg.ptr); | } | | /++ | Generic multiargument overload. | Constructs a string using the `print` function. | +/ | this(Args...)(auto ref scope const Args args, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure | if (Args.length > 1) | { | static assert (__traits(compiles, {import mir.format;}), "MirThrowableImpl needs mir-algorithm for mir.format and exception formatting."); | import mir.format; | auto buf = stringBuf(); | foreach(ref arg; args) | buf.print(arg); | this(buf.data, file, line, nextInChain); | } |} | |/// |enum maxMirExceptionMsgLen = 447; | |pragma(inline, false) |pure nothrow @nogc @safe |const(char)[] mirExceptionInitilizePayloadImpl(ref return char[maxMirExceptionMsgLen] payload, scope const(char)[] msg) |{ | import mir.internal.memory: malloc; | import core.stdc.string: memcpy; 0000000| if (msg.length > payload.length) | { 0000000| if (auto ret = (() @trusted | { 0000000| if (__ctfe) | return null; 0000000| if (auto ptr = malloc(msg.length)) | { 0000000| memcpy(ptr, msg.ptr, msg.length); 0000000| return cast(const(char)[]) ptr[0 .. msg.length]; | } 0000000| return null; | })()) 0000000| return ret; 0000000| msg = msg[0 .. payload.length]; | // remove tail UTF-8 symbol chunk if any 0000000| uint c = msg[$-1]; 0000000| if (c > 0b_0111_1111) | { | do { 0000000| c = msg[$-1]; 0000000| msg = msg[0 .. $ - 1]; | } 0000000| while (msg.length && c < 0b_1100_0000); | } | } 0000000| if (__ctfe) | payload[][0 .. msg.length] = msg; | else 0000000| (() @trusted => memcpy(payload.ptr, msg.ptr, msg.length))(); 0000000| return payload[0 .. msg.length]; |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/exception.d is 0% covered <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-functional.lst |/++ |Functions that manipulate other functions. |This module provides functions for compile time function composition. These |functions are helpful when constructing predicates for the algorithms in |$(MREF mir, ndslice). |$(BOOKTABLE $(H2 Functions), |$(TR $(TH Function Name) $(TH Description)) | $(TR $(TD $(LREF naryFun)) | $(TD Create a unary, binary or N-nary function from a string. Most often | used when defining algorithms on ranges and slices. | )) | $(TR $(TD $(LREF pipe)) | $(TD Join a couple of functions into one that executes the original | functions one after the other, using one function's result for the next | function's argument. | )) | $(TR $(TD $(LREF not)) | $(TD Creates a function that negates another. | )) | $(TR $(TD $(LREF reverseArgs)) | $(TD Predicate that reverses the order of its arguments. | )) | $(TR $(TD $(LREF forward)) | $(TD Forwards function arguments with saving ref-ness. | )) | $(TR $(TD $(LREF tuple)) | $(TD Removes $(LREF Ref) shell. | )) | $(TR $(TD $(LREF unref)) | $(TD Creates a $(LREF Tuple) structure. | )) | $(TR $(TD $(LREF __ref)) | $(TD Creates a $(LREF Ref) structure. | )) |) |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki, $(HTTP erdani.org, Andrei Alexandrescu (some original code from std.functional)) | |Macros: |NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |+/ |module mir.functional; | |private enum isRef(T) = is(T : Ref!T0, T0); | |import mir.math.common: optmath; | |public import core.lifetime : forward; | |@optmath: | |/++ |Constructs static array. |+/ |T[N] staticArray(T, size_t N)(T[N] a...) |{ | return a; |} | |/++ |Simple wrapper that holds a pointer. |It is used for as workaround to return multiple auto ref values. |+/ |struct Ref(T) | if (!isRef!T) |{ | @optmath: | | @disable this(); | /// | this(ref T value) @trusted | { | __ptr = &value; | } | /// | T* __ptr; | /// | ref inout(T) __value() inout @property { return *__ptr; } | /// | alias __value this; | | /// | bool opEquals(ref scope const T rhs) const scope | { | return __value == rhs; | } | | /// | bool opEquals(scope const T rhs) const scope | { | return __value == rhs; | } | | static if (__traits(hasMember, T, "toHash") || __traits(isScalar, T)) | /// | size_t toHash() const | { | return hashOf(__value); | } |} | |/// Creates $(LREF Ref) wrapper. |Ref!T _ref(T)(ref T value) |{ | return Ref!T(value); |} | |private mixin template _RefTupleMixin(T...) | if (T.length <= 26) |{ | static if (T.length) | { | enum i = T.length - 1; | static if (isRef!(T[i])) | mixin(`@optmath @property ref ` ~ cast(char)('a' + i) ~ `() { return *expand[` ~ i.stringof ~ `].__ptr; }` ); | else | mixin(`alias ` ~ cast(char)('a' + i) ~ ` = expand[` ~ i.stringof ~ `];`); | mixin ._RefTupleMixin!(T[0 .. $-1]); | } |} | |/++ |Simplified tuple structure. Some fields may be type of $(LREF Ref). |Ref stores a pointer to a values. |+/ |struct Tuple(T...) |{ | @optmath: | T expand; | alias expand this; | mixin _RefTupleMixin!T; |} | |deprecated("Use 'Tuple' instead") |alias RefTuple = Tuple; | |/// Removes $(LREF Ref) shell. |alias Unref(V : Ref!T, T) = T; |/// ditto |template Unref(V : Tuple!T, T...) |{ | import std.meta: staticMap; | alias Unref = Tuple!(staticMap!(.Unref, T)); |} | |/// ditto |alias Unref(V) = V; | |/++ |Returns: a $(LREF Tuple) structure. |+/ |Tuple!Args tuple(Args...)(auto ref Args args) |{ | return Tuple!Args(args); |} | |deprecated("Use 'tuple' instead") |alias refTuple = tuple; | |/// Removes $(LREF Ref) shell. |ref T unref(V : Ref!T, T)(scope return V value) |{ | return *value.__ptr; |} | |/// ditto |Unref!(Tuple!T) unref(V : Tuple!T, T...)(V value) |{ | typeof(return) ret; | foreach(i, ref elem; ret.expand) | elem = unref(value.expand[i]); | return ret; |} | |/// ditto |ref V unref(V)(scope return ref V value) |{ | return value; |} | |/// ditto |V unref(V)(V value) |{ | import std.traits: hasElaborateAssign; | static if (hasElaborateAssign!V) | { | import core.lifetime: move; | return move(value); | } | else | return value; |} | |private template autoExpandAndForwardElem(alias value) |{ | |} | |template autoExpandAndForward(alias value) | if (is(typeof(value) : Tuple!Types, Types...)) |{ | | import core.lifetime: move; | enum isLazy = __traits(isRef, value) || __traits(isOut, value) || __traits(isLazy, value); | template autoExpandAndForwardElem(size_t i) | { | alias T = typeof(value.expand[i]); | static if (isRef!T) | { | ref autoExpandAndForwardElem() | { | return *value.expand[i].__ptr; | } | } | else | { | static if (isLazy) | @property ref autoExpandAndForwardElem(){ pragma(inline, true); return value.expand[i]; } | else | static if (is(typeof(move(value.expand[i])))) | @property auto autoExpandAndForwardElem(){ pragma(inline, true); return move(value.expand[i]); } | else | @property auto autoExpandAndForwardElem(){ pragma(inline, true); return value.expand[i]; } | } | } | | import mir.internal.utility: Iota; | import std.meta: staticMap; | alias autoExpandAndForward = staticMap!(autoExpandAndForwardElem, Iota!(value.expand.length)); |} | |version(mir_core_test) unittest |{ | long v; | auto tup = tuple(v._ref, 2.3); | | auto f(ref long a, double b) | { | assert(b == 2.3); | assert(a == v); | assert(&a == &v); | } | | f(autoExpandAndForward!tup); |} | |private string joinStrings()(string[] strs) |{ | if (strs.length) | { | auto ret = strs[0]; | foreach(s; strs[1 .. $]) | ret ~= s; | return ret; | } | return null; |} | |private auto copyArg(alias a)() |{ | return a; |} | |/++ |Takes multiple functions and adjoins them together. The result is a |$(LREF Tuple) with one element per passed-in function. Upon |invocation, the returned tuple is the adjoined results of all |functions. |Note: In the special case where only a single function is provided |(`F.length == 1`), adjoin simply aliases to the single passed function |(`F[0]`). |+/ |template adjoin(fun...) if (fun.length && fun.length <= 26) |{ | static if (fun.length != 1) | { | import std.meta: staticMap, Filter; | static if (Filter!(_needNary, fun).length == 0) | { | /// | @optmath auto adjoin(Args...)(auto ref Args args) | { | template _adjoin(size_t i) | { | static if (__traits(compiles, &(fun[i](forward!args)))) | enum _adjoin = "Ref!(typeof(fun[" ~ i.stringof ~ "](forward!args)))(fun[" ~ i.stringof ~ "](args)), "; | else | enum _adjoin = "fun[" ~ i.stringof ~ "](args), "; | } | | import mir.internal.utility; | mixin("return tuple(" ~ [staticMap!(_adjoin, Iota!(fun.length))].joinStrings ~ ");"); | } | } | else alias adjoin = .adjoin!(staticMap!(naryFun, fun)); | } | else alias adjoin = naryFun!(fun[0]); |} | |/// |@safe version(mir_core_test) unittest |{ | static bool f1(int a) { return a != 0; } | static int f2(int a) { return a / 2; } | auto x = adjoin!(f1, f2)(5); | assert(is(typeof(x) == Tuple!(bool, int))); | assert(x.a == true && x.b == 2); |} | |@safe version(mir_core_test) unittest |{ | alias f = pipe!(adjoin!("a", "a * a"), "a[0]"); | static assert(is(typeof(f(3)) == int)); | auto d = 4; | static assert(is(typeof(f(d)) == Ref!int)); |} | |@safe version(mir_core_test) unittest |{ | static bool F1(int a) { return a != 0; } | auto x1 = adjoin!(F1)(5); | static int F2(int a) { return a / 2; } | auto x2 = adjoin!(F1, F2)(5); | assert(is(typeof(x2) == Tuple!(bool, int))); | assert(x2.a && x2.b == 2); | auto x3 = adjoin!(F1, F2, F2)(5); | assert(is(typeof(x3) == Tuple!(bool, int, int))); | assert(x3.a && x3.b == 2 && x3.c == 2); | | bool F4(int a) { return a != x1; } | alias eff4 = adjoin!(F4); | static struct S | { | bool delegate(int) @safe store; | int fun() { return 42 + store(5); } | } | S s; | s.store = (int a) { return eff4(a); }; | auto x4 = s.fun(); | assert(x4 == 43); |} | |//@safe |version(mir_core_test) unittest |{ | import std.meta: staticMap; | alias funs = staticMap!(naryFun, "a", "a * 2", "a * 3", "a * a", "-a"); | alias afun = adjoin!funs; | int a = 5, b = 5; | assert(afun(a) == tuple(Ref!int(a), 10, 15, 25, -5)); | assert(afun(a) == tuple(Ref!int(b), 10, 15, 25, -5)); | | static class C{} | alias IC = immutable(C); | IC foo(){return typeof(return).init;} | Tuple!(IC, IC, IC, IC) ret1 = adjoin!(foo, foo, foo, foo)(); | | static struct S{int* p;} | alias IS = immutable(S); | IS bar(){return typeof(return).init;} | enum Tuple!(IS, IS, IS, IS) ret2 = adjoin!(bar, bar, bar, bar)(); |} | |private template needOpCallAlias(alias fun) |{ | /* Determine whether or not naryFun need to alias to fun or | * fun.opCall. Basically, fun is a function object if fun(...) compiles. We | * want is(naryFun!fun) (resp., is(naryFun!fun)) to be true if fun is | * any function object. There are 4 possible cases: | * | * 1) fun is the type of a function object with static opCall; | * 2) fun is an instance of a function object with static opCall; | * 3) fun is the type of a function object with non-static opCall; | * 4) fun is an instance of a function object with non-static opCall. | * | * In case (1), is(naryFun!fun) should compile, but does not if naryFun | * aliases itself to fun, because typeof(fun) is an error when fun itself | * is a type. So it must be aliased to fun.opCall instead. All other cases | * should be aliased to fun directly. | */ | static if (is(typeof(fun.opCall) == function)) | { | import std.traits: Parameters; | enum needOpCallAlias = !is(typeof(fun)) && __traits(compiles, () { | return fun(Parameters!fun.init); | }); | } | else | enum needOpCallAlias = false; |} | |private template _naryAliases(size_t n) | if (n <= 26) |{ | static if (n == 0) | enum _naryAliases = ""; | else | { | enum i = n - 1; | enum _naryAliases = _naryAliases!i ~ "alias " ~ cast(char)('a' + i) ~ " = args[" ~ i.stringof ~ "];\n"; | } |} | |private template stringFun(string fun) |{ | /// Specialization for string lambdas | @optmath auto ref stringFun(Args...)(auto ref Args args) | if (args.length <= 26 && (Args.length == 0) == (fun.length == 0)) | { | import mir.math.common; | static if (fun.length) | { | mixin(_naryAliases!(Args.length)); | return mixin(fun); | } | else | { | return; | } | } |} | |/++ |Aliases itself to a set of functions. | |Transforms strings representing an expression into a binary function. The |strings must use symbol names `a`, `b`, ..., `z` as the parameters. |If `functions[i]` is not a string, `naryFun` aliases itself away to `functions[i]`. |+/ |template naryFun(functions...) | if (functions.length >= 1) |{ | static foreach (fun; functions) | { | static if (is(typeof(fun) : string)) | { | alias naryFun = stringFun!fun; | } | else static if (needOpCallAlias!fun) | alias naryFun = fun.opCall; | else | alias naryFun = fun; | } |} | |/// |@safe version(mir_core_test) unittest |{ | // Strings are compiled into functions: | alias isEven = naryFun!("(a & 1) == 0"); | assert(isEven(2) && !isEven(1)); |} | |/// |@safe version(mir_core_test) unittest |{ | alias less = naryFun!("a < b"); | assert(less(1, 2) && !less(2, 1)); | alias greater = naryFun!("a > b"); | assert(!greater("1", "2") && greater("2", "1")); |} | |/// `naryFun` accepts up to 26 arguments. |@safe version(mir_core_test) unittest |{ | assert(naryFun!("a * b + c")(2, 3, 4) == 10); |} | |/// `naryFun` can return by reference. |version(mir_core_test) unittest |{ | int a; | assert(&naryFun!("a")(a) == &a); |} | |/// `args` parameter tuple |version(mir_core_test) unittest |{ | assert(naryFun!("args[0] + args[1]")(2, 3) == 5); |} | |/// Multiple functions |@safe pure nothrow @nogc |version(mir_core_test) unittest |{ | alias fun = naryFun!( | (uint a) => a, | (ulong a) => a * 2, | a => a * 3, | ); | | int a = 10; | long b = 10; | float c = 10; | | assert(fun(a) == 10); | assert(fun(b) == 20); | assert(fun(c) == 30); |} | |@safe version(mir_core_test) unittest |{ | static int f1(int a) { return a + 1; } | static assert(is(typeof(naryFun!(f1)(1)) == int)); | assert(naryFun!(f1)(41) == 42); | int f2(int a) { return a + 1; } | static assert(is(typeof(naryFun!(f2)(1)) == int)); | assert(naryFun!(f2)(41) == 42); | assert(naryFun!("a + 1")(41) == 42); | | int num = 41; | assert(naryFun!"a + 1"(num) == 42); | | // Issue 9906 | struct Seen | { | static bool opCall(int n) { return true; } | } | static assert(needOpCallAlias!Seen); | static assert(is(typeof(naryFun!Seen(1)))); | assert(naryFun!Seen(1)); | | Seen s; | static assert(!needOpCallAlias!s); | static assert(is(typeof(naryFun!s(1)))); | assert(naryFun!s(1)); | | struct FuncObj | { | bool opCall(int n) { return true; } | } | FuncObj fo; | static assert(!needOpCallAlias!fo); | static assert(is(typeof(naryFun!fo))); | assert(naryFun!fo(1)); | | // Function object with non-static opCall can only be called with an | // instance, not with merely the type. | static assert(!is(typeof(naryFun!FuncObj))); |} | |@safe version(mir_core_test) unittest |{ | static int f1(int a, string b) { return a + 1; } | static assert(is(typeof(naryFun!(f1)(1, "2")) == int)); | assert(naryFun!(f1)(41, "a") == 42); | string f2(int a, string b) { return b ~ "2"; } | static assert(is(typeof(naryFun!(f2)(1, "1")) == string)); | assert(naryFun!(f2)(1, "4") == "42"); | assert(naryFun!("a + b")(41, 1) == 42); | //@@BUG | //assert(naryFun!("return a + b;")(41, 1) == 42); | | // Issue 9906 | struct Seen | { | static bool opCall(int x, int y) { return true; } | } | static assert(is(typeof(naryFun!Seen))); | assert(naryFun!Seen(1,1)); | | struct FuncObj | { | bool opCall(int x, int y) { return true; } | } | FuncObj fo; | static assert(!needOpCallAlias!fo); | static assert(is(typeof(naryFun!fo))); | assert(naryFun!fo(1,1)); | | // Function object with non-static opCall can only be called with an | // instance, not with merely the type. | static assert(!is(typeof(naryFun!FuncObj))); |} | | |/++ |N-ary predicate that reverses the order of arguments, e.g., given |`pred(a, b, c)`, returns `pred(c, b, a)`. |+/ |template reverseArgs(alias fun) |{ | import std.meta: Reverse; | /// | @optmath auto ref reverseArgs(Args...)(auto ref Args args) | if (is(typeof(fun(Reverse!args)))) | { | return fun(Reverse!args); | } | |} | |/// |@safe version(mir_core_test) unittest |{ | int abc(int a, int b, int c) { return a * b + c; } | alias cba = reverseArgs!abc; | assert(abc(91, 17, 32) == cba(32, 17, 91)); |} | |@safe version(mir_core_test) unittest |{ | int a(int a) { return a * 2; } | alias _a = reverseArgs!a; | assert(a(2) == _a(2)); |} | |@safe version(mir_core_test) unittest |{ | int b() { return 4; } | alias _b = reverseArgs!b; | assert(b() == _b()); |} | |@safe version(mir_core_test) unittest |{ | alias gt = reverseArgs!(naryFun!("a < b")); | assert(gt(2, 1) && !gt(1, 1)); | int x = 42; | bool xyz(int a, int b) { return a * x < b / x; } | auto foo = &xyz; | foo(4, 5); | alias zyx = reverseArgs!(foo); | assert(zyx(5, 4) == foo(4, 5)); |} | |/++ |Negates predicate `pred`. |+/ |template not(alias pred) |{ | static if (!is(typeof(pred) : string) && !needOpCallAlias!pred) | /// | @optmath bool not(T...)(auto ref T args) | { | return !pred(args); | } | else | alias not = .not!(naryFun!pred); |} | |/// |@safe version(mir_core_test) unittest |{ | import std.algorithm.searching : find; | import std.uni : isWhite; | string a = " Hello, world!"; | assert(find!(not!isWhite)(a) == "Hello, world!"); |} | |@safe version(mir_core_test) unittest |{ | assert(not!"a != 5"(5)); | assert(not!"a != b"(5, 5)); | | assert(not!(() => false)()); | assert(not!(a => a != 5)(5)); | assert(not!((a, b) => a != b)(5, 5)); | assert(not!((a, b, c) => a * b * c != 125 )(5, 5, 5)); |} | |private template _pipe(size_t n) |{ | static if (n) | { | enum i = n - 1; | enum _pipe = "f[" ~ i.stringof ~ "](" ~ ._pipe!i ~ ")"; | } | else | enum _pipe = "forward!args"; |} | |private template _unpipe(alias fun) |{ | import std.traits: TemplateArgsOf, TemplateOf; | static if (__traits(compiles, TemplateOf!fun)) | static if (__traits(isSame, TemplateOf!fun, .pipe)) | alias _unpipe = TemplateArgsOf!fun; | else | alias _unpipe = fun; | else | alias _unpipe = fun; | |} | |private enum _needNary(alias fun) = is(typeof(fun) : string) || needOpCallAlias!fun; | |/++ |Composes passed-in functions `fun[0], fun[1], ...` returning a |function `f(x)` that in turn returns |`...(fun[1](fun[0](x)))...`. Each function can be a regular |functions, a delegate, a lambda, or a string. |+/ |template pipe(fun...) |{ | static if (fun.length != 1) | { | import std.meta: staticMap, Filter; | alias f = staticMap!(_unpipe, fun); | static if (f.length == fun.length && Filter!(_needNary, f).length == 0) | { | /// | @optmath auto ref pipe(Args...)(auto ref Args args) | { | return mixin (_pipe!(fun.length)); | } | } | else alias pipe = .pipe!(staticMap!(naryFun, f)); | } | else alias pipe = naryFun!(fun[0]); |} | |/// |@safe version(mir_core_test) unittest |{ | assert(pipe!("a + b", a => a * 10)(2, 3) == 50); |} | |/// `pipe` can return by reference. |version(mir_core_test) unittest |{ | int a; | assert(&pipe!("a", "a")(a) == &a); |} | |/// Template bloat reduction |version(mir_core_test) unittest |{ | enum a = "a * 2"; | alias b = e => e + 2; | | alias p0 = pipe!(pipe!(a, b), pipe!(b, a)); | alias p1 = pipe!(a, b, b, a); | | static assert(__traits(isSame, p0, p1)); |} | |@safe version(mir_core_test) unittest |{ | import std.algorithm.comparison : equal; | import std.algorithm.iteration : map; | import std.array : split; | import std.conv : to; | | // First split a string in whitespace-separated tokens and then | // convert each token into an integer | assert(pipe!(split, map!(to!(int)))("1 2 3").equal([1, 2, 3])); |} | | |struct AliasCall(T, string methodName, TemplateArgs...) |{ | T __this; | alias __this this; | | /// | auto lightConst()() const @property | { | import mir.qualifier; | return AliasCall!(LightConstOf!T, methodName, TemplateArgs)(__this.lightConst); | } | | /// | auto lightImmutable()() immutable @property | { | import mir.qualifier; | return AliasCall!(LightImmutableOf!T, methodName, TemplateArgs)(__this.lightImmutable); | } | | this()(auto ref T value) | { | __this = value; | } | auto ref opCall(Args...)(auto ref Args args) | { | import std.traits: TemplateArgsOf; | mixin("return __this." ~ methodName ~ (TemplateArgs.length ? "!TemplateArgs" : "") ~ "(forward!args);"); | } |} | |/++ |Replaces call operator (`opCall`) for the value using its method. |The funciton is designed to use with $(NDSLICE, topology, vmap) or $(NDSLICE, topology, map). |Params: | methodName = name of the methods to use for opCall and opIndex | TemplateArgs = template arguments |+/ |template aliasCall(string methodName, TemplateArgs...) |{ | /++ | Params: | value = the value to wrap | Returns: | wrapped value with implemented opCall and opIndex methods | +/ | AliasCall!(T, methodName, TemplateArgs) aliasCall(T)(T value) @property | { | return typeof(return)(value); | } | | /// ditto | ref AliasCall!(T, methodName, TemplateArgs) aliasCall(T)(return ref T value) @property @trusted | { | return *cast(typeof(return)*) &value; | } |} | |/// |@safe pure nothrow version(mir_core_test) unittest |{ | static struct S | { | auto lightConst()() const @property { return S(); } | | auto fun(size_t ct_param = 1)(size_t rt_param) const | { | return rt_param + ct_param; | } | } | | S s; | | auto sfun = aliasCall!"fun"(s); | assert(sfun(3) == 4); | | auto sfun10 = aliasCall!("fun", 10)(s); // uses fun!10 | assert(sfun10(3) == 13); |} | |/++ |+/ |template recurseTemplatePipe(alias Template, size_t N, Args...) |{ | static if (N == 0) | alias recurseTemplatePipe = Args; | else | { | alias recurseTemplatePipe = Template!(.recurseTemplatePipe!(Template, N - 1, Args)); | } |} | |/// |@safe version(mir_core_test) unittest |{ | // import mir.ndslice.topology: map; | alias map(alias fun) = a => a; // some template | static assert (__traits(isSame, recurseTemplatePipe!(map, 2, "a * 2"), map!(map!"a * 2"))); |} | |/++ |+/ |template selfAndRecurseTemplatePipe(alias Template, size_t N, Args...) |{ | static if (N == 0) | alias selfAndRecurseTemplatePipe = Args; | else | { | alias selfAndRecurseTemplatePipe = Template!(.selfAndRecurseTemplatePipe!(Template, N - 1, Args)); | } |} | |/// |@safe version(mir_core_test) unittest |{ | // import mir.ndslice.topology: map; | alias map(alias fun) = a => a; // some template | static assert (__traits(isSame, selfAndRecurseTemplatePipe!(map, 2, "a * 2"), map!(pipe!("a * 2", map!"a * 2")))); |} | |/++ |+/ |template selfTemplatePipe(alias Template, size_t N, Args...) |{ | static if (N == 0) | alias selfTemplatePipe = Args; | else | { | alias selfTemplatePipe = Template!(.selfTemplatePipe!(Template, N - 1, Args)); | } |} | |/// |@safe version(mir_core_test) unittest |{ | // import mir.ndslice.topology: map; | alias map(alias fun) = a => a; // some template | static assert (__traits(isSame, selfTemplatePipe!(map, 2, "a * 2"), map!(pipe!("a * 2", map!"a * 2")))); |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/functional.d has no code <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-internal-meta.lst |module mir.internal.meta; | |/++ | Gets the matching $(DDSUBLINK spec/attribute, uda, user-defined attributes) | from the given symbol. | If the UDA is a type, then any UDAs of the same type on the symbol will | match. If the UDA is a template for a type, then any UDA which is an | instantiation of that template will match. And if the UDA is a value, | then any UDAs on the symbol which are equal to that value will match. | See_Also: | $(LREF hasUDA) | +/ |template getUDAs(T, string member, alias attribute) |{ | import std.meta : Filter, AliasSeq; | T* aggregate; | static if (__traits(compiles, __traits(getAttributes, __traits(getMember, *aggregate, member)))) | alias getUDAs = Filter!(isDesiredUDA!attribute, __traits(getAttributes, __traits(getMember, *aggregate, member))); | else | alias getUDAs = AliasSeq!(); |} | |/++ | Determine if a symbol has a given | $(DDSUBLINK spec/attribute, uda, user-defined attribute). | See_Also: | $(LREF getUDAs) | +/ |enum hasUDA(T, string member, alias attribute) = getUDAs!(T, member, attribute).length != 0; | | |private template isDesiredUDA(alias attribute) |{ | template isDesiredUDA(alias toCheck) | { | static if (is(typeof(attribute)) && !__traits(isTemplate, attribute)) | { | static if (__traits(compiles, toCheck == attribute)) | enum isDesiredUDA = toCheck == attribute; | else | enum isDesiredUDA = false; | } | else static if (is(typeof(toCheck))) | { | static if (__traits(isTemplate, attribute)) | enum isDesiredUDA = isInstanceOf!(attribute, typeof(toCheck)); | else | enum isDesiredUDA = is(typeof(toCheck) == attribute); | } | else static if (__traits(isTemplate, attribute)) | enum isDesiredUDA = isInstanceOf!(attribute, toCheck); | else | enum isDesiredUDA = is(toCheck == attribute); | } |} | |template memberTypeOf(T, string member) |{ | T* aggregate; | alias memberTypeOf = typeof(__traits(getMember, aggregate, member)); |} | |template isMemberType(T, string member) |{ | enum isMemberType = is(typeof((ref __traits(getMember, T, member) v){})) || is(__traits(getMember, T, member) : void); |} | |template isSingleMember(T, string member) |{ | import std.meta: AliasSeq; | enum isSingleMember = AliasSeq!(__traits(getMember, T, member)).length == 1; |} | |template AllMembersRec(T) |{ | static if (is(T == class) || is(T == struct) || is(T == union) || is(T == interface)) | { | static if (__traits(getAliasThis, T).length) | { | T* aggregate; | static if (is(typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))))) | { | import std.meta: Filter, AliasSeq; | alias baseMembers = AllMembersRec!(typeof(__traits(getMember, aggregate, __traits(getAliasThis, T)))); | alias members = Erase!(__traits(getAliasThis, T)[0], __traits(allMembers, T)); | alias AllMembersRec = NoDuplicates!(AliasSeq!(baseMembers, members)); | } | else | { | alias AllMembersRec = __traits(allMembers, T); | } | } | else | { | alias AllMembersRec = __traits(allMembers, T); | } | } | else | { | import std.meta: AliasSeq; | alias AllMembersRec = AliasSeq!(); | } |} | |alias ConstOf(T) = const T; |enum Alignof(T) = T.alignof; |enum canConstructWith(From, To) = __traits(compiles, (From a) { To b = a; } ); |enum canImplicitlyRemoveConst(T) = __traits(compiles, {static T _function_(ref const T a) { return a; }} ); |enum canRemoveConst(T) = canConstructWith!(const T, T); |enum canRemoveImmutable(T) = canConstructWith!(immutable T, T); |enum hasOpPostMove(T) = __traits(hasMember, T, "opPostMove"); |enum hasOpCmp(T) = __traits(hasMember, T, "opCmp"); |enum hasToHash(T) = __traits(hasMember, T, "toHash"); |static if (__VERSION__ < 2094) | enum isCopyable(S) = is(typeof({ S foo = S.init; S copy = foo; })); |else | enum isCopyable(S) = __traits(isCopyable, S); |enum isPOD(T) = __traits(isPOD, T); |enum Sizeof(T) = T.sizeof; | |enum hasInoutConstruction(T) = __traits(compiles, {static struct S { T a; this(ref return scope inout S rhs) inout { this.a = rhs.a; } }} ); |enum hasConstConstruction(T) = __traits(compiles, {static struct S { T a; this(ref return scope const S rhs) const { this.a = rhs.a; } }} ); |enum hasImmutableConstruction(T) = __traits(compiles, {static struct S { T a; this(ref return scope immutable S rhs) immutable { this.a = rhs.a; } }} ); |enum hasMutableConstruction(T) = __traits(compiles, {static struct S { T a; this(ref return scope S rhs) { this.a = rhs.a; } }} ); |enum hasSemiImmutableConstruction(T) = __traits(compiles, {static struct S { T a; this(ref return scope const S rhs) immutable { this.a = rhs.a; } }} ); |enum hasSemiMutableConstruction(T) = __traits(compiles, {static struct S { T a; this(ref return scope const S rhs) { this.a = rhs.a; } }} ); | |@safe version(mir_core_test) unittest |{ | static struct S { this(ref return scope inout S) inout {} } | static inout(S) _function_(ref inout S a) { return a; } | static struct C2 { uint* a; this(ref return scope const S) const {} } | static assert(hasInoutConstruction!uint); | static assert(hasInoutConstruction!(immutable(uint)[])); | static assert(hasInoutConstruction!(typeof(null))); | static assert(hasInoutConstruction!S); |} | |template staticIsSorted(alias cmp, Seq...) |{ | static if (Seq.length <= 1) | enum staticIsSorted = true; | else static if (Seq.length == 2) | enum staticIsSorted = cmp!(Seq[0], Seq[1]); | else | { | enum staticIsSorted = | cmp!(Seq[($ / 2) - 1], Seq[$ / 2]) && | staticIsSorted!(cmp, Seq[0 .. $ / 2]) && | staticIsSorted!(cmp, Seq[$ / 2 .. $]); | } |} | |template TryRemoveConst(T) |{ | import std.traits: Unqual; | alias U = Unqual!T; | static if (canImplicitlyRemoveConst!U) | { | alias TryRemoveConst = U; | } | else | { | alias TryRemoveConst = T; | } |} | | |template TypeCmp(A, B) |{ | enum bool TypeCmp = is(A == B) ? false: | is(A == typeof(null)) ? true: | is(B == typeof(null)) ? false: | is(A == void) ? true: | is(B == void) ? false: | A.sizeof < B.sizeof ? true: | A.sizeof > B.sizeof ? false: | A.mangleof < B.mangleof; |} | |template isInstanceOf(alias S) |{ | enum isInstanceOf(T) = is(T == S!Args, Args...); |} | |version(mir_core_test) unittest |{ | static assert(is(TryRemoveConst!(const int) == int)); |} | | |// taken from std.meta.allSatisfy |template allSatisfy(alias F, T...) |{ | static foreach (Ti; T) | { | static if (!is(typeof(allSatisfy) == bool) && // not yet defined | !F!(Ti)) | { | enum allSatisfy = false; | } | } | static if (!is(typeof(allSatisfy) == bool)) // if not yet defined | { | enum allSatisfy = true; | } |} | |template Erase(T, TList...) |{ | alias Erase = GenericErase!(T, TList).result; |} | |template Erase(alias T, TList...) |{ | alias Erase = GenericErase!(T, TList).result; |} | |template GenericErase(args...) |if (args.length >= 1) |{ | import std.meta: AliasSeq; | | alias e = OldAlias!(args[0]); | alias tuple = args[1 .. $] ; | | static if (tuple.length) | { | alias head = OldAlias!(tuple[0]); | alias tail = tuple[1 .. $]; | | static if (isSame!(e, head)) | alias result = tail; | else | alias result = AliasSeq!(head, GenericErase!(e, tail).result); | } | else | { | alias result = AliasSeq!(); | } |} | |template Pack(T...) |{ | alias Expand = T; | enum equals(U...) = isSame!(Pack!T, Pack!U); |} | | |template EraseAll(T, TList...) |{ | alias EraseAll = GenericEraseAll!(T, TList).result; |} | |template EraseAll(alias T, TList...) |{ | alias EraseAll = GenericEraseAll!(T, TList).result; |} | |template GenericEraseAll(args...) |if (args.length >= 1) |{ | import std.meta: AliasSeq; | | alias e = OldAlias!(args[0]); | alias tuple = args[1 .. $]; | | static if (tuple.length) | { | alias head = OldAlias!(tuple[0]); | alias tail = tuple[1 .. $]; | alias next = AliasSeq!( | GenericEraseAll!(e, tail[0..$/2]).result, | GenericEraseAll!(e, tail[$/2..$]).result | ); | | static if (isSame!(e, head)) | alias result = next; | else | alias result = AliasSeq!(head, next); | } | else | { | alias result = AliasSeq!(); | } |} | |template OldAlias(T) |{ | alias OldAlias = T; |} | |template OldAlias(alias T) |{ | alias OldAlias = T; |} | |template EraseAllN(uint N, TList...) |{ | static if (N == 1) | { | alias EraseAllN = EraseAll!(TList[0], TList[1 .. $]); | } | else | { | static if (N & 1) | alias EraseAllN = EraseAllN!(N / 2, TList[N / 2 + 1 .. N], | EraseAllN!(N / 2 + 1, TList[0 .. N / 2 + 1], TList[N .. $])); | else | alias EraseAllN = EraseAllN!(N / 2, TList[N / 2 .. N], | EraseAllN!(N / 2, TList[0 .. N / 2], TList[N .. $])); | } |} | |template NoDuplicates(TList...) |{ | static if (TList.length >= 2) | { | import std.meta: AliasSeq; | | alias fst = NoDuplicates!(TList[0 .. $/2]); | alias snd = NoDuplicates!(TList[$/2 .. $]); | alias NoDuplicates = AliasSeq!(fst, EraseAllN!(fst.length, fst, snd)); | } | else | { | alias NoDuplicates = TList; | } |} | | |template isSame(ab...) |if (ab.length == 2) |{ | static if (is(ab[0]) && is(ab[1])) | { | enum isSame = is(ab[0] == ab[1]); | } | else static if (!is(ab[0]) && !is(ab[1]) && | !(is(typeof(&ab[0])) && is(typeof(&ab[1]))) && | __traits(compiles, { enum isSame = ab[0] == ab[1]; })) | { | enum isSame = (ab[0] == ab[1]); | } | else | { | enum isSame = __traits(isSame, ab[0], ab[1]); | } |} | |template Mod(From, To) |{ | template Mod(T) | { | static if (is(T == From)) | alias Mod = To; | else | alias Mod = T; | } |} | |template Replace(From, To, T...) |{ | import std.meta: staticMap; | alias Replace = staticMap!(Mod!(From, To), T); |} | |template ReplaceTypeUnless(alias pred, From, To, T...) |{ | static if (T.length == 1) | { | import std.meta: staticMap; | static if (pred!(T[0])) | alias ReplaceTypeUnless = T[0]; | else static if (is(T[0] == From)) | alias ReplaceTypeUnless = To; | else static if (is(T[0] == const(U), U)) | alias ReplaceTypeUnless = const(ReplaceTypeUnless!(pred, From, To, U)); | else static if (is(T[0] == immutable(U), U)) | alias ReplaceTypeUnless = immutable(ReplaceTypeUnless!(pred, From, To, U)); | else static if (is(T[0] == shared(U), U)) | alias ReplaceTypeUnless = shared(ReplaceTypeUnless!(pred, From, To, U)); | else static if (is(T[0] == U*, U)) | { | static if (is(U == function)) | alias ReplaceTypeUnless = replaceTypeInFunctionTypeUnless!(pred, From, To, T[0]); | else | alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)*; | } | else static if (is(T[0] == delegate)) | { | alias ReplaceTypeUnless = replaceTypeInFunctionTypeUnless!(pred, From, To, T[0]); | } | else static if (is(T[0] == function)) | { | static assert(0, "Function types not supported," ~ | " use a function pointer type instead of " ~ T[0].stringof); | } | else static if (is(T[0] == U!V, alias U, V...)) | { | template replaceTemplateArgs(T...) | { | static if (is(typeof(T[0]))) | static if (__traits(compiles, {alias replaceTemplateArgs = T[0];})) | alias replaceTemplateArgs = T[0]; | else | enum replaceTemplateArgs = T[0]; | else | alias replaceTemplateArgs = ReplaceTypeUnless!(pred, From, To, T[0]); | } | alias ReplaceTypeUnless = U!(staticMap!(replaceTemplateArgs, V)); | } | else static if (is(T[0] == struct)) | // don't match with alias this struct below | // https://issues.dlang.org/show_bug.cgi?id=15168 | alias ReplaceTypeUnless = T[0]; | else static if (is(T[0] == enum)) | alias ReplaceTypeUnless = T[0]; | else static if (is(T[0] == U[], U)) | alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)[]; | else static if (is(T[0] == U[n], U, size_t n)) | alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)[n]; | else static if (is(T[0] == U[V], U, V)) | alias ReplaceTypeUnless = | ReplaceTypeUnless!(pred, From, To, U)[ReplaceTypeUnless!(pred, From, To, V)]; | else | alias ReplaceTypeUnless = T[0]; | } | else static if (T.length > 1) | { | import std.meta: AliasSeq; | alias ReplaceTypeUnless = AliasSeq!(ReplaceTypeUnless!(pred, From, To, T[0]), | ReplaceTypeUnless!(pred, From, To, T[1 .. $])); | } | else | { | import std.meta: AliasSeq; | alias ReplaceTypeUnless = AliasSeq!(); | } |} | |@safe version(mir_core_test) unittest |{ | import std.typecons: Tuple; | import std.traits : isArray; | static assert( | is(ReplaceTypeUnless!(isArray, int, string, int*) == string*) && | is(ReplaceTypeUnless!(isArray, int, string, int[]) == int[]) && | is(ReplaceTypeUnless!(isArray, int, string, Tuple!(int, int[])) | == Tuple!(string, int[])) | ); |} | |template Contains(Types...) |{ | import std.meta: staticIndexOf; | enum Contains(T) = staticIndexOf!(T, Types) >= 0; |} | |template replaceTypeInFunctionTypeUnless(alias pred, From, To, fun) |{ | import std.meta; | import std.traits; | alias RX = ReplaceTypeUnless!(pred, From, To, ReturnType!fun); | alias PX = AliasSeq!(ReplaceTypeUnless!(pred, From, To, Parameters!fun)); | // Wrapping with AliasSeq is neccesary because ReplaceType doesn't return | // tuple if Parameters!fun.length == 1 | string gen() | { | enum linkage = functionLinkage!fun; | alias attributes = functionAttributes!fun; | enum variadicStyle = variadicFunctionStyle!fun; | alias storageClasses = ParameterStorageClassTuple!fun; | string result; | result ~= "extern(" ~ linkage ~ ") "; | static if (attributes & FunctionAttribute.ref_) | { | result ~= "ref "; | } | result ~= "RX"; | static if (is(fun == delegate)) | result ~= " delegate"; | else | result ~= " function"; | result ~= "("; | static foreach (i; 0 .. PX.length) | { | if (i) | result ~= ", "; | if (storageClasses[i] & ParameterStorageClass.scope_) | result ~= "scope "; | if (storageClasses[i] & ParameterStorageClass.out_) | result ~= "out "; | if (storageClasses[i] & ParameterStorageClass.ref_) | result ~= "ref "; | if (storageClasses[i] & ParameterStorageClass.lazy_) | result ~= "lazy "; | if (storageClasses[i] & ParameterStorageClass.return_) | result ~= "return "; | result ~= "PX[" ~ i.stringof ~ "]"; | } | static if (variadicStyle == Variadic.typesafe) | result ~= " ..."; | else static if (variadicStyle != Variadic.no) | result ~= ", ..."; | result ~= ")"; | static if (attributes & FunctionAttribute.pure_) | result ~= " pure"; | static if (attributes & FunctionAttribute.nothrow_) | result ~= " nothrow"; | static if (attributes & FunctionAttribute.property) | result ~= " @property"; | static if (attributes & FunctionAttribute.trusted) | result ~= " @trusted"; | static if (attributes & FunctionAttribute.safe) | result ~= " @safe"; | static if (attributes & FunctionAttribute.nogc) | result ~= " @nogc"; | static if (attributes & FunctionAttribute.system) | result ~= " @system"; | static if (attributes & FunctionAttribute.const_) | result ~= " const"; | static if (attributes & FunctionAttribute.immutable_) | result ~= " immutable"; | static if (attributes & FunctionAttribute.inout_) | result ~= " inout"; | static if (attributes & FunctionAttribute.shared_) | result ~= " shared"; | static if (attributes & FunctionAttribute.return_) | result ~= " return"; | return result; | } | mixin("alias replaceTypeInFunctionTypeUnless = " ~ gen() ~ ";"); |} | |enum false_(T) = false; | |alias ReplaceType(From, To, T...) = ReplaceTypeUnless!(false_, From, To, T); | |version(mir_core_test) @safe unittest |{ | import std.typecons: Unique, Tuple; | template Test(Ts...) | { | static if (Ts.length) | { | //pragma(msg, "Testing: ReplaceType!("~Ts[0].stringof~", " | // ~Ts[1].stringof~", "~Ts[2].stringof~")"); | static assert(is(ReplaceType!(Ts[0], Ts[1], Ts[2]) == Ts[3]), | "ReplaceType!("~Ts[0].stringof~", "~Ts[1].stringof~", " | ~Ts[2].stringof~") == " | ~ReplaceType!(Ts[0], Ts[1], Ts[2]).stringof); | alias Test = Test!(Ts[4 .. $]); | } | else alias Test = void; | } | //import core.stdc.stdio; | alias RefFun1 = ref int function(float, long); | alias RefFun2 = ref float function(float, long); | extern(C) int printf(const char*, ...) nothrow @nogc @system; | extern(C) float floatPrintf(const char*, ...) nothrow @nogc @system; | int func(float); | int x; | struct S1 { void foo() { x = 1; } } | struct S2 { void bar() { x = 2; } } | alias Pass = Test!( | int, float, typeof(&func), float delegate(float), | int, float, typeof(&printf), typeof(&floatPrintf), | int, float, int function(out long, ...), | float function(out long, ...), | int, float, int function(ref float, long), | float function(ref float, long), | int, float, int function(ref int, long), | float function(ref float, long), | int, float, int function(out int, long), | float function(out float, long), | int, float, int function(lazy int, long), | float function(lazy float, long), | int, float, int function(out long, ref const int), | float function(out long, ref const float), | int, int, int, int, | int, float, int, float, | int, float, const int, const float, | int, float, immutable int, immutable float, | int, float, shared int, shared float, | int, float, int*, float*, | int, float, const(int)*, const(float)*, | int, float, const(int*), const(float*), | const(int)*, float, const(int*), const(float), | int*, float, const(int)*, const(int)*, | int, float, int[], float[], | int, float, int[42], float[42], | int, float, const(int)[42], const(float)[42], | int, float, const(int[42]), const(float[42]), | int, float, int[int], float[float], | int, float, int[double], float[double], | int, float, double[int], double[float], | int, float, int function(float, long), float function(float, long), | int, float, int function(float), float function(float), | int, float, int function(float, int), float function(float, float), | int, float, int delegate(float, long), float delegate(float, long), | int, float, int delegate(float), float delegate(float), | int, float, int delegate(float, int), float delegate(float, float), | int, float, Unique!int, Unique!float, | int, float, Tuple!(float, int), Tuple!(float, float), | int, float, RefFun1, RefFun2, | S1, S2, | S1[1][][S1]* function(), | S2[1][][S2]* function(), | int, string, | int[3] function( int[] arr, int[2] ...) pure @trusted, | string[3] function(string[] arr, string[2] ...) pure @trusted, | ); | // https://issues.dlang.org/show_bug.cgi?id=15168 | static struct T1 { string s; alias s this; } | static struct T2 { char[10] s; alias s this; } | static struct T3 { string[string] s; alias s this; } | alias Pass2 = Test!( | ubyte, ubyte, T1, T1, | ubyte, ubyte, T2, T2, | ubyte, ubyte, T3, T3, | ); |} |// https://issues.dlang.org/show_bug.cgi?id=17116 |version(mir_core_test) @safe unittest |{ | alias ConstDg = void delegate(float) const; | alias B = void delegate(int) const; | alias A = ReplaceType!(float, int, ConstDg); | static assert(is(B == A)); |} | // https://issues.dlang.org/show_bug.cgi?id=19696 |version(mir_core_test) @safe unittest |{ | static struct T(U) {} | static struct S { T!int t; alias t this; } | static assert(is(ReplaceType!(float, float, S) == S)); |} | // https://issues.dlang.org/show_bug.cgi?id=19697 |version(mir_core_test) @safe unittest |{ | class D(T) {} | class C : D!C {} | static assert(is(ReplaceType!(float, float, C))); |} |// https://issues.dlang.org/show_bug.cgi?id=16132 |version(mir_core_test) @safe unittest |{ | interface I(T) {} | class C : I!int {} | static assert(is(ReplaceType!(int, string, C) == C)); |} | |template basicElementType(T) |{ | import std.traits: isArray, ForeachType; | static if (isArray!T) | alias basicElementType = ForeachType!T; | else | alias basicElementType = T; |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/internal/meta.d has no code <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-internal-utility.lst |/// |module mir.internal.utility; | |private alias AliasSeq(T...) = T; | |/// |alias Iota(size_t j) = Iota!(0, j); | |/// |template Iota(size_t i, size_t j) |{ | static assert(i <= j, "Iota: i should be less than or equal to j"); | static if (i == j) | alias Iota = AliasSeq!(); | else | alias Iota = AliasSeq!(i, Iota!(i + 1, j)); |} | |/// |template realType(C) | if (__traits(isFloating, C) || isComplex!C) |{ | import std.traits: Unqual; | static if (isComplex!C) | alias realType = typeof(Unqual!C.init.re); | else | alias realType = Unqual!C; |} | |/// |template isComplex(C) |{ | static if (is(C == struct) || is(C == enum)) | { | static if (hasField!(C, "re") && hasField!(C, "im") && C.init.tupleof.length == 2) | enum isComplex = isFloatingPoint!(typeof(C.init.tupleof[0])); | else | static if (__traits(getAliasThis, C).length == 1) | enum isComplex = .isComplex!(typeof(__traits(getMember, C, __traits(getAliasThis, C)[0]))); | else | enum isComplex = false; | } | else | { | // for backward compatability with cfloat, cdouble and creal | enum isComplex = __traits(isFloating, C) && !isFloatingPoint!C && !is(C : __vector(F[N]), F, size_t N); | } |} | |/// |version(mir_core_test) |unittest |{ | static assert(!isComplex!double); | import mir.complex: Complex; | static assert(isComplex!(Complex!double)); | import std.complex: PhobosComplex = Complex; | static assert(isComplex!(PhobosComplex!double)); | | static struct C | { | Complex!double value; | alias value this; | } | | static assert(isComplex!C); |} | |/// |version(LDC) |version(mir_core_test) |unittest |{ | static assert(!isComplex!(__vector(double[2]))); |} | |/// |template isComplexOf(C, F) | if (isFloatingPoint!F) |{ | static if (isComplex!C) | enum isComplexOf = is(typeof(C.init.re) == F); | else | enum isComplexOf = false; |} | |/// |template isFloatingPoint(C) |{ | import std.traits: Unqual; | alias U = Unqual!C; | enum isFloatingPoint = is(U == double) || is(U == float) || is(U == real); |} | |// copy to reduce imports |enum bool hasField(T, string member) = __traits(compiles, (ref T aggregate) { return __traits(getMember, aggregate, member).offsetof; }); ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/internal/utility.d has no code <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-primitives.lst |/++ |Templates used to check primitives and |range primitives for arrays with multi-dimensional like API support. | |Note: |UTF strings behaves like common arrays in Mir. |`std.uni.byCodePoint` can be used to create a range of characters. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki, $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, and | $(HTTP jmdavisprog.com, Jonathan M Davis). Credit for some of the ideas | in building this module goes to | $(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi) |+/ |module mir.primitives; | |import mir.internal.utility; |import mir.math.common: optmath; |import std.traits; | |@optmath: | |/++ |Returns: `true` if `R` has a `length` member that returns an |integral type implicitly convertible to `size_t`. | |`R` does not have to be a range. |+/ |enum bool hasLength(R) = is(typeof( |(const R r, inout int = 0) |{ | size_t l = r.length; |})); | |/// |@safe version(mir_core_test) unittest |{ | static assert(hasLength!(char[])); | static assert(hasLength!(int[])); | static assert(hasLength!(inout(int)[])); | | struct B { size_t length() const { return 0; } } | struct C { @property size_t length() const { return 0; } } | static assert(hasLength!(B)); | static assert(hasLength!(C)); |} | |/++ |Returns: `true` if `R` has a `shape` member that returns an static array type of size_t[N]. |+/ |enum bool hasShape(R) = is(typeof( |(const R r, inout int = 0) |{ | auto l = r.shape; | alias F = typeof(l); | import std.traits; | static assert(isStaticArray!F); | static assert(is(ForeachType!F == size_t)); |})); | |/// |@safe version(mir_core_test) unittest |{ | static assert(hasShape!(char[])); | static assert(hasShape!(int[])); | static assert(hasShape!(inout(int)[])); | | struct B { size_t length() const { return 0; } } | struct C { @property size_t length() const { return 0; } } | static assert(hasShape!(B)); | static assert(hasShape!(C)); |} | |/// |auto shape(Range)(scope const auto ref Range range) @property | if (hasLength!Range || hasShape!Range) |{ | static if (__traits(hasMember, Range, "shape")) | { | return range.shape; | } | else | { | size_t[1] ret; | ret[0] = range.length; | return ret; | } |} | |/// |version(mir_core_test) unittest |{ | static assert([2, 2, 2].shape == [3]); |} | |/// |template DimensionCount(T) |{ | import mir.ndslice.slice: Slice, SliceKind; | /// Extracts dimension count from a $(LREF Slice). Alias for $(LREF isSlice). | static if(is(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind)) | enum size_t DimensionCount = N; | else | static if (hasShape!T) | enum size_t DimensionCount = typeof(T.init.shape).length; | else | enum size_t DimensionCount = 1; |} | |package(mir) bool anyEmptyShape(size_t N)(scope const auto ref size_t[N] shape) @property |{ | foreach (i; Iota!N) | if (shape[i] == 0) | return true; | return false; |} | |/// |bool anyEmpty(Range)(scope const auto ref Range range) @property | if (hasShape!Range || __traits(hasMember, Range, "anyEmpty")) |{ | static if (__traits(hasMember, Range, "anyEmpty")) | { | return range.anyEmpty; | } | else | static if (__traits(hasMember, Range, "shape")) | { | return anyEmptyShape(range.shape); | } | else | { | return range.empty; | } |} | |/// |size_t elementCount(Range)(scope const auto ref Range range) @property | if (hasShape!Range || __traits(hasMember, Range, "elementCount")) |{ | static if (__traits(hasMember, Range, "elementCount")) | { | return range.elementCount; | } | else | { | auto sh = range.shape; | size_t ret = sh[0]; | foreach(i; Iota!(1, sh.length)) | { | ret *= sh[i]; | } | return ret; | } |} | |deprecated("use elementCount instead") |alias elementsCount = elementCount; | | |/++ |Returns the element type of a struct with `.DeepElement` inner alias or a type of common array. |Returns `ForeachType` if struct does not have `.DeepElement` member. |+/ |template DeepElementType(S) | if (is(S == struct) || is(S == class) || is(S == interface)) |{ | static if (__traits(hasMember, S, "DeepElement")) | alias DeepElementType = S.DeepElement; | else | alias DeepElementType = ForeachType!S; |} | |/// ditto |alias DeepElementType(S : T[], T) = T; | |/+ ARRAY PRIMITIVES +/ |pragma(inline, true): | |/// |bool empty(size_t dim = 0, T)(scope const T[] ar) | if (!dim) |{ | return !ar.length; |} | |/// |version(mir_core_test) unittest |{ | assert((int[]).init.empty); | assert(![1].empty!0); // Slice-like API |} | |/// |ref inout(T) front(size_t dim = 0, T)(scope return inout(T)[] ar) | if (!dim && !is(Unqual!T[] == void[])) |{ | assert(ar.length, "Accessing front of an empty array."); | return ar[0]; |} | |/// |version(mir_core_test) unittest |{ | assert(*&[3, 4].front == 3); // access be ref | assert([3, 4].front!0 == 3); // Slice-like API |} | | |/// |ref inout(T) back(size_t dim = 0, T)(scope return inout(T)[] ar) | if (!dim && !is(Unqual!T[] == void[])) |{ | assert(ar.length, "Accessing back of an empty array."); | return ar[$ - 1]; |} | |/// |version(mir_core_test) unittest |{ | assert(*&[3, 4].back == 4); // access be ref | assert([3, 4].back!0 == 4); // Slice-like API |} | |/// |void popFront(size_t dim = 0, T)(scope ref inout(T)[] ar) | if (!dim && !is(Unqual!T[] == void[])) |{ | assert(ar.length, "Evaluating popFront() on an empty array."); | ar = ar[1 .. $]; |} | |/// |version(mir_core_test) unittest |{ | auto ar = [3, 4]; | ar.popFront; | assert(ar == [4]); | ar.popFront!0; // Slice-like API | assert(ar == []); |} | |/// |void popBack(size_t dim = 0, T)(scope ref inout(T)[] ar) | if (!dim && !is(Unqual!T[] == void[])) |{ | assert(ar.length, "Evaluating popBack() on an empty array."); | ar = ar[0 .. $ - 1]; |} | |/// |version(mir_core_test) unittest |{ | auto ar = [3, 4]; | ar.popBack; | assert(ar == [3]); | ar.popBack!0; // Slice-like API | assert(ar == []); |} | |/// |size_t popFrontN(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) | if (!dim && !is(Unqual!T[] == void[])) |{ | n = ar.length < n ? ar.length : n; | ar = ar[n .. $]; | return n; |} | |/// |version(mir_core_test) unittest |{ | auto ar = [3, 4]; | ar.popFrontN(1); | assert(ar == [4]); | ar.popFrontN!0(10); // Slice-like API | assert(ar == []); |} | |/// |size_t popBackN(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) | if (!dim && !is(Unqual!T[] == void[])) |{ | n = ar.length < n ? ar.length : n; | ar = ar[0 .. $ - n]; | return n; |} | |/// |version(mir_core_test) unittest |{ | auto ar = [3, 4]; | ar.popBackN(1); | assert(ar == [3]); | ar.popBackN!0(10); // Slice-like API | assert(ar == []); |} | |/// |void popFrontExactly(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) | if (!dim && !is(Unqual!T[] == void[])) |{ | assert(ar.length >= n, "Evaluating *.popFrontExactly(n) on an array with length less then n."); | ar = ar[n .. $]; |} | |/// |version(mir_core_test) unittest |{ | auto ar = [3, 4, 5]; | ar.popFrontExactly(2); | assert(ar == [5]); | ar.popFrontExactly!0(1); // Slice-like API | assert(ar == []); |} | |/// |void popBackExactly(size_t dim = 0, T)(scope ref inout(T)[] ar, size_t n) | if (!dim && !is(Unqual!T[] == void[])) |{ | assert(ar.length >= n, "Evaluating *.popBackExactly(n) on an array with length less then n."); | ar = ar[0 .. $ - n]; |} | |/// |version(mir_core_test) unittest |{ | auto ar = [3, 4, 5]; | ar.popBackExactly(2); | assert(ar == [3]); | ar.popBackExactly!0(1); // Slice-like API | assert(ar == []); |} | |/// |size_t length(size_t d : 0, T)(in T[] array) | if (d == 0) |{ | return array.length; |} | |/// |version(mir_core_test) unittest |{ | assert([1, 2].length!0 == 2); | assert([1, 2].elementCount == 2); |} | |/// |inout(T)[] save(T)(scope return inout(T)[] array) |{ | return array; |} | |/// |version(mir_core_test) unittest |{ | auto a = [1, 2]; | assert(a is a.save); |} | |/** |Returns `true` if `R` is an input range. An input range must |define the primitives `empty`, `popFront`, and `front`. The |following code should compile for any input range. |---- |R r; // can define a range object |if (r.empty) {} // can test for empty |r.popFront(); // can invoke popFront() |auto h = r.front; // can get the front of the range of non-void type |---- |The following are rules of input ranges are assumed to hold true in all |Phobos code. These rules are not checkable at compile-time, so not conforming |to these rules when writing ranges or range based code will result in |undefined behavior. |$(UL | $(LI `r.empty` returns `false` if and only if there is more data | available in the range.) | $(LI `r.empty` evaluated multiple times, without calling | `r.popFront`, or otherwise mutating the range object or the | underlying data, yields the same result for every evaluation.) | $(LI `r.front` returns the current element in the range. | It may return by value or by reference.) | $(LI `r.front` can be legally evaluated if and only if evaluating | `r.empty` has, or would have, equaled `false`.) | $(LI `r.front` evaluated multiple times, without calling | `r.popFront`, or otherwise mutating the range object or the | underlying data, yields the same result for every evaluation.) | $(LI `r.popFront` advances to the next element in the range.) | $(LI `r.popFront` can be called if and only if evaluating `r.empty` | has, or would have, equaled `false`.) |) |Also, note that Phobos code assumes that the primitives `r.front` and |`r.empty` are $(BIGOH 1) time complexity wise or "cheap" in terms of |running time. $(BIGOH) statements in the documentation of range functions |are made with this assumption. |Params: | R = type to be tested |Returns: | `true` if R is an input range, `false` if not | */ |enum bool isInputRange(R) = | is(typeof(R.init) == R) | && is(ReturnType!((R r) => r.empty) == bool) | && is(typeof((return ref R r) => r.front)) | && !is(ReturnType!((R r) => r.front) == void) | && is(typeof((R r) => r.popFront)); | |/** |Returns `true` if `R` is an infinite input range. An |infinite input range is an input range that has a statically-defined |enumerated member called `empty` that is always `false`, |for example: |---- |struct MyInfiniteRange |{ | enum bool empty = false; | ... |} |---- | */ | |template isInfinite(R) |{ | static if (isInputRange!R && __traits(compiles, { enum e = R.empty; })) | enum bool isInfinite = !R.empty; | else | enum bool isInfinite = false; |} | | |/** |The element type of `R`. `R` does not have to be a range. The |element type is determined as the type yielded by `r.front` for an |object `r` of type `R`. For example, `ElementType!(T[])` is |`T` if `T[]` isn't a narrow string; if it is, the element type is |`dchar`. If `R` doesn't have `front`, `ElementType!R` is |`void`. | */ |template ElementType(R) |{ | static if (is(typeof(R.init.front.init) T)) | alias ElementType = T; | else | alias ElementType = void; |} | |/++ |This is a best-effort implementation of `length` for any kind of |range. |If `hasLength!Range`, simply returns `range.length` without |checking `upTo` (when specified). |Otherwise, walks the range through its length and returns the number |of elements seen. Performes $(BIGOH n) evaluations of `range.empty` |and `range.popFront()`, where `n` is the effective length of $(D |range). |+/ |auto walkLength(Range)(Range range) |if (isIterable!Range && !isInfinite!Range) |{ | static if (hasLength!Range) | return range.length; | else | static if (__traits(hasMember, Range, "walkLength")) | return range.walkLength; | static if (isInputRange!Range) | { | size_t result; | for ( ; !range.empty ; range.popFront() ) | ++result; | return result; | } | else | { | size_t result; | foreach (ref e; range) | ++result; | return result; | } |} | |/++ |Returns `true` if `R` is an output range for elements of type |`E`. An output range is defined functionally as a range that |supports the operation $(D r.put(e)). | +/ |enum bool isOutputRange(R, E) = | is(typeof(R.init.put(E.init))); ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/primitives.d has no code <<<<<< EOF # path=..-..-..-.dub-packages-mir-core-1.3.6-mir-core-source-mir-reflection.lst |/++ |Base reflection utilities. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Macros: |+/ |module mir.reflection; | |import std.meta; |import std.traits: hasUDA, getUDAs, Parameters, isSomeFunction, FunctionAttribute, functionAttributes, EnumMembers, isAggregateType; |import mir.internal.meta: hasUDA; |import mir.functional: Tuple; | |deprecated |package alias isSomeStruct = isAggregateType; | |/++ |Attribute to force member serialization for static fields, compiletime `enum` members and non-property methods. |+/ |enum reflectSerde; | |/++ |Match types like `std.typeconst: Nullable`. |+/ |template isStdNullable(T) |{ | import std.traits : hasMember; | | T* aggregate; | | enum bool isStdNullable = | hasMember!(T, "isNull") && | hasMember!(T, "get") && | hasMember!(T, "nullify") && | is(typeof(__traits(getMember, aggregate, "isNull")()) == bool) && | !is(typeof(__traits(getMember, aggregate, "get")()) == void) && | is(typeof(__traits(getMember, aggregate, "nullify")()) == void); |} | |/// |version(mir_core_test) unittest |{ | import std.typecons; | static assert(isStdNullable!(Nullable!double)); |} | |/// |version(mir_core_test) unittest |{ | import mir.algebraic; | static assert(isStdNullable!(Nullable!double)); |} | |/// Attribute for deprecated API |struct reflectDeprecated(string target) |{ | /// | string msg; | | /++ | Number in an issue tracker. Not mandatory. | +/ | uint issueNumber = uint.max; | /++ | Should be kind of version number if one can be given. | Can be something else if that's not possible. Not mandatory. | +/ | string removalTime; |} | |/// Attribute to rename methods, types and functions |template ReflectName(string target) |{ | /// | struct ReflectName(Args...) | { | /// | string name; | } |} | |/// ditto |template reflectName(string target = null, Args...) |{ | /// | auto reflectName(string name) | { | alias TargetName = ReflectName!target; | return TargetName!Args(name); | } |} | |/// |version(mir_core_test) unittest |{ | enum E { A, B, C } | | struct S | { | @reflectName("A") | int a; | | @reflectName!"c++"("B") | int b; | | @reflectName!("C", double)("cd") | @reflectName!("C", float)("cf") | F c(F)() | { | return b; | } | } | | import std.traits: hasUDA; | | alias UniName = ReflectName!null; | alias CppName = ReflectName!"c++"; | alias CName = ReflectName!"C"; | | static assert(hasUDA!(S.a, UniName!()("A"))); | static assert(hasUDA!(S.b, CppName!()("B"))); | | // static assert(hasUDA!(S.c, ReflectName)); // doesn't work for now | static assert(hasUDA!(S.c, CName)); | static assert(hasUDA!(S.c, CName!double)); | static assert(hasUDA!(S.c, CName!float)); | static assert(hasUDA!(S.c, CName!double("cd"))); | static assert(hasUDA!(S.c, CName!float("cf"))); |} | |/// Attribute to rename methods, types and functions |template ReflectMeta(string target, string[] fields) |{ | /// | struct ReflectMeta(Args...) | { | /// | Args args; | static foreach(i, field; fields) | mixin(`alias ` ~ field ~` = args[` ~ i.stringof ~`];`); | } |} | |/// ditto |template reflectMeta(string target, string[] fields) |{ | /// | auto reflectMeta(Args...)(Args args) | if (args.length <= fields.length) | { | alias TargetMeta = ReflectMeta!(target, fields); | return TargetMeta!Args(args); | } |} | |/// |version(mir_core_test) unittest |{ | enum E { A, B, C } | | struct S | { | int a; | @reflectMeta!("c++", ["type"])(E.C) | int b; | } | | import std.traits: hasUDA; | | alias CppMeta = ReflectMeta!("c++", ["type"]); | | static assert(CppMeta!E(E.C).type == E.C); | static assert(!hasUDA!(S.a, CppMeta!E(E.A))); | static assert(hasUDA!(S.b, CppMeta!E(E.C))); |} | |/++ |Attribute to ignore a reflection target |+/ |template reflectIgnore(string target) |{ | enum reflectIgnore; |} | |/// |version(mir_core_test) unittest |{ | struct S | { | @reflectIgnore!"c++" | int a; | } | | import std.traits: hasUDA; | static assert(hasUDA!(S.a, reflectIgnore!"c++")); |} | |/// Attribute for documentation and unittests |struct ReflectDoc(string target) |{ | /// | string text; | /// | reflectUnittest!target test; | | /// | @safe pure nothrow @nogc | this(string text) | { | this.text = text; | } | | /// | @safe pure nothrow @nogc | this(string text, reflectUnittest!target test) | { | this.text = text; | this.test = test; | } | | /// | void toString(W)(scope ref W w) scope const | { | w.put(cast()this.text); | | if (this.test.text.length) | { | w.put("\nExample usage:\n"); | w.put(cast()this.test.text); | } | } | | /// | @safe pure nothrow | string toString()() scope const | { | return this.text ~ "\nExample usage:\n" ~ this.test.text; | } |} | |/++ |Attribute for documentation. |+/ |template reflectDoc(string target = null) |{ | /// | ReflectDoc!target reflectDoc(string text) | { | return ReflectDoc!target(text); | } | | /// | ReflectDoc!target reflectDoc(string text, reflectUnittest!target test) | { | return ReflectDoc!target(text, test); | } |} | |/++ |+/ |template reflectGetDocs(string target, alias symbol) |{ | static if (hasUDA!(symbol, ReflectDoc!target)) | static immutable(ReflectDoc!target[]) reflectGetDocs = [getUDAs!(symbol, ReflectDoc!target)]; | else | static immutable(ReflectDoc!target[]) reflectGetDocs = null; |} | |/// ditto |template reflectGetDocs(string target) |{ | /// | alias reflectGetDocs(alias symbol) = .reflectGetDocs!(target, symbol); | | /// ditto | immutable(ReflectDoc!target)[] reflectGetDocs(T)(T value) | @safe pure nothrow @nogc | if (is(T == enum)) | { | foreach (i, member; EnumMembers!T) | {{ | alias all = __traits(getAttributes, EnumMembers!T[i]); | }} | static immutable ReflectDoc!target[][EnumMembers!T.length] docs = [staticMap!(reflectGetDocs, EnumMembers!T)]; | import mir.enums: getEnumIndex; | uint index = void; | if (getEnumIndex(value, index)) | return docs[index]; | assert(0); | } |} | |/// |version(mir_core_test) unittest |{ | enum E | { | @reflectDoc("alpha") | a, | @reflectDoc!"C#"("Beta", reflectUnittest!"C#"("some c# code")) | @reflectDoc("beta") | b, | c, | } | | alias Doc = ReflectDoc!null; | alias CSDoc = ReflectDoc!"C#"; | | static assert(reflectGetDocs!null(E.a) == [Doc("alpha")]); | static assert(reflectGetDocs!"C#"(E.b) == [CSDoc("Beta", reflectUnittest!"C#"("some c# code"))]); | static assert(reflectGetDocs!null(E.b) == [Doc("beta")]); | static assert(reflectGetDocs!null(E.c) is null); | | struct S | { | @reflectDoc("alpha") | @reflectDoc!"C#"("Alpha") | int a; | } | | static assert(reflectGetDocs!(null, S.a) == [Doc("alpha")]); | static assert(reflectGetDocs!("C#", S.a) == [CSDoc("Alpha")]); | | import std.conv: to; | static assert(CSDoc("Beta", reflectUnittest!"C#"("some c# code")).to!string == "Beta\nExample usage:\nsome c# code"); |} | |/++ |Attribute for extern unit-test. |+/ |struct reflectUnittest(string target) |{ | /// | string text; | |@safe pure nothrow @nogc: | | this(string text) | { | this.text = text; | } | | this(const typeof(this) other) | { | this.text = other.text; | } |} | |/++ |+/ |template reflectGetUnittest(string target, alias symbol) |{ | static if (hasUDA!(symbol, reflectUnittest!target)) | enum string reflectGetUnittest = getUDA!(symbol, reflectUnittest).text; | else | enum string reflectGetUnittest = null; |} | |/// ditto |template reflectGetUnittest(string target) |{ | /// | alias reflectGetUnittest(alias symbol) = .reflectGetUnittest!(target, symbol); | | /// | string reflectGetUnittest(T)(T value) | if (is(T == enum)) | { | foreach (i, member; EnumMembers!T) | {{ | alias all = __traits(getAttributes, EnumMembers!T[i]); | }} | static immutable string[EnumMembers!T.length] tests = [staticMap!(reflectGetUnittest, EnumMembers!T)]; | import mir.enums: getEnumIndex; | uint index = void; | if (getEnumIndex(value, index)) | return tests[index]; | assert(0); | } |} | |/// |version(mir_core_test) unittest |{ | enum E | { | @reflectUnittest!"c++"("assert(E::a == 0);") | a, | @reflectUnittest!"c++"("assert(E::b == 1);") | b, | c, | } | | static assert(reflectGetUnittest!"c++"(E.a) == "assert(E::a == 0);"); | static assert(reflectGetUnittest!"c++"(E.b) == "assert(E::b == 1);"); | static assert(reflectGetUnittest!"c++"(E.c) is null); | | struct S | { | @reflectUnittest!"c++"("alpha") | int a; | } | | static assert(reflectGetUnittest!("c++", S.a) == "alpha"); |} | |/++ |Returns: single UDA. |+/ |template getUDA(alias symbol, alias attribute) |{ | private alias all = getUDAs!(symbol, attribute); | static if (all.length != 1) | static assert(0, "Exactly one " ~ attribute.stringof ~ " attribute is required, " ~ "got " ~ all.length.stringof); | else | { | static if (is(typeof(all[0]))) | enum getUDA = all[0]; | else | alias getUDA = all[0]; | } |} | |/++ |Checks if T has a field member. |+/ |enum bool isOriginalMember(T, string member) = __traits(identifier, __traits(getMember, T, member)) == member; | |/// |version(mir_core_test) unittest |{ | struct D | { | int a; | alias b = a; | } | | static assert(isOriginalMember!(D, "a")); | static assert(!isOriginalMember!(D, "b")); |} | |/++ |Checks if T has a field member. |+/ |enum bool hasField(T, string member) = __traits(compiles, (ref T aggregate) { return __traits(getMember, aggregate, member).offsetof; }); | |deprecated("use 'hasField' instead") alias isField = hasField; | |/// |version(mir_core_test) unittest |{ | struct D | { | int gi; | } | | struct I | { | int f; | | D base; | alias base this; | | void gi(double ) @property {} | void gi(uint ) @property {} | } | | struct S | { | int d; | | I i; | alias i this; | | int gm() @property {return 0;} | int gc() const @property {return 0;} | void gs(int) @property {} | } | | static assert(!hasField!(S, "gi")); | static assert(!hasField!(S, "gs")); | static assert(!hasField!(S, "gc")); | static assert(!hasField!(S, "gm")); | static assert(!hasField!(S, "gi")); | static assert(hasField!(S, "d")); | static assert(hasField!(S, "f")); | static assert(hasField!(S, "i")); |} | |/// with classes |version(mir_core_test) unittest |{ | class I | { | int f; | | void gi(double ) @property {} | void gi(uint ) @property {} | } | | class S | { | int d; | | I i; | alias i this; | | int gm() @property {return 0;} | int gc() const @property {return 0;} | void gs(int) @property {} | } | | static assert(!hasField!(S, "gi")); | static assert(!hasField!(S, "gs")); | static assert(!hasField!(S, "gc")); | static assert(!hasField!(S, "gm")); | static assert(hasField!(S, "d")); | static assert(hasField!(S, "f")); | static assert(hasField!(S, "i")); |} | |/++ |Checks if member is property. |+/ |template isProperty(T, string member) |{ | T* aggregate; | | static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member)))) | { | static if (isSomeFunction!(__traits(getMember, *aggregate, member))) | { | enum bool isProperty = isPropertyImpl!(__traits(getMember, *aggregate, member)); | } | else | { | enum bool isProperty = false; | } | } | else | enum bool isProperty = false; |} | |/// |version(mir_core_test) unittest |{ | struct D | { | int y; | | void gf(double ) @property {} | void gf(uint ) @property {} | } | | struct I | { | int f; | | D base; | alias base this; | | void gi(double ) @property {} | void gi(uint ) @property {} | } | | struct S | { | int d; | | I i; | alias i this; | | int gm() @property {return 0;} | int gc() const @property {return 0;} | void gs(int) @property {} | } | | static assert(isProperty!(S, "gf")); | static assert(isProperty!(S, "gi")); | static assert(isProperty!(S, "gs")); | static assert(isProperty!(S, "gc")); | static assert(isProperty!(S, "gm")); | static assert(!isProperty!(S, "d")); | static assert(!isProperty!(S, "f")); | static assert(!isProperty!(S, "y")); |} | |version(mir_core_test) unittest |{ | struct S | { | @reflectSerde enum s = "str"; | enum t = "str"; | } | static assert(hasUDA!(S, "s", reflectSerde)); | static assert(!hasUDA!(S, "t", reflectSerde)); |} | |/++ |Returns: list of the setter properties. | |Note: The implementation ignores templates. |+/ |template getSetters(T, string member) |{ | static if (__traits(hasMember, T, member)) | alias getSetters = Filter!(hasSingleArgument, Filter!(isPropertyImpl, __traits(getOverloads, T, member))); | else | alias getSetters = AliasSeq!(); |} | |/// |version(mir_core_test) unittest |{ | struct I | { | int f; | | void gi(double ) @property {} | void gi(uint ) @property {} | } | | struct S | { | int d; | | I i; | alias i this; | | int gm() @property {return 0;} | int gc() const @property {return 0;} | void gs(int) @property {} | } | | static assert(getSetters!(S, "gi").length == 2); | static assert(getSetters!(S, "gs").length == 1); | static assert(getSetters!(S, "gc").length == 0); | static assert(getSetters!(S, "gm").length == 0); | static assert(getSetters!(S, "d").length == 0); | static assert(getSetters!(S, "f").length == 0); |} | |/++ |Returns: list of the serializable (public getters) members. |+/ |enum string[] SerializableMembers(T) = [Filter!(ApplyLeft!(Serializable, T), SerdeFieldsAndProperties!T)]; | |/// |version(mir_core_test) unittest |{ | struct D | { | int y; | | int gf() @property {return 0;} | } | | struct I | { | int f; | | D base; | alias base this; | | int gi() @property {return 0;} | } | | struct S | { | int d; | | package int p; | | enum s = "str"; | @reflectSerde enum t = "str"; | | int gm() @property {return 0;} | | private int q; | | I i; | alias i this; | | int gc() const @property {return 0;} | void gs(int) @property {} | } | | static assert(SerializableMembers!S == ["y", "gf", "f", "gi", "d", "t", "gm", "gc"]); | static assert(SerializableMembers!(const S) == ["y", "f", "d", "t", "gc"]); |} | |/++ |Returns: list of the deserializable (public setters) members. |+/ |enum string[] DeserializableMembers(T) = [Filter!(ApplyLeft!(Deserializable, T), SerdeFieldsAndProperties!T)]; | |/// |version(mir_core_test) unittest |{ | struct I | { | int f; | void ga(int) @property {} | } | | struct S | { | int d; | package int p; | | int gm() @property {return 0;} | void gm(int) @property {} | | private int q; | | I i; | alias i this; | | | void gc(int, int) @property {} | void gc(int) @property {} | } | | S s; | // s.gc(0); | | static assert (DeserializableMembers!S == ["f", "ga", "d", "gm", "gc"]); | static assert (DeserializableMembers!(const S) == []); |} | |// This trait defines what members should be serialized - |// public members that are either readable and writable or getter properties |private template Serializable(T, string member) |{ | static if (!isPublic!(T, member)) | enum Serializable = false; | else | enum Serializable = isReadable!(T, member); // any readable is good |} | |private enum bool hasSingleArgument(alias fun) = Parameters!fun.length == 1; |private enum bool hasZeroArguments(alias fun) = Parameters!fun.length == 0; | |// This trait defines what members should be serialized - |// public members that are either readable and writable or setter properties |private template Deserializable(T, string member) |{ | static if (!isPublic!(T, member)) | enum Deserializable = false; | else | static if (isReadableAndWritable!(T, member)) | enum Deserializable = true; | else | static if (getSetters!(T, member).length == 1) | enum Deserializable = is(typeof((ref T val){ __traits(getMember, val, member) = Parameters!(getSetters!(T, member)[0])[0].init; })); | else | enum Deserializable = false; |} | |private enum SerdeFieldsAndProperties(T) = Reverse!(NoDuplicates!(Reverse!(SerdeFieldsAndPropertiesImpl!T))); | | |private static immutable exlMembers = [ | "opAssign", | "opCast", | "opCmp", | "opEquals", | "opPostMove", | "toHash", | "toString", | "trustedGet", | "deserializeFromAsdf", | "deserializeFromIon", |]; | |private auto filterMembers(string[] members) |{ 0000000| string[] ret; | 0000000| L: foreach(member; members) | { 0000000| if (member.length >= 2 && (member[0 .. 2] == "__" || member[$ - 2 .. $] == "__")) 0000000| continue; 0000000| foreach(exlMember; exlMembers) 0000000| if (exlMember == member) 0000000| continue L; 0000000| ret ~= member; | } 0000000| return ret; |}; | |private template allMembers(T) |{ | import std.meta: aliasSeqOf; | static if (isAggregateType!T) | alias allMembers = aliasSeqOf!(filterMembers([__traits(allMembers, T)])); | else | alias allMembers = AliasSeq!(); |} | |private template SerdeFieldsAndPropertiesImpl(T) |{ | alias isProperty = ApplyLeft!(.isProperty, T); | alias hasField = ApplyLeft!(.hasField, T); | alias isOriginalMember = ApplyLeft!(.isOriginalMember, T); | T* aggregate; | template hasReflectSerde(string member) | { | static if (is(typeof(__traits(getMember, *aggregate, member)))) | enum hasReflectSerde = hasUDA!(T, member, reflectSerde); | else | enum hasReflectSerde = false; | } | alias isMember = templateAnd!(templateOr!(hasField, isProperty, hasReflectSerde), isOriginalMember); | static if (!is(immutable T == Tuple!Types, Types...) && __traits(getAliasThis, T).length) | { | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | static if (isAggregateType!T) | alias baseMembers = SerdeFieldsAndPropertiesImpl!A; | else | alias baseMembers = AliasSeq!(); | alias members = Erase!(__traits(getAliasThis, T)[0], __traits(allMembers, T)); | alias SerdeFieldsAndPropertiesImpl = AliasSeq!(baseMembers, Filter!(isMember, members)); | } | else | { | import mir.algebraic; | static if (isVariant!T) | alias members = staticMap!(allMembers, T.AllowedTypes); | else | alias members = allMembers!T; | alias SerdeFieldsAndPropertiesImpl = AliasSeq!(Filter!(isMember, members)); | } |} | |// check if the member is readable |private template isReadable(T, string member) |{ | T* aggregate; | enum bool isReadable = __traits(compiles, { static fun(T)(auto ref T t) {} fun(__traits(getMember, *aggregate, member)); }); |} | |// check if the member is readable/writeble? |private template isReadableAndWritable(T, string member) |{ | T* aggregate; | enum bool isReadableAndWritable = __traits(compiles, __traits(getMember, *aggregate, member) = __traits(getMember, *aggregate, member)); |} | |package template isPublic(T, string member) |{ | T* aggregate; | enum bool isPublic = !__traits(getProtection, __traits(getMember, *aggregate, member)).privateOrPackage; |} | |// check if the member is property |private template isSetter(T, string member) |{ | T* aggregate; | static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member)))) | { | static if (isSomeFunction!(__traits(getMember, *aggregate, member))) | { | enum bool isSetter = getSetters!(T, member).length > 0;; | } | else | { | enum bool isSetter = false; | } | } | else | enum bool isSetter = false; |} | |private template isGetter(T, string member) |{ | T* aggregate; | static if (__traits(compiles, isSomeFunction!(__traits(getMember, *aggregate, member)))) | { | static if (isSomeFunction!(__traits(getMember, *aggregate, member))) | { | enum bool isGetter = Filter!(hasZeroArguments, Filter!(isPropertyImpl, __traits(getOverloads, T, member))).length == 1; | } | else | { | enum bool isGetter = false; | } | } | else | enum bool isGetter = false; |} | |private enum bool isPropertyImpl(alias member) = (functionAttributes!member & FunctionAttribute.property) != 0; | |private bool privateOrPackage()(string protection) |{ | return protection == "private" || protection == "package"; |} ../../../.dub/packages/mir-core-1.3.6/mir-core/source/mir/reflection.d is 0% covered <<<<<< EOF # path=.dub-code-mir-algorithm-test-default-unittest-cov-linux.posix-x86_64-ldc_v1.30.0-F747A2D2B2FAE6DA964CC7BEBF13B66D_dub_test_root.lst |module dub_test_root; |import std.typetuple; |static import mir.algebraic_alias.ion; |static import mir.algebraic_alias.json; |static import mir.algebraic_alias.transform; |static import mir.algorithm.iteration; |static import mir.algorithm.setops; |static import mir.annotated; |static import mir.appender; |static import mir.array.allocation; |static import mir.base64; |static import mir.bignum.decimal; |static import mir.bignum.fixed; |static import mir.bignum.fp; |static import mir.bignum.integer; |static import mir.bignum.internal.dec2float; |static import mir.bignum.internal.dec2float_table; |static import mir.bignum.internal.kernel; |static import mir.bignum.internal.parse; |static import mir.bignum.internal.phobos_kernel; |static import mir.bignum.internal.ryu.generic_128; |static import mir.bignum.internal.ryu.table; |static import mir.bignum.low_level_view; |static import mir.container.binaryheap; |static import mir.cpp_export.numeric; |static import mir.date; |static import mir.ediff; |static import mir.format; |static import mir.format_impl; |static import mir.graph.tarjan; |static import mir.interpolate.constant; |static import mir.interpolate.extrapolate; |static import mir.interpolate.generic; |static import mir.interpolate.linear; |static import mir.interpolate.mod; |static import mir.interpolate.polynomial; |static import mir.interpolate.spline; |static import mir.interpolate.utility; |static import mir.lob; |static import mir.math.func.expdigamma; |static import mir.math.func.hermite; |static import mir.math.func.normal; |static import mir.math.numeric; |static import mir.math.stat; |static import mir.math.sum; |static import mir.ndslice.allocation; |static import mir.ndslice.chunks; |static import mir.ndslice.concatenation; |static import mir.ndslice.connect.cpython; |static import mir.ndslice.dynamic; |static import mir.ndslice.field; |static import mir.ndslice.filling; |static import mir.ndslice.fuse; |static import mir.ndslice.internal; |static import mir.ndslice.iterator; |static import mir.ndslice.mutation; |static import mir.ndslice.ndfield; |static import mir.ndslice.slice; |static import mir.ndslice.sorting; |static import mir.ndslice.topology; |static import mir.ndslice.traits; |static import mir.numeric; |static import mir.parse; |static import mir.polynomial; |static import mir.range; |static import mir.rc.array; |static import mir.rc.context; |static import mir.rc.ptr; |static import mir.rc.slim_ptr; |static import mir.serde; |static import mir.series; |static import mir.small_array; |static import mir.small_string; |static import mir.stdio; |static import mir.string; |static import mir.string_map; |static import mir.test; |static import mir.timestamp; |static import mir.type_info; |alias allModules = TypeTuple!(mir.algebraic_alias.ion, mir.algebraic_alias.json, mir.algebraic_alias.transform, mir.algorithm.iteration, mir.algorithm.setops, mir.annotated, mir.appender, mir.array.allocation, mir.base64, mir.bignum.decimal, mir.bignum.fixed, mir.bignum.fp, mir.bignum.integer, mir.bignum.internal.dec2float, mir.bignum.internal.dec2float_table, mir.bignum.internal.kernel, mir.bignum.internal.parse, mir.bignum.internal.phobos_kernel, mir.bignum.internal.ryu.generic_128, mir.bignum.internal.ryu.table, mir.bignum.low_level_view, mir.container.binaryheap, mir.cpp_export.numeric, mir.date, mir.ediff, mir.format, mir.format_impl, mir.graph.tarjan, mir.interpolate.constant, mir.interpolate.extrapolate, mir.interpolate.generic, mir.interpolate.linear, mir.interpolate.mod, mir.interpolate.polynomial, mir.interpolate.spline, mir.interpolate.utility, mir.lob, mir.math.func.expdigamma, mir.math.func.hermite, mir.math.func.normal, mir.math.numeric, mir.math.stat, mir.math.sum, mir.ndslice.allocation, mir.ndslice.chunks, mir.ndslice.concatenation, mir.ndslice.connect.cpython, mir.ndslice.dynamic, mir.ndslice.field, mir.ndslice.filling, mir.ndslice.fuse, mir.ndslice.internal, mir.ndslice.iterator, mir.ndslice.mutation, mir.ndslice.ndfield, mir.ndslice.slice, mir.ndslice.sorting, mir.ndslice.topology, mir.ndslice.traits, mir.numeric, mir.parse, mir.polynomial, mir.range, mir.rc.array, mir.rc.context, mir.rc.ptr, mir.rc.slim_ptr, mir.serde, mir.series, mir.small_array, mir.small_string, mir.stdio, mir.string, mir.string_map, mir.test, mir.timestamp, mir.type_info); | | import std.stdio; | import core.runtime; | 0000000| void main() { writeln("All unit tests have been run successfully."); } | shared static this() { | version (Have_tested) { | import tested; | import core.runtime; | import std.exception; | Runtime.moduleUnitTester = () => true; | enforce(runUnitTests!allModules(new ConsoleTestResultWriter), "Unit tests failed."); | } | } | .dub/code/mir-algorithm-test-default-unittest-cov-linux.posix-x86_64-ldc_v1.30.0-F747A2D2B2FAE6DA964CC7BEBF13B66D_dub_test_root.d is 0% covered <<<<<< EOF # path=source-mir-algebraic_alias-ion.lst |/++ |$(H1 Mutable Ion value) | |This module contains a single alias definition and doesn't provide Ion serialization API. | |See_also: Ion library $(MIR_PACKAGE mir-ion) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Macros: |+/ |module mir.algebraic_alias.ion; | |import mir.algebraic: Algebraic, This; |/// |public import mir.annotated: Annotated; |/// |public import mir.lob: Clob, Blob; |/// |public import mir.string_map: StringMap; |/// |public import mir.timestamp: Timestamp; | | |/++ |Definition union for $(LREF IonAlgebraic). |+/ |union Ion_ |{ | /// | typeof(null) null_; | /// | bool boolean; | /// | long integer; | /// | double float_; | /// | immutable(char)[] string; | /// | Blob blob; | /// | Clob clob; | /// | Timestamp timestamp; | /// Self alias in array. | This[] array; | /// Self alias in $(MREF mir,string_map). | StringMap!This object; | /// Self alias in $(MREF mir,annotated). | Annotated!This annotated; |} | |/++ |Ion tagged algebraic alias. | |The example below shows only the basic features. Advanced API to work with algebraic types can be found at $(GMREF mir-core, mir,algebraic). |See also $(MREF mir,string_map) - ordered string-value associative array. |+/ |alias IonAlgebraic = Algebraic!Ion_; | |/// |version(mir_test) |unittest |{ | import mir.ndslice.topology: map; | import mir.array.allocation: array; | 1| IonAlgebraic value; | 1| StringMap!IonAlgebraic object; | | // Default 1| assert(value.isNull); 1| assert(value.kind == IonAlgebraic.Kind.null_); | | // Boolean 1| value = object["bool"] = true; 1| assert(!value.isNull); 1| assert(value == true); 1| assert(value.kind == IonAlgebraic.Kind.boolean); | // access 1| assert(value.boolean == true); 1| assert(value.get!bool == true); 1| assert(value.get!"boolean" == true); 1| assert(value.get!(IonAlgebraic.Kind.boolean) == true); | // nothrow access 1| assert(value.trustedGet!bool == true); 1| assert(value.trustedGet!"boolean" == true); 1| assert(value.trustedGet!(IonAlgebraic.Kind.boolean) == true); | // checks 1| assert(!value._is!string); 1| assert(value._is!bool); 1| assert(value._is!"boolean"); 1| assert(value._is!(IonAlgebraic.Kind.boolean)); | | // Null 1| value = object["null"] = null; 1| assert(value.isNull); 1| assert(value == null); 1| assert(value.kind == IonAlgebraic.Kind.null_); | // access 1| assert(value.null_ == null); 1| assert(value.get!(typeof(null)) == null); 1| assert(value.get!(IonAlgebraic.Kind.null_) == null); | | // String 1| value = object["string"] = "s"; 1| assert(value.kind == IonAlgebraic.Kind.string); 1| assert(value == "s"); | // access | // Yep, `string` here is an alias to `get!(immutable(char)[])` method 1| assert(value.string == "s"); | // `string` here is an alias of type `immutable(char)[]` 1| assert(value.get!string == "s"); 1| assert(value.get!"string" == "s"); | // finally, `string` here is an enum meber 1| assert(value.get!(IonAlgebraic.Kind.string) == "s"); | | // Integer 1| value = object["integer"] = 4; 1| assert(value.kind == IonAlgebraic.Kind.integer); 1| assert(value == 4); 1| assert(value != 4.0); 1| assert(value.integer == 4); | | // Float 1| value = object["float"] = 3.0; 1| assert(value.kind == IonAlgebraic.Kind.float_); 1| assert(value != 3); 1| assert(value == 3.0); 1| assert(value.float_ == 3.0); | | // Array 1| IonAlgebraic[] arr = [0, 1, 2, 3, 4].map!IonAlgebraic.array; | 1| value = object["array"] = arr; 1| assert(value.kind == IonAlgebraic.Kind.array); 1| assert(value == arr); 1| assert(value.array[3] == 3); | | // Object 1| assert(object.keys == ["bool", "null", "string", "integer", "float", "array"]); 1| object.values[0] = "false"; 1| assert(object["bool"] == "false"); // it is a string now 1| object.remove("bool"); // remove the member | 1| value = object["array"] = object; 1| assert(value.kind == IonAlgebraic.Kind.object); 1| assert(value.object.keys is object.keys); | 1| IonAlgebraic[string] aa = object.toAA; 1| object = aa.StringMap!IonAlgebraic; | 1| IonAlgebraic fromAA = ["a" : IonAlgebraic(3), "b" : IonAlgebraic("b")]; 1| assert(fromAA.object["a"] == 3); 1| assert(fromAA.object["b"] == "b"); | 1| auto annotated = Annotated!IonAlgebraic(["birthday"], Timestamp("2001-01-01")); 1| value = annotated; 1| assert(value == annotated); 1| value = annotated.IonAlgebraic; 1| assert(value == annotated); |} source/mir/algebraic_alias/ion.d is 100% covered <<<<<< EOF # path=source-mir-algebraic_alias-json.lst |/++ |$(H1 Mutable JSON value) | |This module contains a single alias definition and doesn't provide JSON serialization API. | |See_also: JSON libraries $(MIR_PACKAGE mir-ion) and $(MIR_PACKAGE asdf); | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Macros: |+/ |module mir.algebraic_alias.json; | |import mir.algebraic: Algebraic, This; |/// |public import mir.string_map: StringMap; | |/++ |Definition union for $(LREF JsonAlgebraic). |+/ |union Json_ |{ | /// | typeof(null) null_; | /// | bool boolean; | /// | long integer; | /// | double float_; | /// | immutable(char)[] string; | /// Self alias in array. | This[] array; | /// Self alias in $(MREF mir,string_map). | StringMap!This object; |} | |/++ |JSON tagged algebraic alias. | |The example below shows only the basic features. Advanced API to work with algebraic types can be found at $(GMREF mir-core, mir,algebraic). |See also $(MREF mir,string_map) - ordered string-value associative array. |+/ |alias JsonAlgebraic = Algebraic!Json_; | |/// |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.topology: map; | import mir.array.allocation: array; | 1| JsonAlgebraic value; | 1| StringMap!JsonAlgebraic object; | | // Default 1| assert(value.isNull); 1| assert(value.kind == JsonAlgebraic.Kind.null_); | | // Boolean 1| value = object["bool"] = true; 1| assert(!value.isNull); 1| assert(value == true); 1| assert(value.kind == JsonAlgebraic.Kind.boolean); | // access 1| assert(value.boolean == true); 1| assert(value.get!bool == true); 1| assert(value.get!"boolean" == true); 1| assert(value.get!(JsonAlgebraic.Kind.boolean) == true); | // nothrow access 1| assert(value.trustedGet!bool == true); 1| assert(value.trustedGet!"boolean" == true); 1| assert(value.trustedGet!(JsonAlgebraic.Kind.boolean) == true); | // checks 1| assert(!value._is!string); 1| assert(value._is!bool); 1| assert(value._is!"boolean"); 1| assert(value._is!(JsonAlgebraic.Kind.boolean)); | | // Null 1| value = object["null"] = null; 1| assert(value.isNull); 1| assert(value == null); 1| assert(value.kind == JsonAlgebraic.Kind.null_); | // access 1| assert(value.null_ == null); 1| assert(value.get!(typeof(null)) == null); 1| assert(value.get!(JsonAlgebraic.Kind.null_) == null); | | // String 1| value = object["string"] = "s"; 1| assert(value.kind == JsonAlgebraic.Kind.string); 1| assert(value == "s"); | // access | // Yep, `string` here is an alias to `get!(immutable(char)[])` method 1| assert(value.string == "s"); | // `string` here is an alias of type `immutable(char)[]` 1| assert(value.get!string == "s"); 1| assert(value.get!"string" == "s"); | // finally, `string` here is an enum meber 1| assert(value.get!(JsonAlgebraic.Kind.string) == "s"); | | // Integer 1| value = object["integer"] = 4; 1| assert(value.kind == JsonAlgebraic.Kind.integer); 1| assert(value == 4); 1| assert(value != 4.0); 1| assert(value.integer == 4); | | // Float 1| value = object["float"] = 3.0; 1| assert(value.kind == JsonAlgebraic.Kind.float_); 1| assert(value != 3); 1| assert(value == 3.0); 1| assert(value.float_ == 3.0); | | // Array 1| JsonAlgebraic[] arr = [0, 1, 2, 3, 4].map!JsonAlgebraic.array; | 1| value = object["array"] = arr; 1| assert(value.kind == JsonAlgebraic.Kind.array); 1| assert(value == arr); 1| assert(value.array[3] == 3); | | // Object 1| assert(object.keys == ["bool", "null", "string", "integer", "float", "array"]); 1| object.values[0] = "false"; 1| assert(object["bool"] == "false"); // it is a string now 1| object.remove("bool"); // remove the member | 1| value = object["array"] = object; 1| assert(value.kind == JsonAlgebraic.Kind.object); 1| assert(value.object.keys is object.keys); | 1| JsonAlgebraic[string] aa = object.toAA; 1| object = aa.StringMap!JsonAlgebraic; | 1| JsonAlgebraic fromAA = ["a" : JsonAlgebraic(3), "b" : JsonAlgebraic("b")]; 1| assert(fromAA.object["a"] == 3); 1| assert(fromAA.object["b"] == "b"); |} source/mir/algebraic_alias/json.d is 100% covered <<<<<< EOF # path=source-mir-algebraic_alias-transform.lst |/++ |$(H1 Transformation utilities for JSON-like values) | |See_also: JSON libraries $(MIR_PACKAGE mir-ion) and $(MIR_PACKAGE asdf); | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Macros: |+/ |module mir.algebraic_alias.transform; | |import mir.algebraic: Algebraic, tryVisit, visit, optionalVisit; |import mir.functional: naryFun; |private alias AliasSeq(T...) = T; | |/++ |Transforms algebraics leafs recursively in place, |ensuring that all leaf types are handled by the visiting functions. | |Recursion is done for `This[]`, `StringMap!This`, `This[string]`, and `Annotated!This` types. |+/ |alias transformLeafs(visitors...) = transformLeafsImpl!(visit, naryFun!visitors); | |/// |version(mir_test) |unittest |{ | import mir.format: text; | import mir.algebraic_alias.json; 1| JsonAlgebraic value = ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, null.JsonAlgebraic].JsonAlgebraic]; | | // converts all leavs to a text form 1| value.transformLeafs!text; 1| assert(value == ["key" : ["str".JsonAlgebraic, "2.32".JsonAlgebraic, "null".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); | 1| value = ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, true.JsonAlgebraic].JsonAlgebraic].JsonAlgebraic; | | /// converts only bool values 1| value.transformLeafs!( 1| (bool b) => b.text, 2| v => v, // other values are copied as is | ); | 1| assert(value == ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, "true".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); |} | |/++ |Behaves as $(LREF transformLeafs) but doesn't enforce at compile time that all types can be handled by the visiting functions. | |Throws: Exception if `naryFun!visitors` can't be called with provided arguments |+/ |alias tryTransformLeafs(visitors...) = transformLeafsImpl!(tryVisit, naryFun!visitors); | |/// |version(mir_test) |unittest |{ | import mir.format: text; | import mir.algebraic_alias.json; 1| JsonAlgebraic value = ["key" : [true.JsonAlgebraic, 100.JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic]; | | // converts long and double numbers to a text form, bool values is converted to `long` 4| value.tryTransformLeafs!((double v) => v.text, (long v) => v.text); 1| assert(value == ["key" : ["1".JsonAlgebraic, "100".JsonAlgebraic, "2.32".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); |} | |/++ |Behaves as $(LREF transformLeafs) but doesn't enforce at compile time that all types can be handled by the visiting functions. | |The function ignores leafs that can't be handled by the visiting functions. |+/ |alias optionalTransformLeafs(visitors...) = transformLeafsImpl!(optionalVisit, naryFun!visitors); | |/// |version(mir_test) |unittest |{ | import mir.format: text; | import mir.algebraic_alias.json; 1| JsonAlgebraic value = ["key" : [null.JsonAlgebraic, true.JsonAlgebraic, 100.JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic]; | | // converts long numbers to a text form, ignores other types 1| value.optionalTransformLeafs!( 1| (long v) => v.text, 1| (bool b) => b, // needs special overload for bool to get rid of implicit converion to long/double | ); 1| assert(value == ["key" : [null.JsonAlgebraic, true.JsonAlgebraic, "100".JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); |} | |/// |template transformLeafsImpl(alias handler, alias visitor) |{ | /// | ref Algebraic!Types transformLeafsImpl(Types...)(ref return Algebraic!Types value) | { | import core.lifetime: move; | import mir.algebraic: visit; | import mir.annotated: Annotated; | import mir.string_map: StringMap; | alias T = Algebraic!Types; | static if (is(T.AllowedTypes[0] == typeof(null))) | { | enum nullCompiles = __traits(compiles, value = visitor(null)); | static if (nullCompiles || __traits(isSame, handler, visit)) | { | alias nullHandler = (typeof(null)) { | static if (nullCompiles) 1| value = visitor(null); | else | assert(0, "Null " ~ T.stringof); | }; | } | else | { | alias nullHandler = AliasSeq!(); | } | } | else | { | alias nullHandler = AliasSeq!(); | } 21| handler!( | (T[] v) { 51| foreach (ref e; v) 13| transformLeafsImpl(e); | }, | (StringMap!T v) { 24| foreach (ref e; v.values) 4| transformLeafsImpl(e); | }, | (T[string] v) { | foreach (key, ref e; v) | transformLeafsImpl(e); | }, | (Annotated!T v) { | transformLeafsImpl(v.value); | }, | nullHandler, | (ref v) { // auto for typeof(null) support | static if (__traits(compiles, value = visitor(move(v)))) 10| value = visitor(move(v)); | else | value = visitor(v); | } | )(value); 21| return value; | } | | /// ditto | Algebraic!Types transformLeafsImpl(Types...)(Algebraic!Types value) | { | import core.lifetime: move; | transformLeafsImpl(value); | return move(value); | } |} source/mir/algebraic_alias/transform.d is 100% covered <<<<<< EOF # path=source-mir-algorithm-iteration.lst |// Written in the D programming language. |/** |This module contains generic _iteration algorithms. |$(SCRIPT inhibitQuickIndex = 1;) | |$(BOOKTABLE $(H2 Function), |$(TR $(TH Function Name) $(TH Description)) |$(T2 all, Checks if all elements satisfy to a predicate.) |$(T2 any, Checks if at least one element satisfy to a predicate.) |$(T2 cmp, Compares two slices.) |$(T2 count, Counts elements in a slices according to a predicate.) |$(T2 each, Iterates elements.) |$(T2 eachLower, Iterates lower triangle of matrix.) |$(T2 eachOnBorder, Iterates elementes on tensors borders and corners.) |$(T2 eachUploPair, Iterates upper and lower pairs of elements in square matrix.) |$(T2 eachUpper, Iterates upper triangle of matrix.) |$(T2 equal, Compares two slices for equality.) |$(T2 filter, Filters elements in a range or an ndslice.) |$(T2 find, Finds backward index.) |$(T2 findIndex, Finds index.) |$(T2 fold, Accumulates all elements (different parameter order than `reduce`).) |$(T2 isSymmetric, Checks if the matrix is symmetric.) |$(T2 maxIndex, Finds index of the maximum.) |$(T2 maxPos, Finds backward index of the maximum.) |$(T2 minIndex, Finds index of the minimum.) |$(T2 minmaxIndex, Finds indices of the minimum and the maximum.) |$(T2 minmaxPos, Finds backward indices of the minimum and the maximum.) |$(T2 minPos, Finds backward index of the minimum.) |$(T2 nBitsToCount, Сount bits until set bit count is reached.) |$(T2 reduce, Accumulates all elements.) |$(T2 Chequer, Chequer color selector to work with $(LREF each) .) |$(T2 uniq, Iterates over the unique elements in a range or an ndslice, which is assumed sorted.) |) | |Transform function is represented by $(NDSLICEREF topology, map). | |All operators are suitable to change slices using `ref` argument qualification in a function declaration. |Note, that string lambdas in Mir are `auto ref` functions. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki, John Michael Hall, Andrei Alexandrescu (original Phobos code) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments | |Authors: , Ilia Ki (Mir & BetterC rework). |Source: $(PHOBOSSRC std/algorithm/_iteration.d) |Macros: | NDSLICEREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) | T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) | */ |module mir.algorithm.iteration; | |import mir.functional: naryFun; |import mir.internal.utility; |import mir.math.common: optmath; |import mir.ndslice.field: BitField; |import mir.ndslice.internal; |import mir.ndslice.iterator: FieldIterator, RetroIterator; |import mir.ndslice.slice; |import mir.primitives; |import mir.qualifier; |import std.meta; |import std.traits; | |/++ |Chequer color selector to work with $(LREF each) |+/ |enum Chequer : bool |{ | /// Main diagonal color | black, | /// First sub-diagonal color | red, |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; 1| auto s = [5, 4].slice!int; | 1| Chequer.black.each!"a = 1"(s); 1| assert(s == [ | [1, 0, 1, 0], | [0, 1, 0, 1], | [1, 0, 1, 0], | [0, 1, 0, 1], | [1, 0, 1, 0], | ]); | 11| Chequer.red.each!((ref b) => b = 2)(s); 1| assert(s == [ | [1, 2, 1, 2], | [2, 1, 2, 1], | [1, 2, 1, 2], | [2, 1, 2, 1], | [1, 2, 1, 2], | ]); | |} | |@optmath: | |/+ |Bitslice representation for accelerated bitwise algorithm. |1-dimensional contiguousitslice can be split into three chunks: head bits, body chunks, and tail bits. | |Bitslice can have head bits because it has slicing and the zero bit may not be aligned to the zero of a body chunk. |+/ |private struct BitSliceAccelerator(Field, I = typeof(Field.init[size_t.init])) | if (__traits(isUnsigned, I)) |{ | import mir.bitop; | import mir.qualifier: lightConst; | import mir.ndslice.traits: isIterator; | import mir.ndslice.iterator: FieldIterator; | import mir.ndslice.field: BitField; | | /// | alias U = typeof(I + 1u); | /// body bits chunks | static if (isIterator!Field) | Slice!Field bodyChunks; | else | Slice!(FieldIterator!Field) bodyChunks; | /// head length | int headLength; | /// tail length | int tailLength; | |@optmath: | 9| this(Slice!(FieldIterator!(BitField!(Field, I))) slice) | { | enum mask = bitShiftMask!I; | enum shift = bitElemShift!I; 9| size_t length = slice.length; 9| size_t index = slice._iterator._index; 9| if (auto hlen = index & mask) | { 4| auto l = I.sizeof * 8 - hlen; 4| if (l > length) | { | // central problem 1| headLength = -cast(int) length; 1| tailLength = cast(int) hlen; 1| goto F; | } | else | { 3| headLength = cast(uint) l; 3| length -= l; 3| index += l; | } | } 8| tailLength = cast(int) (length & mask); | F: 9| length >>= shift; 9| index >>= shift; 9| bodyChunks._lengths[0] = length; | static if (isIterator!Field) | { 9| bodyChunks._iterator = slice._iterator._field._field; 9| bodyChunks._iterator += index; | } | else | { | bodyChunks._iterator._index = index; | bodyChunks._iterator._field = slice._iterator._field._field; | } | } | |scope const: | | bool isCentralProblem() | { 24| return headLength < 0; | } | | U centralBits() | { 1| assert(isCentralProblem); 1| return *bodyChunks._iterator.lightConst >>> tailLength; | } | | uint centralLength() | { 1| assert(isCentralProblem); 1| return -headLength; | } | | /// head bits (last `headLength` bits are valid). | U headBits() | { 4| assert(!isCentralProblem); 4| if (headLength == 0) 1| return U.init; | static if (isIterator!Field) 3| return bodyChunks._iterator.lightConst[-1]; | else | return bodyChunks._iterator._field.lightConst[bodyChunks._iterator._index - 1]; | } | | /// tail bits (first `tailLength` bits are valid). | U tailBits() | { 9| assert(!isCentralProblem); 9| if (tailLength == 0) 4| return U.init; | static if (isIterator!Field) 5| return bodyChunks._iterator.lightConst[bodyChunks.length]; | else | return bodyChunks._iterator._field.lightConst[bodyChunks._iterator._index + bodyChunks.length]; | } | | U negCentralMask() | { 1| return U.max << centralLength; | } | | U negHeadMask() | { 0000000| return U.max << headLength; | } | | U negTailMask() | { 2| return U.max << tailLength; | } | | U negCentralMaskS() | { 0000000| return U.max >> centralLength; | } | | U negHeadMaskS() | { 0000000| return U.max >> headLength; | } | | U negTailMaskS() | { 0000000| return U.max >> tailLength; | } | | U centralBitsWithRemainingZeros() | { 1| return centralBits & ~negCentralMask; | } | | U centralBitsWithRemainingZerosS() | { 0000000| return centralBits << (U.sizeof * 8 - centralLength); | } | | U headBitsWithRemainingZeros() | { 4| return headBits >>> (I.sizeof * 8 - headLength); | } | | U headBitsWithRemainingZerosS() | { | static if (U.sizeof > I.sizeof) 0000000| return (headBits << (U.sizeof - I.sizeof) * 8) & ~negTailMaskS; | else 0000000| return headBits & ~negTailMaskS; | } | | U tailBitsWithRemainingZeros() | { 2| return tailBits & ~negTailMask; | } | | U tailBitsWithRemainingZerosS() | { 1| return tailBits << (U.sizeof * 8 - tailLength); | } | | U centralBitsWithRemainingOnes() | { 0000000| return centralBits | negCentralMask; | } | | U centralBitsWithRemainingOnesS() | { 0000000| return centralBitsWithRemainingZerosS | negCentralMaskS; | } | | U headBitsWithRemainingOnes() | { 0000000| return headBitsWithRemainingZeros | negHeadMask; | } | | U headBitsWithRemainingOnesS() | { 0000000| return headBitsWithRemainingZerosS | negHeadMaskS; | } | | U tailBitsWithRemainingOnes() | { 0000000| return tailBits | negTailMask; | } | | U tailBitsWithRemainingOnesS() | { 0000000| return tailBitsWithRemainingZerosS | negTailMaskS; | } | | size_t ctpop() | { | import mir.bitop: ctpop; 7| if (isCentralProblem) 1| return centralBitsWithRemainingZeros.ctpop; 6| size_t ret; 6| if (headLength) 3| ret = cast(size_t) headBitsWithRemainingZeros.ctpop; 6| if (bodyChunks.length) | { 6| auto bc = bodyChunks.lightConst; | do | { 31| ret += cast(size_t) bc.front.ctpop; 31| bc.popFront; | } 31| while(bc.length); | } 6| if (tailBits) 2| ret += cast(size_t) tailBitsWithRemainingZeros.ctpop; 6| return ret; | } | | bool any() | { 0000000| if (isCentralProblem) 0000000| return centralBitsWithRemainingZeros != 0; 0000000| if (headBitsWithRemainingZeros != 0) 0000000| return true; 0000000| if (bodyChunks.length) | { 0000000| auto bc = bodyChunks.lightConst; | do | { 0000000| if (bc.front != 0) 0000000| return true; 0000000| bc.popFront; | } 0000000| while(bc.length); | } 0000000| if (tailBitsWithRemainingZeros != 0) 0000000| return true; 0000000| return false; | } | | bool all() | { 0000000| if (isCentralProblem) 0000000| return centralBitsWithRemainingOnes != U.max; 0000000| size_t ret; 0000000| if (headBitsWithRemainingOnes != U.max) 0000000| return false; 0000000| if (bodyChunks.length) | { 0000000| auto bc = bodyChunks.lightConst; | do | { 0000000| if (bc.front != I.max) 0000000| return false; 0000000| bc.popFront; | } 0000000| while(bc.length); | } 0000000| if (tailBitsWithRemainingOnes != U.max) 0000000| return false; 0000000| return true; | } | | size_t cttz() | { 0000000| U v; 0000000| size_t ret; 0000000| if (isCentralProblem) | { 0000000| v = centralBitsWithRemainingOnes; 0000000| if (v) 0000000| goto R; 0000000| ret = centralLength; 0000000| goto L; | } 0000000| v = headBitsWithRemainingOnes; 0000000| if (v) 0000000| goto R; 0000000| ret = headLength; 0000000| if (bodyChunks.length) | { 0000000| auto bc = bodyChunks.lightConst; | do | { 0000000| v = bc.front; 0000000| if (v) 0000000| goto R; 0000000| ret += I.sizeof * 8; 0000000| bc.popFront; | } 0000000| while(bc.length); | } 0000000| v = tailBitsWithRemainingOnes; 0000000| if (v) 0000000| goto R; 0000000| ret += tailLength; 0000000| goto L; | R: 0000000| ret += v.cttz; | L: 0000000| return ret; | } | | size_t ctlz() | { 0000000| U v; 0000000| size_t ret; 0000000| if (isCentralProblem) | { 0000000| v = centralBitsWithRemainingOnes; 0000000| if (v) 0000000| goto R; 0000000| ret = centralLength; 0000000| goto L; | } 0000000| v = tailBitsWithRemainingOnesS; 0000000| if (v) 0000000| goto R; 0000000| ret = tailLength; 0000000| if (bodyChunks.length) | { 0000000| auto bc = bodyChunks.lightConst; | do | { 0000000| v = bc.back; 0000000| if (v) 0000000| goto R; 0000000| ret += I.sizeof * 8; 0000000| bc.popBack; | } 0000000| while(bc.length); | } 0000000| v = headBitsWithRemainingOnesS; 0000000| if (v) 0000000| goto R; 0000000| ret += headLength; 0000000| goto L; | R: 0000000| ret += v.ctlz; | L: 0000000| return ret; | } | | sizediff_t nBitsToCount(size_t count) | { 1| size_t ret; 1| if (count == 0) 0000000| return count; 2| U v, cnt; 1| if (isCentralProblem) | { 0000000| v = centralBitsWithRemainingZeros; 0000000| goto E; | } 1| v = headBitsWithRemainingZeros; 1| cnt = v.ctpop; 1| if (cnt >= count) 0000000| goto R; 1| ret += headLength; 1| count -= cast(size_t) cnt; 1| if (bodyChunks.length) | { 1| auto bc = bodyChunks.lightConst; | do | { 5| v = bc.front; 5| cnt = v.ctpop; 5| if (cnt >= count) 1| goto R; 4| ret += I.sizeof * 8; 4| count -= cast(size_t) cnt; 4| bc.popFront; | } 4| while(bc.length); | } 0000000| v = tailBitsWithRemainingZeros; | E: 0000000| cnt = v.ctpop; 0000000| if (cnt >= count) 0000000| goto R; 0000000| return -1; | R: 1| return ret + v.nTrailingBitsToCount(count); | } | | sizediff_t retroNBitsToCount(size_t count) | { 1| if (count == 0) 0000000| return count; 1| size_t ret; 2| U v, cnt; 1| if (isCentralProblem) | { 0000000| v = centralBitsWithRemainingZerosS; 0000000| goto E; | } 1| v = tailBitsWithRemainingZerosS; 1| cnt = v.ctpop; 1| if (cnt >= count) 0000000| goto R; 1| ret += tailLength; 1| count -= cast(size_t) cnt; 1| if (bodyChunks.length) | { 1| auto bc = bodyChunks.lightConst; | do | { 14| v = bc.back; 14| cnt = v.ctpop; 14| if (cnt >= count) 1| goto R; 13| ret += I.sizeof * 8; 13| count -= cast(size_t) cnt; 13| bc.popBack; | } 13| while(bc.length); | } 0000000| v = headBitsWithRemainingZerosS; | E: 0000000| cnt = v.ctpop; 0000000| if (cnt >= count) 0000000| goto R; 0000000| return -1; | R: 1| return ret + v.nLeadingBitsToCount(count); | } |} | |/++ |Сount bits until set bit count is reached. Works with ndslices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). |Returns: bit count if set bit count is reached or `-1` otherwise. |+/ |sizediff_t nBitsToCount(Field, I)(Slice!(FieldIterator!(BitField!(Field, I))) bitSlice, size_t count) |{ 1| return BitSliceAccelerator!(Field, I)(bitSlice).nBitsToCount(count); |} | |///ditto |sizediff_t nBitsToCount(Field, I)(Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))) bitSlice, size_t count) |{ | import mir.ndslice.topology: retro; 1| return BitSliceAccelerator!(Field, I)(bitSlice.retro).retroNBitsToCount(count); |} | |/// |version(mir_test) |pure unittest |{ | import mir.ndslice.allocation: bitSlice; | import mir.ndslice.topology: retro; 1| auto s = bitSlice(1000); 1| s[50] = true; 1| s[100] = true; 1| s[200] = true; 1| s[300] = true; 1| s[400] = true; 1| assert(s.nBitsToCount(4) == 301); 1| assert(s.retro.nBitsToCount(4) == 900); |} | |private void checkShapesMatch( | string fun = __FUNCTION__, | string pfun = __PRETTY_FUNCTION__, | Slices...) | (Slices slices) | if (Slices.length > 1) |{ | enum msgShape = "all slices must have the same shape" ~ tailErrorMessage!(fun, pfun); | enum N = slices[0].shape.length; | foreach (i, Slice; Slices) | { | static if (i == 0) 1505| continue; | else | static if (slices[i].shape.length == N) 1448| assert(slices[i].shape == slices[0].shape, msgShape); | else | { | import mir.ndslice.fuse: fuseShape; | static assert(slices[i].fuseShape.length >= N); 59| assert(cast(size_t[N])slices[i].fuseShape[0 .. N] == slices[0].shape, msgShape); | } | } |} | | |package(mir) template allFlattened(args...) |{ | static if (args.length) | { | alias arg = args[0]; | @optmath @property allFlattenedMod()() | { | import mir.ndslice.topology: flattened; 304| return flattened(arg); | } | alias allFlattened = AliasSeq!(allFlattenedMod, allFlattened!(args[1..$])); | } | else | alias allFlattened = AliasSeq!(); |} | |private template areAllContiguousSlices(Slices...) |{ | import mir.ndslice.traits: isContiguousSlice; | static if (allSatisfy!(isContiguousSlice, Slices)) | enum areAllContiguousSlices = Slices[0].N > 1 && areAllContiguousSlicesImpl!(Slices[0].N, Slices[1 .. $]); | else | enum areAllContiguousSlices = false; |} | |private template areAllContiguousSlicesImpl(size_t N, Slices...) |{ | static if (Slices.length == 0) | enum areAllContiguousSlicesImpl = true; | else | enum areAllContiguousSlicesImpl = Slices[0].N == N && areAllContiguousSlicesImpl!(N, Slices[1 .. $]); |} | |version(LDC) {} |else version(GNU) {} |else version (Windows) {} |else version (X86_64) |{ | //Compiling with DMD for x86-64 for Linux & OS X with optimizations enabled, | //"Tensor mutation on-the-fly" unittest was failing. Disabling inlining | //caused it to succeed. | //TODO: Rework so this is unnecessary! | version = Mir_disable_inlining_in_reduce; |} | |version(Mir_disable_inlining_in_reduce) |{ | private enum Mir_disable_inlining_in_reduce = true; | | private template _naryAliases(size_t n) | { | static if (n == 0) | enum _naryAliases = ""; | else | { | enum i = n - 1; | enum _naryAliases = _naryAliases!i ~ "alias " ~ cast(char)('a' + i) ~ " = args[" ~ i.stringof ~ "];\n"; | } | } | | private template nonInlinedNaryFun(alias fun) | { | import mir.math.common : optmath; | static if (is(typeof(fun) : string)) | { | /// Specialization for string lambdas | @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) | if (args.length <= 26) | { | pragma(inline,false); | mixin(_naryAliases!(Args.length)); | return mixin(fun); | } | } | else static if (is(typeof(fun.opCall) == function)) | { | @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) | if (is(typeof(fun.opCall(args)))) | { | pragma(inline,false); | return fun.opCall(args); | } | } | else | { | @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) | if (is(typeof(fun(args)))) | { | pragma(inline,false); | return fun(args); | } | } | } |} |else |{ | private enum Mir_disable_inlining_in_reduce = false; |} | |S reduceImpl(alias fun, S, Slices...)(S seed, Slices slices) |{ | do | { | static if (DimensionCount!(Slices[0]) == 1) | mixin(`seed = fun(seed,` ~ frontOf!(Slices.length) ~ `);`); | else | mixin(`seed = .reduceImpl!fun(seed,` ~ frontOf!(Slices.length) ~ `);`); 10707| foreach_reverse(ref slice; slices) 10707| slice.popFront; | } 10701| while(!slices[0].empty); 760| return seed; |} | |/++ |Implements the homonym function (also known as `accumulate`, |`compress`, `inject`, or `fold`) present in various programming |languages of functional flavor. The call `reduce!(fun)(seed, slice1, ..., sliceN)` |first assigns `seed` to an internal variable `result`, |also called the accumulator. Then, for each set of element `x1, ..., xN` in |`slice1, ..., sliceN`, `result = fun(result, x1, ..., xN)` gets evaluated. Finally, |`result` is returned. | |`reduce` allows to iterate multiple slices in the lockstep. | |Note: | $(NDSLICEREF topology, pack) can be used to specify dimensions. |Params: | fun = A function. |See_Also: | $(HTTP llvm.org/docs/LangRef.html#fast-math-flags, LLVM IR: Fast Math Flags) | | $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) |+/ |template reduce(alias fun) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun) | && !Mir_disable_inlining_in_reduce) | /++ | Params: | seed = An initial accumulation value. | slices = One or more slices, range, and arrays. | Returns: | the accumulated `result` | +/ | @optmath auto reduce(S, Slices...)(S seed, Slices slices) | if (Slices.length) | { | static if (Slices.length > 1) 2| slices.checkShapesMatch; | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; 6| return .reduce!fun(seed, allFlattened!(allLightScope!slices)); | } | else | { 769| if (slices[0].anyEmpty) 9| return cast(Unqual!S) seed; | static if (is(S : Unqual!S)) | alias UT = Unqual!S; | else | alias UT = S; 760| return reduceImpl!(fun, UT, Slices)(seed, allLightScope!slices); | } | } | else version(Mir_disable_inlining_in_reduce) | //As above, but with inlining disabled. | @optmath auto reduce(S, Slices...)(S seed, Slices slices) | if (Slices.length) | { | static if (Slices.length > 1) | slices.checkShapesMatch; | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; | return .reduce!fun(seed, allFlattened!(allLightScope!slices)); | } | else | { | if (slices[0].anyEmpty) | return cast(Unqual!S) seed; | static if (is(S : Unqual!S)) | alias UT = Unqual!S; | else | alias UT = S; | return reduceImpl!(nonInlinedNaryFun!fun, UT, Slices)(seed, allLightScope!slices); | } | } | else | alias reduce = .reduce!(naryFun!fun); |} | |/// Ranges and arrays |version(mir_test) |unittest |{ 1| auto ar = [1, 2, 3]; 1| auto s = 0.reduce!"a + b"(ar); 1| assert (s == 6); |} | |/// Single slice |version(mir_test) |unittest |{ | import mir.ndslice.topology : iota; | | //| 0 1 2 | => 3 | | //| 3 4 5 | => 12 | => 15 1| auto sl = iota(2, 3); | | // sum of all element in the slice 1| auto res = size_t(0).reduce!"a + b"(sl); | 1| assert(res == 15); |} | |/// Multiple slices, dot product |version(mir_test) |unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | //| 0 1 2 | | //| 3 4 5 | 1| auto a = iota([2, 3], 0).as!double.slice; | //| 1 2 3 | | //| 4 5 6 | 1| auto b = iota([2, 3], 1).as!double.slice; | | alias dot = reduce!"a + b * c"; 1| auto res = dot(0.0, a, b); | | // check the result: | import mir.ndslice.topology : flattened; | import std.numeric : dotProduct; 1| assert(res == dotProduct(a.flattened, b.flattened)); |} | |/// Zipped slices, dot product |pure |version(mir_test) unittest |{ | import std.typecons : Yes; | import std.numeric : dotProduct; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota, zip, universal; | import mir.math.common : optmath; | | static @optmath T fmuladd(T, Z)(const T a, Z z) | { 6| return a + z.a * z.b; | } | | // 0 1 2 | // 3 4 5 1| auto sl1 = iota(2, 3).as!double.slice.universal; | // 1 2 3 | // 4 5 6 1| auto sl2 = iota([2, 3], 1).as!double.slice; | | // slices must have the same strides for `zip!true`. 1| assert(sl1.strides == sl2.strides); | 1| auto z = zip!true(sl1, sl2); | 1| auto dot = reduce!fmuladd(0.0, z); | 1| assert(dot == dotProduct(iota(6), iota([6], 1))); |} | |/// Tensor mutation on-the-fly |version(mir_test) |unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | import mir.math.common : optmath; | | static @optmath T fun(T)(const T a, ref T b) | { 6| return a + b++; | } | | //| 0 1 2 | | //| 3 4 5 | 1| auto sl = iota(2, 3).as!double.slice; | 1| auto res = reduce!fun(double(0), sl); | 1| assert(res == 15); | | //| 1 2 3 | | //| 4 5 6 | 1| assert(sl == iota([2, 3], 1)); |} | |/++ |Packed slices. | |Computes minimum value of maximum values for each row. |+/ |version(mir_test) |unittest |{ | import mir.math.common; | import mir.ndslice.allocation : slice; | import mir.ndslice.dynamic : transposed; | import mir.ndslice.topology : as, iota, pack, map, universal; | 5| alias maxVal = (a) => reduce!fmax(-double.infinity, a); 2| alias minVal = (a) => reduce!fmin(double.infinity, a); 2| alias minimaxVal = (a) => minVal(a.pack!1.map!maxVal); | 1| auto sl = iota(2, 3).as!double.slice; | | // Vectorized computation: row stride equals 1. | //| 0 1 2 | => | 2 | | //| 3 4 5 | => | 5 | => 2 1| auto res = minimaxVal(sl); 1| assert(res == 2); | | // Common computation: row stride does not equal 1. | //| 0 1 2 | | 0 3 | => | 3 | | //| 3 4 5 | => | 1 4 | => | 4 | | // | 2 5 | => | 5 | => 3 1| auto resT = minimaxVal(sl.universal.transposed); 1| assert(resT == 3); |} | |/// Dlang Range API support. |version(mir_test) |unittest |{ | import mir.algorithm.iteration: each; | import std.range: phobos_iota = iota; | 1| int s; | // 0 1 2 3 5| 4.phobos_iota.each!(i => s += i); 1| assert(s == 6); |} | |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; 1| auto a = reduce!"a + b"(size_t(7), iota([0, 1], 1)); 1| assert(a == 7); |} | |void eachImpl(alias fun, Slices...)(Slices slices) |{ 2419| foreach(ref slice; slices) 2419| assert(!slice.empty); | do | { | static if (DimensionCount!(Slices[0]) == 1) | mixin(`fun(`~ frontOf!(Slices.length) ~ `);`); | else 604| .eachImpl!fun(frontOf2!slices); | foreach_reverse(i; Iota!(Slices.length)) 16728| slices[i].popFront; | } 8511| while(!slices[0].empty); |} | |void chequerEachImpl(alias fun, Slices...)(Chequer color, Slices slices) |{ 12| foreach(ref slice; slices) 12| assert(!slice.empty); | static if (DimensionCount!(Slices[0]) == 1) | { 10| if (color) | { | foreach_reverse(i; Iota!(Slices.length)) 5| slices[i].popFront; 5| if (slices[0].empty) 0000000| return; | } 10| eachImpl!fun(strideOf!slices); | } | else | { | do | { | mixin(`.chequerEachImpl!fun(color,` ~ frontOf!(Slices.length) ~ `);`); 10| color = cast(Chequer)!color; | foreach_reverse(i; Iota!(Slices.length)) 10| slices[i].popFront; | } 10| while(!slices[0].empty); | } |} | |/++ |The call `each!(fun)(slice1, ..., sliceN)` |evaluates `fun` for each set of elements `x1, ..., xN` in |the borders of `slice1, ..., sliceN` respectively. | |`each` allows to iterate multiple slices in the lockstep. | |Params: | fun = A function. |Note: | $(NDSLICEREF dynamic, transposed) and | $(NDSLICEREF topology, pack) can be used to specify dimensions. |+/ |template eachOnBorder(alias fun) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun)) | /++ | Params: | slices = One or more slices. | +/ | @optmath void eachOnBorder(Slices...)(Slices slices) | if (allSatisfy!(isSlice, Slices)) | { | import mir.ndslice.traits: isContiguousSlice; | static if (Slices.length > 1) 2| slices.checkShapesMatch; 5| if (!slices[0].anyEmpty) | { | alias N = DimensionCount!(Slices[0]); | static if (N == 1) | { | mixin(`fun(`~ frontOf!(Slices.length) ~ `);`); 1| if (slices[0].length > 1) 1| fun(backOf!slices); | } | else | static if (anySatisfy!(isContiguousSlice, Slices)) | { | import mir.ndslice.topology: canonical; | template f(size_t i) | { | static if (isContiguousSlice!(Slices[i])) 2| auto f () { return canonical(slices[i]); } | else | alias f = slices[i]; | } 2| eachOnBorder(staticMap!(f, Iota!(Slices.length))); | } | else | { | foreach (dimension; Iota!N) | { 4| eachImpl!fun(frontOfD!(dimension, slices)); 6| foreach_reverse(ref slice; slices) 6| slice.popFront!dimension; 4| if (slices[0].empty!dimension) 0000000| return; 4| eachImpl!fun(backOfD!(dimension, slices)); 6| foreach_reverse(ref slice; slices) 6| slice.popBack!dimension; 4| if (slices[0].empty!dimension) 0000000| return; | } | } | } | } | else | alias eachOnBorder = .eachOnBorder!(naryFun!fun); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : repeat, iota; | 1| auto sl = [3, 4].iota.slice; 1| auto zeros = repeat(0, [3, 4]); | 1| sl.eachOnBorder!"a = b"(zeros); | 1| assert(sl == | [[0, 0, 0 ,0], | [0, 5, 6, 0], | [0, 0, 0 ,0]]); | 1| sl.eachOnBorder!"a = 1"; 1| sl[0].eachOnBorder!"a = 2"; | 1| assert(sl == | [[2, 1, 1, 2], | [1, 5, 6, 1], | [1, 1, 1 ,1]]); |} | |/++ |The call `each!(fun)(slice1, ..., sliceN)` |evaluates `fun` for each set of elements `x1, ..., xN` in |`slice1, ..., sliceN` respectively. | |`each` allows to iterate multiple slices in the lockstep. |Params: | fun = A function. |Note: | $(NDSLICEREF dynamic, transposed) and | $(NDSLICEREF topology, pack) can be used to specify dimensions. |See_Also: | This is functionally similar to $(LREF reduce) but has not seed. |+/ |template each(alias fun) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun)) | { | /++ | Params: | slices = One or more slices, ranges, and arrays. | +/ | @optmath auto each(Slices...)(Slices slices) | if (Slices.length && !is(Slices[0] : Chequer)) | { | static if (Slices.length > 1) 633| slices.checkShapesMatch; | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; 94| .each!fun(allFlattened!(allLightScope!slices)); | } | else | { 547| if (slices[0].anyEmpty) 3| return; 544| eachImpl!fun(allLightScope!slices); | } | } | | /++ | Iterates elements of selected $(LREF Chequer) color. | Params: | color = $(LREF Chequer). | slices = One or more slices. | +/ | @optmath auto each(Slices...)(Chequer color, Slices slices) | if (Slices.length && allSatisfy!(isSlice, Slices)) | { | static if (Slices.length > 1) | slices.checkShapesMatch; 2| if (slices[0].anyEmpty) 0000000| return; 2| chequerEachImpl!fun(color, allLightScope!slices); | } | } | else | alias each = .each!(naryFun!fun); |} | |/// Ranges and arrays |version(mir_test) |unittest |{ 1| auto ar = [1, 2, 3]; 1| ar.each!"a *= 2"; 1| assert (ar == [2, 4, 6]); |} | |/// Single slice, multiply-add |version(mir_test) |unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | //| 0 1 2 | | //| 3 4 5 | 1| auto sl = iota(2, 3).as!double.slice; | 7| sl.each!((ref a) { a = a * 10 + 5; }); | 1| assert(sl == | [[ 5, 15, 25], | [35, 45, 55]]); |} | |/// Swap two slices |version(mir_test) |unittest |{ | import mir.utility : swap; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | //| 0 1 2 | | //| 3 4 5 | 1| auto a = iota([2, 3], 0).as!double.slice; | //| 10 11 12 | | //| 13 14 15 | 1| auto b = iota([2, 3], 10).as!double.slice; | 1| each!swap(a, b); | 1| assert(a == iota([2, 3], 10)); 1| assert(b == iota([2, 3], 0)); |} | |/// Swap two zipped slices |version(mir_test) |unittest |{ | import mir.utility : swap; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, zip, iota; | | //| 0 1 2 | | //| 3 4 5 | 1| auto a = iota([2, 3], 0).as!double.slice; | //| 10 11 12 | | //| 13 14 15 | 1| auto b = iota([2, 3], 10).as!double.slice; | 1| auto z = zip(a, b); | 13| z.each!(z => swap(z.a, z.b)); | 1| assert(a == iota([2, 3], 10)); 1| assert(b == iota([2, 3], 0)); |} | |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; 1| size_t i; 1| iota(0, 2).each!((a){i++;}); 1| assert(i == 0); |} | |/++ |The call `eachUploPair!(fun)(matrix)` |evaluates `fun` for each pair (`matrix[j, i]`, `matrix[i, j]`), |for i <= j (default) or i < j (if includeDiagonal is false). | |Params: | fun = A function. | includeDiagonal = true if applying function to diagonal, | false (default) otherwise. |+/ |template eachUploPair(alias fun, bool includeDiagonal = false) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun)) | { | /++ | Params: | matrix = Square matrix. | +/ | auto eachUploPair(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix) | in | { 10| assert(matrix.length!0 == matrix.length!1, "matrix must be square."); | } | do | { | static if (kind == Contiguous) | { | import mir.ndslice.topology: canonical; 5| .eachUploPair!(fun, includeDiagonal)(matrix.canonical); | } | else | { | static if (includeDiagonal == true) | { 2| if (matrix.length) do | { 6| eachImpl!fun(matrix.lightScope.front!0, matrix.lightScope.front!1); 6| matrix.popFront!1; 6| matrix.popFront!0; | // hint for optimizer 6| matrix._lengths[1] = matrix._lengths[0]; | } 6| while (matrix.length); | } | else | { 3| if (matrix.length) for(;;) | { 10| assert(!matrix.empty!0); 10| assert(!matrix.empty!1); 10| auto l = matrix.lightScope.front!1; 10| auto u = matrix.lightScope.front!0; 10| matrix.popFront!1; 10| matrix.popFront!0; 10| l.popFront; 10| u.popFront; | // hint for optimizer 10| matrix._lengths[1] = matrix._lengths[0] = l._lengths[0] = u._lengths[0]; 10| if (u.length == 0) 3| break; 7| eachImpl!fun(u, l); | } | } | } | } | } | else | { | alias eachUploPair = .eachUploPair!(naryFun!fun, includeDiagonal); | } |} | |/// Transpose matrix in place. |version(mir_test) |unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota, universal; | import mir.ndslice.dynamic: transposed; | import mir.utility: swap; | 1| auto m = iota(4, 4).slice; | 1| m.eachUploPair!swap; | 1| assert(m == iota(4, 4).universal.transposed); |} | |/// Reflect Upper matrix part to lower part. |version(mir_test) |unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota, universal; | import mir.ndslice.dynamic: transposed; | import mir.utility: swap; | | // 0 1 2 | // 3 4 5 | // 6 7 8 1| auto m = iota(3, 3).slice; | 4| m.eachUploPair!((u, ref l) { l = u; }); | 1| assert(m == [ | [0, 1, 2], | [1, 4, 5], | [2, 5, 8]]); |} | |/// Fill lower triangle and diagonal with zeroes. |version(mir_test) |unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | // 1 2 3 | // 4 5 6 | // 7 8 9 1| auto m = iota([3, 3], 1).slice; | 7| m.eachUploPair!((u, ref l) { l = 0; }, true); | 1| assert(m == [ | [0, 2, 3], | [0, 0, 6], | [0, 0, 0]]); |} | |version(mir_test) |unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | // 0 1 2 | // 3 4 5 | // 6 7 8 1| auto m = iota(3, 3).slice; 7| m.eachUploPair!((u, ref l) { l = l + 1; }, true); 1| assert(m == [ | [1, 1, 2], | [4, 5, 5], | [7, 8, 9]]); |} | |version(mir_test) |unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | // 0 1 2 | // 3 4 5 | // 6 7 8 1| auto m = iota(3, 3).slice; 4| m.eachUploPair!((u, ref l) { l = l + 1; }, false); | 1| assert(m == [ | [0, 1, 2], | [4, 4, 5], | [7, 8, 8]]); |} | |/++ |Checks if the matrix is symmetric. |+/ |template isSymmetric(alias fun = "a == b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun)) | /++ | Params: | matrix = 2D ndslice. | +/ | bool isSymmetric(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix) | { | static if (kind == Contiguous) | { | import mir.ndslice.topology: canonical; 2| return .isSymmetric!fun(matrix.canonical); | } | else | { 2| if (matrix.length!0 != matrix.length!1) 0000000| return false; 2| if (matrix.length) do | { 3| if (!allImpl!fun(matrix.lightScope.front!0, matrix.lightScope.front!1)) | { 1| return false; | } 2| matrix.popFront!1; 2| matrix.popFront!0; 2| matrix._lengths[1] = matrix._lengths[0]; | } 2| while (matrix.length); 1| return true; | } | } | else | alias isSymmetric = .isSymmetric!(naryFun!fun); |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: iota; 1| assert(iota(2, 2).isSymmetric == false); | 1| assert( | [1, 2, | 2, 3].sliced(2, 2).isSymmetric == true); |} | |bool minPosImpl(alias fun, Iterator, size_t N, SliceKind kind)(scope ref size_t[N] backwardIndex, scope ref Iterator iterator, Slice!(Iterator, N, kind) slice) |{ 32| bool found; | do | { | static if (slice.shape.length == 1) | { 132| if (fun(*slice._iterator, *iterator)) | { 42| backwardIndex[0] = slice.length; 42| iterator = slice._iterator; 42| found = true; | } | } | else | { 21| if (minPosImpl!(fun, LightScopeOf!Iterator, N - 1, kind)(backwardIndex[1 .. $], iterator, lightScope(slice).front)) | { 11| backwardIndex[0] = slice.length; 11| found = true; | } | } 153| slice.popFront; | } 153| while(!slice.empty); 32| return found; |} | |bool[2] minmaxPosImpl(alias fun, Iterator, size_t N, SliceKind kind)(scope ref size_t[2][N] backwardIndex, scope ref Iterator[2] iterator, Slice!(Iterator, N, kind) slice) |{ 10| bool[2] found; | do | { | static if (slice.shape.length == 1) | { 48| if (fun(*slice._iterator, *iterator[0])) | { 4| backwardIndex[0][0] = slice.length; 4| iterator[0] = slice._iterator; 4| found[0] = true; | } | else 44| if (fun(*iterator[1], *slice._iterator)) | { 27| backwardIndex[0][1] = slice.length; 27| iterator[1] = slice._iterator; 27| found[1] = true; | } | } | else | { 6| auto r = minmaxPosImpl!(fun, LightScopeOf!Iterator, N - 1, kind)(backwardIndex[1 .. $], iterator, lightScope(slice).front); 6| if (r[0]) | { 4| backwardIndex[0][0] = slice.length; | } 6| if (r[1]) | { 4| backwardIndex[0][1] = slice.length; | } | } 54| slice.popFront; | } 54| while(!slice.empty); 10| return found; |} | |/++ |Finds a positions (ndslices) such that |`position[0].first` is minimal and `position[1].first` is maximal elements in the slice. | |Position is sub-ndslice of the same dimension in the right-$(RPAREN)down-$(RPAREN)etc$(LPAREN)$(LPAREN) corner. | |Params: | pred = A predicate. | |See_also: | $(LREF minmaxIndex), | $(LREF minPos), | $(LREF maxPos), | $(NDSLICEREF slice, Slice.backward). |+/ |template minmaxPos(alias pred = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slice = ndslice. | Returns: | 2 subslices with minimal and maximal `first` elements. | +/ | @optmath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind)[2] | minmaxPos(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { 2| typeof(return) pret; 2| if (!slice.anyEmpty) | { 2| size_t[2][N] ret; 2| auto scopeSlice = lightScope(slice); 2| auto it = scopeSlice._iterator; 2| LightScopeOf!Iterator[2] iterator = [it, it]; 2| minmaxPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); | foreach (i; Iota!N) | { 3| pret[0]._lengths[i] = ret[i][0]; 3| pret[1]._lengths[i] = ret[i][1]; | } 2| pret[0]._iterator = slice._iterator + (iterator[0] - scopeSlice._iterator); 2| pret[1]._iterator = slice._iterator + (iterator[1] - scopeSlice._iterator); | } 2| auto strides = slice.strides; | foreach(i; Iota!(0, pret[0].S)) | { 1| pret[0]._strides[i] = strides[i]; 1| pret[1]._strides[i] = strides[i]; | } 2| return pret; | } | else | alias minmaxPos = .minmaxPos!(naryFun!pred); |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; 1| auto s = [ | 2, 6, 4, -3, | 0, -4, -3, 3, | -3, -2, 7, 2, | ].sliced(3, 4); | 1| auto pos = s.minmaxPos; | 1| assert(pos[0] == s[$ - 2 .. $, $ - 3 .. $]); 1| assert(pos[1] == s[$ - 1 .. $, $ - 2 .. $]); | 1| assert(pos[0].first == -4); 1| assert(s.backward(pos[0].shape) == -4); 1| assert(pos[1].first == 7); 1| assert(s.backward(pos[1].shape) == 7); |} | |/++ |Finds a backward indices such that |`slice[indices[0]]` is minimal and `slice[indices[1]]` is maximal elements in the slice. | |Params: | pred = A predicate. | |See_also: | $(LREF minmaxIndex), | $(LREF minPos), | $(LREF maxPos), | $(REF Slice.backward, mir,ndslice,slice). |+/ |template minmaxIndex(alias pred = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slice = ndslice. | Returns: | Subslice with minimal (maximal) `first` element. | +/ | @optmath size_t[N][2] minmaxIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { 2| typeof(return) pret = size_t.max; 2| if (!slice.anyEmpty) | { 2| auto shape = slice.shape; 2| size_t[2][N] ret; | foreach (i; Iota!N) | { 3| ret[i][1] = ret[i][0] = shape[i]; | } 2| auto scopeSlice = lightScope(slice); 2| auto it = scopeSlice._iterator; 2| LightScopeOf!Iterator[2] iterator = [it, it]; 2| minmaxPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); | foreach (i; Iota!N) | { 3| pret[0][i] = slice._lengths[i] - ret[i][0]; 3| pret[1][i] = slice._lengths[i] - ret[i][1]; | } | } 2| return pret; | } | else | alias minmaxIndex = .minmaxIndex!(naryFun!pred); |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; 1| auto s = [ | 2, 6, 4, -3, | 0, -4, -3, 3, | -3, -2, 7, 8, | ].sliced(3, 4); | 1| auto indices = s.minmaxIndex; | 1| assert(indices == [[1, 1], [2, 3]]); 1| assert(s[indices[0]] == -4); 1| assert(s[indices[1]] == 8); |} | |/++ |Finds a backward index such that |`slice.backward(index)` is minimal(maximal). | |Params: | pred = A predicate. | |See_also: | $(LREF minIndex), | $(LREF maxPos), | $(LREF maxIndex), | $(REF Slice.backward, mir,ndslice,slice). |+/ |template minPos(alias pred = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slice = ndslice. | Returns: | Multidimensional backward index such that element is minimal(maximal). | Backward index equals zeros, if slice is empty. | +/ | @optmath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind) | minPos(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { 4| typeof(return) ret; 4| auto iterator = slice.lightScope._iterator; 4| if (!slice.anyEmpty) | { 4| minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret._lengths, iterator, lightScope(slice)); 4| ret._iterator = slice._iterator + (iterator - slice.lightScope._iterator); | } 4| auto strides = slice.strides; | foreach(i; Iota!(0, ret.S)) | { 2| ret._strides[i] = strides[i]; | } 4| return ret; | } | else | alias minPos = .minPos!(naryFun!pred); |} | |/// ditto |template maxPos(alias pred = "a < b") |{ | import mir.functional: naryFun, reverseArgs; | alias maxPos = minPos!(reverseArgs!(naryFun!pred)); |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; 1| auto s = [ | 2, 6, 4, -3, | 0, -4, -3, 3, | -3, -2, 7, 2, | ].sliced(3, 4); | 1| auto pos = s.minPos; | 1| assert(pos == s[$ - 2 .. $, $ - 3 .. $]); 1| assert(pos.first == -4); 1| assert(s.backward(pos.shape) == -4); | 1| pos = s.maxPos; | 1| assert(pos == s[$ - 1 .. $, $ - 2 .. $]); 1| assert(pos.first == 7); 1| assert(s.backward(pos.shape) == 7); |} | |/++ |Finds an index such that |`slice[index]` is minimal(maximal). | |Params: | pred = A predicate. | |See_also: | $(LREF minIndex), | $(LREF maxPos), | $(LREF maxIndex). |+/ |template minIndex(alias pred = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slice = ndslice. | Returns: | Multidimensional index such that element is minimal(maximal). | Index elements equal to `size_t.max`, if slice is empty. | +/ | @optmath size_t[N] minIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { 7| size_t[N] ret = size_t.max; 7| if (!slice.anyEmpty) | { 7| ret = slice.shape; 7| auto scopeSlice = lightScope(slice); 7| auto iterator = scopeSlice._iterator; 7| minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); | foreach (i; Iota!N) 12| ret[i] = slice._lengths[i] - ret[i]; | } 7| return ret; | } | else | alias minIndex = .minIndex!(naryFun!pred); |} | |/// ditto |template maxIndex(alias pred = "a < b") |{ | import mir.functional: naryFun, reverseArgs; | alias maxIndex = minIndex!(reverseArgs!(naryFun!pred)); |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; 1| auto s = [ | 2, 6, 4, -3, | 0, -4, -3, 3, | -3, -2, 7, 8, | ].sliced(3, 4); | 1| auto index = s.minIndex; | 1| assert(index == [1, 1]); 1| assert(s[index] == -4); | 1| index = s.maxIndex; | 1| assert(index == [2, 3]); 1| assert(s[index] == 8); |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; 1| auto s = [ | -8, 6, 4, -3, | 0, -4, -3, 3, | -3, -2, 7, 8, | ].sliced(3, 4); | 1| auto index = s.minIndex; | 1| assert(index == [0, 0]); 1| assert(s[index] == -8); |} | |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; 1| auto s = [ | 0, 1, 2, 3, | 4, 5, 6, 7, | 8, 9, 10, 11 | ].sliced(3, 4); | 1| auto index = s.minIndex; 1| assert(index == [0, 0]); 1| assert(s[index] == 0); | 1| index = s.maxIndex; 1| assert(index == [2, 3]); 1| assert(s[index] == 11); |} | |bool findImpl(alias fun, size_t N, Slices...)(scope ref size_t[N] backwardIndex, Slices slices) | if (Slices.length) |{ | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) | { | auto cnt = BitSliceAccelerator!(Field, I)(slices[0]).cttz; | if (cnt = -1) | return false; | backwardIndex[0] = slices[0].length - cnt; | } | else | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) | { | import mir.ndslice.topology: retro; | auto cnt = BitSliceAccelerator!(Field, I)(slices[0].retro).ctlz; | if (cnt = -1) | return false; | backwardIndex[0] = slices[0].length - cnt; | } | else | { | do | { | static if (DimensionCount!(Slices[0]) == 1) | { 47| if (mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) | { 7| backwardIndex[0] = slices[0].length; 7| return true; | } | } | else | { 14| if (mixin(`findImpl!fun(backwardIndex[1 .. $],` ~ frontOf!(Slices.length) ~ `)`)) | { 5| backwardIndex[0] = slices[0].length; 5| return true; | } | } 53| foreach_reverse(ref slice; slices) 53| slice.popFront; | } 49| while(!slices[0].empty); 12| return false; | } |} | |/++ |Finds an index such that |`pred(slices[0][index], ..., slices[$-1][index])` is `true`. | |Params: | pred = A predicate. | |See_also: | $(LREF find), | $(LREF any). |Optimization: | `findIndex!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). |+/ |template findIndex(alias pred) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slices = One or more slices. | Returns: | Multidimensional index such that the predicate is true. | Index equals `size_t.max`, if the predicate evaluates `false` for all indices. | Constraints: | All slices must have the same shape. | +/ | @optmath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) findIndex(Slices...)(Slices slices) | if (Slices.length) | { | static if (Slices.length > 1) | slices.checkShapesMatch; 4| size_t[DimensionCount!(Slices[0])] ret = -1; 4| auto lengths = slices[0].shape; 8| if (!slices[0].anyEmpty && findImpl!pred(ret, allLightScope!slices)) | foreach (i; Iota!(DimensionCount!(Slices[0]))) 3| ret[i] = lengths[i] - ret[i]; | static if (DimensionCount!(Slices[0]) > 1) 2| return ret; | else 2| return ret[0]; | } | else | alias findIndex = .findIndex!(naryFun!pred); |} | |/// Ranges and arrays |version(mir_test) |unittest |{ | import std.range : iota; | // 0 1 2 3 4 5 1| auto sl = iota(5); 1| size_t index = sl.findIndex!"a == 3"; | 1| assert(index == 3); 1| assert(sl[index] == 3); | 6| assert(sl.findIndex!(a => a == 8) == size_t.max); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3); 5| size_t[2] index = sl.findIndex!(a => a == 3); | 1| assert(sl[index] == 3); | 1| index = sl.findIndex!"a == 6"; 1| assert(index[0] == size_t.max); 1| assert(index[1] == size_t.max); |} | |/++ |Finds a backward index such that |`pred(slices[0].backward(index), ..., slices[$-1].backward(index))` is `true`. | |Params: | pred = A predicate. | |Optimization: | To check if any element was found | use the last dimension (row index). | This will slightly optimize the code. |-------- |if (backwardIndex) |{ | auto elem1 = slice1.backward(backwardIndex); | //... | auto elemK = sliceK.backward(backwardIndex); |} |else |{ | // not found |} |-------- | |See_also: | $(LREF findIndex), | $(LREF any), | $(REF Slice.backward, mir,ndslice,slice). | |Optimization: | `find!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). |+/ |template find(alias pred) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slices = One or more slices. | Returns: | Multidimensional backward index such that the predicate is true. | Backward index equals zeros, if the predicate evaluates `false` for all indices. | Constraints: | All slices must have the same shape. | +/ | @optmath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) find(Slices...)(Slices slices) | if (Slices.length && allSatisfy!(hasShape, Slices)) | { | static if (Slices.length > 1) 1| slices.checkShapesMatch; 7| size_t[DimensionCount!(Slices[0])] ret; 7| if (!slices[0].anyEmpty) 6| findImpl!pred(ret, allLightScope!slices); | static if (DimensionCount!(Slices[0]) > 1) 6| return ret; | else 1| return ret[0]; | } | else | alias find = .find!(naryFun!pred); |} | |/// Ranges and arrays |version(mir_test) |unittest |{ | import std.range : iota; | 1| auto sl = iota(10); 1| size_t index = sl.find!"a == 3"; | 1| assert(sl[$ - index] == 3); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3); 1| size_t[2] bi = sl.find!"a == 3"; 1| assert(sl.backward(bi) == 3); 1| assert(sl[$ - bi[0], $ - bi[1]] == 3); | 1| bi = sl.find!"a == 6"; 1| assert(bi[0] == 0); 1| assert(bi[1] == 0); |} | |/// Multiple slices |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | | // 0 1 2 | // 3 4 5 1| auto a = iota(2, 3); | // 10 11 12 | // 13 14 15 1| auto b = iota([2, 3], 10); | 5| size_t[2] bi = find!((a, b) => a * b == 39)(a, b); 1| assert(a.backward(bi) == 3); 1| assert(b.backward(bi) == 13); |} | |/// Zipped slices |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota, zip; | | // 0 1 2 | // 3 4 5 1| auto a = iota(2, 3); | // 10 11 12 | // 13 14 15 1| auto b = iota([2, 3], 10); | 1| size_t[2] bi = zip!true(a, b).find!"a.a * a.b == 39"; | 1| assert(a.backward(bi) == 3); 1| assert(b.backward(bi) == 13); |} | |/// Mutation on-the-fly |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3).as!double.slice; | | static bool pred(T)(ref T a) | { 6| if (a == 5) 1| return true; 5| a = 8; 5| return false; | } | 1| size_t[2] bi = sl.find!pred; | 1| assert(bi == [1, 1]); 1| assert(sl.backward(bi) == 5); | | import mir.test; | // sl was changed 1| sl.should == [[8, 8, 8], | [8, 8, 5]]; |} | |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; 1| size_t i; 1| size_t[2] bi = iota(2, 0).find!((elem){i++; return true;}); 1| assert(i == 0); 1| assert(bi == [0, 0]); |} | |size_t anyImpl(alias fun, Slices...)(Slices slices) | if (Slices.length) |{ | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) | { | return BitSliceAccelerator!(Field, I)(slices[0]).any; | } | else | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) | { | // pragma(msg, S); | import mir.ndslice.topology: retro; | return .anyImpl!fun(lightScope(slices[0]).retro); | } | else | { | do | { | static if (DimensionCount!(Slices[0]) == 1) | { 226| if (mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) 5| return true; | } | else | { | if (anyImpl!fun(frontOf2!slices)) | return true; | } 224| foreach_reverse(ref slice; slices) 224| slice.popFront; | } 221| while(!slices[0].empty); 3| return false; | } |} | |/++ |Like $(LREF find), but only returns whether or not the search was successful. | |Params: | pred = The predicate. |Optimization: | `any!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). |+/ |template any(alias pred = "a") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slices = One or more slices, ranges, and arrays. | Returns: | `true` if the search was successful and `false` otherwise. | Constraints: | All slices must have the same shape. | +/ | @optmath bool any(Slices...)(Slices slices) | if ((Slices.length == 1 || !__traits(isSame, pred, "a")) && Slices.length) | { | static if (Slices.length > 1) 2| slices.checkShapesMatch; | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; 5| return .any!pred(allFlattened!(allLightScope!slices)); | } | else | { 16| return !slices[0].anyEmpty && anyImpl!pred(allLightScope!slices); | } | } | else | alias any = .any!(naryFun!pred); |} | |/// Ranges and arrays |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import std.range : iota; | // 0 1 2 3 4 5 1| auto r = iota(6); | 1| assert(r.any!"a == 3"); 1| assert(!r.any!"a == 6"); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3); | 1| assert(sl.any!"a == 3"); 1| assert(!sl.any!"a == 6"); |} | |/// Multiple slices |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | | // 0 1 2 | // 3 4 5 1| auto a = iota(2, 3); | // 10 11 12 | // 13 14 15 1| auto b = iota([2, 3], 10); | 5| assert(any!((a, b) => a * b == 39)(a, b)); |} | |/// Zipped slices |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota, zip; | | // 0 1 2 | // 3 4 5 1| auto a = iota(2, 3); | // 10 11 12 | // 13 14 15 1| auto b = iota([2, 3], 10); | | // slices must have the same strides | 1| assert(zip!true(a, b).any!"a.a * a.b == 39"); |} | |/// Mutation on-the-fly |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3).as!double.slice; | | static bool pred(T)(ref T a) | { 6| if (a == 5) 1| return true; 5| a = 8; 5| return false; | } | 1| assert(sl.any!pred); | | // sl was changed 1| assert(sl == [[8, 8, 8], | [8, 8, 5]]); |} | |size_t allImpl(alias fun, Slices...)(Slices slices) | if (Slices.length) |{ | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) | { | return BitSliceAccelerator!(LightScopeOf!Field, I)(lightScope(slices[0])).all; | } | else | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) | { | // pragma(msg, S); | import mir.ndslice.topology: retro; | return .allImpl!fun(lightScope(slices[0]).retro); | } | else | { | do | { | static if (DimensionCount!(Slices[0]) == 1) | { 5508| if (!mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) 15| return false; | } | else | { 132| if (!allImpl!fun(frontOf2!slices)) 0000000| return false; | } 10921| foreach_reverse(ref slice; slices) 10921| slice.popFront; | } 5625| while(!slices[0].empty); 960| return true; | } |} | |/++ |Checks if all of the elements verify `pred`. | |Params: | pred = The predicate. |Optimization: | `all!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). |+/ |template all(alias pred = "a") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | slices = One or more slices. | Returns: | `true` all of the elements verify `pred` and `false` otherwise. | Constraints: | All slices must have the same shape. | +/ | @optmath bool all(Slices...)(Slices slices) | if ((Slices.length == 1 || !__traits(isSame, pred, "a")) && Slices.length) | { | static if (Slices.length > 1) 849| slices.checkShapesMatch; | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; 47| return .all!pred(allFlattened!(allLightScope!slices)); | } | else | { 1687| return slices[0].anyEmpty || allImpl!pred(allLightScope!slices); | } | } | else | alias all = .all!(naryFun!pred); |} | |/// Ranges and arrays |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import std.range : iota; | // 0 1 2 3 4 5 1| auto r = iota(6); | 1| assert(r.all!"a < 6"); 1| assert(!r.all!"a < 5"); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3); | 1| assert(sl.all!"a < 6"); 1| assert(!sl.all!"a < 5"); |} | |/// Multiple slices |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3); | 1| assert(all!"a - b == 0"(sl, sl)); |} | |/// Zipped slices |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota, zip; | | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3); | | 1| assert(zip!true(sl, sl).all!"a.a - a.b == 0"); |} | |/// Mutation on-the-fly |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | // 0 1 2 | // 3 4 5 1| auto sl = iota(2, 3).as!double.slice; | | static bool pred(T)(ref T a) | { 5| if (a < 4) | { 4| a = 8; 4| return true; | } 1| return false; | } | 1| assert(!sl.all!pred); | | // sl was changed 1| assert(sl == [[8, 8, 8], | [8, 4, 5]]); |} | |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; 1| size_t i; 1| assert(iota(2, 0).all!((elem){i++; return true;})); 1| assert(i == 0); |} | |/++ |Counts elements in slices according to the `fun`. |Params: | fun = A predicate. | |Optimization: | `count!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). |+/ |template count(alias fun) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun)) | /++ | Params: | slices = One or more slices, ranges, and arrays. | | Returns: The number elements according to the `fun`. | | Constraints: | All slices must have the same shape. | +/ | @optmath size_t count(Slices...)(Slices slices) | if (Slices.length) | { | static if (Slices.length > 1) | slices.checkShapesMatch; | static if (__traits(isSame, fun, naryFun!"true")) | { 3| return slices[0].elementCount; | } | else | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; 5| return .count!fun(allFlattened!(allLightScope!slices)); | } | else | { 12| if (slices[0].anyEmpty) 0000000| return 0; 12| return countImpl!(fun)(allLightScope!slices); | } | } | else | alias count = .count!(naryFun!fun); | |} | |/// Ranges and arrays |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import std.range : iota; | // 0 1 2 3 4 5 1| auto r = iota(6); | 1| assert(r.count!"true" == 6); 1| assert(r.count!"a" == 5); 1| assert(r.count!"a % 2" == 3); |} | |/// Single slice |version(mir_test) |unittest |{ | import mir.ndslice.topology : iota; | | //| 0 1 2 | | //| 3 4 5 | 1| auto sl = iota(2, 3); | 1| assert(sl.count!"true" == 6); 1| assert(sl.count!"a" == 5); 1| assert(sl.count!"a % 2" == 3); |} | |/// Accelerated set bit count |version(mir_test) |unittest |{ | import mir.ndslice.topology: retro, iota, bitwise; | import mir.ndslice.allocation: slice; | | //| 0 1 2 | | //| 3 4 5 | 1| auto sl = iota!size_t(2, 3).bitwise; | 1| assert(sl.count!"true" == 6 * size_t.sizeof * 8); | 1| assert(sl.slice.count!"a" == 7); | | // accelerated 1| assert(sl.count!"a" == 7); 1| assert(sl.retro.count!"a" == 7); | 1| auto sl2 = iota!ubyte([6], 128).bitwise; | // accelerated 1| assert(sl2.count!"a" == 13); 1| assert(sl2[4 .. $].count!"a" == 13); 1| assert(sl2[4 .. $ - 1].count!"a" == 12); 1| assert(sl2[4 .. $ - 1].count!"a" == 12); 1| assert(sl2[41 .. $ - 1].count!"a" == 1); |} | |version(mir_test) |unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: bitwise, assumeFieldsHaveZeroShift; 1| auto sl = slice!uint([6]).bitwise; 1| auto slb = slice!ubyte([6]).bitwise; 1| slb[4] = true; 1| auto d = slb[4]; 1| auto c = assumeFieldsHaveZeroShift(slb & ~slb); | // pragma(msg, typeof(c)); 1| assert(!sl.any); 1| assert((~sl).all); | // pragma(msg, typeof(~slb)); | // pragma(msg, typeof(~slb)); | // assert(sl.findIndex); |} | |/++ |Compares two or more slices for equality, as defined by predicate `pred`. | |See_also: $(NDSLICEREF slice, Slice.opEquals) | |Params: | pred = The predicate. |+/ |template equal(alias pred = "a == b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | { | /++ | Params: | slices = Two or more ndslices, ranges, and arrays. | | Returns: | `true` any of the elements verify `pred` and `false` otherwise. | +/ | bool equal(Slices...)(Slices slices) @safe | if (Slices.length >= 2) | { | import mir.internal.utility; | static if (allSatisfy!(hasShape, Slices)) | { 263| auto shape0 = slices[0].shape; | enum N = DimensionCount!(Slices[0]); 264| foreach (ref slice; slices[1 .. $]) | { 264| if (slice.shape != shape0) 2| goto False; | } 261| return all!pred(allLightScope!slices); | } | else | { | for(;;) | { 137| auto empty = slices[0].empty; 137| foreach (ref slice; slices[1 .. $]) | { 137| if (slice.empty != empty) 0000000| goto False; | } 137| if (empty) 34| return true; 103| if (!mixin(`pred(`~ frontOf!(Slices.length) ~ `)`)) 0000000| goto False; 206| foreach (ref slice; slices) 206| slice.popFront; | } | } 2| False: return false; | } | } | else | alias equal = .equal!(naryFun!pred); |} | |/// Ranges and arrays |@safe pure nothrow |version(mir_test) unittest |{ | import std.range : iota; 1| auto r = iota(6); 1| assert(r.equal([0, 1, 2, 3, 4, 5])); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : iota; | | // 0 1 2 | // 3 4 5 1| auto sl1 = iota(2, 3); | // 1 2 3 | // 4 5 6 1| auto sl2 = iota([2, 3], 1); | 1| assert(equal(sl1, sl1)); 1| assert(sl1 == sl1); //can also use opEquals for two Slices 1| assert(equal!"2 * a == b + c"(sl1, sl1, sl1)); | 1| assert(equal!"a < b"(sl1, sl2)); | 1| assert(!equal(sl1[0 .. $ - 1], sl1)); 1| assert(!equal(sl1[0 .. $, 0 .. $ - 1], sl1)); |} | |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.allocation: rcslice; | import mir.ndslice.topology: as, iota; | 2| auto x = 5.iota.as!double.rcslice; 2| auto y = x.rcslice; | 1| assert(equal(x, y)); 1| assert(equal!approxEqual(x, y)); |} | |ptrdiff_t cmpImpl(alias pred, A, B) | (scope A sl1, scope B sl2) | if (DimensionCount!A == DimensionCount!B) |{ | for (;;) | { | static if (DimensionCount!A == 1) | { | import mir.functional : naryFun; 36| if (naryFun!pred(sl1.front, sl2.front)) 14| return -1; 22| if (naryFun!pred(sl2.front, sl1.front)) 3| return 1; | } | else | { 6| if (auto res = .cmpImpl!pred(sl1.front, sl2.front)) 3| return res; | } 22| sl1.popFront; 22| if (sl1.empty) 8| return -cast(ptrdiff_t)(sl2.length > 1); 14| sl2.popFront; 14| if (sl2.empty) 1| return 1; | } |} | |/++ |Performs three-way recursive lexicographical comparison on two slices according to predicate `pred`. |Iterating `sl1` and `sl2` in lockstep, `cmp` compares each `N-1` dimensional element `e1` of `sl1` |with the corresponding element `e2` in `sl2` recursively. |If one of the slices has been finished,`cmp` returns a negative value if `sl1` has fewer elements than `sl2`, |a positive value if `sl1` has more elements than `sl2`, |and `0` if the ranges have the same number of elements. | |Params: | pred = The predicate. |+/ |template cmp(alias pred = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | /++ | Params: | sl1 = First slice, range, or array. | sl2 = Second slice, range, or array. | | Returns: | `0` if both ranges compare equal. | Negative value if the first differing element of `sl1` is less than the corresponding | element of `sl2` according to `pred`. | Positive value if the first differing element of `sl2` is less than the corresponding | element of `sl1` according to `pred`. | +/ | auto cmp(A, B) | (scope A sl1, scope B sl2) | if (DimensionCount!A == DimensionCount!B) | { 28| auto b = sl2.anyEmpty; 28| if (sl1.anyEmpty) | { 4| if (!b) 1| return -1; 3| auto sh1 = sl1.shape; 3| auto sh2 = sl2.shape; | foreach (i; Iota!(DimensionCount!A)) 4| if (sh1[i] != sh2[i]) 2| return sh1[i] > sh2[i] ? 1 : -1; 1| return 0; | } 24| if (b) 1| return 1; 23| return cmpImpl!pred(lightScope(sl1), lightScope(sl2)); | } | else | alias cmp = .cmp!(naryFun!pred); |} | |/// Ranges and arrays |@safe pure nothrow |version(mir_test) unittest |{ | import std.range : iota; | | // 0 1 2 3 4 5 1| auto r1 = iota(0, 6); | // 1 2 3 4 5 6 1| auto r2 = iota(1, 7); | 1| assert(cmp(r1, r1) == 0); 1| assert(cmp(r1, r2) < 0); 1| assert(cmp!"a >= b"(r1, r2) > 0); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | | // 0 1 2 | // 3 4 5 1| auto sl1 = iota(2, 3); | // 1 2 3 | // 4 5 6 1| auto sl2 = iota([2, 3], 1); | 1| assert(cmp(sl1, sl1) == 0); 1| assert(cmp(sl1, sl2) < 0); 1| assert(cmp!"a >= b"(sl1, sl2) > 0); |} | |@safe pure nothrow @nogc |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | 1| auto sl1 = iota(2, 3); 1| auto sl2 = iota([2, 3], 1); | 1| assert(cmp(sl1[0 .. $ - 1], sl1) < 0); 1| assert(cmp(sl1, sl1[0 .. $, 0 .. $ - 1]) > 0); | 1| assert(cmp(sl1[0 .. $ - 2], sl1) < 0); 1| assert(cmp(sl1, sl1[0 .. $, 0 .. $ - 3]) > 0); 1| assert(cmp(sl1[0 .. $, 0 .. $ - 3], sl1[0 .. $, 0 .. $ - 3]) == 0); 1| assert(cmp(sl1[0 .. $, 0 .. $ - 3], sl1[0 .. $ - 1, 0 .. $ - 3]) > 0); 1| assert(cmp(sl1[0 .. $ - 1, 0 .. $ - 3], sl1[0 .. $, 0 .. $ - 3]) < 0); |} | |size_t countImpl(alias fun, Slices...)(Slices slices) |{ 13| size_t ret; | alias S = Slices[0]; | import mir.functional: naryFun; | import mir.ndslice.iterator: FieldIterator, RetroIterator; | import mir.ndslice.field: BitField; | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) | { 7| ret = BitSliceAccelerator!(Field, I)(slices[0]).ctpop; | } | else | static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) | { | // pragma(msg, S); | import mir.ndslice.topology: retro; 1| ret = .countImpl!fun(lightScope(slices[0]).retro); | } | else | do | { | static if (DimensionCount!(Slices[0]) == 1) | { 408| if(mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) 23| ret++; | } | else | ret += .countImpl!fun(frontOf2!slices); 408| foreach_reverse(ref slice; slices) 408| slice.popFront; | } 408| while(!slices[0].empty); 13| return ret; |} | |/++ |Returns: max length across all dimensions. |+/ |size_t maxLength(S)(auto ref scope S s) | if (hasShape!S) |{ 81| auto shape = s.shape; 81| size_t length = 0; | foreach(i; Iota!(shape.length)) 87| if (shape[i] > length) 85| length = shape[i]; 81| return length; |} | |/++ |The call `eachLower!(fun)(slice1, ..., sliceN)` evaluates `fun` on the lower |triangle in `slice1, ..., sliceN` respectively. | |`eachLower` allows iterating multiple slices in the lockstep. | |Params: | fun = A function |See_Also: | This is functionally similar to $(LREF each). |+/ |template eachLower(alias fun) |{ | import mir.functional : naryFun; | | static if (__traits(isSame, naryFun!fun, fun)) | { | /++ | Params: | inputs = One or more two-dimensional slices and an optional | integer, `k`. | | The value `k` determines which diagonals will have the function | applied: | For k = 0, the function is also applied to the main diagonal | For k = 1 (default), only the non-main diagonals below the main | diagonal will have the function applied. | For k > 1, fewer diagonals below the main diagonal will have the | function applied. | For k < 0, more diagonals above the main diagonal will have the | function applied. | +/ | void eachLower(Inputs...)(scope Inputs inputs) | if (((Inputs.length > 1) && | (isIntegral!(Inputs[$ - 1]))) || | (Inputs.length)) | { | import mir.ndslice.traits : isMatrix; | 19| size_t val; | | static if ((Inputs.length > 1) && (isIntegral!(Inputs[$ - 1]))) | { 14| immutable(sizediff_t) k = inputs[$ - 1]; | alias Slices = Inputs[0..($ - 1)]; | alias slices = inputs[0..($ - 1)]; | } | else | { | enum sizediff_t k = 1; | alias Slices = Inputs; | alias slices = inputs; | } | | static assert (allSatisfy!(isMatrix, Slices), | "eachLower: Every slice input must be a two-dimensional slice"); | static if (Slices.length > 1) 1| slices.checkShapesMatch; 19| if (slices[0].anyEmpty) 0000000| return; | 20| foreach(ref slice; slices) 20| assert(!slice.empty); | 19| immutable(size_t) m = slices[0].length!0; 19| immutable(size_t) n = slices[0].length!1; | 19| if ((n + k) < m) | { 6| val = m - (n + k); 6| .eachImpl!fun(selectBackOf!(val, slices)); | } | 19| size_t i; | 19| if (k > 0) | { 9| foreach(ref slice; slices) 9| slice.popFrontExactly!0(k); 8| i = k; | } | | do | { 41| val = i - k + 1; 41| .eachImpl!fun(frontSelectFrontOf!(val, slices)); | 43| foreach(ref slice; slices) 43| slice.popFront!0; 41| i++; 71| } while ((i < (n + k)) && (i < m)); | } | } | else | { | alias eachLower = .eachLower!(naryFun!fun); | } |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota, canonical, universal; | alias AliasSeq(T...) = T; | | pure nothrow | void test(alias func)() | { | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 3| auto m = func(iota([3, 3], 1).slice); 3| m.eachLower!"a = 0"(0); 3| assert(m == [ | [0, 2, 3], | [0, 0, 6], | [0, 0, 0]]); | } | | @safe pure nothrow @nogc | T identity(T)(T x) | { 1| return x; | } | | alias kinds = AliasSeq!(identity, canonical, universal); 1| test!(kinds[0]); 1| test!(kinds[1]); 1| test!(kinds[2]); |} | |/// |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachLower!"a = 0"; 1| assert(m == [ | [1, 2, 3], | [0, 5, 6], | [0, 0, 9]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachLower!"a = 0"(-1); 1| assert(m == [ | [0, 0, 3], | [0, 0, 0], | [0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachLower!"a = 0"(2); 1| assert(m == [ | [1, 2, 3], | [4, 5, 6], | [0, 8, 9]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachLower!"a = 0"(-2); 1| assert(m == [ | [0, 0, 0], | [0, 0, 0], | [0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachLower!"a = 0"(0); 1| assert(m == [ | [0, 2, 3, 4], | [0, 0, 7, 8], | [0, 0, 0, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachLower!"a = 0"; 1| assert(m == [ | [1, 2, 3, 4], | [0, 6, 7, 8], | [0, 0, 11, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachLower!"a = 0"(-1); 1| assert(m == [ | [0, 0, 3, 4], | [0, 0, 0, 8], | [0, 0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachLower!"a = 0"(2); 1| assert(m == [ | [1, 2, 3, 4], | [5, 6, 7, 8], | [0, 10, 11, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachLower!"a = 0"(-2); 1| assert(m == [ | [0, 0, 0, 4], | [0, 0, 0, 0], | [0, 0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachLower!"a = 0"(0); 1| assert(m == [ | [0, 2, 3], | [0, 0, 6], | [0, 0, 0], | [0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachLower!"a = 0"; 1| assert(m == [ | [1, 2, 3], | [0, 5, 6], | [0, 0, 9], | [0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachLower!"a = 0"(-1); 1| assert(m == [ | [0, 0, 3], | [0, 0, 0], | [0, 0, 0], | [0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachLower!"a = 0"(2); 1| assert(m == [ | [1, 2, 3], | [4, 5, 6], | [0, 8, 9], | [0, 0, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachLower!"a = 0"(-2); 1| assert(m == [ | [0, 0, 0], | [0, 0, 0], | [0, 0, 0], | [0, 0, 0]]); |} | |/// Swap two slices |pure nothrow |version(mir_test) unittest |{ | import mir.utility : swap; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | //| 0 1 2 | | //| 3 4 5 | | //| 6 7 8 | 1| auto a = iota([3, 3]).as!double.slice; | //| 10 11 12 | | //| 13 14 15 | | //| 16 17 18 | 1| auto b = iota([3, 3], 10).as!double.slice; | 1| eachLower!swap(a, b); | 1| assert(a == [ | [ 0, 1, 2], | [13, 4, 5], | [16, 17, 8]]); 1| assert(b == [ | [10, 11, 12], | [ 3, 14, 15], | [ 6, 7, 18]]); |} | |/// Swap two zipped slices |pure nothrow |version(mir_test) unittest |{ | import mir.utility : swap; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, zip, iota; | | //| 0 1 2 | | //| 3 4 5 | | //| 6 7 8 | 1| auto a = iota([3, 3]).as!double.slice; | //| 10 11 12 | | //| 13 14 15 | | //| 16 17 18 | 1| auto b = iota([3, 3], 10).as!double.slice; | 1| auto z = zip(a, b); | 7| z.eachLower!(z => swap(z.a, z.b)); | 1| assert(a == [ | [ 0, 1, 2], | [13, 4, 5], | [16, 17, 8]]); 1| assert(b == [ | [10, 11, 12], | [ 3, 14, 15], | [ 6, 7, 18]]); |} | |/++ |The call `eachUpper!(fun)(slice1, ..., sliceN)` evaluates `fun` on the upper |triangle in `slice1, ..., sliceN`, respectively. | |`eachUpper` allows iterating multiple slices in the lockstep. | |Params: | fun = A function |See_Also: | This is functionally similar to $(LREF each). |+/ |template eachUpper(alias fun) |{ | import mir.functional: naryFun; | | static if (__traits(isSame, naryFun!fun, fun)) | { | /++ | Params: | inputs = One or more two-dimensional slices and an optional | integer, `k`. | | The value `k` determines which diagonals will have the function | applied: | For k = 0, the function is also applied to the main diagonal | For k = 1 (default), only the non-main diagonals above the main | diagonal will have the function applied. | For k > 1, fewer diagonals below the main diagonal will have the | function applied. | For k < 0, more diagonals above the main diagonal will have the | function applied. | +/ | void eachUpper(Inputs...)(scope Inputs inputs) | if (((Inputs.length > 1) && | (isIntegral!(Inputs[$ - 1]))) || | (Inputs.length)) | { | import mir.ndslice.traits : isMatrix; | 19| size_t val; | | static if ((Inputs.length > 1) && (isIntegral!(Inputs[$ - 1]))) | { 14| immutable(sizediff_t) k = inputs[$ - 1]; | alias Slices = Inputs[0..($ - 1)]; | alias slices = inputs[0..($ - 1)]; | } | else | { | enum sizediff_t k = 1; | alias Slices = Inputs; | alias slices = inputs; | } | | static assert (allSatisfy!(isMatrix, Slices), | "eachUpper: Every slice input must be a two-dimensional slice"); | static if (Slices.length > 1) 1| slices.checkShapesMatch; 19| if (slices[0].anyEmpty) 0000000| return; | 20| foreach(ref slice; slices) 20| assert(!slice.empty); | 19| immutable(size_t) m = slices[0].length!0; 19| immutable(size_t) n = slices[0].length!1; | 19| size_t i; | 19| if (k < 0) | { 6| val = -k; 6| .eachImpl!fun(selectFrontOf!(val, slices)); | 6| foreach(ref slice; slices) 6| slice.popFrontExactly!0(-k); 6| i = -k; | } | | do | { 41| val = (n - k) - i; 41| .eachImpl!fun(frontSelectBackOf!(val, slices)); | 43| foreach(ref slice; slices) 43| slice.popFront; 41| i++; 69| } while ((i < (n - k)) && (i < m)); | } | } | else | { | alias eachUpper = .eachUpper!(naryFun!fun); | } |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota, canonical, universal; | | pure nothrow | void test(alias func)() | { | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 3| auto m = func(iota([3, 3], 1).slice); 3| m.eachUpper!"a = 0"(0); 3| assert(m == [ | [0, 0, 0], | [4, 0, 0], | [7, 8, 0]]); | } | | @safe pure nothrow @nogc | T identity(T)(T x) | { 1| return x; | } | | alias kinds = AliasSeq!(identity, canonical, universal); 1| test!(kinds[0]); 1| test!(kinds[1]); 1| test!(kinds[2]); |} | |/// |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachUpper!"a = 0"; 1| assert(m == [ | [1, 0, 0], | [4, 5, 0], | [7, 8, 9]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachUpper!"a = 0"(-1); 1| assert(m == [ | [0, 0, 0], | [0, 0, 0], | [7, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachUpper!"a = 0"(2); 1| assert(m == [ | [1, 2, 0], | [4, 5, 6], | [7, 8, 9]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | 1| auto m = iota([3, 3], 1).slice; 1| m.eachUpper!"a = 0"(-2); 1| assert(m == [ | [0, 0, 0], | [0, 0, 0], | [0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachUpper!"a = 0"(0); 1| assert(m == [ | [0, 0, 0, 0], | [5, 0, 0, 0], | [9, 10, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachUpper!"a = 0"; 1| assert(m == [ | [1, 0, 0, 0], | [5, 6, 0, 0], | [9, 10, 11, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachUpper!"a = 0"(-1); 1| assert(m == [ | [0, 0, 0, 0], | [0, 0, 0, 0], | [9, 0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachUpper!"a = 0"(2); 1| assert(m == [ | [1, 2, 0, 0], | [5, 6, 7, 0], | [9, 10, 11, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 4 | | //| 5 6 7 8 | | //| 9 10 11 12 | 1| auto m = iota([3, 4], 1).slice; 1| m.eachUpper!"a = 0"(-2); 1| assert(m == [ | [0, 0, 0, 0], | [0, 0, 0, 0], | [0, 0, 0, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachUpper!"a = 0"(0); 1| assert(m == [ | [0, 0, 0], | [4, 0, 0], | [7, 8, 0], | [10, 11, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachUpper!"a = 0"; 1| assert(m == [ | [1, 0, 0], | [4, 5, 0], | [7, 8, 9], | [10, 11, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachUpper!"a = 0"(-1); 1| assert(m == [ | [0, 0, 0], | [0, 0, 0], | [7, 0, 0], | [10, 11, 0]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachUpper!"a = 0"(2); 1| assert(m == [ | [1, 2, 0], | [4, 5, 6], | [7, 8, 9], | [10, 11, 12]]); |} | |pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | //| 1 2 3 | | //| 4 5 6 | | //| 7 8 9 | | //| 10 11 12 | 1| auto m = iota([4, 3], 1).slice; 1| m.eachUpper!"a = 0"(-2); 1| assert(m == [ | [0, 0, 0], | [0, 0, 0], | [0, 0, 0], | [10, 0, 0]]); |} | |/// Swap two slices |pure nothrow |version(mir_test) unittest |{ | import mir.utility : swap; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, iota; | | //| 0 1 2 | | //| 3 4 5 | | //| 6 7 8 | 1| auto a = iota([3, 3]).as!double.slice; | //| 10 11 12 | | //| 13 14 15 | | //| 16 17 18 | 1| auto b = iota([3, 3], 10).as!double.slice; | 1| eachUpper!swap(a, b); | 1| assert(a == [ | [0, 11, 12], | [3, 4, 15], | [6, 7, 8]]); 1| assert(b == [ | [10, 1, 2], | [13, 14, 5], | [16, 17, 18]]); |} | |/// Swap two zipped slices |pure nothrow |version(mir_test) unittest |{ | import mir.utility : swap; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : as, zip, iota; | | //| 0 1 2 | | //| 3 4 5 | | //| 6 7 8 | 1| auto a = iota([3, 3]).as!double.slice; | //| 10 11 12 | | //| 13 14 15 | | //| 16 17 18 | 1| auto b = iota([3, 3], 10).as!double.slice; | 1| auto z = zip(a, b); | 7| z.eachUpper!(z => swap(z.a, z.b)); | 1| assert(a == [ | [0, 11, 12], | [3, 4, 15], | [6, 7, 8]]); 1| assert(b == [ | [10, 1, 2], | [13, 14, 5], | [16, 17, 18]]); |} | |// uniq |/** |Lazily iterates unique consecutive elements of the given range (functionality |akin to the $(HTTP wikipedia.org/wiki/_Uniq, _uniq) system |utility). Equivalence of elements is assessed by using the predicate |$(D pred), by default $(D "a == b"). The predicate is passed to |$(REF nary, mir,functional), and can either accept a string, or any callable |that can be executed via $(D pred(element, element)). If the given range is |bidirectional, $(D uniq) also yields a |`std,range,primitives`. |Params: | pred = Predicate for determining equivalence between range elements. |*/ |template uniq(alias pred = "a == b") |{ | static if (__traits(isSame, naryFun!pred, pred)) | { | /++ | Params: | r = An input range of elements to filter. | Returns: | An input range of | consecutively unique elements in the original range. If `r` is also a | forward range or bidirectional range, the returned range will be likewise. | +/ | Uniq!(naryFun!pred, Range) uniq(Range)(Range r) | if (isInputRange!Range && !isSlice!Range) | { | import core.lifetime: move; 4| return typeof(return)(r.move); | } | | /// ditto | auto uniq(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { | import mir.ndslice.topology: flattened; | import core.lifetime: move; 4| auto r = slice.move.flattened; 4| return Uniq!(pred, typeof(r))(move(r)); | } | } | else | alias uniq = .uniq!(naryFun!pred); |} | |/// |@safe version(mir_test) unittest |{ 1| int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; 1| assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ])); | | import std.algorithm.mutation : copy; | // Filter duplicates in-place using copy 1| arr.length -= arr.uniq.copy(arr).length; 1| assert(arr == [ 1, 2, 3, 4, 5 ]); | | // Note that uniqueness is only determined consecutively; duplicated | // elements separated by an intervening different element will not be | // eliminated: 1| assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1])); |} | |/// N-dimensional case |version(mir_test) |@safe pure unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: byDim, map, iota; | 1| auto matrix = [ [1, 2, 2], [2, 2, 3], [4, 4, 4] ].fuse; | 1| assert(matrix.uniq.equal([ 1, 2, 3, 4 ])); | | // unique elements for each row 1| assert(matrix.byDim!0.map!uniq.equal!equal([ [1, 2], [2, 3], [4] ])); |} | |/++ |Authros: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilia Ki (betterC rework) |+/ |struct Uniq(alias pred, Range) |{ | Range _input; | | void popFront() scope | { 178| assert(!empty, "Attempting to popFront an empty uniq."); 178| auto last = _input.front; | do | { 266| _input.popFront(); | } 499| while (!_input.empty && pred(last, _input.front)); | } | | auto ref front() @property | { 79| assert(!empty, "Attempting to fetch the front of an empty uniq."); 79| return _input.front; | } | | import std.range.primitives: isBidirectionalRange; | | static if (isBidirectionalRange!Range) | { | void popBack() scope | { 0000000| assert(!empty, "Attempting to popBack an empty uniq."); 0000000| auto last = _input.back; | do | { 0000000| _input.popBack(); | } 0000000| while (!_input.empty && pred(last, _input.back)); | } | | auto ref back() return scope @property | { 0000000| assert(!empty, "Attempting to fetch the back of an empty uniq."); 0000000| return _input.back; | } | } | | static if (isInfinite!Range) | { | enum bool empty = false; // Propagate infiniteness. | } | else | { 447| @property bool empty() const { return _input.empty; } | } | | ref opIndex()() scope return | { 1| return this; | } | | auto opIndex()() const return scope | { | return Filter!(typeof(_input[]))(_input[]); | } | | import std.range.primitives: isForwardRange; | | static if (isForwardRange!Range) | { | @property typeof(this) save() return scope | { 0000000| return typeof(this)(_input.save); | } | } |} | |version(none) |@safe version(mir_test) unittest |{ | import std.internal.test.dummyrange; | import std.range; | | int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; | auto r = uniq(arr); | static assert(isForwardRange!(typeof(r))); | | assert(equal(r, [ 1, 2, 3, 4, 5 ][])); | assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][]))); | | foreach (DummyType; AllDummyRanges) | { | DummyType d; | auto u = uniq(d); | assert(equal(u, [1,2,3,4,5,6,7,8,9,10])); | | static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u))); | | static if (d.rt >= RangeType.Bidirectional) | { | assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1])); | } | } |} | |@safe version(mir_test) unittest // https://issues.dlang.org/show_bug.cgi?id=17264 |{ 1| const(int)[] var = [0, 1, 1, 2]; 1| assert(var.uniq.equal([0, 1, 2])); |} | |@safe version(mir_test) unittest { | import mir.ndslice.allocation; | import mir.math.common: approxEqual; 2| auto x = rcslice!double(2); 2| auto y = rcslice!double(2); 1| x[] = [2, 3]; 1| y[] = [2, 3]; 1| assert(equal!approxEqual(x,y)); |} | |/++ |Implements the higher order filter function. The predicate is passed to |`mir.functional.naryFun`, and can either accept a string, or any callable |that can be executed via `pred(element)`. |Params: | pred = Function to apply to each element of range |Returns: | `filter!(pred)(range)` returns a new range containing only elements `x` in `range` for | which `pred(x)` returns `true`. |See_Also: | $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) |Note: | $(RED User and library code MUST call `empty` method ahead each call of pair or one of `front` and `popFront` methods.) |+/ |template filter(alias pred = "a") |{ | static if (__traits(isSame, naryFun!pred, pred)) | { | /++ | Params: | r = An input range of elements to filter. | Returns: | A new range containing only elements `x` in `range` for which `predicate(x)` returns `true`. | +/ | Filter!(naryFun!pred, Range) filter(Range)(Range r) | if (isInputRange!Range && !isSlice!Range) | { | import core.lifetime: move; 7| return typeof(return)(r.move); | } | | /// ditto | auto filter(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { | import mir.ndslice.topology: flattened; | import core.lifetime: move; 20| auto r = slice.move.flattened; 20| return Filter!(pred, typeof(r))(move(r)); | } | } | else | alias filter = .filter!(naryFun!pred); |} | |/// ditto |struct Filter(alias pred, Range) |{ | Range _input; | version(assert) bool _freshEmpty; | | void popFront() scope | { 61| assert(!_input.empty, "Attempting to popFront an empty Filter."); 61| version(assert) assert(_freshEmpty, "Attempting to pop the front of a Filter without calling '.empty' method ahead."); 61| version(assert) _freshEmpty = false; 61| _input.popFront; | } | | auto ref front() @safe @property return scope | { 61| assert(!_input.empty, "Attempting to fetch the front of an empty Filter."); 61| version(assert) assert(_freshEmpty, "Attempting to fetch the front of a Filter without calling '.empty' method ahead."); 61| return _input.front; | } | | bool empty() @safe scope @property | { 112| version(assert) _freshEmpty = true; | for (;;) | { 159| if (auto r = _input.empty) 27| return true; 132| if (pred(_input.front)) 85| return false; 47| _input.popFront; | } | } | | import std.range.primitives: isForwardRange; | static if (isForwardRange!Range) | { | @property typeof(this) save() return scope | { 0000000| return typeof(this)(_input.save); | } | } | | ref opIndex()() scope return | { | return this; | } | | auto opIndex()() const return scope | { | return Filter!(pred, typeof(_input[]))(_input[]); | } |} | |/// |version(mir_test) |@safe pure nothrow unittest |{ 1| int[] arr = [ 0, 1, 2, 3, 4, 5 ]; | | // Filter below 3 7| auto small = filter!(a => a < 3)(arr); 1| assert(equal(small, [ 0, 1, 2 ])); | | // Filter again, but with Uniform Function Call Syntax (UFCS) 7| auto sum = arr.filter!(a => a < 3); 1| assert(equal(sum, [ 0, 1, 2 ])); | | // Filter with the default predicate 1| auto nonZeros = arr.filter; 1| assert(equal(nonZeros, [ 1, 2, 3, 4, 5 ])); | | // In combination with concatenation() to span multiple ranges | import mir.ndslice.concatenation; | 1| int[] a = [ 3, -2, 400 ]; 1| int[] b = [ 100, -101, 102 ]; 7| auto r = concatenation(a, b).filter!(a => a > 0); 1| assert(equal(r, [ 3, 400, 100, 102 ])); | | // Mixing convertible types is fair game, too 1| double[] c = [ 2.5, 3.0 ]; 9| auto r1 = concatenation(c, a, b).filter!(a => cast(int) a != a); 1| assert(equal(r1, [ 2.5 ])); |} | |/// N-dimensional filtering |version(mir_test) |@safe pure unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: byDim, map; | 1| auto matrix = | [[ 3, -2, 400 ], | [ 100, -101, 102 ]].fuse; | | alias filterPositive = filter!"a > 0"; | | // filter all elements in the matrix 1| auto r = filterPositive(matrix); 1| assert(equal(r, [ 3, 400, 100, 102 ])); | | // filter all elements for each row 1| auto rr = matrix.byDim!0.map!filterPositive; 1| assert(equal!equal(rr, [ [3, 400], [100, 102] ])); | | // filter all elements for each column 1| auto rc = matrix.byDim!1.map!filterPositive; 1| assert(equal!equal(rc, [ [3, 100], [], [400, 102] ])); |} | |/// N-dimensional filtering based on value in specific row/column |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: byDim; | 1| auto matrix = | [[ 3, 2, 400 ], | [ 100, -101, 102 ]].fuse; | | // filter row based on value in index 1 1| auto r1 = matrix.byDim!0.filter!("a[1] > 0"); 1| assert(equal!equal(r1, [ [3, 2, 400] ])); | | // filter column based on value in index 1 1| auto r2 = matrix.byDim!1.filter!("a[1] > 0"); 1| assert(equal!equal(r2, [ [3, 100], [400, 102] ])); |} | |/// Filter out NaNs |version(mir_test) |@safe pure nothrow |unittest { | import mir.algorithm.iteration: equal, filter; | import mir.ndslice.slice: sliced; | import std.math.traits: isNaN; | | static immutable result1 = [1.0, 2]; | 1| double x; 1| auto y = [1.0, 2, x].sliced; 4| auto z = y.filter!(a => !isNaN(a)); 1| assert(z.equal(result1)); |} | |/// Filter out NaNs by row and by column |version(mir_test) |@safe pure |unittest { | import mir.algorithm.iteration: equal, filter; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: byDim, map; | import std.math.traits: isNaN; | | static immutable result1 = [[1.0, 2], [3.0, 4, 5]]; | static immutable result2 = [[1.0, 3], [2.0, 4], [5.0]]; | 1| double x; 1| auto y = [[1.0, 2, x], [3.0, 4, 5]].fuse; | | // by row 7| auto z1 = y.byDim!0.map!(filter!(a => !isNaN(a))); 1| assert(z1.equal!equal(result1)); | // by column 7| auto z2 = y.byDim!1.map!(filter!(a => !isNaN(a))); 1| assert(z2.equal!equal(result2)); |} | |/// Filter entire rows/columns that have NaNs |version(mir_test) |@safe pure |unittest { | import mir.algorithm.iteration: equal, filter; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: byDim, map; | import std.math.traits: isNaN; | | static immutable result1 = [[3.0, 4, 5]]; | static immutable result2 = [[1.0, 3], [2.0, 4]]; | 1| double x; 1| auto y = [[1.0, 2, x], [3.0, 4, 5]].fuse; | | // by row 9| auto z1 = y.byDim!0.filter!(a => a.all!(b => !isNaN(b))); 1| assert(z1.equal!equal(result1)); | // by column 9| auto z2 = y.byDim!1.filter!(a => a.all!(b => !isNaN(b))); 1| assert(z2.equal!equal(result2)); |} | |/++ |Implements the higher order filter and map function. The predicate and map functions are passed to |`mir.functional.naryFun`, and can either accept a string, or any callable |that can be executed via `pred(element)` and `map(element)`. |Params: | pred = Filter function to apply to each element of range (optional) | map = Map function to apply to each element of range |Returns: | `rcfilter!(pred)(range)` returns a new RCArray containing only elements `map(x)` in `range` for | which `pred(x)` returns `true`. |See_Also: | $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) |+/ |template rcfilter(alias pred = "a", alias map = "a") |{ | static if (__traits(isSame, naryFun!pred, pred) && __traits(isSame, naryFun!map, map)) | { | /++ | Params: | r = An input range of elements to filter. | Returns: | A new range containing only elements `x` in `range` for which `predicate(x)` returns `true`. | +/ | auto rcfilter(Range)(Range r) | if (isIterable!Range && (!isSlice!Range || DimensionCount!Range == 1)) | { | import core.lifetime: forward; | import mir.appender: scopedBuffer; | import mir.primitives: isInputRange; | import mir.rc.array: RCArray; | | alias T = typeof(map(r.front)); 8| auto buffer = scopedBuffer!T; 80| foreach (ref e; r) | { 24| if (pred(e)) | { | static if (__traits(isSame, naryFun!"a", map)) 6| buffer.put(forward!e); | else 6| buffer.put(map(forward!e)); | } | } 4| return () @trusted | { 4| auto ret = RCArray!T(buffer.length); 4| buffer.moveDataAndEmplaceTo(ret[]); 4| return ret; | } (); | } | | /// ditto | auto rcfilter(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | if (N > 1) | { | import mir.ndslice.topology: flattened; | import core.lifetime: move; 2| return rcfilter(slice.move.flattened); | } | } | else | alias rcfilter = .rcfilter!(naryFun!pred, naryFun!map); |} | |/// |version(mir_test) |@safe pure nothrow @nogc unittest |{ | import mir.ndslice.topology: iota; | 1| auto val = 3; 1| auto factor = 5; | // Filter iota 2x3 matrix below 3 7| assert(iota(2, 3).rcfilter!(a => a < val).moveToSlice.equal(val.iota)); | // Filter and map below 3 10| assert(6.iota.rcfilter!(a => a < val, a => a * factor).moveToSlice.equal(val.iota * factor)); |} | |version(mir_test) |@safe pure nothrow @nogc unittest |{ | import mir.ndslice.topology: iota, as; | 1| auto val = 3; 1| auto factor = 5; | // Filter iota 2x3 matrix below 3 7| assert(iota(2, 3).as!(const int).rcfilter!(a => a < val).moveToSlice.equal(val.iota)); | // Filter and map below 3 10| assert(6.iota.as!(immutable int).rcfilter!(a => a < val, a => a * factor).moveToSlice.equal(val.iota * factor)); |} | |/++ |Implements the homonym function (also known as `accumulate`, $(D |compress), `inject`, or `foldl`) present in various programming |languages of functional flavor. The call `fold!(fun)(slice, seed)` |first assigns `seed` to an internal variable `result`, |also called the accumulator. Then, for each element `x` in $(D |slice), `result = fun(result, x)` gets evaluated. Finally, $(D |result) is returned. | |Params: | fun = the predicate function to apply to the elements | |See_Also: | $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) | $(LREF sum) is similar to `fold!((a, b) => a + b)` that offers | precise summing of floating point numbers. | This is functionally equivalent to $(LREF reduce) with the argument order | reversed. |+/ |template fold(alias fun) |{ | /++ | Params: | slice = A slice, range, and array. | seed = An initial accumulation value. | Returns: | the accumulated result | +/ | @optmath auto fold(Slice, S)(scope Slice slice, S seed) | { | import core.lifetime: move; 9| return reduce!fun(seed, slice.move); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: map; | 1| auto arr = [1, 2, 3, 4, 5].sliced; | | // Sum all elements 6| assert(arr.fold!((a, b) => a + b)(0) == 15); 6| assert(arr.fold!((a, b) => a + b)(6) == 21); | | // Can be used in a UFCS chain 11| assert(arr.map!(a => a + 1).fold!((a, b) => a + b)(0) == 20); | | // Return the last element of any range 6| assert(arr.fold!((a, b) => b)(0) == 5); |} | |/// Works for matrices |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | 1| auto arr = [ | [1, 2, 3], | [4, 5, 6] | ].fuse; | 7| assert(arr.fold!((a, b) => a + b)(0) == 21); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.topology: map; | 1| int[] arr = [1, 2, 3, 4, 5]; | | // Sum all elements 6| assert(arr.fold!((a, b) => a + b)(0) == 15); 6| assert(arr.fold!((a, b) => a + b)(6) == 21); | | // Can be used in a UFCS chain 11| assert(arr.map!(a => a + 1).fold!((a, b) => a + b)(0) == 20); | | // Return the last element of any range 6| assert(arr.fold!((a, b) => b)(0) == 5); |} | |version(mir_test) |@safe pure nothrow |unittest |{ 1| int[] arr = [1]; | static assert(!is(typeof(arr.fold!()(0)))); | static assert(!is(typeof(arr.fold!(a => a)(0)))); | static assert(is(typeof(arr.fold!((a, b) => a)(0)))); 1| assert(arr.length == 1); |} | |version(mir_test) |unittest |{ | import mir.rc.array: RCArray; | import mir.algorithm.iteration: minmaxPos, minPos, maxPos, minmaxIndex, minIndex, maxIndex; | | static immutable a = [0.0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; | 2| auto x = RCArray!double(12); 51| foreach(i, ref e; x) 12| e = a[i]; 2| auto y = x.asSlice; 2| auto z0 = y.minmaxPos; 2| auto z1 = y.minPos; 2| auto z2 = y.maxPos; 1| auto z3 = y.minmaxIndex; 1| auto z4 = y.minIndex; 1| auto z5 = y.maxIndex; |} source/mir/algorithm/iteration.d is 87% covered <<<<<< EOF # path=source-mir-algorithm-setops.lst |// Written in the D programming language. |/** |This is a submodule of $(MREF mir, algorithm). It contains `nothrow` `@nogc` BetterC alternative to `MultiwayMerge` from `std.algorithm.setops`. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments | |Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilia Ki (Mir & BetterC rework, optimization). | */ |module mir.algorithm.setops; | |import core.lifetime: move; |import mir.functional: naryFun; |import mir.primitives; |import mir.qualifier; |import std.range.primitives: isRandomAccessRange; | |/** |Merges multiple sets. The input sets are passed as a |range of ranges and each is assumed to be sorted by $(D |less). Computation is done lazily, one union element at a time. The |complexity of one $(D popFront) operation is $(BIGOH |log(ror.length)). However, the length of $(D ror) decreases as ranges |in it are exhausted, so the complexity of a full pass through $(D |MultiwayMerge) is dependent on the distribution of the lengths of ranges |contained within $(D ror). If all ranges have the same length $(D n) |(worst case scenario), the complexity of a full pass through $(D |MultiwayMerge) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D |log(ror.length)) times worse than just spanning all ranges in |turn. The output comes sorted (unstably) by $(D less). |The length of the resulting range is the sum of all lengths of |the ranges passed as input. This means that all elements (duplicates |included) are transferred to the resulting range. |For backward compatibility, `multiwayMerge` is available under |the name `nWayUnion` and `MultiwayMerge` under the name of `NWayUnion` . |Future code should use `multiwayMerge` and `MultiwayMerge` as `nWayUnion` |and `NWayUnion` will be deprecated. |Params: | less = Predicate the given ranges are sorted by. | ror = A range of ranges sorted by `less` to compute the union for. |Returns: | A range of the union of the ranges in `ror`. |Warning: Because $(D MultiwayMerge) does not allocate extra memory, it |will leave $(D ror) modified. Namely, $(D MultiwayMerge) assumes ownership |of $(D ror) and discretionarily swaps and advances elements of it. If |you want $(D ror) to preserve its contents after the call, you may |want to pass a duplicate to $(D MultiwayMerge) (and perhaps cache the |duplicate in between calls). | */ |struct MultiwayMerge(alias less, RangeOfRanges) | if (isRandomAccessRange!RangeOfRanges) |{ | import mir.primitives; | import mir.container.binaryheap; | | /// | @disable this(); | /// | @disable this(this); | | /// | static bool compFront(ElementType!RangeOfRanges a, ElementType!RangeOfRanges b) | { | // revert comparison order so we get the smallest elements first 378| return less(b.front, a.front); | } | | /// Heap | BinaryHeap!(compFront, RangeOfRanges) _heap; | | /// 27| this(RangeOfRanges ror) | { | // Preemptively get rid of all empty ranges in the input | // No need for stability either 27| auto temp = ror.lightScope; 99| for (;!temp.empty;) | { 72| if (!temp.front.empty) | { 72| temp.popFront; 72| continue; | } | import mir.utility: swap; 0000000| swap(temp.back, temp.front); 0000000| temp.popBack; 0000000| ror.popBack; | } | //Build the heap across the range 27| _heap = typeof(_heap)(ror.move); | } | | /// 1016| @property bool empty() scope const { return _heap.empty; } | | /// | @property auto ref front() | { 417| assert(!empty); 417| return _heap.front.front; | } | | /// | void popFront() scope @safe | { 239| _heap._store.front.popFront; 239| if (!_heap._store.front.empty) 167| _heap.siftDown(_heap._store[], 0, _heap._length); | else 72| _heap.removeFront; | } |} | |/// Ditto |MultiwayMerge!(naryFun!less, RangeOfRanges) multiwayMerge |(alias less = "a < b", RangeOfRanges) |(RangeOfRanges ror) |{ 27| return typeof(return)(move(ror)); |} | |/// |@safe nothrow @nogc version(mir_test) unittest |{ | import mir.algorithm.iteration: equal; | | static a = | [ | [ 1, 4, 7, 8 ], | [ 1, 7 ], | [ 1, 7, 8], | [ 4 ], | [ 7 ], | ]; | static witness = [ | 1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8 | ]; 1| assert(a.multiwayMerge.equal(witness)); | | static b = | [ | // range with duplicates | [ 1, 1, 4, 7, 8 ], | [ 7 ], | [ 1, 7, 8], | [ 4 ], | [ 7 ], | ]; | // duplicates are propagated to the resulting range 1| assert(b.multiwayMerge.equal(witness)); |} | |/** |Computes the union of multiple ranges. The input ranges are passed |as a range of ranges and each is assumed to be sorted by $(D |less). Computation is done lazily, one union element at a time. |`multiwayUnion(ror)` is functionally equivalent to `multiwayMerge(ror).uniq`. |"The output of multiwayUnion has no duplicates even when its inputs contain duplicates." |Params: | less = Predicate the given ranges are sorted by. | ror = A range of ranges sorted by `less` to compute the intersection for. |Returns: | A range of the union of the ranges in `ror`. |See also: $(LREF multiwayMerge) | */ |auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) |{ | import mir.functional: not; | import mir.algorithm.iteration : Uniq; | 25| return Uniq!(not!less, typeof(multiwayMerge!less(ror)))(multiwayMerge!less(move(ror))); |} | |/// |@safe version(mir_test) unittest |{ | import mir.algorithm.iteration: equal; | | // sets 1| double[][] a = | [ | [ 1, 4, 7, 8 ], | [ 1, 7 ], | [ 1, 7, 8], | [ 4 ], | [ 7 ], | ]; | 1| auto witness = [1, 4, 7, 8]; 1| assert(a.multiwayUnion.equal(witness)); | | // multisets 1| double[][] b = | [ | [ 1, 1, 1, 4, 7, 8 ], | [ 1, 7 ], | [ 1, 7, 7, 8], | [ 4 ], | [ 7 ], | ]; 1| assert(b.multiwayUnion.equal(witness)); |} | |/++ |Computes the length of union of multiple ranges. The input ranges are passed |as a range of ranges and each is assumed to be sorted by `less`. | |Params: | less = Predicate the given ranges are sorted by. | ror = A range of ranges sorted by `less` to compute the intersection for. |Returns: | A length of the union of the ranges in `ror`. |+/ |pragma(inline, false) |size_t unionLength(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) |{ 17| size_t length; 17| auto u = move(ror).multiwayUnion!less; 17| if (!u.empty) do { 99| length++; 99| u.popFront; 99| } while(!u.empty); 17| return length; |} source/mir/algorithm/setops.d is 91% covered <<<<<< EOF # path=source-mir-annotated.lst |/++ |$(H1 Annotated value) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Macros: |+/ |module mir.annotated; | |import mir.internal.meta: basicElementType; |import mir.serde: serdeRegister, serdeAnnotation, serdeIsDynamicAlgebraic; | |static immutable excMsg = "At least one annotation is required to create an annotated value."; |version (D_Exceptions) | static immutable exc = new Exception(excMsg); | |/++ |A convenience definition of an annotated value. | |A structure that behaves like a recursive algebraic type should define `enum _serdeRecursiveAlgebraic;` member. |+/ |@serdeRegister |@serdeAnnotation |struct Annotated(T) { | /// | @serdeAnnotation | string[] annotations; | | static if (!(is(T == union) || is(T == struct))) | private enum _alloc_ = false; | else | static if (__traits(hasMember, T, "_serdeRecursiveAlgebraic")) | private enum _alloc_ = true; | else | { | import mir.algebraic: isVariant; | static if (isVariant!T) | private enum _alloc_ = true; | else | private enum _alloc_ = false; | } | | static if (_alloc_) | { | /// | private T* _value; | /// | ref inout(T) value() inout @property 6| in(_value) | { 6| return *_value; | } | | /// | ref T value(T value) @property return scope | { 0000000| if (_value is null) | { 0000000| _value = new T; | import core.lifetime: move; 0000000| *_value = move(value); | } 0000000| return *_value; | } | | /// | bool opEquals(scope const Annotated rhs) scope const | { 0000000| return annotations == rhs.annotations && value == rhs.value; | } | | /// ditto | bool opEquals(ref scope const Annotated rhs) scope const | { 4| return annotations == rhs.annotations && value == rhs.value; | } | | size_t toHash() scope @trusted const pure nothrow @nogc | { | static if (__traits(compiles, hashOf(value))) 0000000| return hashOf(value); | else | { | debug pragma(msg, "Mir warning: can't compute hash. Expexted `size_t toHash() scope @safe const pure nothrow @nogc` method for " ~ T.stringof); | return cast(size_t)_value; | } | } | } | else | { | /// | T value; | } | | | /++ | Params: | annotations = non-empty array of annotations | args = arguments to construct value with | +/ 5| this(Args...)(string[] annotations, Args args) @safe pure { 5| if (annotations.length == 0) | { | version (D_Exceptions) 0000000| throw exc; | else | assert(0, excMsg); | } | import core.lifetime: forward; 5| this.annotations = annotations; | static if (_alloc_) 3| this._value = new T(forward!args); | else | static if (__traits(compiles, value = args)) | this.value = args; | else | static if (is(T == class)) | this.value = new T(forward!args); | else 2| this.value = T(forward!args); | } | | private alias E = .basicElementType!T; | | import std.traits: isAssociativeArray, isAggregateType; | /// | int opCmp()(ref scope const typeof(this) rhs) scope const pure nothrow @nogc | if (!isAssociativeArray!E && (!isAggregateType!E || __traits(hasMember, E, "opCmp"))) | { 0000000| if (auto d = __cmp(annotations, rhs.annotations)) 0000000| return d; | | static if (__traits(compiles, __cmp(value, rhs.value))) | return __cmp(value, rhs.value); | else | static if (__traits(hasMember, value, "opCmp") && !is(T[i] == U*, U)) 0000000| return value.opCmp(rhs.value); | else | return value < rhs.value ? -1 : value > rhs.value ? +1 : 0; | } |} | |/// |version(mir_test) |unittest |{ 1| auto annotations = ["annotation"]; | static struct S {double x;} 1| auto as = Annotated!S(annotations, 5); 1| assert(as.annotations == annotations); 1| assert(as.value.x == 5); | | static struct C {double x;} 1| auto ac = Annotated!S(annotations, 5); 1| assert(ac.annotations == annotations); 1| assert(ac.value.x == 5); |} | |/// |version(mir_test) |unittest |{ | import mir.algebraic; 1| auto annotations = ["annotation"]; | static struct S {double x;} 1| auto as = Annotated!(Variant!S)(annotations, 5); 1| assert(as.annotations == annotations); 1| assert(as.value.x == 5); | | static struct C {double x;} 1| auto ac = Annotated!(Variant!S)(annotations, 5); 1| assert(ac.annotations == annotations); 1| assert(ac.value.x == 5); |} | |/++ |A convenience definition of an annotated value. | |A structure that behaves like a recursive algebraic type should define `enum _serdeRecursiveAlgebraic;` member. |+/ |@serdeRegister |@serdeAnnotation |struct AnnotatedOnce(T) { | /// | @serdeAnnotation | string annotation; | | static if (!(is(T == union) || is(T == struct))) | private enum _alloc_ = false; | else | static if (__traits(hasMember, T, "_serdeRecursiveAlgebraic")) | private enum _alloc_ = true; | else | { | import mir.algebraic: isVariant; | static if (isVariant!T) | private enum _alloc_ = true; | else | private enum _alloc_ = false; | } | | static if (_alloc_) | { | /// | private T* _value; | /// | ref inout(T) value() inout @property | { 2| return *_value; | } | | /// | ref T value(T value) @property | { 0000000| if (_value is null) | { 0000000| _value = new T; | import core.lifetime: move; 0000000| *_value = move(value); | } 0000000| return *_value; | } | | /// | bool opEquals(scope const AnnotatedOnce rhs) scope const | { 0000000| return annotation == rhs.annotation && value == rhs.value; | } | | /// | bool opEquals(ref scope const AnnotatedOnce rhs) scope const | { 0000000| return annotation == rhs.annotation && value == rhs.value; | } | } | else | { | /// | T value; | } | | | /++ | Params: | annotation = non-empty array of annotation | args = arguments to construct value with | +/ 4| this(Args...)(string annotation, Args args) @safe pure { | import core.lifetime: forward; 4| this.annotation = annotation; | static if (_alloc_) 2| this._value = new T(forward!args); | else | static if (__traits(compiles, value = args)) | this.value = args; | else | static if (is(T == class)) | this.value = new T(forward!args); | else 2| this.value = T(forward!args); | } | | private alias E = .basicElementType!T; | | import std.traits: isAssociativeArray, isAggregateType; | static if (!isAssociativeArray!E && (!isAggregateType!E || __traits(hasMember, E, "opCmp"))) | /// | int opCmp()(ref scope const typeof(this) rhs) scope const @safe pure nothrow @nogc | { | if (auto d = __cmp(annotation, rhs.annotation)) | return d; | | static if (__traits(compiles, __cmp(value, rhs.value))) | return __cmp(value, rhs.value); | else | static if (__traits(hasMember, value, "opCmp") && !is(T[i] == U*, U)) | return value.opCmp(rhs.value); | else | return value < rhs.value ? -1 : value > rhs.value ? +1 : 0; | } |} | |/// |version(mir_test) |unittest |{ 1| auto annotation = "annotation"; | static struct S {double x;} 1| auto as = AnnotatedOnce!S(annotation, 5); 1| assert(as.annotation == annotation); 1| assert(as.value.x == 5); | | static struct C {double x;} 1| auto ac = AnnotatedOnce!S(annotation, 5); 1| assert(ac.annotation == annotation); 1| assert(ac.value.x == 5); |} | |/// |version(mir_test) |unittest |{ | import mir.algebraic; 1| auto annotation = "annotation"; | static struct S {double x;} 1| auto as = AnnotatedOnce!(Variant!S)(annotation, 5); 1| assert(as.annotation == annotation); 1| assert(as.value.x == 5); | | static struct C {double x;} 1| auto ac = AnnotatedOnce!(Variant!S)(annotation, 5); 1| assert(ac.annotation == annotation); 1| assert(ac.value.x == 5); |} source/mir/annotated.d is 71% covered <<<<<< EOF # path=source-mir-appender.lst |/++ |$(H1 Scoped Buffer) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |+/ |module mir.appender; | |// import std.traits: isAssignable, hasElaborateDestructorhasElaborateCopyConstructor, hasElaborateAssign; |import mir.conv: _mir_destroy = xdestroy; | |private extern(C) @system nothrow @nogc pure void* memcpy(scope void* s1, scope const void* s2, size_t n); | | |/++ |The buffer uses stack memory and C Runtime to allocate temporal memory. | |Shouldn't store references to GC allocated data. |+/ |struct ScopedBuffer(T, size_t bytes = 4096) | if (bytes && T.sizeof <= bytes) |{ | import std.traits: Unqual, isMutable, isIterable, hasElaborateAssign, isAssignable, isArray; | import mir.primitives: hasLength; | import mir.conv: emplaceRef; | | private enum size_t _bufferLength = bytes / T.sizeof + (bytes % T.sizeof != 0); | private T[] _buffer; | size_t _currentLength; | | version (mir_secure_memory) | private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload; | else | private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload = void; | | private ref inout(T[_bufferLength]) _scopeBuffer() inout @trusted scope | { 102646| return *cast(inout(T[_bufferLength])*)&_scopeBufferPayload; | } | | /// Reserve `n` more elements. | void reserve(size_t n) @safe scope | { 1| prepare(n); 1| _currentLength -= n; | } | | /// Return a slice to `n` more elements. | T[] prepare(size_t n) @trusted scope | { | import mir.internal.memory: realloc, malloc; 53742| _currentLength += n; 53742| if (_buffer.length == 0) | { 33832| if (_currentLength <= _bufferLength) | { 33788| return _scopeBuffer[0 .. _currentLength]; | } | else | { 44| const newLen = _currentLength << 1; 44| if (auto p = malloc(T.sizeof * newLen)) | { 44| _buffer = (cast(T*)p)[0 .. newLen]; | } | else assert(0); | version (mir_secure_memory) | { | (cast(ubyte[])_buffer)[] = 0; | } 44| memcpy(cast(void*)_buffer.ptr, _scopeBuffer.ptr, T.sizeof * (_currentLength - n)); | } | } | else 19910| if (_currentLength > _buffer.length) | { 42| const newLen = _currentLength << 1; 42| if (auto p = realloc(cast(void*)_buffer.ptr, T.sizeof * newLen)) | { 42| _buffer = (cast(T*)p)[0 .. newLen]; | } | else assert(0); | version (mir_secure_memory) | { | (cast(ubyte[])_buffer[_currentLength .. $])[] = 0; | } | } 19954| return _buffer[0 .. _currentLength]; | } | | static if (isAssignable!(T, const T)) | private alias R = const T; | else | private alias R = T; | | /// Copy constructor is enabled only if `T` is mutable type without eleborate assign. | static if (isMutable!T && !hasElaborateAssign!T) | this(this) | { | import mir.internal.memory: malloc; 168| if (_buffer.ptr) | { 0000000| typeof(_buffer) buffer; 0000000| if (auto p = malloc(T.sizeof * _buffer.length)) | { 0000000| buffer = (cast(T*)p)[0 .. T.sizeof * _buffer.length]; | } | else assert(0); | version (mir_secure_memory) | { | (cast(ubyte[])buffer)[] = 0; | } 0000000| buffer[0 .. _currentLength] = _buffer[0 .. _currentLength]; 0000000| _buffer = buffer; | } | } | else | @disable this(this); | | /// | ~this() | { | import mir.internal.memory: free; 937| data._mir_destroy; | version(mir_secure_memory) | _currentLength = 0; 1918| (() @trusted { if (_buffer.ptr) free(cast(void*)_buffer.ptr); })(); | } | | /// | void shrinkTo(size_t length) | { 53265| assert(length <= _currentLength); 53265| data[length .. _currentLength]._mir_destroy; 53265| _currentLength = length; | } | | /// | size_t length() scope const @property | { 70| return _currentLength; | } | | /// | void popBackN(size_t n) | { 4| sizediff_t t = _currentLength - n; 4| if (t < 0) | assert(0, "ScopedBffer.popBackN: n is too large."); 4| data[t .. _currentLength]._mir_destroy; 4| _currentLength = t; | } | | /// | void put(T e) @safe scope | { 53314| auto cl = _currentLength; 53314| auto d = prepare(1); | static if (isMutable!T) | { | import core.lifetime: moveEmplace; 106616| ()@trusted{moveEmplace(e, d[cl]);}(); | } | else | { 6| emplaceRef!(Unqual!T)(d[cl], e); | } | } | | static if (T.sizeof > 8 || hasElaborateAssign!T) | /// | void put(ref R e) scope | { 0000000| auto cl = _currentLength; 0000000| auto d = prepare(1); 0000000| emplaceRef!(Unqual!T)(d[cl], e); | } | | static if (!hasElaborateAssign!T) | /// | void put(scope R[] e) scope | { 427| auto cl = _currentLength; 427| auto d = prepare(e.length); 427| if (!__ctfe) 854| (()@trusted=>memcpy(cast(void*)(d.ptr + cl), e.ptr, e.length * T.sizeof))(); | else | static if (isMutable!T) | (()@trusted=> d[cl .. cl + e.length] = e)(); | else | assert(0); | } | | /// | void put(Iterable)(Iterable range) scope | if (isIterable!Iterable && !__traits(isStaticArray, Iterable) && (!isArray!Iterable || hasElaborateAssign!T)) | { | static if (hasLength!Iterable) | { | auto cl = _currentLength; | auto d = prepare(range.length); | foreach(ref e; range) | emplaceRef!(Unqual!T)(d[cl++], e); | assert(_currentLength == cl); | } | else | { | foreach(ref e; range) | put(e); | } | } | | /// | alias opOpAssign(string op : "~") = put; | | /// | void reset() scope nothrow | { 230| this.__dtor; 230| _currentLength = 0; 230| _buffer = null; | } | | /// | void initialize() @system scope nothrow @nogc | { 88| _currentLength = 0; 88| _buffer = null; | } | | /// | inout(T)[] data() inout @property @trusted scope return | { 108722| return _buffer.length ? _buffer[0 .. _currentLength] : _scopeBuffer[0 .. _currentLength]; | } | | /++ | Copies data into an array of the same length using `memcpy` C routine. | Shrinks the length to `0`. | +/ | void moveDataAndEmplaceTo(T[] array) @system | in { 7| assert(array.length == _currentLength); | } | do { 7| memcpy(cast(void*)array.ptr, data.ptr, _currentLength * T.sizeof); 7| _currentLength = 0; | } |} | |/// ditto |auto scopedBuffer(T, size_t bytes = 4096)() @trusted |{ 67| ScopedBuffer!(T, bytes) buffer = void; 67| buffer.initialize; 67| return buffer; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ 2| auto buf = scopedBuffer!char; 1| buf.put('c'); 1| buf.put("str"); 1| assert(buf.data == "cstr"); | 1| buf.popBackN(2); 1| assert(buf.data == "cs"); |} | |/// immutable |@safe pure nothrow @nogc |version (mir_test) unittest |{ 2| auto buf = scopedBuffer!(immutable char); 1| buf.put('c'); 1| buf.put("str"); 1| assert(buf.data == "cstr"); | 1| buf.popBackN(2); 1| assert(buf.data == "cs"); |} | |@safe pure nothrow @nogc |version (mir_test) unittest |{ 2| auto buf = scopedBuffer!(char, 3); 1| buf.put('c'); 1| buf.put("str"); 1| assert(buf.data == "cstr"); | 1| buf.popBackN(2); 1| assert(buf.data == "cs"); |} | |@safe pure nothrow @nogc |version (mir_test) unittest |{ | alias T = char; 1| const n = 3; | 2| auto buf = scopedBuffer!(T, n * T.sizeof); 1| assert(buf._scopeBuffer.length == n); // stack 1| assert(buf._buffer.length == 0); // unset | 1| buf.reserve(n + 1); // transition to heap 1| assert(buf._buffer.length >= n + 1); // heap | 1| buf ~= 'c'; 1| buf ~= "str"; 1| assert(buf.data == "cstr"); | 1| buf.popBackN(2); 1| assert(buf.data == "cs"); |} | |/// |struct UnsafeArrayBuffer(T) |{ | import std.traits: isImplicitlyConvertible; | | /// | T[] buffer; | /// | size_t length; | | /// | void put(T a) | { | import core.lifetime: move; 489| assert(length < buffer.length); 489| buffer[length++] = move(a); | } | | static if (isImplicitlyConvertible!(const T, T)) | private alias E = const T; | else | private alias E = T; | | /// | void put(E[] a) | { | import core.lifetime: move; 390| assert(buffer.length >= a.length + length); 390| buffer[length .. length + a.length] = a; 390| length += a.length; | } | | /// | inout(T)[] data() inout @property @safe scope | { 136| return buffer[0 .. length]; | } | | /// | void popBackN(size_t n) | { 1| sizediff_t t = length - n; 1| if (t < 0) | assert(0, "UnsafeBuffer.popBackN: n is too large."); 1| buffer[t .. length]._mir_destroy; 1| length = t; | } |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ 1| char[4] array; 1| auto buf = UnsafeArrayBuffer!char(array); 1| buf.put('c'); 1| buf.put("str"); 1| assert(buf.data == "cstr"); | 1| buf.popBackN(2); 1| assert(buf.data == "cs"); |} | |version(mir_bignum_test) // for DIP1000 |@safe pure nothrow |unittest |{ | import mir.conv: to; | import mir.algebraic : Algebraic; | static struct S | { | @safe pure nothrow @nogc: | @property string toString() scope const | { 1| return "_"; | } | } 1| Algebraic!(int, string, double) x; 1| x = 42; 1| auto s = x.to!string; 1| assert(s == "42"); 1| x = "abc"; 1| assert(x.to!string == "abc"); 1| x = 42.0; 1| assert(x.to!string == "42.0"); 1| Algebraic!S y; 1| y = S(); 1| assert(y.to!string == "_"); |} source/mir/appender.d is 92% covered <<<<<< EOF # path=source-mir-array-allocation.lst |/** |Functions and types that manipulate built-in arrays and associative arrays. | |This module provides all kinds of functions to create, manipulate or convert arrays: | |$(SCRIPT inhibitQuickIndex = 1;) |$(BOOKTABLE , |$(TR $(TH Function Name) $(TH Description) |) | $(TR $(TD $(LREF _array)) | $(TD Returns a copy of the input in a newly allocated dynamic _array. | )) |) | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |Authors: $(HTTP erdani.org, Andrei Alexandrescu) and Jonathan M Davis | |Source: $(PHOBOSSRC std/_array.d) |*/ |module mir.array.allocation; | |import mir.functional; |import mir.primitives; |import std.traits; | |/** | * Allocates an array and initializes it with copies of the elements | * of range $(D r). | * | * Narrow strings are handled as a special case in an overload. | * | * Params: | * r = range (or aggregate with $(D opApply) function) whose elements are copied into the allocated array | * Returns: | * allocated and initialized array | */ |auto array(Range)(Range r) |if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !__traits(isStaticArray, Range) || isPointer!Range && (isInputRange!(PointerTarget!Range) || isIterable!(PointerTarget!Range))) |{ | static if (isIterable!Range) | alias E = ForeachType!Range; | else | static if (isPointer!Range && isIterable!(PointerTarget!Range)) | alias E = ForeachType!(PointerTarget!Range); | else | alias E = ElementType!Range; | 44| if (__ctfe) | { | // Compile-time version to avoid memcpy calls. | // Also used to infer attributes of array(). | E[] result; | static if (isInputRange!Range) | for (; !r.empty; r.popFront) | result ~= r.front; | else | static if (isPointer!Range) | foreach (e; *r) | result ~= e; | else | foreach (e; r) | result ~= e; | return result; | } | | import mir.primitives: hasLength; | | static if (hasLength!Range) | { 34| auto length = r.length; 34| if (length == 0) 0000000| return null; | | import mir.conv : emplaceRef; | import std.array: uninitializedArray; | 68| auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))(); | | static if (isInputRange!Range) | { 15681| foreach(ref e; result) | { 5194| emplaceRef!E(e, r.front); 5194| r.popFront; | } | } | else | static if (isPointer!Range) | { 1| auto it = result; 15| foreach(ref f; *r) | { 4| emplaceRef!E(it[0], f); 4| it = it[1 .. $]; | } | } | else | { | auto it = result; | foreach (f; r) | { | import mir.functional: forward; | emplaceRef!E(it[0], forward!f); | it = it[1 .. $]; | } | } | 68| return (() @trusted => cast(E[]) result)(); | } | else | { | import std.array: std_appender = appender; | 10| auto a = std_appender!(E[]); | | static if (isInputRange!Range) 127| for (; !r.empty; r.popFront) 59| a.put(r.front); | else | static if (isPointer!Range) | { | foreach (e; *r) | a.put(forward!e); | } | else | { 11| foreach (e; r) 10| a.put(forward!e); | } 10| return a.data; | } |} | |/// |@safe pure nothrow version(mir_test) unittest |{ 1| auto a = array([1, 2, 3, 4, 5][]); 1| assert(a == [ 1, 2, 3, 4, 5 ]); |} | |@safe pure nothrow version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; | struct Foo | { | int a; | } 1| auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]); 1| assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)])); |} | |@safe pure nothrow version(mir_test) unittest |{ | struct MyRange | { | enum front = 123; | enum empty = true; | void popFront() {} | } | 1| auto arr = (new MyRange).array; 1| assert(arr.empty); |} | |@system pure nothrow version(mir_test) unittest |{ 1| immutable int[] a = [1, 2, 3, 4]; 1| auto b = (&a).array; 1| assert(b == a); |} | |@system version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; | struct Foo | { | int a; | void opAssign(Foo) | { | assert(0); | } | auto opEquals(Foo foo) | { 5| return a == foo.a; | } | } 1| auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]); 1| assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)])); |} | |@safe version(mir_test) unittest |{ | // Issue 12315 | static struct Bug12315 { immutable int i; } | enum bug12315 = [Bug12315(123456789)].array(); | static assert(bug12315[0].i == 123456789); |} | |@safe version(mir_test) unittest |{ | import mir.ndslice.topology: repeat; | static struct S{int* p;} 1| auto a = array(immutable(S).init.repeat(5)); 1| assert(a.length == 5); |} | |/// |@safe version(mir_test) unittest |{ 1| assert("Hello D".array == "Hello D"); 1| assert("Hello D"w.array == "Hello D"w); 1| assert("Hello D"d.array == "Hello D"d); |} | |@system version(mir_test) unittest |{ | // @system due to array!string | import std.conv : to; | 0000000| static struct TestArray { int x; string toString() scope const @safe { return to!string(x); } } | | static struct OpAssign | { | uint num; 4| this(uint num) { this.num = num; } | | // Templating opAssign to make sure the bugs with opAssign being | // templated are fixed. 0000000| void opAssign(T)(T rhs) { this.num = rhs.num; } | } | | static struct OpApply | { | int opApply(scope int delegate(ref int) dg) | { 1| int res; 33| foreach (i; 0 .. 10) | { 10| res = dg(i); 10| if (res) break; | } | 1| return res; | } | } | 1| auto a = array([1, 2, 3, 4, 5][]); 1| assert(a == [ 1, 2, 3, 4, 5 ]); | 1| auto b = array([TestArray(1), TestArray(2)][]); 1| assert(b == [TestArray(1), TestArray(2)]); | | class C | { | int x; 4| this(int y) { x = y; } 0000000| override string toString() const @safe { return to!string(x); } | } 1| auto c = array([new C(1), new C(2)][]); 1| assert(c[0].x == 1); 1| assert(c[1].x == 2); | 1| auto d = array([1.0, 2.2, 3][]); 1| assert(is(typeof(d) == double[])); 1| assert(d == [1.0, 2.2, 3]); | 1| auto e = [OpAssign(1), OpAssign(2)]; 1| auto f = array(e); 1| assert(e == f); | 1| assert(array(OpApply.init) == [0,1,2,3,4,5,6,7,8,9]); 1| assert(array("ABC") == "ABC"); 1| assert(array("ABC".dup) == "ABC"); |} | |//Bug# 8233 |@safe version(mir_test) unittest |{ 1| assert(array("hello world"d) == "hello world"d); 1| immutable a = [1, 2, 3, 4, 5]; 1| assert(array(a) == a); 1| const b = a; 1| assert(array(b) == a); | | //To verify that the opAssign branch doesn't get screwed up by using Unqual. | //EDIT: array no longer calls opAssign. | struct S | { | ref S opAssign(S)(const ref S rhs) | { | assert(0); | } | | int i; | } | | alias AliasSeq(T...) = T; | foreach (T; AliasSeq!(S, const S, immutable S)) | { 3| auto arr = [T(1), T(2), T(3), T(4)]; 3| assert(array(arr) == arr); | } |} | |@safe version(mir_test) unittest |{ | //9824 | static struct S | { | @disable void opAssign(S); | int i; | } 1| auto arr = [S(0), S(1), S(2)]; 1| arr.array; |} | |// Bugzilla 10220 |@safe version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; | import std.exception; | import mir.ndslice.topology: repeat; | | static struct S | { | int val; | | @disable this(); 4| this(int v) { val = v; } | } | static immutable r = S(1).repeat(2).array(); 1| assert(equal(r, [S(1), S(1)])); |} | |@safe version(mir_test) unittest |{ | //Turn down infinity: | static assert(!is(typeof( | repeat(1).array() | ))); |} source/mir/array/allocation.d is 94% covered <<<<<< EOF # path=source-mir-base64.lst |/++ |$(H1 @nogc Simple Base64 parsing) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Harrison Ford |Copyright: 2021 Harrison Ford, Symmetry Investments |+/ |module mir.base64; | |package static immutable base64DecodeInvalidCharMsg = "base64: Invalid character encountered."; |package static immutable base64DecodeInvalidLenMsg = "Cannot decode a buffer with given length (not a multiple of 4, missing padding?)"; |version(D_Exceptions) { | package static immutable base64DecodeInvalidCharException = new Exception(base64DecodeInvalidCharMsg); | package static immutable base64DecodeInvalidLenException = new Exception(base64DecodeInvalidLenMsg); |} | |// NOTE: I do not know if this would work on big-endian systems. |// Needs further testing to figure out if it *does* work on them. | |// Technique borrowed from http://0x80.pl/notesen/2016-01-12-sse-base64-encoding.html#branchless-code-for-lookup-table |private char lookup_encoding(ubyte i, char plusChar = '+', char slashChar = '/') @safe @nogc pure { 270| assert(i < 64); | 270| ubyte shift; | 270| if (i < 26) | { | // range A-Z 158| shift = 'A'; | } 224| else if (i >= 26 && i < 52) | { | // range a-z 90| shift = 'a' - 26; | } 44| else if (i >= 52 && i < 62) | { | // range 0-9 18| shift = cast(ubyte)('0' - 52); | } 4| else if (i == 62) | { | // character plus 2| shift = cast(ubyte)(plusChar - 62); | } 2| else if (i == 63) | { | // character slash 2| shift = cast(ubyte)(slashChar - 63); | } | 270| return cast(char)(i + shift); |} | |// Do the inverse of above (convert an ASCII value into the Base64 character set) |private ubyte lookup_decoding(char i, char plusChar = '+', char slashChar = '/') @safe @nogc pure |{ | // Branching bad, but this isn't performance sensitive 514| if (i <= 'Z' && i >= 'A') { 187| return cast(ubyte)(i - 'A'); | } 234| else if (i <= 'z' && i >= 'a') { 94| return cast(ubyte)(i - 'a' + 26); | } 42| else if (i <= '9' && i >= '0') { 15| return cast(ubyte)(i - '0' + 52); | } 8| else if (i == plusChar) { 2| return 62; | } 6| else if (i == slashChar) { 1| return 63; | } | // Just return 0 for padding, | // as it typically means nothing. 5| else if (i == '=') { 4| return 0; | } | else { | version(D_Exceptions) { 1| throw base64DecodeInvalidCharException; | } else { | assert(0, base64DecodeInvalidCharMsg); | } | } | |} | |/++ |Decode a Base64 encoded value, returning the buffer. |+/ |ubyte[] decodeBase64(scope const(char)[] data, char plusChar = '+', char slashChar = '/') @safe pure |{ | import mir.appender : scopedBuffer; 44| auto app = scopedBuffer!ubyte; 22| decodeBase64(data, app, plusChar, slashChar); 10| return app.data.dup; |} | |/++ |Decode a Base64 encoded value, placing the result onto an Appender. |+/ |void decodeBase64(Appender)(scope const(char)[] data, | scope ref Appender appender, | char plusChar = '+', | char slashChar = '/') @safe pure |{ | // We expect data should be well-formed (with padding), | // so we should throw if it is not well-formed. 22| if (data.length % 4 != 0) | { | version(D_Exceptions) { 2| throw base64DecodeInvalidLenException; | } else { | assert(0, base64DecodeInvalidLenMsg); | } | } | 20| ubyte[3] decodedByteGroup; 20| ubyte sz = 0; | | // We can't use mir.ndslice.chunk.chunks here, as it violates | // the scope requirements. 186| for (size_t i = 0; i < data.length; i += 4) | { 83| auto group = data[i .. (i + 4)]; | 83| ubyte[4] decodedBytes; 83| decodedBytes[0] = lookup_decoding(group[0], plusChar, slashChar); 82| decodedBytes[1] = lookup_decoding(group[1], plusChar, slashChar); | 82| uint transformed_group = (decodedBytes[0] << 26) | (decodedBytes[1] << 20); | | // According to RFC4648 Section 3.3, we don't have to accept extra padding characters, | // and we can safely throw (and stay within spec). | // x=== is also invalid, so we can just throw on that here. 162| if (group[0] == '=' || group[1] == '=') | { | version(D_Exceptions) 3| throw base64DecodeInvalidCharException; | else | assert(0, base64DecodeInvalidCharMsg); | } | | // xx=(=)? 79| if (group[2] == '=') | { | // If we are not at the end of a string, according to RFC4648, | // we can safely treat a padding character as "non-alphabet data", | // and as such, we should throw. See RFC4648 Section 3.3 for more information 7| if ((i / 4) != ((data.length / 4) - 1)) | { | version(D_Exceptions) 4| throw base64DecodeInvalidCharException; | else | assert(0, base64DecodeInvalidCharMsg); | } | 3| if (group[3] == '=') | { | // xx== 2| sz = 1; | } | // xx=x (invalid) | // Padding should not be in the middle of a chunk | else | { | version(D_Exceptions) 1| throw base64DecodeInvalidCharException; | else | assert(0, base64DecodeInvalidCharMsg); | } | } | // xxx= 72| else if (group[3] == '=') | { | // If we are not at the end of a string, according to RFC4648, | // we can safely treat a padding character as "non-alphabet data", | // and as such, we should throw. See RFC4648 Section 3.3 for more information 4| if ((i / 4) != ((data.length / 4) - 1)) | { | version(D_Exceptions) 1| throw base64DecodeInvalidCharException; | else | assert(0, base64DecodeInvalidCharMsg); | } | 3| decodedBytes[2] = lookup_decoding(group[2], plusChar, slashChar); 3| transformed_group |= (decodedBytes[2] << 14); 3| sz = 2; | } | // xxxx | else | { 68| decodedBytes[2] = lookup_decoding(group[2], plusChar, slashChar); 68| decodedBytes[3] = lookup_decoding(group[3], plusChar, slashChar); 68| transformed_group |= ((decodedBytes[2] << 14) | (decodedBytes[3] << 8)); 68| sz = 3; | } | 73| decodedByteGroup[0] = (transformed_group >> 24) & 0xff; 73| decodedByteGroup[1] = (transformed_group >> 16) & 0xff; 73| decodedByteGroup[2] = (transformed_group >> 8) & 0xff; | | // Only emit the transformed bytes that we got data for. 73| appender.put(decodedByteGroup[0 .. sz]); | } |} | |/// Test decoding of data which has a length which can be |/// cleanly decoded. |version(mir_test) |@safe pure unittest |{ | { | enum data = "QUJD"; 1| assert(data.decodeBase64 == "ABC"); | } | | { | enum data = "QQ=="; 1| assert(data.decodeBase64 == "A"); | } | | { | enum data = "YSBiIGMgZCBlIGYgZyBoIGkgaiBrIGwgbSBuIG8gcCBxIHIgcyB0IHUgdiB3IHggeSB6"; 1| assert(data.decodeBase64 == "a b c d e f g h i j k l m n o p q r s t u v w x y z"); | } | | { | enum data = "LCAuIDsgLyBbICcgXSBcID0gLSAwIDkgOCA3IDYgNSA0IDMgMiAxIGAgfiAhIEAgIyAkICUgXiAmICogKCApIF8gKyB8IDogPCA+ID8="; 1| assert(data.decodeBase64 == ", . ; / [ ' ] \\ = - 0 9 8 7 6 5 4 3 2 1 ` ~ ! @ # $ % ^ & * ( ) _ + | : < > ?"); | } | | { | enum data = "AAA="; 1| assert(data.decodeBase64 == "\x00\x00"); | } | | { | enum data = "AAAABBCC"; 1| assert(data.decodeBase64 == "\x00\x00\x00\x04\x10\x82"); | } | | { | enum data = "AA=="; 1| assert(data.decodeBase64 == "\x00"); | } | | { | enum data = "AA/="; 1| assert(data.decodeBase64 == "\x00\x0f"); | } |} | |/// Test decoding invalid data |version(mir_test) |@safe pure unittest |{ | void testFail(const(char)[] input) @safe pure | { 12| bool thrown = false; | try { 12| ubyte[] decoded = input.decodeBase64; | } catch (Exception t) { 12| thrown = true; | } | 12| assert(thrown); | } | 1| testFail("===A"); 1| testFail("A="); 1| testFail("AA="); 1| testFail("A=AA"); 1| testFail("AA=A"); 1| testFail("AA=A===="); 1| testFail("=AAA"); 1| testFail("AAA=QUJD"); | // This fails because we don't allow extra padding (than what is necessary) 1| testFail("AA======"); | // This fails because we don't allow padding before the end of the string (otherwise we'd have a side-channel) 1| testFail("QU==QUJD"); 1| testFail("QU======QUJD"); | // Invalid data that's out of the alphabet 1| testFail("!@##@@!@"); |} | |/++ |Encode a ubyte array as Base64, returning the encoded value. |+/ |string encodeBase64(scope const(ubyte)[] buf, char plusChar = '+', char slashChar = '/') @safe pure |{ | import mir.appender : scopedBuffer; 22| auto app = scopedBuffer!char; 11| encodeBase64(buf, app, plusChar, slashChar); 11| return app.data.idup; |} | |/++ |Encode a ubyte array as Base64, placing the result onto an Appender. |+/ |void encodeBase64(Appender)(scope const(ubyte)[] input, | scope ref Appender appender, | char plusChar = '+', | char slashChar = '/') @safe pure |{ | import core.bitop : bswap; | import mir.ndslice.topology : bytegroup, map; | // Slice our input array so that n % 3 == 0 (we have a multiple of 3) | // If we have less then 3, then this is effectively a no-op (will result in a 0-length slice) 13| char[4] encodedByteGroup; 13| const(ubyte)[] window = input[0 .. input.length - (input.length % 3)]; 218| foreach(group; window.bytegroup!(3, uint).map!bswap) { 64| const(ubyte) a = (group >> 26) & 0x3f; 64| const(ubyte) b = (group >> 20) & 0x3f; 64| const(ubyte) c = (group >> 14) & 0x3f; 64| const(ubyte) d = (group >> 8) & 0x3f; | 64| encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); 64| encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); 64| encodedByteGroup[2] = c.lookup_encoding(plusChar, slashChar); 64| encodedByteGroup[3] = d.lookup_encoding(plusChar, slashChar); 64| appender.put(encodedByteGroup[]); | } | | // If it's a clean multiple of 3, then it requires no padding. | // If not, then we need to add padding. 13| if (input.length % 3 != 0) | { 6| window = input[window.length .. input.length]; | 6| uint group = (window[0] << 24); | 6| if (window.length == 1) { 4| const(ubyte) a = (group >> 26) & 0x3f; 4| const(ubyte) b = (group >> 20) & 0x3f; 4| encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); 4| encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); 4| encodedByteGroup[2] = '='; 4| encodedByteGroup[3] = '='; | } | else { | // Just in case 2| assert(window.length == 2); | 2| group |= (window[1] << 16); 2| const(ubyte) a = (group >> 26) & 0x3f; 2| const(ubyte) b = (group >> 20) & 0x3f; 2| const(ubyte) c = (group >> 14) & 0x3f; 2| encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); 2| encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); 2| encodedByteGroup[2] = c.lookup_encoding(plusChar, slashChar); 2| encodedByteGroup[3] = '='; | } | 6| appender.put(encodedByteGroup[]); | } |} | |/// Test encoding of data which has a length that can be cleanly |/// encoded. |version(mir_test) |@safe pure unittest |{ | // 3 bytes | { | enum data = cast(immutable(ubyte)[])"ABC"; 1| assert(data.encodeBase64 == "QUJD"); | } | | // 6 bytes | { | enum data = cast(immutable(ubyte)[])"ABCDEF"; 1| assert(data.encodeBase64 == "QUJDREVG"); | } | | // 9 bytes | { | enum data = cast(immutable(ubyte)[])"ABCDEFGHI"; 1| assert(data.encodeBase64 == "QUJDREVGR0hJ"); | } | | // 12 bytes | { | enum data = cast(immutable(ubyte)[])"ABCDEFGHIJKL"; 1| assert(data.encodeBase64 == "QUJDREVGR0hJSktM"); | } |} | |/// Test encoding of data which has a length which CANNOT be cleanly encoded. |/// This typically means that there's padding. |version(mir_test) |@safe pure unittest |{ | // 1 byte | { | enum data = cast(immutable(ubyte)[])"A"; 1| assert(data.encodeBase64 == "QQ=="); | } | // 2 bytes | { | enum data = cast(immutable(ubyte)[])"AB"; 1| assert(data.encodeBase64 == "QUI="); | } | // 2 bytes | { | enum data = [0xFF, 0xFF]; 1| assert(data.encodeBase64 == "//8="); | } | // 4 bytes | { | enum data = [0xDE, 0xAD, 0xBA, 0xBE]; 1| assert(data.encodeBase64 == "3q26vg=="); | } | // 37 bytes | { | enum data = cast(immutable(ubyte)[])"A Very Very Very Very Large Test Blob"; 1| assert(data.encodeBase64 == "QSBWZXJ5IFZlcnkgVmVyeSBWZXJ5IExhcmdlIFRlc3QgQmxvYg=="); | } |} | |/// Test nogc encoding |version(mir_test) |@safe pure @nogc unittest |{ | import mir.appender : scopedBuffer; | | { | enum data = cast(immutable(ubyte)[])"A Very Very Very Very Large Test Blob"; 2| auto appender = scopedBuffer!char(); 1| data.encodeBase64(appender); 1| assert(appender.data == "QSBWZXJ5IFZlcnkgVmVyeSBWZXJ5IExhcmdlIFRlc3QgQmxvYg=="); | } | | { | enum data = cast(immutable(ubyte)[])"abc123!?$*&()'-=@~"; 2| auto appender = scopedBuffer!char(); 1| data.encodeBase64(appender); 1| assert(appender.data == "YWJjMTIzIT8kKiYoKSctPUB+"); | } |} | |/// Make sure we can decode what we encode. |version(mir_test) |@safe pure unittest |{ | // Test an example string | { | enum data = cast(immutable(ubyte)[])"abc123!?$*&()'-=@~"; 1| assert(data.encodeBase64.decodeBase64 == data); | } | // Test an example from Ion data | { | enum data = cast(immutable(ubyte)[])"a b c d e f g h i j k l m n o p q r s t u v w x y z"; 1| assert(data.encodeBase64.decodeBase64 == data); | } |} | source/mir/base64.d is 100% covered <<<<<< EOF # path=source-mir-bignum-decimal.lst |/++ |Stack-allocated decimal type. | |Note: | The module doesn't provide full arithmetic API for now. |+/ |module mir.bignum.decimal; | |import mir.serde: serdeProxy, serdeScoped; |import std.traits: isSomeChar; |/// |public import mir.parse: DecimalExponentKey; |import mir.bignum.low_level_view: ceilLog10Exp2; | |private enum expBufferLength = 2 + ceilLog10Exp2(ulong.sizeof * 8); |private static immutable C[9] zerosImpl(C) = "0.00000.0"; | |/++ |Stack-allocated decimal type. |Params: | size64 = count of 64bit words in coefficient |+/ |@serdeScoped @serdeProxy!(const(char)[]) |struct Decimal(uint size64) | if (size64 && size64 <= ushort.max) |{ | import mir.format: NumericSpec; | import mir.bignum.integer; | import mir.bignum.low_level_view; | import std.traits: isMutable, isFloatingPoint; | | /// | long exponent; | /// | BigInt!size64 coefficient; | | /// | void toString(C = char, W)(ref scope W w, NumericSpec spec = NumericSpec.init) const scope | if(isSomeChar!C && isMutable!C) | { 150| assert(spec.format == NumericSpec.Format.exponent || spec.format == NumericSpec.Format.human); | import mir.utility: _expect; | // handle special values 75| if (_expect(exponent == exponent.max, false)) | { | static immutable C[3] nan = "nan"; | static immutable C[4] ninf = "-inf"; | static immutable C[4] pinf = "+inf"; 8| w.put(coefficient.length == 0 ? coefficient.sign ? ninf[] : pinf[] : nan[]); 8| return; | } | 67| C[coefficientBufferLength + 16] buffer0 = void; 67| auto buffer = buffer0[0 .. $ - 16]; | 67| size_t coefficientLength; | static if (size_t.sizeof == 8) | { 67| if (__ctfe) | { | uint[coefficient.data.length * 2] data; | foreach (i; 0 .. coefficient.length) | { | auto l = cast(uint)coefficient.data[i]; | auto h = cast(uint)(coefficient.data[i] >> 32); | data[i * 2 + 0] = l; | data[i * 2 + 1] = h; | } | auto work = BigUIntView!uint(data); | work = work.topLeastSignificantPart(coefficient.length * 2).normalized; | coefficientLength = work.toStringImpl(buffer); | } | else | { 67| BigInt!size64 work = coefficient; 67| coefficientLength = work.view.unsigned.toStringImpl(buffer); | } | } | else | { | BigInt!size64 work = coefficient; | coefficientLength = work.view.unsigned.toStringImpl(buffer); | } | 67| C[1] sign = coefficient.sign ? "-" : "+"; 120| bool addSign = coefficient.sign || spec.plus; 67| long s = this.exponent + coefficientLength; | | alias zeros = zerosImpl!C; | 67| if (spec.format == NumericSpec.Format.human) | { 67| if (!spec.separatorCount) 0000000| spec.separatorCount = 3; | void putL(scope const(C)[] b) | { 11| assert(b.length); | 11| if (addSign) 2| w.put(sign[]); | 11| auto r = b.length % spec.separatorCount; 11| if (r == 0) 4| r = spec.separatorCount; 11| C[1] sep = spec.separatorChar; 11| goto LS; | do | { 18| w.put(sep[]); | LS: 29| w.put(b[0 .. r]); 29| b = b[r .. $]; 29| r = spec.separatorCount; | } 29| while(b.length); | } | | // try print decimal form without exponent | // up to 6 digits exluding leading 0. or final .0 67| if (s <= 0) | { | //0.001.... | //0.0001 | //0.00001 | //0.000001 | //If separatorChar is defined lets be less greed for space. 60| if (this.exponent >= -6 || s >= -2 - (spec.separatorChar != 0) * 3) | { 30| if (addSign) 8| w.put(sign[]); 30| w.put(zeros[0 .. cast(sizediff_t)(-s + 2)]); 30| w.put(buffer[$ - coefficientLength .. $]); 30| return; | } | } | else 30| if (this.exponent >= 0) | { | ///dddddd.0 18| if (!spec.separatorChar) | { 10| if (s <= 6) | { 8| buffer[$ - coefficientLength - 1] = sign[0]; 8| w.put(buffer[$ - coefficientLength - addSign .. $]); 8| w.put(zeros[($ - (cast(sizediff_t)this.exponent + 2)) .. $]); 8| return; | } | } | else | { 8| if (s <= 12) | { 7| buffer0[$ - 16 .. $] = '0'; 7| putL(buffer0[$ - coefficientLength - 16 .. $ - 16 + cast(sizediff_t)this.exponent]); 7| w.put(zeros[$ - 2 .. $]); 7| return; | } | } | } | else | { | ///dddddd.0 12| if (!spec.separatorChar) | { | ///dddddd.d.... 9| if (s <= 6 || coefficientLength <= 6) | { 7| buffer[$ - coefficientLength - 1] = sign[0]; 7| w.put(buffer[$ - coefficientLength - addSign .. $ - coefficientLength + cast(sizediff_t)s]); | T2: 11| buffer[$ - coefficientLength + cast(sizediff_t)s - 1] = '.'; 11| w.put(buffer[$ - coefficientLength + cast(sizediff_t)s - 1 .. $]); 11| return; | } | } | else | { 4| if (s <= 12 || coefficientLength <= 12) | { 4| putL(buffer[$ - coefficientLength .. $ - coefficientLength + cast(sizediff_t)s]); 4| goto T2; | } | } | } | } | 11| assert(coefficientLength); | 11| long exponent = s - 1; | 11| if (coefficientLength > 1) | { 9| auto c = buffer[$ - coefficientLength]; 9| buffer[$ - coefficientLength] = '.'; 9| buffer[$ - ++coefficientLength] = c; | } | 11| buffer[$ - coefficientLength - 1] = sign[0]; 11| w.put(buffer[$ - coefficientLength - addSign .. $]); | | import mir.format_impl: printSignedToTail; | | static if (exponent.sizeof == 8) | enum N = 21; | else | enum N = 11; | | // prints e+/-exponent 11| auto expLength = printSignedToTail(exponent, buffer0[$ - N - 16 .. $ - 16], '+'); 11| buffer[$ - ++expLength] = spec.exponentChar; 11| w.put(buffer[$ - expLength .. $]); | } | |@safe: | | /// | DecimalView!size_t view() return scope | { 1| return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); | } | | /// ditto | DecimalView!(const size_t) view() const return scope | { 159| return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); | } | | /// | this(C)(scope const(C)[] str, int exponentShift = 0) @safe pure @nogc | if (isSomeChar!C) | { 12| DecimalExponentKey key; 12| if (fromStringImpl(str, key, exponentShift) || key == DecimalExponentKey.nan || key == DecimalExponentKey.infinity) 12| return; | static if (__traits(compiles, () @nogc { throw new Exception("Can't parse Decimal."); })) | { | import mir.exception: MirException; | throw new MirException("Can't parse Decimal!" ~ size64.stringof ~ " from string `", str , "`"); | } | else | { | static immutable exception = new Exception("Can't parse Decimal!" ~ size64.stringof ~ "."); 0000000| throw exception; | } | } | | /++ | Constructs Decimal from the floating point number using the $(HTTPS github.com/ulfjack/ryu, Ryu algorithm). | | The number is the shortest decimal representation that being converted back would result the same floating-point number. | +/ 74| this(T)(const T x) | if (isFloatingPoint!T && size64 >= 1 + (T.mant_dig >= 64)) | { | import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; 74| this = genericBinaryToDecimal(x); | } | | /// | ref opAssign(uint rhsMaxSize64)(auto ref scope const Decimal!rhsMaxSize64 rhs) return | if (rhsMaxSize64 < size64) | { 4| this.exponent = rhs.exponent; 4| this.coefficient = rhs.coefficient; 4| return this; | } | | /++ | Handle thousand separators for non exponential numbers. | | Returns: false in case of overflow or incorrect string. | +/ | bool fromStringWithThousandsSeparatorImpl(C, | bool allowSpecialValues = true, | bool allowStartingPlus = true, | bool allowLeadingZeros = true, | )( | scope const(C)[] str, | const C thousandsSeparator, | const C fractionSeparator, | out DecimalExponentKey key, | int exponentShift = 0, | ) @trusted | if (isSomeChar!C) | { | import mir.algorithm.iteration: find; | import mir.format: stringBuf; | import mir.ndslice.chunks: chunks; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: retro; | 6| auto buffer = stringBuf; 3| assert(thousandsSeparator != fractionSeparator); 9| if (str.length && (str[0] == '+' || str[0] == '-')) | { 0000000| buffer.put(cast(char)str[0]); 0000000| str = str[1 .. $]; | } 28| auto integer = str[0 .. $ - str.find!(a => a == fractionSeparator)]; 3| if (integer.length % 4 == 0) 0000000| return false; 27| foreach_reverse (chunk; integer.sliced.retro.chunks(4)) | { 7| auto s = chunk.retro.field; 7| if (s.length == 4) | { 4| if (s[0] != thousandsSeparator) 0000000| return false; 4| s = s[1 .. $]; | } | do | { 38| if (s[0] < '0' || s[0] > '9') 0000000| return false; 19| buffer.put(cast(char)s[0]); 19| s = s[1 .. $]; | } 19| while(s.length); | } 3| if (str.length > integer.length) | { 2| buffer.put('.'); 2| str = str[integer.length + 1 .. $]; 2| if (str.length == 0) 0000000| return false; | do | { 6| buffer.put(cast(char)str[0]); 6| str = str[1 .. $]; | } 6| while(str.length); | } 3| return fromStringImpl!(char, | allowSpecialValues, | false, // allowDotOnBounds | false, // allowDExponent | allowStartingPlus, | false, // allowUnderscores | allowLeadingZeros, // allowLeadingZeros | false, // allowExponent | false, // checkEmpty | )(buffer.data, key, exponentShift); | } | | /++ | Returns: false in case of overflow or incorrect string. | +/ | bool fromStringImpl(C, | bool allowSpecialValues = true, | bool allowDotOnBounds = true, | bool allowDExponent = true, | bool allowStartingPlus = true, | bool allowUnderscores = true, | bool allowLeadingZeros = true, | bool allowExponent = true, | bool checkEmpty = true, | ) | (scope const(C)[] str, out DecimalExponentKey key, int exponentShift = 0) | scope @trusted pure @nogc nothrow | if (isSomeChar!C) | { | import mir.bignum.low_level_view: DecimalView, BigUIntView, MaxWordPow10; 195| auto work = DecimalView!size_t(false, 0, BigUIntView!size_t(coefficient.data)); 195| auto ret = work.fromStringImpl!(C, | allowSpecialValues, | allowDotOnBounds, | allowDExponent, | allowStartingPlus, | allowUnderscores, | allowLeadingZeros, | allowExponent, | checkEmpty, | )(str, key, exponentShift); 195| coefficient.length = cast(uint) work.coefficient.coefficients.length; 195| coefficient.sign = work.sign; 195| exponent = work.exponent; 195| return ret; | } | | private enum coefficientBufferLength = 2 + ceilLog10Exp2(coefficient.data.length * (size_t.sizeof * 8)); // including dot and sign | private enum eDecimalLength = coefficientBufferLength + expBufferLength; | | /// | immutable(C)[] toString(C = char)(NumericSpec spec = NumericSpec.init) const scope @safe pure nothrow | if(isSomeChar!C && isMutable!C) | { | import mir.appender: UnsafeArrayBuffer; 2| C[eDecimalLength] data = void; 2| auto buffer = UnsafeArrayBuffer!C(data); 2| toString(buffer, spec); 2| return buffer.data.idup; | } | | /++ | Mir parsing supports up-to quadruple precision. The conversion error is 0 ULP for normal numbers. | Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP. +/ | T opCast(T, bool wordNormalized = true)() scope const | if (isFloatingPoint!T && isMutable!T) | { 159| return view.opCast!(T, wordNormalized); | } | | /// | bool isNaN() scope const @property | { 0000000| return exponent == exponent.max && coefficient.length; | } | | /// | bool isInfinity() scope const @property | { 0000000| return exponent == exponent.max && coefficient.length == 0; | } | | /// | bool isSpecial() scope const @property | { 0000000| return exponent == exponent.max; | } | | /// | ref opOpAssign(string op, size_t rhsMaxSize64)(ref const Decimal!rhsMaxSize64 rhs) @safe pure return | if (op == "+" || op == "-") | { | import mir.utility: max; 22| BigInt!(max(rhsMaxSize64, size64, 256u)) rhsCopy = void; 22| BigIntView!(const size_t) rhsView; 22| auto expDiff = cast(sizediff_t) (exponent - rhs.exponent); 22| if (expDiff >= 0) | { 12| exponent = rhs.exponent; 12| coefficient.mulPow5(expDiff); 12| coefficient.opOpAssign!"<<"(expDiff); 12| rhsView = rhs.coefficient.view; | } | else | { 10| rhsCopy.copyFrom(rhs.coefficient.coefficients, rhs.coefficient.sign); 10| rhsCopy.mulPow5(-expDiff); 10| rhsCopy.opOpAssign!"<<"(-expDiff); 10| rhsView = rhsCopy.view; | } 22| coefficient.opOpAssign!op(rhsView); 22| return this; | } |} | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | import mir.test: should; | import mir.conv: to; 1| Decimal!128 decimal = void; 1| DecimalExponentKey key; | 1| assert(decimal.fromStringImpl("3.141592653589793378e-10", key)); 1| decimal.opCast!double.should == 0x1.596bf8ce7631ep-32; 1| key.should == DecimalExponentKey.e; |} | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | import mir.conv: to; 1| Decimal!3 decimal; 1| DecimalExponentKey key; | 1| assert(decimal.fromStringImpl("0", key)); 1| assert(key == DecimalExponentKey.none); 1| assert(decimal.exponent == 0); 1| assert(decimal.coefficient.length == 0); 1| assert(!decimal.coefficient.sign); 1| assert(cast(double) decimal.coefficient == 0); | 1| assert(decimal.fromStringImpl("-0.0", key)); 1| assert(key == DecimalExponentKey.dot); 1| assert(decimal.exponent == -1); 1| assert(decimal.coefficient.length == 0); 1| assert(decimal.coefficient.sign); 1| assert(cast(double) decimal.coefficient == 0); | 1| assert(decimal.fromStringImpl("0e0", key)); 1| assert(key == DecimalExponentKey.e); 1| assert(decimal.exponent == 0); 1| assert(decimal.coefficient.length == 0); 1| assert(!decimal.coefficient.sign); 1| assert(cast(double) decimal.coefficient == 0); |} | |/// |version(mir_bignum_test) @safe pure @nogc unittest |{ 1| auto a = Decimal!1("777.7"); 1| auto b = Decimal!1("777"); | import mir.format; 1| assert(stringBuf() << cast(double)a - cast(double)b << getData == "0.7000000000000455"); 1| a -= b; 1| assert(stringBuf() << a << getData == "0.7"); | 1| a = Decimal!1("-777.7"); 1| b = Decimal!1("777"); 1| a += b; 1| assert(stringBuf() << a << getData == "-0.7"); | 1| a = Decimal!1("777.7"); 1| b = Decimal!1("-777"); 1| a += b; 1| assert(stringBuf() << a << getData == "0.7"); | 1| a = Decimal!1("777"); 1| b = Decimal!1("777.7"); 1| a -= b; 1| assert(stringBuf() << a << getData == "-0.7"); |} | |/// Check @nogc toString impl |version(mir_bignum_test) @safe pure @nogc unittest |{ | import mir.format: stringBuf; 1| auto str = "5.28238923728e-876543210"; 1| auto decimal = Decimal!1(str); 2| auto buffer = stringBuf; 1| buffer << decimal; 1| assert(buffer.data == str); |} | |/// |version(mir_bignum_test) |@safe pure @nogc unittest |{ 1| Decimal!4 i = "-0"; | 1| assert(i.view.coefficient.coefficients.length == 0); 1| assert(i.coefficient.view.unsigned.coefficients.length == 0); 1| assert(i.coefficient.view == 0L); 1| assert(cast(long) i.coefficient == 0); 1| assert(i.coefficient.sign); |} | |/// |version(mir_bignum_test) @safe pure unittest |{ 1| auto str = "-3.4010447314490204552169750449563978034784726557588085989975288830070948234680e-13245"; 1| auto decimal = Decimal!4(str); 1| assert(decimal.toString == str, decimal.toString); | 1| decimal = Decimal!4.init; 1| assert(decimal.toString == "0.0"); |} | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | import mir.test: should; | | import mir.conv: to; 1| Decimal!3 decimal; 1| DecimalExponentKey key; | | // Check precise percentate parsing 1| assert(decimal.fromStringImpl("71.7", key, -2)); 1| key.should == DecimalExponentKey.dot; | // The result is exact value instead of 0.7170000000000001 = 71.7 / 100 1| (cast(double) decimal).should == 0.717; | 1| assert(decimal.fromStringImpl("+0.334e-5"w, key)); 1| key.should == DecimalExponentKey.e; 1| (cast(double) decimal).should == 0.334e-5; | 1| assert(decimal.fromStringImpl("100_000_000"w, key)); 1| key.should == DecimalExponentKey.none; 1| (cast(double) decimal).should == 1e8; | 1| assert(decimal.fromStringImpl("-334D-5"d, key)); 1| key.should == DecimalExponentKey.D; 1| (cast(double) decimal).should == -334e-5; | 1| assert(decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); 1| key.should == DecimalExponentKey.none; 1| (cast(double) decimal).should == 2482734692817364218734682973648217364981273648923423.0; | 1| assert(decimal.fromStringImpl(".023", key)); 1| key.should == DecimalExponentKey.dot; 1| (cast(double) decimal).should == .023; | 1| assert(decimal.fromStringImpl("0E100", key)); 1| key.should == DecimalExponentKey.E; 1| (cast(double) decimal).should == 0; | 12| foreach (str; ["-nan", "-NaN", "-NAN"]) | { 3| assert(decimal.fromStringImpl(str, key)); 3| assert(decimal.coefficient.length > 0); 3| assert(decimal.exponent == decimal.exponent.max); 3| assert(decimal.coefficient.sign); 3| key.should == DecimalExponentKey.nan; 3| auto nan = cast(double) decimal; 3| (cast(double) decimal).should == double.nan; | } | 12| foreach (str; ["inf", "Inf", "INF"]) | { 3| assert(decimal.fromStringImpl(str, key)); 3| assert(decimal.coefficient.length == 0); 3| assert(decimal.exponent == decimal.exponent.max); 3| assert(key == DecimalExponentKey.infinity); 3| (cast(double) decimal).should == double.infinity; | } | 1| assert(decimal.fromStringImpl("-inf", key)); 1| assert(decimal.coefficient.length == 0); 1| assert(decimal.exponent == decimal.exponent.max); 1| assert(key == DecimalExponentKey.infinity); 1| should(cast(double) decimal) == -double.infinity; | 1| assert(!decimal.fromStringImpl("3.3.4", key)); 1| assert(!decimal.fromStringImpl("3.4.", key)); 1| assert(decimal.fromStringImpl("4.", key)); 1| assert(!decimal.fromStringImpl(".", key)); 1| assert(decimal.fromStringImpl("0.", key)); 1| assert(decimal.fromStringImpl("00", key)); 1| assert(!decimal.fromStringImpl("0d", key)); |} | |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | import mir.conv: to; 1| Decimal!1 decimal; 1| DecimalExponentKey key; | 1| assert(decimal.fromStringImpl("1.334", key)); 1| assert(key == DecimalExponentKey.dot); 1| assert(cast(double) decimal == 1.334); | 1| assert(decimal.fromStringImpl("+0.334e-5"w, key)); 1| assert(key == DecimalExponentKey.e); 1| assert(cast(double) decimal == 0.334e-5); | 1| assert(decimal.fromStringImpl("-334D-5"d, key)); 1| assert(key == DecimalExponentKey.D); 1| assert(cast(double) decimal == -334e-5); | 1| assert(!decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); | 1| assert(decimal.fromStringImpl(".023", key)); 1| assert(key == DecimalExponentKey.dot); 1| assert(cast(double) decimal == .023); | 1| assert(decimal.fromStringImpl("0E100", key)); 1| assert(key == DecimalExponentKey.E); 1| assert(cast(double) decimal == 0); | | /++ Test that Issue #365 is handled properly +/ 1| assert(decimal.fromStringImpl("123456.e0", key)); 1| assert(key == DecimalExponentKey.e); 1| assert(cast(double) decimal == 123_456.0); | 1| assert(decimal.fromStringImpl("123_456.e0", key)); 1| assert(key == DecimalExponentKey.e); 1| assert(cast(double) decimal == 123_456.0); | 1| assert(decimal.fromStringImpl("123456.E0", key)); 1| assert(key == DecimalExponentKey.E); 1| assert(cast(double) decimal == 123_456.0); | 1| assert(decimal.fromStringImpl("123_456.E0", key)); 1| assert(key == DecimalExponentKey.E); 1| assert(cast(double) decimal == 123_456.0); | 1| assert(decimal.fromStringImpl("123456.d0", key)); 1| assert(key == DecimalExponentKey.d); 1| assert(cast(double) decimal == 123_456.0); | 1| assert(decimal.fromStringImpl("123_456.d0", key)); 1| assert(key == DecimalExponentKey.d); 1| assert(cast(double) decimal == 123_456.0); | 1| assert(decimal.fromStringImpl("123456.D0", key)); 1| assert(key == DecimalExponentKey.D); 1| assert(cast(double) decimal == 123_456.0); | 1| assert(decimal.fromStringImpl("123_456.D0", key)); 1| assert(key == DecimalExponentKey.D); 1| assert(cast(double) decimal == 123_456.0); | | /++ Test invalid examples with the fix introduced for Issue #365 +/ 1| assert(!decimal.fromStringImpl("123_456_.D0", key)); 1| assert(!decimal.fromStringImpl("123_456.DD0", key)); 1| assert(!decimal.fromStringImpl("123_456_.E0", key)); 1| assert(!decimal.fromStringImpl("123_456.EE0", key)); 1| assert(!decimal.fromStringImpl("123456.ED0", key)); 1| assert(!decimal.fromStringImpl("123456E0D0", key)); 1| assert(!decimal.fromStringImpl("123456._D0", key)); 1| assert(!decimal.fromStringImpl("123456_.D0", key)); 1| assert(!decimal.fromStringImpl("123456.E0D0", key)); 1| assert(!decimal.fromStringImpl("123456.D0_", key)); 1| assert(!decimal.fromStringImpl("123456_", key)); | 12| foreach (str; ["-nan", "-NaN", "-NAN"]) | { 3| assert(decimal.fromStringImpl(str, key)); 3| assert(decimal.coefficient.length > 0); 3| assert(decimal.exponent == decimal.exponent.max); 3| assert(decimal.coefficient.sign); 3| assert(key == DecimalExponentKey.nan); 3| assert(cast(double) decimal != cast(double) decimal); | } | 12| foreach (str; ["inf", "Inf", "INF"]) | { 3| assert(decimal.fromStringImpl(str, key)); 3| assert(decimal.coefficient.length == 0); 3| assert(decimal.exponent == decimal.exponent.max); 3| assert(key == DecimalExponentKey.infinity); 3| assert(cast(double) decimal == double.infinity); | } | 1| assert(decimal.fromStringImpl("-inf", key)); 1| assert(decimal.coefficient.length == 0); 1| assert(decimal.exponent == decimal.exponent.max); 1| assert(key == DecimalExponentKey.infinity); 1| assert(cast(double) decimal == -double.infinity); | 1| assert(!decimal.fromStringImpl("3.3.4", key)); 1| assert(!decimal.fromStringImpl("3.4.", key)); 1| assert(decimal.fromStringImpl("4.", key)); 1| assert(!decimal.fromStringImpl(".", key)); 1| assert(decimal.fromStringImpl("0.", key)); 1| assert(decimal.fromStringImpl("00", key)); 1| assert(!decimal.fromStringImpl("0d", key)); |} | |/// |version(mir_bignum_test) |@safe pure @nogc unittest |{ | import mir.math.constant: PI; 1| Decimal!2 decimal = "3.141592653589793378e-40"; // constructor 1| assert(cast(double) decimal == double(PI) / 1e40); |} | | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | // float and double can be used to construct Decimal of any length 1| auto decimal64 = Decimal!1(-1.235e-7); 1| assert(decimal64.exponent == -10); 1| assert(decimal64.coefficient == -1235); | | // real number may need Decimal at least length of 2 1| auto decimal128 = Decimal!2(-1.235e-7L); 1| assert(decimal128.exponent == -10); 1| assert(decimal128.coefficient == -1235); | 1| decimal128 = Decimal!2(1234e3f); 1| assert(decimal128.exponent == 3); 1| assert(decimal128.coefficient == 1234); |} | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ 1| Decimal!3 decimal; 1| DecimalExponentKey key; | 1| assert(decimal.fromStringWithThousandsSeparatorImpl("12,345.678", ',', '.', key)); 1| assert(cast(double) decimal == 12345.678); 1| assert(key == DecimalExponentKey.dot); | 1| assert(decimal.fromStringWithThousandsSeparatorImpl("12,345,678", ',', '.', key, -3)); 1| assert(cast(double) decimal == 12345.678); 1| assert(key == DecimalExponentKey.none); | 1| assert(decimal.fromStringWithThousandsSeparatorImpl("021 345,678", ' ', ',', key)); 1| assert(cast(double) decimal == 21345.678); 1| assert(key == DecimalExponentKey.dot); |} source/mir/bignum/decimal.d is 96% covered <<<<<< EOF # path=source-mir-bignum-fixed.lst |/++ |Note: | The module doesn't provide full arithmetic API for now. |+/ |module mir.bignum.fixed; | |import std.traits; |import mir.bitop; |import mir.utility; | |/++ |Fixed-length unsigned integer. | |Params: | size = size in bits |+/ |struct UInt(size_t size) | if (size % (size_t.sizeof * 8) == 0 && size >= size_t.sizeof * 8) |{ | import mir.bignum.fixed: UInt; | /++ | Payload. The data is located in the target endianness. | +/ | size_t[size / (size_t.sizeof * 8)] data; | | /// 5292| this(size_t N)(auto ref const size_t[N] data) | if (N && N <= this.data.length) | { | version(LittleEndian) 5292| this.data[0 .. N] = data; | else | this.data[$ - N .. $] = data; | } | | /// 4426| this(size_t argSize)(auto ref const UInt!argSize arg) | if (argSize <= size) | { 4426| this(arg.data); | } | | static if (size_t.sizeof == uint.sizeof && data.length % 2 == 0) | /// | this()(auto ref const ulong[data.length / 2] data) | { | if (!__ctfe) | { | this.data = cast(typeof(this.data)) data; | } | else | { | version(LittleEndian) | { | static foreach (i; 0 .. data.length) | { | this.data[i * 2 + 0] = cast(uint) data[i]; | this.data[i * 2 + 1] = cast(uint) (data[i] >> 32); | } | } | else | { | static foreach (i; 0 .. data.length) | { | this.data[i * 2 + 1] = cast(uint) data[i]; | this.data[i * 2 + 0] = cast(uint) (data[i] >> 32); | } | } | } | } | | static if (size >= 64) | /// 1822| this(ulong data) | { | static if (size_t.sizeof == ulong.sizeof) | { 1822| this.data[0] = data; | } | else | { | this.data[0] = cast(uint) data; | this.data[1] = cast(uint) (data >> 32); | } | } | | static if (size < 64) | /// | this(uint data) | { | this.data[0] = data; | } | | /// | this(C)(scope const(C)[] str) @safe pure @nogc | if (isSomeChar!C) | { 3| if (fromStringImpl(str)) 3| return; | static if (__traits(compiles, () @nogc { throw new Exception("Can't parse UInt."); })) | { | import mir.exception: MirException; | throw new MirException("Can't parse UInt!" ~ size.stringof ~ " from string `", str , "`."); | } | else | { | static immutable exception = new Exception("Can't parse UInt!" ~ size.stringof ~ "."); 0000000| throw exception; | } | } | | static if (size == 128) | /// | version(mir_bignum_test) @safe pure @nogc unittest | { | import mir.math.constant: PI; 1| UInt!256 integer = "34010447314490204552169750449563978034784726557588085989975288830070948234680"; // constructor 1| assert(integer == UInt!256.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8")); | } | | /++ | Returns: false in case of overflow or incorrect string. | Precondition: non-empty coefficients. | +/ | bool fromStringImpl(C)(scope const(C)[] str) | scope @trusted pure @nogc nothrow | if (isSomeChar!C) | { | import mir.bignum.low_level_view: BigUIntView; 3| return BigUIntView!size_t(data[]).fromStringImpl(str); | } | | /// | immutable(C)[] toString(C = char)() scope const @safe pure nothrow | if(isSomeChar!C && isMutable!C) | { 2| UInt!size copy = this; 2| auto work = copy.view.normalized; | import mir.bignum.low_level_view: ceilLog10Exp2; 2| C[ceilLog10Exp2(data.length * (size_t.sizeof * 8))] buffer = void; 2| return buffer[$ - work.toStringImpl(buffer) .. $].idup; | } | | static if (size == 128) | /// | version(mir_bignum_test) @safe pure unittest | { 1| auto str = "34010447314490204552169750449563978034784726557588085989975288830070948234680"; 1| auto integer = UInt!256(str); 1| assert(integer.toString == str); | 1| integer = UInt!256.init; 1| assert(integer.toString == "0"); | } | | /// | void toString(C = char, W)(ref scope W w) scope const | if(isSomeChar!C && isMutable!C) | { 1| UInt!size copy = this; 1| auto work = copy.view.normalized; | import mir.bignum.low_level_view: ceilLog10Exp2; 1| C[ceilLog10Exp2(data.length * (size_t.sizeof * 8))] buffer = void; 1| w.put(buffer[$ - work.toStringImpl(buffer) .. $]); | } | | static if (size == 128) | /// Check @nogc toString impl | version(mir_bignum_test) @safe pure @nogc unittest | { | import mir.format: stringBuf; 1| auto str = "34010447314490204552169750449563978034784726557588085989975288830070948234680"; 1| auto integer = UInt!256(str); 2| auto buffer = stringBuf; 1| buffer << integer; 1| assert(buffer.data == str); | } | | /// | enum UInt!size max = ((){UInt!size ret; ret.data = size_t.max; return ret;})(); | | /// | enum UInt!size min = UInt!size.init; | | import mir.bignum.low_level_view: BigUIntView; | | /// | BigUIntView!size_t view() @property pure nothrow @nogc scope return @safe | { 19590| return BigUIntView!size_t(data); | } | | /// | BigUIntView!(const size_t) view() const @property pure nothrow @nogc scope return @safe | { 604| return BigUIntView!(const size_t)(data); | } | | /// | static UInt!size fromHexString(bool allowUnderscores = false)(scope const(char)[] str) | { 93| typeof(return) ret; 93| if (ret.fromHexStringImpl!(char, allowUnderscores)(str)) 93| return ret; | version(D_Exceptions) | { | import mir.bignum.low_level_view: hexStringException; 0000000| throw hexStringException; | } | else | { | import mir.bignum.low_level_view: hexStringErrorMsg; | assert(0, hexStringErrorMsg); | } | } | | /++ | +/ | bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow | if (isSomeChar!C) | { 93| return view.fromHexStringImpl!(C, allowUnderscores)(str); | } | | /// | static UInt!size fromBinaryString(bool allowUnderscores = false)(scope const(char)[] str) | { | typeof(return) ret; | if (ret.fromBinaryStringImpl!(char, allowUnderscores)(str)) | return ret; | version(D_Exceptions) | { | import mir.bignum.low_level_view: binaryStringException; | throw binaryStringException; | } | else | { | import mir.bignum.low_level_view: binaryStringErrorMsg; | assert(0, binaryStringErrorMsg); | } | } | | /++ | +/ | bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow | if (isSomeChar!C) | { | return view.fromBinaryStringImpl!(C, allowUnderscores)(str); | } | | /++ | +/ | auto opEquals(size_t rhsSize)(auto ref const UInt!rhsSize rhs) const | { | static if (rhsSize == size) 2358| return this.data == rhs.data; | else | static if (rhsSize > size) 1| return this.toSize!rhsSize.data == rhs.data; | else 0000000| return this.data == rhs.toSize!size.data; | } | | static if (size >= 64) | /// ditto | auto opEquals(ulong rhs) const | { 1105| return opEquals(UInt!size(rhs)); | } | else | auto opEquals(uint rhs) const | { | return opEquals(UInt!size(rhs)); | } | | /++ | +/ | auto opCmp(UInt!size rhs) const | { 2715| foreach_reverse(i; 0 .. data.length) | { 719| if (this.data[i] < rhs.data[i]) 408| return -1; 311| if (this.data[i] > rhs.data[i]) 211| return +1; | } 13| return 0; | } | | static if (size >= 64) | /// ditto | auto opCmp(ulong rhs) const scope | { 4| return opCmp(UInt!size(rhs)); | } | else | auto opCmp(uint rhs) const scope | { | return opCmp(UInt!size(rhs)); | } | | static if (size >= 64) | /++ | +/ | ref UInt!size opAssign(ulong rhs) scope return | @safe pure nothrow @nogc | { 1| this.data = UInt!size(rhs).data; 1| return this; | } | else | /// | ref UInt!size opAssign(uint rhs) scope return | @safe pure nothrow @nogc | { | this.data = UInt!size(rhs).data; | return this; | } | | /++ | +/ | ref UInt!size opAssign(uint rhsSize)(UInt!rhsSize rhs) scope return | @safe pure nothrow @nogc | { 4425| this.data = UInt!size(rhs).data; 4425| return this; | } | | /++ | `bool overflow = a += b ` and `bool overflow = a -= b` operations. | +/ | bool opOpAssign(string op)(UInt!size rhs, bool overflow = false) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { 2125| return view.opOpAssign!op(rhs.view, overflow); | } | | /// ditto | bool opOpAssign(string op)(size_t rhs) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { 583| return view.opOpAssign!op(rhs); | } | | static if (size_t.sizeof < ulong.sizeof) | /// ditto | bool opOpAssign(string op)(ulong rhs) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { | return opOpAssign!op(UInt!size(rhs)); | } | | /// ditto | bool opOpAssign(string op, uint rsize)(UInt!rsize rhs, bool overflow = false) | @safe pure nothrow @nogc scope | if ((op == "+" || op == "-") && rsize < size) | { 6| return opOpAssign!op(rhs.toSize!size, overflow); | } | | /++ | Returns: overflow value of multiplication | +/ | size_t opOpAssign(string op : "*")(size_t rhs, size_t carry = 0) | @safe pure nothrow @nogc scope | { 1880| return view.opOpAssign!op(rhs, carry); | } | | static if (size_t.sizeof == 4) | /// ditto | auto opOpAssign(string op : "*")(ulong rhs) | @safe pure nothrow @nogc scope | { | return opOpAssign!op(UInt!64(rhs)); | } | | | /++ | Returns: overflow value of multiplication | +/ | void opOpAssign(string op : "*", size_t rhsSize)(UInt!rhsSize rhs) | @safe pure nothrow @nogc scope | if (rhsSize <= size) | { 19| this = extendedMul(this, rhs).toSize!size; | } | | /++ | Performs `uint remainder = (overflow$big) /= scalar` operatrion, where `$` denotes big-endian concatenation. | Precondition: `overflow < rhs` | Params: | rhs = unsigned value to devide by | overflow = initial unsigned overflow | Returns: | unsigned remainder value (evaluated overflow) | +/ | uint opOpAssign(string op : "/")(uint rhs, uint overflow = 0) | @safe pure nothrow @nogc scope | { | assert(overflow < rhs); | auto work = view.normalized; | if (worl.coefficients.length) | return work.opOpAssign!op(rhs, overflow); | return overflow; | } | | /++ | Performs division & extracts the remainder. | Params: | rhs = unsigned value to divide by | Returns: quotient, sets `rhs` to remainder | +/ | ref divMod(size_t rhsSize)(scope ref UInt!rhsSize rhs) | @safe pure nothrow @nogc scope return | { | import mir.bignum.internal.kernel: divMod, divisionRequiredBuffSize; | 7| UInt!size quotient; | 7| auto dividendV = this.view; 7| auto divisorV = rhs.view; 7| divisorV = divisorV.normalized; 7| dividendV = dividendV.normalized; | | import mir.utility: min; | enum vlen = min(rhs.data.length, data.length); 7| size_t[divisionRequiredBuffSize(data.length, vlen)] buffer = void; | 7| divMod( | quotient.data, | divisorV.coefficients, | dividendV.coefficients, | divisorV.coefficients, | buffer); 7| this = quotient; 7| return this; | } | | /++ | Performs `big /= rhs` operation. | Params: | rhs = unsigned value to divide by | Returns: | quotient from division | +/ | ref opOpAssign(string op : "/", size_t rhsSize)(UInt!rhsSize rhs) | @safe pure nothrow @nogc scope return | { 3| return this.divMod(rhs); | } | | /// ditto | ref opOpAssign(string op : "/")(ulong rhs) | @safe pure nothrow @nogc scope return | { 1| return opOpAssign!(op, ulong.sizeof * 8)(UInt!(ulong.sizeof * 8)(rhs)); | } | | /++ | Performs `big %= rhs` operation. | Params: | rhs = unsigned value to divide by | Returns: | remainder from division | +/ | ref opOpAssign(string op : "%", size_t rhsSize)(UInt!rhsSize rhs) | @safe pure nothrow @nogc scope return | { 4| this.divMod(rhs); 4| this = cast(UInt!size)rhs; | } | | /// ditto | ref opOpAssign(string op : "%")(ulong rhs) | @safe pure nothrow @nogc scope | { 4| return opOpAssign!(op, ulong.sizeof * 8)(UInt!(ulong.sizeof * 8)(rhs)); | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("e3251bacb112c88b71ad3f85a970a314"); 1| auto b = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a / b == UInt!128.fromHexString("1")); 1| assert(a % b == UInt!128.fromHexString("36920c8e407c9645d0b611586c0a077")); | } | | /// | ref UInt!size opOpAssign(string op)(UInt!size rhs) nothrow return | if (op == "^" || op == "|" || op == "&") | { | static foreach (i; 0 .. data.length) | mixin(`data[i] ` ~ op ~ `= rhs.data[i];`); 449| return this; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); 1| auto b = UInt!128.fromHexString("e3251bacb112c88b71ad3f85a970a314"); 1| assert((a.opBinary!"|"(b)) == UInt!128.fromHexString("ffbffbeffd1affaf75adfff5abf0a39d")); | } | | /// | ref UInt!size opOpAssign(string op)(size_t rhs) nothrow return scope | if (op == "^" || op == "|" || op == "&") | { | mixin(`view.coefficients[0] ` ~ op ~ `= rhs;`); 78| return this; | } | | static if (size_t.sizeof < ulong.sizeof) | /// ditto | ref opOpAssign(string op)(ulong rhs) return | @safe pure nothrow @nogc scope | if (op == "^" || op == "|" || op == "&") | { | return opOpAssign!op(UInt!size(rhs)); | } | | /// | ref UInt!size opOpAssign(string op)(size_t shift) | @safe pure nothrow @nogc return | if (op == "<<" || op == ">>") | { 7336| auto d = view.coefficients; 7336| assert(shift < size); 7336| auto index = shift / (size_t.sizeof * 8); 7336| auto bs = shift % (size_t.sizeof * 8); 7336| auto ss = size_t.sizeof * 8 - bs; | static if (op == ">>") | { 6829| if (bs) | { 14319| foreach (j; 0 .. data.length - (index + 1)) | { 1226| d[j] = (d[j + index] >>> bs) | (d[j + (index + 1)] << ss); | } | } | else | { 11439| foreach (j; 0 .. data.length - (index + 1)) | { 531| d[j] = d[j + index]; | } | } 6829| d[$ - (index + 1)] = d[$ - 1] >>> bs; 33054| foreach (j; data.length - index .. data.length) | { 4189| d[j] = 0; | } | } | else | { 507| if (bs) | { 1499| foreach_reverse (j; index + 1 .. data.length) | { 10| d[j] = (d[j - index] << bs) | (d[j - (index + 1)] >> ss); | } | } | else | { 64| foreach_reverse (j; index + 1 .. data.length) | { 11| d[j] = d[j - index]; | } | } 507| d[index] = d[0] << bs; 1599| foreach_reverse (j; 0 .. index) | { 39| d[j] = 0; | } | } 7336| return this; | } | | /++ | `auto c = a << b` operation. | +/ | UInt!size opBinary(string op)(size_t rhs) | const @safe pure nothrow @nogc | if (op == "<<" || op == ">>>" || op == ">>") | { 6753| UInt!size ret = this; 6753| ret.opOpAssign!op(rhs); 6753| return ret; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a << 0 == a); 1| assert(a << 4 == UInt!128.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); 1| assert(a << 68 == UInt!128.fromHexString("4a1de7022b0029d00000000000000000")); 1| assert(a << 127 == UInt!128.fromHexString("80000000000000000000000000000000")); 1| assert(a >> 0 == a); 1| assert(a >> 4 == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029")); 1| assert(a >> 68 == UInt!128.fromHexString("afbbfae3cd0aff2")); 1| assert(a >> 127 == UInt!128(1)); | } | | /++ | Binary operations | +/ | template opBinary(string op) | if (op == "^" || op == "|" || op == "&" || op == "+" || op == "-" || op == "*" || op == "/" || op == "%") | { | /// | UInt!size opBinary(size_t rsize)(UInt!rsize rhs) | const @safe pure nothrow @nogc | if (rsize <= size) | { 2445| UInt!size ret = this; 2445| ret.opOpAssign!op(rhs); 2445| return ret; | } | | /// ditto | UInt!size opBinary(ulong rhs) | const @safe pure nothrow @nogc | { 2444| UInt!size ret = this; 2444| ret.opOpAssign!op(rhs); 2444| return ret; | } | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a / UInt!128.fromHexString("5") == UInt!128.fromHexString("23259893f5ceffd49db9f949a0899a1f")); 1| assert(a == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); 1| assert(a % UInt!128.fromHexString("5") == UInt!128.fromHexString("2")); 1| assert(a == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); | 1| assert(a / 5 == UInt!128.fromHexString("23259893f5ceffd49db9f949a0899a1f")); 1| assert(a % 5 == UInt!64.fromHexString("2")); 1| assert(a % 5 == 2); | } | | /// ditto | template opBinaryRight(string op) | if (op == "^" || op == "|" || op == "&" || op == "+" || op == "*") | { | /// | UInt!size opBinaryRight(size_t lsize)(UInt!lsize lhs) | const @safe pure nothrow @nogc | if (lsize < size) | { | UInt!size ret = this; | ret.opOpAssign!op(lhs); | return ret; | } | | /// ditto | UInt!size opBinaryRight(ulong lhs) | const @safe pure nothrow @nogc | { 0000000| UInt!size ret = this; 0000000| ret.opOpAssign!op(lhs); 0000000| return ret; | } | } | | /++ | Shifts left using at most `size_t.sizeof * 8 - 1` bits | +/ | UInt!size smallLeftShift(uint shift) const | { 360| assert(shift < size_t.sizeof * 8); 360| UInt!size ret = this; 360| if (shift) | { 345| auto csh = size_t.sizeof * 8 - shift; | static foreach_reverse (i; 1 .. data.length) | { 392| ret.data[i] = (ret.data[i] << shift) | (ret.data[i - 1] >>> csh); | } 345| ret.data[0] = ret.data[0] << shift; | } 360| return ret; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a.smallLeftShift(4) == UInt!128.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); | } | | /++ | Shifts right using at most `size_t.sizeof * 8 - 1` bits | +/ | UInt!size smallRightShift(uint shift) const | { 1| assert(shift < size_t.sizeof * 8); 1| UInt!size ret = this; 1| if (shift) | { 1| auto csh = size_t.sizeof * 8 - shift; | static foreach (i; 0 .. data.length - 1) | { 1| ret.data[i] = (ret.data[i] >>> shift) | (ret.data[i + 1] << csh); | } 1| ret.data[$ - 1] = ret.data[$ - 1] >>> shift; | } 1| return ret; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a.smallRightShift(4) == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029")); | } | | /++ | +/ | T opCast(T)() const | if (is(Unqual!T == bool)) | { | static foreach (i; 0 .. data.length) | { 733| if (data[i]) 573| return true; | } 4| return false; | } | | /++ | +/ | T opCast(T)() const | if (is(Unqual!T == ulong)) | { | static if (size_t.sizeof == ulong.sizeof) | { 637| return data[0]; | } | else | { | return data[0] | (ulong(data[1]) << 32); | } | } | | /++ | +/ | T opCast(T)() const @safe pure nothrow @nogc | if (is(T == UInt!newSize, uint newSize)) | { | enum newLength = typeof(return).data.length; | static if (newLength <= data.length) | { | return typeof(return)(data[0 .. newLength]); | } | else | { 2| typeof(return) ret; 2| ret.data[0 .. data.length] = data; 2| return ret; | } | } | | /++ | +/ | T opCast(T)() const | if (is(Unqual!T == uint)) | { 940| return cast(uint) data[0]; | } | | /++ | Returns: | the number with shrinked or extended size. | +/ | UInt!newSize toSize(size_t newSize, bool lowerBits = true)() | const @safe pure @nogc nothrow | { 10895| typeof(return) ret; | import mir.utility: min; | enum N = min(ret.data.length, data.length); | static if (lowerBits) | { 10838| ret.data[0 .. N] = data[0 .. N]; | } | else | { 57| ret.data[0 .. N] = data[$ - N .. $]; | } 10895| return ret; | } | | /// | UInt!(size + additionalRightBits) rightExtend(size_t additionalRightBits)() | const @safe pure @nogc nothrow | { | static if (additionalRightBits) | { 4| typeof(return) ret; | version (BigEndian) | ret.data[0 .. data.length] = data; | else 4| ret.data[$ - data.length .. $] = data; 4| return ret; | } | else | { 1| return this; | } | } | | /++ | +/ | bool bt(size_t position) const | @safe pure nothrow @nogc | { 330| assert(position < data.sizeof * 8); 330| return view.bt(position); | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a.bt(127) == 1); 1| assert(a.bt(126) == 0); 1| assert(a.bt(125) == 1); 1| assert(a.bt(124) == 0); 1| assert(a.bt(0) == 1); 1| assert(a.bt(1) == 0); 1| assert(a.bt(2) == 1); 1| assert(a.bt(3) == 1); | } | | /++ | +/ | size_t ctlz() const scope @property | @safe pure nothrow @nogc | { 271| return view.ctlz; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); 1| assert (a.ctlz == 0); 1| a = UInt!128.init; 1| assert (a.ctlz == 128); 1| a = UInt!128.fromHexString("3"); 1| assert (a.ctlz == 126); | } | | /++ | +/ | size_t cttz() const @property | @safe pure nothrow @nogc | { 3| return view.cttz; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { 1| auto a = UInt!128.fromHexString("d"); 1| assert (a.cttz == 0); 1| a = UInt!128.init; 1| assert (a.cttz == 128); 1| a = UInt!128.fromHexString("300000000000000000"); 1| assert (a.cttz == 68); | } | | /++ | +/ | bool signBit() const @property | { 386| return data[$ - 1] >> (size_t.sizeof * 8 - 1); | } | | /// ditto | void signBit(bool value) @property | { | enum signMask = ptrdiff_t.max; 2| data[$ - 1] = (data[$ - 1] & ptrdiff_t.max) | (size_t(value) << (size_t.sizeof * 8 - 1)); | } | | static if (size == 128) | /// | version(mir_bignum_test) | unittest | { 1| auto a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a.signBit); 1| a.signBit = false; 1| assert(a == UInt!128.fromHexString("5fbbfae3cd0aff2714a1de7022b0029d")); 1| assert(!a.signBit); 1| a.signBit = true; 1| assert(a == UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d")); | } |} | |/++ |+/ |UInt!sizeB extendedMulHigh(size_t sizeA, size_t sizeB)(UInt!sizeA a, UInt!sizeB b) | @safe pure nothrow @nogc |{ 2974| return (extendedMul(a, b) >> sizeA).toSize!sizeB; |} | |/++ |+/ |UInt!(sizeA + sizeB) extendedMul(size_t sizeA, size_t sizeB)(UInt!sizeA a, UInt!sizeB b) | @safe pure nothrow @nogc |{ 3660| UInt!(sizeA + sizeB) ret; | enum al = a.data.length; | enum alp1 = a.data.length + 1; 3660| ret.data[0 .. alp1] = extendedMul(a, b.data[0]).data; | static foreach ( i; 1 .. b.data.length) 527| ret.data[i .. i + alp1] = extendedMulAdd(a, b.data[i], UInt!sizeA(ret.data[i .. i + al])).data; 3660| return ret; |} | |/// ditto |UInt!(size + size_t.sizeof * 8) | extendedMul(size_t size)(UInt!size a, size_t b) | @safe pure nothrow @nogc |{ 4187| size_t overflow = a.view *= b; 4187| auto ret = a.toSize!(size + size_t.sizeof * 8); 4187| ret.data[$ - 1] = overflow; 4187| return ret; |} | |/// ditto |auto extendedMul()(ulong a, ulong b) | @safe pure nothrow @nogc |{ | static if (size_t.sizeof == ulong.sizeof) | { | import mir.utility: extMul; 2| auto e = extMul(a, b); 2| return UInt!128([e.low, e.high]); | } | else | { | return extendedMul(UInt!64(a), UInt!64(b)); | } |} | |/// ditto |auto extendedMul()(uint a, uint b) | @safe pure nothrow @nogc |{ | static if (size_t.sizeof == uint.sizeof) | { | import mir.utility: extMul; | auto e = extMul(a, b); | version(LittleEndian) | return UInt!64([e.low, e.high]); | else | return UInt!64([e.high, e.low]); | } | else | { 2| return UInt!64([ulong(a) * b]); | } |} | |/// |version(mir_bignum_test) |@safe pure @nogc |unittest |{ 1| auto a = UInt!128.max; 1| auto b = UInt!256.max; 1| auto c = UInt!384.max; 1| assert(extendedMul(a, a) == UInt!256.max - UInt!128.max - UInt!128.max); 1| assert(extendedMul(a, b) == UInt!384.max - UInt!128.max - UInt!256.max); 1| assert(extendedMul(b, a) == UInt!384.max - UInt!128.max - UInt!256.max); | 1| a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); 1| b = UInt!256.fromHexString("3fe48f2dc8aad570d037bc9b323fc0cfa312fcc2f63cb521bd8a4ca6157ef619"); 1| c = UInt!384.fromHexString("37d7034b86e8d58a9fc564463fcedef9e2ad1126dd2c0f803e61c72852a9917ef74fa749e7936a9e4e224aeeaff91f55"); 1| assert(extendedMul(a, b) == c); 1| assert(extendedMul(b, a) == c); | 1| a = UInt!128.fromHexString("23edf5ff44ee3a4feafc652607aa1eb9"); 1| b = UInt!256.fromHexString("d3d79144b8941fb50c9102e3251bacb112c88b71ad3f85a970a31458ce24297b"); 1| c = UInt!384.fromHexString("1dbb62fe6ca5fed101068eda7222d6a9857633ecdfed37a2d156ff6309065ecc633f31465727677a93a7acbd1dac63e3"); 1| assert(extendedMul(a, b) == c); 1| assert(extendedMul(b, a) == c); |} | |/// ulong |version(mir_bignum_test) |@safe pure @nogc |unittest |{ 1| ulong a = 0xdfbbfae3cd0aff27; 1| ulong b = 0x14a1de7022b0029d; 1| auto c = UInt!128.fromHexString("120827399968ea2a2db185d16e8cc8eb"); 1| assert(extendedMul(a, b) == c); 1| assert(extendedMul(b, a) == c); |} | |/// uint |version(mir_bignum_test) |@safe pure @nogc |unittest |{ 1| uint a = 0xdfbbfae3; 1| uint b = 0xcd0aff27; 1| auto c = UInt!64.fromHexString("b333243de8695595"); 1| assert(extendedMul(a, b) == c); 1| assert(extendedMul(b, a) == c); |} | |/++ |+/ |UInt!(size + size_t.sizeof * 8) | extendedMulAdd(size_t size)(UInt!size a, size_t b, UInt!size c) |{ 527| auto ret = extendedMul(a, b); 527| auto view = ret.view; 527| view.coefficients[$ - 1] += view.topLeastSignificantPart(a.data.length) += c.view; 527| return ret; |} source/mir/bignum/fixed.d is 97% covered <<<<<< EOF # path=source-mir-bignum-fp.lst |/++ |Note: | The module doesn't provide full arithmetic API for now. |+/ |module mir.bignum.fp; | |import mir.bitop; |import mir.utility; |import std.traits; | |package enum half(uint hs) = (){ | import mir.bignum.fixed: UInt; | UInt!hs ret; ret.signBit = true; return ret; |}(); | |/++ |Software floating point number. | |Params: | size = coefficient size in bits |+/ |struct Fp(uint size) | if (size % (uint.sizeof * 8) == 0 && size >= (uint.sizeof * 8)) |{ | import mir.bignum.fixed: UInt; | | bool sign; | long exponent; | UInt!size coefficient; | | /++ | +/ | nothrow 315| this(bool sign, long exponent, UInt!size normalizedCoefficient) | { 315| this.coefficient = normalizedCoefficient; 315| this.exponent = exponent; 315| this.sign = sign; | } | | /++ | Constructs $(LREF Fp) from hardaware floating point number. | Params: | value = Hardware floating point number. Special values `nan` and `inf` aren't allowed. | normalize = flag to indicate if the normalization should be performed. | +/ 81| this(T)(const T value, bool normalize = true) | @safe pure nothrow @nogc | if (isFloatingPoint!T && T.mant_dig <= size) | { | import mir.math.common : fabs; | import mir.math.ieee : frexp, signbit, ldexp; 87| this.sign = value.signbit != 0; 87| if (value == 0) 1| return; 86| T x = value.fabs; 86| if (_expect(!(x < T.infinity), false)) | { 5| this.exponent = this.exponent.max; 5| this.coefficient = x != T.infinity; 5| return; | } 81| int exp; | { | enum scale = T(2) ^^ T.mant_dig; 81| x = frexp(x, exp) * scale; | } | | static if (T.mant_dig < 64) | { 79| auto xx = cast(ulong)cast(long)x; 79| if (normalize) | { 3| auto shift = ctlz(xx); 3| exp -= shift + T.mant_dig + size - 64; 3| xx <<= shift; 3| this.coefficient = UInt!64(xx).rightExtend!(size - 64); | } | else | { 76| this.coefficient = xx; | } | } | else | static if (T.mant_dig == 64) | { 2| auto xx = cast(ulong)x; 2| if (normalize) | { 1| auto shift = ctlz(xx); 1| exp -= shift + T.mant_dig + size - 64; 1| xx <<= shift; 1| this.coefficient = UInt!64(xx).rightExtend!(size - 64); | } | else | { 1| this.coefficient = xx; | } | } | else | { | enum scale = T(2) ^^ 64; | enum scaleInv = 1 / scale; | x *= scaleInv; | long high = cast(long) x; | if (high > x) | --high; | x -= high; | x *= scale; | auto most = ulong(high); | auto least = cast(ulong)x; | version(LittleEndian) | ulong[2] pair = [least, most]; | else | ulong[2] pair = [most, least]; | | if (normalize) | { | this.coefficient = UInt!128(pair).rightExtend!(size - 128); | auto shift = most ? ctlz(most) : ctlz(least) + 64; | exp -= shift + T.mant_dig + size - 64 * (1 + (T.mant_dig > 64)); | this.coefficient <<= shift; | } | else | { | this.coefficient = pair; | } | } 81| if (!normalize) | { 77| exp -= T.mant_dig; 77| int shift = T.min_exp - T.mant_dig - exp; 77| if (shift > 0) | { 0000000| this.coefficient >>= shift; 0000000| exp = T.min_exp - T.mant_dig; | } | } 81| this.exponent = exp; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc nothrow | unittest | { | enum h = -33.0 * 2.0 ^^ -10; 1| auto f = Fp!64(h); 1| assert(f.sign); 1| assert(f.exponent == -10 - (64 - 6)); 1| assert(f.coefficient == 33UL << (64 - 6)); 1| assert(cast(double) f == h); | | // CTFE | static assert(cast(double) Fp!64(h) == h); | 1| f = Fp!64(-0.0); 1| assert(f.sign); 1| assert(f.exponent == 0); 1| assert(f.coefficient == 0); | | // subnormals | static assert(cast(float) Fp!64(float.min_normal / 2) == float.min_normal / 2); | static assert(cast(float) Fp!64(float.min_normal * float.epsilon) == float.min_normal * float.epsilon); | // subnormals | static assert(cast(double) Fp!64(double.min_normal / 2) == double.min_normal / 2); | static assert(cast(double) Fp!64(double.min_normal * double.epsilon) == double.min_normal * double.epsilon); | // subnormals | static if (real.mant_dig <= 64) | { | static assert(cast(real) Fp!128(real.min_normal / 2) == real.min_normal / 2); | static assert(cast(real) Fp!128(real.min_normal * real.epsilon) == real.min_normal * real.epsilon); | } | | enum d = cast(float) Fp!64(float.min_normal / 2, false); | | // subnormals | static assert(cast(float) Fp!64(float.min_normal / 2, false) == float.min_normal / 2, d.stringof); | static assert(cast(float) Fp!64(float.min_normal * float.epsilon, false) == float.min_normal * float.epsilon); | // subnormals | static assert(cast(double) Fp!64(double.min_normal / 2, false) == double.min_normal / 2); | static assert(cast(double) Fp!64(double.min_normal * double.epsilon, false) == double.min_normal * double.epsilon); | // subnormals | static if (real.mant_dig <= 64) | { | static assert(cast(real) Fp!64(real.min_normal / 2, false) == real.min_normal / 2); | static assert(cast(real) Fp!64(real.min_normal * real.epsilon, false) == real.min_normal * real.epsilon); | } | | import mir.bignum.fixed: UInt; | 1| assert(cast(double)Fp!128(+double.infinity) == +double.infinity); 1| assert(cast(double)Fp!128(-double.infinity) == -double.infinity); | | import mir.math.ieee : signbit; 1| auto r = cast(double)Fp!128(-double.nan); 2| assert(r != r && r.signbit); | } | | // static if (size == 128) | // /// Without normalization | // version(mir_bignum_test) | // @safe pure @nogc nothrow | // unittest | // { | // auto f = Fp!64(-33.0 * 2.0 ^^ -10, false); | // assert(f.sign); | // assert(f.exponent == -10 - (double.mant_dig - 6)); | // assert(f.coefficient == 33UL << (double.mant_dig - 6)); | // } | | /++ | +/ 595| this(uint isize)(UInt!isize integer, bool normalizedInteger = false) | nothrow | { | import mir.bignum.fixed: UInt; | static if (isize < size) | { 36| if (normalizedInteger) | { 1| this(false, long(isize) - size, integer.rightExtend!(size - isize)); | } | else | { 35| this(integer.toSize!size, false); | } | } | else | { 559| if (!integer) 0000000| return; 559| this.exponent = isize - size; 559| if (!normalizedInteger) | { 268| auto c = integer.ctlz; 268| integer <<= c; 268| this.exponent -= c; | } | static if (isize == size) | { 265| coefficient = integer; | } | else | { | enum N = coefficient.data.length; | version (LittleEndian) 294| coefficient.data = integer.data[$ - N .. $]; | else | coefficient.data = integer.data[0 .. N]; | enum tailSize = isize - size; 294| auto cr = integer.toSize!tailSize.opCmp(half!tailSize); 509| if (cr > 0 || cr == 0 && coefficient.bt(0)) | { 87| if (auto overflow = coefficient += 1) | { 1| coefficient = half!size; 1| exponent++; | } | } | } | } | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.fixed: UInt; | 1| auto fp = Fp!128(UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); 1| assert(fp.exponent == 0); 1| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); | 1| fp = Fp!128(UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"), true); 1| assert(fp.exponent == 0); 1| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); | 1| fp = Fp!128(UInt!128.fromHexString("ae3cd0aff2714a1de7022b0029d")); 1| assert(fp.exponent == -20); 1| assert(fp.coefficient == UInt!128.fromHexString("ae3cd0aff2714a1de7022b0029d00000")); | 1| fp = Fp!128(UInt!128.fromHexString("e7022b0029d")); 1| assert(fp.exponent == -84); 1| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 1| fp = Fp!128(UInt!64.fromHexString("e7022b0029d")); 1| assert(fp.exponent == -84); 1| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 1| fp = Fp!128(UInt!64.fromHexString("e7022b0029dd0aff"), true); 1| assert(fp.exponent == -64); 1| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029dd0aff0000000000000000")); | 1| fp = Fp!128(UInt!64.fromHexString("e7022b0029d")); 1| assert(fp.exponent == -84); 1| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 1| fp = Fp!128(UInt!192.fromHexString("ffffffffffffffffffffffffffffffff1000000000000000")); 1| assert(fp.exponent == 64); 1| assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); | 1| fp = Fp!128(UInt!192.fromHexString("ffffffffffffffffffffffffffffffff8000000000000000")); 1| assert(fp.exponent == 65); 1| assert(fp.coefficient == UInt!128.fromHexString("80000000000000000000000000000000")); | 1| fp = Fp!128(UInt!192.fromHexString("fffffffffffffffffffffffffffffffe8000000000000000")); 1| assert(fp.exponent == 64); 1| assert(fp.coefficient == UInt!128.fromHexString("fffffffffffffffffffffffffffffffe")); | 1| fp = Fp!128(UInt!192.fromHexString("fffffffffffffffffffffffffffffffe8000000000000001")); 1| assert(fp.exponent == 64); 1| assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); | } | | /// ditto 258| this(ulong value) | { 258| this(UInt!64(value)); | } | | /// 1| this(long value) | { 1| this(ulong(value >= 0 ? value : -value)); 1| this.sign = !(value >= 0); | } | | /// 1| this(int value) | { 1| this(long(value)); | } | | /// 0000000| this(uint value) | { 0000000| this(ulong(value)); | } | | /// | bool isNaN() scope const @property | { 0000000| return this.exponent == this.exponent.max && this.coefficient != this.coefficient.init; | } | | /// | bool isInfinity() scope const @property | { 0000000| return this.exponent == this.exponent.max && this.coefficient == coefficient.init; | } | | /// | bool isSpecial() scope const @property | { 373| return this.exponent == this.exponent.max; | } | | /// | bool opEquals(const Fp rhs) scope const | { 3| if (this.exponent != rhs.exponent) 0000000| return false; 3| if (this.coefficient != rhs.coefficient) 0000000| return false; 3| if (this.coefficient == 0) 2| return !this.isSpecial || this.sign == rhs.sign; 2| if (this.sign != rhs.sign) 0000000| return false; 2| return !this.isSpecial; | } | | /// | ref Fp opOpAssign(string op)(Fp rhs) nothrow scope return | if (op == "*" || op == "/") | { 26| this = this.opBinary!op(rhs); 26| return this; | } | | /// | Fp!(max(size, rhsSize)) opBinary(string op : "*", uint rhsSize)(Fp!rhsSize rhs) nothrow const | { 28| return cast(Fp) .extendedMul(cast()this, rhs); | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.fixed: UInt; | 1| auto a = Fp!128(0, -13, UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d")); 1| auto b = Fp!128(1, 100, UInt!128.fromHexString("e3251bacb112c88b71ad3f85a970a314")); 1| auto fp = a.opBinary!"*"(b); 1| assert(fp.sign); 1| assert(fp.exponent == 100 - 13 + 128); 1| assert(fp.coefficient == UInt!128.fromHexString("c6841dd302415d785373ab6d93712988")); | } | | /// Uses approximate division for now | /// TODO: use full precision division for void when Fp division is ready | Fp!(max(size, rhsSize)) opBinary(string op : "/", uint rhsSize)(Fp!rhsSize rhs) nothrow const | { 1| Fp a = this; | alias b = rhs; 1| auto exponent = a.exponent - b.exponent; 1| a.exponent = b.exponent = -long(size); 1| auto ret = typeof(return)(cast(real) a / cast(real) b); 1| ret.exponent += exponent; 1| return ret; | } | | /// | T opCast(T)() nothrow const | if (is(Unqual!T == bool)) | { 291| return exponent || coefficient; | } | | /// | T opCast(T, bool noSpecial = false, bool noHalf = false)() nothrow const | if (is(T == float) || is(T == double) || is(T == real)) | { | import mir.math.ieee: ldexp; | static if (!noSpecial) | { 19| if (_expect(this.isSpecial, false)) | { 5| T ret = this.coefficient ? T.nan : T.infinity; 5| if (this.sign) 4| ret = -ret; 5| return ret; | } | } 408| auto exp = cast()this.exponent; | static if (size == 32) | { | T c = cast(uint) coefficient; | } | else | static if (size == 64) | { 93| T c = cast(ulong) coefficient; | } | else | { | enum shift = size - T.mant_dig; | enum rMask = (UInt!size(1) << shift) - UInt!size(1); | enum rHalf = UInt!size(1) << (shift - 1); | enum rInc = UInt!size(1) << shift; 315| UInt!size adC = this.coefficient; | static if (!noHalf) | { 315| auto cr = (this.coefficient & rMask).opCmp(rHalf); 315| if ((cr > 0) | (cr == 0) & this.coefficient.bt(shift)) | { 113| if (auto overflow = adC += rInc) | { 19| adC = half!size; 19| exp++; | } | } | } 315| adC >>= shift; 315| exp += shift; 315| T c = cast(ulong) adC; | static if (T.mant_dig > 64) // | { | static assert (T.mant_dig <= 128); | c += ldexp(cast(T) cast(ulong) (adC >> 64), 64); | } | } 408| if (this.sign) 5| c = -c; | static if (exp.sizeof > int.sizeof) | { | import mir.utility: min, max; 408| exp = exp.max(int.min).min(int.max); | } 408| return ldexp(c, cast(int)exp); | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.fixed: UInt; 1| auto fp = Fp!128(1, 100, UInt!128.fromHexString("e3251bacb112cb8b71ad3f85a970a314")); 1| assert(cast(double)fp == -0xE3251BACB112C8p+172); | 1| fp = Fp!128(1, long.max, UInt!128.init); 1| assert(cast(double)fp == -double.infinity); | | import mir.math.ieee : signbit; 1| fp = Fp!128(1, long.max, UInt!128(123)); 1| auto r = cast(double)fp; 2| assert(r != r && r.signbit); | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.fixed: UInt; 1| auto fp = Fp!128(1, 100, UInt!128.fromHexString("e3251bacb112cb8b71ad3f85a970a314")); | static if (real.mant_dig == 64) 1| assert(cast(real)fp == -0xe3251bacb112cb8bp+164L); | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.fixed: UInt; 1| auto fp = Fp!64(1, 100, UInt!64(0xe3251bacb112cb8b)); | version (DigitalMars) | { | // https://issues.dlang.org/show_bug.cgi?id=20963 | assert(cast(double)fp == -0xE3251BACB112C8p+108 | || cast(double)fp == -0xE3251BACB112D0p+108); | } | else | { 1| assert(cast(double)fp == -0xE3251BACB112C8p+108); | } | } |// -0x1.c64a375962259p+163 = |// -0xe.3251bacb112cb8bp+160 = |// -0x1.c64a37596225ap+163 = |// -0xe.3251bacb112cb8bp+160 = | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.fixed: UInt; 1| auto fp = Fp!64(1, 100, UInt!64(0xe3251bacb112cb8b)); | static if (real.mant_dig == 64) 1| assert(cast(real)fp == -0xe3251bacb112cb8bp+100L); | } | | /// | T opCast(T : Fp!newSize, bool noSpecial = false, size_t newSize)() nothrow const | if (newSize != size) | { 291| Fp!newSize ret; 291| ret.sign = this.sign; | | static if (!noSpecial) | { 291| if (_expect(this.isSpecial, false)) | { 1| ret.exponent = ret.exponent.max; 1| ret.coefficient = !!this.coefficient; 1| return ret; | } 290| if (!this) | { 0000000| return ret; | } | } | 290| UInt!size coefficient = this.coefficient; 290| int shift; | // subnormal | | static if (!noSpecial) | { 290| if (this.exponent == this.exponent.min) | { 0000000| shift = cast(int)coefficient.ctlz; 0000000| coefficient <<= shift; | } | } | 290| ret = Fp!newSize(coefficient, true); 290| ret.exponent -= shift; 290| ret.sign = this.sign; | | import mir.checkedint: adds; | /// overflow | | static if (!noSpecial) | { 290| bool overflow; 290| ret.exponent = adds(ret.exponent, this.exponent, overflow); 290| if (_expect(overflow, false)) | { | // overflow 0000000| if (this.exponent > 0) | { 0000000| ret.exponent = ret.exponent.max; 0000000| ret.coefficient = 0u; | } | // underflow | else | { 0000000| ret.coefficient >>= cast(uint)(ret.exponent - exponent.min); 0000000| ret.exponent = ret.coefficient ? ret.exponent.min : 0; | } | } | } | else | { | ret.exponent += this.exponent; | } 290| return ret; | } | | static if (size == 128) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.fixed: UInt; 1| auto fp = cast(Fp!64) Fp!128(UInt!128.fromHexString("afbbfae3cd0aff2784a1de7022b0029d")); 1| assert(fp.exponent == 64); 1| assert(fp.coefficient == UInt!64.fromHexString("afbbfae3cd0aff28")); | 1| assert(Fp!128(-double.infinity) * Fp!128(1) == Fp!128(-double.infinity)); | } |} | |/// |Fp!(coefficientizeA + coefficientizeB) extendedMul(bool noSpecial = false, uint coefficientizeA, uint coefficientizeB)(Fp!coefficientizeA a, Fp!coefficientizeB b) | @safe pure nothrow @nogc |{ | import mir.bignum.fixed: extendedMul; | import mir.checkedint: adds; | 332| typeof(return) ret = void; 332| ret.coefficient = extendedMul(a.coefficient, b.coefficient); | static if (noSpecial) | { 304| ret.exponent = a.exponent + b.exponent; 304| if (!ret.coefficient.signBit) | { 214| ret.exponent -= 1; // check overflow 214| ret.coefficient = ret.coefficient.smallLeftShift(1); | } | } | else | { | // nan * any -> nan | // inf * fin -> inf 28| if (_expect(a.isSpecial | b.isSpecial, false)) | { // set nan 1| ret.exponent = ret.exponent.max; | // nan inf case 1| if (a.isSpecial & b.isSpecial) 0000000| ret.coefficient = a.coefficient | b.coefficient; | } | else | { 27| bool overflow; 27| ret.exponent = adds(a.exponent, b.exponent, overflow); | // exponent underflow -> 0 or subnormal | // overflow -> inf 27| if (_expect(overflow, false)) | { | // overflow 0000000| if (a.exponent > 0) // && b.exponent > 0 is always true | { 0000000| ret.exponent = ret.exponent.max; 0000000| ret.coefficient = 0; | } | // underflow | else // a.exponent < 0 and b.exponent < 0 | { | // TODO: subnormal 0000000| ret.exponent = 0; 0000000| ret.coefficient = 0; | } | } | else 27| if (!ret.coefficient.signBit) | { 16| auto normal = ret.exponent != ret.exponent.min; 16| ret.exponent -= normal; // check overflow 16| ret.coefficient = ret.coefficient.smallLeftShift(normal); | } | } | } 332| ret.sign = a.sign ^ b.sign; 332| return ret; |} | |/// |template fp_log2(T) | if (is(T == float) || is(T == double) || is(T == real)) |{ | /// | T fp_log2(uint size)(Fp!size x) | { | import mir.math.common: log2; 2| auto exponent = x.exponent + size; 2| if (!x.isSpecial) 2| x.exponent = -long(size); 2| return log2(cast(T)x) + exponent; | } |} | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: log2, approxEqual; | import mir.bignum.fp: fp_log2; | 1| double x = 123456789.0e+123; 1| assert(approxEqual(x.Fp!128.fp_log2!double, x.log2)); |} | |/// |template fp_log(T) | if (is(T == float) || is(T == double) || is(T == real)) |{ | /// | T fp_log(uint size)(Fp!size x) | { | import mir.math.constant: LN2; 1| return T(LN2) * fp_log2!T(x); | } |} | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: log, approxEqual; | import mir.bignum.fp: fp_log; | 1| double x = 123456789.0e+123; 1| assert(approxEqual(x.Fp!128.fp_log!double, x.log)); |} source/mir/bignum/fp.d is 89% covered <<<<<< EOF # path=source-mir-bignum-integer.lst |/++ |Note: | The module doesn't provide full arithmetic API for now. |+/ |module mir.bignum.integer; | |import mir.bitop; |import mir.serde: serdeProxy, serdeScoped; |import mir.utility; |import std.traits; | |/++ |Stack-allocated big signed integer. |Params: | size64 = count of 64bit words in coefficient |+/ |@serdeScoped @serdeProxy!(const(char)[]) |struct BigInt(uint size64) | if (size64 && size64 <= ushort.max) |{ | import mir.bignum.low_level_view; | import mir.bignum.fixed; | | /// | bool sign; | /// | uint length; | /// | size_t[ulong.sizeof / size_t.sizeof * size64] data;// = void; | |@safe: | | /// 85| this(uint size)(UInt!size fixedInt) | { 85| this(fixedInt.data); | } | | /// 85| this(uint N)(size_t[N] data) | if (N <= this.data.length) | { | static if (data.length == 0) | { | sign = false; | length = 0; | } | static if (data.length == 1) | { 76| this(data[0]); | } | else | static if (data.length == 2) | { 7| sign = false; 7| this.data[0] = data[0]; 7| this.data[1] = data[1]; 7| this.length = data[1] ? 2 : data[0] != 0; | } | else | { 2| sign = false; 2| this.data[0 .. N] = data; 2| length = data.length; 2| normalize; | } | } | | /// 109| this(ulong data) | { 109| sign = false; | static if (size_t.sizeof == ulong.sizeof) | { 109| length = data != 0; 109| view.coefficients[0] = data; | } | else | { | this.length = !!data + !!(data >> 32); | this.data[0] = cast(uint) data; | this.data[1] = cast(uint) (data >> 32); | } | } | | /// 27| this(long data) | { 27| this(ulong(data < 0 ? -data : data)); 27| this.sign = data < 0; | } | | /// 24| this(int data) | { 24| this(long(data)); | } | | /// 6| this(uint data) | { 6| this(ulong(data)); | } | | /// | this()(scope const(char)[] str) @safe pure @nogc | { 11| if (fromStringImpl(str)) 11| return; | static if (__traits(compiles, () @nogc { throw new Exception("Can't parse BigInt."); })) | { | import mir.exception: MirException; | throw new MirException("Can't parse BigInt!" ~ size64.stringof ~ " from string `", str , "`."); | } | else | { | static immutable exception = new Exception("Can't parse BigInt!" ~ size64.stringof ~ "."); 0000000| throw exception; | } | } | | /// | inout(size_t)[] coefficients()() inout @property scope return | { 1252| return data[0 .. length]; | } | | /// | ref opAssign(ulong data) return scope | { 0000000| __ctor(data); 0000000| return this; | } | | /// | ref opAssign(long data) return scope | { 0000000| __ctor(data); 0000000| return this; | } | | /// | ref opAssign(uint data) return scope | { 6| __ctor(data); 6| return this; | } | | /// | ref opAssign(int data) return scope | { 20| __ctor(data); 20| return this; | } | | /// | ref opAssign(uint rhsSize)(UInt!rhsSize data) return scope | { | __ctor(data); | return this; | } | | static if (size64 == 3) | /// | version(mir_bignum_test) @safe pure @nogc unittest | { | import mir.math.constant: PI; 1| BigInt!4 integer = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; // constructor 1| assert(integer.sign); 1| integer.sign = false; 1| assert(integer == BigInt!4.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8")); | } | | /// | ref opAssign(uint rhsSize64)(auto ref scope const BigInt!rhsSize64 rhs) return | @trusted pure nothrow @nogc 332| in (rhs.length <= this.data.length) | { | static if (size64 == rhsSize64) | { 110| if (&this is &rhs) 0000000| return this; | } 332| this.sign = rhs.sign; 332| this.length = rhs.length; 332| this.data[0 .. length] = rhs.data[0 .. length]; 332| return this; | } | | /// | static BigInt fromBigEndian()(scope const(ubyte)[] data, bool sign = false) | @trusted pure @nogc | { | BigInt ret = void; | if (!ret.copyFromBigEndian(data, sign)) | static immutable bigIntOverflowException = new Exception("BigInt!" ~ size64.stringof ~ ".fromBigEndian: data overflow"); | return ret; | } | | /// | bool copyFromBigEndian()(scope const(ubyte)[] data, bool sign = false) | @trusted pure @nogc | { | while(data.length && data[0] == 0) | data = data[1 .. $]; | if (data.length == 0) | { | this.length = 0; | this.sign = false; | } | else | { | if (data.length > this.data.sizeof) | return false; | this.sign = sign; | this.length = cast(uint) (data.length / size_t.sizeof); | auto tail = data[0 .. data.length % size_t.sizeof]; | data = data[data.length % size_t.sizeof .. $]; | foreach_reverse (ref c; this.coefficients) | { | size_t value; | foreach (j; 0 .. size_t.sizeof) | { | value <<= 8; | value |= data[0]; | data = data[1 .. $]; | } | c = value; | } | assert(data.length == 0); | if (tail.length) | { | this.length++; | size_t value; | foreach (b; tail) | { | value <<= 8; | value |= b; | } | this.data[length - 1] = value; | } | } | return true; | } | | /++ | Returns: false in case of overflow or incorrect string. | Precondition: non-empty coefficients. | +/ | bool fromStringImpl(C)(scope const(C)[] str) | scope @trusted pure @nogc nothrow | if (isSomeChar!C) | { 11| auto work = data[].BigIntView!size_t; 11| if (work.fromStringImpl(str)) | { 11| length = cast(uint) work.coefficients.length; 11| sign = work.sign; 11| return true; | } 0000000| return false; | } | | /// | BigInt copy() @property | { 0000000| BigInt ret; 0000000| ret.sign = sign; 0000000| ret.length = length; 0000000| ret.data = data; 0000000| return ret; | } | | /// | bool opEquals()(auto ref const BigInt rhs) | const @safe pure nothrow @nogc | { 20| return view == rhs.view; | } | | /// | bool opEquals(ulong rhs, bool rhsSign = false) | const @safe pure nothrow @nogc | { 45| if (rhs == 0 && this.length == 0 || this.length == 1 && this.sign == rhsSign && this.data[0] == rhs) 9| return true; | static if (is(size_t == ulong) || size64 == 1) 4| return false; | else | return this.length == 2 && this.data[0] == cast(uint) rhs && this.data[1] == cast(uint) (rhs >> 32); | } | | /// | bool opEquals(long rhs) | const @safe pure nothrow @nogc | { 12| auto sign = rhs < 0; 12| return this.opEquals(sign ? ulong(-rhs) : ulong(rhs), sign); | } | | /// | bool opEquals(uint rhs) | const @safe pure nothrow @nogc | { 1| return opEquals(ulong(rhs), false); | } | | /// | bool opEquals(int rhs) | const @safe pure nothrow @nogc | { 10| return opEquals(long(rhs)); | } | | /++ | +/ | auto opCmp()(auto ref const BigInt rhs) | const @safe pure nothrow @nogc | { 21| return view.opCmp(rhs.view); | } | | /// | BigIntView!size_t view()() scope return @property | { 1071| return typeof(return)(this.data[0 .. this.length], this.sign); | } | | /// | BigIntView!(const size_t) view()() const scope return @property | { 282| return typeof(return)(this.data[0 .. this.length], this.sign); | } | | /// | void normalize()() | { | pragma(inline, false); 278| auto norm = view.normalized; 278| this.length = cast(uint) norm.unsigned.coefficients.length; 278| this.sign = norm.sign; | } | | /++ | +/ | void putCoefficient(size_t value) | { 391| assert(length < data.length); 391| data[length++] = value; | } | | /++ | Performs `size_t overflow = (big += overflow) *= scalar` operatrion. | Params: | rhs = unsigned value to multiply by | overflow = initial overflow value | Returns: | unsigned overflow value | +/ | size_t opOpAssign(string op : "*")(size_t rhs, size_t overflow = 0u) | @safe pure nothrow @nogc | { 430| if (length == 0) 0000000| goto L; 430| overflow = view.unsigned.opOpAssign!op(rhs, overflow); 822| if (overflow && length < data.length) | { | L: 391| putCoefficient(overflow); 391| overflow = 0; | } 430| return overflow; | } | | /++ | Performs `uint remainder = (overflow$big) /= scalar` operatrion, where `$` denotes big-endian concatenation. | Precondition: `overflow < rhs` | Params: | rhs = unsigned value to devide by | overflow = initial unsigned overflow | Returns: | unsigned remainder value (evaluated overflow) | +/ | uint opOpAssign(string op : "/")(uint rhs, uint overflow = 0) | @safe pure nothrow @nogc | { | assert(overflow < rhs); | if (length) | return view.unsigned.opOpAssign!op(rhs, overflow); | return overflow; | } | | /++ | Performs `size_t overflow = (big += overflow) *= fixed` operatrion. | Params: | rhs = unsigned value to multiply by | overflow = initial overflow value | Returns: | unsigned overflow value | +/ | UInt!size opOpAssign(string op : "*", size_t size)(UInt!size rhs, UInt!size overflow = 0) | @safe pure nothrow @nogc | { 1| if (length == 0) 0000000| goto L; 1| overflow = view.unsigned.opOpAssign!op(rhs, overflow); 2| if (overflow && length < data.length) | { | L: | static if (size <= 64) | { | auto o = cast(ulong)overflow; | static if (size_t.sizeof == ulong.sizeof) | { | putCoefficient(o); | overflow = UInt!size.init; | } | else | { | putCoefficient(cast(uint)o); | o >>= 32; | if (length < data.length) | { | putCoefficient(cast(uint)o); | o = 0; | } | overflow = UInt!size(o); | } | } | else | { | do | { 0000000| putCoefficient(cast(size_t)overflow); 0000000| overflow >>= size_t.sizeof * 8; | } 0000000| while(overflow && length < data.length); | } | } 1| return overflow; | } | | /// | ref opOpAssign(string op, size_t size)(UInt!size rhs) | @safe pure nothrow @nogc scope return | if (op == "/" || op == "%") | { 8| BigInt!(size / 64) bigRhs = rhs; 8| return this.opOpAssign!op(bigRhs); | } | | /// ditto | ref opOpAssign(string op)(ulong rhs) | @safe pure nothrow @nogc scope return | if (op == "/" || op == "%") | { | BigInt!1 bigRhs = rhs; | return this.opOpAssign!op(bigRhs); | } | | /// ditto | ref opOpAssign(string op)(long rhs) | @safe pure nothrow @nogc scope return | if (op == "/" || op == "%") | { | BigInt!1 bigRhs = rhs; | return this.opOpAssign!op(bigRhs); | } | | /++ | +/ | ref powMod(uint expSize)(scope ref const BigInt!expSize exponent, scope ref const BigInt modulus) | @safe pure nothrow @nogc return scope 2| in(!exponent.sign) | { 2| return this.powMod(exponent.view.unsigned, modulus); | } | | ///ditto | ref powMod()(scope BigUIntView!(const size_t) exponent, scope ref const BigInt modulus) | @safe pure nothrow @nogc return scope | { | pragma(inline, false); | | import mir.ndslice.topology: bitwise; | 4| if (modulus == 1 || modulus == -1) | { 0000000| this.sign = 0; 0000000| this.length = 0; 0000000| return this; | } | 2| BigInt!(size64 * 2) bas = void; 2| bas = this; 2| BigInt!(size64 * 2) res = void; 2| res = 1u; | 448| foreach (b; exponent.coefficients.bitwise[0 .. $ - exponent.ctlz]) | { 148| bas %= modulus; 148| if (b) | { 65| res *= bas; 65| res %= modulus; | } 148| bas *= bas; | } | 2| this = res; 2| return this; | } | | /// | static if (size64 == 3) | version (mir_bignum_test) | unittest | { 1| BigInt!3 x = 2; 1| BigInt!3 e = 10; 1| BigInt!3 m = 100; | 1| x.powMod(e, m); 1| assert(x == 24); | } | | /// | static if (size64 == 3) | version (mir_bignum_test) | unittest | { 1| BigInt!3 x = 564321; 1| BigInt!3 e = "13763753091226345046315979581580902400000310"; 1| BigInt!3 m = "13763753091226345046315979581580902400000311"; | 1| x.powMod(e, m); 1| assert(x == 1); | } | | /++ | +/ | ref multiply(uint aSize64, uint bSize64) | ( | scope ref const BigInt!aSize64 a, | scope ref const BigInt!bSize64 b, | ) | @safe pure nothrow @nogc scope return | if (size64 >= aSize64 + bSize64) | { | import mir.utility: max; | import mir.bignum.internal.kernel : multiply, karatsubaRequiredBuffSize; | enum sizeM = ulong.sizeof / size_t.sizeof; 214| size_t[max(aSize64 * sizeM, bSize64 * sizeM).karatsubaRequiredBuffSize] buffer = void; 214| this.length = cast(uint) multiply(data, a.coefficients, b.coefficients, buffer); 214| this.sign = (this.length != 0) & (a.sign ^ b.sign); 214| return this; | } | | /// | ref divMod(uint divisorSize64, uint remainderSize = size64) | ( | scope ref const BigInt!divisorSize64 divisor, | scope ref BigInt!size64 quotient, | scope ref BigInt!remainderSize remainder, | ) | const @trusted pure nothrow @nogc scope return | if (remainderSize >= divisorSize64) | { 21| return this.divMod(divisor, quotient, &remainder); | } | | private ref divMod(uint divisorSize64, uint remainderSize = size64) | ( | scope ref const BigInt!divisorSize64 divisor, | scope ref BigInt!size64 quotient, | scope BigInt!remainderSize* remainder = null, | ) | const @trusted pure nothrow @nogc scope return | if (remainderSize >= divisorSize64) | { | import mir.bignum.internal.kernel : divMod, divisionRequiredBuffSize; | | pragma(inline, false); | 244| if (divisor.length == 0) | assert(0, "Zero BigInt divizor"); 244| if (divisor.coefficients[$ - 1] == 0) | assert(0, "Denormalized BigInt divizor"); | 244| if (this.length < divisor.length) | { 7| if (remainder !is null) | { 6| if (&this !is remainder) 0000000| *remainder = this; 6| remainder.sign = 0; | } | | static if (size64 == remainderSize) 7| if ("ient is remainder) 6| return this; | 1| quotient.sign = 0; 1| quotient.length = 0; | 1| return this; | } | | enum sizeM = ulong.sizeof / size_t.sizeof; | enum vlen = min(divisorSize64, size64); 237| size_t[divisionRequiredBuffSize(size64 * sizeM, vlen * sizeM)] buffer = void; | 237| quotient.length = cast(uint) divMod( | quotient.data, | remainder !is null ? remainder.data[] : null, | this.coefficients, | divisor.coefficients, | buffer, | ); | 237| quotient.sign = (this.sign ^ divisor.sign) && quotient.length; | 237| if (remainder !is null) | { 233| remainder.sign = 0; 233| remainder.length = divisor.length; 233| remainder.normalize; | } | 237| return this; | } | | /++ | Performs `this %= rhs` and `this /= rhs` operations. | Params: | rhs = value to divide by | Returns: | remainder or quotient from the truncated division | +/ | ref opOpAssign(string op, size_t rhsSize64)(scope const ref BigInt!rhsSize64 rhs) | @safe pure nothrow @nogc return | if (op == "/" || op == "%") | { | enum isRem = op == "%"; 223| return this.divMod(rhs, this, isRem ? &this : null); | } | | /++ | Performs `this %= rhs` and `this /= rhs` operations. | Params: | rhs = value to divide by | Returns: | remainder or quotient from the truncated division | +/ | ref opOpAssign(string op : "*", size_t rhsSize64)(scope const ref BigInt!rhsSize64 rhs) | @safe pure nothrow @nogc return | { 214| BigInt!(size64 + rhsSize64) c = void; 214| c.multiply(this, rhs); 214| this = c; 214| return this; | } | | /// | static if (size64 == 3) | version (mir_bignum_test) | unittest | { 1| BigInt!32 x = "236089459999051800787306800176765276560685597708945239133346035689205694959466543423391020917332149321603397284295007899190053323478336179162578944"; 1| BigInt!32 y = "19095614279677503764429420557677401943131308638097701560446870251856566051181587499424174939645900335127490246389509326965738171086235365599977209919032320327138167362675030414072140005376"; 1| BigInt!32 z = "4508273263639244391466225874448166762388283627989411942887789415132291146444880491003321910228134369483394456858712486391978856464207606191606690798518090459546799016472580324664149788791167494389789813815605288815981925073283892089331019170542792502117265455020551819803771537458327634120582677504637693661973404860326560198184402944"; 1| x *= y; 1| assert(x == z); | } | | /++ | Performs `size_t overflow = big *= fixed` operatrion. | Params: | rhs = unsigned value to multiply by | Returns: | overflow | +/ | bool opOpAssign(string op, size_t rhsSize64)(ref const BigInt!rhsSize64 rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 22| return opOpAssign!op(rhs.view); | } | | /// ditto | bool opOpAssign(string op)(BigIntView!(const size_t) rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 44| sizediff_t diff = length - rhs.coefficients.length; 44| if (diff < 0) | { 8| auto oldLength = length; 8| length = cast(int)rhs.coefficients.length; 8| coefficients[oldLength .. $] = 0; | } | else 36| if (rhs.coefficients.length == 0) 1| return false; 43| auto thisView = view; 43| auto overflow = thisView.opOpAssign!op(rhs); 43| this.sign = thisView.sign; 43| if (overflow) | { 0000000| if (length < data.length) | { 0000000| putCoefficient(overflow); 0000000| overflow = false; | } | } | else | { 43| normalize; | } 43| return overflow; | } | | /// ditto | bool opOpAssign(string op)(ulong rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { | import mir.ndslice.bignum.fixed: UInt; | return this.opOpAssign!op(rhs.UInt!64.view.signed); | } | | /// ditto | bool opOpAssign(string op)(uint rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { | return this.opOpAssign!op(ulong(rhs)); | } | | /// ditto | bool opOpAssign(string op)(long rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { | import mir.bignum.fixed: UInt; | auto sign = rhs < 0; | rhs = sign ? -rhs : rhs; | return this.opOpAssign!op(rhs.UInt!64.view.normalized.signed(sign)); | } | | /// ditto | bool opOpAssign(string op)(int rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { | return this.opOpAssign!op(long(rhs)); | } | | /++ | +/ | static BigInt fromHexString(bool allowUnderscores = false)(scope const(char)[] str) | @trusted pure | { 25| BigInt ret; 25| if (ret.fromHexStringImpl!(char, allowUnderscores)(str)) 25| return ret; | version(D_Exceptions) 0000000| throw hexStringException; | else | assert(0, hexStringErrorMsg); | } | | /++ | +/ | bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow | if (isSomeChar!C) | { 25| auto work = BigIntView!size_t(data); 25| auto ret = work.fromHexStringImpl!(C, allowUnderscores)(str); 25| if (ret) | { 25| length = cast(uint)work.unsigned.coefficients.length; 25| sign = work.sign; | } 25| return ret; | } | | /++ | +/ | static BigInt fromBinaryString(bool allowUnderscores = false)(scope const(char)[] str) | @trusted pure | { 1| BigInt ret; 1| if (ret.fromBinaryStringImpl!(char, allowUnderscores)(str)) 1| return ret; | version(D_Exceptions) 0000000| throw binaryStringException; | else | assert(0, binaryStringErrorMsg); | } | | static if (size64 == 3) | /// | version(mir_bignum_test) @safe pure @nogc unittest | { 1| BigInt!4 integer = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; // constructor 1| assert(integer == BigInt!4.fromBinaryString("-100101100110001001110110010001110101010010101100000111000011011000010011000010111111000100111001011111001101101111101010100011000001000011000001110001110011010011001001011101010010010101101001010101111011101001111101110011101111110010011100000010110111000")); | } | | /++ | +/ | bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow | if (isSomeChar!C) | { 1| auto work = BigIntView!size_t(data); 1| auto ret = work.fromBinaryStringImpl!(C, allowUnderscores)(str); 1| if (ret) | { 1| length = cast(uint)work.unsigned.coefficients.length; 1| sign = work.sign; | } 1| return ret; | } | | /// | ref pow()(ulong degree) | { | BigInt!size64 bas = void; | bas = this; | this = 1u; | | while (degree) | { | if (degree & 1) | this *= bas; | bas *= bas; | degree >>= 1; | } | return this; | } | | /// | bool mulPow5()(ulong degree) | { | import mir.bignum.internal.dec2float: MaxWordPow5; | // assert(approxCanMulPow5(degree)); 43| if (length == 0) 5| return false; | enum n = MaxWordPow5!size_t; | enum wordInit = size_t(5) ^^ n; 38| size_t word = wordInit; 38| size_t overflow; | 467| while(degree) | { 429| if (degree >= n) | { 403| degree -= n; | } | else | { 26| word = 1; 258| do word *= 5; 258| while(--degree); | } 429| overflow |= this *= word; | } 38| return overflow != 0; | } | | /// | ref BigInt opOpAssign(string op)(size_t shift) | @safe pure nothrow @nogc return | if (op == "<<" || op == ">>") | { 68| auto index = shift / (size_t.sizeof * 8); 68| auto bs = shift % (size_t.sizeof * 8); 68| auto ss = size_t.sizeof * 8 - bs; | static if (op == ">>") | { 3| if (index >= length) | { 0000000| length = 0; 0000000| return this; | } 3| auto d = view.coefficients; 3| if (bs) | { 27| foreach (j; 0 .. d.length - (index + 1)) | { 6| d[j] = (d[j + index] >>> bs) | (d[j + (index + 1)] << ss); | } | } | else | { 0000000| foreach (j; 0 .. d.length - (index + 1)) | { 0000000| d[j] = d[j + index]; | } | } 3| auto most = d[$ - (index + 1)] = d[$ - 1] >>> bs; 3| length -= index + (most == 0); | } | else | { 130| if (index >= data.length || length == 0) | { 5| length = 0; 5| return this; | } | 60| if (bs) | { 52| auto most = coefficients[$ - 1] >> ss; 52| length += index; 52| if (length < data.length) | { 48| if (most) | { 12| length++; 12| coefficients[$ - 1] = most; 12| length--; | } | } | else | { 4| length = data.length; | } | 52| auto d = view.coefficients; 908| foreach_reverse (j; index + 1 .. length) | { 376| d[j] = (d[j - index] << bs) | (d[j - (index + 1)] >> ss); | } 52| d[index] = d[0] << bs; 52| if (length < data.length) 48| length += most != 0; | } | else | { 8| length = cast(uint) min(length + index, cast(uint)data.length); 8| auto d = view.coefficients; 84| foreach_reverse (j; index .. length) | { 30| d[j] = d[j - index]; | } | } 60| view.coefficients[0 .. index] = 0; | } 63| return this; | } | | /// | T opCast(T)() const | if (isFloatingPoint!T && isMutable!T) | { 3| return view.opCast!T; | } | | /// | T opCast(T, bool nonZero = false)() const | if (is(T == long) || is(T == int)) | { 2| return this.view.opCast!(T, nonZero); | } | | /++ | Returns: overflow flag | +/ | bool copyFrom(W)(scope const(W)[] coefficients, bool sign = false) | if (__traits(isUnsigned, W)) | { | static if (W.sizeof > size_t.sizeof) | { | return this.copyFrom(cast(BigIntView!(const size_t))view, sign); | } | else | { 32| this.sign = sign; 32| auto dest = cast(W[])data; 32| auto overflow = dest.length < coefficients.length; 32| auto n = overflow ? dest.length : coefficients.length; 32| this.length = cast(uint)(n / (size_t.sizeof / W.sizeof)); 32| dest[0 .. n] = coefficients[0 .. n]; | static if (size_t.sizeof / W.sizeof > 1) | { 1| if (auto tail = n % (size_t.sizeof / W.sizeof)) | { 1| this.length++; 1| auto shift = ((size_t.sizeof / W.sizeof) - tail) * (W.sizeof * 8); 1| auto value = this.coefficients[$ - 1]; 1| value <<= shift; 1| value >>= shift; 1| this.coefficients[$ - 1] = value; | } | } 32| return overflow; | } | } | | /// | immutable(C)[] toString(C = char)() scope const @safe pure nothrow | if(isSomeChar!C && isMutable!C) | { 2| C[ceilLog10Exp2(data.length * (size_t.sizeof * 8)) + 1] buffer = void; 2| BigInt copy = this; 2| auto len = copy.view.unsigned.toStringImpl(buffer); 2| if (sign) 1| buffer[$ - ++len] = '-'; 2| return buffer[$ - len .. $].idup; | } | | static if (size64 == 3) | /// | version(mir_bignum_test) @safe pure unittest | { 1| auto str = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; 1| auto integer = BigInt!4(str); 1| assert(integer.toString == str); | 1| integer = BigInt!4.init; 1| assert(integer.toString == "0"); | } | | /// | void toString(C = char, W)(ref scope W w) scope const | if(isSomeChar!C && isMutable!C) | { 1| C[ceilLog10Exp2(data.length * (size_t.sizeof * 8)) + 1] buffer = void; 1| BigInt copy = this; 1| auto len = copy.view.unsigned.toStringImpl(buffer); 1| if (sign) 1| buffer[$ - ++len] = '-'; 1| w.put(buffer[$ - len .. $]); | } | | /// | size_t bitLength()() const @property | { 41| return length == 0 ? 0 : length * size_t.sizeof * 8 - data[length - 1].ctlz; | } | | /// | size_t ctlz()() const @property | { 20| return data.sizeof * 8 - bitLength; | } |} | |/// Check @nogc toString impl |version(mir_bignum_test) @safe pure @nogc unittest |{ | import mir.format; 1| auto str = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; 1| auto integer = BigInt!4(str); 2| auto buffer = stringBuf; 1| buffer << integer; 1| assert(buffer.data == str); |} | |/// |version(mir_bignum_test) |unittest |{ | import mir.bignum.fixed; | import mir.bignum.low_level_view; | | { 1| auto a = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); 1| auto b = UInt!128.fromHexString("f79a222050aaeaaa417fa25a2ac93291"); | | // ca3d7e25aebe687b 168dcef32d0bb2f0 | import mir.format; 1| assert((a %= b) == BigInt!4.fromHexString("bf4c87424431d21563f23b1fc00d75ac")); 1| a = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); 1| a /= b; 1| assert(a == BigInt!4.fromHexString("ca3d7e25aebe687b7cc1b250b44690fb"), a.data.text); | } | | { 1| auto a = BigInt!4.fromHexString("7fff000080000000000000000000"); 1| auto b = UInt!128.fromHexString("80000000000000000001"); | 1| assert((a /= b) == BigInt!4.fromHexString("fffe0000")); 1| a = BigInt!4.fromHexString("7fff000080000000000000000000"); 1| assert((a %= b) == BigInt!4.fromHexString("7fffffffffff00020000")); | } | | { 1| auto a = BigInt!16.fromHexString("76d053cdcc87ec8c9455c375d6a08c799fad73cf07415e70af5dfacaff4bd306647a7cceb98839cce89ae65900938821564fd2af3c9d881c172264bb17e3530ce79b938d5eb7ffec558be43ab5b684978417c5053fb8df63fc65c9efd8b2e86469c53259509eb597f81647930f24ef05a79bfecf04e5ec52414c6a3f7481d533"); 1| auto b = UInt!128.fromHexString("9c5c1aa6ad7ad18065a3a74598e27bee"); | 1| assert((a /= b) == BigInt!16.fromHexString("c2871f2b07522bda1e63de12850d2208bb242c716b5739d6744ee1d9c937b8d765d3742e18785d08c2405e5c83f3c875d5726d09dfaee29e813675a4f91bfee01e8cbbbca9588325d54cf2a625faffde4d8709e0517f786f609d8ce6997e0e71d2f976ae169b0c4be7a7dba3135af96c")); 1| a = BigInt!16.fromHexString("76d053cdcc87ec8c9455c375d6a08c799fad73cf07415e70af5dfacaff4bd306647a7cceb98839cce89ae65900938821564fd2af3c9d881c172264bb17e3530ce79b938d5eb7ffec558be43ab5b684978417c5053fb8df63fc65c9efd8b2e86469c53259509eb597f81647930f24ef05a79bfecf04e5ec52414c6a3f7481d533"); 1| assert((a %= b) == BigInt!16.fromHexString("85d81587a8b62af1874315d26ebf0ecb")); | } | | { 1| auto a = BigInt!4.fromHexString("DEADBEEF"); 1| auto b = UInt!256.fromHexString("18aeff9fa4aace484a9f8f9002cdf38fa6e53fc0f6c035051dc86931c1c08316"); | 1| assert((a /= b) == 0); 1| a = BigInt!4.fromHexString("DEADBEEF"); 1| assert((a %= b) == 0xDEADBEEF); | } | | void test(const long av, const long bv) | { 1| auto a = BigInt!4(av); 1| const b = BigInt!4(bv); 1| a /= b; 1| assert(a == av / bv); 1| a = BigInt!4(av); 1| a %= b; 1| assert(a == av % bv); | } | | { 1| auto av = 0xDEADBEEF; 1| auto bv = 0xDEAD; 1| test(+av, +bv); | // test(+av, -bv); | // test(-av, +bv); | // test(+av, +bv); | } |} | |/// |version(mir_bignum_test) |unittest |{ | import mir.bignum.fixed; | import mir.bignum.low_level_view; | 1| auto a = BigInt!4.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8"); 1| auto b = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); 1| auto c = BigInt!4.fromHexString("7869dd864619cace5953a09910327b3971413e6aa5f417fa25a2ac93291b941f"); 1| c.sign = true; 1| assert(a != b); 1| assert(a < b); 1| a -= b; 1| assert(a.sign); 1| assert(a == c); 1| a -= a; 1| assert(!a.sign); 1| assert(!a.length); | 1| auto d = BigInt!4.fromHexString("0de1a911c6dc8f90a7169a148e65d22cf34f6a8254ae26362b064f26ac44218a"); 1| assert((b *= 0x7869dd86) == 0x5c019770); 1| assert(b == d); | 1| d = BigInt!4.fromHexString("856eeb23e68cc73f2a517448862cdc97e83f9dfa23768296724bf00fda7df32a"); 1| auto o = b *= UInt!128.fromHexString("f79a222050aaeaaa417fa25a2ac93291"); 1| assert(o == UInt!128.fromHexString("d6d15b99499b73e68c3331eb0f7bf16")); 1| assert(b == d); | 1| d = BigInt!4.fromHexString("d"); // initial value 1| d.mulPow5(60); 1| c = BigInt!4.fromHexString("81704fcef32d3bd8117effd5c4389285b05d"); 1| assert(d == c); | 1| d >>= 80; 1| c = BigInt!4.fromHexString("81704fcef32d3bd8"); 1| assert(d == c); | 1| c = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); 1| d = BigInt!4.fromHexString("9935cea0707f79a222050aaeaaaed17feb7aa76999d700000000000000000000"); 1| c <<= 80; 1| assert(d == c); 1| c >>= 80; 1| c <<= 84; 1| d <<= 4; 1| assert(d == c); 1| assert(c != b); 1| b.sign = true; 1| assert(!c.copyFrom(b.coefficients, b.sign)); 1| assert(c == b); 1| b >>= 18; 1| auto bView = cast(BigIntView!ushort)b.view; 1| assert(!c.copyFrom(bView.coefficients[0 .. $ - 1], bView.sign)); 1| assert(c == b); |} | |version(mir_bignum_test) |@safe pure @nogc unittest |{ 1| BigInt!4 i = "-0"; 1| assert(i.coefficients.length == 0); 1| assert(!i.sign); 1| assert(cast(long) i == 0); |} source/mir/bignum/integer.d is 91% covered <<<<<< EOF # path=source-mir-bignum-internal-dec2float.lst |module mir.bignum.internal.dec2float; | |version (LDC) import ldc.attributes: optStrategy; |else struct optStrategy { string opt; } | |template MaxWordPow5(T) |{ | static if (is(T == ubyte)) | enum MaxWordPow5 = 3; | else | static if (is(T == ushort)) | enum MaxWordPow5 = 6; | else | static if (is(T == uint)) | enum MaxWordPow5 = 13; | else | static if (is(T == ulong)) | enum MaxWordPow5 = 27; | else | static assert(0); |} | |template MaxFpPow5(T) |{ | static if (T.mant_dig == 24) | enum MaxFpPow5 = 6; | else | static if (T.mant_dig == 53) | enum MaxFpPow5 = 10; | else | static if (T.mant_dig == 64) | enum MaxFpPow5 = 27; | else | static if (T.mant_dig == 113) | enum MaxFpPow5 = 48; | else | static assert(0, "floating point format isn't supported"); |} | |@safe pure nothrow @nogc: | |alias decimalTo(T : float) = decimalToFloat32; |alias decimalTo(T : double) = decimalToFloat64; |alias decimalTo(T : real) = decimalToReal; | |alias binaryTo(T : float) = binaryToFloat32; |alias binaryTo(T : double) = binaryToFloat64; |alias binaryTo(T : real) = binaryToReal; | |private float binaryToFloat32(scope const size_t[] coefficients, long exponent = 0) |{ | pragma(inline, false); 30| return binaryToFloatImpl!float(coefficients, exponent); |} | |private double binaryToFloat64(scope const size_t[] coefficients, long exponent = 0) |{ | pragma(inline, false); 54| return binaryToFloatImpl!double(coefficients, exponent); |} | |private real binaryToReal(scope const size_t[] coefficients, long exponent = 0) |{ | pragma(inline, real.mant_dig == double.mant_dig); | static if (real.mant_dig == double.mant_dig) | return binaryToFloat64(coefficients, exponent); | else 14| return binaryToFloatImpl!real(coefficients, exponent); |} | |private float decimalToFloat32(scope const ulong coefficient, long exponent) |{ | pragma(inline, false); 67| return decimalToFloatImpl!float(coefficient, exponent); |} | |private double decimalToFloat64(scope const ulong coefficient, long exponent) |{ | pragma(inline, false); 104| return decimalToFloatImpl!double(coefficient, exponent); |} | |private real decimalToReal(scope const ulong coefficient, long exponent) |{ | pragma(inline, real.mant_dig == double.mant_dig); | static if (real.mant_dig == double.mant_dig) | return decimalToFloat64(coefficient, exponent); | else 66| return decimalToFloatImpl!real(coefficient, exponent); |} | |private float decimalToFloat32(scope const size_t[] coefficients, long exponent) |{ | pragma(inline, false); 78| return decimalToFloatImpl!float(coefficients, exponent); |} | |private double decimalToFloat64(scope const size_t[] coefficients, long exponent) |{ | pragma(inline, false); 120| return decimalToFloatImpl!double(coefficients, exponent); |} | |private real decimalToReal(scope const size_t[] coefficients, long exponent) |{ | pragma(inline, real.mant_dig == double.mant_dig); | static if (real.mant_dig == double.mant_dig) | return decimalToFloat64(coefficients, exponent); | else 77| return decimalToFloatImpl!real(coefficients, exponent); |} | |T decimalToFloatImpl(T)(ulong coefficient, long exponent) | if (is(T == float) || is(T == double) || is(T == real)) |{ | version (LDC) | pragma(inline, true); | | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp, extendedMul; | import mir.utility: _expect; | | enum wordBits = T.mant_dig < 64 ? 64 : 128; | enum ulong half = (1UL << (wordBits - T.mant_dig - 1)); | enum bigHalf = UInt!128([0UL, half]); | static if (T.mant_dig < 64) | enum bigMask = (UInt!128(1UL) << (64 - T.mant_dig)) - 1; | | static if (T.mant_dig > 64) | enum ulong mask = (1UL << (128 - T.mant_dig)) - 1; | else | static if (T.mant_dig == 64) | enum ulong mask = ulong.max; | else | enum ulong mask = (1UL << (64 - T.mant_dig)) - 1; | 237| if (coefficient == 0) 0000000| return 0; | | version (TeslAlgoM) {} else 462| if (_expect(-ExponentM <= exponent && exponent <= ExponentM, true)) | { 225| auto c = coefficient.Fp!64; | 225| T approx = void; | 225| if (exponent < 0) | { | version (all) | {{ 141| auto z = c.extendedMul!true(_load!wordBits(exponent)); 141| approx = z.opCast!(T, true); 141| long bitsDiff = (cast(ulong) z.opCast!(Fp!wordBits).coefficient & mask) - half; 141| uint slop = 3; 233| if (_expect(approx > T.min_normal && (bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) 88| return approx; | }} | 53| if (-exponent <= MaxFpPow5!T) | { 0000000| auto e = _load!wordBits(-exponent); 0000000| return coefficient / e.opCast!(T, true); | } | | static if (T.mant_dig < 64) | {{ 53| auto z = c.extendedMul!true(_load!128(exponent)); 53| approx = z.opCast!(T, true); 53| auto bitsDiff = (z.opCast!(Fp!128).coefficient & bigMask) - bigHalf; 53| if (bitsDiff.signBit) 53| bitsDiff = UInt!128.init - bitsDiff; 53| uint slop = 3; 57| if (_expect(approx > T.min_normal && bitsDiff > slop, true)) 4| return approx; | }} | | } | else | { | version (all) | {{ 84| auto z = c.extendedMul!true(_load!wordBits(exponent)); 84| approx = z.opCast!(T, true); | 84| if (exponent <= 27) // exact exponent | { 42| return approx; | } | 42| long bitsDiff = (cast(ulong) z.opCast!(Fp!wordBits).coefficient & mask) - half; 42| uint slop; 84| if (_expect(approx > T.min_normal && (bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) 42| return approx; | }} | | static if (T.mant_dig < 64) | {{ 0000000| auto z = c.extendedMul!true(_load!128(exponent)); 0000000| approx = z.opCast!(T, true); | 0000000| if (exponent <= 55) // exact exponent | { 0000000| return approx; | } | 0000000| auto bitsDiff = (z.opCast!(Fp!128).coefficient & bigMask) - bigHalf; 0000000| if (bitsDiff.signBit) 0000000| bitsDiff = UInt!128.init - bitsDiff; 0000000| uint slop; 0000000| if (_expect(approx > T.min_normal && bitsDiff > slop, true)) 0000000| return approx; | }} | | } | } 61| size_t[ulong.sizeof / size_t.sizeof] coefficients; 61| coefficients[0] = cast(size_t) coefficient; | static if (coefficients.length == 2) | coefficients[1] = cast(size_t) (coefficient >> 32); | static if (coefficients.length == 1) 61| return algorithmM!T(coefficients, exponent); | else | return algorithmM!T(coefficients[0 .. 1 + (coefficient > uint.max)], exponent); |} | |private T decimalToFloatImpl(T)(scope const size_t[] coefficients, long exponent) | @safe | if ((is(T == float) || is(T == double) || is(T == real))) 538| in (coefficients.length == 0 || coefficients[$ - 1]) |{ | version (LDC) | pragma(inline, true); | | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp, extendedMul; | import mir.utility: _expect; | | enum wordBits = T.mant_dig < 64 ? 64 : 128; | enum ulong half = (1UL << (wordBits - T.mant_dig - 1)); | static if (T.mant_dig > 64) | enum ulong mask = (1UL << (128 - T.mant_dig)) - 1; | else | static if (T.mant_dig == 64) | enum ulong mask = ulong.max; | else | enum ulong mask = (1UL << (64 - T.mant_dig)) - 1; | 275| if (coefficients.length < 1) 12| return 0; | 263| if (coefficients.length == 1) 237| return decimalTo!T(coefficients[0], exponent); | | static if (size_t.sizeof == uint.sizeof) | { | if (coefficients.length == 2) | return decimalTo!T(coefficients[0] | (ulong(coefficients[1]) << 32), exponent); | } | | version (TeslAlgoM) {} else 52| if (_expect(-ExponentM <= exponent && exponent <= ExponentM, true)) | { 26| auto c = coefficients.binaryToFp!wordBits; 26| auto z = c.extendedMul!true(_load!wordBits(exponent)); 26| auto approx = z.opCast!(T, true); 26| auto slop = 1 + 3 * (exponent < 0); 26| long bitsDiff = (cast(ulong) z.opCast!(Fp!wordBits).coefficient & mask) - half; | 52| if (_expect(approx > T.min_normal && (bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) 19| return approx; | } 7| return algorithmM!T(coefficients, exponent); |} | |private enum LOG2_10 = 0x3.5269e12f346e2bf924afdbfd36bf6p0; | |private template bigSize(T) | if ((is(T == float) || is(T == double) || is(T == real))) |{ | static if (T.mant_dig < 64) | { | enum size_t bigSize = 128; | } | else | { | enum size_t bits = T.max_exp - T.min_exp + T.mant_dig; | enum size_t bigSize = bits / 64 + bits % 64 + 1; | } |} | |@optStrategy("minsize") |private T algorithmM(T)(scope const size_t[] coefficients, long exponent) | if ((is(T == float) || is(T == double) || is(T == real))) 68| in (coefficients.length) |{ | pragma(inline, false); | | import mir.bitop: ctlz; | import mir.bignum.fp: Fp; | import mir.bignum.integer: BigInt; | import mir.math.common: log2, ceil; | import mir.math.ieee: ldexp, nextUp; | 68| BigInt!(bigSize!T) u = void; 68| BigInt!(bigSize!T) v = void; 68| BigInt!(bigSize!T) q = void; 68| BigInt!(bigSize!T) r = void; | 68| if (coefficients.length == 0) 0000000| return 0; | | // if no overflow 68| if (exponent >= 0) | { 4| if (3 * exponent + coefficients.length * size_t.sizeof * 8 - ctlz(coefficients[$ - 1]) - 1 > T.max_exp) 0000000| return T.infinity; 4| if (exponent == 0) 4| return coefficients.binaryTo!T; 0000000| u.copyFrom(coefficients); 0000000| u.mulPow5(exponent); 0000000| return u.coefficients.binaryTo!T(exponent); | } | 64| auto log2_u = coefficients.binaryTo!T.log2; 64| auto log2_v = cast(T)(-LOG2_10) * exponent; 64| sizediff_t k = cast(sizediff_t) ceil(log2_u - log2_v); | 64| k -= T.mant_dig; | 64| if (k < T.min_exp - T.mant_dig) | { 60| if (k + T.mant_dig + 1 < T.min_exp - T.mant_dig) 44| return 0; 16| k = T.min_exp - T.mant_dig; | } | else 4| if (k > T.max_exp) | { 0000000| if (k - 2 > T.max_exp) 0000000| return T.infinity; 0000000| k = T.max_exp; | } | 20| if(u.copyFrom(coefficients)) 0000000| return T.nan; 20| if (k < 0) | { 19| if (u.ctlz < -k) 0000000| return T.nan; 19| u <<= -k; | } | 20| if (log2_v >= bigSize!T * 64) 0000000| return T.nan; | 20| v = 1; 20| v.mulPow5(-exponent); 20| v <<= cast(int)-exponent + (k > 0 ? k : 0); | 20| sizediff_t s; | for(;;) | { 21| u.divMod(v, q, r); | 21| s = cast(int) q.bitLength - T.mant_dig; 21| assert(k >= T.min_exp - T.mant_dig); 21| if (s == 0) 6| break; | 15| if (s < 0) | { 14| if (k == T.min_exp - T.mant_dig) 14| break; 0000000| k--; | } | else | { 1| if (k == T.max_exp) 0000000| return T.infinity; 1| k++; | } | 1| if ((s < 0 ? u : v).ctlz == 0) 0000000| return T.nan; 1| (s < 0 ? u : v) <<= 1; | } | 20| sizediff_t cmp; 20| if (s <= 0) | { 20| u = v; 20| u -= r; 20| cmp = r.opCmp(u); | } | else | { 0000000| cmp = s - 1 - q.view.unsigned.cttz; 0000000| if (cmp == 0) // half 0000000| cmp += r != 0; 0000000| q >>= s; 0000000| k += s; | } 20| auto z = q.coefficients.binaryTo!T.ldexp(cast(int)k); 29| return cmp < 0 || cmp == 0 && !q.view.unsigned.bt(0) ? z : nextUp(z); |} | |private T binaryToFloatImpl(T)(scope const size_t[] coefficients, long exponent) | @safe | if ((is(T == float) || is(T == double) || is(T == real))) 188| in (coefficients.length == 0 || coefficients[$ - 1]) |{ | version (LDC) | pragma(inline, true); | | enum md = T.mant_dig; | enum b = size_t.sizeof * 8; | enum n = md / b + (md % b != 0); | enum s = n * b; | 98| if (coefficients.length == 0) 8| return 0; | 90| if (exponent > T.max_exp) 0000000| return T.infinity; | 90| auto fp = coefficients.binaryToFp!(s, s - md); 90| fp.exponent += exponent; 90| return fp.opCast!(T, true, true); |} | | |package(mir.bignum) auto binaryToFp(uint coefficientSize, uint internalRoundLastBits = 0) | (scope const(size_t)[] coefficients) | if (internalRoundLastBits < size_t.sizeof * 8) 129| in (coefficients.length) 129| in (coefficients[$ - 1]) |{ | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp; | import mir.bitop: ctlz; | import mir.utility: _expect; | | version (LDC) | pragma(inline, true); | 129| Fp!coefficientSize ret; | | enum N = ret.coefficient.data.length; 129| sizediff_t size = coefficients.length * (size_t.sizeof * 8); 129| sizediff_t expShift = size - coefficientSize; 129| ret.exponent = expShift; 129| if (_expect(expShift <= 0, true)) | { | static if (N == 1) | { 78| ret.coefficient.data[0] = coefficients[$ - 1]; | } | else | { 14| ret.coefficient.data[$ - coefficients.length .. $] = coefficients; | } 92| auto c = cast(uint) ctlz(ret.coefficient.view.coefficients[$ - 1]); 92| ret.exponent -= c; 92| ret.coefficient = ret.coefficient.smallLeftShift(c); | } | else | { 37| UInt!(coefficientSize + size_t.sizeof * 8) holder; | | static if (N == 1) | { 31| holder.data[0] = coefficients[$ - 2]; 31| holder.data[1] = coefficients[$ - 1]; | } | else | { | import mir.utility: min; 6| auto minLength = min(coefficients.length, holder.data.length); 6| holder.data[$ - minLength .. $] = coefficients[$ - minLength .. $]; | } | 37| auto c = cast(uint) ctlz(holder.data[$ - 1]); 37| ret.exponent -= c; 37| holder = holder.smallLeftShift(c); 37| ret.coefficient = holder.toSize!(coefficientSize, false); 37| auto tail = holder.data[0]; | | bool nonZeroTail() | { 8| while(_expect(coefficients[0] == 0, false)) | { 0000000| coefficients = coefficients[1 .. $]; 0000000| assert(coefficients.length); | } 8| return coefficients.length > N + 1; | } | | static if (internalRoundLastBits) | { | enum half = size_t(1) << (internalRoundLastBits - 1); | enum mask0 = (size_t(1) << internalRoundLastBits) - 1; 6| auto tail0 = ret.coefficient.data[0] & mask0; 6| ret.coefficient.data[0] &= ~mask0; 6| auto condInc = tail0 >= half 5| && ( tail0 > half 0000000| || tail 0000000| || (ret.coefficient.data[0] & 1) 0000000| || nonZeroTail); | } | else | { | enum half = cast(size_t)sizediff_t.min; 31| auto condInc = tail >= half 16| && ( tail > half 9| || (ret.coefficient.data[0] & 1) 8| || nonZeroTail); | } | 37| if (condInc) | { | enum inc = size_t(1) << internalRoundLastBits; 13| if (auto overflow = ret.coefficient += inc) | { | import mir.bignum.fp: half; 1| ret.coefficient = half!coefficientSize; 1| ret.exponent++; | } | } | } 129| return ret; |} | |private enum ExponentM = 512; | |private auto _load(uint size : 64)(long e) @trusted 362| in (-ExponentM < e && e < ExponentM) |{ | version (LDC) | pragma(inline, true); | | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp; | import mir.bignum.internal.dec2float_table; | 181| auto idx = cast(sizediff_t)e - min_p10_e; 181| auto p10coeff = p10_coefficients_h[idx]; 181| auto p10exp = p10_exponents[idx]; 181| return Fp!64(false, p10exp, UInt!64(p10coeff)); |} | |private auto _load(uint size : 128)(long e) @trusted 246| in (-ExponentM < e && e < ExponentM) |{ | version (LDC) | pragma(inline, true); | | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp; | import mir.bignum.internal.dec2float_table; | | static assert(min_p10_e <= -ExponentM); | static assert(max_p10_e >= ExponentM); 123| auto idx = cast(sizediff_t)e - min_p10_e; 123| ulong h = p10_coefficients_h[idx]; 123| ulong l = p10_coefficients_l[idx]; 123| if (l >= cast(ulong)long.min) 30| h--; 123| auto p10coeff = UInt!128(cast(ulong[2])[l, h]); 123| auto p10exp = p10_exponents[idx] - 64; 123| return Fp!128(false, p10exp, p10coeff); |} | |version(mir_test) |unittest |{ | import mir.bignum.fp; | import mir.bignum.fixed; | import mir.test; 1| ulong[2] data = [ulong.max - 2, 1]; 1| auto coefficients = cast(size_t[])data[]; 1| if (coefficients[$ - 1] == 0) 0000000| coefficients = coefficients[0 .. $ - 1]; 1| coefficients.binaryToFp!64.should == Fp!64(false, 1, UInt!64(0xFFFFFFFFFFFFFFFE)); 1| coefficients.binaryToFp!128.should == Fp!128(false, -63, UInt!128([0x8000000000000000, 0xFFFFFFFFFFFFFFFE])); |} source/mir/bignum/internal/dec2float.d is 80% covered <<<<<< EOF # path=source-mir-bignum-internal-dec2float_table.lst |/++ |Tables of approximations of powers of ten. |DO NOT MODIFY: Generated by `etc/dec2float_table.py` |+/ |module mir.bignum.internal.dec2float_table; | |enum min_p10_e = -512; |enum max_p10_e = 512; | |static immutable short[1025] p10_exponents = [ | -1764, | -1761, | -1758, | -1754, | -1751, | -1748, | -1744, | -1741, | -1738, | -1734, | -1731, | -1728, | -1724, | -1721, | -1718, | -1714, | -1711, | -1708, | -1705, | -1701, | -1698, | -1695, | -1691, | -1688, | -1685, | -1681, | -1678, | -1675, | -1671, | -1668, | -1665, | -1661, | -1658, | -1655, | -1651, | -1648, | -1645, | -1641, | -1638, | -1635, | -1631, | -1628, | -1625, | -1621, | -1618, | -1615, | -1612, | -1608, | -1605, | -1602, | -1598, | -1595, | -1592, | -1588, | -1585, | -1582, | -1578, | -1575, | -1572, | -1568, | -1565, | -1562, | -1558, | -1555, | -1552, | -1548, | -1545, | -1542, | -1538, | -1535, | -1532, | -1528, | -1525, | -1522, | -1519, | -1515, | -1512, | -1509, | -1505, | -1502, | -1499, | -1495, | -1492, | -1489, | -1485, | -1482, | -1479, | -1475, | -1472, | -1469, | -1465, | -1462, | -1459, | -1455, | -1452, | -1449, | -1445, | -1442, | -1439, | -1435, | -1432, | -1429, | -1425, | -1422, | -1419, | -1416, | -1412, | -1409, | -1406, | -1402, | -1399, | -1396, | -1392, | -1389, | -1386, | -1382, | -1379, | -1376, | -1372, | -1369, | -1366, | -1362, | -1359, | -1356, | -1352, | -1349, | -1346, | -1342, | -1339, | -1336, | -1332, | -1329, | -1326, | -1323, | -1319, | -1316, | -1313, | -1309, | -1306, | -1303, | -1299, | -1296, | -1293, | -1289, | -1286, | -1283, | -1279, | -1276, | -1273, | -1269, | -1266, | -1263, | -1259, | -1256, | -1253, | -1249, | -1246, | -1243, | -1239, | -1236, | -1233, | -1229, | -1226, | -1223, | -1220, | -1216, | -1213, | -1210, | -1206, | -1203, | -1200, | -1196, | -1193, | -1190, | -1186, | -1183, | -1180, | -1176, | -1173, | -1170, | -1166, | -1163, | -1160, | -1156, | -1153, | -1150, | -1146, | -1143, | -1140, | -1136, | -1133, | -1130, | -1127, | -1123, | -1120, | -1117, | -1113, | -1110, | -1107, | -1103, | -1100, | -1097, | -1093, | -1090, | -1087, | -1083, | -1080, | -1077, | -1073, | -1070, | -1067, | -1063, | -1060, | -1057, | -1053, | -1050, | -1047, | -1043, | -1040, | -1037, | -1034, | -1030, | -1027, | -1024, | -1020, | -1017, | -1014, | -1010, | -1007, | -1004, | -1000, | -997, | -994, | -990, | -987, | -984, | -980, | -977, | -974, | -970, | -967, | -964, | -960, | -957, | -954, | -950, | -947, | -944, | -940, | -937, | -934, | -931, | -927, | -924, | -921, | -917, | -914, | -911, | -907, | -904, | -901, | -897, | -894, | -891, | -887, | -884, | -881, | -877, | -874, | -871, | -867, | -864, | -861, | -857, | -854, | -851, | -847, | -844, | -841, | -838, | -834, | -831, | -828, | -824, | -821, | -818, | -814, | -811, | -808, | -804, | -801, | -798, | -794, | -791, | -788, | -784, | -781, | -778, | -774, | -771, | -768, | -764, | -761, | -758, | -754, | -751, | -748, | -744, | -741, | -738, | -735, | -731, | -728, | -725, | -721, | -718, | -715, | -711, | -708, | -705, | -701, | -698, | -695, | -691, | -688, | -685, | -681, | -678, | -675, | -671, | -668, | -665, | -661, | -658, | -655, | -651, | -648, | -645, | -642, | -638, | -635, | -632, | -628, | -625, | -622, | -618, | -615, | -612, | -608, | -605, | -602, | -598, | -595, | -592, | -588, | -585, | -582, | -578, | -575, | -572, | -568, | -565, | -562, | -558, | -555, | -552, | -549, | -545, | -542, | -539, | -535, | -532, | -529, | -525, | -522, | -519, | -515, | -512, | -509, | -505, | -502, | -499, | -495, | -492, | -489, | -485, | -482, | -479, | -475, | -472, | -469, | -465, | -462, | -459, | -455, | -452, | -449, | -446, | -442, | -439, | -436, | -432, | -429, | -426, | -422, | -419, | -416, | -412, | -409, | -406, | -402, | -399, | -396, | -392, | -389, | -386, | -382, | -379, | -376, | -372, | -369, | -366, | -362, | -359, | -356, | -353, | -349, | -346, | -343, | -339, | -336, | -333, | -329, | -326, | -323, | -319, | -316, | -313, | -309, | -306, | -303, | -299, | -296, | -293, | -289, | -286, | -283, | -279, | -276, | -273, | -269, | -266, | -263, | -259, | -256, | -253, | -250, | -246, | -243, | -240, | -236, | -233, | -230, | -226, | -223, | -220, | -216, | -213, | -210, | -206, | -203, | -200, | -196, | -193, | -190, | -186, | -183, | -180, | -176, | -173, | -170, | -166, | -163, | -160, | -157, | -153, | -150, | -147, | -143, | -140, | -137, | -133, | -130, | -127, | -123, | -120, | -117, | -113, | -110, | -107, | -103, | -100, | -97, | -93, | -90, | -87, | -83, | -80, | -77, | -73, | -70, | -67, | -63, | -60, | -57, | -54, | -50, | -47, | -44, | -40, | -37, | -34, | -30, | -27, | -24, | -20, | -17, | -14, | -10, | -7, | -4, | 0, | 3, | 6, | 10, | 13, | 16, | 20, | 23, | 26, | 30, | 33, | 36, | 39, | 43, | 46, | 49, | 53, | 56, | 59, | 63, | 66, | 69, | 73, | 76, | 79, | 83, | 86, | 89, | 93, | 96, | 99, | 103, | 106, | 109, | 113, | 116, | 119, | 123, | 126, | 129, | 132, | 136, | 139, | 142, | 146, | 149, | 152, | 156, | 159, | 162, | 166, | 169, | 172, | 176, | 179, | 182, | 186, | 189, | 192, | 196, | 199, | 202, | 206, | 209, | 212, | 216, | 219, | 222, | 226, | 229, | 232, | 235, | 239, | 242, | 245, | 249, | 252, | 255, | 259, | 262, | 265, | 269, | 272, | 275, | 279, | 282, | 285, | 289, | 292, | 295, | 299, | 302, | 305, | 309, | 312, | 315, | 319, | 322, | 325, | 328, | 332, | 335, | 338, | 342, | 345, | 348, | 352, | 355, | 358, | 362, | 365, | 368, | 372, | 375, | 378, | 382, | 385, | 388, | 392, | 395, | 398, | 402, | 405, | 408, | 412, | 415, | 418, | 422, | 425, | 428, | 431, | 435, | 438, | 441, | 445, | 448, | 451, | 455, | 458, | 461, | 465, | 468, | 471, | 475, | 478, | 481, | 485, | 488, | 491, | 495, | 498, | 501, | 505, | 508, | 511, | 515, | 518, | 521, | 524, | 528, | 531, | 534, | 538, | 541, | 544, | 548, | 551, | 554, | 558, | 561, | 564, | 568, | 571, | 574, | 578, | 581, | 584, | 588, | 591, | 594, | 598, | 601, | 604, | 608, | 611, | 614, | 617, | 621, | 624, | 627, | 631, | 634, | 637, | 641, | 644, | 647, | 651, | 654, | 657, | 661, | 664, | 667, | 671, | 674, | 677, | 681, | 684, | 687, | 691, | 694, | 697, | 701, | 704, | 707, | 711, | 714, | 717, | 720, | 724, | 727, | 730, | 734, | 737, | 740, | 744, | 747, | 750, | 754, | 757, | 760, | 764, | 767, | 770, | 774, | 777, | 780, | 784, | 787, | 790, | 794, | 797, | 800, | 804, | 807, | 810, | 813, | 817, | 820, | 823, | 827, | 830, | 833, | 837, | 840, | 843, | 847, | 850, | 853, | 857, | 860, | 863, | 867, | 870, | 873, | 877, | 880, | 883, | 887, | 890, | 893, | 897, | 900, | 903, | 907, | 910, | 913, | 916, | 920, | 923, | 926, | 930, | 933, | 936, | 940, | 943, | 946, | 950, | 953, | 956, | 960, | 963, | 966, | 970, | 973, | 976, | 980, | 983, | 986, | 990, | 993, | 996, | 1000, | 1003, | 1006, | 1009, | 1013, | 1016, | 1019, | 1023, | 1026, | 1029, | 1033, | 1036, | 1039, | 1043, | 1046, | 1049, | 1053, | 1056, | 1059, | 1063, | 1066, | 1069, | 1073, | 1076, | 1079, | 1083, | 1086, | 1089, | 1093, | 1096, | 1099, | 1102, | 1106, | 1109, | 1112, | 1116, | 1119, | 1122, | 1126, | 1129, | 1132, | 1136, | 1139, | 1142, | 1146, | 1149, | 1152, | 1156, | 1159, | 1162, | 1166, | 1169, | 1172, | 1176, | 1179, | 1182, | 1186, | 1189, | 1192, | 1196, | 1199, | 1202, | 1205, | 1209, | 1212, | 1215, | 1219, | 1222, | 1225, | 1229, | 1232, | 1235, | 1239, | 1242, | 1245, | 1249, | 1252, | 1255, | 1259, | 1262, | 1265, | 1269, | 1272, | 1275, | 1279, | 1282, | 1285, | 1289, | 1292, | 1295, | 1298, | 1302, | 1305, | 1308, | 1312, | 1315, | 1318, | 1322, | 1325, | 1328, | 1332, | 1335, | 1338, | 1342, | 1345, | 1348, | 1352, | 1355, | 1358, | 1362, | 1365, | 1368, | 1372, | 1375, | 1378, | 1382, | 1385, | 1388, | 1392, | 1395, | 1398, | 1401, | 1405, | 1408, | 1411, | 1415, | 1418, | 1421, | 1425, | 1428, | 1431, | 1435, | 1438, | 1441, | 1445, | 1448, | 1451, | 1455, | 1458, | 1461, | 1465, | 1468, | 1471, | 1475, | 1478, | 1481, | 1485, | 1488, | 1491, | 1494, | 1498, | 1501, | 1504, | 1508, | 1511, | 1514, | 1518, | 1521, | 1524, | 1528, | 1531, | 1534, | 1538, | 1541, | 1544, | 1548, | 1551, | 1554, | 1558, | 1561, | 1564, | 1568, | 1571, | 1574, | 1578, | 1581, | 1584, | 1587, | 1591, | 1594, | 1597, | 1601, | 1604, | 1607, | 1611, | 1614, | 1617, | 1621, | 1624, | 1627, | 1631, | 1634, | 1637, |]; | |static immutable align(16) ulong[1025] p10_coefficients_h = [ | 0x9049EE32DB23D21C, | 0xB45C69BF91ECC6A4, | 0xE173842F7667F84C, | 0x8CE8329DAA00FB30, | 0xB0223F45148139FC, | 0xDC2ACF1659A1887B, | 0x899AC16DF804F54D, | 0xAC0171C9760632A0, | 0xD701CE3BD387BF48, | 0x866120E56434D78D, | 0xA7F9691EBD420D70, | 0xD1F7C3666C9290CC, | 0x833ADA2003DB9A80, | 0xA40990A804D2811F, | 0xCD0BF4D206072167, | 0x8027790343C474E1, | 0xA031574414B59219, | 0xC83DAD1519E2F69F, | 0xFA4D185A605BB447, | 0x9C702F387C3950AC, | 0xC38C3B069B47A4D7, | 0xF46F49C842198E0D, | 0x98C58E1D294FF8C8, | 0xBEF6F1A473A3F6FA, | 0xEEB4AE0D908CF4B9, | 0x9530ECC87A5818F3, | 0xBA7D27FA98EE1F30, | 0xE91C71F93F29A6FC, | 0x91B1C73BC77A085E, | 0xB61E390AB9588A75, | 0xE3A5C74D67AEAD12, | 0x8E479C9060CD2C2C, | 0xB1D983B479007736, | 0xDE4FE4A197409504, | 0x8AF1EEE4FE885D22, | 0xADAE6A9E3E2A746B, | 0xD91A0545CDB51186, | 0x87B0434BA0912AF4, | 0xA99C541E88B575B1, | 0xD40369262AE2D31D, | 0x848221B7DACDC3F2, | 0xA5A2AA25D18134EE, | 0xCF0B54AF45E1822A, | 0x816714ED8BACF15A, | 0xA1C0DA28EE982DB1, | 0xCA3110B32A3E391D, | 0xFCBD54DFF4CDC764, | 0x9DF6550BF9009C9F, | 0xC573EA4EF740C3C6, | 0xF6D0E4E2B510F4B8, | 0x9A428F0DB12A98F3, | 0xC0D332D11D753F30, | 0xF107FF8564D28EFC, | 0x96A4FFB35F03995D, | 0xBC4E3FA036C47FB5, | 0xEB61CF8844759FA2, | 0x931D21B52AC983C5, | 0xB7E46A22757BE4B6, | 0xE5DD84AB12DADDE4, | 0x8FAA72EAEBC8CAAF, | 0xB3950FA5A6BAFD5A, | 0xE07A538F1069BCB1, | 0x8C4C74396A4215EE, | 0xAF5F9147C4D29B6A, | 0xDB377599B6074245, | 0x8902A98011C4896B, | 0xAB4353E01635ABC6, | 0xD61428D81BC316B7, | 0x85CC99871159EE32, | 0xA73FBFE8D5B069BF, | 0xD10FAFE30B1C842F, | 0x82A9CDEDE6F1D29D, | 0xA354416960AE4744, | 0xCC2951C3B8D9D916, | 0xFF33A634A7104F5B, | 0x9F8047E0E86A3199, | 0xC76059D92284BDFF, | 0xF938704F6B25ED7F, | 0x9BC34631A2F7B46F, | 0xC2B417BE0BB5A18B, | 0xF3611DAD8EA309EE, | 0x981CB28C7925E635, | 0xBE23DF2F976F5FC2, | 0xEDACD6FB7D4B37B2, | 0x948C065D2E4F02CF, | 0xB9AF07F479E2C383, | 0xE81AC9F1985B7464, | 0x9110BE36FF3928BF, | 0xB554EDC4BF0772EE, | 0xE2AA2935EEC94FAA, | 0x8DAA59C1B53DD1CA, | 0xB114F032228D463D, | 0xDD5A2C3EAB3097CC, | 0x8A585BA72AFE5EDF, | 0xACEE7290F5BDF697, | 0xD82A0F35332D743D, | 0x871A49813FFC68A6, | 0xA8E0DBE18FFB82D0, | 0xD31912D9F3FA6384, | 0x83EFABC8387C7E32, | 0xA4EB96BA469B9DBF, | 0xCE267C68D842852E, | 0x80D80DC18729933D, | 0xA10E1131E8F3F80C, | 0xC951957E6330F60F, | 0xFBA5FADDFBFD3393, | 0x9D47BCCABD7E403C, | 0xC499ABFD6CDDD04B, | 0xF5C016FCC815445E, | 0x99980E5DFD0D4ABB, | 0xBFFE11F57C509D69, | 0xEFFD9672DB64C4C4, | 0x95FE7E07C91EFAFA, | 0xBB7E1D89BB66B9B9, | 0xEA5DA4EC2A406827, | 0x927A87139A684118, | 0xB71928D88102515E, | 0xE4DF730EA142E5B6, | 0x8F0BA7E924C9CF92, | 0xB2CE91E36DFC4376, | 0xDF82365C497B5454, | 0x8BB161F9ADED14B4, | 0xAE9DBA78196859E1, | 0xDA4529161FC2705A, | 0x886B39ADD3D98638, | 0xAA86081948CFE7C6, | 0xD5278A1F9B03E1B8, | 0x8538B653C0E26D13, | 0xA686E3E8B11B0858, | 0xD0289CE2DD61CA6D, | 0x8219620DCA5D1E84, | 0xA29FBA913CF46625, | 0xCB47A9358C317FAF, | 0xFE199382EF3DDF9B, | 0x9ECFFC31D586ABC1, | 0xC683FB3E4AE856B1, | 0xF824FA0DDDA26C5D, | 0x9B171C48AA8583BA, | 0xC1DCE35AD526E4A9, | 0xF2541C318A709DD3, | 0x9774919EF68662A4, | 0xBD51B606B427FB4D, | 0xECA623886131FA20, | 0x93E7D6353CBF3C54, | 0xB8E1CBC28BEF0B69, | 0xE71A3EB32EEACE43, | 0x9070672FFD52C0EA, | 0xB48C80FBFCA77124, | 0xE1AFA13AFBD14D6E, | 0x8D0DC4C4DD62D064, | 0xB05135F614BB847E, | 0xDC65837399EA659D, | 0x89BF722840327F82, | 0xAC2F4EB2503F1F63, | 0xD73B225EE44EE73B, | 0x8684F57B4EB15085, | 0xA82632DA225DA4A6, | 0xD22FBF90AAF50DD0, | 0x835DD7BA6AD928A2, | 0xA4354DA9058F72CA, | 0xCD42A11346F34F7D, | 0x8049A4AC0C5811AE, | 0xA05C0DD70F6E161A, | 0xC873114CD3499BA0, | 0xFA8FD5A0081C0288, | 0x9C99E58405118195, | 0xC3C05EE50655E1FA, | 0xF4B0769E47EB5A79, | 0x98EE4A22ECF3188C, | 0xBF29DCABA82FDEAE, | 0xEEF453D6923BD65A, | 0x9558B4661B6565F8, | 0xBAAEE17FA23EBF76, | 0xE95A99DF8ACE6F54, | 0x91D8A02BB6C10594, | 0xB64EC836A47146FA, | 0xE3E27A444D8D98B8, | 0x8E6D8C6AB0787F73, | 0xB208EF855C969F50, | 0xDE8B2B66B3BC4724, | 0x8B16FB203055AC76, | 0xADDCB9E83C6B1794, | 0xD953E8624B85DD79, | 0x87D4713D6F33AA6C, | 0xA9C98D8CCB009506, | 0xD43BF0EFFDC0BA48, | 0x84A57695FE98746D, | 0xA5CED43B7E3E9188, | 0xCF42894A5DCE35EA, | 0x818995CE7AA0E1B2, | 0xA1EBFB4219491A1F, | 0xCA66FA129F9B60A7, | 0xFD00B897478238D1, | 0x9E20735E8CB16382, | 0xC5A890362FDDBC63, | 0xF712B443BBD52B7C, | 0x9A6BB0AA55653B2D, | 0xC1069CD4EABE89F9, | 0xF148440A256E2C77, | 0x96CD2A865764DBCA, | 0xBC807527ED3E12BD, | 0xEBA09271E88D976C, | 0x93445B8731587EA3, | 0xB8157268FDAE9E4C, | 0xE61ACF033D1A45DF, | 0x8FD0C16206306BAC, | 0xB3C4F1BA87BC8697, | 0xE0B62E2929ABA83C, | 0x8C71DCD9BA0B4926, | 0xAF8E5410288E1B6F, | 0xDB71E91432B1A24B, | 0x892731AC9FAF056F, | 0xAB70FE17C79AC6CA, | 0xD64D3D9DB981787D, | 0x85F0468293F0EB4E, | 0xA76C582338ED2622, | 0xD1476E2C07286FAA, | 0x82CCA4DB847945CA, | 0xA37FCE126597973D, | 0xCC5FC196FEFD7D0C, | 0xFF77B1FCBEBCDC4F, | 0x9FAACF3DF73609B1, | 0xC795830D75038C1E, | 0xF97AE3D0D2446F25, | 0x9BECCE62836AC577, | 0xC2E801FB244576D5, | 0xF3A20279ED56D48A, | 0x9845418C345644D7, | 0xBE5691EF416BD60C, | 0xEDEC366B11C6CB8F, | 0x94B3A202EB1C3F39, | 0xB9E08A83A5E34F08, | 0xE858AD248F5C22CA, | 0x91376C36D99995BE, | 0xB58547448FFFFB2E, | 0xE2E69915B3FFF9F9, | 0x8DD01FAD907FFC3C, | 0xB1442798F49FFB4B, | 0xDD95317F31C7FA1D, | 0x8A7D3EEF7F1CFC52, | 0xAD1C8EAB5EE43B67, | 0xD863B256369D4A41, | 0x873E4F75E2224E68, | 0xA90DE3535AAAE202, | 0xD3515C2831559A83, | 0x8412D9991ED58092, | 0xA5178FFF668AE0B6, | 0xCE5D73FF402D98E4, | 0x80FA687F881C7F8E, | 0xA139029F6A239F72, | 0xC987434744AC874F, | 0xFBE9141915D7A922, | 0x9D71AC8FADA6C9B5, | 0xC4CE17B399107C23, | 0xF6019DA07F549B2B, | 0x99C102844F94E0FB, | 0xC0314325637A193A, | 0xF03D93EEBC589F88, | 0x96267C7535B763B5, | 0xBBB01B9283253CA3, | 0xEA9C227723EE8BCB, | 0x92A1958A7675175F, | 0xB749FAED14125D37, | 0xE51C79A85916F485, | 0x8F31CC0937AE58D3, | 0xB2FE3F0B8599EF08, | 0xDFBDCECE67006AC9, | 0x8BD6A141006042BE, | 0xAECC49914078536D, | 0xDA7F5BF590966849, | 0x888F99797A5E012D, | 0xAAB37FD7D8F58179, | 0xD5605FCDCF32E1D7, | 0x855C3BE0A17FCD26, | 0xA6B34AD8C9DFC070, | 0xD0601D8EFC57B08C, | 0x823C12795DB6CE57, | 0xA2CB1717B52481ED, | 0xCB7DDCDDA26DA269, | 0xFE5D54150B090B03, | 0x9EFA548D26E5A6E2, | 0xC6B8E9B0709F109A, | 0xF867241C8CC6D4C1, | 0x9B407691D7FC44F8, | 0xC21094364DFB5637, | 0xF294B943E17A2BC4, | 0x979CF3CA6CEC5B5B, | 0xBD8430BD08277231, | 0xECE53CEC4A314EBE, | 0x940F4613AE5ED137, | 0xB913179899F68584, | 0xE757DD7EC07426E5, | 0x9096EA6F3848984F, | 0xB4BCA50B065ABE63, | 0xE1EBCE4DC7F16DFC, | 0x8D3360F09CF6E4BD, | 0xB080392CC4349DED, | 0xDCA04777F541C568, | 0x89E42CAAF9491B61, | 0xAC5D37D5B79B6239, | 0xD77485CB25823AC7, | 0x86A8D39EF77164BD, | 0xA8530886B54DBDEC, | 0xD267CAA862A12D67, | 0x8380DEA93DA4BC60, | 0xA46116538D0DEB78, | 0xCD795BE870516656, | 0x806BD9714632DFF6, | 0xA086CFCD97BF97F4, | 0xC8A883C0FDAF7DF0, | 0xFAD2A4B13D1B5D6C, | 0x9CC3A6EEC6311A64, | 0xC3F490AA77BD60FD, | 0xF4F1B4D515ACB93C, | 0x991711052D8BF3C5, | 0xBF5CD54678EEF0B7, | 0xEF340A98172AACE5, | 0x9580869F0E7AAC0F, | 0xBAE0A846D2195713, | 0xE998D258869FACD7, | 0x91FF83775423CC06, | 0xB67F6455292CBF08, | 0xE41F3D6A7377EECA, | 0x8E938662882AF53E, | 0xB23867FB2A35B28E, | 0xDEC681F9F4C31F31, | 0x8B3C113C38F9F37F, | 0xAE0B158B4738705F, | 0xD98DDAEE19068C76, | 0x87F8A8D4CFA417CA, | 0xA9F6D30A038D1DBC, | 0xD47487CC8470652B, | 0x84C8D4DFD2C63F3B, | 0xA5FB0A17C777CF0A, | 0xCF79CC9DB955C2CC, | 0x81AC1FE293D599C0, | 0xA21727DB38CB0030, | 0xCA9CF1D206FDC03C, | 0xFD442E4688BD304B, | 0x9E4A9CEC15763E2F, | 0xC5DD44271AD3CDBA, | 0xF7549530E188C129, | 0x9A94DD3E8CF578BA, | 0xC13A148E3032D6E8, | 0xF18899B1BC3F8CA2, | 0x96F5600F15A7B7E5, | 0xBCB2B812DB11A5DE, | 0xEBDF661791D60F56, | 0x936B9FCEBB25C996, | 0xB84687C269EF3BFB, | 0xE65829B3046B0AFA, | 0x8FF71A0FE2C2E6DC, | 0xB3F4E093DB73A093, | 0xE0F218B8D25088B8, | 0x8C974F7383725573, | 0xAFBD2350644EEAD0, | 0xDBAC6C247D62A584, | 0x894BC396CE5DA772, | 0xAB9EB47C81F5114F, | 0xD686619BA27255A3, | 0x8613FD0145877586, | 0xA798FC4196E952E7, | 0xD17F3B51FCA3A7A1, | 0x82EF85133DE648C5, | 0xA3AB66580D5FDAF6, | 0xCC963FEE10B7D1B3, | 0xFFBBCFE994E5C620, | 0x9FD561F1FD0F9BD4, | 0xC7CABA6E7C5382C9, | 0xF9BD690A1B68637B, | 0x9C1661A651213E2D, | 0xC31BFA0FE5698DB8, | 0xF3E2F893DEC3F126, | 0x986DDB5C6B3A76B8, | 0xBE89523386091466, | 0xEE2BA6C0678B597F, | 0x94DB483840B717F0, | 0xBA121A4650E4DDEC, | 0xE896A0D7E51E1566, | 0x915E2486EF32CD60, | 0xB5B5ADA8AAFF80B8, | 0xE3231912D5BF60E6, | 0x8DF5EFABC5979C90, | 0xB1736B96B6FD83B4, | 0xDDD0467C64BCE4A1, | 0x8AA22C0DBEF60EE4, | 0xAD4AB7112EB3929E, | 0xD89D64D57A607745, | 0x87625F056C7C4A8B, | 0xA93AF6C6C79B5D2E, | 0xD389B47879823479, | 0x843610CB4BF160CC, | 0xA54394FE1EEDB8FF, | 0xCE947A3DA6A9273E, | 0x811CCC668829B887, | 0xA163FF802A3426A9, | 0xC9BCFF6034C13053, | 0xFC2C3F3841F17C68, | 0x9D9BA7832936EDC1, | 0xC5029163F384A931, | 0xF64335BCF065D37D, | 0x99EA0196163FA42E, | 0xC06481FB9BCF8D3A, | 0xF07DA27A82C37088, | 0x964E858C91BA2655, | 0xBBE226EFB628AFEB, | 0xEADAB0ABA3B2DBE5, | 0x92C8AE6B464FC96F, | 0xB77ADA0617E3BBCB, | 0xE55990879DDCAABE, | 0x8F57FA54C2A9EAB7, | 0xB32DF8E9F3546564, | 0xDFF9772470297EBD, | 0x8BFBEA76C619EF36, | 0xAEFAE51477A06B04, | 0xDAB99E59958885C5, | 0x88B402F7FD75539B, | 0xAAE103B5FCD2A882, | 0xD59944A37C0752A2, | 0x857FCAE62D8493A5, | 0xA6DFBD9FB8E5B88F, | 0xD097AD07A71F26B2, | 0x825ECC24C8737830, | 0xA2F67F2DFA90563B, | 0xCBB41EF979346BCA, | 0xFEA126B7D78186BD, | 0x9F24B832E6B0F436, | 0xC6EDE63FA05D3144, | 0xF8A95FCF88747D94, | 0x9B69DBE1B548CE7D, | 0xC24452DA229B021C, | 0xF2D56790AB41C2A3, | 0x97C560BA6B0919A6, | 0xBDB6B8E905CB600F, | 0xED246723473E3813, | 0x9436C0760C86E30C, | 0xB94470938FA89BCF, | 0xE7958CB87392C2C3, | 0x90BD77F3483BB9BA, | 0xB4ECD5F01A4AA828, | 0xE2280B6C20DD5232, | 0x8D590723948A535F, | 0xB0AF48EC79ACE837, | 0xDCDB1B2798182245, | 0x8A08F0F8BF0F156B, | 0xAC8B2D36EED2DAC6, | 0xD7ADF884AA879177, | 0x86CCBB52EA94BAEB, | 0xA87FEA27A539E9A5, | 0xD29FE4B18E88640F, | 0x83A3EEEEF9153E89, | 0xA48CEAAAB75A8E2B, | 0xCDB02555653131B6, | 0x808E17555F3EBF12, | 0xA0B19D2AB70E6ED6, | 0xC8DE047564D20A8C, | 0xFB158592BE068D2F, | 0x9CED737BB6C4183D, | 0xC428D05AA4751E4D, | 0xF53304714D9265E0, | 0x993FE2C6D07B7FAC, | 0xBF8FDB78849A5F97, | 0xEF73D256A5C0F77D, | 0x95A8637627989AAE, | 0xBB127C53B17EC159, | 0xE9D71B689DDE71B0, | 0x9226712162AB070E, | 0xB6B00D69BB55C8D1, | 0xE45C10C42A2B3B06, | 0x8EB98A7A9A5B04E3, | 0xB267ED1940F1C61C, | 0xDF01E85F912E37A3, | 0x8B61313BBABCE2C6, | 0xAE397D8AA96C1B78, | 0xD9C7DCED53C72256, | 0x881CEA14545C7575, | 0xAA242499697392D3, | 0xD4AD2DBFC3D07788, | 0x84EC3C97DA624AB5, | 0xA6274BBDD0FADD62, | 0xCFB11EAD453994BA, | 0x81CEB32C4B43FCF5, | 0xA2425FF75E14FC32, | 0xCAD2F7F5359A3B3E, | 0xFD87B5F28300CA0E, | 0x9E74D1B791E07E48, | 0xC612062576589DDB, | 0xF79687AED3EEC551, | 0x9ABE14CD44753B53, | 0xC16D9A0095928A27, | 0xF1C90080BAF72CB1, | 0x971DA05074DA7BEF, | 0xBCE5086492111AEB, | 0xEC1E4A7DB69561A5, | 0x9392EE8E921D5D07, | 0xB877AA3236A4B449, | 0xE69594BEC44DE15B, | 0x901D7CF73AB0ACD9, | 0xB424DC35095CD80F, | 0xE12E13424BB40E13, | 0x8CBCCC096F5088CC, | 0xAFEBFF0BCB24AAFF, | 0xDBE6FECEBDEDD5BF, | 0x89705F4136B4A597, | 0xABCC77118461CEFD, | 0xD6BF94D5E57A42BC, | 0x8637BD05AF6C69B6, | 0xA7C5AC471B478423, | 0xD1B71758E219652C, | 0x83126E978D4FDF3B, | 0xA3D70A3D70A3D70A, | 0xCCCCCCCCCCCCCCCD, | 0x8000000000000000, | 0xA000000000000000, | 0xC800000000000000, | 0xFA00000000000000, | 0x9C40000000000000, | 0xC350000000000000, | 0xF424000000000000, | 0x9896800000000000, | 0xBEBC200000000000, | 0xEE6B280000000000, | 0x9502F90000000000, | 0xBA43B74000000000, | 0xE8D4A51000000000, | 0x9184E72A00000000, | 0xB5E620F480000000, | 0xE35FA931A0000000, | 0x8E1BC9BF04000000, | 0xB1A2BC2EC5000000, | 0xDE0B6B3A76400000, | 0x8AC7230489E80000, | 0xAD78EBC5AC620000, | 0xD8D726B7177A8000, | 0x878678326EAC9000, | 0xA968163F0A57B400, | 0xD3C21BCECCEDA100, | 0x84595161401484A0, | 0xA56FA5B99019A5C8, | 0xCECB8F27F4200F3A, | 0x813F3978F8940984, | 0xA18F07D736B90BE5, | 0xC9F2C9CD04674EDF, | 0xFC6F7C4045812296, | 0x9DC5ADA82B70B59E, | 0xC5371912364CE305, | 0xF684DF56C3E01BC7, | 0x9A130B963A6C115C, | 0xC097CE7BC90715B3, | 0xF0BDC21ABB48DB20, | 0x96769950B50D88F4, | 0xBC143FA4E250EB31, | 0xEB194F8E1AE525FD, | 0x92EFD1B8D0CF37BE, | 0xB7ABC627050305AE, | 0xE596B7B0C643C719, | 0x8F7E32CE7BEA5C70, | 0xB35DBF821AE4F38C, | 0xE0352F62A19E306F, | 0x8C213D9DA502DE45, | 0xAF298D050E4395D7, | 0xDAF3F04651D47B4C, | 0x88D8762BF324CD10, | 0xAB0E93B6EFEE0054, | 0xD5D238A4ABE98068, | 0x85A36366EB71F041, | 0xA70C3C40A64E6C52, | 0xD0CF4B50CFE20766, | 0x82818F1281ED44A0, | 0xA321F2D7226895C8, | 0xCBEA6F8CEB02BB3A, | 0xFEE50B7025C36A08, | 0x9F4F2726179A2245, | 0xC722F0EF9D80AAD6, | 0xF8EBAD2B84E0D58C, | 0x9B934C3B330C8577, | 0xC2781F49FFCFA6D5, | 0xF316271C7FC3908B, | 0x97EDD871CFDA3A57, | 0xBDE94E8E43D0C8EC, | 0xED63A231D4C4FB27, | 0x945E455F24FB1CF9, | 0xB975D6B6EE39E437, | 0xE7D34C64A9C85D44, | 0x90E40FBEEA1D3A4B, | 0xB51D13AEA4A488DD, | 0xE264589A4DCDAB15, | 0x8D7EB76070A08AED, | 0xB0DE65388CC8ADA8, | 0xDD15FE86AFFAD912, | 0x8A2DBF142DFCC7AB, | 0xACB92ED9397BF996, | 0xD7E77A8F87DAF7FC, | 0x86F0AC99B4E8DAFD, | 0xA8ACD7C0222311BD, | 0xD2D80DB02AABD62C, | 0x83C7088E1AAB65DB, | 0xA4B8CAB1A1563F52, | 0xCDE6FD5E09ABCF27, | 0x80B05E5AC60B6178, | 0xA0DC75F1778E39D6, | 0xC913936DD571C84C, | 0xFB5878494ACE3A5F, | 0x9D174B2DCEC0E47B, | 0xC45D1DF942711D9A, | 0xF5746577930D6501, | 0x9968BF6ABBE85F20, | 0xBFC2EF456AE276E9, | 0xEFB3AB16C59B14A3, | 0x95D04AEE3B80ECE6, | 0xBB445DA9CA61281F, | 0xEA1575143CF97227, | 0x924D692CA61BE758, | 0xB6E0C377CFA2E12E, | 0xE498F455C38B997A, | 0x8EDF98B59A373FEC, | 0xB2977EE300C50FE7, | 0xDF3D5E9BC0F653E1, | 0x8B865B215899F46D, | 0xAE67F1E9AEC07188, | 0xDA01EE641A708DEA, | 0x884134FE908658B2, | 0xAA51823E34A7EEDF, | 0xD4E5E2CDC1D1EA96, | 0x850FADC09923329E, | 0xA6539930BF6BFF46, | 0xCFE87F7CEF46FF17, | 0x81F14FAE158C5F6E, | 0xA26DA3999AEF774A, | 0xCB090C8001AB551C, | 0xFDCB4FA002162A63, | 0x9E9F11C4014DDA7E, | 0xC646D63501A1511E, | 0xF7D88BC24209A565, | 0x9AE757596946075F, | 0xC1A12D2FC3978937, | 0xF209787BB47D6B85, | 0x9745EB4D50CE6333, | 0xBD176620A501FC00, | 0xEC5D3FA8CE427B00, | 0x93BA47C980E98CE0, | 0xB8A8D9BBE123F018, | 0xE6D3102AD96CEC1E, | 0x9043EA1AC7E41393, | 0xB454E4A179DD1877, | 0xE16A1DC9D8545E95, | 0x8CE2529E2734BB1D, | 0xB01AE745B101E9E4, | 0xDC21A1171D42645D, | 0x899504AE72497EBA, | 0xABFA45DA0EDBDE69, | 0xD6F8D7509292D603, | 0x865B86925B9BC5C2, | 0xA7F26836F282B733, | 0xD1EF0244AF2364FF, | 0x8335616AED761F1F, | 0xA402B9C5A8D3A6E7, | 0xCD036837130890A1, | 0x802221226BE55A65, | 0xA02AA96B06DEB0FE, | 0xC83553C5C8965D3D, | 0xFA42A8B73ABBF48D, | 0x9C69A97284B578D8, | 0xC38413CF25E2D70E, | 0xF46518C2EF5B8CD1, | 0x98BF2F79D5993803, | 0xBEEEFB584AFF8604, | 0xEEAABA2E5DBF6785, | 0x952AB45CFA97A0B3, | 0xBA756174393D88E0, | 0xE912B9D1478CEB17, | 0x91ABB422CCB812EF, | 0xB616A12B7FE617AA, | 0xE39C49765FDF9D95, | 0x8E41ADE9FBEBC27D, | 0xB1D219647AE6B31C, | 0xDE469FBD99A05FE3, | 0x8AEC23D680043BEE, | 0xADA72CCC20054AEA, | 0xD910F7FF28069DA4, | 0x87AA9AFF79042287, | 0xA99541BF57452B28, | 0xD3FA922F2D1675F2, | 0x847C9B5D7C2E09B7, | 0xA59BC234DB398C25, | 0xCF02B2C21207EF2F, | 0x8161AFB94B44F57D, | 0xA1BA1BA79E1632DC, | 0xCA28A291859BBF93, | 0xFCB2CB35E702AF78, | 0x9DEFBF01B061ADAB, | 0xC56BAEC21C7A1916, | 0xF6C69A72A3989F5C, | 0x9A3C2087A63F6399, | 0xC0CB28A98FCF3C80, | 0xF0FDF2D3F3C30B9F, | 0x969EB7C47859E744, | 0xBC4665B596706115, | 0xEB57FF22FC0C795A, | 0x9316FF75DD87CBD8, | 0xB7DCBF5354E9BECE, | 0xE5D3EF282A242E82, | 0x8FA475791A569D11, | 0xB38D92D760EC4455, | 0xE070F78D3927556B, | 0x8C469AB843B89563, | 0xAF58416654A6BABB, | 0xDB2E51BFE9D0696A, | 0x88FCF317F22241E2, | 0xAB3C2FDDEEAAD25B, | 0xD60B3BD56A5586F2, | 0x85C7056562757457, | 0xA738C6BEBB12D16D, | 0xD106F86E69D785C8, | 0x82A45B450226B39D, | 0xA34D721642B06084, | 0xCC20CE9BD35C78A5, | 0xFF290242C83396CE, | 0x9F79A169BD203E41, | 0xC75809C42C684DD1, | 0xF92E0C3537826146, | 0x9BBCC7A142B17CCC, | 0xC2ABF989935DDBFE, | 0xF356F7EBF83552FE, | 0x98165AF37B2153DF, | 0xBE1BF1B059E9A8D6, | 0xEDA2EE1C7064130C, | 0x9485D4D1C63E8BE8, | 0xB9A74A0637CE2EE1, | 0xE8111C87C5C1BA9A, | 0x910AB1D4DB9914A0, | 0xB54D5E4A127F59C8, | 0xE2A0B5DC971F303A, | 0x8DA471A9DE737E24, | 0xB10D8E1456105DAD, | 0xDD50F1996B947519, | 0x8A5296FFE33CC930, | 0xACE73CBFDC0BFB7B, | 0xD8210BEFD30EFA5A, | 0x8714A775E3E95C78, | 0xA8D9D1535CE3B396, | 0xD31045A8341CA07C, | 0x83EA2B892091E44E, | 0xA4E4B66B68B65D61, | 0xCE1DE40642E3F4B9, | 0x80D2AE83E9CE78F4, | 0xA1075A24E4421731, | 0xC94930AE1D529CFD, | 0xFB9B7CD9A4A7443C, | 0x9D412E0806E88AA6, | 0xC491798A08A2AD4F, | 0xF5B5D7EC8ACB58A3, | 0x9991A6F3D6BF1766, | 0xBFF610B0CC6EDD3F, | 0xEFF394DCFF8A948F, | 0x95F83D0A1FB69CD9, | 0xBB764C4CA7A44410, | 0xEA53DF5FD18D5514, | 0x92746B9BE2F8552C, | 0xB7118682DBB66A77, | 0xE4D5E82392A40515, | 0x8F05B1163BA6832D, | 0xB2C71D5BCA9023F8, | 0xDF78E4B2BD342CF7, | 0x8BAB8EEFB6409C1A, | 0xAE9672ABA3D0C321, | 0xDA3C0F568CC4F3E9, | 0x8865899617FB1871, | 0xAA7EEBFB9DF9DE8E, | 0xD51EA6FA85785631, | 0x8533285C936B35DF, | 0xA67FF273B8460357, | 0xD01FEF10A657842C, | 0x8213F56A67F6B29C, | 0xA298F2C501F45F43, | 0xCB3F2F7642717713, | 0xFE0EFB53D30DD4D8, | 0x9EC95D1463E8A507, | 0xC67BB4597CE2CE49, | 0xF81AA16FDC1B81DB, | 0x9B10A4E5E9913129, | 0xC1D4CE1F63F57D73, | 0xF24A01A73CF2DCD0, | 0x976E41088617CA02, | 0xBD49D14AA79DBC82, | 0xEC9C459D51852BA3, | 0x93E1AB8252F33B46, | 0xB8DA1662E7B00A17, | 0xE7109BFBA19C0C9D, | 0x906A617D450187E2, | 0xB484F9DC9641E9DB, | 0xE1A63853BBD26451, | 0x8D07E33455637EB3, | 0xB049DC016ABC5E60, | 0xDC5C5301C56B75F7, | 0x89B9B3E11B6329BB, | 0xAC2820D9623BF429, | 0xD732290FBACAF134, | 0x867F59A9D4BED6C0, | 0xA81F301449EE8C70, | 0xD226FC195C6A2F8C, | 0x83585D8FD9C25DB8, | 0xA42E74F3D032F526, | 0xCD3A1230C43FB26F, | 0x80444B5E7AA7CF85, | 0xA0555E361951C367, | 0xC86AB5C39FA63441, | 0xFA856334878FC151, | 0x9C935E00D4B9D8D2, | 0xC3B8358109E84F07, | 0xF4A642E14C6262C9, | 0x98E7E9CCCFBD7DBE, | 0xBF21E44003ACDD2D, | 0xEEEA5D5004981478, | 0x95527A5202DF0CCB, | 0xBAA718E68396CFFE, | 0xE950DF20247C83FD, | 0x91D28B7416CDD27E, | 0xB6472E511C81471E, | 0xE3D8F9E563A198E5, | 0x8E679C2F5E44FF8F, | 0xB201833B35D63F73, | 0xDE81E40A034BCF50, | 0x8B112E86420F6192, | 0xADD57A27D29339F6, | 0xD94AD8B1C7380874, | 0x87CEC76F1C830549, | 0xA9C2794AE3A3C69B, | 0xD433179D9C8CB841, | 0x849FEEC281D7F329, | 0xA5C7EA73224DEFF3, | 0xCF39E50FEAE16BF0, | 0x81842F29F2CCE376, | 0xA1E53AF46F801C53, | 0xCA5E89B18B602368, | 0xFCF62C1DEE382C42, | 0x9E19DB92B4E31BA9, | 0xC5A05277621BE294, | 0xF70867153AA2DB39, | 0x9A65406D44A5C903, | 0xC0FE908895CF3B44, | 0xF13E34AABB430A15, | 0x96C6E0EAB509E64D, | 0xBC789925624C5FE1, | 0xEB96BF6EBADF77D9, | 0x933E37A534CBAAE8, | 0xB80DC58E81FE95A1, | 0xE61136F2227E3B0A, | 0x8FCAC257558EE4E6, | 0xB3BD72ED2AF29E20, | 0xE0ACCFA875AF45A8, | 0x8C6C01C9498D8B89, | 0xAF87023B9BF0EE6B, | 0xDB68C2CA82ED2A06, | 0x892179BE91D43A44, | 0xAB69D82E364948D4, | 0xD6444E39C3DB9B0A, | 0x85EAB0E41A6940E6, | 0xA7655D1D2103911F, | 0xD13EB46469447567, | 0x82C730BEC1CAC961, | 0xA378FCEE723D7BB9, | 0xCC573C2A0ECCDAA7, | 0xFF6D0B3492801151, | 0x9FA42700DB900AD2, | 0xC78D30C112740D87, | 0xF9707CF1571110E9, | 0x9BE64E16D66AAA91, | 0xC2DFE19C8C055536, | 0xF397DA03AF06AA83, | 0x983EE8424D642A92, | 0xBE4EA252E0BD3537, | 0xEDE24AE798EC8284, | 0x94AD6ED0BF93D193, | 0xB9D8CA84EF78C5F7, | 0xE84EFD262B56F775, | 0x91315E37DB165AA9, | 0xB57DB5C5D1DBF153, | 0xE2DD23374652EDA8, | 0x8DCA36028BF3D489, | 0xB13CC3832EF0C9AC, | 0xDD8BF463FAACFC16, | 0x8A7778BE7CAC1D8E, | 0xAD1556EE1BD724F1, | 0xD85AACA9A2CCEE2E, | 0x8738ABEA05C014DD, | 0xA906D6E487301A14, | 0xD3488C9DA8FC2099, | 0x840D57E2899D945F, | 0xA510ADDB2C04F977, | 0xCE54D951F70637D5, | 0x80F507D33A63E2E5, | 0xA13249C808FCDB9F, | 0xC97EDC3A0B3C1286, | 0xFBDE93488E0B1728, | 0x9D6B1C0D58C6EE79, | 0xC4C5E310AEF8AA17, | 0xF5F75BD4DAB6D49D, | 0x99BA996508B244E2, | 0xC0293FBE4ADED61B, | 0xF0338FADDD968BA1, | 0x962039CCAA7E1745, | 0xBBA8483FD51D9D16, | 0xEA925A4FCA65045B, | 0x929B7871DE7F22B9, | 0xB742568E561EEB67, | 0xE512EC31EBA6A641, | 0x8F2BD39F334827E9, | 0xB2F6C887001A31E3, | 0xDFB47AA8C020BE5C, | 0x8BD0CCA9781476F9, | 0xAEC4FFD3D61994B8, | 0xDA763FC8CB9FF9E6, | 0x8889E7DD7F43FC2F, | 0xAAAC61D4DF14FB3B, | 0xD5577A4A16DA3A0A, | 0x8556AC6E4E486446, | 0xA6AC5789E1DA7D58, | 0xD0576D6C5A511CAE, | 0x8236A463B872B1ED, | 0xA2C44D7CA68F5E68, | 0xCB7560DBD0333602, | 0xFE52B912C4400382, | 0x9EF3B3ABBAA80231, | 0xC6B0A096A95202BE, | 0xF85CC8BC53A6836D, | 0x9B39FD75B4481224, | 0xC2087CD3215A16AD, | 0xF28A9C07E9B09C59, | 0x9796A184F20E61B7, | 0xBD7C49E62E91FA25, | 0xECDB5C5FBA3678AF, | 0x940919BBD4620B6D, | 0xB90B602AC97A8E48, | 0xE74E38357BD931DB, | 0x9090E3216D67BF29, | 0xB4B51BE9C8C1AEF3, | 0xE1E262E43AF21AAF, | 0x8D2D7DCEA4D750AE, | 0xB078DD424E0D24D9, | 0xDC971492E1906E0F, | 0x89DE6CDBCCFA44CA, | 0xAC560812C038D5FC, | 0xD76B8A1770470B7B, | 0x86A3364EA62C672D, | 0xA84C03E24FB780F8, | 0xD25F04DAE3A56136, | 0x837B6308CE475CC2, | 0xA45A3BCB01D933F2, | 0xCD70CABDC24F80EF, | 0x80667EB69971B095, | 0xA0801E643FCE1CBB, | 0xC8A025FD4FC1A3E9, | 0xFAC82F7CA3B20CE4, | 0x9CBD1DADE64F480E, | 0xC3EC65195FE31A12, | 0xF4E77E5FB7DBE096, | 0x9910AEFBD2E96C5E, | 0xBF54DABAC7A3C775, | 0xEF2A1169798CB953, | 0x957A4AE1EBF7F3D4, | 0xBAD8DD9A66F5F0C9, | 0xE98F150100B36CFB, | 0x91F96D20A070241D, | 0xB677C868C88C2D24, | 0xE415BA82FAAF386D, | 0x8E8D9491DCAD8344, | 0xB230F9B653D8E415, | 0xDEBD3823E8CF1D1A, | 0x8B36431671817230, | 0xAE03D3DC0DE1CEBD, | 0xD984C8D3115A426C, | 0x87F2FD83EAD86983, | 0xA9EFBCE4E58E83E4, | 0xD46BAC1E1EF224DD, | 0x84C34B92D357570A, | 0xA5F41E77882D2CCD, | 0xCF7126156A387800, | 0x81A6B7CD62634B00, | 0xA21065C0BAFC1DC0, | 0xCA947F30E9BB2530, | 0xFD399EFD2429EE7C, | 0x9E44035E369A350D, | 0xC5D50435C440C251, | 0xF74A45433550F2E5, | 0x9A8E6B4A015297CF, | 0xC132061C81A73DC3, | 0xF17E87A3A2110D34, | 0x96EF14C6454AA840, | 0xBCAAD9F7D69D5250, | 0xEBD59075CC44A6E4, | 0x93657A499FAAE84F, | 0xB83ED8DC0795A262, | 0xE64E8F13097B0AFB, | 0x8FF1196BE5ECE6DD, | 0xB3ED5FC6DF682094, | 0xE0E8B7B8974228B9, | 0x8C9172D35E895974, | 0xAFB5CF88362BAFD1, | 0xDBA3436A43B69BC5, | 0x89460A226A52215B, | 0xAB978CAB04E6A9B2, | 0xD67D6FD5C620541E, | 0x860E65E59BD43493, | 0xA791FF5F02C941B8, | 0xD1767F36C37B9226, | 0x82EA0F823A2D3B57, | 0xA3A49362C8B88A2D, | 0xCC8DB83B7AE6ACB9, | 0xFFB1264A59A057E7, | 0x9FCEB7EE780436F0, | 0xC7C265EA160544AC, | 0xF9B2FF649B8695D7, | 0x9C0FDF9EE1341DA7, | 0xC313D78699812510, | 0xF3D8CD683FE16E54, | 0x9867806127ECE4F5, | 0xBE81607971E81E32, | 0xEE21B897CE6225BE, | 0x94D5135EE0FD5797, | 0xBA0A5836993CAD7D, | 0xE88CEE443F8BD8DC, | 0x915814EAA7B76789, | 0xB5AE1A2551A5416C, | 0xE319A0AEA60E91C7, |]; | |static immutable align(16) ulong[1025] p10_coefficients_l = [ | 0x7132D332E3F204D5, | 0x8D7F87FF9CEE860A, | 0x70DF69FF842A278D, | 0xC68BA23FB29A58B8, | 0xB82E8ACF9F40EEE6, | 0xA63A2D8387112A9F, | 0xA7E45C72346ABAA4, | 0xD1DD738EC185694D, | 0xC654D07271E6C3A0, | 0xDBF5024787303A44, | 0x12F242D968FC48D5, | 0x17AED38FC33B5B0A, | 0x8ECD4439DA0518E6, | 0x7280954850865F20, | 0x4F20BA9A64A7F6E8, | 0x917474A07EE8FA51, | 0xB5D191C89EA338E5, | 0xE345F63AC64C071E, | 0x9C1773C977DF08E6, | 0x218EA85DEAEB6590, | 0x29F2527565A63EF4, | 0xF46EE712BF0FCEB0, | 0x18C5506BB769E12E, | 0x1EF6A486A544597A, | 0xA6B44DA84E956FD8, | 0x6830B089311D65E7, | 0x423CDCAB7D64BF61, | 0x52CC13D65CBDEF39, | 0xB3BF8C65F9F6B584, | 0x20AF6F7F787462E5, | 0x68DB4B5F56917B9E, | 0x81890F1B961AED43, | 0x61EB52E27BA1A893, | 0xFA66279B1A8A12B8, | 0x7C7FD8C0F0964BB3, | 0x1B9FCEF12CBBDEA0, | 0xE287C2AD77EAD648, | 0xAD94D9AC6AF2C5ED, | 0x98FA101785AF7768, | 0xBF38941D671B5542, | 0xF7835C9260711549, | 0x756433B6F88D5A9C, | 0x12BD40A4B6B0B143, | 0x4BB64866F22E6ECA, | 0xDEA3DA80AEBA0A7C, | 0x164CD120DA688D1B, | 0x5BE005691102B062, | 0xB96C0361AAA1AE3D, | 0x67C7043A154A19CC, | 0x01B8C5489A9CA040, | 0x01137B4D60A1E428, | 0xC1585A20B8CA5D32, | 0xB1AE70A8E6FCF47E, | 0x4F0D0669905E18CF, | 0xA2D04803F4759F02, | 0xCB845A04F19306C3, | 0x1F32B84316FBE43A, | 0x66FF6653DCBADD48, | 0x00BF3FE8D3E9949A, | 0x807787F18471FCE1, | 0x209569EDE58E7C19, | 0xA8BAC4695EF21B1F, | 0x6974BAC1DB5750F3, | 0x03D1E972522D2530, | 0x84C663CEE6B86E7C, | 0xD2FBFE615033450E, | 0x87BAFDF9A4401651, | 0xE9A9BD780D501BE5, | 0x520A166B0852116F, | 0xE68C9C05CA6695CB, | 0xA02FC3073D003B3E, | 0x241DD9E486202507, | 0x6D25505DA7A82E48, | 0x886EA475119239DA, | 0xEA8A4D9255F6C851, | 0xD296707B75BA3D33, | 0x073C0C9A5328CC7F, | 0xC90B0FC0E7F2FF9F, | 0x3DA6E9D890F7DFC3, | 0x0D10A44EB535D7B4, | 0xD054CD6262834DA1, | 0xA235005D7D921085, | 0xCAC24074DCF694A6, | 0x3D72D092143439D0, | 0x6667C25B4CA0A422, | 0x4001B2F21FC8CD2A, | 0x10021FAEA7BB0075, | 0x8A0153CD28D4E049, | 0x2C81A8C0730A185B, | 0xB7A212F08FCC9E72, | 0x12C54BD659DFE307, | 0x97769ECBF057DBC9, | 0xBD54467EEC6DD2BB, | 0x5654AC0F53C4A3B5, | 0x2BE9D71328B5CCA2, | 0xF6E44CD7F2E33FCB, | 0x1A4EB006F7CE07DF, | 0xA0E25C08B5C189D7, | 0x891AF30AE331EC4C, | 0x35B0D7E6CDFF33B0, | 0xC31D0DE0817F009C, | 0x73E45158A1DEC0C2, | 0x086EB2D7652B387A, | 0x4A8A5F8D3E760698, | 0x5D2CF7708E13883E, | 0x3478354CB1986A4D, | 0x00CB214FEEFF4270, | 0x00FDE9A3EABF130C, | 0xC13D640CE56ED7D0, | 0x98C65E880F6546E2, | 0x3EF7F62A133E989A, | 0x8EB5F3B4980E3EC1, | 0x3931B850DF08E738, | 0xC77E266516CB2106, | 0xF95DAFFE5C7DE948, | 0x5BDA8DFEF9CEB1CD, | 0x72D1317EB8425E40, | 0x0F857DDE6652F5D0, | 0xC9B36EAAFFF3D9A2, | 0x3C204A55BFF0D00B, | 0xCB285CEB2FED040E, | 0x5EF93A12FDF42288, | 0x76B78897BD712B2B, | 0xD4656ABDACCD75F5, | 0x24BF62B68C0069B9, | 0x2DEF3B642F008428, | 0xB96B0A3D3AC0A531, | 0xD3E2E66644B8673F, | 0x88DB9FFFD5E6810F, | 0x6B1287FFCB602152, | 0x62EB94FFDF1C14D3, | 0x7BA67A3FD6E31A08, | 0xDA9018CFCC9BE08A, | 0x91341F03BFC2D8AD, | 0x9AC0936257D9C76C, | 0xC170B83AEDD03947, | 0xF1CCE649A9444799, | 0x17200FEE09CAACC0, | 0x9CE813E98C3D57EF, | 0xC42218E3EF4CADEB, | 0xBA954F8E758FECB3, | 0xA93AA37212F3E7E0, | 0xD3894C4E97B0E1D8, | 0xE435CFB11ECE8D27, | 0xDD43439D66823071, | 0x14941484C022BC8D, | 0xECDC8CD2F815B5D8, | 0x6813B007B61B234E, | 0x82189C09A3A1EC21, | 0x714F618606453395, | 0x8DA339E787D6807A, | 0xF10C086169CC2099, | 0x16A7853CE21F945F, | 0x9C51668C1AA77977, | 0x4365C02F215157D5, | 0x0A1F981D74D2D6E5, | 0x4CA77E24D2078C9E, | 0xDFD15DAE06896FC6, | 0xEBE2DA8CC415E5DC, | 0x66DB912FF51B5F53, | 0x0092757BF2623727, | 0x205B896D777D6279, | 0xA8726BC8D55CBB17, | 0x128F06BB0AB3E9DD, | 0x1732C869CD60E454, | 0x0E7FBD42205C8EB4, | 0x521FAC92A873B261, | 0xE6A797B752909EFA, | 0x9028BED2939A635C, | 0x7432EE873880FC33, | 0x113FAA2906A13B40, | 0x4AC7CA59A424C508, | 0x5D79BCF00D2DF64A, | 0xF4D82C2C107973DC, | 0x79071B9B8A4BE86A, | 0x9748E2826CDEE284, | 0xFD1B1B2308169B25, | 0xFE30F0F5E50E20F7, | 0xBDBD2D335E51A935, | 0xAD2C788035E61382, | 0x4C3BCB5021AFCC31, | 0xDF4ABE242A1BBF3E, | 0xD71D6DAD34A2AF0D, | 0x8672648C40E5AD68, | 0x680EFDAF511F18C2, | 0x0212BD1B2566DEF3, | 0x014BB630F7604B58, | 0x419EA3BD35385E2E, | 0x52064CAC828675B9, | 0x7343EFEBD1940994, | 0x1014EBE6C5F90BF9, | 0xD41A26E077774EF7, | 0x8920B098955522B5, | 0x55B46E5F5D5535B1, | 0xEB2189F734AA831D, | 0xA5E9EC7501D523E4, | 0x47B233C92125366F, | 0x999EC0BB696E840A, | 0xC00670EA43CA250D, | 0x380406926A5E5728, | 0xC605083704F5ECF2, | 0xF7864A44C633682F, | 0x7AB3EE6AFBE0211D, | 0x5960EA05BAD82965, | 0x6FB92487298E33BE, | 0xA5D3B6D479F8E057, | 0x8F48A4899877186C, | 0x331ACDABFE94DE87, | 0x9FF0C08B7F1D0B15, | 0x07ECF0AE5EE44DDA, | 0xC9E82CD9F69D6150, | 0xBE311C083A225CD2, | 0x6DBD630A48AAF407, | 0x092CBBCCDAD5B108, | 0x25BBF56008C58EA5, | 0xAF2AF2B80AF6F24E, | 0x1AF5AF660DB4AEE2, | 0x50D98D9FC890ED4D, | 0xE50FF107BAB528A1, | 0x1E53ED49A96272C9, | 0x25E8E89C13BB0F7B, | 0x77B191618C54E9AD, | 0xD59DF5B9EF6A2418, | 0x4B0573286B44AD1E, | 0x4EE367F9430AEC33, | 0x229C41F793CDA73F, | 0x6B43527578C1110F, | 0x830A13896B78AAAA, | 0x23CC986BC656D554, | 0x2CBFBE86B7EC8AA9, | 0x7BF7D71432F3D6AA, | 0xDAF5CCD93FB0CC54, | 0xD1B3400F8F9CFF69, | 0x23100809B9C21FA2, | 0xABD40A0C2832A78A, | 0x16C90C8F323F516D, | 0xAE3DA7D97F6792E4, | 0x99CD11CFDF41779D, | 0x40405643D711D584, | 0x482835EA666B2572, | 0xDA3243650005EECF, | 0x90BED43E40076A83, | 0x5A7744A6E804A292, | 0x711515D0A205CB36, | 0x0D5A5B44CA873E04, | 0xE858790AFE9486C2, | 0x626E974DBE39A873, | 0xFB0A3D212DC81290, | 0x7CE66634BC9D0B9A, | 0x1C1FFFC1EBC44E80, | 0xA327FFB266B56220, | 0x4BF1FF9F0062BAA8, | 0x6F773FC3603DB4A9, | 0xCB550FB4384D21D4, | 0x7E2A53A146606A48, | 0x2EDA7444CBFC426D, | 0xFA911155FEFB5309, | 0x793555AB7EBA27CB, | 0x4BC1558B2F3458DF, | 0x9EB1AAEDFB016F16, | 0x465E15A979C1CADC, | 0x0BFACD89EC191ECA, | 0xCEF980EC671F667C, | 0x82B7E12780E7401B, | 0xD1B2ECB8B0908811, | 0x861FA7E6DCB4AA15, | 0x67A791E093E1D49A, | 0xE0C8BB2C5C6D24E0, | 0x58FAE9F773886E19, | 0xAF39A475506A899F, | 0x6D8406C952429603, | 0xC8E5087BA6D33B84, | 0xFB1E4A9A90880A65, | 0x5CF2EEA09A55067F, | 0xF42FAA48C0EA481F, | 0xF13B94DAF124DA27, | 0x76C53D08D6B70858, | 0x54768C4B0C64CA6E, | 0xA9942F5DCF7DFD0A, | 0xD3F93B35435D7C4C, | 0xC47BC5014A1A6DB0, | 0x359AB6419CA1091B, | 0xC30163D203C94B62, | 0x79E0DE63425DCF1D, | 0x985915FC12F542E5, | 0x3E6F5B7B17B2939E, | 0xA705992CEECF9C43, | 0x50C6FF782A838353, | 0xA4F8BF5635246428, | 0x871B7795E136BE99, | 0x28E2557B59846E3F, | 0x331AEADA2FE589CF, | 0x3FF0D2C85DEF7622, | 0x0FED077A756B53AA, | 0xD3E8495912C62894, | 0x64712DD7ABBBD95D, | 0xBD8D794D96AACFB4, | 0xECF0D7A0FC5583A1, | 0xF41686C49DB57245, | 0x311C2875C522CED6, | 0x7D633293366B828B, | 0xAE5DFF9C02033197, | 0xD9F57F830283FDFD, | 0xD072DF63C324FD7C, | 0x4247CB9E59F71E6D, | 0x52D9BE85F074E609, | 0x67902E276C921F8B, | 0x00BA1CD8A3DB53B7, | 0x80E8A40ECCD228A5, | 0x6122CD128006B2CE, | 0x796B805720085F81, | 0xCBE3303674053BB1, | 0xBEDBFC4411068A9D, | 0xEE92FB5515482D44, | 0x751BDD152D4D1C4B, | 0xD262D45A78A0635D, | 0x86FB897116C87C35, | 0xD45D35E6AE3D4DA1, | 0x8974836059CCA109, | 0x2BD1A438703FC94B, | 0x7B6306A34627DDCF, | 0x1A3BC84C17B1D543, | 0x20CABA5F1D9E4A94, | 0x547EB47B7282EE9C, | 0xE99E619A4F23AA43, | 0x6405FA00E2EC94D4, | 0xDE83BC408DD3DD05, | 0x9624AB50B148D446, | 0x3BADD624DD9B0957, | 0xE54CA5D70A80E5D6, | 0x5E9FCF4CCD211F4C, | 0x7647C3200069671F, | 0x29ECD9F40041E073, | 0xF468107100525890, | 0x7182148D4066EEB4, | 0xC6F14CD848405531, | 0xB8ADA00E5A506A7D, | 0xA6D90811F0E4851C, | 0x908F4A166D1DA663, | 0x9A598E4E043287FE, | 0x40EFF1E1853F29FE, | 0xD12BEE59E68EF47D, | 0x82BB74F8301958CE, | 0xE36A52363C1FAF02, | 0xDC44E6C3CB279AC2, | 0x29AB103A5EF8C0B9, | 0x7415D448F6B6F0E8, | 0x111B495B3464AD21, | 0xCAB10DD900BEEC35, | 0x3D5D514F40EEA742, | 0x0CB4A5A3112A5113, | 0x47F0E785EABA72AC, | 0x59ED216765690F57, | 0x306869C13EC3532C, | 0x1E414218C73A13FC, | 0xE5D1929EF90898FB, | 0xDF45F746B74ABF39, | 0x6B8BBA8C328EB784, | 0x066EA92F3F326565, | 0xC80A537B0EFEFEBE, | 0xBD06742CE95F5F37, | 0x2C48113823B73704, | 0xF75A15862CA504C5, | 0x9A984D73DBE722FB, | 0xC13E60D0D2E0EBBA, | 0x318DF905079926A9, | 0xFDF17746497F7053, | 0xFEB6EA8BEDEFA634, | 0xFE64A52EE96B8FC1, | 0x3DFDCE7AA3C673B1, | 0x06BEA10CA65C084F, | 0x486E494FCFF30A62, | 0x5A89DBA3C3EFCCFB, | 0xF89629465A75E01D, | 0xF6BBB397F1135824, | 0x746AA07DED582E2D, | 0xA8C2A44EB4571CDC, | 0x92F34D62616CE413, | 0x77B020BAF9C81D18, | 0x0ACE1474DC1D122F, | 0x0D819992132456BB, | 0x10E1FFF697ED6C69, | 0xCA8D3FFA1EF463C2, | 0xBD308FF8A6B17CB2, | 0xAC7CB3F6D05DDBDF, | 0x6BCDF07A423AA96B, | 0x86C16C98D2C953C6, | 0xE871C7BF077BA8B8, | 0x11471CD764AD4973, | 0xD598E40D3DD89BCF, | 0x4AFF1D108D4EC2C3, | 0xCEDF722A585139BA, | 0xC2974EB4EE658829, | 0x733D226229FEEA33, | 0x0806357D5A3F5260, | 0xCA07C2DCB0CF26F8, | 0xFC89B393DD02F0B6, | 0xBBAC2078D443ACE3, | 0xD54B944B84AA4C0E, | 0x0A9E795E65D4DF11, | 0x4D4617B5FF4A16D6, | 0x504BCED1BF8E4E46, | 0xE45EC2862F71E1D7, | 0x5D767327BB4E5A4D, | 0x3A6A07F8D510F870, | 0x890489F70A55368C, | 0x2B45AC74CCEA842F, | 0x3B0B8BC90012929D, | 0x09CE6EBB40173745, | 0xCC420A6A101D0516, | 0x9FA946824A12232E, | 0x47939822DC96ABF9, | 0x59787E2B93BC56F7, | 0x57EB4EDB3C55B65B, | 0xEDE622920B6B23F1, | 0xE95FAB368E45ECED, | 0x11DBCB0218EBB414, | 0xD652BDC29F26A11A, | 0x4BE76D3346F04960, | 0x6F70A4400C562DDC, | 0xCB4CCD500F6BB953, | 0x7E2000A41346A7A8, | 0x8ED400668C0C28C9, | 0x728900802F0F32FB, | 0x4F2B40A03AD2FFBA, | 0xE2F610C84987BFA8, | 0x0DD9CA7D2DF4D7C9, | 0x91503D1C79720DBB, | 0x75A44C6397CE912A, | 0xC986AFBE3EE11ABA, | 0xFBE85BADCE996169, | 0xFAE27299423FB9C3, | 0xDCCD879FC967D41A, | 0x5400E987BBC1C921, | 0x290123E9AAB23B69, | 0xF9A0B6720AAF6521, | 0xF808E40E8D5B3E6A, | 0xB60B1D1230B20E04, | 0xB1C6F22B5E6F48C3, | 0x1E38AEB6360B1AF3, | 0x25C6DA63C38DE1B0, | 0x579C487E5A38AD0E, | 0x2D835A9DF0C6D852, | 0xF8E431456CF88E66, | 0x1B8E9ECB641B5900, | 0xE272467E3D222F40, | 0x5B0ED81DCC6ABB10, | 0x98E947129FC2B4EA, | 0x3F2398D747B36224, | 0x8EEC7F0D19A03AAD, | 0x1953CF68300424AC, | 0x5FA8C3423C052DD7, | 0x3792F412CB06794D, | 0xE2BBD88BBEE40BD0, | 0x5B6ACEAEAE9D0EC4, | 0xF245825A5A445275, | 0xEED6E2F0F0D56713, | 0x55464DD69685606C, | 0xAA97E14C3C26B887, | 0xD53DD99F4B3066A8, | 0xE546A8038EFE4029, | 0xDE98520472BDD033, | 0x963E66858F6D4440, | 0xDDE7001379A44AA8, | 0x5560C018580D5D52, | 0xAAB8F01E6E10B4A7, | 0xCAB3961304CA70E8, | 0x3D607B97C5FD0D22, | 0x8CB89A7DB77C506B, | 0x77F3608E92ADB243, | 0x55F038B237591ED3, | 0x6B6C46DEC52F6688, | 0x2323AC4B3B3DA015, | 0xABEC975E0A0D081B, | 0x96E7BD358C904A21, | 0x7E50D64177DA2E55, | 0xDDE50BD1D5D0B9EA, | 0x955E4EC64B44E864, | 0xBD5AF13BEF0B113F, | 0xECB1AD8AEACDD58E, | 0x67DE18EDA5814AF2, | 0x80EACF948770CED7, | 0xA1258379A94D028D, | 0x096EE45813A04330, | 0x8BCA9D6E188853FC, | 0x775EA264CF55347E, | 0x95364AFE032A819D, | 0x3A83DDBD83F52205, | 0xC4926A9672793543, | 0x75B7053C0F178294, | 0x5324C68B12DD6338, | 0xD3F6FC16EBCA5E03, | 0x88F4BB1CA6BCF584, | 0x2B31E9E3D06C32E5, | 0x3AFF322E62439FCF, | 0x09BEFEB9FAD487C3, | 0x4C2EBE687989A9B4, | 0x0F9D37014BF60A10, | 0x538484C19EF38C94, | 0x2865A5F206B06FBA, | 0xF93F87B7442E45D4, | 0xF78F69A51539D749, | 0xB573440E5A884D1B, | 0x31680A88F8953031, | 0xFDC20D2B36BA7C3D, | 0x3D32907604691B4D, | 0xA63F9A49C2C1B110, | 0x0FCF80DC33721D54, | 0xD3C36113404EA4A9, | 0x645A1CAC083126E9, | 0x3D70A3D70A3D70A4, | 0xCCCCCCCCCCCCCCCD, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x0000000000000000, | 0x4000000000000000, | 0x5000000000000000, | 0xA400000000000000, | 0x4D00000000000000, | 0xF020000000000000, | 0x6C28000000000000, | 0xC732000000000000, | 0x3C7F400000000000, | 0x4B9F100000000000, | 0x1E86D40000000000, | 0x1314448000000000, | 0x17D955A000000000, | 0x5DCFAB0800000000, | 0x5AA1CAE500000000, | 0xF14A3D9E40000000, | 0x6D9CCD05D0000000, | 0xE4820023A2000000, | 0xDDA2802C8A800000, | 0xD50B2037AD200000, | 0x4526F422CC340000, | 0x9670B12B7F410000, | 0x3C0CDD765F114000, | 0xA5880A69FB6AC800, | 0x8EEA0D047A457A00, | 0x72A4904598D6D880, | 0x47A6DA2B7F864750, | 0x999090B65F67D924, | 0xFFF4B4E3F741CF6D, | 0xBFF8F10E7A8921A4, | 0xAFF72D52192B6A0D, | 0x9BF4F8A69F764490, | 0x02F236D04753D5B5, | 0x01D762422C946591, | 0x424D3AD2B7B97EF5, | 0xD2E0898765A7DEB2, | 0x63CC55F49F88EB2F, | 0x3CBF6B71C76B25FB, | 0x8BEF464E3945EF7A, | 0x97758BF0E3CBB5AC, | 0x3D52EEED1CBEA317, | 0x4CA7AAA863EE4BDD, | 0x8FE8CAA93E74EF6A, | 0xB3E2FD538E122B45, | 0x60DBBCA87196B616, | 0xBC8955E946FE31CE, | 0x6BABAB6398BDBE41, | 0xC696963C7EED2DD2, | 0xFC1E1DE5CF543CA3, | 0x3B25A55F43294BCC, | 0x49EF0EB713F39EBF, | 0x6E3569326C784337, | 0x49C2C37F07965405, | 0xDC33745EC97BE906, | 0x69A028BB3DED71A4, | 0xC40832EA0D68CE0D, | 0xF50A3FA490C30190, | 0x792667C6DA79E0FA, | 0x577001B891185939, | 0xED4C0226B55E6F87, | 0x544F8158315B05B4, | 0x696361AE3DB1C721, | 0x03BC3A19CD1E38EA, | 0x04AB48A04065C724, | 0x62EB0D64283F9C76, | 0x3BA5D0BD324F8394, | 0xCA8F44EC7EE36479, | 0x7E998B13CF4E1ECC, | 0x9E3FEDD8C321A67F, | 0xC5CFE94EF3EA101E, | 0xBBA1F1D158724A13, | 0x2A8A6E45AE8EDC98, | 0xF52D09D71A3293BE, | 0x593C2626705F9C56, | 0x6F8B2FB00C77836C, | 0x0B6DFB9C0F956447, | 0x4724BD4189BD5EAC, | 0x58EDEC91EC2CB658, | 0x2F2967B66737E3ED, | 0xBD79E0D20082EE74, | 0xECD8590680A3AA11, | 0xE80E6F4820CC9496, | 0x3109058D147FDCDE, | 0xBD4B46F0599FD415, | 0x6C9E18AC7007C91A, | 0x03E2CF6BC604DDB0, | 0x84DB8346B786151D, | 0xE612641865679A64, | 0x4FCB7E8F3F60C07E, | 0xE3BE5E330F38F09E, | 0x5CADF5BFD3072CC5, | 0x73D9732FC7C8F7F7, | 0x2867E7FDDCDD9AFA, | 0xB281E1FD541501B9, | 0x1F225A7CA91A4227, | 0x3375788DE9B06958, | 0x0052D6B1641C83AE, | 0xC0678C5DBD23A49A, | 0xF840B7BA963646E0, | 0xB650E5A93BC3D898, | 0xA3E51F138AB4CEBE, | 0xC66F336C36B10137, | 0xB80B0047445D4185, | 0xA60DC059157491E6, | 0x87C89837AD68DB30, | 0x29BABE4598C311FC, | 0xF4296DD6FEF3D67B, | 0x1899E4A65F58660D, | 0x5EC05DCFF72E7F90, | 0x76707543F4FA1F74, | 0x6A06494A791C53A8, | 0x0487DB9D17636892, | 0x45A9D2845D3C42B7, | 0x0B8A2392BA45A9B2, | 0x8E6CAC7768D7141F, | 0x3207D795430CD927, | 0x7F44E6BD49E807B8, | 0x5F16206C9C6209A6, | 0x36DBA887C37A8C10, | 0xC2494954DA2C978A, | 0xF2DB9BAA10B7BD6C, | 0x6F92829494E5ACC7, | 0xCB772339BA1F17F9, | 0xFF2A760414536EFC, | 0xFEF5138519684ABB, | 0x7EB258665FC25D69, | 0xEF2F773FFBD97A62, | 0xAAFB550FFACFD8FA, | 0x95BA2A53F983CF39, | 0xDD945A747BF26184, | 0x94F971119AEEF9E4, | 0x7A37CD5601AAB85E, | 0xAC62E055C10AB33B, | 0x577B986B314D6009, | 0xED5A7E85FDA0B80B, | 0x14588F13BE847307, | 0x596EB2D8AE258FC9, | 0x6FCA5F8ED9AEF3BB, | 0x25DE7BB9480D5855, | 0xAF561AA79A10AE6A, | 0x1B2BA1518094DA05, | 0x90FB44D2F05D0843, | 0x353A1607AC744A54, | 0x42889B8997915CE9, | 0x69956135FEBADA11, | 0x43FAB9837E699096, | 0x94F967E45E03F4BB, | 0x1D1BE0EEBAC278F5, | 0x6462D92A69731732, | 0x7D7B8F7503CFDCFF, | 0x5CDA735244C3D43F, | 0x3A0888136AFA64A7, | 0x088AAA1845B8FDD1, | 0x8AAD549E57273D45, | 0x36AC54E2F678864B, | 0x84576A1BB416A7DE, | 0x656D44A2A11C51D5, | 0x9F644AE5A4B1B325, | 0x873D5D9F0DDE1FEF, | 0xA90CB506D155A7EA, | 0x09A7F12442D588F3, | 0x0C11ED6D538AEB2F, | 0x8F1668C8A86DA5FB, | 0xF96E017D694487BD, | 0x37C981DCC395A9AC, | 0x85BBE253F47B1417, | 0x93956D7478CCEC8E, | 0x387AC8D1970027B2, | 0x06997B05FCC0319F, | 0x441FECE3BDF81F03, | 0xD527E81CAD7626C4, | 0x8A71E223D8D3B075, | 0xF6872D5667844E49, | 0xB428F8AC016561DB, | 0xE13336D701BEBA52, | 0xECC0024661173473, | 0x27F002D7F95D0190, | 0x31EC038DF7B441F4, | 0x7E67047175A15271, | 0x0F0062C6E984D387, | 0x52C07B78A3E60868, | 0xA7709A56CCDF8A83, | 0x88A66076400BB692, | 0x6ACFF893D00EA436, | 0x0583F6B8C4124D43, | 0xC3727A337A8B704A, | 0x744F18C0592E4C5D, | 0x1162DEF06F79DF74, | 0x8ADDCB5645AC2BA8, | 0x6D953E2BD7173693, | 0xC8FA8DB6CCDD0437, | 0x1D9C9892400A22A2, | 0x2503BEB6D00CAB4B, | 0x2E44AE64840FD61E, | 0x5CEAECFED289E5D3, | 0x7425A83E872C5F47, | 0xD12F124E28F77719, | 0x82BD6B70D99AAA70, | 0x636CC64D1001550C, | 0x3C47F7E05401AA4F, | 0x65ACFAEC34810A71, | 0x7F1839A741A14D0D, | 0x1EDE48111209A051, | 0x934AED0AAB460432, | 0xF81DA84D5617853F, | 0x36251260AB9D668F, | 0xC1D72B7C6B426019, | 0xB24CF65B8612F820, | 0xDEE033F26797B628, | 0x169840EF017DA3B1, | 0x8E1F289560EE864F, | 0xF1A6F2BAB92A27E3, | 0xAE10AF696774B1DB, | 0xACCA6DA1E0A8EF29, | 0x17FD090A58D32AF3, | 0xDDFC4B4CEF07F5B0, | 0x4ABDAF101564F98E, | 0x9D6D1AD41ABE37F2, | 0x84C86189216DC5EE, | 0x32FD3CF5B4E49BB5, | 0x3FBC8C33221DC2A2, | 0x0FABAF3FEAA5334A, | 0x29CB4D87F2A7400E, | 0x743E20E9EF511012, | 0x914DA9246B255417, | 0x1AD089B6C2F7548E, | 0xA184AC2473B529B2, | 0xC9E5D72D90A2741E, | 0x7E2FA67C7A658893, | 0xDDBB901B98FEEAB8, | 0x552A74227F3EA565, | 0xD53A88958F87275F, | 0x8A892ABAF368F137, | 0x2D2B7569B0432D85, | 0x9C3B29620E29FC73, | 0x8349F3BA91B47B90, | 0x241C70A936219A74, | 0xED238CD383AA0111, | 0xF4363804324A40AB, | 0xB143C6053EDCD0D5, | 0xDD94B7868E94050A, | 0xCA7CF2B4191C8327, | 0xFD1C2F611F63A3F0, | 0xBC633B39673C8CEC, | 0xD5BE0503E085D814, | 0x4B2D8644D8A74E19, | 0xDDF8E7D60ED1219F, | 0xCABB90E5C942B503, | 0x3D6A751F3B936244, | 0x0CC512670A783AD5, | 0x27FB2B80668B24C5, | 0xB1F9F660802DEDF6, | 0x5E7873F8A0396974, | 0xDB0B487B6423E1E8, | 0x91CE1A9A3D2CDA63, | 0x7641A140CC7810FB, | 0xA9E904C87FCB0A9D, | 0x546345FA9FBDCD44, | 0xA97C177947AD4095, | 0x49ED8EABCCCC485D, | 0x5C68F256BFFF5A75, | 0x73832EEC6FFF3112, | 0xC831FD53C5FF7EAB, | 0xBA3E7CA8B77F5E56, | 0x28CE1BD2E55F35EB, | 0x7980D163CF5B81B3, | 0xD7E105BCC3326220, | 0x8DD9472BF3FEFAA8, | 0xB14F98F6F0FEB952, | 0x6ED1BF9A569F33D3, | 0x0A862F80EC4700C8, | 0xCD27BB612758C0FA, | 0x8038D51CB897789C, | 0xE0470A63E6BD56C3, | 0x1858CCFCE06CAC74, | 0x0F37801E0C43EBC9, | 0xD30560258F54E6BB, | 0x47C6B82EF32A2069, | 0x4CDC331D57FA5442, | 0xE0133FE4ADF8E952, | 0x58180FDDD97723A7, | 0x570F09EAA7EA7648, | 0x2CD2CC6551E513DA, | 0xF8077F7EA65E58D1, | 0xFB04AFAF27FAF783, | 0x79C5DB9AF1F9B563, | 0x18375281AE7822BC, | 0x8F2293910D0B15B6, | 0xB2EB3875504DDB23, | 0x5FA60692A46151EC, | 0xDBC7C41BA6BCD333, | 0x12B9B522906C0800, | 0xD768226B34870A00, | 0xE6A1158300D46640, | 0x60495AE3C1097FD0, | 0x385BB19CB14BDFC4, | 0x46729E03DD9ED7B5, | 0x6C07A2C26A8346D1, | 0xC7098B7305241886, | 0xB8CBEE4FC66D1EA7, | 0x737F74F1DC043328, | 0x505F522E53053FF2, | 0x647726B9E7C68FEF, | 0x5ECA783430DC19F5, | 0xB67D16413D132073, | 0xE41C5BD18C57E88F, | 0x8E91B962F7B6F15A, | 0x723627BBB5A4ADB0, | 0xCEC3B1AAA30DD91C, | 0x213A4F0AA5E8A7B2, | 0xA988E2CD4F62D19E, | 0x93EB1B80A33B8605, | 0xBC72F130660533C3, | 0xEB8FAD7C7F8680B4, | 0xA67398DB9F6820E1, | 0x88083F8943A1148D, | 0x6A0A4F6B948959B0, | 0x848CE34679ABB01C, | 0xF2D80E0C0C0B4E12, | 0x6F8E118F0F0E2196, | 0x4B7195F2D2D1A9FB, | 0x8F26FDB7C3C30A3D, | 0xB2F0BD25B4B3CCCC, | 0xDFACEC6F21E0C000, | 0x9798278AEA58EFFF, | 0x5EBF18B6D2779600, | 0xF66EDEE487157B80, | 0xB40A969DA8DADA5F, | 0x70869E228988C87C, | 0xCCA845AB2BEAFA9B, | 0x3FD25715F6E5B941, | 0x07E3766DBA4F93C9, | 0x89DC540928E378BB, | 0x2C53690B731C56EA, | 0x9BB421A727F1B652, | 0x42A12A10F1EE23E7, | 0x134974952E69ACE0, | 0x2C0DE8DD3D020C0C, | 0x771163148C428F0F, | 0x54D5BBD9AF5332D3, | 0x350595680D93FFC4, | 0x8246FAC210F8FFB5, | 0x62D8B97295373FA2, | 0xFDC773E79D4287C5, | 0x7D3950E1849329B7, | 0xDC87A519E5B7F424, | 0xA9D4C7302F92F897, | 0xD449F8FC3B77B6BC, | 0xC95C773B4A55A46B, | 0x7DD9CA850E7586C3, | 0x5D503D265212E874, | 0x34A44C6FE697A291, | 0x40E6AFC5F01EC59B, | 0x91205BB76C267701, | 0x356872A5473014C1, | 0xC2C28F4E98FC19F2, | 0xD9B999911F9D9037, | 0x1027FFF56784F445, | 0xD431FFF2C1663156, | 0x049F3FF7B8DFDED6, | 0x85C70FF5A717D68B, | 0x2738D3F310DDCC2E, | 0xB8838477EA8A9F9D, | 0xE6A46595E52D4784, | 0x604D7EFB5E789965, | 0x1C306F5D1B0B5FDF, | 0x633C8B3461CE37D7, | 0x3C0BAE017A41C5CD, | 0xC5874CC0EC691BA0, | 0xF6E91FF127836288, | 0xB4A367ED71643B2A, | 0x50E620F466DEA4FA, | 0xA51FA93180964E39, | 0x8E67937DE0BBE1C7, | 0x7900BC2EAC756D1C, | 0x5740EB3A5792C863, | 0x2D112608ED777A7C, | 0x5C2AB7C5946AAC8E, | 0xF33565B6F98557B1, | 0xF002BF24B7E6AD9D, | 0xB601B776F2F02C82, | 0xE3822554AFAC37A3, | 0xDC62AEA9DB97458C, | 0x537B5A54527D16EE, | 0x742D1874B38E2E55, | 0xD1385E91E071B9EA, | 0x45867636588E2865, | 0x4B7409E1F758D93F, | 0x5E510C5A752F0F8F, | 0xB5E54F71127AD373, | 0x71AF51A6AB8CC428, | 0x4E1B2610566FF531, | 0xA1A1EF946C0BF27E, | 0x250535BCC387778F, | 0x6E46832BF4695572, | 0x89D823F6F183AACF, | 0x9627167A56F24AC1, | 0xBBB0DC18ECAEDD72, | 0x6A9D131F27DA94CE, | 0xA2A22BF378E89D01, | 0x0B4AB6F05722C441, | 0x4E1D64AC6CEB7551, | 0x90D25EEBC4132953, | 0xF506F6A6B517F3A7, | 0xF248B450625DF091, | 0xD76D70B23D7AB65B, | 0x0D48CCDECCD963F2, | 0x109B0016800FBCEE, | 0xCA60E00E1009D615, | 0x3CF91811940C4B9A, | 0xCC375E15F90F5E80, | 0x3FA29ACDBBA99B10, | 0x8F8B41812A9401D4, | 0x336E11E175390249, | 0x80499659D28742DC, | 0x302DFDF8239489C9, | 0xBC397D762C79AC3C, | 0x2B47DCD3B798174B, | 0xDB0CEA0452BF0E8F, | 0x51D02485676ED232, | 0xA6442DA6C14A86BF, | 0xA7EA9C8838CE9437, | 0x91E543AA47023945, | 0xB65E9494D8C2C796, | 0xB1FB1CDD0779BCBE, | 0xDE79E41449582BED, | 0xD6185D195BAE36E9, | 0x05CF3A2FD94CE251, | 0x074308BBCFA01AE6, | 0x4913CAEAC388219F, | 0x6DAC5ED2BA351504, | 0x8917768768C25A44, | 0xAB5D542942F2F0D6, | 0x4B1A5499C9D7D685, | 0x1DE0E9C03C4DCC27, | 0x255924304B613F31, | 0x3757B69E2F1CC77E, | 0xC52DA445BAE3F95E, | 0xF6790D57299CF7B5, | 0xFA0BA8567A021AD1, | 0xF88E926C1882A186, | 0xF6B237071EA349E7, | 0xF45EC4C8E64C1C61, | 0x78BB3AFD8FEF91BD, | 0xD6EA09BCF3EB762C, | 0x0CA48C2C30E653B7, | 0x27E6D79B9E8FF452, | 0xF1E08D828633F167, | 0xAE58B0E327C0EDC0, | 0x4CF76E8DF8D89498, | 0x60354A31770EB9BE, | 0x78429CBDD4D2682E, | 0xCB29A1F6A503811D, | 0x7DF40A744E446164, | 0x1D710D1161D579BD, | 0xF266A82ADD256C16, | 0x2F005235946EC71C, | 0x3AC066C2F98A78E2, | 0xC4B84039DBF68B8E, | 0xB5E6504852F42E71, | 0xE35FE45A67B13A0D, | 0x0E1BEEB880CEC448, | 0xD1A2EA66A102755A, | 0x460BA500494312B1, | 0xEBC747202DC9EBAF, | 0xA6B918E8393C669A, | 0x90675F22478B8041, | 0x7A409B756CB73028, | 0x58D0C252C7E4FC33, | 0xAF04F2E779DE3B3F, | 0xDAC62FA15855CA0F, | 0x48BBDDC4D7359E49, | 0x5AEAD5360D0305DC, | 0x71A58A839043C753, | 0xA70776923A2A5C94, | 0x50C95436C8B4F3B9, | 0x64FBA9447AE230A7, | 0xBF1D49CACCCD5E68, | 0xEEE49C3D8000B602, | 0x6A9DC34CE000E383, | 0x02A29A100C008E32, | 0xC34B40940F00B1BE, | 0xF41E10B912C0DE2E, | 0x7892CA73ABB88ADD, | 0xD6B77D1096A6AD94, | 0xCC655C54BC5058F9, |]; source/mir/bignum/internal/dec2float_table.d has no code <<<<<< EOF # path=source-mir-bignum-internal-kernel.lst |module mir.bignum.internal.kernel; | |import mir.bignum.internal.phobos_kernel; |public import mir.bignum.internal.phobos_kernel: karatsubaRequiredBuffSize, divisionRequiredBuffSize; | |private inout(uint)[] toUints(return scope inout ulong[] data) | @trusted pure nothrow @nogc |{ 916| auto ret = cast(typeof(return)) data; 1832| if (ret.length && ret[$ - 1] == 0) 860| ret = ret[0 .. $ - 1]; 916| return ret; |} | |static if (is(size_t == ulong)) |size_t multiply( | scope ulong[] c, | scope const(ulong)[] a, | scope const(ulong)[] b, | scope ulong[] buffer, |) | @safe pure nothrow @nogc 214| in (c.length >= a.length + b.length) |{ | pragma (inline, false); | 214| c[a.length + b.length - 1] = 0; | 214| auto length = multiply( | cast(uint[]) c[], | a.toUints, | b.toUints, | cast(uint[]) buffer[], | ); | 214| return length / 2 + length % 2; |} | |size_t multiply( | scope uint[] c, | scope const(uint)[] a, | scope const(uint)[] b, | scope uint[] buffer, |) | @trusted pure nothrow @nogc 214| in (c.length >= a.length + b.length) |{ | pragma (inline, false); | 214| if (a.length < b.length) | { 4| auto t = a; 4| a = b; 4| b = t; | } | 214| if (b.length == 0) 0000000| return 0; | 214| assert(a.length); 214| assert(b.length); 214| assert(c.length); | 214| c = c[0 .. a.length + b.length]; | 214| if (a is b) 148| squareInternal(c, a, buffer); | else 66| mulInternal(c, a, b, buffer); | 214| auto ret = a.length + b.length; 428| ret -= ret && !c[ret - 1]; 214| return ret; |} | | |static if (is(size_t == ulong)) |size_t divMod( | scope ulong[] q, | scope ulong[] r, | scope const(ulong)[] u, | scope const(ulong)[] v, | scope ulong[] buffer, |) | @trusted pure nothrow @nogc 488| in (u.length == 0 || u[$ - 1]) 244| in (v.length) 244| in (v[$ - 1]) 244| in (q.length + v.length >= u.length + 1) |{ | pragma (inline, false); | 244| sizediff_t idx = u.length - v.length; 244| if (idx < 0) 0000000| return 0; | 244| auto ui = u.toUints; 244| auto vi = v.toUints; | 244| auto length = divMod( | cast(uint[])q, | cast(uint[])r, | ui, | vi, | cast(uint[]) buffer, | ); | 484| if (r.length && vi.length % 2) 221| r[vi.length / 2] &= uint.max; | 244| if (q.ptr is r.ptr) 212| return 0; | 32| auto ret = length / 2; 32| if (length % 2) 11| q[ret++] &= uint.max; 32| return ret; |} | |size_t divMod( | scope uint[] q, | scope uint[] r, | scope const(uint)[] u, | scope const(uint)[] v, | scope uint[] buffer, |) | @trusted pure nothrow @nogc 496| in (u.length == 0 || u[$ - 1]) 248| in (v.length) 248| in (v[$ - 1]) 248| in (q.length + v.length >= u.length + 1) |{ | pragma (inline, false); | | import mir.bitop: ctlz; | import mir.utility: _expect; | 248| if (u.length < v.length) 0000000| return 0; | 248| q = q[0 .. u.length - v.length + 1]; 248| if (v.length == 1) | { 19| if (q !is u) 11| q[] = u; 19| auto rem = multibyteDivAssign(q, v[0], 0); 19| if (r) | { 18| r[0] = rem; 18| r[1 .. $] = 0; | } | } | else | { 229| divModInternal(q[], r ? r[0 .. v.length] : null, u, v, buffer); | } | 248| auto length = u.length - v.length; 248| return length + (q[length] != 0); |} | |/// |version(mir_bignum_test) |unittest |{ | import mir.bignum.fixed: UInt; | import mir.bignum.low_level_view: BigUIntView; | 1| uint[100] buffer = void; | auto divMod(BigUIntView!uint a, BigUIntView!uint b, BigUIntView!uint c) | { 4| .divMod( | c.coefficients, | a.coefficients, | a.coefficients, | b.coefficients, | buffer, | ); 4| a.coefficients[b.coefficients.length .. $] = 0; | } | | { | // Test division by a single-digit divisor here. 1| auto dividend = BigUIntView!uint.fromHexString("5"); 1| auto divisor = BigUIntView!uint.fromHexString("5"); 1| auto quotient = BigUIntView!uint.fromHexString("0"); | // auto remainder = BigUIntView!uint.fromHexString("0"); | 1| divMod(dividend, divisor, quotient); 1| assert(quotient == BigUIntView!uint.fromHexString("1")); | } | | { | // Test division by a single-digit divisor here. 1| auto dividend = BigUIntView!uint.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf"); 1| auto divisor = BigUIntView!uint.fromHexString("5"); 1| auto quotient = BigUIntView!uint.fromHexString("0000000000000000000000000000000000000000000000000000000000000000"); 1| divMod(dividend, divisor, quotient); 1| assert(dividend.normalized == BigUIntView!uint.fromHexString("3")); 1| assert(quotient == BigUIntView!uint.fromHexString("1120a1229e8a217d0691b02b819107174a4b677088ef0df874259b283c1d588c")); | } | | // Test big number division | { 1| auto dividend = BigUIntView!uint.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf0b5e2e2c7771cd472ae5d7acdb159a56fbf74f851a058ae341f69d1eb750d7e3"); 1| auto divisor = BigUIntView!uint.fromHexString("55e5669576d31726f4a9b58a90159de5923adc6c762ebd3c4ba518d495229072"); 1| auto quotient = BigUIntView!uint.fromHexString("00000000000000000000000000000000000000000000000000000000000000000"); | // auto remainder = BigUIntView!uint.fromHexString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); | 1| divMod(dividend, divisor, quotient); 1| assert(quotient.normalized == BigUIntView!uint.fromHexString("ff3a8aa4da35237811a0ffbf007fe938630dee8a810f2f82ae01f80c033291f6")); 1| assert(dividend.normalized == BigUIntView!uint.fromHexString("27926263cf248bef1c2cd63ea004d9f7041bffc8568560ec30fc9a9548057857")); | } | | // Trigger the adding back sequence | { 1| auto dividend = BigUIntView!uint.fromHexString("800000000000000000000003"); 1| auto divisor = BigUIntView!uint.fromHexString("200000000000000000000001"); 1| auto quotient = BigUIntView!uint.fromHexString("0"); | // auto remainder = BigUIntView!uint.fromHexString("000000000000000000000000"); 1| divMod(dividend, divisor, quotient); 1| assert(quotient == BigUIntView!uint.fromHexString("3")); 1| assert(dividend == BigUIntView!uint.fromHexString("200000000000000000000000")); | } |} source/mir/bignum/internal/kernel.d is 96% covered <<<<<< EOF # path=source-mir-bignum-internal-parse.lst |module mir.bignum.internal.parse; | |import std.traits: isSomeChar; |import mir.parse: DecimalExponentKey; | |/+ |https://arxiv.org/abs/2101.11408 |Number Parsing at a Gigabyte per Second |Daniel Lemire |+/ |bool isMadeOfEightDigits()(ref const char[8] chars) |{ | pragma(inline, true); 187| ulong val = (cast(ulong[1]) cast(ubyte[8]) chars)[0]; 187| return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); |} | |// ditto |uint parseEightDigits()(ref const char[8] chars) |{ | pragma(inline, true); 100| ulong val = (cast(ulong[1]) cast(ubyte[8]) chars)[0]; 100| val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; 100| val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; 100| return uint((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); |} | |/++ |+/ |struct SmallDecimalParsingResult |{ | /// | bool success; | /// | bool sign; | /// | DecimalExponentKey key; | /// | long exponent; | /// | ulong coefficient; |} | |/++ |+/ |SmallDecimalParsingResult parseJsonNumberImpl()(scope const(char)[] str) | @trusted pure nothrow @nogc 3| in (str.length) |{ | pragma(inline, true); | | alias W = ulong; | enum bool allowSpecialValues = false; | enum bool allowDotOnBounds = false; | enum bool allowDExponent = false; | enum bool allowStartingPlus = false; | enum bool allowUnderscores = false; | enum bool allowLeadingZeros = false; | enum bool allowExponent = true; | enum bool checkEmpty = false; | 3| typeof(return) result; | | bool mullAdd(ulong multiplier, ulong carry) | { | import mir.checkedint: mulu, addu; 15| bool overflow; 15| result.coefficient = mulu(result.coefficient, multiplier, overflow); 15| if (overflow) 0000000| return overflow; 15| result.coefficient = addu(result.coefficient, carry, overflow); 15| return overflow; | } | | alias impl = decimalFromStringImpl!(mullAdd, ulong); | alias specialization = impl!(char, | allowSpecialValues, | allowDotOnBounds, | allowDExponent, | allowStartingPlus, | allowUnderscores, | allowLeadingZeros, | allowExponent, | checkEmpty, | ); | | with(result) 3| success = specialization(str, key, exponent, sign); 3| return result; |} | |version(mir_test) |unittest |{ | import mir.test; 1| auto res = "-0.1234567890e-30".parseJsonNumberImpl; 1| res.key.should == DecimalExponentKey.e; 1| res.sign.should == true; 1| res.exponent.should == -40; 1| res.coefficient.should == 1234567890; |} | |version(mir_test) |unittest |{ | import mir.test; 1| auto res = "-12345.67890e-10".parseJsonNumberImpl; 1| res.key.should == DecimalExponentKey.e; 1| res.sign.should == true; 1| res.exponent.should == -15; 1| res.coefficient.should == 1234567890; |} | | |version(mir_test) |unittest |{ | import mir.test; 1| auto res = "2.9802322387695312E-8".parseJsonNumberImpl; 1| res.key.should == DecimalExponentKey.E; 1| res.sign.should == false; 1| res.exponent.should == -24; 1| res.coefficient.should == 29802322387695312; |} | |/++ |Returns: false in case of overflow or incorrect string. |+/ |template decimalFromStringImpl(alias mullAdd, W = size_t) |{ | bool decimalFromStringImpl(C, | bool allowSpecialValues, | bool allowDotOnBounds, | bool allowDExponent, | bool allowStartingPlus, | bool allowUnderscores, | bool allowLeadingZeros, | bool allowExponent, | bool checkEmpty, | ) | (scope const(C)[] str, out DecimalExponentKey key, scope ref long exponent, scope ref bool sign) | scope @trusted pure @nogc nothrow | if (isSomeChar!C) | { | version(LDC) | pragma(inline, true); | | import mir.utility: _expect; | static if (checkEmpty) | { | if (_expect(str.length == 0, false)) | return false; | } | 3| sign = str[0] == '-'; 3| if (sign) | { 2| str = str[1 .. $]; 2| if (_expect(str.length == 0, false)) 0000000| return false; | } | else | static if (allowStartingPlus) | { | if (_expect(str[0] == '+', false)) | { | str = str[1 .. $]; | if (_expect(str.length == 0, false)) | return false; | } | } | 3| W multiplier = 10; 3| W d = str[0] - C('0'); 3| str = str[1 .. $]; | 3| if (_expect(d >= 10, false)) | { | static if (allowDotOnBounds) | { | if (d == '.' - '0') | { | if (str.length == 0) | return false; | key = DecimalExponentKey.dot; | d = str[0] - C('0'); | str = str[1 .. $]; | if (_expect(d < 10, true)) | goto FI; | return false; | } | } | static if (allowSpecialValues) | goto NI; | else 0000000| return false; | } | | static if (!allowLeadingZeros) | { 3| if (_expect(d == 0, false)) | { 1| if (str.length == 0) 0000000| return true; 1| d = str[0] - C('0'); 1| str = str[1 .. $]; 1| if (d < 10) 0000000| return false; 1| goto DOT; | } | } | 2| goto F0; | | S: 6| if (str.length == 0) 0000000| return true; 6| d = str[0] - C('0'); 6| str = str[1 .. $]; | 6| if (d < 10) | { 4| multiplier = 10; | static if (is(C == char) && is(W == ulong)) 4| if (!__ctfe) 8| if (str.length >= 8 && isMadeOfEightDigits(str[0 .. 8])) | { 0000000| multiplier = 1000000000UL; 0000000| d *= 100000000; 0000000| d += parseEightDigits(str[0 .. 8]); 0000000| str = str[8 .. $]; 0000000| if (str.length >= 8 && isMadeOfEightDigits(str[0 .. 8])) | { 0000000| multiplier = 1000000000UL * 100000000; 0000000| d *= 100000000; 0000000| d += parseEightDigits(str[0 .. 8]); 0000000| str = str[8 .. $]; | } | } | | F0: 6| if (_expect(mullAdd(multiplier, d), false)) 0000000| return false; 6| goto S; | } | | static if (allowUnderscores) | { | if (d == '_' - '0') | { | if (str.length == 0) | return false; | d = str[0] - C('0'); | str = str[1 .. $]; | if (_expect(d < 10, true)) | goto F0; | return false; | } | } | DOT: 3| if (d == DecimalExponentKey.dot) | { | // The dot modifier CANNOT be preceded by any modifiers. 3| if (key != DecimalExponentKey.none) 0000000| return false; | 3| key = DecimalExponentKey.dot; | 3| if (str.length == 0) | { 0000000| return allowDotOnBounds; | } | | IF: 3| multiplier = 10; | static if (is(C == char) && is(W == ulong)) 3| if (!__ctfe) | { 6| if (str.length >= 8 && isMadeOfEightDigits(str[0 .. 8])) | { 2| multiplier = 100000000; 2| d = parseEightDigits(str[0 .. 8]); 2| str = str[8 .. $]; 2| exponent -= 8; 2| if (str.length >= 7) | { 1| if (isMadeOfEightDigits((str.ptr - 1)[0 .. 8])) | { 1| multiplier = 100000000UL * 10000000; 1| d -= str.ptr[-1] - '0'; 1| d *= 10000000; 1| d += parseEightDigits((str.ptr - 1)[0 .. 8]); 1| str = str[7 .. $]; 1| exponent -= 7; 1| if (str.length) | { 1| auto md = str[0] - C('0'); 1| if (md < 10) | { 1| d *= 10; 1| multiplier = 100000000UL * 100000000; 1| d += md; 1| str = str[1 .. $]; 1| exponent -= 1; | } | } | } | else | { | TrySix: 1| if (isMadeOfEightDigits((str.ptr - 2)[0 .. 8])) | { 0000000| multiplier = 100000000UL * 1000000; 0000000| d -= str.ptr[-1] - '0'; 0000000| d -= (str.ptr[-2] - '0') * 10; 0000000| d *= 1000000; 0000000| d += parseEightDigits((str.ptr - 2)[0 .. 8]); 0000000| str = str[6 .. $]; 0000000| exponent -= 6; | } | } | | } | else 1| if (str.length == 6) 1| goto TrySix; 2| goto FIL; | } | } | 1| d = str[0] - C('0'); 1| str = str[1 .. $]; 1| if (_expect(d >= 10, false)) 0000000| goto DOB; | FI: 7| exponent--; 7| multiplier = 10; | FIL: 9| if (_expect(mullAdd(multiplier, d), false)) 0000000| return false; 9| if (str.length == 0) 0000000| return true; 9| d = str[0] - C('0'); 9| str = str[1 .. $]; 9| if (d < 10) 6| goto FI; | | static if (allowUnderscores) | { | if (d == '_' - '0') | { | if (str.length == 0) | return false; | d = str[0] - C('0'); | str = str[1 .. $]; | if (_expect(d < 10, true)) | goto FI; | return false; | } | } | DOB: | static if (!allowDotOnBounds) | { 3| if (exponent == 0) 0000000| return false; | } | } | | static if (allowExponent) | { 3| if (d == DecimalExponentKey.e 1| || d == DecimalExponentKey.E 0000000| || d == DecimalExponentKey.d && allowDExponent 0000000| || d == DecimalExponentKey.D && allowDExponent | ) | { | import mir.parse: parse; 3| key = cast(DecimalExponentKey)d; 3| long exponentPlus; 6| if (parse(str, exponentPlus) && str.length == 0) | { | import mir.checkedint: adds; 3| bool overflow; 3| exponent = exponent.adds(exponentPlus, overflow); 3| return !overflow; | } | } | } 0000000| return false; | | static if (allowSpecialValues) | { | NI: | if (str.length == 2) | { | auto stail = cast(C[2])str[0 .. 2]; | if (d == 'i' - '0' && stail == cast(C[2])"nf" || d == 'I' - '0' && (stail == cast(C[2])"nf" || stail == cast(C[2])"NF")) | { | key = DecimalExponentKey.infinity; | return true; | } | if (d == 'n' - '0' && stail == cast(C[2])"an" || d == 'N' - '0' && (stail == cast(C[2])"aN" || stail == cast(C[2])"AN")) | { | key = DecimalExponentKey.nan; | return true; | } | } | return false; | } | } |} source/mir/bignum/internal/parse.d is 76% covered <<<<<< EOF # path=source-mir-bignum-internal-phobos_kernel.lst |/** Fundamental operations for arbitrary-precision arithmetic | * | * These functions are for internal use only. | */ | | // Original Phobos code: |/* Copyright Don Clugston 2008 - 2010. | * Distributed under the Boost Software License, Version 1.0. | * (See accompanying file LICENSE_1_0.txt or copy at | * http://www.boost.org/LICENSE_1_0.txt) | */ | | // All other changes: |/* Copyright Ilia Ki 2022 | * Distributed under Apache-2.0 License | */ | |/* References: | "Modern Computer Arithmetic" (MCA) is the primary reference for all | algorithms used in this library. | - R.P. Brent and P. Zimmermann, "Modern Computer Arithmetic", | Version 0.5.9, (Oct 2010). | - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022, | Max-Planck Institute fuer Informatik, (Oct 1998). | - G. Hanrot, M. Quercia, and P. Zimmermann, "The Middle Product Algorithm, I.", | INRIA 4664, (Dec 2002). | - M. Bodrato and A. Zanoni, "What about Toom-Cook Matrices Optimality?", | http://bodrato.it/papers (2006). | - A. Fog, "Optimizing subroutines in assembly language", | www.agner.org/optimize (2008). | - A. Fog, "The microarchitecture of Intel and AMD CPU's", | www.agner.org/optimize (2008). | - A. Fog, "Instruction tables: Lists of instruction latencies, throughputs | and micro-operation breakdowns for Intel and AMD CPU's.", www.agner.org/optimize (2008). |Idioms: | Many functions in this module use | 'func(Tulong)(Tulong x) if (is(Tulong == ulong))' rather than 'func(ulong x)' | in order to disable implicit conversion. |*/ |module mir.bignum.internal.phobos_kernel; | |static if (__VERSION__ > 2100) | version (D_InlineAsm_X86) | static import std.internal.math.biguintx86; | |version (DMD) |{ | static if (__VERSION__ > 2100) | version (D_InlineAsm_X86) | version = HaveAsmVersion; |} | |version (LDC) |{ | static if (__VERSION__ > 2100) | version (D_InlineAsm_X86) | version = HaveAsmVersion; | | version (ARM) | { | version (ARM_Thumb) {} | else | { | static import std.internal.math.biguintarm; | version = LDC_ARM_asm; | version = HaveAsmVersion; | } | } |} |static import std.internal.math.biguintnoasm; |alias multibyteAdd = multibyteAddSub!('+'); |alias multibyteSub = multibyteAddSub!('-'); | |// import std.internal.math.biguintnoasm : BigDigit, KARATSUBALIMIT, |// KARATSUBASQUARELIMIT; | |import std.internal.math.biguintnoasm : BigDigit; |enum FASTDIVLIMIT = 16; // crossover to recursive division |enum CACHELIMIT = 256; |enum KARATSUBALIMIT = 32; // Minimum value for which Karatsuba is worthwhile. |enum KARATSUBASQUARELIMIT = 32; // Minimum value for which square Karatsuba is worthwhile | |import std.ascii: LetterCase; | |pure nothrow @nogc: |package(mir.bignum): | |// dipatchers to the right low-level primitives. Added to allow BigInt CTFE for |// 32 bit systems (https://issues.dlang.org/show_bug.cgi?id=14767) although it's |// used by the other architectures too. |// See comments below in case it has to be refactored. |version (HaveAsmVersion) |uint multibyteAddSub(char op)(uint[] dest, const(uint)[] src1, const (uint)[] src2, uint carry) |{ | // must be checked before, otherwise D_InlineAsm_X86 is true. | if (__ctfe) | return std.internal.math.biguintnoasm.multibyteAddSub!op(dest, src1, src2, carry); | // Runtime. | else version (D_InlineAsm_X86) | return std.internal.math.biguintx86.multibyteAddSub!op(dest, src1, src2, carry); | else version (LDC_ARM_asm) | return std.internal.math.biguintarm.multibyteAddSub!op(dest, src1, src2, carry); | // Runtime if no asm available. | else | return std.internal.math.biguintnoasm.multibyteAddSub!op(dest, src1, src2, carry); |} |// Any other architecture |else alias multibyteAddSub = std.internal.math.biguintnoasm.multibyteAddSub; | |version (HaveAsmVersion) |uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) |{ | if (__ctfe) | return std.internal.math.biguintnoasm.multibyteIncrementAssign!op(dest, carry); | else version (D_InlineAsm_X86) | return std.internal.math.biguintx86.multibyteIncrementAssign!op(dest, carry); | else version (LDC_ARM_asm) | return std.internal.math.biguintarm.multibyteIncrementAssign!op(dest, carry); | else | return std.internal.math.biguintnoasm.multibyteIncrementAssign!op(dest, carry); |} |else alias multibyteIncrementAssign = std.internal.math.biguintnoasm.multibyteIncrementAssign; | |version (HaveAsmVersion) |uint multibyteShl()(uint[] dest, const(uint)[] src, uint numbits) |{ | if (__ctfe) | return std.internal.math.biguintnoasm.multibyteShl(dest, src, numbits); | else version (D_InlineAsm_X86) | return std.internal.math.biguintx86.multibyteShl(dest, src, numbits); | else version (LDC_ARM_asm) | return std.internal.math.biguintarm.multibyteShl(dest, src, numbits); | else | return std.internal.math.biguintnoasm.multibyteShl(dest, src, numbits); |} |else alias multibyteShl = std.internal.math.biguintnoasm.multibyteShl; | |version (HaveAsmVersion) |void multibyteShr()(uint[] dest, const(uint)[] src, uint numbits) |{ | if (__ctfe) | std.internal.math.biguintnoasm.multibyteShr(dest, src, numbits); | else version (D_InlineAsm_X86) | std.internal.math.biguintx86.multibyteShr(dest, src, numbits); | else version (LDC_ARM_asm) | std.internal.math.biguintarm.multibyteShr(dest, src, numbits); | else | std.internal.math.biguintnoasm.multibyteShr(dest, src, numbits); |} |else alias multibyteShr = std.internal.math.biguintnoasm.multibyteShr; | |version (HaveAsmVersion) |uint multibyteMul()(uint[] dest, const(uint)[] src, uint multiplier, uint carry) |{ | if (__ctfe) | return std.internal.math.biguintnoasm.multibyteMul(dest, src, multiplier, carry); | else version (D_InlineAsm_X86) | return std.internal.math.biguintx86.multibyteMul(dest, src, multiplier, carry); | else version (LDC_ARM_asm) | return std.internal.math.biguintarm.multibyteMul(dest, src, multiplier, carry); | else | return std.internal.math.biguintnoasm.multibyteMul(dest, src, multiplier, carry); |} |else alias multibyteMul = std.internal.math.biguintnoasm.multibyteMul; | |version (HaveAsmVersion) |uint multibyteMulAdd(char op)(uint[] dest, const(uint)[] src, uint multiplier, uint carry) |{ | if (__ctfe) | return std.internal.math.biguintnoasm.multibyteMulAdd!op(dest, src, multiplier, carry); | else version (D_InlineAsm_X86) | return std.internal.math.biguintx86.multibyteMulAdd!op(dest, src, multiplier, carry); | else version (LDC_ARM_asm) | return std.internal.math.biguintarm.multibyteMulAdd!op(dest, src, multiplier, carry); | else | return std.internal.math.biguintnoasm.multibyteMulAdd!op(dest, src, multiplier, carry); |} |else alias multibyteMulAdd = std.internal.math.biguintnoasm.multibyteMulAdd; | |version (HaveAsmVersion) |void multibyteMultiplyAccumulate()(uint[] dest, const(uint)[] left, const(uint)[] right) |{ | if (__ctfe) | std.internal.math.biguintnoasm.multibyteMultiplyAccumulate(dest, left, right); | else version (D_InlineAsm_X86) | std.internal.math.biguintx86.multibyteMultiplyAccumulate(dest, left, right); | else version (LDC_ARM_asm) | std.internal.math.biguintarm.multibyteMultiplyAccumulate(dest, left, right); | else | std.internal.math.biguintnoasm.multibyteMultiplyAccumulate(dest, left, right); |} |else alias multibyteMultiplyAccumulate = std.internal.math.biguintnoasm.multibyteMultiplyAccumulate; | |version (HaveAsmVersion) |uint multibyteDivAssign()(uint[] dest, uint divisor, uint overflow) |{ | if (__ctfe) | return std.internal.math.biguintnoasm.multibyteDivAssign(dest, divisor, overflow); | else version (D_InlineAsm_X86) | return std.internal.math.biguintx86.multibyteDivAssign(dest, divisor, overflow); | else version (LDC_ARM_asm) | return std.internal.math.biguintarm.multibyteDivAssign(dest, divisor, overflow); | else | return std.internal.math.biguintnoasm.multibyteDivAssign(dest, divisor, overflow); |} |else alias multibyteDivAssign = std.internal.math.biguintnoasm.multibyteDivAssign; | |version (HaveAsmVersion) |void multibyteAddDiagonalSquares()(uint[] dest, const(uint)[] src) |{ | if (__ctfe) | std.internal.math.biguintnoasm.multibyteAddDiagonalSquares(dest, src); | else version (D_InlineAsm_X86) | std.internal.math.biguintx86.multibyteAddDiagonalSquares(dest, src); | else version (LDC_ARM_asm) | std.internal.math.biguintarm.multibyteAddDiagonalSquares(dest, src); | else | std.internal.math.biguintnoasm.multibyteAddDiagonalSquares(dest, src); |} |else alias multibyteAddDiagonalSquares = std.internal.math.biguintnoasm.multibyteAddDiagonalSquares; | |version (HaveAsmVersion) |void multibyteTriangleAccumulate()(uint[] dest, const(uint)[] x) |{ | if (__ctfe) | std.internal.math.biguintnoasm.multibyteTriangleAccumulate(dest, x); | else version (D_InlineAsm_X86) | std.internal.math.biguintx86.multibyteTriangleAccumulate(dest, x); | else version (LDC_ARM_asm) | std.internal.math.biguintarm.multibyteTriangleAccumulate(dest, x); | else | std.internal.math.biguintnoasm.multibyteTriangleAccumulate(dest, x); |} |else alias multibyteTriangleAccumulate = std.internal.math.biguintnoasm.multibyteTriangleAccumulate; | |version (HaveAsmVersion) |void multibyteSquare()(BigDigit[] result, const(BigDigit)[] x) |{ | if (__ctfe) | std.internal.math.biguintnoasm.multibyteSquare(result, x); | else version (D_InlineAsm_X86) | std.internal.math.biguintx86.multibyteSquare(result, x); | else version (LDC_ARM_asm) | std.internal.math.biguintarm.multibyteSquare(result, x); | else | std.internal.math.biguintnoasm.multibyteSquare(result, x); |} |else alias multibyteSquare = std.internal.math.biguintnoasm.multibyteSquare; | | |// These constants are used by shift operations |static if (BigDigit.sizeof == int.sizeof) |{ | enum { LG2BIGDIGITBITS = 5, BIGDIGITSHIFTMASK = 31 } | alias BIGHALFDIGIT = ushort; |} |else static if (BigDigit.sizeof == long.sizeof) |{ | alias BIGHALFDIGIT = uint; | enum { LG2BIGDIGITBITS = 6, BIGDIGITSHIFTMASK = 63 } |} |else static assert(0, "Unsupported BigDigit size"); | |import std.traits : isIntegral; |enum BigDigitBits = BigDigit.sizeof*8; |template maxBigDigits(T) |if (isIntegral!T) |{ | enum maxBigDigits = (T.sizeof+BigDigit.sizeof-1)/BigDigit.sizeof; |} | |static immutable BigDigit[] ZERO = [0]; |static immutable BigDigit[] ONE = [1]; |static immutable BigDigit[] TWO = [2]; |static immutable BigDigit[] TEN = [10]; | |void twosComplement(const(BigDigit) [] x, BigDigit[] result) |pure nothrow @safe |{ 0000000| foreach (i; 0 .. x.length) | { 0000000| result[i] = ~x[i]; | } 0000000| result[x.length..$] = BigDigit.max; | 0000000| foreach (i; 0 .. result.length) | { 0000000| if (result[i] == BigDigit.max) | { 0000000| result[i] = 0; | } | else | { 0000000| result[i] += 1; 0000000| break; | } | } |} | |// works for any type |T intpow(T)(T x, ulong n) pure nothrow @safe |{ | T p; | | switch (n) | { | case 0: | p = 1; | break; | | case 1: | p = x; | break; | | case 2: | p = x * x; | break; | | default: | p = 1; | while (1) | { | if (n & 1) | p *= x; | n >>= 1; | if (!n) | break; | x *= x; | } | break; | } | return p; |} | | |// returns the maximum power of x that will fit in a uint. |int highestPowerBelowUintMax(uint x) pure nothrow @safe |{ 0000000| assert(x > 1, "x must be greater than 1"); | static immutable ubyte [22] maxpwr = [ 31, 20, 15, 13, 12, 11, 10, 10, 9, 9, | 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7]; 0000000| if (x<24) return maxpwr[x-2]; 0000000| if (x<41) return 6; 0000000| if (x<85) return 5; 0000000| if (x<256) return 4; 0000000| if (x<1626) return 3; 0000000| if (x<65_536) return 2; 0000000| return 1; |} | |// returns the maximum power of x that will fit in a ulong. |int highestPowerBelowUlongMax(uint x) pure nothrow @safe |{ 0000000| assert(x > 1, "x must be greater than 1"); | static immutable ubyte [39] maxpwr = [ 63, 40, 31, 27, 24, 22, 21, 20, 19, 18, | 17, 17, 16, 16, 15, 15, 15, 15, 14, 14, | 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, | 12, 12, 12, 12, 12, 12, 12, 12, 12]; 0000000| if (x<41) return maxpwr[x-2]; 0000000| if (x<57) return 11; 0000000| if (x<85) return 10; 0000000| if (x<139) return 9; 0000000| if (x<256) return 8; 0000000| if (x<566) return 7; 0000000| if (x<1626) return 6; 0000000| if (x<7132) return 5; 0000000| if (x<65_536) return 4; 0000000| if (x<2_642_246) return 3; 0000000| return 2; |} | |version (StdUnittest) |{ | |private int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe |{ | int pwr = 1; | for (ulong q = x;x*q < cast(ulong) uint.max; ) | { | q*=x; ++pwr; | } | return pwr; |} | |version(mir_bignum_test) |@safe pure unittest |{ | assert(highestPowerBelowUintMax(10)==9); | for (int k=82; k<88; ++k) | { | assert(highestPowerBelowUintMax(k)== slowHighestPowerBelowUintMax(k)); | } |} | |} | |/** General unsigned multiply routine for bigints. | * Sets result = x * y. | * | * The length of y must not be larger than the length of x. | * Different algorithms are used, depending on the lengths of x and y. | * TODO: "Modern Computer Arithmetic" suggests the OddEvenKaratsuba algorithm for the | * unbalanced case. (But I doubt it would be faster in practice). | * | */ |void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y, BigDigit[] scratchbuff) | pure nothrow @safe |{ 103| assert( result.length == x.length + y.length, | "result array must have enough space to store computed result"); 103| assert( y.length > 0, "y must not be empty"); 103| assert( x.length >= y.length, "x must be greater or equal than y"); 103| if (y.length < KARATSUBALIMIT) | { | // Small multiplier, we'll just use the asm classic multiply. 102| if (y.length == 1) | { // Trivial case, no cache effects to worry about 3| result[x.length] = multibyteMul(result[0 .. x.length], x, y[0], 0); 3| return; | } | | static assert (CACHELIMIT >= KARATSUBALIMIT * 2); | 99| auto chunksize = CACHELIMIT - y.length * 2; | 99| if (chunksize > x.length) 99| chunksize = x.length; | 99| mulSimple(result[0 .. chunksize + y.length], x[0 .. chunksize], y); | 99| auto done = chunksize; | 99| while (done < x.length) | { 0000000| if (done + chunksize > x.length) 0000000| chunksize = x.length - done; 0000000| BigDigit[KARATSUBALIMIT] partial = void; 0000000| partial[0 .. y.length] = result[done .. done+y.length]; 0000000| mulSimple(result[done .. done+chunksize+y.length], x[done .. done+chunksize], y); 0000000| addAssignSimple(result[done .. done+chunksize + y.length], partial[0 .. y.length]); 0000000| done += chunksize; | } 99| return; | } | 1| immutable half = (x.length >> 1) + (x.length & 1); 1| if (2*y.length*y.length <= x.length*x.length) | { | // UNBALANCED MULTIPLY | // Use school multiply to cut into quasi-squares of Karatsuba-size | // or larger. The ratio of the two sides of the 'square' must be | // between 1.414:1 and 1:1. Use Karatsuba on each chunk. | // | // For maximum performance, we want the ratio to be as close to | // 1:1 as possible. To achieve this, we can either pad x or y. | // The best choice depends on the modulus x%y. 1| auto numchunks = x.length / y.length; 1| auto chunksize = y.length; 1| auto extra = x.length % y.length; 1| auto maxchunk = chunksize + extra; 1| bool paddingY; // true = we're padding Y, false = we're padding X. 1| bool isExtraSmall = extra * extra * 2 < y.length * y.length; 2| if (numchunks == 1 && isExtraSmall) | { | // We divide (x_first_half * y) and (x_last_half * y) | // between 1.414:1 and 1.707:1 (1.707 = 1+1/sqrt(2)). | // (1.414 ~ 1.707)/2:1 is balanced. 1| BigDigit [] partial = scratchbuff[$ - y.length .. $]; 1| scratchbuff = scratchbuff[0 .. $ - y.length]; 1| mulKaratsuba(result[0 .. half + y.length], y, x[0 .. half], scratchbuff); 1| partial[] = result[half .. half + y.length]; 1| mulKaratsuba(result[half .. $], y, x[half .. $], scratchbuff); 1| BigDigit c = addAssignSimple(result[half .. half + y.length], partial); 1| if (c) multibyteIncrementAssign!('+')(result[half + y.length..$], c); | } | else | { 0000000| if (isExtraSmall) | { | // The leftover bit is small enough that it should be incorporated | // in the existing chunks. | // Make all the chunks a tiny bit bigger | // (We're padding y with zeros) 0000000| chunksize += extra / numchunks; 0000000| extra = x.length - chunksize*numchunks; | // there will probably be a few left over. | // Every chunk will either have size chunksize, or chunksize+1. 0000000| maxchunk = chunksize + 1; 0000000| paddingY = true; 0000000| assert(chunksize + extra + chunksize *(numchunks-1) == x.length, | "Unexpected size"); | } | else | { | // the extra bit is large enough that it's worth making a new chunk. | // (This means we're padding x with zeros, when doing the first one). 0000000| maxchunk = chunksize; 0000000| ++numchunks; 0000000| paddingY = false; 0000000| assert(extra + chunksize *(numchunks-1) == x.length, | "Unexpected size"); | } | // We make the buffer a bit bigger so we have space for the partial sums. 0000000| BigDigit [] partial = scratchbuff[$ - y.length .. $]; 0000000| scratchbuff = scratchbuff[0 .. $ - y.length]; 0000000| size_t done; // how much of X have we done so far? 0000000| if (paddingY) | { | // If the first chunk is bigger, do it first. We're padding y. 0000000| mulKaratsuba(result[0 .. y.length + chunksize + (extra > 0 ? 1 : 0 )], | x[0 .. chunksize + (extra>0?1:0)], y, scratchbuff); 0000000| done = chunksize + (extra > 0 ? 1 : 0); 0000000| if (extra) --extra; | } | else | { // We're padding X. Begin with the extra bit. 0000000| mulKaratsuba(result[0 .. y.length + extra], y, x[0 .. extra], scratchbuff); 0000000| done = extra; 0000000| extra = 0; | } 0000000| immutable basechunksize = chunksize; 0000000| while (done < x.length) | { 0000000| chunksize = basechunksize + (extra > 0 ? 1 : 0); 0000000| if (extra) --extra; 0000000| partial[] = result[done .. done+y.length]; 0000000| mulKaratsuba(result[done .. done + y.length + chunksize], | x[done .. done+chunksize], y, scratchbuff); 0000000| addAssignSimple(result[done .. done + y.length + chunksize], partial); 0000000| done += chunksize; | } | } | } | else | { | // Balanced. Use Karatsuba directly. 0000000| mulKaratsuba(result, x, y, scratchbuff); | } |} | |// https://issues.dlang.org/show_bug.cgi?id=20493 |version(mir_bignum_test) |@safe unittest |{ | // the bug report has a testcase with very large numbers (~10^3800 and ~10^2300) | // the number itself isn't important, only the amount of digits, so we do a simpler | // multiplication of the same size, analogous to: | // 11111111 * 11111111 = 0123456787654321 | // but instead of base 10, it's in base `BigDigit` | 1| BigDigit[398] x = 1; 1| BigDigit[236] y = 1; 1| BigDigit[x.length + y.length] result; 1| BigDigit[x.length.karatsubaRequiredBuffSize] buff; 1| mulInternal(result[], x[], y[], buff); | | // create an array of the form [1, 2, ..., y.length, ..., y.length, y.length-1, ..., 1, 0] 1| BigDigit[x.length + y.length] expected = y.length; 711| foreach (BigDigit i; 0 .. y.length) | { 236| expected[i] = i+1; 236| expected[$-1-i] = i; | } | 1| assert(result == expected); |} | |/** General unsigned squaring routine for BigInts. | * Sets result = x*x. | * NOTE: If the highest half-digit of x is zero, the highest digit of result will | * also be zero. | */ |void squareInternal(BigDigit[] result, const BigDigit[] x, BigDigit [] scratchbuff) pure nothrow @safe 148| in (scratchbuff.length >= x.length.karatsubaRequiredBuffSize) |{ | // Squaring is potentially half a multiply, plus add the squares of | // the diagonal elements. 148| assert(result.length == 2*x.length, | "result needs to have twice the capacity of x"); 148| if (x.length <= KARATSUBASQUARELIMIT) | { 148| if (x.length == 1) | { 5| result[1] = multibyteMul(result[0 .. 1], x, x[0], 0); 5| return; | } 286| return squareSimple(result, x); | } | // The nice thing about squaring is that it always stays balanced 0000000| squareKaratsuba(result, x, scratchbuff); |} | | |/// if remainder is null, only calculate quotient. |void divModInternal(BigDigit [] quotient, BigDigit[] remainder, const BigDigit [] u, | const BigDigit [] v, BigDigit[] buffer) pure nothrow @safe |{ | import core.bitop : bsr; | 232| assert(quotient.length == u.length - v.length + 1, | "Invalid quotient length"); 461| assert(remainder == null || remainder.length == v.length, | "Invalid remainder"); 232| assert(v.length > 1, "v must have more than 1 element"); 232| assert(u.length >= v.length, "u must be as longer or longer than v"); | | // Normalize by shifting v left just enough so that | // its high-order bit is on, and shift u left the | // same amount. The highest bit of u will never be set. | 232| auto vn = buffer[0 .. v.length]; 232| buffer = buffer[v.length .. $]; | 232| auto un = buffer[0 .. u.length + 1]; 232| buffer = buffer[u.length + 1 .. $]; | | // How much to left shift v, so that its MSB is set. 232| uint s = BIGDIGITSHIFTMASK - bsr(v[$-1]); 232| if (s != 0) | { 223| multibyteShl(vn, v, s); 223| un[$-1] = multibyteShl(un[0..$-1], u, s); | } | else | { 9| vn[] = v[]; 9| un[0..$-1] = u[]; 9| un[$-1] = 0; | } 232| if (quotient.length < FASTDIVLIMIT) | { 229| schoolbookDivMod(quotient, un, vn); | } | else | { 3| blockDivMod(quotient, un, vn, buffer); | } | | // Unnormalize remainder, if required. 232| if (remainder != null) | { 236| if (s == 0) remainder[] = un[0 .. vn.length]; 222| else multibyteShr(remainder, un[0 .. vn.length+1], s); | } |} | |version(mir_bignum_test) |pure @safe unittest |{ //7fff_00007fff_ffffffff_00020000 1| uint[3] u = [0, 0xFFFF_FFFE, 0x8000_0000]; 1| uint[2] v = [0xFFFF_FFFF, 0x8000_0000]; 1| uint[10] buffer = void; 1| uint[u.length - v.length + 1] q; 1| uint[2] r; 1| divModInternal(q, r, u, v, buffer); 1| assert(q[]==[0xFFFF_FFFFu, 0]); 1| assert(r[]==[0xFFFF_FFFFu, 0x7FFF_FFFF]); 1| u = [0, 0xFFFF_FFFE, 0x8000_0001]; 1| v = [0xFFFF_FFFF, 0x8000_0000]; 1| divModInternal(q, r, u, v, buffer); |} | | |// Converts a big uint to a hexadecimal string. |// |// Optionally, a separator character (eg, an underscore) may be added between |// every 8 digits. |// buff.length must be data.length*8 if separator is zero, |// or data.length*9 if separator is non-zero. It will be completely filled. |char [] biguintToHex(return scope char [] buff, const scope BigDigit [] data, char separator=0, | LetterCase letterCase = LetterCase.upper) pure nothrow @safe |{ 0000000| int x=0; 0000000| for (ptrdiff_t i=data.length - 1; i >= 0; --i) | { 0000000| toHexZeroPadded(buff[x .. x+8], data[i], letterCase); 0000000| x+=8; 0000000| if (separator) | { 0000000| if (i>0) buff[x] = separator; 0000000| ++x; | } | } 0000000| return buff; |} | |/** | * Convert a big uint into an octal string. | * | * Params: | * buff = The destination buffer for the octal string. Must be large enough to | * store the result, including leading zeroes, which is | * ceil(data.length * BigDigitBits / 3) characters. | * The buffer is filled from back to front, starting from `buff[$-1]`. | * data = The biguint to be converted. | * | * Returns: The index of the leading non-zero digit in `buff`. Will be | * `buff.length - 1` if the entire big uint is zero. | */ |size_t biguintToOctal(char[] buff, const(BigDigit)[] data) | pure nothrow @safe @nogc |{ 5| ubyte carry = 0; 5| int shift = 0; 5| size_t penPos = buff.length - 1; 5| size_t lastNonZero = buff.length - 1; | | pragma(inline) void output(uint digit) @nogc nothrow | { 108| if (digit != 0) 34| lastNonZero = penPos; 108| buff[penPos--] = cast(char)('0' + digit); | } | 45| foreach (bigdigit; data) | { 10| if (shift < 0) | { | // Some bits were carried over from previous word. 4| assert(shift > -3, "shift must be greater than -3"); 4| output(((bigdigit << -shift) | carry) & 0b111); 4| shift += 3; 4| assert(shift > 0, "shift must be 1 or greater"); | } | 110| while (shift <= BigDigitBits - 3) | { 100| output((bigdigit >>> shift) & 0b111); 100| shift += 3; | } | 10| if (shift < BigDigitBits) | { | // Some bits need to be carried forward. 8| carry = (bigdigit >>> shift) & 0b11; | } 10| shift -= BigDigitBits; 20| assert(shift >= -2 && shift <= 0, "shift must in [-2,0]"); | } | 5| if (shift < 0) | { | // Last word had bits that haven't been output yet. 4| assert(shift > -3, "Shift must be greater than -3"); 4| output(carry); | } | 5| return lastNonZero; |} | |/** Convert a big uint into a decimal string. | * | * Params: | * buff = The destination buffer for the decimal string. Must be | * large enough to store the result, including leading zeros. | * Will be filled backwards, starting from buff[$-1]. | * data = The biguint to be converted. Will be destroyed. | * | * buff.length must be >= (data.length*32)/log2(10) = 9.63296 * data.length. | * Returns: | * the lowest index of buff which was used. | */ |size_t biguintToDecimal(char [] buff, BigDigit [] data) pure nothrow @safe |{ 0000000| ptrdiff_t sofar = buff.length; | // Might be better to divide by (10^38/2^32) since that gives 38 digits for | // the price of 3 divisions and a shr; this version only gives 27 digits | // for 3 divisions. 0000000| while (data.length>1) | { 0000000| uint rem = multibyteDivAssign(data, 10_0000_0000, 0); 0000000| itoaZeroPadded(buff[sofar-9 .. sofar], rem); 0000000| sofar -= 9; 0000000| if (data[$-1] == 0 && data.length > 1) | { 0000000| data = data[0 .. $ - 1]; | } | } 0000000| itoaZeroPadded(buff[sofar-10 .. sofar], data[0]); 0000000| sofar -= 10; | // and strip off the leading zeros 0000000| while (sofar != buff.length-1 && buff[sofar] == '0') 0000000| sofar++; 0000000| return sofar; |} | |/** Convert a decimal string into a big uint. | * | * Params: | * data = The biguint to be receive the result. Must be large enough to | * store the result. | * s = The decimal string. May contain _ or 0 .. 9 | * | * The required length for the destination buffer is slightly less than | * 1 + s.length/log2(10) = 1 + s.length/3.3219. | * | * Returns: | * the highest index of data which was used. 0 if case of failure. | */ |int biguintFromDecimal(Range)(BigDigit[] data, Range s) |if ( | isInputRange!Range && | isSomeChar!(ElementType!Range) && | !isInfinite!Range) |in |{ | static if (hasLength!Range) | assert((data.length >= 2) || (data.length == 1 && s.length == 1), | "data has a invalid length"); |} |do |{ | // Convert to base 1e19 = 10_000_000_000_000_000_000. | // (this is the largest power of 10 that will fit into a long). | // The length will be less than 1 + s.length/log2(10) = 1 + s.length/3.3219. | // 485 bits will only just fit into 146 decimal digits. | // As we convert the string, we record the number of digits we've seen in base 19: | // hi is the number of digits/19, lo is the extra digits (0 to 18). | // TODO: This is inefficient for very large strings (it is O(n^^2)). | // We should take advantage of fast multiplication once the numbers exceed | // Karatsuba size. | uint lo = 0; // number of powers of digits, 0 .. 18 | uint x = 0; | ulong y = 0; | uint hi = 0; // number of base 1e19 digits | data[0] = 0; // initially number is 0. | if (data.length > 1) | data[1] = 0; | | foreach (character; s) | { | if (character == '_') | continue; | | if (character < '0' || character > '9') | return 0; | x *= 10; | x += character - '0'; | ++lo; | if (lo == 9) | { | y = x; | x = 0; | } | if (lo == 18) | { | y *= 10_0000_0000; | y += x; | x = 0; | } | if (lo == 19) | { | y *= 10; | y += x; | x = 0; | // Multiply existing number by 10^19, then add y1. | if (hi>0) | { | data[hi] = multibyteMul(data[0 .. hi], data[0 .. hi], 1_220_703_125*2u, 0); // 5^13*2 = 0x9184_E72A | ++hi; | data[hi] = multibyteMul(data[0 .. hi], data[0 .. hi], 15_625*262_144u, 0); // 5^6*2^18 = 0xF424_0000 | ++hi; | } | else | hi = 2; | uint c = multibyteIncrementAssign!('+')(data[0 .. hi], cast(uint)(y&0xFFFF_FFFF)); | c += multibyteIncrementAssign!('+')(data[1 .. hi], cast(uint)(y >> 32)); | if (c != 0) | { | data[hi]=c; | ++hi; | } | y = 0; | lo = 0; | } | } | // Now set y = all remaining digits. | if (lo >= 18) | { | } | else if (lo >= 9) | { | for (int k=9; k>> 32); | hi=2; | } | } | else | { | while (lo>0) | { | immutable c = multibyteMul(data[0 .. hi], data[0 .. hi], 10, 0); | if (c != 0) | { | data[hi]=c; | ++hi; | } | --lo; | } | uint c = multibyteIncrementAssign!('+')(data[0 .. hi], cast(uint)(y&0xFFFF_FFFF)); | if (y > 0xFFFF_FFFFL) | { | c += multibyteIncrementAssign!('+')(data[1 .. hi], cast(uint)(y >> 32)); | } | if (c != 0) | { | data[hi]=c; | ++hi; | } | } | } | while (hi>1 && data[hi-1]==0) | --hi; | return hi; |} | | |// ------------------------ |// These in-place functions are only for internal use; they are incompatible |// with COW. | |// Classic 'schoolbook' multiplication. |void mulSimple(BigDigit[] result, const(BigDigit) [] left, | const(BigDigit)[] right) pure nothrow @safe |in |{ 163| assert(result.length == left.length + right.length, | "Result must be able to store left + right"); 163| assert(right.length>1, "right must not be empty"); |} |do |{ 163| result[left.length] = multibyteMul(result[0 .. left.length], left, right[0], 0); 163| multibyteMultiplyAccumulate(result[1..$], left, right[1..$]); |} | |// Classic 'schoolbook' squaring |void squareSimple(BigDigit[] result, const(BigDigit) [] x) pure nothrow @safe |in |{ 143| assert(result.length == 2*x.length, "result must be twice as long as x"); 143| assert(x.length>1, "x must not be empty"); |} |do |{ 143| multibyteSquare(result, x); |} | | |// add two uints of possibly different lengths. Result must be as long |// as the larger length. |// Returns carry (0 or 1). |uint addSimple(BigDigit[] result, const BigDigit [] left, const BigDigit [] right) |pure nothrow @safe |in |{ 0000000| assert(result.length == left.length, | "result and left must be of the same length"); 0000000| assert(left.length >= right.length, | "left must be longer or of equal length to right"); 0000000| assert(right.length > 0, "right must not be empty"); |} |do |{ 0000000| uint carry = multibyteAdd(result[0 .. right.length], | left[0 .. right.length], right, 0); 0000000| if (right.length < left.length) | { 0000000| result[right.length .. left.length] = left[right.length .. $]; 0000000| carry = multibyteIncrementAssign!('+')(result[right.length..$], carry); | } 0000000| return carry; |} | |// result = left - right |// returns carry (0 or 1) |BigDigit subSimple(BigDigit [] result,const(BigDigit) [] left, | const(BigDigit) [] right) pure nothrow |in |{ 0000000| assert(result.length == left.length, | "result and left must be of the same length"); 0000000| assert(left.length >= right.length, | "left must be longer or of equal length to right"); 0000000| assert(right.length > 0, "right must not be empty"); |} |do |{ 0000000| BigDigit carry = multibyteSub(result[0 .. right.length], | left[0 .. right.length], right, 0); 0000000| if (right.length < left.length) | { 0000000| result[right.length .. left.length] = left[right.length .. $]; 0000000| carry = multibyteIncrementAssign!('-')(result[right.length..$], carry); | } //else if (result.length == left.length+1) { result[$-1] = carry; carry=0; } 0000000| return carry; |} | | |/* result = result - right | * Returns carry = 1 if result was less than right. |*/ |BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right) |pure nothrow @safe |{ 60| assert(result.length >= right.length, | "result must be longer or of equal length to right"); 60| uint c = multibyteSub(result[0 .. right.length], result[0 .. right.length], right, 0); 61| if (c && result.length > right.length) 1| c = multibyteIncrementAssign!('-')(result[right.length .. $], c); 60| return c; |} | |/* result = result + right |*/ |BigDigit addAssignSimple(BigDigit [] result, const(BigDigit) [] right) |pure nothrow @safe |{ 39| assert(result.length >= right.length, | "result must be longer or of equal length to right"); 39| uint c = multibyteAdd(result[0 .. right.length], result[0 .. right.length], right, 0); 39| if (c && result.length > right.length) 0000000| c = multibyteIncrementAssign!('+')(result[right.length .. $], c); 39| return c; |} | |/* performs result += wantSub? - right : right; |*/ |BigDigit addOrSubAssignSimple(BigDigit [] result, const(BigDigit) [] right, | bool wantSub) pure nothrow @safe |{ 30| if (wantSub) 24| return subAssignSimple(result, right); | else 6| return addAssignSimple(result, right); |} | | |// return true if x= y.length, | "x must be longer or of equal length to y"); 60| auto k = x.length-1; 112| while (x[k]==0 && k >= y.length) 16| --k; 60| if (k >= y.length) 34| return false; 1868| while (k>0 && x[k]==y[k]) 918| --k; 26| return x[k] < y[k]; |} | |// Set result = abs(x-y), return true if result is negative(x= y.length) ? x.length : y.length), | "result must capable to store the maximum of x and y"); | 60| size_t minlen; 60| bool negative; 60| if (x.length >= y.length) | { 60| minlen = y.length; 60| negative = less(x, y); | } | else | { 0000000| minlen = x.length; 0000000| negative = !less(y, x); | } 120| const (BigDigit)[] large, small; 60| if (negative) | { 12| large = y; small = x; | } | else | { 108| large = x; small = y; | } | 60| BigDigit carry = multibyteSub(result[0 .. minlen], large[0 .. minlen], small[0 .. minlen], 0); 60| if (x.length != y.length) | { 50| result[minlen .. large.length]= large[minlen..$]; 50| result[large.length..$] = 0; 50| if (carry) 8| multibyteIncrementAssign!('-')(result[minlen..$], carry); | } 60| return negative; |} | |/* Determine how much space is required for the temporaries | * when performing a Karatsuba multiplication. | * TODO: determining a tight bound is non-trivial and depends on KARATSUBALIMIT, see: | * https://issues.dlang.org/show_bug.cgi?id=20493 | */ |size_t karatsubaRequiredBuffSize()(size_t xlen) pure nothrow @safe |{ 148| return xlen < KARATSUBALIMIT ? 0 : (xlen * 9) / 4; |} | |size_t divisionRequiredBuffSize()(size_t ulen, size_t vlen) pure nothrow @safe |{ 0000000| return 2 * vlen + ulen + 2 + vlen.karatsubaRequiredBuffSize; |} | |/* Sets result = x*y, using Karatsuba multiplication. |* x must be longer or equal to y. |* Valid only for balanced multiplies, where x is not shorter than y. |* It is superior to schoolbook multiplication if and only if |* sqrt(2)*y.length > x.length > y.length. |* Karatsuba multiplication is O(n^1.59), whereas schoolbook is O(n^2) |* The maximum allowable length of x and y is uint.max; but better algorithms |* should be used far before that length is reached. |* Params: |* scratchbuff An array long enough to store all the temporaries. Will be destroyed. |*/ |void mulKaratsuba(BigDigit [] result, const(BigDigit) [] x, | const(BigDigit)[] y, BigDigit [] scratchbuff) pure nothrow @safe |{ 90| assert(x.length >= y.length, "x must be greater or equal to y"); 90| assert(result.length < uint.max, "Operands too large"); 90| assert(result.length == x.length + y.length, | "result must be as large as x + y"); 90| if (x.length < KARATSUBALIMIT) | { 120| return mulSimple(result, x, y); | } | // Must be almost square (otherwise, a schoolbook iteration is better) 30| assert(2L * y.length * y.length > (x.length-1) * (x.length-1), | "Bigint Internal Error: Asymmetric Karatsuba"); | | // The subtractive version of Karatsuba multiply uses the following result: | // (Nx1 + x0)*(Ny1 + y0) = (N*N)*x1y1 + x0y0 + N * (x0y0 + x1y1 - mid) | // where mid = (x0-x1)*(y0-y1) | // requiring 3 multiplies of length N, instead of 4. | // The advantage of the subtractive over the additive version is that | // the mid multiply cannot exceed length N. But there are subtleties: | // (x0-x1),(y0-y1) may be negative or zero. To keep it simple, we | // retain all of the leading zeros in the subtractions | | // half length, round up. 30| auto half = (x.length >> 1) + (x.length & 1); | 30| const(BigDigit) [] x0 = x[0 .. half]; 30| const(BigDigit) [] x1 = x[half .. $]; 30| const(BigDigit) [] y0 = y[0 .. half]; 30| const(BigDigit) [] y1 = y[half .. $]; 30| BigDigit [] mid = scratchbuff[0 .. half*2]; 30| BigDigit [] newscratchbuff = scratchbuff[half*2 .. $]; 30| BigDigit [] resultLow = result[0 .. 2*half]; 30| BigDigit [] resultHigh = result[2*half .. $]; | // initially use result to store temporaries 30| BigDigit [] xdiff= result[0 .. half]; 30| BigDigit [] ydiff = result[half .. half*2]; | | // First, we calculate mid, and sign of mid 30| immutable bool midNegative = inplaceSub(xdiff, x0, x1) | ^ inplaceSub(ydiff, y0, y1); 30| mulKaratsuba(mid, xdiff, ydiff, newscratchbuff); | | // Low half of result gets x0 * y0. High half gets x1 * y1 | 30| mulKaratsuba(resultLow, x0, y0, newscratchbuff); | 30| if (2L * y1.length * y1.length < x1.length * x1.length) | { | // an asymmetric situation has been created. | // Worst case is if x:y = 1.414 : 1, then x1:y1 = 2.41 : 1. | // Applying one schoolbook multiply gives us two pieces each 1.2:1 6| if (y1.length < KARATSUBALIMIT) 4| mulSimple(resultHigh, x1, y1); | else | { | // divide x1 in two, then use schoolbook multiply on the two pieces. 2| auto quarter = (x1.length >> 1) + (x1.length & 1); 2| immutable ysmaller = (quarter >= y1.length); 2| mulKaratsuba(resultHigh[0 .. quarter+y1.length], ysmaller ? x1[0 .. quarter] : y1, | ysmaller ? y1 : x1[0 .. quarter], newscratchbuff); | // Save the part which will be overwritten. 2| immutable ysmaller2 = ((x1.length - quarter) >= y1.length); 2| newscratchbuff[0 .. y1.length] = resultHigh[quarter .. quarter + y1.length]; 2| mulKaratsuba(resultHigh[quarter..$], ysmaller2 ? x1[quarter..$] : y1, | ysmaller2 ? y1 : x1[quarter..$], newscratchbuff[y1.length..$]); | 2| resultHigh[quarter..$].addAssignSimple(newscratchbuff[0 .. y1.length]); | } | } | else 24| mulKaratsuba(resultHigh, x1, y1, newscratchbuff); | | /* We now have result = x0y0 + (N*N)*x1y1 | Before adding or subtracting mid, we must calculate | result += N * (x0y0 + x1y1) | We can do this with three half-length additions. With a = x0y0, b = x1y1: | aHI aLO | + aHI aLO | + bHI bLO | + bHI bLO | = R3 R2 R1 R0 | R1 = aHI + bLO + aLO | R2 = aHI + bLO + aHI + carry_from_R1 | R3 = bHi + carry_from_R2 | It might actually be quicker to do it in two full-length additions: | newscratchbuff[2*half] = addSimple(newscratchbuff[0 .. 2*half], result[0 .. 2*half], result[2*half..$]); | addAssignSimple(result[half..$], newscratchbuff[0 .. 2*half+1]); | */ 30| BigDigit[] R1 = result[half .. half*2]; 30| BigDigit[] R2 = result[half*2 .. half*3]; 30| BigDigit[] R3 = result[half*3..$]; 30| BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1 30| BigDigit c2 = multibyteAdd(R1, R2, result[0 .. half], 0); // c2:R1 = R2 + R1 + R0 30| BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3 30| if (c1+c2) 0000000| multibyteIncrementAssign!('+')(result[half*2..$], c1+c2); 30| if (c1+c3) 0000000| multibyteIncrementAssign!('+')(R3, c1+c3); | | // And finally we subtract mid 30| addOrSubAssignSimple(result[half..$], mid, !midNegative); |} | |void squareKaratsuba(BigDigit [] result, const BigDigit [] x, | BigDigit [] scratchbuff) pure nothrow @safe |{ | // See mulKaratsuba for implementation comments. | // Squaring is simpler, since it never gets asymmetric. 0000000| assert(result.length < uint.max, "Operands too large"); 0000000| assert(result.length == 2*x.length, | "result must be twice the length of x"); 0000000| if (x.length <= KARATSUBASQUARELIMIT) | { 0000000| return squareSimple(result, x); | } | // half length, round up. 0000000| auto half = (x.length >> 1) + (x.length & 1); | 0000000| const(BigDigit)[] x0 = x[0 .. half]; 0000000| const(BigDigit)[] x1 = x[half .. $]; 0000000| BigDigit [] mid = scratchbuff[0 .. half*2]; 0000000| BigDigit [] newscratchbuff = scratchbuff[half*2 .. $]; | // initially use result to store temporaries 0000000| BigDigit [] xdiff= result[0 .. half]; 0000000| const BigDigit [] ydiff = result[half .. half*2]; | | // First, we calculate mid. We don't need its sign 0000000| inplaceSub(xdiff, x0, x1); 0000000| squareKaratsuba(mid, xdiff, newscratchbuff); | | // Set result = x0x0 + (N*N)*x1x1 0000000| squareKaratsuba(result[0 .. 2*half], x0, newscratchbuff); 0000000| squareKaratsuba(result[2*half .. $], x1, newscratchbuff); | | /* result += N * (x0x0 + x1x1) | Do this with three half-length additions. With a = x0x0, b = x1x1: | R1 = aHI + bLO + aLO | R2 = aHI + bLO + aHI + carry_from_R1 | R3 = bHi + carry_from_R2 | */ 0000000| BigDigit[] R1 = result[half .. half*2]; 0000000| BigDigit[] R2 = result[half*2 .. half*3]; 0000000| BigDigit[] R3 = result[half*3..$]; 0000000| BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1 0000000| BigDigit c2 = multibyteAdd(R1, R2, result[0 .. half], 0); // c2:R1 = R2 + R1 + R0 0000000| BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3 0000000| if (c1+c2) multibyteIncrementAssign!('+')(result[half*2..$], c1+c2); 0000000| if (c1+c3) multibyteIncrementAssign!('+')(R3, c1+c3); | | // And finally we subtract mid, which is always positive 0000000| subAssignSimple(result[half..$], mid); |} | |/* Knuth's Algorithm D, as presented in | * H.S. Warren, "Hacker's Delight", Addison-Wesley Professional (2002). | * Also described in "Modern Computer Arithmetic" 0.2, Exercise 1.8.18. | * Given u and v, calculates quotient = u / v, u = u % v. | * v must be normalized (ie, the MSB of v must be 1). | * The most significant words of quotient and u may be zero. | * u[0 .. v.length] holds the remainder. | */ |void schoolbookDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) | pure nothrow @safe |{ 271| assert(quotient.length == u.length - v.length, | "quotient has wrong length"); 271| assert(v.length > 1, "v must not be empty"); 271| assert(u.length >= v.length, "u must be larger or equal to v"); 271| assert((v[$ - 1] & 0x8000_0000) != 0, "Invalid value at v[$ - 1]"); 271| assert(u[$ - 1] < v[$ - 1], "u[$ - 1] must be less than v[$ - 1]"); | // BUG: This code only works if BigDigit is uint. 271| uint vhi = v[$-1]; 271| uint vlo = v[$-2]; | 3342| for (ptrdiff_t j = u.length - v.length - 1; j >= 0; j--) | { | // Compute estimate of quotient[j], | // qhat = (three most significant words of u)/(two most sig words of v). 1400| uint qhat; 1400| if (u[j + v.length] == vhi) | { | // uu/vhi could exceed uint.max (it will be 0x8000_0000 or 0x8000_0001) 1| qhat = uint.max; | } | else | { 1399| uint ulo = u[j + v.length - 2]; | version (D_InlineAsm_X86) | { | // Note: On DMD, this is only ~10% faster than the non-asm code. | uint *p = &u[j + v.length - 1]; | asm pure nothrow @trusted @nogc | { | mov EAX, p; | mov EDX, [EAX+4]; | mov EAX, [EAX]; | div dword ptr [vhi]; | mov qhat, EAX; | mov ECX, EDX; |div3by2correction: | mul dword ptr [vlo]; // EDX:EAX = qhat * vlo | sub EAX, ulo; | sbb EDX, ECX; | jbe div3by2done; | mov EAX, qhat; | dec EAX; | mov qhat, EAX; | add ECX, dword ptr [vhi]; | jnc div3by2correction; |div3by2done: ; | } | } | else | { // version (InlineAsm) 1399| ulong uu = (cast(ulong)(u[j + v.length]) << 32) | u[j + v.length - 1]; 1399| immutable bigqhat = uu / vhi; 1399| ulong rhat = uu - bigqhat * vhi; 1399| qhat = cast(uint) bigqhat; |again: 1802| if (cast(ulong) qhat * vlo > ((rhat << 32) + ulo)) | { 438| --qhat; 438| rhat += vhi; 438| if (!(rhat & 0xFFFF_FFFF_0000_0000L)) 403| goto again; | } | } // version (InlineAsm) | } | // Multiply and subtract. 1400| uint carry = multibyteMulAdd!('-')(u[j .. j + v.length], v, qhat, 0); | 1400| if (u[j+v.length] < carry) | { | // If we subtracted too much, add back 3| --qhat; 3| carry -= multibyteAdd(u[j .. j + v.length],u[j .. j + v.length], v, 0); | } 1400| quotient[j] = qhat; 1400| u[j + v.length] = u[j + v.length] - carry; | } |} | |// TODO: Replace with a library call |void itoaZeroPadded(char[] output, uint value) | pure nothrow @safe @nogc |{ 0000000| for (auto i = output.length; i--;) | { 0000000| if (value < 10) | { 0000000| output[i] = cast(char)(value + '0'); 0000000| value = 0; | } | else | { 0000000| output[i] = cast(char)(value % 10 + '0'); 0000000| value /= 10; | } | } |} | |void toHexZeroPadded(char[] output, uint value, | LetterCase letterCase = LetterCase.upper) pure nothrow @safe |{ 0000000| ptrdiff_t x = output.length - 1; | static immutable string upperHexDigits = "0123456789ABCDEF"; | static immutable string lowerHexDigits = "0123456789abcdef"; 0000000| for ( ; x >= 0; --x) | { 0000000| if (letterCase == LetterCase.upper) | { 0000000| output[x] = upperHexDigits[value & 0xF]; | } | else | { 0000000| output[x] = lowerHexDigits[value & 0xF]; | } 0000000| value >>= 4; | } |} | |// Returns the highest value of i for which left[i]!=right[i], |// or 0 if left[] == right[] |size_t highestDifferentDigit(const BigDigit [] left, const BigDigit [] right) |pure nothrow @nogc @safe |{ 0000000| assert(left.length == right.length, | "left have a length equal to that of right"); 0000000| for (ptrdiff_t i = left.length - 1; i>0; --i) | { 0000000| if (left[i] != right[i]) 0000000| return i; | } 0000000| return 0; |} | |// Returns the lowest value of i for which x[i]!=0. |int firstNonZeroDigit(const BigDigit [] x) pure nothrow @nogc @safe |{ 0000000| int k = 0; 0000000| while (x[k]==0) | { 0000000| ++k; 0000000| assert(k < x.length, "k must be less than x.length"); | } 0000000| return k; |} | |/* | Calculate quotient and remainder of u / v using fast recursive division. | v must be normalised, and must be at least half as long as u. | Given u and v, v normalised, calculates quotient = u/v, u = u%v. | scratch is temporary storage space, length must be >= quotient + 1. |Returns: | u[0 .. v.length] is the remainder. u[v.length..$] is corrupted. | Implements algorithm 1.8 from MCA. | This algorithm has an annoying special case. After the first recursion, the | highest bit of the quotient may be set. This means that in the second | recursive call, the 'in' contract would be violated. (This happens only | when the top quarter of u is equal to the top half of v. A base 10 | equivalent example of this situation is 5517/56; the first step gives | 55/5 = 11). To maintain the in contract, we pad a zero to the top of both | u and the quotient. 'mayOverflow' indicates that that the special case | has occurred. | (In MCA, a different strategy is used: the in contract is weakened, and | schoolbookDivMod is more general: it allows the high bit of u to be set). | See also: | - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022, | Max-Planck Institute fuer Informatik, (Oct 1998). |*/ |void recursiveDivMod(BigDigit[] quotient, BigDigit[] u, const(BigDigit)[] v, | BigDigit[] scratch, bool mayOverflow = false) | pure nothrow @safe |in |{ | // v must be normalized 60| assert(v.length > 1, "v must not be empty"); 60| assert((v[$ - 1] & 0x8000_0000) != 0, "Invalid value at v[$ - 1]"); 60| assert(!(u[$ - 1] & 0x8000_0000), "Invalid value at u[$ - 1]"); 60| assert(quotient.length == u.length - v.length, | "quotient must be of equal length of u - v"); 60| if (mayOverflow) | { 8| assert(u[$-1] == 0, "Invalid value at u[$ - 1]"); 8| assert(u[$-2] & 0x8000_0000, "Invalid value at u[$ - 2]"); | } | | // Must be symmetric. Use block schoolbook division if not. 60| assert((mayOverflow ? u.length-1 : u.length) <= 2 * v.length, | "Invalid length of u"); 60| assert((mayOverflow ? u.length-1 : u.length) >= v.length, | "Invalid length of u"); 60| assert(scratch.length >= quotient.length + (mayOverflow ? 0 : 1), | "Invalid quotient length"); |} |do |{ 60| if (quotient.length < FASTDIVLIMIT) | { 84| return schoolbookDivMod(quotient, u, v); | } | | // Split quotient into two halves, but keep padding in the top half 18| auto k = (mayOverflow ? quotient.length - 1 : quotient.length) >> 1; | | // RECURSION 1: Calculate the high half of the quotient | | // Note that if u and quotient were padded, they remain padded during | // this call, so in contract is satisfied. 18| recursiveDivMod(quotient[k .. $], u[2 * k .. $], v[k .. $], | scratch, mayOverflow); | | // quotient[k..$] is our guess at the high quotient. | // u[2*k .. 2.*k + v.length - k = k + v.length] is the high part of the | // first remainder. u[0 .. 2*k] is the low part. | | // Calculate the full first remainder to be | // remainder - highQuotient * lowDivisor | // reducing highQuotient until the remainder is positive. | // The low part of the remainder, u[0 .. k], cannot be altered by this. | 18| adjustRemainder(quotient[k .. $], u[k .. k + v.length], v, k, | scratch, mayOverflow); | | // RECURSION 2: Calculate the low half of the quotient | // The full first remainder is now in u[0 .. k + v.length]. | 18| if (u[k + v.length - 1] & 0x8000_0000) | { | // Special case. The high quotient is 0x1_00...000 or 0x1_00...001. | // This means we need an extra quotient word for the next recursion. | // We need to restore the invariant for the recursive calls. | // We do this by padding both u and quotient. Extending u is trivial, | // because the higher words will not be used again. But for the | // quotient, we're clobbering the low word of the high quotient, | // so we need save it, and add it back in after the recursive call. | 3| auto clobberedQuotient = quotient[k]; 3| u[k + v.length] = 0; | 3| recursiveDivMod(quotient[0 .. k + 1], u[k .. k + v.length + 1], | v[k .. $], scratch, true); 3| adjustRemainder(quotient[0 .. k + 1], u[0 .. v.length], v, k, | scratch, true); | | // Now add the quotient word that got clobbered earlier. 3| multibyteIncrementAssign!('+')(quotient[k..$], clobberedQuotient); | } | else | { | // The special case has NOT happened. 15| recursiveDivMod(quotient[0 .. k], u[k .. k + v.length], v[k .. $], | scratch, false); | | // high remainder is in u[k .. k+(v.length-k)] == u[k .. v.length] | 15| adjustRemainder(quotient[0 .. k], u[0 .. v.length], v, k, | scratch); | } |} | |// rem -= quot * v[0 .. k]. |// If would make rem negative, decrease quot until rem is >= 0. |// Needs (quot.length * k) scratch space to store the result of the multiply. |void adjustRemainder(BigDigit[] quot, BigDigit[] rem, const(BigDigit)[] v, | ptrdiff_t k, | BigDigit[] scratch, bool mayOverflow = false) pure nothrow @safe |{ 36| assert(rem.length == v.length, "rem must be as long as v"); 36| auto res = scratch[0 .. quot.length + k]; 36| mulInternal(res, quot, v[0 .. k], scratch[quot.length + k .. $]); 36| uint carry = subAssignSimple(rem, res[0..$-mayOverflow]); 36| if (mayOverflow) 5| carry += res[$-1]; 36| while (carry) | { 0000000| multibyteIncrementAssign!('-')(quot, 1); // quot-- 0000000| carry -= multibyteAdd(rem, rem, v, 0); | } |} |//u + 1, v, v kur, |// Cope with unbalanced division by performing block schoolbook division. |void blockDivMod(BigDigit [] q, BigDigit [] u, in BigDigit [] v, BigDigit [] scratch) |pure nothrow @safe |{ 4| assert(q.length == u.length - v.length, | "quotient must be of equal length of u - v"); 4| assert(v.length > 1, "v must not be empty"); 4| assert(u.length >= v.length, "u must be longer or of equal length as v"); 4| assert((v[$-1] & 0x8000_0000)!=0, "Invalid value at v[$ - 1]"); 4| assert((u[$-1] & 0x8000_0000)==0, "Invalid value at u[$ - 1]"); | | // Perform block schoolbook division, with 'v.length' blocks. 4| auto m = u.length - v.length; 4| auto n = u.length - v.length; | do | { 24| if (n <= v.length) 4| n = v.length; 24| auto mayOverflow = (u[m + v.length -1 ] & 0x8000_0000)!=0; 24| BigDigit saveq; 24| if (mayOverflow) | { 3| u[m + v.length] = 0; 3| saveq = q[m]; | } | 24| recursiveDivMod( | q[n - v.length .. m + mayOverflow], | u[n - v.length .. m + mayOverflow + v.length], | v, scratch, mayOverflow); 24| if (mayOverflow) | { 3| assert(q[m] == 0, "q must not be 0"); 3| q[m] = saveq; | } 24| m -= v.length; 24| n -= v.length; | } 24| while(n); |} | |version(mir_bignum_test) |@system unittest |{ | version (none) | { | import core.stdc.stdio; | | void printBiguint(const uint [] data) | { | char [] buff = biguintToHex(new char[data.length*9], data, '_'); | printf("%.*s\n", cast(int) buff.length, buff.ptr); | } | | void printDecimalBigUint(BigUint data) | { | auto str = data.toDecimalString(0); | printf("%.*s\n", cast(int) str.length, str.ptr); | } | } | 1| uint[43] a; 1| uint[179] b; 131| for (int i=0; i= 0); 265| assert(e <= 1 << 15); 265| return cast(uint) (((e * 163391164108059UL) >> 46) + 1); |} | |void mul_128_256_shift(const UInt!128 a, const UInt!256 b, const uint shift, const uint corr, ref UInt!256 result) |{ | version(LDC) pragma(inline, true); 94| assert(shift > 0); 94| assert(shift < 256); 94| result = (extendedMul(a, b) >> shift).toSize!256 + corr; |} | |// Computes 5^i in the form required by Ryu, and stores it in the given pointer. |void generic_computePow5(const uint i, ref UInt!256 result) |{ | import mir.bignum.internal.ryu.table; | | version(LDC) pragma(inline, true); 85| const uint base = i / POW5_TABLE_SIZE; 85| const uint base2 = base * POW5_TABLE_SIZE; 85| const mul = UInt!256(GENERIC_POW5_SPLIT[base]); 85| if (i == base2) | { 1| result = mul; | } | else | { 84| const uint offset = i - base2; 84| const m = UInt!128(GENERIC_POW5_TABLE[offset]); 84| const uint delta = pow5bits(i) - pow5bits(base2); 84| const uint corr = cast(uint) ((POW5_ERRORS[i / 32] >> (2 * (i % 32))) & 3); 84| mul_128_256_shift(m, mul, delta, corr, result); | } |} | |version(mir_bignum_test) unittest |{ | // We only test a few entries - we could test the fUL table instead, but should we? | static immutable uint[10] EXACT_POW5_IDS = [1, 10, 55, 56, 300, 1000, 2345, 3210, 4968 - 3, 4968 - 1]; | | static immutable ulong[4][10] EXACT_POW5 = [ | [ 0u, 0u, 0u, 90071992547409920u], | [ 0u, 0u, 0u, 83886080000000000u], | [ 0u, 15708555500268290048u, 14699724349295723422u, 117549435082228750u], | [ 0u, 5206161169240293376u, 4575641699882439235u, 73468396926392969u], | [ 2042133660145364371u, 9702060195405861314u, 6467325284806654637u, 107597969523956154u], | [15128847313296546509u, 11916317791371073891u, 788593023170869613u, 137108429762886488u], | [10998857860460266920u, 858411415306315808u, 12732466392391605111u, 136471991906002539u], | [ 5404652432687674341u, 18039986361557197657u, 2228774284272261859u, 94370442653226447u], | [15313487127299642753u, 9780770376910163681u, 15213531439620567348u, 93317108016191349u], | [ 7928436552078881485u, 723697829319983520u, 932817143438521969u, 72903990637649492u], | ]; | 22| for (int i = 0; i < 10; i++) | { 10| UInt!256 result; 10| generic_computePow5(EXACT_POW5_IDS[i], result); 10| assert(UInt!256(EXACT_POW5[i]) == result); | } |} | |// Computes 5^-i in the form required by Ryu, and stores it in the given pointer. |void generic_computeInvPow5(const uint i, ref UInt!256 result) |{ | import mir.bignum.internal.ryu.table; | | version(LDC) pragma(inline, true); 11| const uint base = (i + POW5_TABLE_SIZE - 1) / POW5_TABLE_SIZE; 11| const uint base2 = base * POW5_TABLE_SIZE; 11| const mul = UInt!256(GENERIC_POW5_INV_SPLIT[base]); // 1/5^base2 11| if (i == base2) | { 1| result = mul + 1; | } | else | { 10| const uint offset = base2 - i; 10| const m = UInt!128(GENERIC_POW5_TABLE[offset]); // 5^offset 10| const uint delta = pow5bits(base2) - pow5bits(i); 10| const uint corr = cast(uint) ((POW5_INV_ERRORS[i / 32] >> (2 * (i % 32))) & 3) + 1; 10| mul_128_256_shift(m, mul, delta, corr, result); | } |} | |version(mir_bignum_test) unittest |{ | static immutable uint[9] EXACT_INV_POW5_IDS = [10, 55, 56, 300, 1000, 2345, 3210, 4897 - 3, 4897 - 1]; | | static immutable ulong[4][10] EXACT_INV_POW5 = [ | [13362655651931650467u, 3917988799323120213u, 9037289074543890586u, 123794003928538027u], | [ 983662216614650042u, 15516934687640009097u, 8839031818921249472u, 88342353238919216u], | [ 1573859546583440066u, 2691002611772552616u, 6763753280790178510u, 141347765182270746u], | [ 1607391579053635167u, 943946735193622172u, 10726301928680150504u, 96512915280967053u], | [ 7238603269427345471u, 17319296798264127544u, 14852913523241959878u, 75740009093741608u], | [ 2734564866961569744u, 13277212449690943834u, 17231454566843360565u, 76093223027199785u], | [ 5348945211244460332u, 14119936934335594321u, 15647307321253222579u, 110040743956546595u], | [ 2848579222248330872u, 15087265905644220040u, 4449739884766224405u, 100774177495370196u], | [ 1432572115632717323u, 9719393440895634811u, 3482057763655621045u, 128990947194073851u], | ]; | 20| for (int i = 0; i < 9; i++) | { 9| UInt!256 result; 9| generic_computeInvPow5(EXACT_INV_POW5_IDS[i], result); 9| assert(UInt!256(EXACT_INV_POW5[i]) == result); | } |} | |version(LittleEndian) | enum fiveReciprocal = UInt!128([0xCCCCCCCCCCCCCCCD, 0xCCCCCCCCCCCCCCCC]); |else | enum fiveReciprocal = UInt!128([0xCCCCCCCCCCCCCCCC, 0xCCCCCCCCCCCCCCCD]); | |enum baseDiv5 = UInt!128([0x3333333333333333, 0x3333333333333333]); | |uint divRem5(uint size)(ref UInt!size value) |{ 0000000| auto q = div5(value); 0000000| auto r = cast(uint)(value - q * 5); 0000000| value = q; 0000000| return r; |} | |uint divRem10(uint size)(ref UInt!size value) |{ 940| auto q = div10(value); 940| auto r = cast(uint)(value - q * 10); 940| value = q; 940| return r; |} | |uint rem5(uint size)(UInt!size value) |{ 0000000| return divRem5(value); |} | |uint rem10(uint size)(UInt!size value) |{ | return divRem10(value); |} | |UInt!size div5(uint size)(UInt!size value) |{ 0000000| return extendedMulHigh(value, fiveReciprocal.toSize!size) >> 2; |} | |UInt!size div10(uint size)(UInt!size value) |{ 2974| return extendedMulHigh(value, fiveReciprocal.toSize!size) >> 3; |} | |// Returns true if value is divisible by 5^p. |bool multipleOfPowerOf5(uint size)(UInt!size value, const uint p) |{ | enum fiveReciprocal = .fiveReciprocal.toSize!size; | enum baseDiv5 = .baseDiv5.toSize!size; | version(LDC) pragma(inline, true); 10| assert(value); 19| for (uint count = 0;; ++count) | { 19| value *= fiveReciprocal; 19| if (value > baseDiv5) 10| return count >= p; | } |} | |version(mir_bignum_test) unittest |{ 1| assert(multipleOfPowerOf5(UInt!128(1), 0) == true); 1| assert(multipleOfPowerOf5(UInt!128(1), 1) == false); 1| assert(multipleOfPowerOf5(UInt!128(5), 1) == true); 1| assert(multipleOfPowerOf5(UInt!128(25), 2) == true); 1| assert(multipleOfPowerOf5(UInt!128(75), 2) == true); 1| assert(multipleOfPowerOf5(UInt!128(50), 2) == true); 1| assert(multipleOfPowerOf5(UInt!128(51), 2) == false); 1| assert(multipleOfPowerOf5(UInt!128(75), 4) == false); |} | |// Returns true if value is divisible by 2^p. |bool multipleOfPowerOf2(uint size)(const UInt!size value, const uint p) |{ | version(LDC) pragma(inline, true); 80| return (value & ((UInt!size(1) << p) - 1)) == 0; |} | |version(mir_bignum_test) unittest |{ 1| assert(multipleOfPowerOf5(UInt!128(1), 0) == true); 1| assert(multipleOfPowerOf5(UInt!128(1), 1) == false); 1| assert(multipleOfPowerOf2(UInt!128(2), 1) == true); 1| assert(multipleOfPowerOf2(UInt!128(4), 2) == true); 1| assert(multipleOfPowerOf2(UInt!128(8), 2) == true); 1| assert(multipleOfPowerOf2(UInt!128(12), 2) == true); 1| assert(multipleOfPowerOf2(UInt!128(13), 2) == false); 1| assert(multipleOfPowerOf2(UInt!128(8), 4) == false); |} | |UInt!size mulShift(uint size)(const UInt!size m, const UInt!256 mul, const uint j) |{ | version(LDC) pragma(inline, true); 234| assert(j > 128); 234| return (extendedMul(mul, m) >> 128 >> (j - 128)).toSize!size; |} | |version(mir_bignum_test) unittest |{ 1| UInt!256 m = cast(ulong[4])[0, 0, 2, 0]; 1| assert(mulShift(UInt!128(1), m, 129) == 1u); 1| assert(mulShift(UInt!128(12345), m, 129) == 12345u); |} | |version(mir_bignum_test) unittest |{ 1| UInt!256 m = cast(ulong[4])[0, 0, 8, 0]; 1| UInt!128 f = (UInt!128(123) << 64) | 321; 1| assert(mulShift(f, m, 131) == f); |} | |// Returns floor(log_10(2^e)). |uint log10Pow2(const int e) |{ | version(LDC) pragma(inline, true); | // The first value this approximation fails for is 2^1651 which is just greater than 10^297. 5| assert(e >= 0); 5| assert(e <= 1 << 15); 5| return (e * 0x9A209A84FBCFUL) >> 49; |} | |version(mir_bignum_test) unittest |{ 1| assert(log10Pow2(1) == 0u); 1| assert(log10Pow2(5) == 1u); 1| assert(log10Pow2(1 << 15) == 9864u); |} | |// Returns floor(log_10(5^e)). |uint log10Pow5(const int e) |{ | version(LDC) pragma(inline, true); | // The first value this approximation fails for is 5^2621 which is just greater than 10^1832. 79| assert(e >= 0); 79| assert(e <= 1 << 15); 79| return (e * 0xB2EFB2BD8218UL) >> 48; |} | |version(mir_bignum_test) unittest |{ 1| assert(log10Pow5(1) == 0u); 1| assert(log10Pow5(2) == 1u); 1| assert(log10Pow5(3) == 2u); 1| assert(log10Pow5(1 << 15) == 22903u); |} | |debug(ryu) |private char* s(UInt!128 v) |{ | import mir.conv: to; | return (v.to!string ~ "\0").ptr; |} | |// Converts the given binary floating point number to the shortest decimal floating point number |// that still accurately represents it. |Decimal!(T.mant_dig < 64 ? 1 : 2) genericBinaryToDecimal(T)(const T x) |{ | import mir.utility: _expect; | import mir.math: signbit, fabs; | enum coefficientSize = T.mant_dig <= 64 ? 64 : 128; | enum workSize = T.mant_dig < 64 ? 64 : 128; | enum wordCount = workSize / 64; | 92| Decimal!wordCount fd; 92| if (_expect(x != x, false)) | { 4| fd.coefficient = 1u; 4| fd.exponent = fd.exponent.max; | } | else 88| if (_expect(x.fabs == T.infinity, false)) | { 4| fd.exponent = fd.exponent.max; | } | else 84| if (x) | { | import mir.bignum.fp: Fp; 77| const fp = Fp!coefficientSize(x, false); 77| int e2 = cast(int) fp.exponent - 2; 77| UInt!workSize m2 = fp.coefficient; | 77| const bool even = (fp.coefficient & 1) == 0; 77| const bool acceptBounds = even; | | debug(ryu) if (!__ctfe) | { | printf("-> %s %s * 2^%d\n", (fp.sign ? "-" : "+").ptr, s(m2), e2 + 2); | } | | // Step 2: Determine the interval of legal decimal representations. 77| const UInt!workSize mv = m2 << 2; | // Implicit bool -> int conversion. True is 1, false is 0. 77| const bool mmShift = fp.coefficient != (UInt!coefficientSize(1) << (T.mant_dig - 1)); | | // Step 3: Convert to a decimal power base using 128-bit arithmetic. 231| UInt!workSize vr, vp, vm; 77| int e10; 77| bool vmIsTrailingZeros = false; 77| bool vrIsTrailingZeros = false; 77| if (e2 >= 0) | { | // I tried special-casing q == 0, but there was no effect on performance. | // This expression is slightly faster than max(0, log10Pow2(e2) - 1). 2| const uint q = log10Pow2(e2) - (e2 > 3); 2| e10 = q; 2| const int k = FLOAT_128_POW5_INV_BITCOUNT + pow5bits(q) - 1; 2| const int i = -e2 + q + k; 2| UInt!256 pow5; 2| generic_computeInvPow5(q, pow5); 2| vr = mulShift(mv, pow5, i); 2| vp = mulShift(mv + 2, pow5, i); 2| vm = mulShift(mv - 1 - mmShift, pow5, i); | debug(ryu) if (!__ctfe) | { | printf("%s * 2^%d / 10^%d\n", s(mv), e2, q); | printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); | } | // floor(log_5(2^128)) = 55, this is very conservative 2| if (q <= 55) | { | // Only one of mp, mv, and mm can be a multiple of 5, if any. 0000000| if (rem5(mv) == 0) | { 0000000| vrIsTrailingZeros = multipleOfPowerOf5(mv, q - 1); | } | else 0000000| if (acceptBounds) | { | // Same as min(e2 + (~mm & 1), pow5Factor(mm)) >= q | // <=> e2 + (~mm & 1) >= q && pow5Factor(mm) >= q | // <=> true && pow5Factor(mm) >= q, since e2 >= q. 0000000| vmIsTrailingZeros = multipleOfPowerOf5(mv - 1 - mmShift, q); | } | else | { | // Same as min(e2 + 1, pow5Factor(mp)) >= q. 0000000| vp -= multipleOfPowerOf5(mv + 2, q); | } | } | } | else | { | // This expression is slightly faster than max(0, log10Pow5(-e2) - 1). 75| const uint q = log10Pow5(-e2) - (-e2 > 1); 75| e10 = q + e2; 75| const int i = -e2 - q; 75| const int k = pow5bits(i) - FLOAT_128_POW5_BITCOUNT; 75| const int j = q - k; 75| UInt!256 pow5; 75| generic_computePow5(i, pow5); 75| vr = mulShift(mv, pow5, j); 75| vp = mulShift(mv + 2, pow5, j); 75| vm = mulShift(mv - 1 - mmShift, pow5, j); | debug(ryu) if (!__ctfe) | { | printf("%s * 5^%d / 10^%d\n", s(mv), -e2, q); | printf("%d %d %d %d\n", q, i, k, j); | printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); | } 75| if (q <= 1) | { | // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. | // mv = 4 m2, so it always has at least two trailing 0 bits. 0000000| vrIsTrailingZeros = true; 0000000| if (acceptBounds) | { | // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1. 0000000| vmIsTrailingZeros = mmShift == 1; | } | else | { | // mp = mv + 2, so it always has at least one trailing 0 bit. 0000000| --vp; | } | } | else 75| if (q < workSize - 1) | { | // TODO(ulfjack): Use a tighter bound here. | // We need to compute min(ntz(mv), pow5Factor(mv) - e2) >= q-1 | // <=> ntz(mv) >= q-1 && pow5Factor(mv) - e2 >= q-1 | // <=> ntz(mv) >= q-1 (e2 is negative and -e2 >= q) | // <=> (mv & ((1 << (q-1)) - 1)) == 0 | // We also need to make sure that the left shift does not overflow. 74| vrIsTrailingZeros = multipleOfPowerOf2(mv, q - 1); | debug(ryu) if (!__ctfe) | { | printf("vr is trailing zeros=%s\n", (vrIsTrailingZeros ? "true" : "false").ptr); | } | } | } | debug(ryu) if (!__ctfe) | { | printf("e10=%d\n", e10); | printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); | printf("vm is trailing zeros=%s\n", (vmIsTrailingZeros ? "true" : "false").ptr); | printf("vr is trailing zeros=%s\n", (vrIsTrailingZeros ? "true" : "false").ptr); | } | | // Step 4: Find the shortest decimal representation in the interval of legal representations. 77| uint removed = 0; 77| uint lastRemovedDigit = 0; 77| UInt!workSize output; | | for (;;) | { 1017| auto div10vp = div10(vp); 1017| auto div10vm = div10(vm); 1017| if (div10vp == div10vm) 77| break; 940| vmIsTrailingZeros &= vm - div10vm * 10 == 0; 940| vrIsTrailingZeros &= lastRemovedDigit == 0; 940| lastRemovedDigit = vr.divRem10; 940| vp = div10vp; 940| vm = div10vm; 940| ++removed; | } | debug(ryu) if (!__ctfe) | { | printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); | printf("d-10=%s\n", (vmIsTrailingZeros ? "true" : "false").ptr); | printf("lastRemovedDigit=%d\n", lastRemovedDigit); | } 77| if (vmIsTrailingZeros) | { | for (;;) | { 0000000| auto div10vm = div10(vm); 0000000| if (vm - div10vm * 10) 0000000| break; 0000000| vrIsTrailingZeros &= lastRemovedDigit == 0; 0000000| lastRemovedDigit = cast(uint) (vr - div10vm * 10); 0000000| vr = vp = vm = div10vm; 0000000| ++removed; | } | } | debug(ryu) if (!__ctfe) | { | printf("%s %d\n", s(vr), lastRemovedDigit); | printf("vr is trailing zeros=%s\n", (vrIsTrailingZeros ? "true" : "false").ptr); | printf("lastRemovedDigit=%d\n", lastRemovedDigit); | } 104| if (vrIsTrailingZeros && (lastRemovedDigit == 5) && ((vr & 1) == 0)) | { | // Round even if the exact numbers is .....50..0. 0000000| lastRemovedDigit = 4; | } | // We need to take vr+1 if vr is outside bounds or we need to round up. 167| output = vr + ((vr == vm && (!acceptBounds || !vmIsTrailingZeros)) || (lastRemovedDigit >= 5)); | 77| const int exp = e10 + removed; | | debug(ryu) if (!__ctfe) | { | printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); | printf("acceptBounds=%d\n", acceptBounds); | printf("vmIsTrailingZeros=%d\n", vmIsTrailingZeros); | printf("lastRemovedDigit=%d\n", lastRemovedDigit); | printf("vrIsTrailingZeros=%d\n", vrIsTrailingZeros); | printf("O=%s\n", s(output)); | printf("EXP=%d\n", exp); | } | | import mir.bignum.integer: BigInt; 77| fd.coefficient.__ctor(output); 77| fd.exponent = exp; | } 92| fd.coefficient.sign = x.signbit; 92| return fd; |} | |private enum FLOAT_128_POW5_INV_BITCOUNT = 249; |private enum FLOAT_128_POW5_BITCOUNT = 249; source/mir/bignum/internal/ryu/generic_128.d is 86% covered <<<<<< EOF # path=source-mir-bignum-internal-ryu-table.lst |module mir.bignum.internal.ryu.table; | |package enum POW5_TABLE_SIZE = 56; | |// These tables are ~4.5 kByte total, compared to ~160 kByte for the fUL tables. | |// There's no way to define 128-bit constants in C, so we use little-endian |// pairs of 64-bit constants. |package static immutable ulong[2] [POW5_TABLE_SIZE] GENERIC_POW5_TABLE = [ | [ 1u, 0u], | [ 5u, 0u], | [ 25u, 0u], | [ 125u, 0u], | [ 625u, 0u], | [ 3125u, 0u], | [ 15625u, 0u], | [ 78125u, 0u], | [ 390625u, 0u], | [ 1953125u, 0u], | [ 9765625u, 0u], | [ 48828125u, 0u], | [ 244140625u, 0u], | [ 1220703125u, 0u], | [ 6103515625u, 0u], | [ 30517578125u, 0u], | [ 152587890625u, 0u], | [ 762939453125u, 0u], | [ 3814697265625u, 0u], | [ 19073486328125u, 0u], | [ 95367431640625u, 0u], | [ 476837158203125u, 0u], | [ 2384185791015625u, 0u], | [ 11920928955078125u, 0u], | [ 59604644775390625u, 0u], | [ 298023223876953125u, 0u], | [ 1490116119384765625u, 0u], | [ 7450580596923828125u, 0u], | [ 359414837200037393u, 2u], | [ 1797074186000186965u, 10u], | [ 8985370930000934825u, 50u], | [ 8033366502585570893u, 252u], | [ 3273344365508751233u, 1262u], | [16366721827543756165u, 6310u], | [ 8046632842880574361u, 31554u], | [ 3339676066983768573u, 157772u], | [16698380334918842865u, 788860u], | [ 9704925379756007861u, 3944304u], | [11631138751360936073u, 19721522u], | [ 2815461535676025517u, 98607613u], | [14077307678380127585u, 493038065u], | [15046306170771983077u, 2465190328u], | [ 1444554559021708921u, 12325951644u], | [ 7222772795108544605u, 61629758220u], | [17667119901833171409u, 308148791101u], | [14548623214327650581u, 1540743955509u], | [17402883850509598057u, 7703719777548u], | [13227442957709783821u, 38518598887744u], | [10796982567420264257u, 192592994438723u], | [17091424689682218053u, 962964972193617u], | [11670147153572883801u, 4814824860968089u], | [ 3010503546735764157u, 24074124304840448u], | [15052517733678820785u, 120370621524202240u], | [ 1475612373555897461u, 601853107621011204u], | [ 7378061867779487305u, 3009265538105056020u], | [18443565265187884909u, 15046327690525280101u], |]; | |package static immutable ulong[4][89] GENERIC_POW5_SPLIT = [ | [ 0u, 0u, 0u, 72057594037927936u], | [ 0u, 5206161169240293376u, 4575641699882439235u, 73468396926392969u], | [ 3360510775605221349u, 6983200512169538081u, 4325643253124434363u, 74906821675075173u], | [ 11917660854915489451u, 9652941469841108803u, 946308467778435600u, 76373409087490117u], | [ 1994853395185689235u, 16102657350889591545u, 6847013871814915412u, 77868710555449746u], | [ 958415760277438274u, 15059347134713823592u, 7329070255463483331u, 79393288266368765u], | [ 2065144883315240188u, 7145278325844925976u, 14718454754511147343u, 80947715414629833u], | [ 8980391188862868935u, 13709057401304208685u, 8230434828742694591u, 82532576417087045u], | [ 432148644612782575u, 7960151582448466064u, 12056089168559840552u, 84148467132788711u], | [ 484109300864744403u, 15010663910730448582u, 16824949663447227068u, 85795995087002057u], | [ 14793711725276144220u, 16494403799991899904u, 10145107106505865967u, 87475779699624060u], | [ 15427548291869817042u, 12330588654550505203u, 13980791795114552342u, 89188452518064298u], | [ 9979404135116626552u, 13477446383271537499u, 14459862802511591337u, 90934657454687378u], | [ 12385121150303452775u, 9097130814231585614u, 6523855782339765207u, 92715051028904201u], | [ 1822931022538209743u, 16062974719797586441u, 3619180286173516788u, 94530302614003091u], | [ 12318611738248470829u, 13330752208259324507u, 10986694768744162601u, 96381094688813589u], | [ 13684493829640282333u, 7674802078297225834u, 15208116197624593182u, 98268123094297527u], | [ 5408877057066295332u, 6470124174091971006u, 15112713923117703147u, 100192097295163851u], | [ 11407083166564425062u, 18189998238742408185u, 4337638702446708282u, 102153740646605557u], | [ 4112405898036935485u, 924624216579956435u, 14251108172073737125u, 104153790666259019u], | [ 16996739107011444789u, 10015944118339042475u, 2395188869672266257u, 106192999311487969u], | [ 4588314690421337879u, 5339991768263654604u, 15441007590670620066u, 108272133262096356u], | [ 2286159977890359825u, 14329706763185060248u, 5980012964059367667u, 110391974208576409u], | [ 9654767503237031099u, 11293544302844823188u, 11739932712678287805u, 112553319146000238u], | [ 11362964448496095896u, 7990659682315657680u, 251480263940996374u, 114756980673665505u], | [ 1423410421096377129u, 14274395557581462179u, 16553482793602208894u, 117003787300607788u], | [ 2070444190619093137u, 11517140404712147401u, 11657844572835578076u, 119294583757094535u], | [ 7648316884775828921u, 15264332483297977688u, 247182277434709002u, 121630231312217685u], | [ 17410896758132241352u, 10923914482914417070u, 13976383996795783649u, 124011608097704390u], | [ 9542674537907272703u, 3079432708831728956u, 14235189590642919676u, 126439609438067572u], | [ 10364666969937261816u, 8464573184892924210u, 12758646866025101190u, 128915148187220428u], | [ 14720354822146013883u, 11480204489231511423u, 7449876034836187038u, 131439155071681461u], | [ 1692907053653558553u, 17835392458598425233u, 1754856712536736598u, 134012579040499057u], | [ 5620591334531458755u, 11361776175667106627u, 13350215315297937856u, 136636387622027174u], | [ 17455759733928092601u, 10362573084069962561u, 11246018728801810510u, 139311567287686283u], | [ 2465404073814044982u, 17694822665274381860u, 1509954037718722697u, 142039123822846312u], | [ 2152236053329638369u, 11202280800589637091u, 16388426812920420176u, 72410041352485523u], | [ 17319024055671609028u, 10944982848661280484u, 2457150158022562661u, 73827744744583080u], | [ 17511219308535248024u, 5122059497846768077u, 2089605804219668451u, 75273205100637900u], | [ 10082673333144031533u, 14429008783411894887u, 12842832230171903890u, 76746965869337783u], | [ 16196653406315961184u, 10260180891682904501u, 10537411930446752461u, 78249581139456266u], | [ 15084422041749743389u, 234835370106753111u, 16662517110286225617u, 79781615848172976u], | [ 8199644021067702606u, 3787318116274991885u, 7438130039325743106u, 81343645993472659u], | [ 12039493937039359765u, 9773822153580393709u, 5945428874398357806u, 82936258850702722u], | [ 984543865091303961u, 7975107621689454830u, 6556665988501773347u, 84560053193370726u], | [ 9633317878125234244u, 16099592426808915028u, 9706674539190598200u, 86215639518264828u], | [ 6860695058870476186u, 4471839111886709592u, 7828342285492709568u, 87903640274981819u], | [ 14583324717644598331u, 4496120889473451238u, 5290040788305728466u, 89624690099949049u], | [ 18093669366515003715u, 12879506572606942994u, 18005739787089675377u, 91379436055028227u], | [ 17997493966862379937u, 14646222655265145582u, 10265023312844161858u, 93168537870790806u], | [ 12283848109039722318u, 11290258077250314935u, 9878160025624946825u, 94992668194556404u], | [ 8087752761883078164u, 5262596608437575693u, 11093553063763274413u, 96852512843287537u], | [ 15027787746776840781u, 12250273651168257752u, 9290470558712181914u, 98748771061435726u], | [ 15003915578366724489u, 2937334162439764327u, 5404085603526796602u, 100682155783835929u], | [ 5225610465224746757u, 14932114897406142027u, 2774647558180708010u, 102653393903748137u], | [ 17112957703385190360u, 12069082008339002412u, 3901112447086388439u, 104663226546146909u], | [ 4062324464323300238u, 3992768146772240329u, 15757196565593695724u, 106712409346361594u], | [ 5525364615810306701u, 11855206026704935156u, 11344868740897365300u, 108801712734172003u], | [ 9274143661888462646u, 4478365862348432381u, 18010077872551661771u, 110931922223466333u], | [ 12604141221930060148u, 8930937759942591500u, 9382183116147201338u, 113103838707570263u], | [ 14513929377491886653u, 1410646149696279084u, 587092196850797612u, 115318278760358235u], | [ 2226851524999454362u, 7717102471110805679u, 7187441550995571734u, 117576074943260147u], | [ 5527526061344932763u, 2347100676188369132u, 16976241418824030445u, 119878076118278875u], | [ 6088479778147221611u, 17669593130014777580u, 10991124207197663546u, 122225147767136307u], | [ 11107734086759692041u, 3391795220306863431u, 17233960908859089158u, 124618172316667879u], | [ 7913172514655155198u, 17726879005381242552u, 641069866244011540u, 127058049470587962u], | [ 12596991768458713949u, 15714785522479904446u, 6035972567136116512u, 129545696547750811u], | [ 16901996933781815980u, 4275085211437148707u, 14091642539965169063u, 132082048827034281u], | [ 7524574627987869240u, 15661204384239316051u, 2444526454225712267u, 134668059898975949u], | [ 8199251625090479942u, 6803282222165044067u, 16064817666437851504u, 137304702024293857u], | [ 4453256673338111920u, 15269922543084434181u, 3139961729834750852u, 139992966499426682u], | [ 15841763546372731299u, 3013174075437671812u, 4383755396295695606u, 142733864029230733u], | [ 9771896230907310329u, 4900659362437687569u, 12386126719044266361u, 72764212553486967u], | [ 9420455527449565190u, 1859606122611023693u, 6555040298902684281u, 74188850200884818u], | [ 5146105983135678095u, 2287300449992174951u, 4325371679080264751u, 75641380576797959u], | [ 11019359372592553360u, 8422686425957443718u, 7175176077944048210u, 77122349788024458u], | [ 11005742969399620716u, 4132174559240043701u, 9372258443096612118u, 78632314633490790u], | [ 8887589641394725840u, 8029899502466543662u, 14582206497241572853u, 80171842813591127u], | [ 360247523705545899u, 12568341805293354211u, 14653258284762517866u, 81741513143625247u], | [ 12314272731984275834u, 4740745023227177044u, 6141631472368337539u, 83341915771415304u], | [ 441052047733984759u, 7940090120939869826u, 11750200619921094248u, 84973652399183278u], | [ 3436657868127012749u, 9187006432149937667u, 16389726097323041290u, 86637336509772529u], | [ 13490220260784534044u, 15339072891382896702u, 8846102360835316895u, 88333593597298497u], | [ 4125672032094859833u, 158347675704003277u, 10592598512749774447u, 90063061402315272u], | [ 12189928252974395775u, 2386931199439295891u, 7009030566469913276u, 91826390151586454u], | [ 9256479608339282969u, 2844900158963599229u, 11148388908923225596u, 93624242802550437u], | [ 11584393507658707408u, 2863659090805147914u, 9873421561981063551u, 95457295292572042u], | [ 13984297296943171390u, 1931468383973130608u, 12905719743235082319u, 97326236793074198u], | [ 5837045222254987499u, 10213498696735864176u, 14893951506257020749u, 99231769968645227u], |]; | |// Unfortunately, the results are sometimes off by one or two. We use an additional |// lookup table to store those cases and adjust the result. |package static immutable ulong[156] POW5_ERRORS = [ | 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x9555596400000000u, | 0x65a6569525565555u, 0x4415551445449655u, 0x5105015504144541u, 0x65a69969a6965964u, | 0x5054955969959656u, 0x5105154515554145u, 0x4055511051591555u, 0x5500514455550115u, | 0x0041140014145515u, 0x1005440545511051u, 0x0014405450411004u, 0x0414440010500000u, | 0x0044000440010040u, 0x5551155000004001u, 0x4554555454544114u, 0x5150045544005441u, | 0x0001111400054501u, 0x6550955555554554u, 0x1504159645559559u, 0x4105055141454545u, | 0x1411541410405454u, 0x0415555044545555u, 0x0014154115405550u, 0x1540055040411445u, | 0x0000000500000000u, 0x5644000000000000u, 0x1155555591596555u, 0x0410440054569565u, | 0x5145100010010005u, 0x0555041405500150u, 0x4141450455140450u, 0x0000000144000140u, | 0x5114004001105410u, 0x4444100404005504u, 0x0414014410001015u, 0x5145055155555015u, | 0x0141041444445540u, 0x0000100451541414u, 0x4105041104155550u, 0x0500501150451145u, | 0x1001050000004114u, 0x5551504400141045u, 0x5110545410151454u, 0x0100001400004040u, | 0x5040010111040000u, 0x0140000150541100u, 0x4400140400104110u, 0x5011014405545004u, | 0x0000000044155440u, 0x0000000010000000u, 0x1100401444440001u, 0x0040401010055111u, | 0x5155155551405454u, 0x0444440015514411u, 0x0054505054014101u, 0x0451015441115511u, | 0x1541411401140551u, 0x4155104514445110u, 0x4141145450145515u, 0x5451445055155050u, | 0x4400515554110054u, 0x5111145104501151u, 0x565a655455500501u, 0x5565555555525955u, | 0x0550511500405695u, 0x4415504051054544u, 0x6555595965555554u, 0x0100915915555655u, | 0x5540001510001001u, 0x5450051414000544u, 0x1405010555555551u, 0x5555515555644155u, | 0x5555055595496555u, 0x5451045004415000u, 0x5450510144040144u, 0x5554155555556455u, | 0x5051555495415555u, 0x5555554555555545u, 0x0000000010005455u, 0x4000005000040000u, | 0x5565555555555954u, 0x5554559555555505u, 0x9645545495552555u, 0x4000400055955564u, | 0x0040000000000001u, 0x4004100100000000u, 0x5540040440000411u, 0x4565555955545644u, | 0x1140659549651556u, 0x0100000410010000u, 0x5555515400004001u, 0x5955545555155255u, | 0x5151055545505556u, 0x5051454510554515u, 0x0501500050415554u, 0x5044154005441005u, | 0x1455445450550455u, 0x0010144055144545u, 0x0000401100000004u, 0x1050145050000010u, | 0x0415004554011540u, 0x1000510100151150u, 0x0100040400001144u, 0x0000000000000000u, | 0x0550004400000100u, 0x0151145041451151u, 0x0000400400005450u, 0x0000100044010004u, | 0x0100054100050040u, 0x0504400005410010u, 0x4011410445500105u, 0x0000404000144411u, | 0x0101504404500000u, 0x0000005044400400u, 0x0000000014000100u, 0x0404440414000000u, | 0x5554100410000140u, 0x4555455544505555u, 0x5454105055455455u, 0x0115454155454015u, | 0x4404110000045100u, 0x4400001100101501u, 0x6596955956966a94u, 0x0040655955665965u, | 0x5554144400100155u, 0xa549495401011041u, 0x5596555565955555u, 0x5569965959549555u, | 0x969565a655555456u, 0x0000001000000000u, 0x0000000040000140u, 0x0000040100000000u, | 0x1415454400000000u, 0x5410415411454114u, 0x0400040104000154u, 0x0504045000000411u, | 0x0000001000000010u, 0x5554000000001040u, 0x5549155551556595u, 0x1455541055515555u, | 0x0510555454554541u, 0x9555555555540455u, 0x6455456555556465u, 0x4524565555654514u, | 0x5554655255559545u, 0x9555455441155556u, 0x0000000051515555u, 0x0010005040000550u, | 0x5044044040000000u, 0x1045040440010500u, 0x0000400000040000u, 0x0000000000000000u, |]; | |package static immutable ulong[4][89] GENERIC_POW5_INV_SPLIT = [ | [ 0u, 0u, 0u, 144115188075855872u ], | [ 1573859546583440065u, 2691002611772552616u, 6763753280790178510u, 141347765182270746u ], | [ 12960290449513840412u, 12345512957918226762u, 18057899791198622765u, 138633484706040742u ], | [ 7615871757716765416u, 9507132263365501332u, 4879801712092008245u, 135971326161092377u ], | [ 7869961150745287587u, 5804035291554591636u, 8883897266325833928u, 133360288657597085u ], | [ 2942118023529634767u, 15128191429820565086u, 10638459445243230718u, 130799390525667397u ], | [ 14188759758411913794u, 5362791266439207815u, 8068821289119264054u, 128287668946279217u ], | [ 7183196927902545212u, 1952291723540117099u, 12075928209936341512u, 125824179589281448u ], | [ 5672588001402349748u, 17892323620748423487u, 9874578446960390364u, 123407996258356868u ], | [ 4442590541217566325u, 4558254706293456445u, 10343828952663182727u, 121038210542800766u ], | [ 3005560928406962566u, 2082271027139057888u, 13961184524927245081u, 118713931475986426u ], | [ 13299058168408384786u, 17834349496131278595u, 9029906103900731664u, 116434285200389047u ], | [ 5414878118283973035u, 13079825470227392078u, 17897304791683760280u, 114198414639042157u ], | [ 14609755883382484834u, 14991702445765844156u, 3269802549772755411u, 112005479173303009u ], | [ 15967774957605076027u, 2511532636717499923u, 16221038267832563171u, 109854654326805788u ], | [ 9269330061621627145u, 3332501053426257392u, 16223281189403734630u, 107745131455483836u ], | [ 16739559299223642282u, 1873986623300664530u, 6546709159471442872u, 105676117443544318u ], | [ 17116435360051202055u, 1359075105581853924u, 2038341371621886470u, 103646834405281051u ], | [ 17144715798009627550u, 3201623802661132408u, 9757551605154622431u, 101656519392613377u ], | [ 17580479792687825857u, 6546633380567327312u, 15099972427870912398u, 99704424108241124u ], | [ 9726477118325522902u, 14578369026754005435u, 11728055595254428803u, 97789814624307808u ], | [ 134593949518343635u, 5715151379816901985u, 1660163707976377376u, 95911971106466306u ], | [ 5515914027713859358u, 7124354893273815720u, 5548463282858794077u, 94070187543243255u ], | [ 6188403395862945512u, 5681264392632320838u, 15417410852121406654u, 92263771480600430u ], | [ 15908890877468271457u, 10398888261125597540u, 4817794962769172309u, 90492043761593298u ], | [ 1413077535082201005u, 12675058125384151580u, 7731426132303759597u, 88754338271028867u ], | [ 1486733163972670293u, 11369385300195092554u, 11610016711694864110u, 87050001685026843u ], | [ 8788596583757589684u, 3978580923851924802u, 9255162428306775812u, 85378393225389919u ], | [ 7203518319660962120u, 15044736224407683725u, 2488132019818199792u, 83738884418690858u ], | [ 4004175967662388707u, 18236988667757575407u, 15613100370957482671u, 82130858859985791u ], | [ 18371903370586036463u, 53497579022921640u, 16465963977267203307u, 80553711981064899u ], | [ 10170778323887491315u, 1999668801648976001u, 10209763593579456445u, 79006850823153334u ], | [ 17108131712433974546u, 16825784443029944237u, 2078700786753338945u, 77489693813976938u ], | [ 17221789422665858532u, 12145427517550446164u, 5391414622238668005u, 76001670549108934u ], | [ 4859588996898795878u, 1715798948121313204u, 3950858167455137171u, 74542221577515387u ], | [ 13513469241795711526u, 631367850494860526u, 10517278915021816160u, 73110798191218799u ], | [ 11757513142672073111u, 2581974932255022228u, 17498959383193606459u, 143413724438001539u ], | [ 14524355192525042817u, 5640643347559376447u, 1309659274756813016u, 140659771648132296u ], | [ 2765095348461978538u, 11021111021896007722u, 3224303603779962366u, 137958702611185230u ], | [ 12373410389187981037u, 13679193545685856195u, 11644609038462631561u, 135309501808182158u ], | [ 12813176257562780151u, 3754199046160268020u, 9954691079802960722u, 132711173221007413u ], | [ 17557452279667723458u, 3237799193992485824u, 17893947919029030695u, 130162739957935629u ], | [ 14634200999559435155u, 4123869946105211004u, 6955301747350769239u, 127663243886350468u ], | [ 2185352760627740240u, 2864813346878886844u, 13049218671329690184u, 125211745272516185u ], | [ 6143438674322183002u, 10464733336980678750u, 6982925169933978309u, 122807322428266620u ], | [ 1099509117817174576u, 10202656147550524081u, 754997032816608484u, 120449071364478757u ], | [ 2410631293559367023u, 17407273750261453804u, 15307291918933463037u, 118136105451200587u ], | [ 12224968375134586697u, 1664436604907828062u, 11506086230137787358u, 115867555084305488u ], | [ 3495926216898000888u, 18392536965197424288u, 10992889188570643156u, 113642567358547782u ], | [ 8744506286256259680u, 3966568369496879937u, 18342264969761820037u, 111460305746896569u ], | [ 7689600520560455039u, 5254331190877624630u, 9628558080573245556u, 109319949786027263u ], | [ 11862637625618819436u, 3456120362318976488u, 14690471063106001082u, 107220694767852583u ], | [ 5697330450030126444u, 12424082405392918899u, 358204170751754904u, 105161751436977040u ], | [ 11257457505097373622u, 15373192700214208870u, 671619062372033814u, 103142345693961148u ], | [ 16850355018477166700u, 1913910419361963966u, 4550257919755970531u, 101161718304283822u ], | [ 9670835567561997011u, 10584031339132130638u, 3060560222974851757u, 99219124612893520u ], | [ 7698686577353054710u, 11689292838639130817u, 11806331021588878241u, 97313834264240819u ], | [ 12233569599615692137u, 3347791226108469959u, 10333904326094451110u, 95445130927687169u ], | [ 13049400362825383933u, 17142621313007799680u, 3790542585289224168u, 93612312028186576u ], | [ 12430457242474442072u, 5625077542189557960u, 14765055286236672238u, 91814688482138969u ], | [ 4759444137752473128u, 2230562561567025078u, 4954443037339580076u, 90051584438315940u ], | [ 7246913525170274758u, 8910297835195760709u, 4015904029508858381u, 88322337023761438u ], | [ 12854430245836432067u, 8135139748065431455u, 11548083631386317976u, 86626296094571907u ], | [ 4848827254502687803u, 4789491250196085625u, 3988192420450664125u, 84962823991462151u ], | [ 7435538409611286684u, 904061756819742353u, 14598026519493048444u, 83331295300025028u ], | [ 11042616160352530997u, 8948390828345326218u, 10052651191118271927u, 81731096615594853u ], | [ 11059348291563778943u, 11696515766184685544u, 3783210511290897367u, 80161626312626082u ], | [ 7020010856491885826u, 5025093219346041680u, 8960210401638911765u, 78622294318500592u ], | [ 17732844474490699984u, 7820866704994446502u, 6088373186798844243u, 77112521891678506u ], | [ 688278527545590501u, 3045610706602776618u, 8684243536999567610u, 75631741404109150u ], | [ 2734573255120657297u, 3903146411440697663u, 9470794821691856713u, 74179396127820347u ], | [ 15996457521023071259u, 4776627823451271680u, 12394856457265744744u, 72754940025605801u ], | [ 13492065758834518331u, 7390517611012222399u, 1630485387832860230u, 142715675091463768u ], | [ 13665021627282055864u, 9897834675523659302u, 17907668136755296849u, 139975126841173266u ], | [ 9603773719399446181u, 10771916301484339398u, 10672699855989487527u, 137287204938390542u ], | [ 3630218541553511265u, 8139010004241080614u, 2876479648932814543u, 134650898807055963u ], | [ 8318835909686377084u, 9525369258927993371u, 2796120270400437057u, 132065217277054270u ], | [ 11190003059043290163u, 12424345635599592110u, 12539346395388933763u, 129529188211565064u ], | [ 8701968833973242276u, 820569587086330727u, 2315591597351480110u, 127041858141569228u ], | [ 5115113890115690487u, 16906305245394587826u, 9899749468931071388u, 124602291907373862u ], | [ 15543535488939245974u, 10945189844466391399u, 3553863472349432246u, 122209572307020975u ], | [ 7709257252608325038u, 1191832167690640880u, 15077137020234258537u, 119862799751447719u ], | [ 7541333244210021737u, 9790054727902174575u, 5160944773155322014u, 117561091926268545u ], | [ 12297384708782857832u, 1281328873123467374u, 4827925254630475769u, 115303583460052092u ], | [ 13243237906232367265u, 15873887428139547641u, 3607993172301799599u, 113089425598968120u ], | [ 11384616453739611114u, 15184114243769211033u, 13148448124803481057u, 110917785887682141u ], | [ 17727970963596660683u, 1196965221832671990u, 14537830463956404138u, 108787847856377790u ], | [ 17241367586707330931u, 8880584684128262874u, 11173506540726547818u, 106698810713789254u ], | [ 7184427196661305643u, 14332510582433188173u, 14230167953789677901u, 104649889046128358u ], |]; | |package static immutable ulong[154] POW5_INV_ERRORS = [ | 0x1144155514145504u, 0x0000541555401141u, 0x0000000000000000u, 0x0154454000000000u, | 0x4114105515544440u, 0x0001001111500415u, 0x4041411410011000u, 0x5550114515155014u, | 0x1404100041554551u, 0x0515000450404410u, 0x5054544401140004u, 0x5155501005555105u, | 0x1144141000105515u, 0x0541500000500000u, 0x1104105540444140u, 0x4000015055514110u, | 0x0054010450004005u, 0x4155515404100005u, 0x5155145045155555u, 0x1511555515440558u, | 0x5558544555515555u, 0x0000000000000010u, 0x5004000000000050u, 0x1415510100000010u, | 0x4545555444514500u, 0x5155151555555551u, 0x1441540144044554u, 0x5150104045544400u, | 0x5450545401444040u, 0x5554455045501400u, 0x4655155555555145u, 0x1000010055455055u, | 0x1000004000055004u, 0x4455405104000005u, 0x4500114504150545u, 0x0000000014000000u, | 0x5450000000000000u, 0x5514551511445555u, 0x4111501040555451u, 0x4515445500054444u, | 0x5101500104100441u, 0x1545115155545055u, 0x0000000000000000u, 0x1554000000100000u, | 0x5555545595551555u, 0x5555051851455955u, 0x5555555555555559u, 0x0000400011001555u, | 0x0000004400040000u, 0x5455511555554554u, 0x5614555544115445u, 0x6455156145555155u, | 0x5455855455415455u, 0x5515555144555545u, 0x0114400000145155u, 0x0000051000450511u, | 0x4455154554445100u, 0x4554150141544455u, 0x65955555559a5965u, 0x5555555854559559u, | 0x9569654559616595u, 0x1040044040005565u, 0x1010010500011044u, 0x1554015545154540u, | 0x4440555401545441u, 0x1014441450550105u, 0x4545400410504145u, 0x5015111541040151u, | 0x5145051154000410u, 0x1040001044545044u, 0x4001400000151410u, 0x0540000044040000u, | 0x0510555454411544u, 0x0400054054141550u, 0x1001041145001100u, 0x0000000140000000u, | 0x0000000014100000u, 0x1544005454000140u, 0x4050055505445145u, 0x0011511104504155u, | 0x5505544415045055u, 0x1155154445515554u, 0x0000000000004555u, 0x0000000000000000u, | 0x5101010510400004u, 0x1514045044440400u, 0x5515519555515555u, 0x4554545441555545u, | 0x1551055955551515u, 0x0150000011505515u, 0x0044005040400000u, 0x0004001004010050u, | 0x0000051004450414u, 0x0114001101001144u, 0x0401000001000001u, 0x4500010001000401u, | 0x0004100000005000u, 0x0105000441101100u, 0x0455455550454540u, 0x5404050144105505u, | 0x4101510540555455u, 0x1055541411451555u, 0x5451445110115505u, 0x1154110010101545u, | 0x1145140450054055u, 0x5555565415551554u, 0x1550559555555555u, 0x5555541545045141u, | 0x4555455450500100u, 0x5510454545554555u, 0x1510140115045455u, 0x1001050040111510u, | 0x5555454555555504u, 0x9954155545515554u, 0x6596656555555555u, 0x0140410051555559u, | 0x0011104010001544u, 0x965669659a680501u, 0x5655a55955556955u, 0x4015111014404514u, | 0x1414155554505145u, 0x0540040011051404u, 0x1010000000015005u, 0x0010054050004410u, | 0x5041104014000100u, 0x4440010500100001u, 0x1155510504545554u, 0x0450151545115541u, | 0x4000100400110440u, 0x1004440010514440u, 0x0000115050450000u, 0x0545404455541500u, | 0x1051051555505101u, 0x5505144554544144u, 0x4550545555515550u, 0x0015400450045445u, | 0x4514155400554415u, 0x4555055051050151u, 0x1511441450001014u, 0x4544554510404414u, | 0x4115115545545450u, 0x5500541555551555u, 0x5550010544155015u, 0x0144414045545500u, | 0x4154050001050150u, 0x5550511111000145u, 0x1114504055000151u, 0x5104041101451040u, | 0x0010501401051441u, 0x0010501450504401u, 0x4554585440044444u, 0x5155555951450455u, | 0x0040000400105555u, 0x0000000000000001u, |]; source/mir/bignum/internal/ryu/table.d has no code <<<<<< EOF # path=source-mir-bignum-low_level_view.lst |/++ |Low-level betterC utilities for big integer arithmetic libraries. | |The module provides $(REF BigUIntView), and $(LREF BigIntView), $(REF DecimalView). | |Note: | The module doesn't provide full arithmetic API for now. |+/ |module mir.bignum.low_level_view; | |import mir.checkedint; |import std.traits; | |private alias cop(string op : "-") = subu; |private alias cop(string op : "+") = addu; |private enum inverseSign(string op) = op == "+" ? "-" : "+"; | |package immutable hexStringErrorMsg = "Incorrect hex string for BigUIntView.fromHexString"; |package immutable binaryStringErrorMsg = "Incorrect binary string for BigUIntView.fromBinaryString"; |version (D_Exceptions) |{ | package immutable hexStringException = new Exception(hexStringErrorMsg); | package immutable binaryStringException = new Exception(binaryStringErrorMsg); |} | |package template MaxWordPow10(T) |{ | static if (is(T == ubyte)) | enum MaxWordPow10 = 2; | else | static if (is(T == ushort)) | enum MaxWordPow10 = 4; | else | static if (is(T == uint)) | enum MaxWordPow10 = 9; | else | static if (is(T == ulong)) | enum MaxWordPow10 = 19; | else | static assert(0); |} | |/++ |Fast integer computation of `ceil(log10(exp2(e)))` with 64-bit mantissa precision. |The result is guaranted to be greater then `log10(exp2(e))`, which is irrational number. |+/ |T ceilLog10Exp2(T)(const T e) | @safe pure nothrow @nogc | if (is(T == ubyte) || is(T == ushort) || is(T == uint) || is(T == ulong)) |{ | import mir.utility: extMul; 77| auto result = extMul(0x9a209a84fbcff799UL, e); 77| return cast(T) ((result.high >> 1) + ((result.low != 0) | (result.high & 1))); |} | |/// |version(mir_bignum_test_llv) |@safe pure nothrow @nogc unittest |{ 1| assert(ceilLog10Exp2(ubyte(10)) == 4); // ubyte 1| assert(ceilLog10Exp2(10U) == 4); // uint 1| assert(ceilLog10Exp2(10UL) == 4); // ulong |} | |/++ |Arbitrary length unsigned integer view. |+/ |struct BigUIntView(W) | if (__traits(isUnsigned, W)) |{ | import mir.bignum.fp: Fp, half; | import mir.bignum.fixed: UInt; | | /++ | A group of coefficients for a radix `W.max + 1`. | | The order corresponds to endianness. | +/ | W[] coefficients; | |@safe: | | /++ | Retrurns: signed integer view using the same data payload | +/ | size_t length()() @safe pure nothrow @nogc const @property | { 217| return coefficients.length; | } | | /++ | Retrurns: signed integer view using the same data payload | +/ | BigIntView!W signed()(bool sign = false) @safe pure nothrow @nogc return @property | { 4| return typeof(return)(this, sign); | } | | static if (W.sizeof >= size_t.sizeof) | /// | T opCast(T)() const scope | if (isFloatingPoint!T && isMutable!T) | { | import mir.bignum.internal.dec2float: binaryTo; 10| return normalized.coefficients.binaryTo!T; | } | | static if (W.sizeof >= size_t.sizeof) | /// | @safe | T opCast(T : Fp!coefficientSize, uint coefficientSize)() const scope | { | static if (isMutable!W) | { 11| return lightConst.opCast!T; | } | else | static if (W.sizeof > size_t.sizeof) | { | return lightConst.opCast!(BigUIntView!(const size_t)).opCast!T; | } | else | { | import mir.bignum.internal.dec2float: binaryToFp; 11| auto coefficients = normalized.coefficients; 11| return coefficients.length | ? coefficients.binaryToFp!coefficientSize | : Fp!coefficientSize.init; | } | } | | /// | T opCast(T, bool nonZero = false)() const scope | if (isIntegral!T && isUnsigned!T && isMutable!T) | { 61| auto work = lightConst; | static if (!nonZero) | { 61| if (coefficients.length == 0) | { 2| return 0; | } | } | static if (T.sizeof <= W.sizeof) | { 24| return cast(T) work.coefficients[0]; | } | else | { 35| T ret; | do | { 404| ret <<= W.sizeof * 8; 404| ret |= work.coefficients[$ - 1]; 404| work.popMostSignificant; | } 404| while(work.coefficients.length); 35| return ret; | } | } | | /// | pure nothrow @nogc | BigUIntView!V opCast(T : BigUIntView!V, V)() return scope | if (V.sizeof <= W.sizeof) | { 39| return typeof(return)(cast(V[])this.coefficients); | } | | pure nothrow @nogc | BigUIntView!V opCast(T : BigUIntView!V, V)() const return scope | if (V.sizeof <= W.sizeof) | { | return typeof(return)(cast(V[])this.coefficients); | } | | /// | BigUIntView!(const W) lightConst()() return scope | const @safe pure nothrow @nogc @property | { 3122| return typeof(return)(coefficients); | } | ///ditto | alias lightConst this; | | /++ | +/ | sizediff_t opCmp(scope BigUIntView!(const W) rhs) | const @safe pure nothrow @nogc scope | { | import mir.algorithm.iteration: cmp; 21| auto l = this.lightConst.normalized; 21| auto r = rhs.lightConst.normalized; 21| if (sizediff_t d = l.coefficients.length - r.coefficients.length) 6| return d; 15| return cmp(l.mostSignificantFirst, r.mostSignificantFirst); | } | | /// | bool opEquals(BigUIntView!(const W) rhs) | const @safe pure nothrow @nogc scope | { 32| return this.coefficients == rhs.coefficients; | } | | /++ | +/ | void popMostSignificant() scope | { 452| coefficients = coefficients[0 .. $ - 1]; | } | | /++ | +/ | void popLeastSignificant() scope | { 0000000| coefficients = coefficients[1 .. $]; | } | | /++ | +/ | BigUIntView topMostSignificantPart(size_t length) | { 16| return BigUIntView(coefficients[$ - length .. $]); | } | | /++ | +/ | BigUIntView topLeastSignificantPart(size_t length) | { 1632| return BigUIntView(coefficients[0 .. length]); | } | | /++ | Shifts left using at most `size_t.sizeof * 8 - 1` bits | +/ | void smallLeftShiftInPlace()(uint shift) scope | { 2| assert(shift < W.sizeof * 8); 2| if (shift == 0) 1| return; 1| auto csh = W.sizeof * 8 - shift; 1| auto d = coefficients[]; 1| assert(d.length); 5| foreach_reverse (i; 1 .. d.length) 1| d[i] = (d[i] << shift) | (d[i - 1] >>> csh); 1| d[0] <<= shift; | } | | /++ | Shifts right using at most `size_t.sizeof * 8 - 1` bits | +/ | void smallRightShiftInPlace()(uint shift) | { 1| assert(shift < W.sizeof * 8); 1| if (shift == 0) 0000000| return; 1| auto csh = W.sizeof * 8 - shift; 1| auto d = coefficients[]; 1| assert(d.length); 6| foreach (i; 0 .. d.length - 1) 1| d[i] = (d[i] >>> shift) | (d[i + 1] << csh); 1| d[$ - 1] >>>= shift; | } | | /++ | +/ | static BigUIntView fromHexString(C, bool allowUnderscores = false)(scope const(C)[] str) | @trusted pure | if (isSomeChar!C) | { 98| auto length = str.length / (W.sizeof * 2) + (str.length % (W.sizeof * 2) != 0); 98| auto data = new Unqual!W[length]; 98| auto view = BigUIntView!(Unqual!W)(data); 98| if (view.fromHexStringImpl!(C, allowUnderscores)(str)) 88| return BigUIntView(cast(W[])view.coefficients); | version(D_Exceptions) 10| throw hexStringException; | else | assert(0, hexStringErrorMsg); | } | | static if (isMutable!W) | /++ | +/ | bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow scope | if (isSomeChar!C) | { | pragma(inline, false); | import mir.utility: _expect; | static if (allowUnderscores) { 21| if (_expect(str.length == 0, false)) // can't tell how big the coeff array needs to be, rely on a runtime check 0000000| return false; | } else { 422| if (_expect(str.length == 0 || str.length > coefficients.length * W.sizeof * 2, false)) 0000000| return false; | } | 232| coefficients[0] = 0; 232| auto work = topLeastSignificantPart(1); 232| W current; 464| size_t i, j; 21| static if (allowUnderscores) bool recentUnderscore; | | do | { 7538| ubyte c; 7538| switch(str[$ - ++i]) | { 3651| case '0': c = 0x0; break; 1161| case '1': c = 0x1; break; 1683| case '2': c = 0x2; break; 939| case '3': c = 0x3; break; 786| case '4': c = 0x4; break; 891| case '5': c = 0x5; break; 597| case '6': c = 0x6; break; 1287| case '7': c = 0x7; break; 759| case '8': c = 0x8; break; 1086| case '9': c = 0x9; break; 14| case 'A': 1668| case 'a': c = 0xA; break; 22| case 'B': 1296| case 'b': c = 0xB; break; 15| case 'C': 945| case 'c': c = 0xC; break; 16| case 'D': 1344| case 'd': c = 0xD; break; 16| case 'E': 1164| case 'e': c = 0xE; break; 33| case 'F': 2958| case 'f': c = 0xF; break; | static if (allowUnderscores) | { 133| case '_': 140| if (recentUnderscore) return false; 126| recentUnderscore = true; 126| continue; | } 0000000| default: return false; | } 7405| ++j; 522| static if (allowUnderscores) recentUnderscore = false; | // how far do we need to shift to get to the top 4 bits | enum s = W.sizeof * 8 - 4; | // shift number to the top most 4 bits 7405| W cc = cast(W)(W(c) << s); | // shift unsigned right 4 bits 7405| current >>>= 4; | // add number to top most 4 bits of current var 7405| current |= cc; 7405| if (j % (W.sizeof * 2) == 0) // is this packed var full? | { 559| work.coefficients[$ - 1] = current; 559| current = 0; 559| if (_expect(work.coefficients.length < coefficients.length, true)) | { 444| work = topLeastSignificantPart(work.coefficients.length + 1); | } 115| else if (i < str.length) // if we've run out of coefficients before reaching the end of the string, error | { 0000000| return false; | } | } | } 7531| while(i < str.length); | | static if (allowUnderscores) | { | // check for a underscore at the beginning or the end 29| if (recentUnderscore || str[$ - 1] == '_') return false; | } | 222| if (current) | { 81| current >>>= 4 * (W.sizeof * 2 - j % (W.sizeof * 2)); 81| work.coefficients[$ - 1] = current; | } | 222| coefficients = coefficients[0 .. (j / (W.sizeof * 2) + (j % (W.sizeof * 2) != 0))]; | 222| return true; | } | | /++ | +/ | static BigUIntView fromBinaryString(C, bool allowUnderscores = false)(scope const(C)[] str) | @trusted pure | if (isSomeChar!C) | { 10| auto length = str.length / (W.sizeof * 8) + (str.length % (W.sizeof * 8) != 0); 10| auto data = new Unqual!W[length]; 10| auto view = BigUIntView!(Unqual!W)(data); 10| if (view.fromBinaryStringImpl!(C, allowUnderscores)(str)) 1| return BigUIntView(cast(W[])view.coefficients); | version(D_Exceptions) 9| throw binaryStringException; | else | assert(0, binaryStringErrorMsg); | } | | static if (isMutable!W) | /++ | +/ | bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow scope | if (isSomeChar!C) | { | pragma(inline, false); | import mir.utility: _expect; | static if (allowUnderscores) { 23| if (_expect(str.length == 0, false)) // can't tell how big the coeff array needs to be, rely on a runtime check 0000000| return false; | } else { 4| if (_expect(str.length == 0 || str.length > coefficients.length * W.sizeof * 8, false)) 0000000| return false; | } | 25| coefficients[0] = 0; 25| auto work = topLeastSignificantPart(1); 25| W current; 50| size_t i, j; 23| static if (allowUnderscores) bool recentUnderscore; | | do | { 2341| ubyte c; 2341| switch(str[$ - ++i]) | { 3033| case '0': c = 0x0; break; 3264| case '1': c = 0x1; break; | static if (allowUnderscores) | { 241| case '_': 247| if (recentUnderscore) return false; 235| recentUnderscore = true; 235| continue; | } 2| default: return false; | } 2099| ++j; 1716| static if (allowUnderscores) recentUnderscore = false; | // how far do we need to shift to get to the top bit? | enum s = W.sizeof * 8 - 1; | // shift number to the top most bit 2099| W cc = cast(W)(W(c) << s); | // shift unsigned right 1 bit 2099| current >>>= 1; | // add number to top most bit of current var 2099| current |= cc; 2099| if (j % (W.sizeof * 8) == 0) // is this packed var full? | { 111| work.coefficients[$ - 1] = current; 111| current = 0; 111| if (_expect(work.coefficients.length < coefficients.length, true)) | { 111| work = topLeastSignificantPart(work.coefficients.length + 1); | } 0000000| else if (i < str.length) // if we've run out of coefficients before reaching the end of the string, error | { 0000000| return false; | } | } | } 2334| while(i < str.length); | | static if (allowUnderscores) | { | // check for a underscore at the beginning or the end 32| if (recentUnderscore || str[$ - 1] == '_') return false; | } | 16| if (current) | { 2| current >>>= (W.sizeof * 8 - j % (W.sizeof * 8)); 2| work.coefficients[$ - 1] = current; | } | 16| coefficients = coefficients[0 .. (j / (W.sizeof * 8) + (j % (W.sizeof * 8) != 0))]; | 16| return true; | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Returns: false in case of overflow or incorrect string. | Precondition: non-empty coefficients | Note: doesn't support signs. | +/ | bool fromStringImpl(C)(scope const(C)[] str) | scope @trusted pure @nogc nothrow | if (isSomeChar!C) | { | import mir.utility: _expect; | 14| assert(coefficients.length); | 14| if (_expect(str.length == 0, false)) 0000000| return false; | 14| coefficients[0] = 0; 14| uint d = str[0] - '0'; 14| str = str[1 .. $]; | 14| W v; 14| W t = 1; | 14| if (d == 0) | { 1| if (str.length == 0) | { 1| coefficients = null; 1| return true; | } 0000000| return false; | } | else 13| if (d >= 10) 0000000| return false; | 13| size_t len = 1; 13| goto S; | | for(;;) | { | enum mp10 = W(10) ^^ MaxWordPow10!W; 1322| d = str[0] - '0'; 1322| str = str[1 .. $]; 1322| if (_expect(d > 10, false)) 0000000| break; 1322| v *= 10; | S: 1335| t *= 10; 1335| v += d; | 2603| if (_expect(t == mp10 || str.length == 0, false)) | { | L: 80| if (auto overflow = topLeastSignificantPart(len).opOpAssign!"*"(t, v)) | { 59| if (_expect(len < coefficients.length, true)) | { 59| coefficients[len++] = overflow; | } | else | { 0000000| return false; | } | } 80| v = 0; 80| t = 1; 80| if (str.length == 0) | { 13| this = topLeastSignificantPart(len); 13| return true; | } | } | } 0000000| return false; | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Performs `bool overflow = big +(-)= big` operatrion. | Params: | rhs = value to add with non-empty coefficients | overflow = (overflow) initial iteration overflow | Precondition: non-empty coefficients length of greater or equal to the `rhs` coefficients length. | Returns: | true in case of unsigned overflow | +/ | bool opOpAssign(string op)(scope BigUIntView!(const W) rhs, bool overflow = false) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { 2711| assert(this.coefficients.length > 0); 2711| assert(rhs.coefficients.length <= this.coefficients.length); 2711| auto ls = this.coefficients; 2711| auto rs = rhs.coefficients; | do | { 8206| bool overflowM, overflowG; 4103| ls[0] = ls[0].cop!op(rs[0], overflowM).cop!op(overflow, overflowG); 4103| overflow = overflowG | overflowM; 4103| ls = ls[1 .. $]; 4103| rs = rs[1 .. $]; | } 4103| while(rs.length); 2901| if (overflow && ls.length) 16| return topMostSignificantPart(ls.length).opOpAssign!op(W(overflow)); 2695| return overflow; | } | | static if (isMutable!W && W.sizeof >= 4) | /// ditto | bool opOpAssign(string op)(scope BigIntView!(const W) rhs, bool overflow = false) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { 4| return rhs.sign == false ? | opOpAssign!op(rhs.unsigned, overflow): | opOpAssign!(inverseSign!op)(rhs.unsigned, overflow); | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Performs `bool Overflow = big +(-)= scalar` operatrion. | Precondition: non-empty coefficients | Params: | rhs = value to add | Returns: | true in case of unsigned overflow | +/ | bool opOpAssign(string op, T)(const T rhs) | @safe pure nothrow @nogc scope | if ((op == "+" || op == "-") && is(T == W)) | { 639| assert(this.coefficients.length > 0); 639| auto ns = this.coefficients; 639| W additive = rhs; | do | { 660| bool overflow; 660| ns[0] = ns[0].cop!op(additive, overflow); 660| if (!overflow) 617| return overflow; 43| additive = overflow; 43| ns = ns[1 .. $]; | } 43| while (ns.length); 22| return true; | } | | static if (isMutable!W && W.sizeof >= 4) | /// ditto | bool opOpAssign(string op, T)(const T rhs) | @safe pure nothrow @nogc scope | if ((op == "+" || op == "-") && is(T == Signed!W)) | { 4| return rhs >= 0 ? | opOpAssign!op(cast(W)rhs): | opOpAssign!(inverseSign!op)(cast(W)(-rhs)); | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Performs `W overflow = (big += overflow) *= scalar` operatrion. | Precondition: non-empty coefficients | Params: | rhs = unsigned value to multiply by | overflow = initial overflow | Returns: | unsigned overflow value | +/ | W opOpAssign(string op : "*")(W rhs, W overflow = 0u) | @safe pure nothrow @nogc scope | { 7296| auto ns = this.coefficients; 34340| while (ns.length) | { | import mir.utility: extMul; 27044| auto ext = ns[0].extMul(rhs); 27044| bool overflowM; 27044| ns[0] = ext.low.cop!"+"(overflow, overflowM); 27044| overflow = ext.high + overflowM; 27044| ns = ns[1 .. $]; | } 7296| return overflow; | } | | static if (isMutable!W && W.sizeof == 4 || W.sizeof == 8) | /++ | Performs `uint remainder = (overflow$big) /= scalar` operatrion, where `$` denotes big-endian concatenation. | Precondition: non-empty coefficients, `overflow < rhs` | Params: | rhs = unsigned value to devide by | overflow = initial unsigned overflow | Returns: | unsigned remainder value (evaluated overflow) | +/ | uint opOpAssign(string op : "/")(uint rhs, uint overflow = 0) | @safe pure nothrow @nogc scope | { 76| assert(overflow < rhs); 76| assert(coefficients.length); | static if (W.sizeof == 4) | { 38| auto ns = this.mostSignificantFirst; 38| size_t i; | do | { 216| auto ext = (ulong(overflow) << 32) ^ ns[i]; 216| ns[i] = cast(uint)(ext / rhs); 216| overflow = ext % rhs; | } 216| while (++i < ns.length); 38| if (coefficients[$ - 1] == 0) 32| popMostSignificant; 38| return overflow; | } | else | { 38| auto work = opCast!(BigUIntView!uint); 38| if (work.coefficients[$ - 1] == 0) 16| work.popMostSignificant; 38| auto remainder = work.opOpAssign!op(rhs, overflow); 38| coefficients = coefficients[0 .. work.coefficients.length / 2 + work.coefficients.length % 2]; 38| return remainder; | } | } | | static if (isMutable!W && W.sizeof == size_t.sizeof) | /++ | Performs `W overflow = (big += overflow) *= scalar` operatrion. | Precondition: non-empty coefficients | Params: | rhs = unsigned fixed-length integer to multiply by | overflow = initial overflow | Returns: | unsigned fixed-length integer overflow value | +/ | UInt!size | opOpAssign(string op : "*", size_t size)(UInt!size rhs, UInt!size overflow = 0) | @safe pure nothrow @nogc scope | { 3| assert(coefficients.length); 3| auto ns = this.coefficients; | do | { 20| auto t = rhs; 20| auto overflowW = t.view *= ns[0]; 20| auto overflowM = t += overflow; 20| overflowW += overflowM; 20| ns[0] = cast(size_t) t; | static if (size > size_t.sizeof * 8) 20| overflow = t.toSize!(size - size_t.sizeof * 8, false).toSize!size; 20| BigUIntView!size_t(overflow.data).coefficients[$ - 1] = overflowW; 20| ns = ns[1 .. $]; | } 20| while (ns.length); 3| return overflow; | } | | /++ | Returns: the same intger view with inversed sign | +/ | BigIntView!W opUnary(string op : "-")() | { 8| return typeof(return)(this, true); | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | +/ | void bitwiseNotInPlace() scope | { 105| foreach (ref coefficient; this.coefficients) 25| coefficient = cast(W)~(0 + coefficient); | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Performs `number=-number` operatrion. | Precondition: non-empty coefficients | Returns: | true if 'number=-number=0' and false otherwise | +/ | bool twoComplementInPlace() scope | { 10| assert(coefficients.length); 10| bitwiseNotInPlace(); 10| return this.opOpAssign!"+"(W(1)); | } | | /++ | Returns: a slice of coefficients starting from the most significant. | +/ | auto mostSignificantFirst() | @safe pure nothrow @nogc @property | { | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: retro; 76| return coefficients.sliced.retro; | } | | /// | auto mostSignificantFirst() | const @safe pure nothrow @nogc @property | { | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: retro; 273| return coefficients.sliced.retro; | } | | /++ | Strips most significant zero coefficients. | +/ | BigUIntView normalized() return scope | { 513| auto number = this; 513| if (number.coefficients.length) do | { 558| if (number.coefficients[$ - 1]) 497| break; 61| number.coefficients = number.coefficients[0 .. $ - 1]; | } 61| while (number.coefficients.length); 513| return number; | } | | ///ditto | BigUIntView!(const W) normalized() const return scope | { 21| return lightConst.normalized; | } | | /++ | +/ | bool bt()(size_t position) scope | { | import mir.ndslice.topology: bitwise; 332| assert(position < coefficients.length * W.sizeof * 8); 332| return coefficients.bitwise[position]; | } | | /++ | +/ | size_t ctlz()() scope const @property | @safe pure nothrow @nogc | { | import mir.bitop: ctlz; 273| assert(coefficients.length); 273| auto d = mostSignificantFirst; 273| size_t ret; | do | { 311| if (auto c = d[0]) | { 272| ret += ctlz(c); 272| break; | } 39| ret += W.sizeof * 8; 39| d = d[1 .. $]; | } 39| while(d.length); 273| return ret; | } | | /++ | +/ | size_t cttz()() scope const @property | @safe pure nothrow @nogc 3| in (coefficients.length) | { | import mir.bitop: cttz; 3| auto d = coefficients[]; 3| size_t ret; | do | { 5| if (auto c = d[0]) | { 2| ret += cttz(c); 2| break; | } 3| ret += W.sizeof * 8; 3| d = d[1 .. $]; | } 3| while(d.length); 3| return ret; | } | | /// | BigIntView!W withSign()(bool sign) return scope | { | return typeof(return)(this, sign); | } | | /++ | Params: | value = (out) unsigned integer | Returns: true on success | +/ | bool get(U)(scope out U value) | @safe pure nothrow @nogc const | if (isUnsigned!U) | { | auto d = lightConst.mostSignificantFirst; | if (d.length == 0) | return false; | static if (U.sizeof > W.sizeof) | { | size_t i; | for(;;) | { | value |= d[0]; | d = d[1 .. $]; | if (d.length == 0) | return false; | i += cast(bool)value; | value <<= W.sizeof * 8; | import mir.utility: _expect; | if (_expect(i >= U.sizeof / W.sizeof, false)) | return true; | } | } | else | { | for(;;) | { | W f = d[0]; | d = d[1 .. $]; | if (d.length == 0) | { | value = cast(U)f; | static if (U.sizeof < W.sizeof) | { | if (value != f) | return true; | } | return false; | } | if (f) | return true; | } | } | } | | /++ | Returns: true if the integer and equals to `rhs`. | +/ | bool opEquals(ulong rhs) | @safe pure nothrow @nogc const scope | { 0000000| foreach (d; lightConst.coefficients) | { | static if (W.sizeof >= ulong.sizeof) | { 0000000| if (d != rhs) 0000000| return false; 0000000| rhs = 0; | } | else | { 0000000| if (d != (rhs & W.max)) 0000000| return false; 0000000| rhs >>>= W.sizeof * 8; | } | } 0000000| return rhs == 0; | } | | | static if (isMutable!W && W.sizeof >= 4) | /++ | Params: | str = string buffer, the tail paer | Precondition: mutable number with word size at least 4 bytes | Postconditoin: the number is destroyed | Returns: last N bytes used in the buffer | +/ | size_t toStringImpl(C)(scope C[] str) | @safe pure nothrow @nogc | if (isSomeChar!C && isMutable!C) | { 74| assert(str.length); 74| assert(str.length >= ceilLog10Exp2(coefficients.length * (W.sizeof * 8))); | 74| size_t i = str.length; 112| while(coefficients.length > 1) | { 38| uint rem = this /= 1_000_000_000; 1140| foreach (_; 0 .. 9) | { 342| str[--i] = cast(char)(rem % 10 + '0'); 342| rem /= 10; | } | } | 74| W rem = coefficients.length == 1 ? coefficients[0] : W(0); | do | { 554| str[--i] = cast(char)(rem % 10 + '0'); 554| rem /= 10; | } 554| while(rem); | 74| return str.length - i; | } | |} | |/// |version(mir_bignum_test_llv) |@safe pure @nogc |unittest |{ | import mir.bignum.integer; | 1| auto a = BigInt!2("123456789098765432123456789098765432100"); 1| char[ceilLog10Exp2(a.data.length * (size_t.sizeof * 8))] buffer; 1| auto len = a.view.unsigned.toStringImpl(buffer); 1| assert(buffer[$ - len .. $] == "123456789098765432123456789098765432100"); |} | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ 1| auto view = BigUIntView!size_t.fromHexString!(char, true)("abcd_efab_cdef"); 1| assert(cast(ulong)view == 0xabcd_efab_cdef); |} | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ 1| auto a = BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| a.smallLeftShiftInPlace(4); 1| assert(a == BigUIntView!size_t.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); 1| a.smallLeftShiftInPlace(0); 1| assert(a == BigUIntView!size_t.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); |} | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ 1| auto a = BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| a.smallRightShiftInPlace(4); 1| assert(a == BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029")); |} | | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ | // Check that invalid underscores in hex literals throw an error. | void expectThrow(const(char)[] input) { 10| bool caught = false; | try { 10| auto view = BigUIntView!size_t.fromHexString!(char, true)(input); | } catch (Exception e) { 10| caught = true; | } | 10| assert(caught); | } | 1| expectThrow("abcd_efab_cef_"); 1| expectThrow("abcd__efab__cef"); 1| expectThrow("_abcd_efab_cdef"); 1| expectThrow("_abcd_efab_cdef_"); 1| expectThrow("_abcd_efab_cdef__"); 1| expectThrow("__abcd_efab_cdef"); 1| expectThrow("__abcd_efab_cdef_"); 1| expectThrow("__abcd_efab_cdef__"); 1| expectThrow("__abcd__efab_cdef__"); 1| expectThrow("__abcd__efab__cdef__"); |} | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ 1| auto view = BigUIntView!size_t.fromBinaryString!(char, true)("1111_0000_0101"); 1| assert(cast(ulong)view == 0b1111_0000_0101); |} | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ | // Check that invalid underscores in hex literals throw an error. | void expectThrow(const(char)[] input) { 9| bool caught = false; | try { 9| auto view = BigUIntView!size_t.fromBinaryString!(char, true)(input); | } catch (Exception e) { 9| caught = true; | } | 9| assert(caught); | } | 1| expectThrow("abcd"); 1| expectThrow("0101__1011__0111"); 1| expectThrow("_0101_1011_0111"); 1| expectThrow("_0101_1011_0111_"); 1| expectThrow("_0101_1011_0111__"); 1| expectThrow("__0101_1011_0111_"); 1| expectThrow("__0101_1011_0111__"); 1| expectThrow("__0101__1011_0111__"); 1| expectThrow("__1011__0111__1011__"); |} | |/// |version(mir_bignum_test_llv) |unittest |{ 1| auto a = cast(double) BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a == 0xa.fbbfae3cd0bp+124); 1| assert(cast(double) BigUIntView!size_t.init == 0); 1| assert(cast(double) BigUIntView!size_t([0]) == 0); |} | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ | import mir.bignum.fp: Fp; | import mir.bignum.fixed: UInt; | 1| auto fp = cast(Fp!128) BigUIntView!ulong.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(fp.exponent == 0); 1| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ae3cd0aff2714a1de7022b0029d"); 1| assert(fp.exponent == -20); 1| assert(fp.coefficient == UInt!128.fromHexString("ae3cd0aff2714a1de7022b0029d00000")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("e7022b0029d"); 1| assert(fp.exponent == -84); 1| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("e7022b0029d"); 1| assert(fp.exponent == -84); 1| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("e7022b0029d"); 1| assert(fp.exponent == -84); 1| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ffffffffffffffffffffffffffffffff1000000000000000"); 1| assert(fp.exponent == 64); 1| assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ffffffffffffffffffffffffffffffff8000000000000000"); 1| assert(fp.exponent == 65); 1| assert(fp.coefficient == UInt!128.fromHexString("80000000000000000000000000000000")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("fffffffffffffffffffffffffffffffe8000000000000000"); 1| assert(fp.exponent == 64); 1| assert(fp.coefficient == UInt!128.fromHexString("fffffffffffffffffffffffffffffffe")); | 1| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("fffffffffffffffffffffffffffffffe8000000000000001"); 1| assert(fp.exponent == 64); 1| assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); |} | |/// |version(mir_bignum_test_llv) |@safe pure |unittest |{ 1| auto view = BigUIntView!ulong.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(cast(ulong) view == 0x14a1de7022b0029d); 1| assert(cast(uint) view == 0x22b0029d); 1| assert(cast(ubyte) view == 0x9d); |} |version(mir_bignum_test_llv) |@safe pure |unittest |{ 1| auto view = BigUIntView!ushort.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(cast(ulong) view == 0x14a1de7022b0029d); 1| assert(cast(uint) view == 0x22b0029d); 1| assert(cast(ubyte) view == 0x9d); |} | |version(mir_bignum_test_llv) |@safe pure |unittest |{ 1| auto view = BigUIntView!uint.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(cast(ulong) view == 0x14a1de7022b0029d); 1| assert(cast(uint) view == 0x22b0029d); 1| assert(cast(ubyte) view == 0x9d); |} | | |/// |version(mir_bignum_test_llv) |@safe pure nothrow |unittest |{ | import std.traits; | alias AliasSeq(T...) = T; | | foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) | { 4| T[3] lhsData = [1, T.max-1, 0]; 4| T[3] rhsData = [T.max, T.max, 0]; | 4| auto lhs = BigUIntView!T(lhsData).normalized; | | /// bool overflow = bigUInt op= scalar 4| assert(lhs.coefficients == [1, T.max-1]); 4| assert(lhs.mostSignificantFirst == [T.max-1, 1]); | static if (T.sizeof >= 4) | { 2| assert((lhs += T.max) == false); 2| assert(lhs.coefficients == [0, T.max]); 2| assert((lhs += T.max) == false); 2| assert((lhs += T.max) == true); // overflow bit 2| assert(lhs.coefficients == [T.max-1, 0]); 2| assert((lhs -= T(1)) == false); 2| assert(lhs.coefficients == [T.max-2, 0]); 2| assert((lhs -= T.max) == true); // underflow bit 2| assert(lhs.coefficients == [T.max-1, T.max]); 2| assert((lhs -= Signed!T(-4)) == true); // overflow bit 2| assert(lhs.coefficients == [2, 0]); 2| assert((lhs += Signed!T.max) == false); // overflow bit 2| assert(lhs.coefficients == [Signed!T.max + 2, 0]); | | /// bool overflow = bigUInt op= bigUInt/bigInt 2| lhs = BigUIntView!T(lhsData); 2| auto rhs = BigUIntView!T(rhsData).normalized; 2| assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); 2| assert(rhs.coefficients == [T.max, T.max]); 2| assert((lhs += rhs) == false); 2| assert(lhs.coefficients == [Signed!T.max + 1, 0, 1]); 2| assert((lhs -= rhs) == false); 2| assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); 2| assert((lhs += -rhs) == true); 2| assert(lhs.coefficients == [Signed!T.max + 3, 0, T.max]); 2| assert((lhs += -(-rhs)) == true); 2| assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); | | /// W overflow = bigUInt *= scalar 2| assert((lhs *= T.max) == 0); 2| assert((lhs += T(Signed!T.max + 2)) == false); 2| assert(lhs.coefficients == [0, Signed!T.max + 2, 0]); 2| lhs = lhs.normalized; 2| lhs.coefficients[1] = T.max / 2 + 3; 2| assert(lhs.coefficients == [0, T.max / 2 + 3]); 2| assert((lhs *= 8u) == 4); 2| assert(lhs.coefficients == [0, 16]); | } | } |} | |/++ |Arbitrary length signed integer view. |+/ |struct BigIntView(W) | if (is(Unqual!W == ubyte) || is(Unqual!W == ushort) || is(Unqual!W == uint) || is(Unqual!W == ulong)) |{ | import mir.bignum.fp: Fp; | | /++ | Self-assigned to unsigned integer view $(MREF BigUIntView). | | Sign is stored in the most significant bit. | | The number is encoded as pair of `unsigned` and `sign`. | +/ | BigUIntView!W unsigned; | | /++ | Sign bit | +/ | bool sign; | |@safe: | | /// | inout(W)[] coefficients() inout @property | { 0000000| return unsigned.coefficients; | } | | /// 1426| this(W[] coefficients, bool sign = false) | { 1426| this(BigUIntView!W(coefficients), sign); | } | | /// 1459| this(BigUIntView!W unsigned, bool sign = false) | { 1459| this.unsigned = unsigned; 1459| this.sign = sign; | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Returns: false in case of overflow or incorrect string. | Precondition: non-empty coefficients. | +/ | bool fromStringImpl(C)(scope const(C)[] str) | scope @trusted pure @nogc nothrow | if (isSomeChar!C) | { | import mir.utility: _expect; | 11| if (_expect(str.length == 0, false)) 0000000| return false; | 11| if (str[0] == '-') | { 5| sign = true; 5| str = str[1 .. $]; | } | else 6| if (_expect(str[0] == '+', false)) | { 0000000| str = str[1 .. $]; | } | 11| if (!unsigned.fromStringImpl(str)) 0000000| return false; 11| sign &= unsigned.coefficients.length != 0; 11| return true; | } | | /++ | +/ | static BigIntView fromHexString(C, bool allowUnderscores = false)(scope const(C)[] str) | @trusted pure | if (isSomeChar!C) | { 16| auto length = str.length / (W.sizeof * 2) + (str.length % (W.sizeof * 2) != 0); 16| auto ret = BigIntView!(Unqual!W)(new Unqual!W[length]); 16| if (ret.fromHexStringImpl!(C, allowUnderscores)(str)) 16| return cast(BigIntView) ret; | version(D_Exceptions) 0000000| throw hexStringException; | else | assert(0, hexStringErrorMsg); | } | | static if (isMutable!W) | /++ | +/ | bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow | if (isSomeChar!C) | { | pragma(inline, false); | import mir.utility: _expect; | 41| assert(unsigned.coefficients.length); | 41| if (_expect(str.length == 0, false)) 0000000| return false; | 41| sign = false; | 41| if (str[0] == '-') | { 16| sign = true; 16| str = str[1 .. $]; | } | else 25| if (_expect(str[0] == '+', false)) | { 0000000| str = str[1 .. $]; | } | 41| return unsigned.fromHexStringImpl!(C, allowUnderscores)(str); | } | | /++ | +/ | static BigIntView fromBinaryString(C, bool allowUnderscores = false)(scope const(C)[] str) | @trusted pure | if (isSomeChar!C) | { 14| auto length = str.length / (W.sizeof * 8) + (str.length % (W.sizeof * 8) != 0); 14| auto ret = BigIntView!(Unqual!W)(new Unqual!W[length]); 14| if (ret.fromBinaryStringImpl!(C, allowUnderscores)(str)) 14| return cast(BigIntView) ret; | version(D_Exceptions) 0000000| throw binaryStringException; | else | assert(0, binaryStringErrorMsg); | } | | static if (isMutable!W) | /++ | +/ | bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) | @safe pure @nogc nothrow | if (isSomeChar!C) | { | pragma(inline, false); | import mir.utility: _expect; | 15| assert(unsigned.coefficients.length); | 15| if (_expect(str.length == 0, false)) 0000000| return false; | 15| sign = false; | 15| if (str[0] == '-') | { 15| sign = true; 15| str = str[1 .. $]; | } | else 0000000| if (_expect(str[0] == '+', false)) | { 0000000| str = str[1 .. $]; | } | 15| return unsigned.fromBinaryStringImpl!(C, allowUnderscores)(str); | } | | /// | T opCast(T)() const scope | if (isFloatingPoint!T && isMutable!T) | { 7| auto ret = this.unsigned.opCast!T; 7| if (sign) 5| ret = -ret; 7| return ret; | } | | /// | T opCast(T, bool nonZero = false)() const scope | if (is(T == long) || is(T == int)) | { 50| auto ret = this.unsigned.opCast!(Unsigned!T, nonZero); 50| if (sign) 49| ret = -ret; 50| return ret; | } | | static if (W.sizeof == size_t.sizeof) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0021d"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_021d"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!ushort.fromHexString("-afbbfae3cd0aff2714a1de7022b0021d"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test_llv) | @safe pure | unittest | { 2| auto view = BigIntView!ushort.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_021d"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!ubyte.fromHexString("-afbbfae3cd0aff2714a1de7022b0021d"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!ubyte.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_021d"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!size_t.fromBinaryString!(char, true)("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001000011101"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!size_t.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_0001_1101"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!ushort.fromBinaryString!(char, true)("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001000011101"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!ushort.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_0001_1101"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigIntView!ubyte.fromBinaryString!(char, true)("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001000011101"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | static if (W.sizeof == size_t.sizeof) | version(mir_bignum_test_llv) | @safe pure | unittest | { 2| auto view = BigIntView!ubyte.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_0001_1101"); 2| assert(cast(long) view == -0x14a1de7022b0021d); 2| assert(cast(int) view == -0x22b0021d); | } | | /++ | +/ | T opCast(T : Fp!coefficientSize, uint coefficientSize)() const scope | { 2| auto ret = unsigned.opCast!(Fp!coefficientSize); 2| ret.sign = sign; 2| return ret; | } | | /// | BigIntView!V opCast(T : BigIntView!V, V)() return scope | if (V.sizeof <= W.sizeof) | { 1| return typeof(return)(this.unsigned.opCast!(BigUIntView!V), sign); | } | | /// | BigIntView!V opCast(T : BigIntView!V, V)() const return scope | if (V.sizeof <= W.sizeof) | { | return typeof(return)(this.unsigned.opCast!(BigUIntView!V), sign); | } | | /// | BigIntView!(const W) lightConst()() return scope | const @safe pure nothrow @nogc @property | { 18| return typeof(return)(unsigned.lightConst, sign); | } | | ///ditto | alias lightConst this; | | /++ | +/ | sizediff_t opCmp(BigIntView!(const W) rhs) | const @safe pure nothrow @nogc scope | { | import mir.algorithm.iteration: cmp; 21| if (auto s = rhs.sign - this.sign) | { 0000000| if (this.unsigned.coefficients.length && rhs.unsigned.coefficients.length) 0000000| return s; | } 21| auto d = this.unsigned.opCmp(rhs.unsigned); 21| return sign ? -d : d; | } | | /// | bool opEquals(BigIntView!(const W) rhs) | const @safe pure nothrow @nogc scope | { 40| return (this.sign == rhs.sign || unsigned.coefficients.length == 0) && this.unsigned == rhs.unsigned; | } | | /++ | Returns: true if the integer and equals to `rhs`. | +/ | bool opEquals(long rhs) | @safe pure nothrow @nogc const scope | { 2| if (rhs == 0 && unsigned.coefficients.length == 0) 1| return true; 0000000| bool sign = rhs < 0; 0000000| ulong urhs = sign ? -rhs : rhs; 0000000| return sign == this.sign && unsigned == urhs; | } | | /++ | +/ | BigIntView topMostSignificantPart(size_t length) | { 0000000| return BigIntView(unsigned.topMostSignificantPart(length), sign); | } | | /++ | +/ | BigIntView topLeastSignificantPart(size_t length) | { 0000000| return BigIntView(unsigned.topLeastSignificantPart(length), sign); | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Performs `bool overflow = big +(-)= big` operatrion. | Params: | rhs = value to add with non-empty coefficients | overflow = (overflow) initial iteration overflow | Precondition: non-empty coefficients length of greater or equal to the `rhs` coefficients length. | Returns: | true in case of unsigned overflow | +/ | bool opOpAssign(string op)(scope BigIntView!(const W) rhs, bool overflow = false) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 51| assert(rhs.coefficients.length > 0); | import mir.conv; 51| debug assert(this.coefficients.length >= rhs.coefficients.length, this.coefficients.length.to!string ~ " " ~ rhs.coefficients.length.to!string); | enum sum = op == "+"; | // pos += pos | // neg += neg | // neg -= pos | // pos -= neg 51| if ((sign == rhs.sign) == sum) 17| return unsigned.opOpAssign!"+"(rhs.unsigned, overflow); | // pos -= pos | // pos += neg | // neg += pos | // neg -= neg 34| if (unsigned.opOpAssign!"-"(rhs.unsigned, overflow)) | { 6| sign = !sign; 6| unsigned.twoComplementInPlace; | } 34| return false; | } | | static if (isMutable!W && W.sizeof >= 4) | /// ditto | bool opOpAssign(string op)(BigUIntView!(const W) rhs, bool overflow = false) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 4| return opOpAssign!op(rhs.signed, overflow); | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Performs `bool overflow = big +(-)= scalar` operatrion. | Precondition: non-empty coefficients | Params: | rhs = value to add | Returns: | true in case of unsigned overflow | +/ | bool opOpAssign(string op, T)(const T rhs) | @safe pure nothrow @nogc | if ((op == "+" || op == "-") && is(T == Signed!W)) | { 4| assert(this.coefficients.length > 0); | enum sum = op == "+"; | // pos += pos | // neg += neg | // neg -= pos | // pos -= neg 4| auto urhs = cast(W) (rhs < 0 ? -rhs : rhs); 4| if ((sign == (rhs < 0)) == sum) 2| return unsigned.opOpAssign!"+"(urhs); | // pos -= pos | // pos += neg | // neg += pos | // neg -= neg 2| if (unsigned.opOpAssign!"-"(urhs)) | { 2| sign = !sign; 2| unsigned.twoComplementInPlace; | } 2| return false; | } | | static if (isMutable!W && W.sizeof >= 4) | /// ditto | bool opOpAssign(string op, T)(const T rhs) | @safe pure nothrow @nogc | if ((op == "+" || op == "-") && is(T == W)) | { 10| assert(this.coefficients.length > 0); | enum sum = op == "+"; | // pos += pos | // neg -= pos 10| if ((sign == false) == sum) 6| return unsigned.opOpAssign!"+"(rhs); | // pos -= pos | // neg += pos 4| if (unsigned.opOpAssign!"-"(rhs)) | { 2| sign = !sign; 2| unsigned.twoComplementInPlace; | } 4| return false; | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | Performs `W overflow = (big += overflow) *= scalar` operatrion. | Precondition: non-empty coefficients | Params: | rhs = unsigned value to multiply by | overflow = initial overflow | Returns: | unsigned overflow value | +/ | W opOpAssign(string op : "*")(W rhs, W overflow = 0u) | @safe pure nothrow @nogc | { | return unsigned.opOpAssign!op(rhs, overflow); | } | | /++ | Returns: the same intger view with inversed sign | +/ | BigIntView opUnary(string op : "-")() | { 2| return BigIntView(unsigned, !sign); | } | | /++ | Returns: a slice of coefficients starting from the least significant. | +/ | auto coefficients() | @safe pure nothrow @nogc @property | { 807| return unsigned.coefficients; | } | | /++ | Returns: a slice of coefficients starting from the most significant. | +/ | auto mostSignificantFirst() | @safe pure nothrow @nogc @property | { 4| return unsigned.mostSignificantFirst; | } | | /++ | Strips zero most significant coefficients. | Strips most significant zero coefficients. | Sets sign to zero if no coefficients were left. | +/ | BigIntView normalized() return scope | { 282| auto number = this; 282| number.unsigned = number.unsigned.normalized; 282| number.sign = number.coefficients.length == 0 ? false : number.sign; 282| return number; | } | | ///ditto | BigIntView!(const W) normalized() const return scope | { 0000000| return lightConst.normalized; | } |} | |/// |version(mir_bignum_test) |@safe pure |unittest |{ | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp; | 1| auto fp = cast(Fp!128) BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(fp.sign); 1| assert(fp.exponent == 0); 1| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); |} | |/// |version(mir_bignum_test) |@safe pure |unittest |{ 1| auto a = cast(double) BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0029d"); 1| assert(a == -0xa.fbbfae3cd0bp+124); |} | |/// |version(mir_bignum_test) |@safe pure |unittest |{ 1| auto a = cast(double) BigIntView!size_t.fromBinaryString("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001010011101"); 1| assert(a == -0xa.fbbfae3cd0bp+124); |} | |/// |version(mir_bignum_test) |@safe pure |unittest |{ 1| auto a = cast(double) BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_029d"); 1| assert(a == -0xa.fbbfae3cd0bp+124); |} | |/// |version(mir_bignum_test) |@safe pure |unittest |{ 1| auto a = cast(double) BigIntView!size_t.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_1001_1101"); 1| assert(a == -0xa.fbbfae3cd0bp+124); |} | |version(mir_bignum_test_llv) |@safe pure |unittest |{ | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp; | 1| auto fp = cast(Fp!128) BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_029d"); 1| assert(fp.sign); 1| assert(fp.exponent == 0); 1| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); |} | |/// |version(mir_bignum_test_llv) |@safe pure nothrow |unittest |{ | import std.traits; | alias AliasSeq(T...) = T; | | foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) | { 4| T[3] lhsData = [1, T.max-1, 0]; 4| T[3] rhsData = [T.max, T.max, 0]; | 4| auto lhs = BigIntView!T(lhsData).normalized; | | /// bool overflow = bigUInt op= scalar 4| assert(lhs.coefficients == [1, T.max-1]); 4| assert(lhs.mostSignificantFirst == [T.max-1, 1]); | | static if (T.sizeof >= 4) | { | 2| assert((lhs += T.max) == false); 2| assert(lhs.coefficients == [0, T.max]); 2| assert((lhs += T.max) == false); 2| assert((lhs += T.max) == true); // overflow bit 2| assert(lhs.coefficients == [T.max-1, 0]); 2| assert((lhs -= T(1)) == false); 2| assert(lhs.coefficients == [T.max-2, 0]); 2| assert((lhs -= T.max) == false); 2| assert(lhs.coefficients == [2, 0]); 2| assert(lhs.sign); 2| assert((lhs -= Signed!T(-4)) == false); 2| assert(lhs.coefficients == [2, 0]); 2| assert(lhs.sign == false); 2| assert((lhs += Signed!T.max) == false); 2| assert(lhs.coefficients == [Signed!T.max + 2, 0]); | | /// bool overflow = bigUInt op= bigUInt/bigInt 2| lhs = BigIntView!T(lhsData); 2| auto rhs = BigUIntView!T(rhsData).normalized; 2| assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); 2| assert(rhs.coefficients == [T.max, T.max]); 2| assert((lhs += rhs) == false); 2| assert(lhs.coefficients == [Signed!T.max + 1, 0, 1]); 2| assert((lhs -= rhs) == false); 2| assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); 2| assert((lhs += -rhs) == false); 2| assert(lhs.sign); 2| assert(lhs.coefficients == [T.max - (Signed!T.max + 2), T.max, 0]); 2| assert(lhs.sign); 2| assert((lhs -= -rhs) == false); 2| assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); 2| assert(lhs.sign == false); | } | } |} | |/// |version(mir_bignum_test_llv) |unittest |{ | import mir.bignum.fixed: UInt; | import mir.bignum.low_level_view: BigUIntView; 1| auto bigView = BigUIntView!size_t.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf0b5e2e2c7771cd472ae5d7acdb159a56fbf74f851a058ae341f69d1eb750d7e3"); 1| auto fixed = UInt!256.fromHexString("55e5669576d31726f4a9b58a90159de5923adc6c762ebd3c4ba518d495229072"); 1| auto overflow = bigView *= fixed; 1| assert(overflow == UInt!256.fromHexString("1cbbe8c42dc21f936e4ce5b2f52ac404439857f174084012fcd1b71fdec2a398")); 1| assert(bigView == BigUIntView!size_t.fromHexString("c73fd2b26f2514c103c324943b6c90a05d2732118d5f0099c36a69a8051bb0573adc825b5c9295896c70280faa4c4d369df8e92f82bfffafe078b52ae695d316")); | |} | |/// |version(mir_bignum_test_llv) |unittest |{ | import mir.bignum.fixed: UInt; | import mir.bignum.low_level_view: BigUIntView; 1| auto bigView2 = BigUIntView!size_t.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf0b5e2e2c7771cd472ae5d7acdb159a56fbf74f851a058ae341f69d1eb750d7e3"); 1| auto bigView = BigUIntView!size_t.fromHexString!(char, true)("55a3_25ad_18b2_a771_20d8_70d9_87d5_2374_7379_0532_acab_45da_44bc_07c9_2c92_babf_0b5e_2e2c_7771_cd47_2ae5_d7ac_db15_9a56_fbf7_4f85_1a05_8ae3_41f6_9d1e_b750_d7e3"); 1| auto fixed = UInt!256.fromHexString!(true)("55e5_6695_76d3_1726_f4a9_b58a_9015_9de5_923a_dc6c_762e_bd3c_4ba5_18d4_9522_9072"); 1| auto overflow = bigView *= fixed; 1| assert(overflow == UInt!256.fromHexString("1cbbe8c42dc21f936e4ce5b2f52ac404439857f174084012fcd1b71fdec2a398")); 1| assert(bigView == BigUIntView!size_t.fromHexString("c73fd2b26f2514c103c324943b6c90a05d2732118d5f0099c36a69a8051bb0573adc825b5c9295896c70280faa4c4d369df8e92f82bfffafe078b52ae695d316")); |} | |/++ |+/ |struct DecimalView(W) | if (is(Unqual!W == size_t)) |{ | import mir.parse: DecimalExponentKey; | | /// | bool sign; | /// | long exponent; | /// | BigUIntView!W coefficient; | |@safe: | | static if (is(W == size_t)) | /++ | Returns: false in case of overflow or incorrect string. | Precondition: non-empty coefficients | +/ | bool fromStringImpl(C, | bool allowSpecialValues = true, | bool allowDotOnBounds = true, | bool allowDExponent = true, | bool allowStartingPlus = true, | bool allowUnderscores = true, | bool allowLeadingZeros = true, | bool allowExponent = true, | bool checkEmpty = true, | ) | (scope const(C)[] str, out DecimalExponentKey key, int exponentShift = 0) | scope @trusted pure @nogc nothrow | if (isSomeChar!C) 195| in (coefficient.length) | { | pragma(inline, false); | | import mir.utility: _expect; | 195| BigUIntView!W work; | | bool mullAdd(W rhs, W overflow) | { | import mir.stdio; | // debug dump(rhs, overflow, work); 695| if ((overflow = work.opOpAssign!"*"(rhs, overflow)) != 0) | { 185| if (_expect(work.coefficients.length < coefficient.coefficients.length, true)) | { 184| work = coefficient.topLeastSignificantPart(work.coefficients.length + 1); 184| work.coefficients[$ - 1] = overflow; | // debug dump(overflow, work); 184| return false; | } 1| return true; | } | // debug dump(overflow, work); 510| return false; | } | | import mir.bignum.internal.parse: decimalFromStringImpl; | alias impl = decimalFromStringImpl!(mullAdd, W); | alias specification = impl!(C, | allowSpecialValues, | allowDotOnBounds, | allowDExponent, | allowStartingPlus, | allowUnderscores, | allowLeadingZeros, | allowExponent, | checkEmpty, | ); 195| exponent += exponentShift; 195| auto ret = specification(str, key, exponent, sign); 195| switch (key) | { 9| case DecimalExponentKey.infinity: 9| exponent = exponent.max; 9| coefficient = coefficient.topLeastSignificantPart(0); 9| return ret; 7| case DecimalExponentKey.nan: 7| exponent = exponent.max; 7| coefficient = coefficient.topLeastSignificantPart(1); 7| coefficient.coefficients[0] = 1; 7| return ret; 179| default: 179| coefficient = work; 179| return ret; | } | } | | /// | DecimalView!(const W) lightConst()() return scope | const @safe pure nothrow @nogc @property | { | return typeof(return)(sign, exponent, coefficient.lightConst); | } | ///ditto | alias lightConst this; | | /++ | +/ | BigIntView!W signedCoefficient() | { 0000000| return typeof(return)(coefficient, sign); | } | | static if (W.sizeof >= size_t.sizeof) | /++ | Mir parsing supports up-to quadruple precision. The conversion error is 0 ULP for normal numbers. | Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP. | +/ | T opCast(T, bool wordNormalized = false)() const scope | if (isFloatingPoint!T && isMutable!T) | { | import mir.bignum.internal.dec2float; | 297| auto coeff = coefficient.lightConst; | | static if (!wordNormalized) 138| coeff = coeff.normalized; 297| auto ret = exponent != exponent.max ? | coeff.coefficients.decimalTo!T(exponent) : | coeff.length ? T.nan : T.infinity; 297| if (sign) 30| ret = -ret; 297| return ret; | } |} | |version(none) |/// |unittest |{ | { | auto view = DecimalView!ulong(false, -8, BigUIntView!ulong.fromHexString("BEBC2000000011E1A3")); | auto coeff = (cast(BigUIntView!uint)view.coefficient).lightConst; | assert (algoM!double(0.0, coeff, cast(int)view.exponent) == 3.518437208883201171875E+013); | } | | // TBD: triggers underflow | // { | // auto view = DecimalView!ulong(false, 0, BigUIntView!ulong.fromHexString("88BF4748507FB9900ADB624CCFF8D78897DC900FB0460327D4D86D327219")); | // auto coeff = (cast(BigUIntView!uint)view.coefficient).lightConst; | // debug { | // import std.stdio; | // writefln("%s", algoM!float(0.0, coeff, cast(int)view.exponent)); | // writefln("%s", algoM!double(0.0, coeff, cast(int)view.exponent)); | // } | // assert (algoM!float(0.0, coeff, cast(int)view.exponent) == float.infinity); | // assert (algoM!double(0.0, coeff, cast(int)view.exponent) == 0x1.117e8e90a0ff7p+239); | // } | | { | auto view = DecimalView!ulong(false, -324, BigUIntView!ulong.fromHexString("4F0CEDC95A718E")); | auto coeff = (cast(BigUIntView!uint)view.coefficient).lightConst; | assert (algoM!float(0.0, coeff, cast(int)view.exponent) == 0); | assert (algoM!double(0.0, coeff, cast(int)view.exponent) == 2.2250738585072014e-308); | } |} | |/// |version(mir_bignum_test_llv) |unittest |{ | import mir.test; | 1| auto view = DecimalView!size_t(false, -8, BigUIntView!size_t.fromHexString("BEBC2000000011E1A3")); | 1| should(cast(float)view) == 3.518437208883201171875E+013f; 1| should(cast(double)view) == 3.518437208883201171875E+013; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 3.518437208883201171875E+013L; | 1| view = DecimalView!size_t(true, -169, BigUIntView!size_t.fromHexString("5A174AEDA65CC")); 1| should(cast(float)view) == -0; 1| should(cast(double)view) == -0x1.1p-511; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == -0x8.80000000000019fp-514L; | 1| view = DecimalView!size_t(true, 293, BigUIntView!size_t.fromHexString("36496F6C4ED38")); 1| should(cast(float)view) == -float.infinity; 1| should(cast(double)view) == -9.55024478104888e+307; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == -9.55024478104888e+307L; | 1| view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 1; 1| should(cast(double)view) == 1; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 1L; | 1| view = DecimalView!size_t(false, -5, BigUIntView!size_t.fromHexString("3")); 1| should(cast(float)view) == 3e-5f; 1| should(cast(double)view) == 3e-5; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 3e-5L; | 1| view = DecimalView!size_t(false, -1, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0.1f; 1| should(cast(double)view) == 0.1; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0.1L; | 1| view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("3039")); 1| should(cast(float)view) == 12345.0f; 1| should(cast(double)view) == 12345.0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 12345.0L; | 1| view = DecimalView!size_t(false, -7, BigUIntView!size_t.fromHexString("98967F")); 1| should(cast(float)view) == 0.9999999f; 1| should(cast(double)view) == 0.9999999; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0.9999999L; | 1| view = DecimalView!size_t(false, -324, BigUIntView!size_t.fromHexString("4F0CEDC95A718E")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 2.2250738585072014e-308; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 2.2250738585072014e-308L; | 1| view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("1FFFFFFFFFFFFFFFD")); 1| should(cast(float)view) == 36893488147419103229f; 1| should(cast(double)view) == 36893488147419103229.0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x1FFFFFFFFFFFFFFFDp0L; | 1| view = DecimalView!size_t(false, -33, BigUIntView!size_t.fromHexString("65")); 1| should(cast(float)view) == 101e-33f; 1| should(cast(double)view) == 101e-33; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 101e-33L; | 1| view = DecimalView!size_t(false, 23, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 1e23f; 1| should(cast(double)view) == 1e23; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 1e23L; | 1| view = DecimalView!size_t(false, 23, BigUIntView!size_t.fromHexString("81B")); 1| should(cast(float)view) == 2075e23f; 1| should(cast(double)view) == 0xaba3d58a1f1a98p+32; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xaba3d58a1f1a9cp+32L; | 1| view = DecimalView!size_t(false, -23, BigUIntView!size_t.fromHexString("2209")); 1| should(cast(float)view) == 8713e-23f; 1| should(cast(double)view) == 0x1.9b75b4e7de2b9p-64; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xc.dbada73ef15c401p-67L; | 1| view = DecimalView!size_t(false, 300, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == 0x1.7e43c8800759cp+996; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xb.f21e44003acdd2dp+993L; | 1| view = DecimalView!size_t(false, 245, BigUIntView!size_t.fromHexString("B3A73CEB227")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == 0x1.48e3735333cb6p+857; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xa.471b9a999e5b01ep+854L; | 1| view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("88BF4748507FB9900ADB624CCFF8D78897DC900FB0460327D4D86D327219")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == 0x1.117e8e90a0ff7p+239; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.8bf4748507fb99p+236L; | 1| view = DecimalView!size_t(false, -324, BigUIntView!size_t.fromHexString("5")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0x0.0000000000001p-1022; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.18995ce7aa0e1b2p-1077L; | 1| view = DecimalView!size_t(false, -324, BigUIntView!size_t.fromHexString("5B")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0x0.0000000000012p-1022; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x9.3594d9adeb09a55p-1073L; | 1| view = DecimalView!size_t(false, -322, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0x0.0000000000014p-1022; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xa.1ebfb4219491a1fp-1073L; | 1| view = DecimalView!size_t(false, -320, BigUIntView!size_t.fromHexString("CA1CCB")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0x0.000063df832d9p-1022; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xc.7bf065b215888c7p-1043L; | 1| view = DecimalView!size_t(false, -319, BigUIntView!size_t.fromHexString("33CE7943FB")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0x1.000000000162p-1022; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.000000000b103b6p-1025L; | 1| view = DecimalView!size_t(false, -309, BigUIntView!size_t.fromHexString("15")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0x0.f19c2629ccf53p-1022; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xf.19c2629ccf52fc4p-1026L; | 1| view = DecimalView!size_t(false, -340, BigUIntView!size_t.fromHexString("AF87023B9BF0EE")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0x0.0000000000001p-1022; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xf.fffffffffffff64p-1078L; | 1| view = DecimalView!size_t(false, 400, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == double.infinity; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xd.a763fc8cb9ff9e6p+1325L; | 1| view = DecimalView!size_t(false, 309, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == double.infinity; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xb.201833b35d63f73p+1023L; | 1| view = DecimalView!size_t(false, 308, BigUIntView!size_t.fromHexString("2")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == double.infinity; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.e679c2f5e44ff8fp+1021L; | 1| view = DecimalView!size_t(false, 308, BigUIntView!size_t.fromHexString("2")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == double.infinity; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.e679c2f5e44ff8fp+1021L; | 1| view = DecimalView!size_t(false, 295, BigUIntView!size_t.fromHexString("1059949B7090")); 1| should(cast(float)view) == float.infinity; 1| should(cast(double)view) == double.infinity; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.00000000006955ap+1021L; | 1| view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("0")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0L; | 1| view = view; 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0L; | 1| view = DecimalView!size_t(false, -325, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xa.5ced43b7e3e9188p-1083L; | 1| view = DecimalView!size_t(false, -326, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.4a57695fe98746dp-1086L; | 1| view = DecimalView!size_t(false, -500, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.33ada2003db9a8p-1664L; | 1| view = DecimalView!size_t(false, -1000, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.68a9188a89e1467p-3325L; | 1| view = DecimalView!size_t(false, -4999, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0L; | 1| view = DecimalView!size_t(false, -10000, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0L; | 1| view = DecimalView!size_t(false, -4969, BigUIntView!size_t.fromHexString("329659A941466C6B")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == real.min_normal * real.epsilon; | 1| view = DecimalView!size_t(false, -15, BigUIntView!size_t.fromHexString("525DB0200FFAB")); 1| should(cast(float)view) == 1.448997445238699f; 1| should(cast(double)view) == 0x1.72f17f1f49aadp+0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xb.978bf8fa4d56cp-3L; | 1| view = DecimalView!size_t(false, -15, BigUIntView!size_t.fromHexString("525DB0200FFAB")); 1| should(cast(float)view) == 1.448997445238699f; 1| should(cast(double)view) == 0x1.72f17f1f49aadp+0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xb.978bf8fa4d56cp-3L; | 1| view = DecimalView!size_t(false, -325, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xa.5ced43b7e3e9188p-1083L; | 1| view = DecimalView!size_t(false, -326, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0; 1| should(cast(double)view) == 0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8.4a57695fe98746dp-1086L; | 1| view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 1; 1| should(cast(double)view) == 0x1p+0; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0x8p-3L; | 1| view = DecimalView!size_t(false, -5, BigUIntView!size_t.fromHexString("3")); 1| should(cast(float)view) == 3e-5f; 1| should(cast(double)view) == 0x1.f75104d551d69p-16; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xf.ba8826aa8eb4635p-19L; | 1| view = DecimalView!size_t(false, -1, BigUIntView!size_t.fromHexString("1")); 1| should(cast(float)view) == 0.1f; 1| should(cast(double)view) == 0x1.999999999999ap-4; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xc.ccccccccccccccdp-7L; | 1| view = DecimalView!size_t(false, -7, BigUIntView!size_t.fromHexString("98967F")); 1| should(cast(float)view) == 0.9999999f; 1| should(cast(double)view) == 0x1.fffffca501acbp-1; | static if (real.mant_dig >= 64) 1| should(cast(real)view) == 0xf.ffffe5280d65435p-4L; |} | |/++ |+/ |struct BinaryView(W) |{ | /// | bool sign; | /// | long exponent; | /// | BigUIntView!W coefficient; | |@safe: | | /// | DecimalView!(const W) lightConst()() return scope | const @safe pure nothrow @nogc @property | { | return typeof(return)(sign, exponent, coefficient.lightConst); | } | ///ditto | alias lightConst this; | | /++ | +/ | BigIntView!W signedCoefficient() | { | return typeof(return)(sign, coefficients); | } |} source/mir/bignum/low_level_view.d is 94% covered <<<<<< EOF # path=source-mir-combinatorics-package.lst |/** |This module contains various combinatorics algorithms. | |Authors: Sebastian Wilzbach, Ilia Ki | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |*/ |module mir.combinatorics; | |import mir.primitives: hasLength; |import mir.qualifier; |import std.traits; | |/// |version(mir_test) unittest |{ | import mir.ndslice.fuse; | import std.string: representation; | 1| assert(['a', 'b'].representation.permutations.fuse == [['a', 'b'], ['b', 'a']]); 1| assert(['a', 'b'].representation.cartesianPower(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'a'], ['b', 'b']]); 1| assert(['a', 'b'].representation.combinations(2).fuse == [['a', 'b']]); 1| assert(['a', 'b'].representation.combinationsRepeat(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'b']]); | 1| assert(permutations!ushort(2).fuse == [[0, 1], [1, 0]]); 1| assert(cartesianPower!ushort(2, 2).fuse == [[0, 0], [0, 1], [1, 0], [1, 1]]); 1| assert(combinations!ushort(2, 2).fuse == [[0, 1]]); 1| assert(combinationsRepeat!ushort(2, 2).fuse == [[0, 0], [0, 1], [1, 1]]); | 1| assert([3, 1].permutations!ubyte.fuse == [[3, 1], [1, 3]]); 1| assert([3, 1].cartesianPower!ubyte(2).fuse == [[3, 3], [3, 1], [1, 3], [1, 1]]); 1| assert([3, 1].combinations!ubyte(2).fuse == [[3, 1]]); 1| assert([3, 1].combinationsRepeat!ubyte(2).fuse == [[3, 3], [3, 1], [1, 1]]); |} | |/** |Checks whether we can do basic arithmetic operations, comparisons, modulo and |assign values to the type. |*/ |private template isArithmetic(R) |{ | enum bool isArithmetic = is(typeof( | (inout int = 0) | { | R r = 1; | R test = (r * r / r + r - r) % r; | if (r < r && r > r) {} | })); |} | |/** |Checks whether we can do basic arithmetic operations, comparison and modulo |between two types. R needs to support item assignment of S (it includes S). |Both R and S need to be arithmetic types themselves. |*/ |private template isArithmetic(R, S) |{ | enum bool isArithmetic = is(typeof( | (inout int = 0) | { | if (isArithmetic!R && isArithmetic!S) {} | S s = 1; | R r = 1; | R test = r * s + r * s; | R test2 = r / s + r / s; | R test3 = r - s + r - s; | R test4 = r % s + r % s; | if (r < s && s > r) {} | if (s < r && r > s) {} | })); |} | |/** |Computes the $(WEB en.wikipedia.org/wiki/Binomial_coefficient, binomial coefficient) |of n and k. |It is also known as "n choose k" or more formally as `_n!/_k!(_n-_k)`. |If a fixed-length integer type is used and an overflow happens, `0` is returned. | |Uses the generalized binomial coefficient for negative integers and floating |point number | |Params: | n = arbitrary arithmetic type | k = arbitrary arithmetic type | |Returns: | Binomial coefficient |*/ |R binomial(R = ulong, T)(const T n, const T k) | if (isArithmetic!(R, T) && | ((is(typeof(T.min < 0)) && is(typeof(T.init & 1))) || !is(typeof(T.min < 0))) ) |{ 103| R result = 1; | | enum hasMinProperty = is(typeof(T.min < 0)); | 103| T n2 = n; 103| T k2 = k; | | // only add negative support if possible | static if ((hasMinProperty && T.min < 0) || !hasMinProperty) | { 24| if (n2 < 0) | { 3| if (k2 >= 0) | { 2| return (k2 & 1 ? -1 : 1) * binomial!(R, T)(-n2 + k2 - 1, k2); | } 1| else if (k2 <= n2) | { 1| return ((n2 - k2) & 1 ? -1 : 1) * binomial!(R, T)(-k2 - 1, n2 - k2); | } | } 21| if (k2 < 0) | { 2| result = 0; 2| return result; | } | } | 98| if (k2 > n2) | { 9| result = 0; 9| return result; | } 89| if (k2 > n2 - k2) | { 38| k2 = n2 - k2; | } | // make a copy of n (could be a custom type) 721| for (T i = 1, m = n2; i <= k2; i++, m--) | { | // check whether an overflow can happen | // hasMember!(Result, "max") doesn't work with dmd2.068 and ldc 0.17 | static if (is(typeof(0 > R.max))) | { 205| if (result / i > R.max / m) return 0; 203| result = result / i * m + result % i * m / i; | } | else | { 24| result = result * m / i; | } | } 88| return result; |} | |/// |pure version(mir_test) unittest |{ 1| assert(binomial(5, 2) == 10); 1| assert(binomial(6, 4) == 15); 1| assert(binomial(3, 1) == 3); | | import std.bigint: BigInt; 1| assert(binomial!BigInt(1000, 10) == BigInt("263409560461970212832400")); |} | |pure nothrow @safe @nogc version(mir_test) unittest |{ 1| assert(binomial(5, 1) == 5); 1| assert(binomial(5, 0) == 1); 1| assert(binomial(1, 2) == 0); 1| assert(binomial(1, 0) == 1); 1| assert(binomial(1, 1) == 1); 1| assert(binomial(2, 1) == 2); 1| assert(binomial(2, 1) == 2); | | // negative 1| assert(binomial!long(-5, 3) == -35); 1| assert(binomial!long(5, -3) == 0); |} | |version(mir_test) unittest |{ | import std.bigint; | | // test larger numbers 1| assert(binomial(100, 10) == 17_310_309_456_440); 1| assert(binomial(999, 5) == 82_09_039_793_949); 1| assert(binomial(300, 10) == 1_398_320_233_241_701_770LU); 1| assert(binomial(300LU, 10LU) == 1_398_320_233_241_701_770LU); | | // test overflow 1| assert(binomial(500, 10) == 0); | | // all parameters as custom types 2| BigInt n = 1010, k = 9; 1| assert(binomial!BigInt(n, k) == BigInt("2908077120956865974260")); | | // negative 1| assert(binomial!BigInt(-5, 3) == -35); 1| assert(binomial!BigInt(5, -3) == 0); 1| assert(binomial!BigInt(-5, -7) == 15); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ 1| const size_t n = 5; 1| const size_t k = 2; 1| assert(binomial(n, k) == 10); |} | |/** |Creates a projection of a generalized `Collection` range for the numeric case |case starting from `0` onto a custom `range` of any type. | |Params: | collection = range to be projected from | range = random access range to be projected to | |Returns: | Range with a projection to range for every element of collection | |See_Also: | $(LREF permutations), $(LREF cartesianPower), $(LREF combinations), | $(LREF combinationsRepeat) |*/ |IndexedRoR!(Collection, Range) indexedRoR(Collection, Range)(Collection collection, Range range) | if (__traits(compiles, Range.init[size_t.init])) |{ 72| return IndexedRoR!(Collection, Range)(collection, range); |} | |/// ditto |struct IndexedRoR(Collection, Range) | if (__traits(compiles, Range.init[size_t.init])) |{ | private Collection c; | private Range r; | | /// | alias DeepElement = ForeachType!Range; | | /// 77| this()(Collection collection, Range range) | { 77| this.c = collection; 77| this.r = range; | } | | /// | auto lightScope()() return scope | { | return IndexedRoR!(LightScopeOf!Collection, LightScopeOf!Range)(.lightScope(c), .lightScope(r)); | } | | /// | auto lightScope()() return scope const | { | return IndexedRoR!(LightConstOf!(LightScopeOf!Collection), LightConstOf!(LightScopeOf!Range))(.lightScope(c), .lightScope(r)); | } | | /// | auto lightConst()() return scope const | { | return IndexedRoR!(LightConstOf!Collection, LightConstOf!Range)(.lightConst(c), .lightConst(r)); | } | | /// Input range primitives | auto front()() @property | { | import mir.ndslice.slice: isSlice, sliced; | import mir.ndslice.topology: indexed; | import std.traits: ForeachType; | static if (isSlice!(ForeachType!Collection)) 390| return r.indexed(c.front); | else | return r.indexed(c.front.sliced); | } | | /// ditto | void popFront() scope | { 381| c.popFront; | } | | /// ditto | bool empty()() @property scope const | { 50| return c.empty; | } | | static if (hasLength!Collection) | { | /// ditto | @property size_t length()() scope const | { 13| return c.length; | } | | /// | @property size_t[2] shape()() scope const | { 109| return c.shape; | } | } | | static if (__traits(hasMember, Collection, "save")) | { | /// Forward range primitive. Calls `collection.save`. | typeof(this) save()() @property | { 5| return IndexedRoR!(Collection, Range)(c.save, r); | } | } |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.fuse; | 1| auto perms = 2.permutations; 1| assert(perms.save.fuse == [[0, 1], [1, 0]]); | 1| auto projection = perms.indexedRoR([1, 2]); 1| assert(projection.fuse == [[1, 2], [2, 1]]); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.fuse; | import std.string: representation; | // import mir.ndslice.topology: only; | 1| auto projectionD = 2.permutations.indexedRoR("ab"d.representation); 1| assert(projectionD.fuse == [['a', 'b'], ['b', 'a']]); | | // auto projectionC = 2.permutations.indexedRoR(only('a', 'b')); | // assert(projectionC.fuse == [['a', 'b'], ['b', 'a']]); |} | |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.fuse; | import std.range: dropOne; | 1| auto perms = 2.permutations; 1| auto projection = perms.indexedRoR([1, 2]); 1| assert(projection.length == 2); | | // can save 1| assert(projection.save.dropOne.front == [2, 1]); 1| assert(projection.front == [1, 2]); |} | |@safe nothrow @nogc version(mir_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.slice: sliced; | import mir.ndslice.fuse; | static perms = 2.permutations; | static immutable projectionArray = [1, 2]; 1| auto projection = perms.indexedRoR(projectionArray); | | static immutable result = [1, 2, | 2, 1]; 1| assert(result.sliced(2, 2).all!"a == b"(projection)); |} | |/** |Lazily computes all _permutations of `r` using $(WEB |en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm). | |While generating a new item is in `O(k)` (amortized `O(1)`), |the number of permutations is `|n|!`. | |Params: | n = number of elements (`|r|`) | r = random access field. A field may not have iteration primitivies. | alloc = custom Allocator | |Returns: | Forward range, which yields the permutations | |See_Also: | $(LREF Permutations) |*/ |Permutations!T permutations(T = uint)(size_t n) @safe pure nothrow | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 15| assert(n, "must have at least one item"); 14| return Permutations!T(new T[n-1], new T[n]); |} | |/// ditto |IndexedRoR!(Permutations!T, Range) permutations(T = uint, Range)(Range r) @safe pure nothrow | if (__traits(compiles, Range.init[size_t.init])) |{ 4| return permutations!T(r.length).indexedRoR(r); |} | |/// ditto |Permutations!T makePermutations(T = uint, Allocator)(auto ref Allocator alloc, size_t n) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 2| assert(n, "must have at least one item"); | import std.experimental.allocator: makeArray; 1| auto state = alloc.makeArray!T(n - 1); 1| auto indices = alloc.makeArray!T(n); 1| return Permutations!T(state, indices); |} | |/** |Lazy Forward range of permutations using $(WEB |en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm). | |It always generates the permutations from 0 to `n - 1`, |use $(LREF indexedRoR) to map it to your range. | |Generating a new item is in `O(k)` (amortized `O(1)`), |the total number of elements is `n^k`. | |See_Also: | $(LREF permutations), $(LREF makePermutations) |*/ |struct Permutations(T) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ | import mir.ndslice.slice: sliced, Slice; | | private T[] indices, state; | private bool _empty; | private size_t _max_states = 1, _pos; | | /// | alias DeepElement = const T; | | /** | state should have the length of `n - 1`, | whereas the length of indices should be `n` | */ 15| this()(T[] state, T[] indices) @safe pure nothrow @nogc | { 15| assert(state.length + 1 == indices.length); | // iota 213| foreach (i, ref index; indices) 42| index = cast(T)i; 15| state[] = 0; | 15| this.indices = indices; 15| this.state = state; | 15| _empty = indices.length == 0; | | // factorial 171| foreach (i; 1..indices.length + 1) 42| _max_states *= i; | } | | /// Input range primitives | @property Slice!(const(T)*) front()() @safe pure nothrow @nogc | { | import mir.ndslice.slice: sliced; 33| return indices.sliced; | } | | /// ditto | void popFront()() scope @safe pure nothrow @nogc | { | import std.algorithm.mutation : swapAt; | 30| assert(!empty); 30| _pos++; | 46| for (T h = 0;;h++) | { 46| if (h + 2 > indices.length) | { 10| _empty = true; 10| break; | } | 36| indices.swapAt((h & 1) ? 0 : state[h], h + 1); | 36| if (state[h] == h + 1) | { 16| state[h] = 0; 16| continue; | } 20| state[h]++; 20| break; | } | } | | /// ditto | @property bool empty()() @safe pure nothrow @nogc scope const | { 38| return _empty; | } | | /// ditto | @property size_t length()() @safe pure nothrow @nogc scope const | { 25| return _max_states - _pos; | } | | /// | @property size_t[2] shape()() scope const | { 19| return [length, indices.length]; | } | | /// Forward range primitive. Allocates using GC. | @property Permutations save()() @safe pure nothrow | { 3| typeof(this) c = this; 3| c.indices = indices.dup; 3| c.state = state.dup; 3| return c; | } |} | |/// |pure @safe nothrow version(mir_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology : iota; | 1| auto expectedRes = [[0, 1, 2], | [1, 0, 2], | [2, 0, 1], | [0, 2, 1], | [1, 2, 0], | [2, 1, 0]]; | 1| auto r = iota(3); 1| auto rp = permutations(r.length).indexedRoR(r); 1| assert(rp.fuse == expectedRes); | | // direct style 1| auto rp2 = iota(3).permutations; 1| assert(rp2.fuse == expectedRes); |} | |/// |@nogc version(mir_test) unittest |{ | import mir.algorithm.iteration: equal; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology : iota; | | import std.experimental.allocator.mallocator; | | static immutable expected2 = [0, 1, 1, 0]; 1| auto r = iota(2); 1| auto rp = makePermutations(Mallocator.instance, r.length); 1| assert(expected2.sliced(2, 2).equal(rp.indexedRoR(r))); 1| dispose(Mallocator.instance, rp); |} | |pure @safe nothrow version(mir_test) unittest |{ | // is copyable? | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | import std.range: dropOne; 1| auto a = iota(2).permutations; 1| assert(a.front == [0, 1]); 1| assert(a.save.dropOne.front == [1, 0]); 1| assert(a.front == [0, 1]); | | // length 1| assert(1.permutations.length == 1); 1| assert(2.permutations.length == 2); 1| assert(3.permutations.length == 6); 1| assert(4.permutations.length == 24); 1| assert(10.permutations.length == 3_628_800); |} | |version (assert) |version(mir_test) unittest |{ | // check invalid | import std.exception: assertThrown; | import core.exception: AssertError; | import std.experimental.allocator.mallocator: Mallocator; | 2| assertThrown!AssertError(0.permutations); 2| assertThrown!AssertError(Mallocator.instance.makePermutations(0)); |} | |/** |Disposes a Permutations object. It destroys and then deallocates the |Permutations object pointed to by a pointer. |It is assumed the respective entities had been allocated with the same allocator. | |Params: | alloc = Custom allocator | perm = Permutations object | |See_Also: | $(LREF makePermutations) |*/ |void dispose(T, Allocator)(auto ref Allocator alloc, auto ref Permutations!T perm) |{ | import std.experimental.allocator: dispose; 1| dispose(alloc, perm.state); 1| dispose(alloc, perm.indices); |} | |/** |Lazily computes the Cartesian power of `r` with itself |for a number of repetitions `D repeat`. |If the input is sorted, the product is in lexicographic order. | |While generating a new item is in `O(k)` (amortized `O(1)`), |the total number of elements is `n^k`. | |Params: | n = number of elements (`|r|`) | r = random access field. A field may not have iteration primitivies. | repeat = number of repetitions | alloc = custom Allocator | |Returns: | Forward range, which yields the product items | |See_Also: | $(LREF CartesianPower) |*/ |CartesianPower!T cartesianPower(T = uint)(size_t n, size_t repeat = 1) @safe pure nothrow | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 26| assert(repeat >= 1, "Invalid number of repetitions"); 25| return CartesianPower!T(n, new T[repeat]); |} | |/// ditto |IndexedRoR!(CartesianPower!T, Range) cartesianPower(T = uint, Range)(Range r, size_t repeat = 1) |if (isUnsigned!T && __traits(compiles, Range.init[size_t.init])) |{ 13| assert(repeat >= 1, "Invalid number of repetitions"); 13| return cartesianPower!T(r.length, repeat).indexedRoR(r); |} | |/// ditto |CartesianPower!T makeCartesianPower(T = uint, Allocator)(auto ref Allocator alloc, size_t n, size_t repeat) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 2| assert(repeat >= 1, "Invalid number of repetitions"); | import std.experimental.allocator: makeArray; 1| return CartesianPower!T(n, alloc.makeArray!T(repeat)); |} | |/** |Lazy Forward range of Cartesian Power. |It always generates Cartesian Power from 0 to `n - 1`, |use $(LREF indexedRoR) to map it to your range. | |Generating a new item is in `O(k)` (amortized `O(1)`), |the total number of elements is `n^k`. | |See_Also: | $(LREF cartesianPower), $(LREF makeCartesianPower) |*/ |struct CartesianPower(T) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ | import mir.ndslice.slice: Slice; | | private T[] _state; | private size_t n; | private size_t _max_states, _pos; | | /// | alias DeepElement = const T; | | /// state should have the length of `repeat` 26| this()(size_t n, T[] state) @safe pure nothrow @nogc | { 26| assert(state.length >= 1, "Invalid number of repetitions"); | | import std.math: pow; 26| this.n = n; 26| assert(n <= T.max); 26| this._state = state; | 26| _max_states = pow(n, state.length); | } | | /// Input range primitives | @property Slice!(const(T)*) front()() @safe pure nothrow @nogc | { | import mir.ndslice.slice: sliced; 79| return _state.sliced; | } | | /// ditto | void popFront()() scope @safe pure nothrow @nogc | { 77| assert(!empty); 77| _pos++; | | /* | * Bitwise increment - starting from back | * It works like adding 1 in primary school arithmetic. | * If a block has reached the number of elements, we reset it to | * 0, and continue to increment, e.g. for n = 2: | * | * [0, 0, 0] -> [0, 0, 1] | * [0, 1, 1] -> [1, 0, 0] | */ 483| foreach_reverse (i, ref el; _state) | { 106| ++el; 106| if (el < n) 66| break; | 40| el = 0; | } | } | | /// ditto | @property size_t length()() @safe pure nothrow @nogc scope const | { 36| return _max_states - _pos; | } | | /// ditto | @property bool empty()() @safe pure nothrow @nogc scope const | { 87| return _pos == _max_states; | } | | /// | @property size_t[2] shape()() scope const | { 22| return [length, _state.length]; | } | | /// Forward range primitive. Allocates using GC. | @property CartesianPower save()() @safe pure nothrow | { 1| typeof(this) c = this; 1| c._state = _state.dup; 1| return c; | } |} | |/// |pure nothrow @safe version(mir_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | 1| assert(iota(2).cartesianPower.fuse == [[0], [1]]); 1| assert(iota(2).cartesianPower(2).fuse == [[0, 0], [0, 1], [1, 0], [1, 1]]); | 1| auto three_nums_two_bins = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]; 1| assert(iota(3).cartesianPower(2).fuse == three_nums_two_bins); | | import std.string: representation; 1| assert("AB"d.representation.cartesianPower(2).fuse == ["AA"d, "AB"d, "BA"d, "BB"d]); |} | |/// |@nogc version(mir_test) unittest |{ | import mir.ndslice.topology: iota; | import mir.algorithm.iteration: equal; | import mir.ndslice.slice: sliced; | | import std.experimental.allocator.mallocator: Mallocator; 1| auto alloc = Mallocator.instance; | | static immutable expected2r2 = [ | 0, 0, | 0, 1, | 1, 0, | 1, 1]; 1| auto r = iota(2); 1| auto rc = alloc.makeCartesianPower(r.length, 2); 1| assert(expected2r2.sliced(4, 2).equal(rc.indexedRoR(r))); 1| alloc.dispose(rc); |} | |pure nothrow @safe version(mir_test) unittest |{ | import mir.ndslice.fuse; | import mir.array.allocation: array; | import mir.ndslice.topology: iota; | import std.range: dropOne; | import std.string: representation; | 1| assert(iota(0).cartesianPower.length == 0); 1| assert("AB"d.representation.cartesianPower(3).fuse == ["AAA"d, "AAB"d, "ABA"d, "ABB"d, "BAA"d, "BAB"d, "BBA"d, "BBB"d]); 1| auto expected = ["AA"d, "AB"d, "AC"d, "AD"d, | "BA"d, "BB"d, "BC"d, "BD"d, | "CA"d, "CB"d, "CC"d, "CD"d, | "DA"d, "DB"d, "DC"d, "DD"d]; 1| assert("ABCD"d.representation.cartesianPower(2).fuse == expected); | // verify with array too 1| assert("ABCD"d.representation.cartesianPower(2).fuse == expected); | 1| assert(iota(2).cartesianPower.front == [0]); | | // is copyable? 1| auto a = iota(2).cartesianPower; 1| assert(a.front == [0]); 1| assert(a.save.dropOne.front == [1]); 1| assert(a.front == [0]); | | // test length shrinking 1| auto d = iota(2).cartesianPower; 1| assert(d.length == 2); 1| d.popFront; 1| assert(d.length == 1); |} | |version(assert) |version(mir_test) unittest |{ | // check invalid | import std.exception: assertThrown; | import core.exception: AssertError; | import std.experimental.allocator.mallocator : Mallocator; | 2| assertThrown!AssertError(0.cartesianPower(0)); 2| assertThrown!AssertError(Mallocator.instance.makeCartesianPower(0, 0)); |} | |// length |pure nothrow @safe version(mir_test) unittest |{ 1| assert(1.cartesianPower(1).length == 1); 1| assert(1.cartesianPower(2).length == 1); 1| assert(2.cartesianPower(1).length == 2); 1| assert(2.cartesianPower(2).length == 4); 1| assert(2.cartesianPower(3).length == 8); 1| assert(3.cartesianPower(1).length == 3); 1| assert(3.cartesianPower(2).length == 9); 1| assert(3.cartesianPower(3).length == 27); 1| assert(3.cartesianPower(4).length == 81); 1| assert(4.cartesianPower(10).length == 1_048_576); 1| assert(14.cartesianPower(7).length == 105_413_504); |} | |/** |Disposes a CartesianPower object. It destroys and then deallocates the |CartesianPower object pointed to by a pointer. |It is assumed the respective entities had been allocated with the same allocator. | |Params: | alloc = Custom allocator | cartesianPower = CartesianPower object | |See_Also: | $(LREF makeCartesianPower) |*/ |void dispose(T = uint, Allocator)(auto ref Allocator alloc, auto ref CartesianPower!T cartesianPower) |{ | import std.experimental.allocator: dispose; 1| dispose(alloc, cartesianPower._state); |} | |/** |Lazily computes all k-combinations of `r`. |Imagine this as the $(LREF cartesianPower) filtered for only strictly ordered items. | |While generating a new combination is in `O(k)`, |the number of combinations is `binomial(n, k)`. | |Params: | n = number of elements (`|r|`) | r = random access field. A field may not have iteration primitivies. | k = number of combinations | alloc = custom Allocator | |Returns: | Forward range, which yields the k-combinations items | |See_Also: | $(LREF Combinations) |*/ |Combinations!T combinations(T = uint)(size_t n, size_t k = 1) @safe pure nothrow | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 41| assert(k >= 1, "Invalid number of combinations"); 40| return Combinations!T(n, new T[k]); |} | |/// ditto |IndexedRoR!(Combinations!T, Range) combinations(T = uint, Range)(Range r, size_t k = 1) |if (isUnsigned!T && __traits(compiles, Range.init[size_t.init])) |{ 25| assert(k >= 1, "Invalid number of combinations"); 25| return combinations!T(r.length, k).indexedRoR(r); |} | |/// ditto |Combinations!T makeCombinations(T = uint, Allocator)(auto ref Allocator alloc, size_t n, size_t repeat) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 2| assert(repeat >= 1, "Invalid number of repetitions"); | import std.experimental.allocator: makeArray; 1| return Combinations!T(cast(T) n, alloc.makeArray!T(cast(T) repeat)); |} | |/** |Lazy Forward range of Combinations. |It always generates combinations from 0 to `n - 1`, |use $(LREF indexedRoR) to map it to your range. | |Generating a new combination is in `O(k)`, |the number of combinations is `binomial(n, k)`. | |See_Also: | $(LREF combinations), $(LREF makeCombinations) |*/ |struct Combinations(T) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ | import mir.ndslice.slice: Slice; | | private T[] state; | private size_t n; | private size_t max_states, pos; | | /// | alias DeepElement = const T; | | /// state should have the length of `repeat` 41| this()(size_t n, T[] state) @safe pure nothrow @nogc | { | import mir.ndslice.topology: iota; | 41| assert(state.length <= T.max); 41| this.n = n; 41| assert(n <= T.max); 41| this.max_states = cast(size_t) binomial(n, state.length); 41| this.state = state; | | // set initial state and calculate max possibilities 41| if (n > 0) | { | // skip first duplicate 75| if (n > 1 && state.length > 1) | { 29| auto iotaResult = iota(state.length); 547| foreach (i, ref el; state) | { 115| el = cast(T) iotaResult[i]; | } | } | } | } | | /// Input range primitives | @property Slice!(const(T)*) front()() @safe pure nothrow @nogc | { | import mir.ndslice.slice: sliced; 122| return state.sliced; | } | | /// ditto | void popFront()() scope @safe pure nothrow @nogc | { 120| assert(!empty); 120| pos++; | // we might have bumped into the end state now 140| if (empty) return; | | // Behaves like: do _getNextState(); while (!_state.isStrictlySorted); 100| size_t i = state.length - 1; | /* Go from the back to next settable block | * - A must block must be lower than it's previous | * - A state i is not settable if it's maximum height is reached | * | * Think of it as a backwords search on state with | * iota(_repeat + d, _repeat + d) as search mask. | * (d = _nrElements -_repeat) | * | * As an example n = 3, r = 2, iota is [1, 2] and hence: | * [0, 1] -> i = 2 | * [0, 2] -> i = 1 | */ 180| while (state[i] == n - state.length + i) | { 80| i--; | } 100| state[i] = cast(T)(state[i] + 1); | | /* Starting from our changed block, we need to take the change back | * to the end of the state array and update them by their new diff. | * [0, 1, 4] -> [0, 2, 3] | * [0, 3, 4] -> [1, 2, 3] | */ 540| foreach (j; i + 1 .. state.length) | { 80| state[j] = cast(T)(state[i] + j - i); | } | } | | /// ditto | @property size_t length()() @safe pure nothrow @nogc scope const | { 60| return max_states - pos; | } | | /// ditto | @property bool empty()() @safe pure nothrow @nogc scope const | { 259| return pos == max_states; | } | | /// | @property size_t[2] shape()() scope const | { 40| return [length, state.length]; | } | | /// Forward range primitive. Allocates using GC. | @property Combinations save()() @safe pure nothrow | { 1| typeof(this) c = this; 1| c.state = state.dup; 1| return c; | } |} | |/// |pure nothrow @safe version(mir_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | import std.string: representation; 1| assert(iota(3).combinations(2).fuse == [[0, 1], [0, 2], [1, 2]]); 1| assert("AB"d.representation.combinations(2).fuse == ["AB"d]); 1| assert("ABC"d.representation.combinations(2).fuse == ["AB"d, "AC"d, "BC"d]); |} | |/// |@nogc version(mir_test) unittest |{ | import mir.algorithm.iteration: equal; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: iota; | | import std.experimental.allocator.mallocator; 1| auto alloc = Mallocator.instance; | | static immutable expected3r2 = [ | 0, 1, | 0, 2, | 1, 2]; 1| auto r = iota(3); 1| auto rc = alloc.makeCombinations(r.length, 2); 1| assert(expected3r2.sliced(3, 2).equal(rc.indexedRoR(r))); 1| alloc.dispose(rc); |} | |pure nothrow @safe version(mir_test) unittest |{ | import mir.ndslice.fuse; | import mir.array.allocation: array; | import mir.ndslice.topology: iota; | import std.range: dropOne; | import std.string: representation; | 1| assert(iota(0).combinations.length == 0); 1| assert(iota(2).combinations.fuse == [[0], [1]]); | 1| auto expected = ["AB"d, "AC"d, "AD"d, "BC"d, "BD"d, "CD"d]; 1| assert("ABCD"d.representation.combinations(2).fuse == expected); | // verify with array too 1| assert("ABCD"d.representation.combinations(2).fuse == expected); 1| assert(iota(2).combinations.front == [0]); | | // is copyable? 1| auto a = iota(2).combinations; 1| assert(a.front == [0]); 1| assert(a.save.dropOne.front == [1]); 1| assert(a.front == [0]); | | // test length shrinking 1| auto d = iota(2).combinations; 1| assert(d.length == 2); 1| d.popFront; 1| assert(d.length == 1); | | // test larger combinations 1| auto expected5 = [[0, 1, 2], [0, 1, 3], [0, 1, 4], | [0, 2, 3], [0, 2, 4], [0, 3, 4], | [1, 2, 3], [1, 2, 4], [1, 3, 4], | [2, 3, 4]]; 1| assert(iota(5).combinations(3).fuse == expected5); 1| assert(iota(4).combinations(3).fuse == [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]); 1| assert(iota(3).combinations(3).fuse == [[0, 1, 2]]); 1| assert(iota(2).combinations(3).length == 0); 1| assert(iota(1).combinations(3).length == 0); | 1| assert(iota(3).combinations(2).fuse == [[0, 1], [0, 2], [1, 2]]); 1| assert(iota(2).combinations(2).fuse == [[0, 1]]); 1| assert(iota(1).combinations(2).length == 0); | 1| assert(iota(1).combinations(1).fuse == [[0]]); |} | |pure nothrow @safe version(mir_test) unittest |{ | // test larger combinations | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | 1| auto expected6r4 = [[0, 1, 2, 3], [0, 1, 2, 4], [0, 1, 2, 5], | [0, 1, 3, 4], [0, 1, 3, 5], [0, 1, 4, 5], | [0, 2, 3, 4], [0, 2, 3, 5], [0, 2, 4, 5], | [0, 3, 4, 5], [1, 2, 3, 4], [1, 2, 3, 5], | [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]; 1| assert(iota(6).combinations(4).fuse == expected6r4); | 1| auto expected6r3 = [[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 1, 5], | [0, 2, 3], [0, 2, 4], [0, 2, 5], [0, 3, 4], | [0, 3, 5], [0, 4, 5], [1, 2, 3], [1, 2, 4], | [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], | [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]; 1| assert(iota(6).combinations(3).fuse == expected6r3); | 1| auto expected6r2 = [[0, 1], [0, 2], [0, 3], [0, 4], [0, 5], | [1, 2], [1, 3], [1, 4], [1, 5], [2, 3], | [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]; 1| assert(iota(6).combinations(2).fuse == expected6r2); | 1| auto expected7r5 = [[0, 1, 2, 3, 4], [0, 1, 2, 3, 5], [0, 1, 2, 3, 6], | [0, 1, 2, 4, 5], [0, 1, 2, 4, 6], [0, 1, 2, 5, 6], | [0, 1, 3, 4, 5], [0, 1, 3, 4, 6], [0, 1, 3, 5, 6], | [0, 1, 4, 5, 6], [0, 2, 3, 4, 5], [0, 2, 3, 4, 6], | [0, 2, 3, 5, 6], [0, 2, 4, 5, 6], [0, 3, 4, 5, 6], | [1, 2, 3, 4, 5], [1, 2, 3, 4, 6], [1, 2, 3, 5, 6], | [1, 2, 4, 5, 6], [1, 3, 4, 5, 6], [2, 3, 4, 5, 6]]; 1| assert(iota(7).combinations(5).fuse == expected7r5); |} | |// length |pure nothrow @safe version(mir_test) unittest |{ 1| assert(1.combinations(1).length == 1); 1| assert(1.combinations(2).length == 0); 1| assert(2.combinations(1).length == 2); 1| assert(2.combinations(2).length == 1); 1| assert(2.combinations(3).length == 0); 1| assert(3.combinations(1).length == 3); 1| assert(3.combinations(2).length == 3); 1| assert(3.combinations(3).length == 1); 1| assert(3.combinations(4).length == 0); 1| assert(4.combinations(10).length == 0); 1| assert(14.combinations(11).length == 364); 1| assert(20.combinations(7).length == 77_520); 1| assert(30.combinations(10).length == 30_045_015); 1| assert(30.combinations(15).length == 155_117_520); |} | |version(assert) |version(mir_test) unittest |{ | // check invalid | import std.exception: assertThrown; | import core.exception: AssertError; | import std.experimental.allocator.mallocator: Mallocator; | 2| assertThrown!AssertError(0.combinations(0)); 2| assertThrown!AssertError(Mallocator.instance.makeCombinations(0, 0)); |} | |/** |Disposes a Combinations object. It destroys and then deallocates the |Combinations object pointed to by a pointer. |It is assumed the respective entities had been allocated with the same allocator. | |Params: | alloc = Custom allocator | perm = Combinations object | |See_Also: | $(LREF makeCombinations) |*/ |void dispose(T, Allocator)(auto ref Allocator alloc, auto ref Combinations!T perm) |{ | import std.experimental.allocator: dispose; 1| dispose(alloc, perm.state); |} | |/** |Lazily computes all k-combinations of `r` with repetitions. |A k-combination with repetitions, or k-multicombination, |or multisubset of size k from a set S is given by a sequence of k |not necessarily distinct elements of S, where order is not taken into account. |Imagine this as the cartesianPower filtered for only ordered items. | |While generating a new combination with repeats is in `O(k)`, |the number of combinations with repeats is `binomial(n + k - 1, k)`. | |Params: | n = number of elements (`|r|`) | r = random access field. A field may not have iteration primitivies. | k = number of combinations | alloc = custom Allocator | |Returns: | Forward range, which yields the k-multicombinations items | |See_Also: | $(LREF CombinationsRepeat) |*/ |CombinationsRepeat!T combinationsRepeat(T = uint)(size_t n, size_t k = 1) @safe pure nothrow | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 37| assert(k >= 1, "Invalid number of combinations"); 36| return CombinationsRepeat!T(n, new T[k]); |} | |/// ditto |IndexedRoR!(CombinationsRepeat!T, Range) combinationsRepeat(T = uint, Range)(Range r, size_t k = 1) | if (isUnsigned!T && __traits(compiles, Range.init[size_t.init])) |{ 21| assert(k >= 1, "Invalid number of combinations"); 21| return combinationsRepeat!T(r.length, k).indexedRoR(r); |} | |/// ditto |CombinationsRepeat!T makeCombinationsRepeat(T = uint, Allocator)(auto ref Allocator alloc, size_t n, size_t repeat) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ 2| assert(repeat >= 1, "Invalid number of repetitions"); | import std.experimental.allocator: makeArray; 1| return CombinationsRepeat!T(n, alloc.makeArray!T(repeat)); |} | |/** |Lazy Forward range of combinations with repeats. |It always generates combinations with repeats from 0 to `n - 1`, |use $(LREF indexedRoR) to map it to your range. | |Generating a new combination with repeats is in `O(k)`, |the number of combinations with repeats is `binomial(n, k)`. | |See_Also: | $(LREF combinationsRepeat), $(LREF makeCombinationsRepeat) |*/ |struct CombinationsRepeat(T) | if (isUnsigned!T && T.sizeof <= size_t.sizeof) |{ | import mir.ndslice.slice: Slice; | | private T[] state; | private size_t n; | private size_t max_states, pos; | | /// | alias DeepElement = const T; | | /// state should have the length of `repeat` 37| this()(size_t n, T[] state) @safe pure nothrow @nogc | { 37| this.n = n; 37| assert(n <= T.max); 37| this.state = state; 37| size_t repeatLen = state.length; | | // set initial state and calculate max possibilities 37| if (n > 0) | { 36| max_states = cast(size_t) binomial(n + repeatLen - 1, repeatLen); | } | } | | /// Input range primitives | @property Slice!(const(T)*) front()() @safe pure nothrow @nogc | { | import mir.ndslice.slice: sliced; 168| return state.sliced; | } | | /// ditto | void popFront()() scope @safe pure nothrow @nogc | { 166| assert(!empty); 166| pos++; | 166| immutable repeat = state.length; | | // behaves like: do _getNextState(); while (!_state.isSorted); 166| size_t i = repeat - 1; | // go to next settable block | // a block is settable if its not in the end state (=nrElements - 1) 385| while (state[i] == n - 1 && i != 0) | { 100| i--; | } 166| state[i] = cast(T)(state[i] + 1); | | // if we aren't at the last block, we need to set all blocks | // to equal the current one | // e.g. [0, 2] -> (upper block: [1, 2]) -> [1, 1] 166| if (i != repeat - 1) | { 354| for (size_t j = i + 1; j < repeat; j++) 100| state[j] = state[i]; | } | } | | /// ditto | @property size_t length()() @safe pure nothrow @nogc scope const | { 55| return max_states - pos; | } | | /// ditto | @property bool empty()() @safe pure nothrow @nogc scope const | { 184| return pos == max_states; | } | | /// | @property size_t[2] shape()() scope const | { 38| return [length, state.length]; | } | | /// Forward range primitive. Allocates using GC. | @property CombinationsRepeat save()() @safe pure nothrow | { 1| typeof(this) c = this; 1| c.state = state.dup; 1| return c; | } |} | |/// |pure nothrow @safe version(mir_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | import std.string: representation; | 1| assert(iota(2).combinationsRepeat.fuse == [[0], [1]]); 1| assert(iota(2).combinationsRepeat(2).fuse == [[0, 0], [0, 1], [1, 1]]); 1| assert(iota(3).combinationsRepeat(2).fuse == [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]); 1| assert("AB"d.representation.combinationsRepeat(2).fuse == ["AA"d, "AB"d, "BB"d]); |} | |/// |@nogc version(mir_test) unittest |{ | import mir.algorithm.iteration: equal; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: iota; | | import std.experimental.allocator.mallocator; 1| auto alloc = Mallocator.instance; | | static immutable expected3r1 = [ | 0, | 1, | 2]; 1| auto r = iota(3); 1| auto rc = alloc.makeCombinationsRepeat(r.length, 1); 1| assert(expected3r1.sliced(3, 1).equal(rc.indexedRoR(r))); 1| alloc.dispose(rc); |} | |version(mir_test) unittest |{ | import mir.ndslice.fuse; | import mir.array.allocation: array; | import mir.ndslice.topology: iota; | import std.range: dropOne; | import std.string: representation; | 1| assert(iota(0).combinationsRepeat.length == 0); 1| assert("AB"d.representation.combinationsRepeat(3).fuse == ["AAA"d, "AAB"d, "ABB"d,"BBB"d]); | 1| auto expected = ["AA"d, "AB"d, "AC"d, "AD"d, "BB"d, "BC"d, "BD"d, "CC"d, "CD"d, "DD"d]; 1| assert("ABCD"d.representation.combinationsRepeat(2).fuse == expected); | // verify with array too 1| assert("ABCD"d.representation.combinationsRepeat(2).fuse == expected); | 1| assert(iota(2).combinationsRepeat.front == [0]); | | // is copyable? 1| auto a = iota(2).combinationsRepeat; 1| assert(a.front == [0]); 1| assert(a.save.dropOne.front == [1]); 1| assert(a.front == [0]); | | // test length shrinking 1| auto d = iota(2).combinationsRepeat; 1| assert(d.length == 2); 1| d.popFront; 1| assert(d.length == 1); |} | |// length |pure nothrow @safe version(mir_test) unittest |{ 1| assert(1.combinationsRepeat(1).length == 1); 1| assert(1.combinationsRepeat(2).length == 1); 1| assert(2.combinationsRepeat(1).length == 2); 1| assert(2.combinationsRepeat(2).length == 3); 1| assert(2.combinationsRepeat(3).length == 4); 1| assert(3.combinationsRepeat(1).length == 3); 1| assert(3.combinationsRepeat(2).length == 6); 1| assert(3.combinationsRepeat(3).length == 10); 1| assert(3.combinationsRepeat(4).length == 15); 1| assert(4.combinationsRepeat(10).length == 286); 1| assert(11.combinationsRepeat(14).length == 1_961_256); 1| assert(20.combinationsRepeat(7).length == 657_800); 1| assert(20.combinationsRepeat(10).length == 20_030_010); 1| assert(30.combinationsRepeat(10).length == 635_745_396); |} | |pure nothrow @safe version(mir_test) unittest |{ | // test larger combinations | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | 1| auto expected3r1 = [[0], [1], [2]]; 1| assert(iota(3).combinationsRepeat(1).fuse == expected3r1); | 1| auto expected3r2 = [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]; 1| assert(iota(3).combinationsRepeat(2).fuse == expected3r2); | 1| auto expected3r3 = [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 1], | [0, 1, 2], [0, 2, 2], [1, 1, 1], [1, 1, 2], | [1, 2, 2], [2, 2, 2]]; 1| assert(iota(3).combinationsRepeat(3).fuse == expected3r3); | 1| auto expected3r4 = [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2], | [0, 0, 1, 1], [0, 0, 1, 2], [0, 0, 2, 2], | [0, 1, 1, 1], [0, 1, 1, 2], [0, 1, 2, 2], | [0, 2, 2, 2], [1, 1, 1, 1], [1, 1, 1, 2], | [1, 1, 2, 2], [1, 2, 2, 2], [2, 2, 2, 2]]; 1| assert(iota(3).combinationsRepeat(4).fuse == expected3r4); | 1| auto expected4r3 = [[0, 0, 0], [0, 0, 1], [0, 0, 2], | [0, 0, 3], [0, 1, 1], [0, 1, 2], | [0, 1, 3], [0, 2, 2], [0, 2, 3], | [0, 3, 3], [1, 1, 1], [1, 1, 2], | [1, 1, 3], [1, 2, 2], [1, 2, 3], | [1, 3, 3], [2, 2, 2], [2, 2, 3], | [2, 3, 3], [3, 3, 3]]; 1| assert(iota(4).combinationsRepeat(3).fuse == expected4r3); | 1| auto expected4r2 = [[0, 0], [0, 1], [0, 2], [0, 3], | [1, 1], [1, 2], [1, 3], [2, 2], | [2, 3], [3, 3]]; 1| assert(iota(4).combinationsRepeat(2).fuse == expected4r2); | 1| auto expected5r3 = [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4], | [0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2], | [0, 2, 3], [0, 2, 4], [0, 3, 3], [0, 3, 4], [0, 4, 4], | [1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4], [1, 2, 2], | [1, 2, 3], [1, 2, 4], [1, 3, 3], [1, 3, 4], [1, 4, 4], | [2, 2, 2], [2, 2, 3], [2, 2, 4], [2, 3, 3], [2, 3, 4], | [2, 4, 4], [3, 3, 3], [3, 3, 4], [3, 4, 4], [4, 4, 4]]; 1| assert(iota(5).combinationsRepeat(3).fuse == expected5r3); | 1| auto expected5r2 = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], | [1, 1], [1, 2], [1, 3], [1, 4], [2, 2], | [2, 3], [2, 4], [3, 3], [3, 4], [4, 4]]; 1| assert(iota(5).combinationsRepeat(2).fuse == expected5r2); |} | |version(assert) |version(mir_test) unittest |{ | // check invalid | import std.exception: assertThrown; | import core.exception: AssertError; | import std.experimental.allocator.mallocator: Mallocator; | 2| assertThrown!AssertError(0.combinationsRepeat(0)); 2| assertThrown!AssertError(Mallocator.instance.makeCombinationsRepeat(0, 0)); |} | |/** |Disposes a CombinationsRepeat object. It destroys and then deallocates the |CombinationsRepeat object pointed to by a pointer. |It is assumed the respective entities had been allocated with the same allocator. | |Params: | alloc = Custom allocator | perm = CombinationsRepeat object | |See_Also: | $(LREF makeCombinationsRepeat) |*/ |void dispose(T, Allocator)(auto ref Allocator alloc, auto ref CombinationsRepeat!T perm) |{ | import std.experimental.allocator: dispose; 1| dispose(alloc, perm.state); |} source/mir/combinatorics/package.d is 100% covered <<<<<< EOF # path=source-mir-container-binaryheap.lst |/** |This module provides a $(D BinaryHeap) (aka priority queue) |adaptor that makes a binary heap out of any user-provided random-access range. | |Current implementation is suitable for Mir/BetterC concepts. | |This module is a submodule of $(MREF mir, container). | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilia Ki (Mir & BetterC rework). |*/ |module mir.container.binaryheap; | |import mir.primitives; |import std.range.primitives: isRandomAccessRange, hasSwappableElements; |import std.traits; | |/// |@system nothrow @nogc version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; | import std.range : takeExactly; | static a = [4, 7, 3, 1, 5], b = [7, 5, 4]; 1| auto maxHeap = a.heapify; 1| assert((&maxHeap).takeExactly(3).equal(b)); | | static c = [4, 7, 3, 1, 5], d = [1, 3, 4]; 1| auto minHeap = c.heapify!"a > b"; 1| assert((&minHeap).takeExactly(3).equal(d)); |} | |/++ |Implements a $(HTTP en.wikipedia.org/wiki/Binary_heap, binary heap) |container on top of a given random-access range type (usually $(D |T[])) or a random-access container type (usually $(D Array!T)). The |documentation of $(D BinaryHeap) will refer to the underlying range or |container as the $(I store) of the heap. | |The binary heap induces structure over the underlying store such that |accessing the largest element (by using the $(D front) property) is a |$(BIGOH 1) operation and extracting it (by using the $(D |removeFront()) method) is done fast in $(BIGOH log n) time. | |If $(D less) is the less-than operator, which is the default option, |then $(D BinaryHeap) defines a so-called max-heap that optimizes |extraction of the $(I largest) elements. To define a min-heap, |instantiate BinaryHeap with $(D "a > b") as its predicate. | |Simply extracting elements from a $(D BinaryHeap) container is |tantamount to lazily fetching elements of $(D Store) in descending |order. Extracting elements from the $(D BinaryHeap) to completion |leaves the underlying store sorted in ascending order but, again, |yields elements in descending order. | |If $(D Store) is not a container, the $(D BinaryHeap) cannot grow beyond the |size of that range. If $(D Store) is a container that supports $(D |insertBack), the $(D BinaryHeap) may grow by adding elements to the |container. |+/ |struct BinaryHeap(alias less = "a < b", Store) |if (isRandomAccessRange!Store || isRandomAccessRange!(typeof(Store.init[]))) |{ | import mir.utility : min; | import mir.functional : naryFun; | import core.lifetime: move; | import std.algorithm.mutation : swapAt; | | static if (isRandomAccessRange!Store) | alias Range = Store; | else | alias Range = typeof(Store.init[]); | | alias percolate = HeapOps!(less, Range).percolate; | alias siftDown = HeapOps!(less, Range).siftDown; | alias buildHeap = HeapOps!(less, Range).buildHeap; | | @disable this(); | @disable this(this); | | // Comparison predicate | private alias comp = naryFun!(less); | // Convenience accessors | | // Asserts that the heap property is respected. | private void assertValid() scope | { | debug | { 36| if (_length < 2) return; 292| for (size_t n = _length - 1; n >= 1; --n) | { 110| auto parentIdx = (n - 1) / 2; 110| assert(!comp(_store[parentIdx], _store[n])); | } | } | } | |public: | | /// The payload includes the support store and the effective length | size_t _length; | /// ditto | Store _store; | | | /++ | Converts the store $(D s) into a heap. If $(D initialSize) is | specified, only the first $(D initialSize) elements in $(D s) | are transformed into a heap, after which the heap can grow up | to $(D r.length) (if $(D Store) is a range) or indefinitely (if | $(D Store) is a container with $(D insertBack)). Performs | $(BIGOH min(r.length, initialSize)) evaluations of $(D less). | +/ 38| this(Store s, size_t initialSize = size_t.max) | { 38| acquire(s, initialSize); | } | | /++ | Takes ownership of a store. After this, manipulating $(D s) may make | the heap work incorrectly. | +/ | void acquire(Store s, size_t initialSize = size_t.max) | { 38| _store = move(s); 38| _length = min(_store.length, initialSize); 40| if (_length < 2) return; 36| buildHeap(_store[]); 36| assertValid(); | } | | /++ | Takes ownership of a store assuming it already was organized as a | heap. | +/ | void assume(Store s, size_t initialSize = size_t.max) | { 0000000| _store = move(s); 0000000| _length = min(_store.length, initialSize); 0000000| assertValid(); | } | | /++ | Returns the _length of the heap. | +/ | @property size_t length() scope const | { 1642| return _length; | } | | /++ | Returns $(D true) if the heap is _empty, $(D false) otherwise. | +/ | @property bool empty() scope const | { 1625| return !length; | } | | /++ | Returns the _capacity of the heap, which is the length of the | underlying store (if the store is a range) or the _capacity of the | underlying store (if the store is a container). | +/ | @property size_t capacity() scope const | { | static if (is(typeof(_store.capacity) : size_t)) | { 0000000| return _store.capacity; | } | else | { 0000000| return _store.length; | } | } | | /++ | Returns a _front of the heap, which is the largest element | according to `less`. | +/ | @property auto ref ElementType!Store front() return scope | { 463| assert(!empty, "Cannot call front on an empty heap."); 463| return _store.front; | } | | | | /++ | Inserts `value` into the store. If the underlying store is a range | and `length == capacity`, throws an AssertException. | +/ | size_t insert(ElementType!Store value) scope | { | static if (is(typeof(_store.insertBack(value)))) | { 3| if (length == _store.length) | { | // reallocate 3| _store.insertBack(value); | } | else | { | // no reallocation 0000000| _store[_length] = value; | } | } | else | { | // can't grow 10| assert(length < _store.length, "Cannot grow a heap created over a range"); 10| _store[_length] = value; | } | | // sink down the element 37| for (size_t n = _length; n; ) | { 20| auto parentIdx = (n - 1) / 2; 29| if (!comp(_store[parentIdx], _store[n])) break; // done! | // must swap and continue 11| _store.swapAt(parentIdx, n); 11| n = parentIdx; | } 13| ++_length; | debug(BinaryHeap) assertValid(); 13| return 1; | } | | /++ | Removes the largest element from the heap. | +/ | void removeFront() scope | { 113| assert(!empty, "Cannot call removeFront on an empty heap."); 113| if (--_length) 83| _store.swapAt(0, _length); 113| percolate(_store[], 0, _length); | } | | /// ditto | alias popFront = removeFront; | | /++ | Removes the largest element from the heap and returns | it. The element still resides in the heap's store. For performance | reasons you may want to use $(D removeFront) with heaps of objects | that are expensive to copy. | +/ | auto ref removeAny() scope | { 0000000| removeFront(); 0000000| return _store[_length]; | } | | /++ | Replaces the largest element in the store with `value`. | +/ | void replaceFront(ElementType!Store value) scope | { | // must replace the top 0000000| assert(!empty, "Cannot call replaceFront on an empty heap."); 0000000| _store.front = value; 0000000| percolate(_store[], 0, _length); | debug(BinaryHeap) assertValid(); | } | | /++ | If the heap has room to grow, inserts `value` into the store and | returns $(D true). Otherwise, if $(D less(value, front)), calls $(D | replaceFront(value)) and returns again $(D true). Otherwise, leaves | the heap unaffected and returns $(D false). This method is useful in | scenarios where the smallest $(D k) elements of a set of candidates | must be collected. | +/ | bool conditionalInsert(ElementType!Store value) scope | { 0000000| if (_length < _store.length) | { 0000000| insert(value); 0000000| return true; | } | 0000000| assert(!_store.empty, "Cannot replace front of an empty heap."); 0000000| if (!comp(value, _store.front)) return false; // value >= largest 0000000| _store.front = value; | 0000000| percolate(_store[], 0, _length); | debug(BinaryHeap) assertValid(); 0000000| return true; | } | | /++ | Swapping is allowed if the heap is full. If $(D less(value, front)), the | method exchanges store.front and value and returns $(D true). Otherwise, it | leaves the heap unaffected and returns $(D false). | +/ | bool conditionalSwap(ref ElementType!Store value) scope | { 10| assert(_length == _store.length); 10| assert(!_store.empty, "Cannot swap front of an empty heap."); | 15| if (!comp(value, _store.front)) return false; // value >= largest | | import std.algorithm.mutation : swap; 5| swap(_store.front, value); | 5| percolate(_store[], 0, _length); | debug(BinaryHeap) assertValid(); | 5| return true; | } |} | |/// Example from "Introduction to Algorithms" Cormen et al, p 146 |@system nothrow version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; 1| int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; 1| auto h = heapify(a); | // largest element 1| assert(h.front == 16); | // a has the heap property 1| assert(equal(a, [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ])); |} | |/// $(D BinaryHeap) implements the standard input range interface, allowing |/// lazy iteration of the underlying range in descending order. |@system nothrow version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; | import std.range : takeExactly; 1| int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; 1| auto heap = heapify(a); 1| auto top5 = (&heap).takeExactly(5); 1| assert(top5.equal([16, 14, 10, 9, 8])); |} | |/++ |Convenience function that returns a $(D BinaryHeap!Store) object |initialized with $(D s) and $(D initialSize). |+/ |BinaryHeap!(less, Store) heapify(alias less = "a < b", Store)(Store s, | size_t initialSize = size_t.max) |{ | 10| return BinaryHeap!(less, Store)(s, initialSize); |} | |/// |@system nothrow version(mir_test) unittest |{ | import std.range.primitives; | { | // example from "Introduction to Algorithms" Cormen et al., p 146 1| int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; 1| auto h = heapify(a); 1| h = heapify!"a < b"(a); 1| assert(h.front == 16); 1| assert(a == [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ]); 1| auto witness = [ 16, 14, 10, 9, 8, 7, 4, 3, 2, 1 ]; 21| for (; !h.empty; h.removeFront(), witness.popFront()) | { 10| assert(!witness.empty); 10| assert(witness.front == h.front); | } 1| assert(witness.empty); | } | { 1| int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; 1| int[] b = new int[a.length]; 1| BinaryHeap!("a < b", int[]) h = BinaryHeap!("a < b", int[])(b, 0); 33| foreach (e; a) | { 10| h.insert(e); | } 1| assert(b == [ 16, 14, 10, 8, 7, 3, 9, 1, 4, 2 ]); | } |} | |@system nothrow version(mir_test) unittest |{ | // Test range interface. | import mir.primitives: isInputRange; | import mir.algorithm.iteration : equal; 1| int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; 1| auto h = heapify(a); | static assert(isInputRange!(typeof(h))); 1| assert((&h).equal([16, 14, 10, 9, 8, 7, 4, 3, 2, 1])); |} | |@system nothrow version(mir_test) unittest // 15675 |{ | import std.container.array : Array; | 2| Array!int elements = [1, 2, 10, 12]; 2| auto heap = heapify(elements); 1| assert(heap.front == 12); 1| heap.insert(100); 1| assert(heap.front == 100); 1| heap.insert(1); 1| assert(heap.front == 100); |} | |@system version(mir_test) unittest |{ | import std.container.array : Array; | 2| Array!Object elements; 2| auto heap = heapify(elements); 1| Object obj; 1| heap.insert(obj); |} | |@system nothrow version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; | import std.internal.test.dummyrange; | | alias RefRange = DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random); | 1| RefRange a; 1| RefRange b; 1| a.reinit(); 1| b.reinit(); | 1| auto heap = heapify(a); 32| foreach (ref elem; b) | { 10| heap.conditionalSwap(elem); | } | 1| assert(equal(&heap, [ 5, 5, 4, 4, 3, 3, 2, 2, 1, 1])); 1| assert(equal(b, [10, 9, 8, 7, 6, 6, 7, 8, 9, 10])); |} | |/// Heap operations for random-access ranges |template HeapOps(alias less, Range) |{ | import std.range.primitives : hasSwappableElements, hasAssignableElements; | import mir.functional; | import std.algorithm.mutation : swapAt; | | static assert(isRandomAccessRange!Range); | static assert(hasLength!Range); | static assert(hasSwappableElements!Range || hasAssignableElements!Range); | | alias lessFun = naryFun!less; | | /// template because of @@@12410@@@ | void heapSort()(Range r) | { | // If true, there is nothing to do | if (r.length < 2) return; | // Build Heap | buildHeap(r); | // Sort | for (size_t i = r.length - 1; i > 0; --i) | { | r.swapAt(0, i); | percolate(r, 0, i); | } | } | | /// template because of @@@12410@@@ | void buildHeap()(Range r) | { 36| immutable n = r.length; 139| for (size_t i = n / 2; i-- > 0; ) | { 67| siftDown(r, i, n); | } 36| assert(isHeap(r)); | } | | /// | bool isHeap()(Range r) | { 36| size_t parent = 0; 438| foreach (child; 1 .. r.length) | { 110| if (lessFun(r[parent], r[child])) return false; | // Increment parent every other pass 110| parent += !(child & 1); | } 36| return true; | } | | /// Sifts down r[parent] (which is initially assumed to be messed up) so the | /// heap property is restored for r[parent .. end]. | /// template because of @@@12410@@@ | void siftDown()(Range r, size_t parent, immutable size_t end) | { | for (;;) | { 312| auto child = (parent + 1) * 2; 312| if (child >= end) | { | // Leftover left child? 333| if (child == end && lessFun(r[parent], r[--child])) 67| r.swapAt(parent, child); 200| break; | } 112| auto leftChild = child - 1; 161| if (lessFun(r[child], r[leftChild])) child = leftChild; 146| if (!lessFun(r[parent], r[child])) break; 78| r.swapAt(parent, child); 78| parent = child; | } | } | | /// Alternate version of siftDown that performs fewer comparisons, see | /// https://en.wikipedia.org/wiki/Heapsort#Bottom-up_heapsort. The percolate | /// process first sifts the parent all the way down (without comparing it | /// against the leaves), and then a bit up until the heap property is | /// restored. So there are more swaps but fewer comparisons. Gains are made | /// when the final position is likely to end toward the bottom of the heap, | /// so not a lot of sifts back are performed. | /// template because of @@@12410@@@ | void percolate()(Range r, size_t parent, immutable size_t end) | { 118| immutable root = parent; | | // Sift down | for (;;) | { 190| auto child = (parent + 1) * 2; | 190| if (child >= end) | { 118| if (child == end) | { | // Leftover left node. 21| --child; 21| r.swapAt(parent, child); 21| parent = child; | } 118| break; | } | 72| auto leftChild = child - 1; 107| if (lessFun(r[child], r[leftChild])) child = leftChild; 72| r.swapAt(parent, child); 72| parent = child; | } | | // Sift up 262| for (auto child = parent; child > root; child = parent) | { 65| parent = (child - 1) / 2; 117| if (!lessFun(r[parent], r[child])) break; 13| r.swapAt(parent, child); | } | } |} source/mir/container/binaryheap.d is 86% covered <<<<<< EOF # path=source-mir-cpp_export-numeric.lst |/++ |This module contans extern C++ wrappers for $(MREF mir, numeric). |+/ |module mir.cpp_export.numeric; | |import mir.numeric: findRootImpl, mir_find_root_result; | |private alias CFunction(T) = extern(C++) T function(scope const(void)* ctx, T) @safe pure nothrow @nogc; | |private alias CTolerance(T) = extern(C++) bool function(scope const(void)* ctx, T, T) @safe pure nothrow @nogc; | |export extern(C++) @safe pure nothrow @nogc: | |/// Wrapper for $(REF_ALTTEXT $(TT findRoot), findRoot, mir, numeric)$(NBSP) |mir_find_root_result!float mir_find_root( | float ax, | float bx, | float fax, | float fbx, | float lowerBound, | float upperBound, | uint maxIterations, | uint steps, | scope CFunction!float f, | scope const(void)* f_ctx, | scope CTolerance!float tolerance, | scope const(void)* tolerance_ctx, |) |{ | pragma(inline, false); 0000000| if (tolerance is null) 0000000| return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, (float x) => f(f_ctx, x), null); | else 0000000| return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, (float x) => f(f_ctx, x), (float a, float b) => tolerance(tolerance_ctx, a, b) != 0); |} | |/// ditto |mir_find_root_result!double mir_find_root( | double ax, | double bx, | double fax, | double fbx, | double lowerBound, | double upperBound, | uint maxIterations, | uint steps, | scope CFunction!double f, | scope const(void)* f_ctx, | scope CTolerance!double tolerance, | scope const(void)* tolerance_ctx, |) |{ | pragma(inline, false); 0000000| if (tolerance is null) 0000000| return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, (double x) => f(f_ctx, x), null); | else 0000000| return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, (double x) => f(f_ctx, x), (double a, double b) => tolerance(tolerance_ctx, a, b) != 0); |} | |/// ditto |mir_find_root_result!real mir_find_root( | real ax, | real bx, | real fax, | real fbx, | real lowerBound, | real upperBound, | uint maxIterations, | uint steps, | scope CFunction!real f, | scope const(void)* f_ctx, | scope CTolerance!real tolerance, | scope const(void)* tolerance_ctx, |) |{ | pragma(inline, false); 0000000| if (tolerance is null) 0000000| return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, (real x) => f(f_ctx, x), null); | else 0000000| return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, (real x) => f(f_ctx, x), (real a, real b) => tolerance(tolerance_ctx, a, b) != 0); |} source/mir/cpp_export/numeric.d is 0% covered <<<<<< EOF # path=source-mir-date.lst |/++ |Fast BetterC Date type with Boost ABI and mangling compatability. | |$(SCRIPT inhibitQuickIndex = 1;) |$(DIVC quickindex, |$(BOOKTABLE, |$(TR $(TH Category) $(TH Functions)) |$(TR $(TD Main date types) $(TD | $(LREF Date) | $(LREF YearMonth) | $(LREF YearQuarter) |)) |$(TR $(TD Other date types) $(TD | $(LREF Month) | $(LREF Quarter) | $(LREF DayOfWeek) |)) |$(TR $(TD Date checking) $(TD | $(LREF valid) | $(LREF yearIsLeapYear) |)) |$(TR $(TD Date conversion) $(TD | $(LREF daysToDayOfWeek) | $(LREF quarter) |)) |$(TR $(TD Other) $(TD | $(LREF AllowDayOverflow) | $(LREF DateTimeException) | $(LREF AssumePeriod) |)) |)) |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: $(HTTP jmdavisprog.com, Jonathan M Davis), Ilia Ki (boost-like and BetterC rework) |+/ |module mir.date; | |import mir.primitives: isOutputRange; |import mir.serde: serdeProxy; |import mir.timestamp: Timestamp; |import std.traits: isSomeChar, Unqual; | |version(mir_test) |version(D_Exceptions) |version(unittest) import std.exception : assertThrown; | |version(test_with_asdf) |unittest |{ | import asdf.serialization; | | assert(Date(2020, 3, 19).serializeToJson == `"2020-03-19"`); | assert(`"2020-03-19"`.deserialize!Date == Date(2020, 3, 19)); | assert(`"20200319"`.deserialize!Date == Date(2020, 3, 19)); | assert(`"2020-Mar-19"`.deserialize!Date == Date(2020, 3, 19)); |} | |/++ |Returns whether the given value is valid for the given unit type when in a |time point. Naturally, a duration is not held to a particular range, but |the values in a time point are (e.g. a month must be in the range of |1 - 12 inclusive). |Params: | units = The units of time to validate. | value = The number to validate. |+/ |bool valid(string units)(int value) @safe pure nothrow @nogc |if (units == "months" || | units == "hours" || | units == "minutes" || | units == "seconds") |{ | static if (units == "months") 2653| return value >= Month.jan && value <= Month.dec; | else static if (units == "hours") 4| return value >= 0 && value <= 23; | else static if (units == "minutes") | return value >= 0 && value <= 59; | else static if (units == "seconds") | return value >= 0 && value <= 59; |} | |/// |version (mir_test) |@safe unittest |{ 1| assert(valid!"hours"(12)); 1| assert(!valid!"hours"(32)); 1| assert(valid!"months"(12)); 1| assert(!valid!"months"(13)); |} | |/++ |Returns whether the given day is valid for the given year and month. |Params: | units = The units of time to validate. | year = The year of the day to validate. | month = The month of the day to validate (January is 1). | day = The day to validate. |+/ |bool valid(string units)(int year, int month, int day) @safe pure nothrow @nogc | if (units == "days") |{ 1217| return day > 0 && day <= maxDay(year, month); |} | |/// |version (mir_test) |@safe pure nothrow @nogc unittest |{ 1| assert(valid!"days"(2016, 2, 29)); 1| assert(!valid!"days"(2016, 2, 30)); 1| assert(valid!"days"(2017, 2, 20)); 1| assert(!valid!"days"(2017, 2, 29)); |} | |/// |enum AllowDayOverflow : bool |{ | /// | no, | /// | yes |} | |/++ |Whether the given Gregorian Year is a leap year. |Params: | year = The year to to be tested. | +/ |bool yearIsLeapYear(int year) @safe pure nothrow @nogc |{ 986| if (year % 400 == 0) 195| return true; 791| if (year % 100 == 0) 28| return false; 763| return year % 4 == 0; |} | |/// |version (mir_test) |@safe unittest |{ 36| foreach (year; [1, 2, 100, 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010]) | { 11| assert(!yearIsLeapYear(year)); 11| assert(!yearIsLeapYear(-year)); | } | 36| foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012]) | { 11| assert(yearIsLeapYear(year)); 11| assert(yearIsLeapYear(-year)); | } |} | |version (mir_test) |@safe unittest |{ | import std.format : format; 72| foreach (year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999, | 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011]) | { 23| assert(!yearIsLeapYear(year), format("year: %s.", year)); 23| assert(!yearIsLeapYear(-year), format("year: %s.", year)); | } | 36| foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012]) | { 11| assert(yearIsLeapYear(year), format("year: %s.", year)); 11| assert(yearIsLeapYear(-year), format("year: %s.", year)); | } |} | |/// |enum Month : short |{ | /// | jan = 1, | /// | feb, | /// | mar, | /// | apr, | /// | may, | /// | jun, | /// | jul, | /// | aug, | /// | sep, | /// | oct, | /// | nov, | /// | dec, |} | |version(D_Exceptions) |/// |class DateTimeException : Exception |{ | /// 47| @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) | { 47| super(msg, file, line, nextInChain); | } | | /// ditto 0000000| @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) | { 0000000| super(msg, file, line, nextInChain); | } |} | |version(D_Exceptions) |{ | private static immutable InvalidMonth = new DateTimeException("Date: Invalid Month"); | private static immutable InvalidDay = new DateTimeException("Date: Invalid Day"); | private static immutable InvalidISOString = new DateTimeException("Date: Invalid ISO String"); | private static immutable InvalidISOExtendedString = new DateTimeException("Date: Invalid ISO Extended String"); | private static immutable InvalidSimpleString = new DateTimeException("Date: Invalid Simple String"); | private static immutable InvalidString = new DateTimeException("Date: Invalid String"); |} | |version (mir_test) |@safe unittest |{ 1| initializeTests(); |} | |/++ | Represents the 7 days of the Gregorian week (Monday is 0). | +/ |extern(C++, "mir") |enum DayOfWeek |{ | mon = 0, /// | tue, /// | wed, /// | thu, /// | fri, /// | sat, /// | sun, /// |} | |/// |@serdeProxy!Timestamp |struct YearMonthDay |{ | short year = 1; | Month month = Month.jan; | ubyte day = 1; | | /// | Quarter quarter() @safe pure nothrow @nogc @property | { 7| return month.quarter; | } | | /// | Timestamp timestamp() @safe pure nothrow @nogc const @property | { 3| return Timestamp(year, cast(ubyte)month, day); | } | | /// | alias opCast(T : Timestamp) = timestamp; | |@safe pure @nogc: | | /// | YearQuarter yearQuarter() @safe pure nothrow @nogc @property | { 0000000| return YearQuarter(year, this.quarter); | } | | /// | alias opCast(T : YearQuarter) = yearQuarter; | | /// | version(mir_test) | unittest | { | import mir.timestamp; 1| auto timestamp = cast(Timestamp) YearMonthDay(2020, Month.may, 12); | } | | /// 13| this(short year, Month month, ubyte day) @safe pure nothrow @nogc | { 13| this.year = year; 13| this.month = month; 13| this.day = day; | } | | /// 4| this(Date date) @safe pure nothrow @nogc | { 4| this = date.yearMonthDay; | } | | /// | version(mir_test) | @safe unittest | { 1| auto d = YearMonthDay(2020, Month.may, 31); 1| auto ym = d.YearMonth; 1| assert(ym.year == 2020); 1| assert(ym.month == Month.may); | } | | // | version(mir_test) | @safe unittest | { 1| auto d = YearMonthDay(2050, Month.dec, 31); 1| auto ym = d.YearMonth; 1| assert(ym.year == 2050); 1| assert(ym.month == Month.dec); | } | | /// 0000000| this(YearMonth yearMonth, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure nothrow @nogc | { 0000000| with(yearMonth) this(year, month, day(assumePeriod)); | } | | /// 0000000| this(YearQuarter yearQuarter, AssumePeriod assumePeriodMonth = AssumePeriod.begin, AssumePeriod assumePeriodDay = AssumePeriod.begin) @safe pure nothrow @nogc | { 0000000| with(yearQuarter) this(year, month(assumePeriodMonth), day(assumePeriodDay)); | } | | version(D_Exceptions) | /// 0000000| this(Timestamp timestamp) @safe pure @nogc | { 0000000| if (timestamp.precision != Timestamp.Precision.day) | { | static immutable exc = new Exception("YearMonthDay: invalid timestamp precision"); 0000000| throw exc; | } 0000000| with(timestamp) this(year, cast(Month)month, day); | } | | /// | @safe pure nothrow @nogc | ref YearMonthDay add(string units : "months")(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) | { | auto years = months / 12; | months %= 12; | auto newMonth = month + months; | | if (months < 0) | { | if (newMonth < 1) | { | newMonth += 12; | --years; | } | } | else if (newMonth > 12) | { | newMonth -= 12; | ++years; | } | | year += years; | month = cast(Month) newMonth; | | immutable currMaxDay = maxDay(year, month); | immutable overflow = day - currMaxDay; | | if (overflow > 0) | { | if (allowOverflow == AllowDayOverflow.yes) | { | ++month; | day = cast(ubyte) overflow; | } | else | day = cast(ubyte) currMaxDay; | } | | return this; | } | | /// | @safe pure nothrow @nogc | ref YearMonthDay add(string units : "quarters")(long quarters) | { | return add!"months"(quarters * 4); | } | | // Shares documentation with "years" version. | @safe pure nothrow @nogc | ref YearMonthDay add(string units : "years")(long years, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) | { | year += years; | | immutable currMaxDay = maxDay(year, month); | immutable overflow = day - currMaxDay; | | if (overflow > 0) | { | if (allowOverflow == AllowDayOverflow.yes) | { | ++month; | day = cast(ubyte) overflow; | } | else | day = cast(ubyte) currMaxDay; | } | return this; | } | | /++ | Day of the year this $(LREF Date) is on. | +/ | @property int dayOfYear() const @safe pure nothrow @nogc | { 6| if (month >= Month.jan && month <= Month.dec) | { 3| immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; 3| auto monthIndex = month - Month.jan; | 3| return lastDay[monthIndex] + day; | } | assert(0, "Invalid month."); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonthDay(1999, cast(Month) 1, 1).dayOfYear == 1); 1| assert(YearMonthDay(1999, cast(Month) 12, 31).dayOfYear == 365); 1| assert(YearMonthDay(2000, cast(Month) 12, 31).dayOfYear == 366); | } | | /++ | Whether this $(LREF Date) is in a leap year. | +/ | @property bool isLeapYear() const @safe pure nothrow @nogc | { 111| return yearIsLeapYear(year); | } | | private void setDayOfYear(bool useExceptions = false)(int days) | { 54| immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; | 108| bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear); | | static if (useExceptions) | { | if (dayOutOfRange) throw InvalidDay; | } | else | { 54| assert(!dayOutOfRange, "Invalid Day"); | } | 1440| foreach (i; 1 .. lastDay.length) | { 462| if (days <= lastDay[i]) | { 54| month = cast(Month)(cast(int) Month.jan + i - 1); 54| day = cast(ubyte)(days - lastDay[i - 1]); 54| return; | } | } | assert(0, "Invalid day of the year."); | } | | /++ | The last day in the month that this $(LREF Date) is in. | +/ | @property ubyte daysInMonth() const @safe pure nothrow @nogc | { 0000000| return maxDay(year, month); | } | | /++ | Whether the current year is a date in A.D. | +/ | @property bool isAD() const @safe pure nothrow @nogc | { 0000000| return year > 0; | } |} | |/++ |Controls the assumed start period of days for `YearMonth` or days and quarters |for `YearQuarter` |+/ |enum AssumePeriod { | /// | begin, | /// | end |} | |/// Represents a date as a pair of years and months. |@serdeProxy!Timestamp |struct YearMonth |{ | short year = 1; | Month month = Month.jan; | | version(D_BetterC){} else | { | private string toStringImpl(alias fun)() const @safe pure nothrow | { | import mir.small_string : SmallString; 2| SmallString!16 w; | try 2| fun(w); | catch (Exception e) | assert(0, __traits(identifier, fun) ~ " threw."); 2| return w[].idup; | } | | string toISOExtString() const @safe pure nothrow | { 2| return toStringImpl!toISOExtString; | } | | alias toString = toISOExtString; | } | | /// | void toISOExtString(W)(scope ref W w) const scope | if (isOutputRange!(W, char)) | { | import mir.format: printZeroPad; 2| if (year >= 10_000) 1| w.put('+'); 2| w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); 2| w.put('-'); 2| w.printZeroPad(cast(uint)month, 2); | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(1999, Month.jan); 1| assert(ym.toISOExtString == "1999-01"); | } | | // | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(10_001, Month.jan); 1| assert(ym.toISOExtString == "+10001-01"); | } | | @property ubyte day(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc | { 13| final switch (assumePeriod) | { 9| case AssumePeriod.begin: 9| return 1; 4| case AssumePeriod.end: 4| return daysInMonth; | } | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonth(1999, cast(Month) 1).day(AssumePeriod.begin) == 1); 1| assert(YearMonth(1999, cast(Month) 12).day(AssumePeriod.end) == 31); | } | | /// | Quarter quarter() @safe pure nothrow @nogc @property | { 2| return month.quarter; | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonth(1999, Month.jan).quarter == 1); | } | | /// | Timestamp timestamp() @safe pure nothrow @nogc const @property | { 1| return Timestamp(year, cast(ubyte)month); | } | | /// | alias opCast(T : Timestamp) = timestamp; | | /// | version(mir_test) | unittest | { | import mir.timestamp; 1| auto ym0 = YearMonth(2020, Month.may); 1| auto timestamp1 = cast(Timestamp) ym0; 1| auto ym1 = YearMonth(timestamp1); | } | | /// 77| this(short year, Month month) @safe pure nothrow @nogc | { 77| this.year = year; 77| this.month = month; | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(2000, Month.dec); | } | | /// 2| this(short year, ushort month) @safe pure @nogc | { | static immutable exc = new Exception("Month out of bounds [1, 12]"); 4| if (1 > month || month > 12) 0000000| throw exc; 2| this.year = year; 2| this.month = cast(Month)month; | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(2000, 12); | } | | /// 4| this(Date date) @safe pure nothrow @nogc | { 4| this(date.YearMonthDay); | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(Date(2000, Month.dec, 31)); | } | | /// | version(mir_test) | @safe unittest | { 1| auto d = Date(2020, Month.may, 31); 1| auto ym = d.YearMonth; 1| assert(ym.year == 2020); 1| assert(ym.month == Month.may); | } | | // | version(mir_test) | @safe unittest | { 1| auto d = Date(2050, Month.dec, 31); 1| auto ym = d.YearMonth; 1| assert(ym.year == 2050); 1| assert(ym.month == Month.dec); | } | | /// 7| this(YearMonthDay yearMonthDay) @safe pure nothrow @nogc | { 7| with(yearMonthDay) this(year, month); | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(YearMonthDay(2000, Month.dec, 31)); | } | | /// 2| this(YearQuarter yearQuarter, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure nothrow @nogc | { 2| with(yearQuarter) this(year, month(assumePeriod)); | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym1 = YearMonth(YearQuarter(2000, Quarter.q1)); 1| auto ym2 = YearMonth(YearQuarter(2000, Quarter.q1), AssumePeriod.end); | } | | version(D_Exceptions) | /// 1| this(Timestamp timestamp) @safe pure @nogc | { 1| if (timestamp.precision != Timestamp.Precision.month) | { | static immutable exc = new Exception("YearMonth: invalid timestamp precision"); 0000000| throw exc; | } 1| with(timestamp) this(year, cast(Month)month); | } | | Date nthWeekday(int n, DayOfWeek dow) const @safe pure nothrow @nogc | { 2| auto d = trustedWithDayOfMonth(1); 2| auto dc = d.dayOfWeek.daysToDayOfWeek(dow) + (n - 1) * 7; 2| d = d + dc; 2| return d; | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(2000, Month.nov); 1| assert(ym.nthWeekday(1, DayOfWeek.mon) == Date(2000, 11, 6)); 1| assert(ym.nthWeekday(5, DayOfWeek.mon) == Date(2000, 12, 4)); | } | | /// | Date trustedWithDayOfMonth(int days) const @safe pure nothrow @nogc | { 3| assert(days <= lengthOfMonth); 3| return Date.trustedCreate(year, month, days); | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(2000, Month.nov); 1| assert(ym.trustedWithDayOfMonth(6) == Date(2000, 11, 6)); | } | | /// | int opCmp(YearMonth rhs) const pure nothrow @nogc @safe | { 4| if (auto d = this.year - rhs.year) 1| return d; 3| return this.month - rhs.month; | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth(2000, Month.nov); 1| assert(ym.opCmp(YearMonth(2000, Month.nov)) == 0); 1| assert(ym.opCmp(YearMonth(2000, Month.oct)) == 1); 1| assert(ym.opCmp(YearMonth(2000, Month.dec)) == -1); 1| assert(ym.opCmp(YearMonth(2001, Month.nov)) == -1); | } | | /// | size_t toHash() const pure nothrow @nogc @safe | { 1| return year * 16 + month; | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonth(2000, Month.dec).toHash == 32012); | } | | /// | Date endOfMonth() const nothrow @property @nogc @safe pure | { 1| return Date.trustedCreate(year, month, lengthOfMonth); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonth(2000, Month.dec).endOfMonth == Date(2000, Month.dec, 31)); | } | | /// | ushort lengthOfMonth() const pure nothrow @property @nogc @safe | { 5| return maxDay(year, month); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonth(2000, Month.dec).lengthOfMonth == 31); | } | | /// 1| this(scope const(char)[] str) @safe pure @nogc | { 1| this = fromISOExtString(str); | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearMonth("1999-01"); 1| assert(ym.year == 1999); 1| assert(ym.month == 1); | } | | static bool fromISOExtString(C)(scope const(C)[] str, out YearMonth value) @safe pure @nogc | if (isSomeChar!C) | { | import mir.parse: fromString; 2| if (str.length < 7 || str[$-3] != '-') 0000000| return false; | 1| auto yearStr = str[0 .. $ - 3]; | 2| if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) 0000000| return false; | 1| short year; 1| ushort month; | 1| const ret = | fromString(str[$ - 2 .. $], month) 1| && fromString(yearStr, year); | 1| value = YearMonth(year, month); 1| return ret; | } | | static YearMonth fromISOExtString(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 1| YearMonth ret; 1| if (fromISOExtString(str, ret)) 1| return ret; | static immutable exc = new Exception("Invalid YearMonth string"); 0000000| throw exc; | } | |nothrow: | | /// | deprecated("please use addMonths instead") | @safe pure nothrow @nogc | ref YearMonth add(string units : "months")(long months) | { | auto years = months / 12; | months %= 12; | auto newMonth = month + months; | | if (months < 0) | { | if (newMonth < 1) | { | newMonth += 12; | --years; | } | } | else if (newMonth > 12) | { | newMonth -= 12; | ++years; | } | | year += years; | month = cast(Month) newMonth; | | return this; | } | | /// | version(mir_test_deprecated) | @safe unittest | { | auto ym0 = YearMonth(2020, Month.jan); | | ym0.add!"months"(1); | assert(ym0.year == 2020); | assert(ym0.month == Month.feb); | | auto ym1 = ym0.add!"months"(1); | assert(ym1.year == 2020); | assert(ym1.month == Month.mar); | | // also changes ym0 | assert(ym0.year == 2020); | assert(ym0.month == Month.mar); | | ym1.add!"months"(10); | assert(ym1.year == 2021); | assert(ym1.month == Month.jan); | | ym1.add!"months"(-13); | assert(ym1.year == 2019); | assert(ym1.month == Month.dec); | } | | /// | deprecated("please use addQuarters instead") | @safe pure nothrow @nogc | ref YearMonth add(string units : "quarters")(long quarters) | { | return add!"months"(quarters * 3); | } | | /// | version(mir_test_deprecated) | @safe unittest | { | auto yq0 = YearMonth(2020, Month.jan); | | yq0.add!"quarters"(1); | assert(yq0.year == 2020); | assert(yq0.month == Month.apr); | | auto yq1 = yq0.add!"quarters"(1); | assert(yq1.year == 2020); | assert(yq1.month == Month.jul); | | // also changes yq0 | assert(yq0.year == 2020); | assert(yq0.month == Month.jul); | | yq1.add!"quarters"(2); | assert(yq1.year == 2021); | assert(yq1.month == Month.jan); | | yq1.add!"quarters"(-5); | assert(yq1.year == 2019); | assert(yq1.month == Month.oct); | } | | /// | deprecated("please use addYears instead") | @safe pure nothrow @nogc | ref YearMonth add(string units : "years")(long years) | { | year += years; | return this; | } | | /// | version(mir_test_deprecated) | @safe unittest | { | auto ym0 = YearMonth(2020, Month.jan); | | ym0.add!"years"(1); | assert(ym0.year == 2021); | assert(ym0.month == Month.jan); | | auto ym1 = ym0.add!"years"(1); | assert(ym1.year == 2022); | assert(ym1.month == Month.jan); | | // also changes ym0 | assert(ym0.year == 2022); | assert(ym0.month == Month.jan); | } | | /// | @safe pure nothrow @nogc | YearMonth addMonths(long months) | { 23| auto newYear = year; 23| newYear += months / 12; 23| months %= 12; 23| auto newMonth = month; 23| newMonth += months; | 23| if (months < 0) | { 3| if (newMonth < 1) | { 2| newMonth += 12; 2| --newYear; | } | } 20| else if (newMonth > 12) | { 2| newMonth -= 12; 2| ++newYear; | } | 23| return YearMonth(newYear, newMonth); | } | | /// | version(mir_test) | @safe unittest | { 1| auto ym0 = YearMonth(2020, Month.jan); | 1| auto ym1 = ym0.addMonths(15); 1| assert(ym1.year == 2021); 1| assert(ym1.month == Month.apr); | 1| auto ym2 = ym1.addMonths(-6); 1| assert(ym2.year == 2020); 1| assert(ym2.month == Month.oct); | 1| auto ym3 = YearMonth(2020, Month.dec).addMonths(3); 1| assert(ym3.year == 2021); 1| assert(ym3.month == Month.mar); | | // ym0 is left unchagned 1| assert(ym0.year == 2020); 1| assert(ym0.month == Month.jan); | } | | /// | @safe pure nothrow @nogc | YearMonth addQuarters(long quarters) | { 3| return addMonths(quarters * 3); | } | | /// | version(mir_test) | @safe unittest | { 1| auto ym0 = YearMonth(2020, Month.jan); | 1| auto ym1 = ym0.addQuarters(5); 1| assert(ym1.year == 2021); 1| assert(ym1.month == Month.apr); | 1| auto ym2 = ym1.addQuarters(-2); 1| assert(ym2.year == 2020); 1| assert(ym2.month == Month.oct); | 1| auto ym3 = YearMonth(2020, Month.dec).addQuarters(1); 1| assert(ym3.year == 2021); 1| assert(ym3.month == Month.mar); | | // ym0 is left unchagned 1| assert(ym0.year == 2020); 1| assert(ym0.month == Month.jan); | } | | /// | @safe pure nothrow @nogc | YearMonth addYears(long years) | { 1| auto newYear = this.year; 1| newYear += years; 1| return YearMonth(newYear, month); | } | | /// | version(mir_test) | @safe unittest | { 1| auto ym0 = YearMonth(2020, Month.jan); | 1| auto ym1 = ym0.addYears(1); 1| assert(ym1.year == 2021); 1| assert(ym1.month == Month.jan); | | // leaves ym0 unchanged 1| assert(ym0.year == 2020); 1| assert(ym0.month == Month.jan); | } | | private void setMonthOfYear(bool useExceptions = false)(int days) | { 4| immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; | 8| bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear); | | static if (useExceptions) | { | if (dayOutOfRange) throw InvalidDay; | } | else | { 4| assert(!dayOutOfRange, "Invalid Day"); | } | 70| foreach (i; 1 .. lastDay.length) | { 22| if (days <= lastDay[i]) | { 4| month = cast(Month)(cast(int) Month.jan + i - 1); 4| return; | } | } | assert(0, "Invalid day of the year."); | } | | /// | version(mir_test) | @safe unittest | { 1| auto ym = YearMonth(2020, Month.feb); 1| ym.setMonthOfYear(10); 1| assert(ym.year == 2020); 1| assert(ym.month == Month.jan); 1| ym.setMonthOfYear(100); 1| assert(ym.year == 2020); 1| assert(ym.month == Month.apr); 1| ym.setMonthOfYear(200); 1| assert(ym.year == 2020); 1| assert(ym.month == Month.jul); 1| ym.setMonthOfYear(300); 1| assert(ym.year == 2020); 1| assert(ym.month == Month.oct); | } | | /// | int opBinary(string op : "-")(YearMonth rhs) | { | alias a = this; | alias b = rhs; | return (a.year - b.year) * 12 + a.month - b.month; | } | | /// | YearMonth opBinary(string op)(int rhs) | if (op == "+" || op == "-") | { | static if (op == "+") 15| return addMonths(rhs); | else | return addMonths(-rhs); | } | | /// | alias opBinaryRight(string op : "+") = opBinary!"+"; | | /// | ref YearMonth opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc | if (op == "+" || op == "-") | { | static if (op == "+") 1| this = addMonths(rhs); | else 1| this = addMonths(-rhs); 2| return this; | } | | /// | @safe pure @nogc nothrow | version(mir_test) | unittest | { 1| auto x = YearMonth(2020, Month.mar); 1| auto x1 = x + 1; 1| assert(x1 == YearMonth(2020, Month.apr)); 1| auto x2 = x + 2; 1| assert(x2 == YearMonth(2020, Month.may)); 1| auto x3 = x + 3; 1| assert(x3 == YearMonth(2020, Month.jun)); | } | | /// | @safe pure @nogc nothrow | version(mir_test) | unittest { 1| auto ym = YearMonth(2020, Month.mar); 1| ym += 2; 1| assert(ym == YearMonth(2020, Month.may)); 1| ym -= 1; 1| assert(ym == YearMonth(2020, Month.apr)); | } | | /// Get a slice of YearMonths | @safe pure @nogc nothrow | version(mir_test) | unittest { | import mir.ndslice.topology: iota; | | static immutable result1 = [YearMonth(2020, Month.mar), YearMonth(2020, Month.apr), YearMonth(2020, Month.may), YearMonth(2020, Month.jun)]; | static immutable result2 = [YearMonth(2020, Month.mar), YearMonth(2020, Month.may), YearMonth(2020, Month.jul), YearMonth(2020, Month.sep)]; | 1| auto ym = YearMonth(2020, Month.mar); | 1| auto x = ym + 4.iota!uint; 1| assert(x == result1); | | // every other month 1| auto y = ym + iota!uint([4], 0, 2); 1| assert(y == result2); | } | | /++ | Day of the year this $(LREF Date) is on. | +/ | @property int dayOfYear(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc | { 10| if (month >= Month.jan && month <= Month.dec) | { 5| immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; 5| auto monthIndex = month - Month.jan; | 5| return lastDay[monthIndex] + day(assumePeriod); | } | assert(0, "Invalid month."); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonth(1999, cast(Month) 1).dayOfYear == 1); 1| assert(YearMonth(1999, cast(Month) 12).dayOfYear(AssumePeriod.begin) == 335); 1| assert(YearMonth(1999, cast(Month) 12).dayOfYear(AssumePeriod.end) == 365); 1| assert(YearMonth(2000, cast(Month) 12).dayOfYear(AssumePeriod.begin) == 336); 1| assert(YearMonth(2000, cast(Month) 12).dayOfYear(AssumePeriod.end) == 366); | } | | /++ | Whether this $(LREF Date) is in a leap year. | +/ | @property bool isLeapYear() const @safe pure nothrow @nogc | { 15| return yearIsLeapYear(year); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearMonth(1999, cast(Month) 12).isLeapYear == false); 1| assert(YearMonth(2000, cast(Month) 12).isLeapYear == true); | } | | /++ | The last day in the month that this $(LREF Date) is in. | +/ | @property ubyte daysInMonth() const @safe pure nothrow @nogc | { 5| return maxDay(year, month); | } | | /// | version(mir_test) | @safe unittest | { 1| assert(YearMonth(2020, Month.dec).daysInMonth == 31); | } | | /++ | Whether the current year is a date in A.D. | +/ | @property bool isAD() const @safe pure nothrow @nogc | { 1| return year > 0; | } | | /// | version(mir_test) | @safe unittest | { 1| assert(YearMonth(2020, Month.jan).isAD == true); | } |} | |/// |enum Quarter : short |{ | /// | q1 = 1, | /// | q2, | /// | q3, | /// | q4, |} | |/++ |Returns the quarter for a given month. | |Params: | month = month | |+/ |@safe pure @nogc nothrow |Quarter quarter(Month month) |{ 23| return cast(Quarter)((cast(ubyte)month - 1) / 3 + 1); |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest { 1| assert(Month.jan.quarter == Quarter.q1); 1| assert(Month.feb.quarter == Quarter.q1); 1| assert(Month.mar.quarter == Quarter.q1); 1| assert(Month.apr.quarter == Quarter.q2); 1| assert(Month.may.quarter == Quarter.q2); 1| assert(Month.jun.quarter == Quarter.q2); 1| assert(Month.jul.quarter == Quarter.q3); 1| assert(Month.aug.quarter == Quarter.q3); 1| assert(Month.sep.quarter == Quarter.q3); 1| assert(Month.oct.quarter == Quarter.q4); 1| assert(Month.nov.quarter == Quarter.q4); 1| assert(Month.dec.quarter == Quarter.q4); |} | |private |@safe pure @nogc nothrow |Month monthInQuarter(Quarter quarter, AssumePeriod assumePeriod = AssumePeriod.begin) |{ 66| assert (assumePeriod == AssumePeriod.begin || assumePeriod == AssumePeriod.end); 49| return cast(Month) ((cast(byte)quarter - 1) * 3 + 1 + 2 * assumePeriod); |} | |version(mir_test) |@safe pure @nogc nothrow |unittest { 1| assert(Quarter.q1.monthInQuarter == Month.jan); 1| assert(Quarter.q1.monthInQuarter(AssumePeriod.end) == Month.mar); 1| assert(Quarter.q2.monthInQuarter == Month.apr); 1| assert(Quarter.q2.monthInQuarter(AssumePeriod.end) == Month.jun); 1| assert(Quarter.q3.monthInQuarter == Month.jul); 1| assert(Quarter.q3.monthInQuarter(AssumePeriod.end) == Month.sep); 1| assert(Quarter.q4.monthInQuarter == Month.oct); 1| assert(Quarter.q4.monthInQuarter(AssumePeriod.end) == Month.dec); |} | |/// Represents a date as a pair of years and quarters. |@serdeProxy!Timestamp |struct YearQuarter |{ | short year = 1; | Quarter quarter = Quarter.q1; | | /// | @property Month month(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc | { 41| return quarter.monthInQuarter(assumePeriod); | } | | /// | version (mir_test) | @safe unittest | { 1| auto yq = YearQuarter(2000, Quarter.q4); 1| assert(yq.month == 10); 1| assert(yq.month(AssumePeriod.end) == 12); | } | | /// | @property ubyte day(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc | { 23| final switch (assumePeriod) | { 11| case AssumePeriod.begin: 11| return 1; 12| case AssumePeriod.end: 12| return daysInMonth; | } | } | | /// | version (mir_test) | @safe unittest | { 1| auto yq = YearQuarter(2000, Quarter.q4); 1| assert(yq.day == 1); 1| assert(yq.day(AssumePeriod.end) == 31); | } | | /// | Timestamp timestamp() @safe pure nothrow @nogc const @property | { 2| return Timestamp(year, cast(ubyte)month); | } | | /// | version(mir_test) | unittest | { | import mir.timestamp; 1| auto yq = YearQuarter(2020, Quarter.q2); 1| auto ts = yq.timestamp; | } | | /// | alias opCast(T : Timestamp) = timestamp; | | /// | version(mir_test) | unittest | { | import mir.timestamp; 1| auto yq = YearQuarter(2020, Quarter.q2); 1| auto timestamp = cast(Timestamp) yq; | } | | /// 69| this(short year, Quarter quarter) @safe pure nothrow @nogc | { 69| this.year = year; 69| this.quarter = quarter; | } | | /// | version (mir_test) | @safe unittest | { 1| auto yq = YearQuarter(2000, Quarter.q4); | } | | /// 2| this(short year, Month month) @safe pure nothrow @nogc | { 2| this.year = year; 2| this.quarter = month.quarter; | } | | /// | version (mir_test) | @safe unittest | { 1| auto yq = YearQuarter(2000, Month.dec); | } | | /// 2| this(Date date) @safe pure nothrow @nogc | { 2| this = date.yearQuarter; | } | | /// | version (mir_test) | @safe unittest | { 1| auto yq = YearQuarter(Date(2000, Month.dec, 31)); | } | | /// 7| this(YearMonthDay yearMonthDay) @safe pure nothrow @nogc | { 7| with(yearMonthDay) this(year, quarter); | } | | /// | version (mir_test) | @safe unittest | { 1| auto ym = YearQuarter(YearMonthDay(2000, Month.dec, 31)); | } | | /// 1| this(YearMonth yearMonth) @safe pure nothrow @nogc | { 1| with(yearMonth) this(year, quarter); | } | | /// | version (mir_test) | @safe unittest | { 1| auto yq = YearQuarter(YearMonth(2000, Month.dec)); | } | | version(D_Exceptions) | /// 1| this(Timestamp timestamp) @safe pure @nogc | { 1| if (timestamp.precision != Timestamp.Precision.month) | { | static immutable exc = new Exception("YearMonth: invalid timestamp precision"); 0000000| throw exc; | } 1| with(timestamp) this(year, cast(Month)month); | } | | /// | version(mir_test) | @safe unittest | { | import mir.timestamp; 1| auto ts = Timestamp(2020, 4); 1| auto yq = YearQuarter(ts); | } | | /// | deprecated("please use addQuarters instead") | @safe pure nothrow @nogc | ref YearQuarter add(string units : "quarters")(long quarters) | { | auto years = quarters / 4; | quarters %= 4; | auto newQuarter = quarter + quarters; | | if (quarters < 0) | { | if (newQuarter < 1) | { | newQuarter += 4; | --years; | } | } | else if (newQuarter > 4) | { | newQuarter -= 4; | ++years; | } | | year += years; | quarter = cast(Quarter) newQuarter; | | return this; | } | | /// | version(mir_test_deprecated) | @safe unittest | { | auto yq0 = YearQuarter(2020, Quarter.q1); | | yq0.add!"quarters"(1); | assert(yq0.year == 2020); | assert(yq0.quarter == Quarter.q2); | | auto yq1 = yq0.add!"quarters"(1); | assert(yq1.year == 2020); | assert(yq1.quarter == Quarter.q3); | | // also changes yq0 | assert(yq0.year == 2020); | assert(yq0.quarter == Quarter.q3); | | yq1.add!"quarters"(2); | assert(yq1.year == 2021); | assert(yq1.quarter == Quarter.q1); | | yq1.add!"quarters"(-5); | assert(yq1.year == 2019); | assert(yq1.quarter == Quarter.q4); | } | | /// | deprecated("please use addYears instead") | @safe pure nothrow @nogc | ref YearQuarter add(string units : "years")(long years) | { | year += years; | return this; | } | | /// | version(mir_test_deprecated) | @safe unittest | { | auto yq0 = YearQuarter(2020, Quarter.q1); | | yq0.add!"years"(1); | assert(yq0.year == 2021); | assert(yq0.quarter == Quarter.q1); | | auto yq1 = yq0.add!"years"(1); | assert(yq1.year == 2022); | assert(yq1.quarter == Quarter.q1); | | // also changes yq0 | assert(yq0.year == 2022); | assert(yq0.quarter == Quarter.q1); | } | | /// | @safe pure nothrow @nogc | YearQuarter addQuarters(long quarters) | { 20| auto years = quarters / 4; 20| auto newYear = year; 20| newYear += years; 20| quarters %= 4; 20| auto newQuarter = quarter + quarters; | 20| if (quarters < 0) | { 2| if (newQuarter < 1) | { 1| newQuarter += 4; 1| --newYear; | } | } 18| else if (newQuarter > 4) | { 1| newQuarter -= 4; 1| ++newYear; | } | 20| return YearQuarter(newYear, cast(Quarter) newQuarter); | } | | /// | version(mir_test) | @safe unittest | { 1| auto yq0 = YearQuarter(2020, Quarter.q1); | 1| auto yq1 = yq0.addQuarters(5); 1| assert(yq1.year == 2021); 1| assert(yq1.quarter == Quarter.q2); | 1| auto yq2 = yq1.addQuarters(-2); 1| assert(yq2.year == 2020); 1| assert(yq2.quarter == Quarter.q4); | 1| auto yq3 = YearQuarter(2020, Quarter.q4).addQuarters(1); 1| assert(yq3.year == 2021); 1| assert(yq3.quarter == Quarter.q1); | | // yq0 is left unchagned 1| assert(yq0.year == 2020); 1| assert(yq0.quarter == Quarter.q1); | } | | /// | @safe pure nothrow @nogc | YearQuarter addYears(long years) | { 1| auto newYear = this.year; 1| newYear += years; 1| return YearQuarter(newYear, quarter); | } | | /// | version(mir_test) | @safe unittest | { 1| auto yq0 = YearQuarter(2020, Quarter.q1); | 1| auto yq1 = yq0.addYears(1); 1| assert(yq1.year == 2021); 1| assert(yq1.quarter == Quarter.q1); | | // leaves yq0 unchanged 1| assert(yq0.year == 2020); 1| assert(yq0.quarter == Quarter.q1); | } | | private void setQuarterOfYear(bool useExceptions = false)(int days) | { 5| immutable int[] lastDay = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap; | 10| bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear); | | static if (useExceptions) | { | if (dayOutOfRange) throw InvalidDay; | } | else | { 5| assert(!dayOutOfRange, "Invalid Day"); | } | 41| foreach (i; 1 .. lastDay.length) | { 12| if (days <= lastDay[i]) | { 5| quarter = cast(Quarter)(cast(int) Quarter.q1 + i - 1); 5| return; | } | } | assert(0, "Invalid day of the year."); | } | | /// | version(mir_test) | @safe unittest | { 1| auto yq = YearQuarter(2020, Quarter.q3); 1| yq.setQuarterOfYear(10); 1| assert(yq.year == 2020); 1| assert(yq.quarter == Quarter.q1); 1| yq.setQuarterOfYear(100); 1| assert(yq.year == 2020); 1| assert(yq.quarter == Quarter.q2); 1| yq.setQuarterOfYear(200); 1| assert(yq.year == 2020); 1| assert(yq.quarter == Quarter.q3); 1| yq.setQuarterOfYear(300); 1| assert(yq.year == 2020); 1| assert(yq.quarter == Quarter.q4); | } | | /// | int opBinary(string op : "-")(YearQuarter rhs) | { | alias a = this; | alias b = rhs; | return (a.year - b.year) * 4 + a.quarter - b.quarter; | } | | /// | YearQuarter opBinary(string op)(int rhs) | if (op == "+" || op == "-") | { | static if (op == "+") 15| return addQuarters(rhs); | else | return addQuarters(-rhs); | } | | /// | alias opBinaryRight(string op : "+") = opBinary!"+"; | | /// | ref YearQuarter opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc | if (op == "+" || op == "-") | { | static if (op == "+") 1| this = addQuarters(rhs); | else 1| this = addQuarters(-rhs); 2| return this; | } | | /// | @safe pure @nogc nothrow | version(mir_test) | unittest | { 1| auto x = YearQuarter(2020, Quarter.q1); 1| auto x1 = x + 1; 1| assert(x1 == YearQuarter(2020, Quarter.q2)); 1| auto x2 = x + 2; 1| assert(x2 == YearQuarter(2020, Quarter.q3)); 1| auto x3 = x + 3; 1| assert(x3 == YearQuarter(2020, Quarter.q4)); | } | | /// | @safe pure @nogc nothrow | version(mir_test) | unittest { 1| auto yq = YearQuarter(2020, Quarter.q1); 1| yq += 2; 1| assert(yq == YearQuarter(2020, Quarter.q3)); 1| yq -= 1; 1| assert(yq == YearQuarter(2020, Quarter.q2)); | } | | /// Get a slice of YearQuarters | @safe pure @nogc nothrow | version(mir_test) | unittest { | import mir.ndslice.topology: iota; | | static immutable result1 = [YearQuarter(2020, Quarter.q1), YearQuarter(2020, Quarter.q2), YearQuarter(2020, Quarter.q3), YearQuarter(2020, Quarter.q4)]; | static immutable result2 = [YearQuarter(2020, Quarter.q1), YearQuarter(2020, Quarter.q3), YearQuarter(2021, Quarter.q1), YearQuarter(2021, Quarter.q3)]; | 1| auto yq = YearQuarter(2020, Quarter.q1); | 1| auto x = yq + 4.iota!uint; 1| assert(x == result1); | | // every other quarter 1| auto y = yq + iota!uint([4], 0, 2); 1| assert(y == result2); | } | | /++ | Day of the quarter this $(LREF Date) is on. | +/ | @property int dayOfQuarter(AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) const @safe pure nothrow @nogc | { 30| if (quarter >= Quarter.q1 && quarter <= Quarter.q4) | { 15| immutable int[] lastDayQuarter = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap; 15| auto quarterIndex = quarter - Quarter.q1; 15| immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; 15| auto monthIndex = month(assumePeriodMonth) - Month.jan; | 15| return lastDay[monthIndex] - lastDayQuarter[quarterIndex] + day(assumePeriodDay); | } | assert(0, "Invalid quarter."); | } | | /// ditto | @property int dayOfQuarter(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc | { 5| return dayOfQuarter(assumePeriod, assumePeriod); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter == 1); 1| assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31); 1| assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter(AssumePeriod.end) == 90); | 1| assert(YearQuarter(2000, cast(Quarter) 1).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31); 1| assert(YearQuarter(2000, cast(Quarter) 1).dayOfQuarter(AssumePeriod.end) == 91); | 1| assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter == 1); 1| assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31); 1| assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter(AssumePeriod.end) == 92); | } | | /++ | Day of the year this $(LREF Date) is on. | +/ | @property int dayOfYear(AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) const @safe pure nothrow @nogc | { 14| if (quarter >= Quarter.q1 && quarter <= Quarter.q4) | { 7| immutable int[] lastDayQuarter = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap; 7| auto quarterIndex = quarter - Quarter.q1; | 7| return lastDayQuarter[quarterIndex] + dayOfQuarter(assumePeriodMonth, assumePeriodDay); | } | assert(0, "Invalid quarter."); | } | | /// ditto | @property int dayOfYear(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc | { 5| return dayOfYear(assumePeriod, assumePeriod); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearQuarter(1999, cast(Quarter) 1).dayOfYear == 1); 1| assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear == 274); 1| assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear(AssumePeriod.begin, AssumePeriod.end) == 304); 1| assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear(AssumePeriod.end) == 365); 1| assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear == 275); 1| assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear(AssumePeriod.begin, AssumePeriod.end) == 305); 1| assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear(AssumePeriod.end) == 366); | } | | /++ | Whether this $(LREF Date) is in a leap year. | +/ | @property bool isLeapYear() const @safe pure nothrow @nogc | { 49| return yearIsLeapYear(year); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(YearQuarter(1999, cast(Quarter) 4).isLeapYear == false); 1| assert(YearQuarter(2000, cast(Quarter) 4).isLeapYear == true); | } | | /++ | The last day in the month that this $(LREF Date) is in. | +/ | @property ubyte daysInMonth(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc | { 14| return maxDay(year, month(assumePeriod)); | } | | /// | version(mir_test) | @safe unittest | { 1| auto yq = YearQuarter(2020, Quarter.q3); 1| assert(yq.daysInMonth == 31); 1| assert(yq.daysInMonth(AssumePeriod.end) == 30); | } | | /++ | Whether the current year is a date in A.D. | +/ | @property bool isAD() const @safe pure nothrow @nogc | { 1| return year > 0; | } | | /// | version(mir_test) | @safe unittest | { 1| assert(YearQuarter(2020, Quarter.q1).isAD == true); | } |} | |/++ | Represents a date in the | $(HTTP en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic | Gregorian Calendar) ranging from 32,768 B.C. to 32,767 A.D. Positive years | are A.D. Non-positive years are B.C. | | Year, month, and day are kept separately internally so that $(D Date) is | optimized for calendar-based operations. | | $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian | leap year calculations for its entire length. As per | $(HTTP en.wikipedia.org/wiki/ISO_8601, ISO 8601), it treats 1 B.C. as | year 0, i.e. 1 B.C. is 0, 2 B.C. is -1, etc. Use $(LREF yearBC) to use B.C. | as a positive integer with 1 B.C. being the year prior to 1 A.D. | | Year 0 is a leap year. | +/ |extern(C++, "boost", "gregorian") |extern(C++, class) |@serdeProxy!YearMonthDay |struct date |{ |extern(D): |public: | | private enum _julianShift = 1_721_425; | | /// | uint toHash() @safe pure nothrow @nogc const scope | { 0000000| return _julianDay; | } | | /++ | Throws: | $(LREF DateTimeException) if the resulting | $(LREF Date) would not be valid. | | Params: | _year = Year of the Gregorian Calendar. Positive values are A.D. | Non-positive values are B.C. with year 0 being the year | prior to 1 A.D. | _month = Month of the year (January is 1). | _day = Day of the month. | +/ | pragma(inline, false) | static Date trustedCreate(int _year, int _month, int _day) @safe pure @nogc nothrow | { 609| Date ret; 609| immutable int[] lastDay = yearIsLeapYear(_year) ? lastDayLeap : lastDayNonLeap; 609| auto monthIndex = _month - Month.jan; | 609| const dayOfYear = lastDay[monthIndex] + _day; | 1218| if (_month >= Month.jan && _month <= Month.dec) {} else | assert(0, "Invalid month."); 609| if (_year > 0) | { 408| if (_year == 1) | { 21| ret._julianDay = dayOfYear; 21| goto R; | } | 387| int years = _year - 1; 387| auto days = (years / 400) * daysIn400Years; 387| years %= 400; | 387| days += (years / 100) * daysIn100Years; 387| years %= 100; | 387| days += (years / 4) * daysIn4Years; 387| years %= 4; | 387| days += years * daysInYear; | 387| days += dayOfYear; | 387| ret._julianDay = days; | } 201| else if (_year == 0) | { 28| ret._julianDay = dayOfYear - daysInLeapYear; | } | else | { 173| int years = _year; 173| auto days = (years / 400) * daysIn400Years; 173| years %= 400; | 173| days += (years / 100) * daysIn100Years; 173| years %= 100; | 173| days += (years / 4) * daysIn4Years; 173| years %= 4; | 173| if (years < 0) | { 123| days -= daysInLeapYear; 123| ++years; | 123| days += years * daysInYear; | 123| days -= daysInYear - dayOfYear; | } | else 50| days -= daysInLeapYear - dayOfYear; | 173| ret._julianDay = days; | } | R: 609| ret._julianDay += _julianShift; 609| return ret; | } | | /// | Timestamp timestamp() @safe pure nothrow @nogc const @property | { 2| return yearMonthDay.timestamp; | } | | /// | version(mir_test) | @safe unittest | { | import mir.timestamp; 1| auto d1 = Date(2020, Month.may, 15); 1| auto ts2 = d1.timestamp; | } | | version(D_Exceptions) | /// 1| this(Timestamp timestamp) @safe pure @nogc | { 1| if (timestamp.precision != Timestamp.Precision.day) | { | static immutable exc = new Exception("Date: invalid timestamp precision"); 0000000| throw exc; | } 1| with(timestamp) this(year, cast(Month)month, day); | } | | /// | version(mir_test) | @safe unittest | { | import mir.timestamp; 1| auto ts = Date(2020, Month.may, 15).timestamp; 1| auto d2 = Date(ts); | } | | version(D_Exceptions) | /// 1| this(scope const(char)[] str) @safe pure @nogc | { 1| this = fromString(str); | } | | /// | version(mir_test) | @safe unittest | { 1| auto d = Date("2020-12-31"); | } | | version(D_Exceptions) | /// 5| this(YearMonthDay ymd) @safe pure @nogc | { 5| with(ymd) this(year, month, day); | } | | /// | version(mir_test) | @safe unittest | { 1| auto d = date(YearMonthDay(2020, Month.may, 31)); | } | | version(D_Exceptions) | /// 6| this(YearQuarter yq, AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) @safe pure @nogc | { 6| with(yq) this(year, month(assumePeriodMonth), day(assumePeriodDay)); | } | | version(D_Exceptions) | /// 2| this(YearQuarter yq, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure @nogc | { 2| this(yq, assumePeriod, assumePeriod); | } | | /// | version(mir_test) | @safe unittest | { 1| auto d1 = date(YearQuarter(2020, Quarter.q2)); 1| auto d2 = date(YearQuarter(2020, Quarter.q2), AssumePeriod.end); | } | | version(D_Exceptions) | /// 6| this(YearMonth ym, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure @nogc nothrow | { 6| with(ym) this = trustedCreate(year, month, day(assumePeriod)); | } | | /// | version(mir_test) | @safe unittest | { 1| auto d1 = date(YearMonth(2020, Month.may)); 1| auto d2 = date(YearMonth(2020, Month.may), AssumePeriod.end); | } | | version(D_Exceptions) | /// 535| this(int _year, int _month, int _day) @safe pure @nogc | { 554| if (!valid!"months"(_month)) 2| throw InvalidMonth; 552| if (!valid!"days"(_year, cast(Month) _month, _day)) 17| throw InvalidDay; 535| this = trustedCreate(_year, _month, _day); | } | | /// | static bool fromYMD(int _year, int _month, int _day, out Date value) @safe pure nothrow @nogc | { 106| if (valid!"months"(_month) && valid!"days"(_year, cast(Month) _month, _day)) | { 53| value = trustedCreate(_year, _month, _day); 53| return true; | } 0000000| return false; | } | | version (mir_test) | @safe unittest | { | import std.exception : assertNotThrown; | // assert(Date(0, 12, 31) == Date.init); | | // Test A.D. 2| assertThrown!DateTimeException(Date(1, 0, 1)); 2| assertThrown!DateTimeException(Date(1, 1, 0)); 2| assertThrown!DateTimeException(Date(1999, 13, 1)); 2| assertThrown!DateTimeException(Date(1999, 1, 32)); 2| assertThrown!DateTimeException(Date(1999, 2, 29)); 2| assertThrown!DateTimeException(Date(2000, 2, 30)); 2| assertThrown!DateTimeException(Date(1999, 3, 32)); 2| assertThrown!DateTimeException(Date(1999, 4, 31)); 2| assertThrown!DateTimeException(Date(1999, 5, 32)); 2| assertThrown!DateTimeException(Date(1999, 6, 31)); 2| assertThrown!DateTimeException(Date(1999, 7, 32)); 2| assertThrown!DateTimeException(Date(1999, 8, 32)); 2| assertThrown!DateTimeException(Date(1999, 9, 31)); 2| assertThrown!DateTimeException(Date(1999, 10, 32)); 2| assertThrown!DateTimeException(Date(1999, 11, 31)); 2| assertThrown!DateTimeException(Date(1999, 12, 32)); | 2| assertNotThrown!DateTimeException(Date(1999, 1, 31)); 2| assertNotThrown!DateTimeException(Date(1999, 2, 28)); 2| assertNotThrown!DateTimeException(Date(2000, 2, 29)); 2| assertNotThrown!DateTimeException(Date(1999, 3, 31)); 2| assertNotThrown!DateTimeException(Date(1999, 4, 30)); 2| assertNotThrown!DateTimeException(Date(1999, 5, 31)); 2| assertNotThrown!DateTimeException(Date(1999, 6, 30)); 2| assertNotThrown!DateTimeException(Date(1999, 7, 31)); 2| assertNotThrown!DateTimeException(Date(1999, 8, 31)); 2| assertNotThrown!DateTimeException(Date(1999, 9, 30)); 2| assertNotThrown!DateTimeException(Date(1999, 10, 31)); 2| assertNotThrown!DateTimeException(Date(1999, 11, 30)); 2| assertNotThrown!DateTimeException(Date(1999, 12, 31)); | | // Test B.C. 2| assertNotThrown!DateTimeException(Date(0, 1, 1)); 2| assertNotThrown!DateTimeException(Date(-1, 1, 1)); 2| assertNotThrown!DateTimeException(Date(-1, 12, 31)); 2| assertNotThrown!DateTimeException(Date(-1, 2, 28)); 2| assertNotThrown!DateTimeException(Date(-4, 2, 29)); | 2| assertThrown!DateTimeException(Date(-1, 2, 29)); 2| assertThrown!DateTimeException(Date(-2, 2, 29)); 2| assertThrown!DateTimeException(Date(-3, 2, 29)); | } | | | /++ | Params: | day = Julian day. | +/ 64| this(int day) @safe pure nothrow @nogc | { 64| _julianDay = day; | } | | version (mir_test) | @safe unittest | { | import std.range : chain; | | // Test A.D. | // foreach (gd; chain(testGregDaysBC, testGregDaysAD)) | // assert(Date(gd.day) == gd.date); | } | | | /++ | Compares this $(LREF Date) with the given $(LREF Date). | | Returns: | $(BOOKTABLE, | $(TR $(TD this < rhs) $(TD < 0)) | $(TR $(TD this == rhs) $(TD 0)) | $(TR $(TD this > rhs) $(TD > 0)) | ) | +/ | int opCmp(Date rhs) const @safe pure nothrow @nogc | { 146| return this._julianDay - rhs._julianDay; | } | | version (mir_test) | @safe unittest | { | // Test A.D. | // assert(Date(0, 12, 31).opCmp(Date.init) == 0); | 1| assert(Date(1999, 1, 1).opCmp(Date(1999, 1, 1)) == 0); 1| assert(Date(1, 7, 1).opCmp(Date(1, 7, 1)) == 0); 1| assert(Date(1, 1, 6).opCmp(Date(1, 1, 6)) == 0); | 1| assert(Date(1999, 7, 1).opCmp(Date(1999, 7, 1)) == 0); 1| assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 6)) == 0); | 1| assert(Date(1, 7, 6).opCmp(Date(1, 7, 6)) == 0); | 1| assert(Date(1999, 7, 6).opCmp(Date(2000, 7, 6)) < 0); 1| assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 6)) > 0); 1| assert(Date(1999, 7, 6).opCmp(Date(1999, 8, 6)) < 0); 1| assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 6)) > 0); 1| assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 7)) < 0); 1| assert(Date(1999, 7, 7).opCmp(Date(1999, 7, 6)) > 0); | 1| assert(Date(1999, 8, 7).opCmp(Date(2000, 7, 6)) < 0); 1| assert(Date(2000, 8, 6).opCmp(Date(1999, 7, 7)) > 0); 1| assert(Date(1999, 7, 7).opCmp(Date(2000, 7, 6)) < 0); 1| assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 7)) > 0); 1| assert(Date(1999, 7, 7).opCmp(Date(1999, 8, 6)) < 0); 1| assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 7)) > 0); | | // Test B.C. 1| assert(Date(0, 1, 1).opCmp(Date(0, 1, 1)) == 0); 1| assert(Date(-1, 1, 1).opCmp(Date(-1, 1, 1)) == 0); 1| assert(Date(-1, 7, 1).opCmp(Date(-1, 7, 1)) == 0); 1| assert(Date(-1, 1, 6).opCmp(Date(-1, 1, 6)) == 0); | 1| assert(Date(-1999, 7, 1).opCmp(Date(-1999, 7, 1)) == 0); 1| assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 6)) == 0); | 1| assert(Date(-1, 7, 6).opCmp(Date(-1, 7, 6)) == 0); | 1| assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 6)) < 0); 1| assert(Date(-1999, 7, 6).opCmp(Date(-2000, 7, 6)) > 0); 1| assert(Date(-1999, 7, 6).opCmp(Date(-1999, 8, 6)) < 0); 1| assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 6)) > 0); 1| assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 7)) < 0); 1| assert(Date(-1999, 7, 7).opCmp(Date(-1999, 7, 6)) > 0); | 1| assert(Date(-2000, 8, 6).opCmp(Date(-1999, 7, 7)) < 0); 1| assert(Date(-1999, 8, 7).opCmp(Date(-2000, 7, 6)) > 0); 1| assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 7)) < 0); 1| assert(Date(-1999, 7, 7).opCmp(Date(-2000, 7, 6)) > 0); 1| assert(Date(-1999, 7, 7).opCmp(Date(-1999, 8, 6)) < 0); 1| assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 7)) > 0); | | // Test Both 1| assert(Date(-1999, 7, 6).opCmp(Date(1999, 7, 6)) < 0); 1| assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 6)) > 0); | 1| assert(Date(-1999, 8, 6).opCmp(Date(1999, 7, 6)) < 0); 1| assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 6)) > 0); | 1| assert(Date(-1999, 7, 7).opCmp(Date(1999, 7, 6)) < 0); 1| assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 7)) > 0); | 1| assert(Date(-1999, 8, 7).opCmp(Date(1999, 7, 6)) < 0); 1| assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 7)) > 0); | 1| assert(Date(-1999, 8, 6).opCmp(Date(1999, 6, 6)) < 0); 1| assert(Date(1999, 6, 8).opCmp(Date(-1999, 7, 6)) > 0); | 1| auto date = Date(1999, 7, 6); 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(date.opCmp(date) == 0); 1| assert(date.opCmp(cdate) == 0); 1| assert(date.opCmp(idate) == 0); 1| assert(cdate.opCmp(date) == 0); 1| assert(cdate.opCmp(cdate) == 0); 1| assert(cdate.opCmp(idate) == 0); 1| assert(idate.opCmp(date) == 0); 1| assert(idate.opCmp(cdate) == 0); 1| assert(idate.opCmp(idate) == 0); | } | | /++ | Day of the week this $(LREF Date) is on. | +/ | @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc | { 4| return getDayOfWeek(_julianDay); | } | | version (mir_test) | @safe unittest | { 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(cdate.dayOfWeek == DayOfWeek.tue); | static assert(!__traits(compiles, cdate.dayOfWeek = DayOfWeek.sun)); 1| assert(idate.dayOfWeek == DayOfWeek.tue); | static assert(!__traits(compiles, idate.dayOfWeek = DayOfWeek.sun)); | } | | /++ | The Xth day of the Gregorian Calendar that this $(LREF Date) is on. | +/ | @property int dayOfGregorianCal() const @safe pure nothrow @nogc | { 220| return _julianDay - _julianShift; | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date(1, 1, 1).dayOfGregorianCal == 1); 1| assert(Date(1, 12, 31).dayOfGregorianCal == 365); 1| assert(Date(2, 1, 1).dayOfGregorianCal == 366); | 1| assert(Date(0, 12, 31).dayOfGregorianCal == 0); 1| assert(Date(0, 1, 1).dayOfGregorianCal == -365); 1| assert(Date(-1, 12, 31).dayOfGregorianCal == -366); | 1| assert(Date(2000, 1, 1).dayOfGregorianCal == 730_120); 1| assert(Date(2010, 12, 31).dayOfGregorianCal == 734_137); | } | | version (mir_test) | @safe unittest | { | import std.range : chain; | 452| foreach (gd; chain(testGregDaysBC, testGregDaysAD)) 150| assert(gd.date.dayOfGregorianCal == gd.day); | 1| auto date = Date(1999, 7, 6); 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(date.dayOfGregorianCal == 729_941); 1| assert(cdate.dayOfGregorianCal == 729_941); 1| assert(idate.dayOfGregorianCal == 729_941); | } | | /++ | The Xth day of the Gregorian Calendar that this $(LREF Date) is on. | | Params: | day = The day of the Gregorian Calendar to set this $(LREF Date) to. | +/ | @property void dayOfGregorianCal(int day) @safe pure nothrow @nogc | { 9| this = Date(day + _julianShift); | } | | /// | version (mir_test) | @safe unittest | { 1| auto date = Date.init; 1| date.dayOfGregorianCal = 1; 1| assert(date == Date(1, 1, 1)); | 1| date.dayOfGregorianCal = 365; 1| assert(date == Date(1, 12, 31)); | 1| date.dayOfGregorianCal = 366; 1| assert(date == Date(2, 1, 1)); | 1| date.dayOfGregorianCal = 0; 1| assert(date == Date(0, 12, 31)); | 1| date.dayOfGregorianCal = -365; 1| assert(date == Date(-0, 1, 1)); | 1| date.dayOfGregorianCal = -366; 1| assert(date == Date(-1, 12, 31)); | 1| date.dayOfGregorianCal = 730_120; 1| assert(date == Date(2000, 1, 1)); | 1| date.dayOfGregorianCal = 734_137; 1| assert(date == Date(2010, 12, 31)); | } | | version (mir_test) | @safe unittest | { 1| auto date = Date(1999, 7, 6); 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| date.dayOfGregorianCal = 187; 1| assert(date.dayOfGregorianCal == 187); | static assert(!__traits(compiles, cdate.dayOfGregorianCal = 187)); | static assert(!__traits(compiles, idate.dayOfGregorianCal = 187)); | } | | private enum uint _startDict = Date(1900, 1, 1)._julianDay; // [ | private enum uint _endDict = Date(2040, 1, 1)._julianDay; // ) | static immutable _dictYMD = () | { | YearMonthDay[Date._endDict - Date._startDict] dict; | foreach (uint i; 0 .. dict.length) | dict[i] = Date(i + Date._startDict).yearMonthDayImpl; | return dict; | }(); | | /// | YearMonthDay yearMonthDay() const @safe pure nothrow @nogc @property | { 126| uint day = _julianDay; 126| if (day < _endDict) | { | import mir.checkedint: subu; 112| bool overflow; 112| auto index = subu(day, _startDict, overflow); 112| if (!overflow) 70| return _dictYMD[index]; | } 56| return yearMonthDayImpl; | } | | /// | YearQuarter yearQuarter() const @safe pure nothrow @nogc @property | { 7| uint day = _julianDay; 7| if (day < _endDict) | { 6| return yearMonthDay().YearQuarter; | } 1| return yearQuarterImpl; | } | | /// | version(mir_test) | @safe unittest | { 1| auto d = Date(2020, Month.may, 31); 1| auto yq = d.yearQuarter; 1| assert(yq.year == 2020); 1| assert(yq.quarter == Quarter.q2); | } | | // | version(mir_test) | @safe unittest | { 1| auto d = Date(2050, Month.dec, 31); 1| auto yq = d.yearQuarter; 1| assert(yq.year == 2050); 1| assert(yq.quarter == Quarter.q4); | } | | /// | short year() const @safe pure nothrow @nogc @property | { 2| return yearQuarter.year; | } | | /// | Quarter quarter() const @safe pure nothrow @nogc @property | { 1| return yearQuarter.quarter; | } | | /// | Month month() const @safe pure nothrow @nogc @property | { 1| return yearMonthDay.month; | } | | /// | ubyte day() const @safe pure nothrow @nogc @property | { 1| return yearMonthDay.day; | } | | /// | version(mir_test) | @safe unittest | { 1| auto d = Date(2020, Month.may, 31); 1| assert(d.year == 2020); 1| assert(d.quarter == Quarter.q2); 1| assert(d.month == Month.may); 1| assert(d.day == 31); | } | | pragma(inline, false) | YearMonthDay yearMonthDayImpl() const @safe pure nothrow @nogc @property | { 56| YearMonthDay ymd; 56| int days = dayOfGregorianCal; | with(ymd) 56| if (days > 0) | { 17| int years = (days / daysIn400Years) * 400 + 1; 17| days %= daysIn400Years; | | { 17| immutable tempYears = days / daysIn100Years; | 17| if (tempYears == 4) | { 0000000| years += 300; 0000000| days -= daysIn100Years * 3; | } | else | { 17| years += tempYears * 100; 17| days %= daysIn100Years; | } | } | 17| years += (days / daysIn4Years) * 4; 17| days %= daysIn4Years; | | { 17| immutable tempYears = days / daysInYear; | 17| if (tempYears == 4) | { 0000000| years += 3; 0000000| days -= daysInYear * 3; | } | else | { 17| years += tempYears; 17| days %= daysInYear; | } | } | 17| if (days == 0) | { 1| year = cast(short)(years - 1); 1| month = Month.dec; 1| day = 31; | } | else | { 16| year = cast(short) years; | 16| setDayOfYear(days); | } | } 78| else if (days <= 0 && -days < daysInLeapYear) | { 6| year = 0; | 6| setDayOfYear(daysInLeapYear + days); | } | else | { 33| days += daysInLeapYear - 1; 33| int years = (days / daysIn400Years) * 400 - 1; 33| days %= daysIn400Years; | | { 33| immutable tempYears = days / daysIn100Years; | 33| if (tempYears == -4) | { 0000000| years -= 300; 0000000| days += daysIn100Years * 3; | } | else | { 33| years += tempYears * 100; 33| days %= daysIn100Years; | } | } | 33| years += (days / daysIn4Years) * 4; 33| days %= daysIn4Years; | | { 33| immutable tempYears = days / daysInYear; | 33| if (tempYears == -4) | { 0000000| years -= 3; 0000000| days += daysInYear * 3; | } | else | { 33| years += tempYears; 33| days %= daysInYear; | } | } | 33| if (days == 0) | { 1| year = cast(short)(years + 1); 1| month = Month.jan; 1| day = 1; | } | else | { 32| year = cast(short) years; 32| immutable newDoY = (yearIsLeapYear(year) ? daysInLeapYear : daysInYear) + days + 1; | 32| setDayOfYear(newDoY); | } | } 56| return ymd; | } | | | | pragma(inline, false) | YearQuarter yearQuarterImpl() const @safe pure nothrow @nogc @property | { 2| YearQuarter yq; 2| int days = dayOfGregorianCal; | with(yq) 2| if (days > 0) | { 2| int years = (days / daysIn400Years) * 400 + 1; 2| days %= daysIn400Years; | | { 2| immutable tempYears = days / daysIn100Years; | 2| if (tempYears == 4) | { 0000000| years += 300; 0000000| days -= daysIn100Years * 3; | } | else | { 2| years += tempYears * 100; 2| days %= daysIn100Years; | } | } | 2| years += (days / daysIn4Years) * 4; 2| days %= daysIn4Years; | | { 2| immutable tempYears = days / daysInYear; | 2| if (tempYears == 4) | { 0000000| years += 3; 0000000| days -= daysInYear * 3; | } | else | { 2| years += tempYears; 2| days %= daysInYear; | } | } | 2| if (days == 0) | { 1| year = cast(short)(years - 1); 1| quarter = Quarter.q4; | } | else | { 1| year = cast(short) years; 1| setQuarterOfYear(days); | } | } 0000000| else if (days <= 0 && -days < daysInLeapYear) | { 0000000| year = 0; | 0000000| setQuarterOfYear(daysInLeapYear + days); | } | else | { 0000000| days += daysInLeapYear - 1; 0000000| int years = (days / daysIn400Years) * 400 - 1; 0000000| days %= daysIn400Years; | | { 0000000| immutable tempYears = days / daysIn100Years; | 0000000| if (tempYears == -4) | { 0000000| years -= 300; 0000000| days += daysIn100Years * 3; | } | else | { 0000000| years += tempYears * 100; 0000000| days %= daysIn100Years; | } | } | 0000000| years += (days / daysIn4Years) * 4; 0000000| days %= daysIn4Years; | | { 0000000| immutable tempYears = days / daysInYear; | 0000000| if (tempYears == -4) | { 0000000| years -= 3; 0000000| days += daysInYear * 3; | } | else | { 0000000| years += tempYears; 0000000| days %= daysInYear; | } | } | 0000000| if (days == 0) | { 0000000| year = cast(short)(years + 1); 0000000| quarter = Quarter.q2; | } | else | { 0000000| year = cast(short) years; 0000000| immutable newDoY = (yearIsLeapYear(year) ? daysInLeapYear : daysInYear) + days + 1; | 0000000| setQuarterOfYear(newDoY); | } | } 2| return yq; | } | | version(mir_test) | @safe unittest | { 1| auto d = date(2020, Month.may, 31); 1| auto yq = d.yearQuarterImpl; | } | | /++ | $(LREF Date) for the last day in the quarter that this $(LREF Date) is in. | +/ | @property Date endOfQuarter() const @safe pure nothrow @nogc | { 4| with(yearMonthDay) | { 4| int d = _julianDay - day; 4| final switch (month) with(Month) | { 3| case jan: d += maxDay(year, jan); goto case; 9| case feb: d += maxDay(year, feb); goto case; 9| case mar: d += maxDay(year, mar); break; | 0000000| case apr: d += maxDay(year, apr); goto case; 0000000| case may: d += maxDay(year, may); goto case; 3| case jun: d += maxDay(year, jun); break; | 0000000| case jul: d += maxDay(year, jul); goto case; 0000000| case aug: d += maxDay(year, aug); goto case; 0000000| case sep: d += maxDay(year, sep); break; | 0000000| case oct: d += maxDay(year, oct); goto case; 0000000| case nov: d += maxDay(year, nov); goto case; 0000000| case dec: d += maxDay(year, dec); break; | } 4| return Date(d); | } | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date(1999, 1, 6).endOfQuarter == Date(1999, 3, 31)); 1| assert(Date(1999, 2, 7).endOfQuarter == Date(1999, 3, 31)); 1| assert(Date(2000, 2, 7).endOfQuarter == Date(2000, 3, 31)); 1| assert(Date(2000, 6, 4).endOfQuarter == Date(2000, 6, 30)); | } | | /++ | $(LREF Date) for the last day in the month that this $(LREF Date) is in. | +/ | @property Date endOfMonth() const @safe pure nothrow @nogc | { 30| with(yearMonthDay) 30| return Date(_julianDay + maxDay(year, month) - day); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date(1999, 1, 6).endOfMonth == Date(1999, 1, 31)); 1| assert(Date(1999, 2, 7).endOfMonth == Date(1999, 2, 28)); 1| assert(Date(2000, 2, 7).endOfMonth == Date(2000, 2, 29)); 1| assert(Date(2000, 6, 4).endOfMonth == Date(2000, 6, 30)); | } | | version (mir_test) | @safe unittest | { | // Test A.D. 1| assert(Date(1999, 1, 1).endOfMonth == Date(1999, 1, 31)); 1| assert(Date(1999, 2, 1).endOfMonth == Date(1999, 2, 28)); 1| assert(Date(2000, 2, 1).endOfMonth == Date(2000, 2, 29)); 1| assert(Date(1999, 3, 1).endOfMonth == Date(1999, 3, 31)); 1| assert(Date(1999, 4, 1).endOfMonth == Date(1999, 4, 30)); 1| assert(Date(1999, 5, 1).endOfMonth == Date(1999, 5, 31)); 1| assert(Date(1999, 6, 1).endOfMonth == Date(1999, 6, 30)); 1| assert(Date(1999, 7, 1).endOfMonth == Date(1999, 7, 31)); 1| assert(Date(1999, 8, 1).endOfMonth == Date(1999, 8, 31)); 1| assert(Date(1999, 9, 1).endOfMonth == Date(1999, 9, 30)); 1| assert(Date(1999, 10, 1).endOfMonth == Date(1999, 10, 31)); 1| assert(Date(1999, 11, 1).endOfMonth == Date(1999, 11, 30)); 1| assert(Date(1999, 12, 1).endOfMonth == Date(1999, 12, 31)); | | // Test B.C. 1| assert(Date(-1999, 1, 1).endOfMonth == Date(-1999, 1, 31)); 1| assert(Date(-1999, 2, 1).endOfMonth == Date(-1999, 2, 28)); 1| assert(Date(-2000, 2, 1).endOfMonth == Date(-2000, 2, 29)); 1| assert(Date(-1999, 3, 1).endOfMonth == Date(-1999, 3, 31)); 1| assert(Date(-1999, 4, 1).endOfMonth == Date(-1999, 4, 30)); 1| assert(Date(-1999, 5, 1).endOfMonth == Date(-1999, 5, 31)); 1| assert(Date(-1999, 6, 1).endOfMonth == Date(-1999, 6, 30)); 1| assert(Date(-1999, 7, 1).endOfMonth == Date(-1999, 7, 31)); 1| assert(Date(-1999, 8, 1).endOfMonth == Date(-1999, 8, 31)); 1| assert(Date(-1999, 9, 1).endOfMonth == Date(-1999, 9, 30)); 1| assert(Date(-1999, 10, 1).endOfMonth == Date(-1999, 10, 31)); 1| assert(Date(-1999, 11, 1).endOfMonth == Date(-1999, 11, 30)); 1| assert(Date(-1999, 12, 1).endOfMonth == Date(-1999, 12, 31)); | 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); | static assert(!__traits(compiles, cdate.endOfMonth = Date(1999, 7, 30))); | static assert(!__traits(compiles, idate.endOfMonth = Date(1999, 7, 30))); | } | | /// | int opBinary(string op : "-")(Date rhs) const | { 6| return _julianDay - rhs._julianDay; | } | | /// | Date opBinary(string op : "+")(int rhs) const | { 21| return Date(_julianDay + rhs); | } | | /// | Date opBinaryRight(string op : "+")(int rhs) const | { | return Date(_julianDay + rhs); | } | | /// | Date opBinary(string op : "-")(int rhs) const | { | return Date(_julianDay - rhs); | } | | /// | ref Date opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc | if (op == "+" || op == "-") | { | static if (op == "+") 1| this._addDays(rhs); | else 1| this._addDays(-rhs); 2| return this; | } | | /// | @safe pure @nogc | version(mir_test) | unittest { 1| auto d = Date(2020, 1, 1); 1| d += 2; 1| assert(d == Date(2020, 1, 3)); 1| d -= 1; 1| assert(d == Date(2020, 1, 2)); | } | | | /// Get a slice of Dates | @safe pure @nogc | version(mir_test) | unittest { | import mir.ndslice.topology: iota, map; | | static immutable result1 = [Date(2020, Month.mar, 1), Date(2020, Month.mar, 2), Date(2020, Month.mar, 3), Date(2020, Month.mar, 4)]; | static immutable result2 = [Date(2020, Month.mar, 1), Date(2020, Month.mar, 3), Date(2020, Month.mar, 5), Date(2020, Month.mar, 7)]; | static immutable result3 = [Date(2020, Month.mar, 1), Date(2020, Month.apr, 1), Date(2020, Month.may, 1), Date(2020, Month.jun, 1)]; | static immutable result4 = [Date(2020, Month.mar, 1), Date(2020, Month.jun, 1), Date(2020, Month.sep, 1), Date(2020, Month.dec, 1)]; | static immutable result5 = [Date(2020, Month.mar, 1), Date(2021, Month.mar, 1), Date(2022, Month.mar, 1), Date(2023, Month.mar, 1)]; | 1| auto d = Date(2020, Month.mar, 1); | 1| auto x = d + 4.iota!uint; 1| assert(x == result1); | | // every other date 1| auto y = d + iota!uint([4], 0, 2); 1| assert(y == result2); | | // every month 1| auto z = (d.YearMonth + 4.iota!uint).map!Date; 1| assert(z == result3); | | // every quarter 5| auto a = (d.YearQuarter + 4.iota!uint).map!(a => a.Date(AssumePeriod.end, AssumePeriod.begin)); 1| assert(a == result4); | | // every year 5| auto b = (d.year + 4.iota!uint).map!(a => YearMonthDay(cast(short) a, Month.mar, 1).Date); 1| assert(b == result5); | } | | const nothrow @nogc pure @safe | Date add(string units)(long amount, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) | { | with(yearMonthDay.add!units(amount)) return trustedCreate(year, month, day); | } | | /++ | The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this | $(LREF Date) at noon (since the Julian day changes at noon). | +/ | @property int julianDay() const @safe pure nothrow @nogc | { 14| return _julianDay; | } | | version (mir_test) | @safe unittest | { 1| assert(Date(-4713, 11, 24).julianDay == 0); 1| assert(Date(0, 12, 31).julianDay == _julianShift); 1| assert(Date(1, 1, 1).julianDay == 1_721_426); 1| assert(Date(1582, 10, 15).julianDay == 2_299_161); 1| assert(Date(1858, 11, 17).julianDay == 2_400_001); 1| assert(Date(1982, 1, 4).julianDay == 2_444_974); 1| assert(Date(1996, 3, 31).julianDay == 2_450_174); 1| assert(Date(2010, 8, 24).julianDay == 2_455_433); | 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(cdate.julianDay == 2_451_366); 1| assert(idate.julianDay == 2_451_366); | } | | | /++ | The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for | any time on this date (since, the modified Julian day changes at | midnight). | +/ | @property long modJulianDay() const @safe pure nothrow @nogc | { 4| return julianDay - 2_400_001; | } | | version (mir_test) | @safe unittest | { 1| assert(Date(1858, 11, 17).modJulianDay == 0); 1| assert(Date(2010, 8, 24).modJulianDay == 55_432); | 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(cdate.modJulianDay == 51_365); 1| assert(idate.modJulianDay == 51_365); | } | | version(D_BetterC){} else | private string toStringImpl(alias fun)() const @safe pure nothrow | { | import mir.appender: UnsafeArrayBuffer; 60| char[16] buffer = void; 60| auto w = UnsafeArrayBuffer!char(buffer); 60| fun(w); 60| return w.data.idup; | } | | version(D_BetterC){} else | /++ | Converts this $(LREF Date) to a string with the format `YYYYMMDD`. | If `writer` is set, the resulting string will be written directly | to it. | | Returns: | A `string` when not using an output range; `void` otherwise. | +/ | string toISOString() const @safe pure nothrow | { 18| return toStringImpl!toISOString; | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date.init.toISOString == "null"); 1| assert(Date(2010, 7, 4).toISOString == "20100704"); 1| assert(Date(1998, 12, 25).toISOString == "19981225"); 1| assert(Date(0, 1, 5).toISOString == "00000105"); 1| assert(Date(-4, 1, 5).toISOString == "-00040105", Date(-4, 1, 5).toISOString()); | } | | version (mir_test) | @safe unittest | { | // Test A.D. 1| assert(Date(9, 12, 4).toISOString == "00091204"); 1| assert(Date(99, 12, 4).toISOString == "00991204"); 1| assert(Date(999, 12, 4).toISOString == "09991204"); 1| assert(Date(9999, 7, 4).toISOString == "99990704"); 1| assert(Date(10000, 10, 20).toISOString == "+100001020"); | | // Test B.C. 1| assert(Date(0, 12, 4).toISOString == "00001204"); 1| assert(Date(-9, 12, 4).toISOString == "-00091204"); 1| assert(Date(-99, 12, 4).toISOString == "-00991204"); 1| assert(Date(-999, 12, 4).toISOString == "-09991204"); 1| assert(Date(-9999, 7, 4).toISOString == "-99990704"); 1| assert(Date(-10000, 10, 20).toISOString == "-100001020"); | 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(cdate.toISOString == "19990706"); 1| assert(idate.toISOString == "19990706"); | } | | /// ditto | void toISOString(W)(scope ref W w) const scope | if (isOutputRange!(W, char)) | { | import mir.format: printZeroPad; 20| if(this == Date.init) | { 1| w.put("null"); 1| return; | } 19| with(yearMonthDay) | { 19| if (year >= 10_000) 1| w.put('+'); 19| w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); 19| w.printZeroPad(cast(uint)month, 2); 19| w.printZeroPad(day, 2); | } | } | | version (mir_test) | @safe unittest | { 1| auto date = Date(1999, 7, 6); 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(date.toString); 1| assert(cdate.toString); 1| assert(idate.toString); | } | | version(D_BetterC){} else | /++ | Converts this $(LREF Date) to a string with the format `YYYY-MM-DD`. | If `writer` is set, the resulting string will be written directly | to it. | | Returns: | A `string` when not using an output range; `void` otherwise. | +/ | string toISOExtString() const @safe pure nothrow | { 24| return toStringImpl!toISOExtString; | } | | ///ditto | alias toString = toISOExtString; | | /// | version (mir_test) | @safe unittest | { 1| assert(Date.init.toISOExtString == "null"); 1| assert(Date(2010, 7, 4).toISOExtString == "2010-07-04"); 1| assert(Date(1998, 12, 25).toISOExtString == "1998-12-25"); 1| assert(Date(0, 1, 5).toISOExtString == "0000-01-05"); 1| assert(Date(-4, 1, 5).toISOExtString == "-0004-01-05"); | } | | version (mir_test) | @safe pure unittest | { | import std.array : appender; | 1| auto w = appender!(char[])(); 1| Date(2010, 7, 4).toISOString(w); 1| assert(w.data == "20100704"); 1| w.clear(); 1| Date(1998, 12, 25).toISOString(w); 1| assert(w.data == "19981225"); | } | | version (mir_test) | @safe unittest | { | // Test A.D. 1| assert(Date(9, 12, 4).toISOExtString == "0009-12-04"); 1| assert(Date(99, 12, 4).toISOExtString == "0099-12-04"); 1| assert(Date(999, 12, 4).toISOExtString == "0999-12-04"); 1| assert(Date(9999, 7, 4).toISOExtString == "9999-07-04"); 1| assert(Date(10000, 10, 20).toISOExtString == "+10000-10-20"); | | // Test B.C. 1| assert(Date(0, 12, 4).toISOExtString == "0000-12-04"); 1| assert(Date(-9, 12, 4).toISOExtString == "-0009-12-04"); 1| assert(Date(-99, 12, 4).toISOExtString == "-0099-12-04"); 1| assert(Date(-999, 12, 4).toISOExtString == "-0999-12-04"); 1| assert(Date(-9999, 7, 4).toISOExtString == "-9999-07-04"); 1| assert(Date(-10000, 10, 20).toISOExtString == "-10000-10-20"); | 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(cdate.toISOExtString == "1999-07-06"); 1| assert(idate.toISOExtString == "1999-07-06"); | } | | /// ditto | void toISOExtString(W)(scope ref W w) const scope | if (isOutputRange!(W, char)) | { | import mir.format: printZeroPad; 26| if(this == Date.init) | { 1| w.put("null"); 1| return; | } 25| with(yearMonthDay) | { 25| if (year >= 10_000) 1| w.put('+'); 25| w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); 25| w.put('-'); 25| w.printZeroPad(cast(uint)month, 2); 25| w.put('-'); 25| w.printZeroPad(day, 2); | } | } | | version (mir_test) | @safe pure unittest | { | import std.array : appender; | 1| auto w = appender!(char[])(); 1| Date(2010, 7, 4).toISOExtString(w); 1| assert(w.data == "2010-07-04"); 1| w.clear(); 1| Date(-4, 1, 5).toISOExtString(w); 1| assert(w.data == "-0004-01-05"); | } | | version(D_BetterC){} else | /++ | Converts this $(LREF Date) to a string with the format `YYYY-Mon-DD`. | If `writer` is set, the resulting string will be written directly | to it. | | Returns: | A `string` when not using an output range; `void` otherwise. | +/ | string toSimpleString() const @safe pure nothrow | { 18| return toStringImpl!toSimpleString; | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date.init.toSimpleString == "null"); 1| assert(Date(2010, 7, 4).toSimpleString == "2010-Jul-04"); 1| assert(Date(1998, 12, 25).toSimpleString == "1998-Dec-25"); 1| assert(Date(0, 1, 5).toSimpleString == "0000-Jan-05"); 1| assert(Date(-4, 1, 5).toSimpleString == "-0004-Jan-05"); | } | | version (mir_test) | @safe unittest | { | // Test A.D. 1| assert(Date(9, 12, 4).toSimpleString == "0009-Dec-04"); 1| assert(Date(99, 12, 4).toSimpleString == "0099-Dec-04"); 1| assert(Date(999, 12, 4).toSimpleString == "0999-Dec-04"); 1| assert(Date(9999, 7, 4).toSimpleString == "9999-Jul-04"); 1| assert(Date(10000, 10, 20).toSimpleString == "+10000-Oct-20"); | | // Test B.C. 1| assert(Date(0, 12, 4).toSimpleString == "0000-Dec-04"); 1| assert(Date(-9, 12, 4).toSimpleString == "-0009-Dec-04"); 1| assert(Date(-99, 12, 4).toSimpleString == "-0099-Dec-04"); 1| assert(Date(-999, 12, 4).toSimpleString == "-0999-Dec-04"); 1| assert(Date(-9999, 7, 4).toSimpleString == "-9999-Jul-04"); 1| assert(Date(-10000, 10, 20).toSimpleString == "-10000-Oct-20"); | 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); 1| assert(cdate.toSimpleString == "1999-Jul-06"); 1| assert(idate.toSimpleString == "1999-Jul-06"); | } | | /// ditto | void toSimpleString(W)(scope ref W w) const scope | if (isOutputRange!(W, char)) | { | import mir.format: printZeroPad; 20| if(this == Date.init) | { 1| w.put("null"); 1| return; | } 19| with(yearMonthDay) | { 19| if (year >= 10_000) 1| w.put('+'); 19| w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); 19| w.put('-'); 19| w.put(month.monthToString); 19| w.put('-'); 19| w.printZeroPad(day, 2); | } | } | | version (mir_test) | @safe pure unittest | { | import std.array : appender; | 1| auto w = appender!(char[])(); 1| Date(9, 12, 4).toSimpleString(w); 1| assert(w.data == "0009-Dec-04"); 1| w.clear(); 1| Date(-10000, 10, 20).toSimpleString(w); 1| assert(w.data == "-10000-Oct-20"); | } | | /++ | Creates a $(LREF Date) from a string with the format YYYYMMDD. | | Params: | str = A string formatted in the way that $(LREF .date.toISOString) formats dates. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format or if the resulting $(LREF Date) would not | be valid. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting date for single argument overdload. | +/ | static bool fromISOString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc | if (isSomeChar!C) | { | import mir.parse: fromString; | 66| if (str.length < 8) 5| return false; | 61| auto yearStr = str[0 .. $ - 4]; | 117| if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) 29| return false; | 64| uint day, month; 32| int year; | 32| return | fromString(str[$ - 2 .. $], day) 28| && fromString(str[$ - 4 .. $ - 2], month) 18| && fromString(yearStr, year) 18| && fromYMD(year, month, day, value); | } | | /// ditto | static Date fromISOString(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 64| Date ret; 64| if (fromISOString(str, ret)) 17| return ret; 47| throw InvalidISOString; | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date.fromISOString("20100704") == Date(2010, 7, 4)); 1| assert(Date.fromISOString("19981225") == Date(1998, 12, 25)); 1| assert(Date.fromISOString("00000105") == Date(0, 1, 5)); 1| assert(Date.fromISOString("-00040105") == Date(-4, 1, 5)); | } | | version (mir_test) | @safe unittest | { 2| assertThrown!DateTimeException(Date.fromISOString("")); 2| assertThrown!DateTimeException(Date.fromISOString("990704")); 2| assertThrown!DateTimeException(Date.fromISOString("0100704")); 2| assertThrown!DateTimeException(Date.fromISOString("2010070")); 2| assertThrown!DateTimeException(Date.fromISOString("120100704")); 2| assertThrown!DateTimeException(Date.fromISOString("-0100704")); 2| assertThrown!DateTimeException(Date.fromISOString("+0100704")); 2| assertThrown!DateTimeException(Date.fromISOString("2010070a")); 2| assertThrown!DateTimeException(Date.fromISOString("20100a04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010a704")); | 2| assertThrown!DateTimeException(Date.fromISOString("99-07-04")); 2| assertThrown!DateTimeException(Date.fromISOString("010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-07-0")); 2| assertThrown!DateTimeException(Date.fromISOString("12010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOString("-010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOString("+010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-07-0a")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-0a-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-a7-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010/07/04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010/7/04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010/7/4")); 2| assertThrown!DateTimeException(Date.fromISOString("2010/07/4")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-7-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-7-4")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-07-4")); | 2| assertThrown!DateTimeException(Date.fromISOString("99Jul04")); 2| assertThrown!DateTimeException(Date.fromISOString("010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010Jul0")); 2| assertThrown!DateTimeException(Date.fromISOString("12010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOString("-010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOString("+010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010Jul0a")); 2| assertThrown!DateTimeException(Date.fromISOString("2010Jua04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010aul04")); | 2| assertThrown!DateTimeException(Date.fromISOString("99-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOString("010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0")); 2| assertThrown!DateTimeException(Date.fromISOString("12010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOString("-010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOString("+010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0a")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-Jua-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-Jal-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-aul-04")); | 2| assertThrown!DateTimeException(Date.fromISOString("2010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOString("2010-Jul-04")); | 1| assert(Date.fromISOString("19990706") == Date(1999, 7, 6)); 1| assert(Date.fromISOString("-19990706") == Date(-1999, 7, 6)); 1| assert(Date.fromISOString("+019990706") == Date(1999, 7, 6)); 1| assert(Date.fromISOString("19990706") == Date(1999, 7, 6)); | } | | // bug# 17801 | version (mir_test) | @safe unittest | { | import std.conv : to; | import std.meta : AliasSeq; | static foreach (C; AliasSeq!(char, wchar, dchar)) | { | static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) 9| assert(Date.fromISOString(to!S("20121221")) == Date(2012, 12, 21)); | } | } | | /++ | Creates a $(LREF Date) from a string with the format YYYY-MM-DD. | | Params: | str = A string formatted in the way that $(LREF .date.toISOExtString) formats dates. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format or if the resulting $(LREF Date) would not | be valid. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting date for single argument overdload. | +/ | static bool fromISOExtString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc | if (isSomeChar!C) | { | import mir.parse: fromString; | 137| if (str.length < 10 || str[$-3] != '-' || str[$-6] != '-') 42| return false; | 24| auto yearStr = str[0 .. $ - 6]; | 46| if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) 3| return false; | 42| uint day, month; 21| int year; | 21| return | fromString(str[$ - 2 .. $], day) 20| && fromString(str[$ - 5 .. $ - 3], month) 18| && fromString(yearStr, year) 18| && fromYMD(year, month, day, value); | } | | /// ditto | static Date fromISOExtString(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 62| Date ret; 62| if (fromISOExtString(str, ret)) 16| return ret; 46| throw InvalidISOExtendedString; | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date.fromISOExtString("2010-07-04") == Date(2010, 7, 4)); 1| assert(Date.fromISOExtString("1998-12-25") == Date(1998, 12, 25)); 1| assert(Date.fromISOExtString("0000-01-05") == Date(0, 1, 5)); 1| assert(Date.fromISOExtString("-0004-01-05") == Date(-4, 1, 5)); | } | | version (mir_test) | @safe unittest | { 2| assertThrown!DateTimeException(Date.fromISOExtString("")); 2| assertThrown!DateTimeException(Date.fromISOExtString("990704")); 2| assertThrown!DateTimeException(Date.fromISOExtString("0100704")); 2| assertThrown!DateTimeException(Date.fromISOExtString("120100704")); 2| assertThrown!DateTimeException(Date.fromISOExtString("-0100704")); 2| assertThrown!DateTimeException(Date.fromISOExtString("+0100704")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010070a")); 2| assertThrown!DateTimeException(Date.fromISOExtString("20100a04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010a704")); | 2| assertThrown!DateTimeException(Date.fromISOExtString("99-07-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0")); 2| assertThrown!DateTimeException(Date.fromISOExtString("12010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("-010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("+010-07-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0a")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-0a-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-a7-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010/07/04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010/7/04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010/7/4")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010/07/4")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-7-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-7-4")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-07-4")); | 2| assertThrown!DateTimeException(Date.fromISOExtString("99Jul04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0")); 2| assertThrown!DateTimeException(Date.fromISOExtString("12010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("-010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("+010Jul04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0a")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010Jua04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010aul04")); | 2| assertThrown!DateTimeException(Date.fromISOExtString("99-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0")); 2| assertThrown!DateTimeException(Date.fromISOExtString("12010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("-010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("+010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0a")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-Jua-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-Jal-04")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-aul-04")); | 2| assertThrown!DateTimeException(Date.fromISOExtString("20100704")); 2| assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-04")); | 1| assert(Date.fromISOExtString("1999-07-06") == Date(1999, 7, 6)); 1| assert(Date.fromISOExtString("-1999-07-06") == Date(-1999, 7, 6)); 1| assert(Date.fromISOExtString("+01999-07-06") == Date(1999, 7, 6)); | } | | // bug# 17801 | version (mir_test) | @safe unittest | { | import std.conv : to; | import std.meta : AliasSeq; | static foreach (C; AliasSeq!(char, wchar, dchar)) | { | static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) 9| assert(Date.fromISOExtString(to!S("2012-12-21")) == Date(2012, 12, 21)); | } | } | | | /++ | Creates a $(LREF Date) from a string with the format YYYY-Mon-DD. | | Params: | str = A string formatted in the way that $(LREF .date.toSimpleString) formats dates. The function is case sensetive. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format or if the resulting $(LREF Date) would not | be valid. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting date for single argument overdload. | +/ | static bool fromSimpleString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc | if (isSomeChar!C) | { | import mir.parse: fromString; | 114| if (str.length < 11 || str[$-3] != '-' || str[$-7] != '-') 40| return false; | 24| auto yearStr = str[0 .. $ - 7]; | 46| if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) 3| return false; | 21| Month month; | 21| switch (str[$ - 6 .. $ - 3]) | { 6| case "Jan": month = Month.jan; break; 0000000| case "Feb": month = Month.feb; break; 0000000| case "Mar": month = Month.mar; break; 0000000| case "Apr": month = Month.apr; break; 0000000| case "May": month = Month.may; break; 0000000| case "Jun": month = Month.jun; break; 18| case "Jul": month = Month.jul; break; 0000000| case "Aug": month = Month.aug; break; 0000000| case "Sep": month = Month.sep; break; 0000000| case "Oct": month = Month.oct; break; 0000000| case "Nov": month = Month.nov; break; 30| case "Dec": month = Month.dec; break; 6| default: return false; | } | 18| uint day; 18| int year; | 18| return | fromString(str[$ - 2 .. $], day) 17| && fromString(yearStr, year) 17| && fromYMD(year, month, day, value); | } | | /// ditto | static Date fromSimpleString(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 63| Date ret; 63| if (fromSimpleString(str, ret)) 16| return ret; 47| throw new DateTimeException("Invalid Simple String"); | } | | /// | version (mir_test) | @safe unittest | { 1| assert(Date.fromSimpleString("2010-Jul-04") == Date(2010, 7, 4)); 1| assert(Date.fromSimpleString("1998-Dec-25") == Date(1998, 12, 25)); 1| assert(Date.fromSimpleString("0000-Jan-05") == Date(0, 1, 5)); 1| assert(Date.fromSimpleString("-0004-Jan-05") == Date(-4, 1, 5)); | } | | version (mir_test) | @safe unittest | { 2| assertThrown!DateTimeException(Date.fromSimpleString("")); 2| assertThrown!DateTimeException(Date.fromSimpleString("990704")); 2| assertThrown!DateTimeException(Date.fromSimpleString("0100704")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010070")); 2| assertThrown!DateTimeException(Date.fromSimpleString("120100704")); 2| assertThrown!DateTimeException(Date.fromSimpleString("-0100704")); 2| assertThrown!DateTimeException(Date.fromSimpleString("+0100704")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010070a")); 2| assertThrown!DateTimeException(Date.fromSimpleString("20100a04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010a704")); | 2| assertThrown!DateTimeException(Date.fromSimpleString("99-07-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("010-07-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0")); 2| assertThrown!DateTimeException(Date.fromSimpleString("12010-07-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("-010-07-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("+010-07-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0a")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-0a-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-a7-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010/07/04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010/7/04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010/7/4")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010/07/4")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-7-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-7-4")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-07-4")); | 2| assertThrown!DateTimeException(Date.fromSimpleString("99Jul04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("010Jul04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0")); 2| assertThrown!DateTimeException(Date.fromSimpleString("12010Jul04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("-010Jul04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("+010Jul04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0a")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010Jua04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010aul04")); | 2| assertThrown!DateTimeException(Date.fromSimpleString("99-Jul-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0")); 2| assertThrown!DateTimeException(Date.fromSimpleString("12010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("-010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("+010-Jul-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0a")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-Jua-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-Jal-04")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-aul-04")); | 2| assertThrown!DateTimeException(Date.fromSimpleString("20100704")); 2| assertThrown!DateTimeException(Date.fromSimpleString("2010-07-04")); | 1| assert(Date.fromSimpleString("1999-Jul-06") == Date(1999, 7, 6)); 1| assert(Date.fromSimpleString("-1999-Jul-06") == Date(-1999, 7, 6)); 1| assert(Date.fromSimpleString("+01999-Jul-06") == Date(1999, 7, 6)); | } | | // bug# 17801 | version (mir_test) | @safe unittest | { | import std.conv : to; | import std.meta : AliasSeq; | static foreach (C; AliasSeq!(char, wchar, dchar)) | { | static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) 9| assert(Date.fromSimpleString(to!S("2012-Dec-21")) == Date(2012, 12, 21)); | } | } | | /++ | Creates a $(LREF Date) from a string with the format YYYY-MM-DD, YYYYMMDD, or YYYY-Mon-DD. | | Params: | str = A string formatted in the way that $(LREF .date.toISOExtString), $(LREF .date.toISOString), and $(LREF .date.toSimpleString) format dates. The function is case sensetive. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format or if the resulting $(LREF Date) would not | be valid. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting date for single argument overdload. | +/ | static bool fromString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc | { 4| return fromISOExtString(str, value) 2| || fromISOString(str, value) 1| || fromSimpleString(str, value); | } | | /// | version (mir_test) | @safe pure @nogc unittest | { 1| assert(Date.fromString("2010-07-04") == Date(2010, 7, 4)); 1| assert(Date.fromString("20100704") == Date(2010, 7, 4)); 1| assert(Date.fromString("2010-Jul-04") == Date(2010, 7, 4)); | } | | /// ditto | static Date fromString(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 4| Date ret; 4| if (fromString(str, ret)) 4| return ret; 0000000| throw InvalidString; | } | | /++ | Returns the $(LREF Date) farthest in the past which is representable by | $(LREF Date). | +/ | @property static Date min() @safe pure nothrow @nogc | { 0000000| return Date(-(int.max / 2)); | } | | /++ | Returns the $(LREF Date) farthest in the future which is representable | by $(LREF Date). | +/ | @property static Date max() @safe pure nothrow @nogc | { 0000000| return Date(int.max / 2); | } | |private: | | /+ | Whether the given values form a valid date. | | Params: | year = The year to test. | month = The month of the Gregorian Calendar to test. | day = The day of the month to test. | +/ | static bool _valid(int year, int month, int day) @safe pure nothrow @nogc | { 0000000| if (!valid!"months"(month)) 0000000| return false; 0000000| return valid!"days"(year, month, day); | } | | |package: | | /+ | Adds the given number of days to this $(LREF Date). A negative number | will subtract. | | The month will be adjusted along with the day if the number of days | added (or subtracted) would overflow (or underflow) the current month. | The year will be adjusted along with the month if the increase (or | decrease) to the month would cause it to overflow (or underflow) the | current year. | | $(D _addDays(numDays)) is effectively equivalent to | $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days). | | Params: | days = The number of days to add to this Date. | +/ | ref Date _addDays(long days) return @safe pure nothrow @nogc | { 46| _julianDay = cast(int)(_julianDay + days); 46| return this; | } | | version (mir_test) | @safe unittest | { | // Test A.D. | { 1| auto date = Date(1999, 2, 28); 1| date._addDays(1); 1| assert(date == Date(1999, 3, 1)); 1| date._addDays(-1); 1| assert(date == Date(1999, 2, 28)); | } | | { 1| auto date = Date(2000, 2, 28); 1| date._addDays(1); 1| assert(date == Date(2000, 2, 29)); 1| date._addDays(1); 1| assert(date == Date(2000, 3, 1)); 1| date._addDays(-1); 1| assert(date == Date(2000, 2, 29)); | } | | { 1| auto date = Date(1999, 6, 30); 1| date._addDays(1); 1| assert(date == Date(1999, 7, 1)); 1| date._addDays(-1); 1| assert(date == Date(1999, 6, 30)); | } | | { 1| auto date = Date(1999, 7, 31); 1| date._addDays(1); 1| assert(date == Date(1999, 8, 1)); 1| date._addDays(-1); 1| assert(date == Date(1999, 7, 31)); | } | | { 1| auto date = Date(1999, 1, 1); 1| date._addDays(-1); 1| assert(date == Date(1998, 12, 31)); 1| date._addDays(1); 1| assert(date == Date(1999, 1, 1)); | } | | { 1| auto date = Date(1999, 7, 6); 1| date._addDays(9); 1| assert(date == Date(1999, 7, 15)); 1| date._addDays(-11); 1| assert(date == Date(1999, 7, 4)); 1| date._addDays(30); 1| assert(date == Date(1999, 8, 3)); 1| date._addDays(-3); 1| assert(date == Date(1999, 7, 31)); | } | | { 1| auto date = Date(1999, 7, 6); 1| date._addDays(365); 1| assert(date == Date(2000, 7, 5)); 1| date._addDays(-365); 1| assert(date == Date(1999, 7, 6)); 1| date._addDays(366); 1| assert(date == Date(2000, 7, 6)); 1| date._addDays(730); 1| assert(date == Date(2002, 7, 6)); 1| date._addDays(-1096); 1| assert(date == Date(1999, 7, 6)); | } | | // Test B.C. | { 1| auto date = Date(-1999, 2, 28); 1| date._addDays(1); 1| assert(date == Date(-1999, 3, 1)); 1| date._addDays(-1); 1| assert(date == Date(-1999, 2, 28)); | } | | { 1| auto date = Date(-2000, 2, 28); 1| date._addDays(1); 1| assert(date == Date(-2000, 2, 29)); 1| date._addDays(1); 1| assert(date == Date(-2000, 3, 1)); 1| date._addDays(-1); 1| assert(date == Date(-2000, 2, 29)); | } | | { 1| auto date = Date(-1999, 6, 30); 1| date._addDays(1); 1| assert(date == Date(-1999, 7, 1)); 1| date._addDays(-1); 1| assert(date == Date(-1999, 6, 30)); | } | | { 1| auto date = Date(-1999, 7, 31); 1| date._addDays(1); 1| assert(date == Date(-1999, 8, 1)); 1| date._addDays(-1); 1| assert(date == Date(-1999, 7, 31)); | } | | { 1| auto date = Date(-1999, 1, 1); 1| date._addDays(-1); 1| assert(date == Date(-2000, 12, 31)); 1| date._addDays(1); 1| assert(date == Date(-1999, 1, 1)); | } | | { 1| auto date = Date(-1999, 7, 6); 1| date._addDays(9); 1| assert(date == Date(-1999, 7, 15)); 1| date._addDays(-11); 1| assert(date == Date(-1999, 7, 4)); 1| date._addDays(30); 1| assert(date == Date(-1999, 8, 3)); 1| date._addDays(-3); | } | | { 1| auto date = Date(-1999, 7, 6); 1| date._addDays(365); 1| assert(date == Date(-1998, 7, 6)); 1| date._addDays(-365); 1| assert(date == Date(-1999, 7, 6)); 1| date._addDays(366); 1| assert(date == Date(-1998, 7, 7)); 1| date._addDays(730); 1| assert(date == Date(-1996, 7, 6)); 1| date._addDays(-1096); 1| assert(date == Date(-1999, 7, 6)); | } | | // Test Both | { 1| auto date = Date(1, 7, 6); 1| date._addDays(-365); 1| assert(date == Date(0, 7, 6)); 1| date._addDays(365); 1| assert(date == Date(1, 7, 6)); 1| date._addDays(-731); 1| assert(date == Date(-1, 7, 6)); 1| date._addDays(730); 1| assert(date == Date(1, 7, 5)); | } | 1| const cdate = Date(1999, 7, 6); 1| immutable idate = Date(1999, 7, 6); | static assert(!__traits(compiles, cdate._addDays(12))); | static assert(!__traits(compiles, idate._addDays(12))); | } | | int _julianDay; |} | |/// ditto |alias Date = date; | |/++ | Returns the number of days from the current day of the week to the given | day of the week. If they are the same, then the result is 0. | Params: | currDoW = The current day of the week. | dow = The day of the week to get the number of days to. | +/ |int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow @nogc |{ 54| if (currDoW == dow) 8| return 0; 46| if (currDoW < dow) 23| return dow - currDoW; 23| return DayOfWeek.sun - currDoW + dow + 1; |} | |/// |version (mir_test) |@safe pure nothrow @nogc unittest |{ 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2); |} | |version (mir_test) |@safe unittest |{ 1| assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sun) == 0); 1| assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.mon) == 1); 1| assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.tue) == 2); 1| assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.wed) == 3); 1| assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.thu) == 4); 1| assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.fri) == 5); 1| assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sat) == 6); | 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.tue) == 1); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.thu) == 3); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.fri) == 4); 1| assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sat) == 5); | 1| assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sun) == 5); 1| assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.mon) == 6); 1| assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.tue) == 0); 1| assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.wed) == 1); 1| assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.thu) == 2); 1| assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.fri) == 3); 1| assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sat) == 4); | 1| assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sun) == 4); 1| assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.mon) == 5); 1| assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.tue) == 6); 1| assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.wed) == 0); 1| assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.thu) == 1); 1| assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.fri) == 2); 1| assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sat) == 3); | 1| assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sun) == 3); 1| assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.mon) == 4); 1| assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.tue) == 5); 1| assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.wed) == 6); 1| assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.thu) == 0); 1| assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.fri) == 1); 1| assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sat) == 2); | 1| assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sun) == 2); 1| assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.mon) == 3); 1| assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.tue) == 4); 1| assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.wed) == 5); 1| assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.thu) == 6); 1| assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.fri) == 0); 1| assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sat) == 1); | 1| assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sun) == 1); 1| assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.mon) == 2); 1| assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.tue) == 3); 1| assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.wed) == 4); 1| assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.thu) == 5); 1| assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.fri) == 6); 1| assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sat) == 0); |} | |package: | | |/+ | Array of the short (three letter) names of each month. | +/ |immutable string[12] _monthNames = ["Jan", | "Feb", | "Mar", | "Apr", | "May", | "Jun", | "Jul", | "Aug", | "Sep", | "Oct", | "Nov", | "Dec"]; | |/++ | The maximum valid Day in the given month in the given year. | | Params: | year = The year to get the day for. | month = The month of the Gregorian Calendar to get the day for. | +/ |public ubyte maxDay(int year, int month) @safe pure nothrow @nogc |in |{ 718| assert(valid!"months"(month)); |} |do |{ 718| switch (month) | { 2178| case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec: 581| return 31; 58| case Month.feb: 58| return yearIsLeapYear(year) ? 29 : 28; 202| case Month.apr, Month.jun, Month.sep, Month.nov: 79| return 30; | default: | assert(0, "Invalid month."); | } |} | |version (mir_test) |@safe unittest |{ | // Test A.D. 1| assert(maxDay(1999, 1) == 31); 1| assert(maxDay(1999, 2) == 28); 1| assert(maxDay(1999, 3) == 31); 1| assert(maxDay(1999, 4) == 30); 1| assert(maxDay(1999, 5) == 31); 1| assert(maxDay(1999, 6) == 30); 1| assert(maxDay(1999, 7) == 31); 1| assert(maxDay(1999, 8) == 31); 1| assert(maxDay(1999, 9) == 30); 1| assert(maxDay(1999, 10) == 31); 1| assert(maxDay(1999, 11) == 30); 1| assert(maxDay(1999, 12) == 31); | 1| assert(maxDay(2000, 1) == 31); 1| assert(maxDay(2000, 2) == 29); 1| assert(maxDay(2000, 3) == 31); 1| assert(maxDay(2000, 4) == 30); 1| assert(maxDay(2000, 5) == 31); 1| assert(maxDay(2000, 6) == 30); 1| assert(maxDay(2000, 7) == 31); 1| assert(maxDay(2000, 8) == 31); 1| assert(maxDay(2000, 9) == 30); 1| assert(maxDay(2000, 10) == 31); 1| assert(maxDay(2000, 11) == 30); 1| assert(maxDay(2000, 12) == 31); | | // Test B.C. 1| assert(maxDay(-1999, 1) == 31); 1| assert(maxDay(-1999, 2) == 28); 1| assert(maxDay(-1999, 3) == 31); 1| assert(maxDay(-1999, 4) == 30); 1| assert(maxDay(-1999, 5) == 31); 1| assert(maxDay(-1999, 6) == 30); 1| assert(maxDay(-1999, 7) == 31); 1| assert(maxDay(-1999, 8) == 31); 1| assert(maxDay(-1999, 9) == 30); 1| assert(maxDay(-1999, 10) == 31); 1| assert(maxDay(-1999, 11) == 30); 1| assert(maxDay(-1999, 12) == 31); | 1| assert(maxDay(-2000, 1) == 31); 1| assert(maxDay(-2000, 2) == 29); 1| assert(maxDay(-2000, 3) == 31); 1| assert(maxDay(-2000, 4) == 30); 1| assert(maxDay(-2000, 5) == 31); 1| assert(maxDay(-2000, 6) == 30); 1| assert(maxDay(-2000, 7) == 31); 1| assert(maxDay(-2000, 8) == 31); 1| assert(maxDay(-2000, 9) == 30); 1| assert(maxDay(-2000, 10) == 31); 1| assert(maxDay(-2000, 11) == 30); 1| assert(maxDay(-2000, 12) == 31); |} | |/+ | Returns the day of the week for the given day of the Gregorian/Julian Calendar. | | Params: | day = The day of the Gregorian/Julian Calendar for which to get the day of | the week. | +/ |DayOfWeek getDayOfWeek(int day) @safe pure nothrow @nogc |{ | // January 1st, 1 A.D. was a Monday 4| if (day >= 0) 4| return cast(DayOfWeek)(day % 7); | else | { 0000000| immutable dow = cast(DayOfWeek)((day % 7) + 7); | 0000000| if (dow == 7) 0000000| return DayOfWeek.mon; | else 0000000| return dow; | } |} | |private: | |enum daysInYear = 365; // The number of days in a non-leap year. |enum daysInLeapYear = 366; // The numbef or days in a leap year. |enum daysIn4Years = daysInYear * 3 + daysInLeapYear; // Number of days in 4 years. |enum daysIn100Years = daysIn4Years * 25 - 1; // The number of days in 100 years. |enum daysIn400Years = daysIn100Years * 4 + 1; // The number of days in 400 years. | |/+ | Array of integers representing the last days of each month in a year. | +/ |immutable int[13] lastDayNonLeap = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; | |/+ | Array of integers representing the last days of each month in a leap year. | +/ |immutable int[13] lastDayLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; | |/+ | Array of integers representing the last days of each quarter in a year. | +/ |immutable int[5] lastDayQuarterNonLeap = [0, 90, 181, 273, 365]; | |/+ | Array of integers representing the last days of each quarter in a leap year. | +/ |immutable int[5] lastDayQuarterLeap = [0, 91, 182, 274, 366]; | |/+ | Returns the string representation of the given month. | +/ |string monthToString(Month month) @safe pure @nogc nothrow |{ 62| assert(month >= Month.jan && month <= Month.dec, "Invalid month"); 31| return _monthNames[month - Month.jan]; |} | |version (mir_test) |@safe unittest |{ 1| assert(monthToString(Month.jan) == "Jan"); 1| assert(monthToString(Month.feb) == "Feb"); 1| assert(monthToString(Month.mar) == "Mar"); 1| assert(monthToString(Month.apr) == "Apr"); 1| assert(monthToString(Month.may) == "May"); 1| assert(monthToString(Month.jun) == "Jun"); 1| assert(monthToString(Month.jul) == "Jul"); 1| assert(monthToString(Month.aug) == "Aug"); 1| assert(monthToString(Month.sep) == "Sep"); 1| assert(monthToString(Month.oct) == "Oct"); 1| assert(monthToString(Month.nov) == "Nov"); 1| assert(monthToString(Month.dec) == "Dec"); |} | |version (mir_test) |version(unittest) |{ | // All of these helper arrays are sorted in ascending order. | auto testYearsBC = [-1999, -1200, -600, -4, -1, 0]; | auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012]; | | // I'd use a Tuple, but I get forward reference errors if I try. | struct MonthDay | { | Month month; | short day; | 0000000| this(int m, short d) | { 0000000| month = cast(Month) m; 0000000| day = d; | } | } | | MonthDay[] testMonthDays = [MonthDay(1, 1), | MonthDay(1, 2), | MonthDay(3, 17), | MonthDay(7, 4), | MonthDay(10, 27), | MonthDay(12, 30), | MonthDay(12, 31)]; | | auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31]; | | Date[] testDatesBC; | Date[] testDatesAD; | | // I'd use a Tuple, but I get forward reference errors if I try. | struct GregDay { int day; Date date; } | auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), // Start of the Hebrew Calendar | GregDay(-735_233, Date(-2012, 1, 1)), | GregDay(-735_202, Date(-2012, 2, 1)), | GregDay(-735_175, Date(-2012, 2, 28)), | GregDay(-735_174, Date(-2012, 2, 29)), | GregDay(-735_173, Date(-2012, 3, 1)), | GregDay(-734_502, Date(-2010, 1, 1)), | GregDay(-734_472, Date(-2010, 1, 31)), | GregDay(-734_471, Date(-2010, 2, 1)), | GregDay(-734_444, Date(-2010, 2, 28)), | GregDay(-734_443, Date(-2010, 3, 1)), | GregDay(-734_413, Date(-2010, 3, 31)), | GregDay(-734_412, Date(-2010, 4, 1)), | GregDay(-734_383, Date(-2010, 4, 30)), | GregDay(-734_382, Date(-2010, 5, 1)), | GregDay(-734_352, Date(-2010, 5, 31)), | GregDay(-734_351, Date(-2010, 6, 1)), | GregDay(-734_322, Date(-2010, 6, 30)), | GregDay(-734_321, Date(-2010, 7, 1)), | GregDay(-734_291, Date(-2010, 7, 31)), | GregDay(-734_290, Date(-2010, 8, 1)), | GregDay(-734_260, Date(-2010, 8, 31)), | GregDay(-734_259, Date(-2010, 9, 1)), | GregDay(-734_230, Date(-2010, 9, 30)), | GregDay(-734_229, Date(-2010, 10, 1)), | GregDay(-734_199, Date(-2010, 10, 31)), | GregDay(-734_198, Date(-2010, 11, 1)), | GregDay(-734_169, Date(-2010, 11, 30)), | GregDay(-734_168, Date(-2010, 12, 1)), | GregDay(-734_139, Date(-2010, 12, 30)), | GregDay(-734_138, Date(-2010, 12, 31)), | GregDay(-731_215, Date(-2001, 1, 1)), | GregDay(-730_850, Date(-2000, 1, 1)), | GregDay(-730_849, Date(-2000, 1, 2)), | GregDay(-730_486, Date(-2000, 12, 30)), | GregDay(-730_485, Date(-2000, 12, 31)), | GregDay(-730_484, Date(-1999, 1, 1)), | GregDay(-694_690, Date(-1901, 1, 1)), | GregDay(-694_325, Date(-1900, 1, 1)), | GregDay(-585_118, Date(-1601, 1, 1)), | GregDay(-584_753, Date(-1600, 1, 1)), | GregDay(-584_388, Date(-1600, 12, 31)), | GregDay(-584_387, Date(-1599, 1, 1)), | GregDay(-365_972, Date(-1001, 1, 1)), | GregDay(-365_607, Date(-1000, 1, 1)), | GregDay(-183_351, Date(-501, 1, 1)), | GregDay(-182_986, Date(-500, 1, 1)), | GregDay(-182_621, Date(-499, 1, 1)), | GregDay(-146_827, Date(-401, 1, 1)), | GregDay(-146_462, Date(-400, 1, 1)), | GregDay(-146_097, Date(-400, 12, 31)), | GregDay(-110_302, Date(-301, 1, 1)), | GregDay(-109_937, Date(-300, 1, 1)), | GregDay(-73_778, Date(-201, 1, 1)), | GregDay(-73_413, Date(-200, 1, 1)), | GregDay(-38_715, Date(-105, 1, 1)), | GregDay(-37_254, Date(-101, 1, 1)), | GregDay(-36_889, Date(-100, 1, 1)), | GregDay(-36_524, Date(-99, 1, 1)), | GregDay(-36_160, Date(-99, 12, 31)), | GregDay(-35_794, Date(-97, 1, 1)), | GregDay(-18_627, Date(-50, 1, 1)), | GregDay(-18_262, Date(-49, 1, 1)), | GregDay(-3652, Date(-9, 1, 1)), | GregDay(-2191, Date(-5, 1, 1)), | GregDay(-1827, Date(-5, 12, 31)), | GregDay(-1826, Date(-4, 1, 1)), | GregDay(-1825, Date(-4, 1, 2)), | GregDay(-1462, Date(-4, 12, 30)), | GregDay(-1461, Date(-4, 12, 31)), | GregDay(-1460, Date(-3, 1, 1)), | GregDay(-1096, Date(-3, 12, 31)), | GregDay(-1095, Date(-2, 1, 1)), | GregDay(-731, Date(-2, 12, 31)), | GregDay(-730, Date(-1, 1, 1)), | GregDay(-367, Date(-1, 12, 30)), | GregDay(-366, Date(-1, 12, 31)), | GregDay(-365, Date(0, 1, 1)), | GregDay(-31, Date(0, 11, 30)), | GregDay(-30, Date(0, 12, 1)), | GregDay(-1, Date(0, 12, 30)), | GregDay(0, Date(0, 12, 31))]; | | auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)), | GregDay(2, Date(1, 1, 2)), | GregDay(32, Date(1, 2, 1)), | GregDay(365, Date(1, 12, 31)), | GregDay(366, Date(2, 1, 1)), | GregDay(731, Date(3, 1, 1)), | GregDay(1096, Date(4, 1, 1)), | GregDay(1097, Date(4, 1, 2)), | GregDay(1460, Date(4, 12, 30)), | GregDay(1461, Date(4, 12, 31)), | GregDay(1462, Date(5, 1, 1)), | GregDay(17_898, Date(50, 1, 1)), | GregDay(35_065, Date(97, 1, 1)), | GregDay(36_160, Date(100, 1, 1)), | GregDay(36_525, Date(101, 1, 1)), | GregDay(37_986, Date(105, 1, 1)), | GregDay(72_684, Date(200, 1, 1)), | GregDay(73_049, Date(201, 1, 1)), | GregDay(109_208, Date(300, 1, 1)), | GregDay(109_573, Date(301, 1, 1)), | GregDay(145_732, Date(400, 1, 1)), | GregDay(146_098, Date(401, 1, 1)), | GregDay(182_257, Date(500, 1, 1)), | GregDay(182_622, Date(501, 1, 1)), | GregDay(364_878, Date(1000, 1, 1)), | GregDay(365_243, Date(1001, 1, 1)), | GregDay(584_023, Date(1600, 1, 1)), | GregDay(584_389, Date(1601, 1, 1)), | GregDay(693_596, Date(1900, 1, 1)), | GregDay(693_961, Date(1901, 1, 1)), | GregDay(729_755, Date(1999, 1, 1)), | GregDay(730_120, Date(2000, 1, 1)), | GregDay(730_121, Date(2000, 1, 2)), | GregDay(730_484, Date(2000, 12, 30)), | GregDay(730_485, Date(2000, 12, 31)), | GregDay(730_486, Date(2001, 1, 1)), | GregDay(733_773, Date(2010, 1, 1)), | GregDay(733_774, Date(2010, 1, 2)), | GregDay(733_803, Date(2010, 1, 31)), | GregDay(733_804, Date(2010, 2, 1)), | GregDay(733_831, Date(2010, 2, 28)), | GregDay(733_832, Date(2010, 3, 1)), | GregDay(733_862, Date(2010, 3, 31)), | GregDay(733_863, Date(2010, 4, 1)), | GregDay(733_892, Date(2010, 4, 30)), | GregDay(733_893, Date(2010, 5, 1)), | GregDay(733_923, Date(2010, 5, 31)), | GregDay(733_924, Date(2010, 6, 1)), | GregDay(733_953, Date(2010, 6, 30)), | GregDay(733_954, Date(2010, 7, 1)), | GregDay(733_984, Date(2010, 7, 31)), | GregDay(733_985, Date(2010, 8, 1)), | GregDay(734_015, Date(2010, 8, 31)), | GregDay(734_016, Date(2010, 9, 1)), | GregDay(734_045, Date(2010, 9, 30)), | GregDay(734_046, Date(2010, 10, 1)), | GregDay(734_076, Date(2010, 10, 31)), | GregDay(734_077, Date(2010, 11, 1)), | GregDay(734_106, Date(2010, 11, 30)), | GregDay(734_107, Date(2010, 12, 1)), | GregDay(734_136, Date(2010, 12, 30)), | GregDay(734_137, Date(2010, 12, 31)), | GregDay(734_503, Date(2012, 1, 1)), | GregDay(734_534, Date(2012, 2, 1)), | GregDay(734_561, Date(2012, 2, 28)), | GregDay(734_562, Date(2012, 2, 29)), | GregDay(734_563, Date(2012, 3, 1)), | GregDay(734_858, Date(2012, 12, 21))]; | | // I'd use a Tuple, but I get forward reference errors if I try. | struct DayOfYear { int day; MonthDay md; } | auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)), | DayOfYear(2, MonthDay(1, 2)), | DayOfYear(3, MonthDay(1, 3)), | DayOfYear(31, MonthDay(1, 31)), | DayOfYear(32, MonthDay(2, 1)), | DayOfYear(59, MonthDay(2, 28)), | DayOfYear(60, MonthDay(3, 1)), | DayOfYear(90, MonthDay(3, 31)), | DayOfYear(91, MonthDay(4, 1)), | DayOfYear(120, MonthDay(4, 30)), | DayOfYear(121, MonthDay(5, 1)), | DayOfYear(151, MonthDay(5, 31)), | DayOfYear(152, MonthDay(6, 1)), | DayOfYear(181, MonthDay(6, 30)), | DayOfYear(182, MonthDay(7, 1)), | DayOfYear(212, MonthDay(7, 31)), | DayOfYear(213, MonthDay(8, 1)), | DayOfYear(243, MonthDay(8, 31)), | DayOfYear(244, MonthDay(9, 1)), | DayOfYear(273, MonthDay(9, 30)), | DayOfYear(274, MonthDay(10, 1)), | DayOfYear(304, MonthDay(10, 31)), | DayOfYear(305, MonthDay(11, 1)), | DayOfYear(334, MonthDay(11, 30)), | DayOfYear(335, MonthDay(12, 1)), | DayOfYear(363, MonthDay(12, 29)), | DayOfYear(364, MonthDay(12, 30)), | DayOfYear(365, MonthDay(12, 31))]; | | auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)), | DayOfYear(2, MonthDay(1, 2)), | DayOfYear(3, MonthDay(1, 3)), | DayOfYear(31, MonthDay(1, 31)), | DayOfYear(32, MonthDay(2, 1)), | DayOfYear(59, MonthDay(2, 28)), | DayOfYear(60, MonthDay(2, 29)), | DayOfYear(61, MonthDay(3, 1)), | DayOfYear(91, MonthDay(3, 31)), | DayOfYear(92, MonthDay(4, 1)), | DayOfYear(121, MonthDay(4, 30)), | DayOfYear(122, MonthDay(5, 1)), | DayOfYear(152, MonthDay(5, 31)), | DayOfYear(153, MonthDay(6, 1)), | DayOfYear(182, MonthDay(6, 30)), | DayOfYear(183, MonthDay(7, 1)), | DayOfYear(213, MonthDay(7, 31)), | DayOfYear(214, MonthDay(8, 1)), | DayOfYear(244, MonthDay(8, 31)), | DayOfYear(245, MonthDay(9, 1)), | DayOfYear(274, MonthDay(9, 30)), | DayOfYear(275, MonthDay(10, 1)), | DayOfYear(305, MonthDay(10, 31)), | DayOfYear(306, MonthDay(11, 1)), | DayOfYear(335, MonthDay(11, 30)), | DayOfYear(336, MonthDay(12, 1)), | DayOfYear(364, MonthDay(12, 29)), | DayOfYear(365, MonthDay(12, 30)), | DayOfYear(366, MonthDay(12, 31))]; | | void initializeTests() @safe | { 21| foreach (year; testYearsBC) | { 144| foreach (md; testMonthDays) 42| testDatesBC ~= Date(year, md.month, md.day); | } | 21| foreach (year; testYearsAD) | { 144| foreach (md; testMonthDays) 42| testDatesAD ~= Date(year, md.month, md.day); | } | } |} source/mir/date.d is 94% covered <<<<<< EOF # path=source-mir-ediff.lst |/++ |$(H1 Expression differentiation) | |The module provides API for rapid evaluation of a user-requested set of partial derivatives of any order. | |The implementation operates with double precision numbers. | |$(BOOKTABLE $(H2 Expression construction), |$(TR $(TH Name) $(TH Description)) |$(T2 $(LREF Const), A double precision constant with an immidiate value) |$(T2 $(LREF Var), A double precision named variable with an immidiate value) |$(T2 `+`, Constructs the sum of two expressions) |$(T2 `-`, Constructs the difference of two expressions) |$(T2 `*`, Constructs the product of two expressions) |$(T2 `/`, Constructs the quotient of two expressions) |$(T2 $(LREF derivativeOf), Constructs a partial derivative of one order. | The function can be applied in any stage any number of times. | The function does NOT evaluate the expression.) |$(T2 $(LREF powi), Constructs of the expression raised to the specified (positive or negative) compile-time integral power.) |$(T2 $(LREF log), Constructs the natural logarithm of the expression.) |$(T2 $(LREF exp), Constructs the natural exponent of the expression.) |$(T2 $(LREF sqrt), Constructs the square root of the expression.) |$(T2 $(LREF normalDistribution), Constructs the cumulative normal distribution function of the expression.) |) | |$(BOOKTABLE $(H2 Expression composition), |$(TR $(TH Name) $(TH Description)) |$(T2 $(LREF composeAt), Given a compiletime variable name `v` and two expressions `F(v, U)` and `G(Y)`, | constructs a composition `F ∘ G (U, Y)` as `v = G`) |) | |$(BOOKTABLE $(H2 Derivative set definition), |$(TR $(TH Name) $(TH Description)) |$(T2 $(LREF Derivative), User defined attribute that should applied on a struct member definition of a user-defined set of derivatives. | The attribute holds an unordered set with duplicates of variables names to reflect which partial derivative this member contains.) |$(T2 $(LREF Minus), User-defined attribute that can be applied on struct member definition along with $(LREF Derivative) to denote | that the member holds a negative value of the partial derivative. | This attribute is useful for financial code where verbal definitions may denote negative partial derivatives.) |) | |$(BOOKTABLE $(H2 Function definition), |$(TR $(TH Name) $(TH Description)) |$(T2 $(LREF DependsOn), User defined attribute that should applied on struct definition of a user-defined expression function) |$(T2 $(LREF Dependencies), Fetchs a set of variables the expression is depends on. | First, it checks if the expression has $(LREF DependsOn) UDA | ; otherwise, iterates over members with $(LREF Derivative) UDA and collects variable names. ) |$(T2 .getDerivative, The generic method, which a user expression function should define. | The method should accept a compile-time array of variable names and evaluate the corresponding partial derivative.) |$(T2 $(LREF ediffOperators, Mixin template that propagates `+`, `-`, `*`, and `/` binary operators for user defined expression functions.)) |) | |$(BOOKTABLE $(H2 Expression evaluation), |$(TR $(TH Name) $(TH Description)) |$(T2 $(LREF getFunctionValue), Evaluates function value. It is shortcut for $(LREF getDerivative) of zero order.) |$(T2 $(LREF getDerivative), Evaluates partial derivative for user-defined compiletime set of variables. | First, it checks if the expression has `.getDerivative` methods and uses it, | otherwise iterates over members with $(LREF Derivative) UDA and tries to find a member that holds the required partial derivative.) |$(T2 $(LREF setDerivatives), Evaluates partial derivatives and function value, if any, for a user-provided set of partial derivatives. | The derivative set can be defined with $(LREF Derivative) and $(LREF Minus) UDAs.) |) | |$(H2 Optimization note) | |During function differentiation, the resulting set of expressions likely contains a lot of |identical calls of elementary functions. LLVM efficiently eliminates equivalent calls of intrinsic |functions such as `powi`, `log`, `exp`, and `sqrt`. |On the other hand, it can't eliminate identical calls of complex functions. |It is highly recommended to evaluate a set of partial derivatives immediately after constructing |a complex expression such as $(LREF normalDistribution). | |Authors: Ilia Ki |+/ |module mir.ediff; | |/// |version(mir_test) |unittest |{ | /// | static struct D | { | @Derivative() | double _; | @Derivative("a") | double a; | @Derivative("b") | double b; | @Minus @Derivative("a", "b") | double mab; | } | 1| auto d = D(3, 4, 5, -6); 4| assert(d.powi!2.setDerivatives!D == D(9, 24, 30, -76)); |} |/// |version(mir_test) |unittest |{ | /// | static struct Greeks | { | @Derivative("spot") | double delta; | @Derivative("spot", "spot") | double gamma; | @Minus @Derivative("time", "spot") | double charm; | } | 1| auto greeks = Greeks(2, 3, 4); | 1| auto dspot = derivativeOf!"spot"(&greeks); | 1| assert(greeks.delta is dspot.getFunctionValue); 1| assert(greeks.gamma is dspot.getDerivative!(["spot"])); 1| assert(greeks.charm is -dspot.getDerivative!(["time"])); |} | |/// |version(mir_test) |unittest |{ | import mir.math; | | // Test Const | static assert(Dependencies!Const == [].DependsOn); 1| auto c = 5.Const; | static assert(c.getDerivative!(["any"]) == 0); 1| assert(c.getFunctionValue == 5); | | // Test Var 1| auto spot = 7.Var!"spot"; | static assert(Dependencies!(Var!"spot") == ["spot"].DependsOn); | static assert(spot.getDerivative!(["spot"]) == 1); | static assert(spot.getDerivative!(["other"]) == 0); 1| assert(spot.getFunctionValue == 7); | | // Test integer power and exponent 1| auto f1 = exp(3.Const * spot.powi!(-2)); | static assert(Dependencies!(typeof(f1)) == ["spot"].DependsOn); 1| assert(f1.getFunctionValue == mir.math.exp(3 * 7.0 ^^ -2)); 1| assert(f1.getDerivative!(["spot"]).approxEqual(3 * -2 * 7.0 ^^ -3 * mir.math.exp(3 * 7.0 ^^ -2))); | // Test DerivativeOf 1| assert(f1.derivativeOf!"spot".getFunctionValue == f1.getDerivative!(["spot"])); | // Test product and sum 1| assert(f1.derivativeOf!"spot".derivativeOf!"spot".getFunctionValue.approxEqual((3 * (-2 * 7.0 ^^ -3)) ^^ 2 * mir.math.exp(3 * 7.0 ^^ -2) + 3 * (6 * 7.0 ^^ -4)* mir.math.exp(3 * 7.0 ^^ -2))); | 1| auto strike = 9.Var!"strike"; | 1| auto f2 = strike * f1 + strike; 1| assert(f2.getDerivative!(["strike"]).approxEqual(1 + f1.getFunctionValue)); | // Test log 1| assert(f2.log.getFunctionValue == mir.math.log(f2.getFunctionValue)); 1| assert(f2.log.getDerivative!(["strike"]) == getFunctionValue(f2.powi!(-1) * (1.Const + f1))); 1| assert(f2.sqrt.getFunctionValue == mir.math.sqrt(f2.getFunctionValue)); 1| assert(f2.sqrt.getDerivative!(["strike"]) == getFunctionValue(f2.sqrt.powi!(-1) * 0.5.Const * (1.Const + f1))); | | // Compose 1| auto barrier = 13.Var!"barrier"; 1| auto fc = barrier.powi!2 / strike; 1| auto f3 = f2.composeAt!"strike"(fc); 1| assert(f3.getFunctionValue == f2.getFunctionValue); 1| assert(f3.getDerivative!(["vol"]) == f2.getDerivative!(["vol"])); 1| assert(f3.getDerivative!(["strike"]) == f2.getDerivative!(["strike"]) * fc.getDerivative!(["strike"])); 1| assert(f3.getDerivative!(["barrier"]) == f2.getDerivative!(["strike"]) * fc.getDerivative!(["barrier"])); 1| assert(getDerivative!(["barrier"])(f3 + barrier) == f2.getDerivative!(["strike"]) * fc.getDerivative!(["barrier"]) + 1); 1| assert(f3.getDerivative!(["strike", "barrier"]) == | f2.getDerivative!(["strike", "strike"]) * fc.getDerivative!(["strike"]) * fc.getDerivative!(["barrier"]) + | f2.getDerivative!(["strike"]) * fc.getDerivative!(["strike", "barrier"])); | | /// normalDistribution | import mir.math.func.normal: constantNormalCDF = normalCDF, normalPDF; 1| assert(barrier.powi!2.normalCDF.getFunctionValue == constantNormalCDF(13.0 ^^ 2)); 1| assert(barrier.powi!2.normalCDF.getDerivative!(["barrier"]) == normalPDF(13.0 ^^ 2) * (2 * 13)); |} | |import mir.algorithm.iteration: uniq; |import mir.array.allocation; |import mir.math.common; |import mir.ndslice.sorting: sort; |import std.traits: Unqual, getUDAs, hasUDA, isPointer, PointerTarget; | |@optmath: | |/++ |+/ |mixin template ediffOperators() |{ |const: | auto opBinary(string op, R)(const R rhs) | if ((op == "+" || op == "-") && is(R == struct)) | { 7| return Sum!(Unqual!(typeof(this)), R, op == "-")(this, rhs); | } | | auto opBinary(string op, R)(const R rhs) | if (op == "*" && is(R == struct)) | { | alias L = Unqual!(typeof(this)); | static if (is(R == Const) && is(L == Const)) | { | return Const(this.value * rhs.value); | } | else | static if (is(R == Const) && is(L == Powi!(_power, _LT), size_t _power, _LT)) | { 1| return L(this.value, this.coefficient * rhs.value); | } | else | static if (is(L == Const) && is(R == Powi!(_power, _RT), size_t _power, _RT)) | { 1| return R(rhs.value, rhs.coefficient * this.value); | } | else | { 57| return Product!(L, R)(this, rhs); | } | } | | auto opBinaryRight(string op, L)(const L lhs) | if ((op == "+" || op == "*") && is(L == struct)) | { | return this.opBinary!op(lhs); | } | | auto opBinaryRight(string op, L)(const L lhs) | if ((op == "-") && is(L == struct)) | { | return Sum!(L, Unqual!(typeof(this)), true)(lhs, this); | } | | auto opBinary(string op, R)(const R rhs) | if (op == "/" && is(R == struct)) | { | alias L = Unqual!(typeof(this)); | alias A = L; | alias B = R; | alias a = this; | alias b = rhs; | mixin _div; 2| return result; | } | | auto opBinaryRight(string op, L)(const L lhs) | if ((op == "/") && is(L == struct)) | { | alias R = Unqual!(typeof(this)); | alias A = L; | alias B = R; | alias a = lhs; | alias b = this; | mixin _div; | return result; | } |} | |private mixin template _div() |{ | // A / B | static if (is(A == Const) && is(B == Const)) | auto result = Const(a.value / b.value); | else | static if (is(A == Const)) | auto result = Powi!(-1, B)(b, a.value); | else | static if (is(B == Const)) | auto result = Const(1.0 / b.value) * a; | else 2| auto result = b.powi!(-1) * a; |} | |private template Sum(A, B, bool diff = false) |{ | | @(Dependencies!A ~ Dependencies!B) | struct Sum | { | A a; | B b; | | @optmath: | | template getDerivative(string[] variables, bool strict = true) | { | static if (Dependencies!(typeof(this)).containsAll(variables)) | auto getDerivative() const @property | { 29| double ret = 0; | static if (Dependencies!A.containsAll(variables)) 29| ret = a.getDerivative!(variables, strict); | static if (Dependencies!B.containsAll(variables)) | { | static if (diff) | ret -= b.getDerivative!(variables, strict); | else 29| ret += b.getDerivative!(variables, strict); | } 29| return ret; | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |private template Product(A, B) |{ | @(Dependencies!A ~ Dependencies!B) | struct Product | { | A a; | B b; | | template getDerivative(string[] variables, bool strict = true) | { | @optmath: | static if (Dependencies!(typeof(this)).containsAll(variables)) | auto getDerivative() const @property | { | static if (variables.length == 0) | { 58| return a.getFunctionValue!strict * b.getFunctionValue!strict; | } | else | { | enum variable = variables[0]; | static if (!Dependencies!A.contains(variable)) 8| return (a * b.derivativeOf!variable).getDerivative!(variables[1 .. $], strict); | else | static if (!Dependencies!B.contains(variable)) 21| return (a.derivativeOf!variable * b).getDerivative!(variables[1 .. $], strict); | else 3| return (a * b.derivativeOf!variable + a.derivativeOf!variable * b).getDerivative!(variables[1 .. $], strict); | } | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |/++ |User defined attribute that should applied on struct definition of a user-defined expression function. |+/ |struct DependsOn |{ | /// | string[] variables; | |@safe pure nothrow: | /// | auto contains(string variable) const @nogc | { 0000000| foreach (v; variables) 0000000| if (v == variable) 0000000| return true; 0000000| return false; | } | | /// | auto containsAll(string[] variables) const @nogc | { 0000000| foreach (v; variables) 0000000| if (!contains(v)) 0000000| return false; 0000000| return true; | } | | /// | auto containsAny(string[] variables) const @nogc | { 0000000| foreach (v; variables) 0000000| if (contains(v)) 0000000| return true; 0000000| return false; | } | | /// Set union | DependsOn opBinary(string op : "~")(DependsOn rhs) | { 0000000| return (this.variables ~ rhs.variables).sort.uniq.array.DependsOn; | } |} | |/++ |Fetchs a set of variables the expression is depends on. | First, it checks if the expression has $(LREF DependsOn) UDA | ; otherwise, iterates over members with $(LREF Derivative) UDA and collects variable names. |+/ |template Dependencies(T) |{ | static if (isPointer!T) | enum Dependencies = Dependencies!(PointerTarget!T); | else | static if (hasUDA!(T, DependsOn)) | enum DependsOn Dependencies = getUDAs!(T, DependsOn)[0]; | else | { | enum DependsOn Dependencies = () { | string[] variables; | static foreach (member; __traits(allMembers, T)) | { | static if (hasUDA!(__traits(getMember, T, member), Derivative)) | { | variables ~= getUDAs!(__traits(getMember, T, member), Derivative)[0].variables; | } | } | return variables.sort.uniq.array.DependsOn; | } (); | } |} | |/++ |User defined attribute that should applied on a struct member definition of a user-defined set of derivatives. | The attribute holds an unordered set with duplicates of variables names to reflect which partial derivative this member contains. |+/ |struct Derivative |{ | /// | string[] variables; | |@trusted pure nothrow @nogc: | | /// 0000000| this(string[] variables...) | { 0000000| this.variables = variables.sort; | } |} | |/++ |User-defined attribute that can be applied on struct member definition along with $(LREF Derivative) to denote | that the member holds a negative value of the partial derivative. | This attribute is useful for financial code where verbal definitions may denote negative partial derivatives. |+/ |enum Minus; | |/++ |Evaluates function value. It is shortcut for $(LREF getDerivative) of zero order. |Params: | strict = The parameter is used when the expression can't evaluate the function. If true, prints error at compile-time; otherwise, `getFunctionValues` returns NaN. |+/ |alias getFunctionValue(bool strict = true) = getDerivative!(string[].init, strict); | |/++ |Evaluates partial derivative for user-defined compiletime set of variables. | First, it checks if the expression has `.getDerivative` methods and uses it, | otherwise iterates over members with $(LREF Derivative) UDA and tries to find a member that holds the required partial derivative. |Params: | variables = array that denotes partial derivative | strict = The parameter is used when the expression can't evaluate the derivative. If true, prints error at compile-time; otherwise, `getDerivative` returns NaN. |+/ |template getDerivative(string[] variables, bool strict = true) |{ | import mir.ndslice.topology: pairwise; | import mir.algorithm.iteration: all; | |@optmath: | | static if (variables.length == 0 || variables.pairwise!"a <= b".all) | { | auto ref getDerivative(T)(auto ref T value) | { | static if (__traits(hasMember, T, "getDerivative")) 241| return value.getDerivative!(variables, strict); | else | { | import std.meta: anySatisfy; | | static if (isPointer!T) | alias V = PointerTarget!T; | else | alias V = T; | template hasDerivative(string member) | { | static if (hasUDA!(__traits(getMember, V, member), Derivative)) | enum hasDerivative = variables == getUDAs!(__traits(getMember, V, member), Derivative)[0].variables; | else | enum hasDerivative = false; | } | static if (anySatisfy!(hasDerivative, __traits(allMembers, V))) | { | static foreach (member; __traits(allMembers, V)) | { | static if (hasDerivative!member) | { | static if (hasUDA!(__traits(getMember, V, member), Minus)) 2| return -__traits(getMember, value, member); | else 10| return __traits(getMember, value, member); | } | } | } | else | static if (strict) | { | static assert(0, Unqual!V.stringof ~ "'_" ~ variables.stringof ~ " not found"); | } | else | { | return double.nan; | } | } | } | } | else | { | import mir.ndslice.sorting: sort; | alias getDerivative = .getDerivative!(variables.sort, strict); | } |} | |/++ |Evaluates partial derivatives and function value, if any, for a user-provided set of partial derivatives. | The derivative set can be defined with $(LREF Derivative) and $(LREF Minus) UDAs. |Params: | D = type of the requested set of partial derivatives | strict = The parameter is used when the expression can't evaluate the derivative. If true, prints error at compile-time; otherwise, the corresponding member is set to NaN. | derivatives = a structure that holds set of partial derivatives (optional) | expression = expression |+/ |void setDerivatives(bool strict = true, D, E)(scope ref D derivatives, E expression) |{ | static if (__traits(hasMember, D, "setDerivatives")) | return derivatives.setDerivatives!strict(expression); | else | { | import std.traits: getUDAs, hasUDA, isPointer, PointerTarget; | | static foreach (member; __traits(allMembers, D)) | { | static if (hasUDA!(__traits(getMember, D, member), Derivative)) | { | static if (hasUDA!(__traits(getMember, derivatives, member), Minus)) 1| __traits(getMember, derivatives, member) = -expression.getDerivative!(getUDAs!(__traits(getMember, derivatives, member), Derivative)[0].variables, strict); | else 3| __traits(getMember, derivatives, member) = expression.getDerivative!(getUDAs!(__traits(getMember, derivatives, member), Derivative)[0].variables, strict); | } | } | } |} | |/// ditto |template setDerivatives(D, bool strict = true) |{ | /// | D setDerivatives(E)(E expression) | { 1| D ret; 1| .setDerivatives!strict(ret, expression); 1| return ret; | } |} | |private auto removeVariable(DependsOn variables, string variable) |{ 0000000| string[] ret; 0000000| foreach (v; variables.variables) 0000000| if (v != variable) 0000000| ret ~= v; 0000000| return ret.DependsOn; |} | |private template ComposeAt(F, G, string position) | if (Dependencies!F.contains(position)) |{ | @(Dependencies!F.removeVariable(position) ~ Dependencies!G) | struct ComposeAt | { | /// | F f; | /// | G g; | | /// | template getDerivative(string[] variables, bool strict = true) | { | static if (Dependencies!(typeof(this)).containsAll(variables)) | /// | auto ref getDerivative() const @property | { | static if (!Dependencies!G.containsAny(variables)) 6| return f.getDerivative!(variables, strict); | else | { | static if (Dependencies!F.contains(variables[0]) && variables[0] != position) | auto a = f.derivativeOf!(variables[0]).composeAt!position(g).getDerivative!(variables[1 .. $], strict); | else | enum a = 0; | static if (Dependencies!G.contains(variables[0])) 5| auto b = (f.derivativeOf!position.composeAt!position(g) * g.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); | else | enum b = 0; 5| return a + b; | } | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |/++ |Given a compiletime variable name `v` and two expressions `F(v, U)` and `G(Y)`, | constructs a composition `F ∘ G (U, Y)` as `v = G`. | |Params: | position = name of the variable to compose functions at. |+/ |template composeAt(string position) |{ | /++ | Params: | f = F expression | g = G expression | +/ | auto composeAt(F, G)(const F f, const G g) | { 6| return ComposeAt!(F, G, position)(f, g); | } |} | |private template DerivativeOf(T, string variable) |{ | @Dependencies!T | struct DerivativeOf | { | // Underlying expression | T underlying; | | @optmath: | | /// | auto ref getDerivative(string[] variables, bool strict = true)() const @property | { 67| return underlying.getDerivative!(variable ~ variables, strict); | } | | mixin ediffOperators; | } |} | |/++ |Constructs a partial derivative of one order. | The function can be applied in any stage any number of times. | The function does NOT evaluate the expression. |+/ |template derivativeOf(string variable) |{ |@optmath: | | /++ | Params: | value = expression | +/ | auto derivativeOf(T)(const T value) @property | { | static if (isPointer!T) 1| return DerivativeOf!(const(PointerTarget!T)*, variable)(value); | else 60| return DerivativeOf!(T, variable)(value); | } |} | |/++ |A double precision constant with an immidiate value |+/ |@DependsOn() |struct Const |{ | /// Immidiate value | double value; | | template getDerivative(string[] variables, bool strict = true) | { | static if (variables.length == 0) | alias getDerivative = value; | else | enum double getDerivative = 0; | } | | mixin ediffOperators; |} | |/++ |A double precision named variable with an immidiate value |+/ |template Var(string name) |{ | /// | @DependsOn([name]) | struct Var | { | /// Immidiate value | double value; | | template getDerivative(string[] variables, bool strict = true) | { | static if (variables.length == 0) | alias getDerivative = value; | else | enum double getDerivative = variables == [name]; | } | | mixin ediffOperators; | } |} | |private template Powi(int power, T) | if (power) |{ | @Dependencies!T | struct Powi | { | T value; | double coefficient = 1; | | template getDerivative(string[] variables, bool strict = true) | { | @optmath: | static if (Dependencies!(typeof(this)).containsAll(variables)) | auto getDerivative() const @property | { | static if (variables.length == 0) | { | static if (power == 0) | return coefficient; | else | static if (power == 1) 12| return coefficient * value.getFunctionValue!strict; | else | static if (power == 2) 8| return coefficient * value.getFunctionValue!strict ^^ 2; | else | static if (power == -1) 10| return coefficient / value.getFunctionValue!strict; | else | static if (power == -2) 38| return coefficient / value.getFunctionValue!strict ^^ 2; | else 6| return coefficient * mir.math.common.powi(value.getFunctionValue!strict, power); | } | else | { | static if (power == 1) 1| auto v = coefficient.Const; | else 25| auto v = Powi!(power - 1, T)(value, coefficient * power); | static if (is(T == Var!(variables[0]))) 22| return v.getDerivative!(variables[1 .. $], strict); | else 4| return (v * value.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); | } | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |/++ |Constructs of the expression raised to the specified (positive or negative) compile-time integral power. | |Params: | power = integral power (compile-time) | value = expression |+/ |auto powi(int power, T)(const T value) | if (is(T == struct)) |{ | static if (power == 0) | return 1.Const; | else 9| return Powi!(power, T)(value); |} | |private template Exp(T) |{ | @Dependencies!T | struct Exp | { | /// Power function | T power; | | template getDerivative(string[] variables, bool strict = true) | { | @optmath: | static if (Dependencies!(typeof(this)).containsAll(variables)) | auto getDerivative() const @property | { | static if (variables.length == 0) 33| return mir.math.common.exp(power.getFunctionValue!strict); | else 5| return (this * power.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |/++ |Constructs the natural exponent of the expression. | |Params: | power = expression |+/ |auto exp(T)(const T power) | if (is(T == struct)) |{ 2| return Exp!T(power); |} | |private template Log(T) |{ | @Dependencies!T | struct Log | { | T value; | | template getDerivative(string[] variables, bool strict = true) | { | @optmath: | static if (Dependencies!(typeof(this)).containsAll(variables)) | auto getDerivative() const @property | { | static if (variables.length == 0) 1| return mir.math.common.log(value.getFunctionValue!strict); | else 1| return (value.derivativeOf!(variables[0]) / value).getDerivative!(variables[1 .. $], strict); | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |/++ |Constructs the natural logarithm of the expression. | |Params: | value = expression |+/ |auto log(T)(const T value) | if (is(T == struct)) |{ 2| return Log!T(value); |} | |private template Sqrt(T) |{ | @Dependencies!T | struct Sqrt | { | T value; | | template getDerivative(string[] variables, bool strict = true) | { | @optmath: | static if (Dependencies!(typeof(this)).containsAll(variables)) | auto getDerivative() const @property | { | static if (variables.length == 0) 3| return mir.math.common.sqrt(value.getFunctionValue!strict); | else 1| return (Powi!(-1, Sqrt!T)(this, 0.5) * value.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |/++ |Constructs the square root of the expression. | |Params: | value = expression |+/ |auto sqrt(T)(const T value) | if (is(T == struct)) |{ 3| return Sqrt!T(value); |} | |private template NormalCDF(T) |{ | @Dependencies!T | struct NormalCDF | { | /// Square root argument function | T value; | | template getDerivative(string[] variables, bool strict = true) | { | @optmath: | static if (Dependencies!(typeof(this)).containsAll(variables)) | auto getDerivative() const @property | { | import mir.math.func.normal: normalCDF, SQRT2PIINV; | static if (variables.length == 0) 1| return normalCDF(value.getFunctionValue!strict); | else 1| return (SQRT2PIINV.Const * Powi!(2, T)(value, -0.5).exp * value.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); | } | else | enum double getDerivative = 0; | } | | mixin ediffOperators; | } |} | |/++ |Constructs the cumulative normal distribution function of the expression | |Params: | value = expression |+/ |auto normalCDF(T)(const T value) | if (is(T == struct)) |{ 2| return NormalCDF!T(value); |} source/mir/ediff.d is 80% covered <<<<<< EOF # path=source-mir-format.lst |/++ |$(H1 @nogc Formatting Utilities) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |+/ |module mir.format; | |import std.traits; |import mir.primitives: isOutputRange; | |///Scalar styles. |enum StringStyle |{ | /// Uninitialized style | none, | /// Literal block style, `|` | longMultiLine, | /// Folded block style, `>` | longSingleLine, | /// Plain scalar | plain, | /// Single quoted scalar | asSingleQuoted, | /// Double quoted scalar | asEscapedString, |} | |/// Collection styles |enum CollectionStyle |{ | /// Uninitialized style | none, | /// Block style | block, | /// Flow style | flow, |} | |/// `mir.conv: to` extension. |version(mir_test) |@safe pure @nogc |unittest |{ | import mir.conv: to; | import mir.small_string; | alias S = SmallString!32; | | // Floating-point numbers are formatted to | // the shortest precise exponential notation. 1| assert(123.0.to!S == "123.0"); 1| assert(123.to!(immutable S) == "123"); 1| assert(true.to!S == "true"); 1| assert(true.to!string == "true"); | 1| assert((cast(S)"str")[] == "str"); |} | |/// `mir.conv: to` extension. |version(mir_test) |@safe pure |unittest |{ | import mir.conv: to; | import mir.small_string; | alias S = SmallString!32; | 1| auto str = S("str"); 1| assert(str.to!(const(char)[]) == "str"); // GC allocated result 1| assert(str.to!(char[]) == "str"); // GC allocated result |} | |/// ditto |version(mir_test) |@safe pure |unittest |{ | import mir.conv: to; | import mir.small_string; | alias S = SmallString!32; | | // Floating-point numbers are formatted to | // the shortest precise exponential notation. 1| assert(123.0.to!string == "123.0"); 1| assert(123.to!(char[]) == "123"); | 1| assert(S("str").to!string == "str"); // GC allocated result |} | |/// Concatenated string results |string text(string separator = "", Args...)(auto scope ref const Args args) | if (Args.length > 0) |{ | import mir.utility: _expect; | | static if (Args.length == 1) | { | static if (is(immutable Args[0] == immutable typeof(null))) | { 2| return "null"; | } | else | static if (is(Args[0] == enum)) | { | import mir.enums: getEnumIndex, enumStrings; | uint id = void; | if (getEnumIndex(args[0], id)._expect(true)) | return enumStrings!(Args[0])[id]; | assert(0); | } | else | static if (is(Unqual!(Args[0]) == bool)) | { 1| return args[0] ? "true" : "false"; | } | else | static if (is(args[0].toString : string)) | { | return args[0].toString; | } | else | { | import mir.appender: scopedBuffer; 14| auto buffer = scopedBuffer!char; 7| buffer.print(args[0]); 7| return buffer.data.idup; | } | } | else | { | import mir.appender: scopedBuffer; 4| auto buffer = scopedBuffer!char; 11| foreach (i, ref arg; args) | { 11| buffer.print(arg); | static if (separator.length && i + 1 < args.length) | { 4| buffer.printStaticString!char(separator); | } | } 2| return buffer.data.idup; | } |} | |/// |version(mir_test) |@safe pure nothrow unittest |{ 1| const i = 100; 1| assert(text("str ", true, " ", i, " ", 124.1) == "str true 100 124.1", text("str ", true, " ", 100, " ", 124.1)); 1| assert(text!" "("str", true, 100, 124.1, null) == "str true 100 124.1 null"); 1| assert(text(null) == "null", text(null)); |} | |import mir.format_impl; | |/// |struct GetData {} | |/// |enum getData = GetData(); | |/++ |+/ |struct StringBuf(C, uint scopeSize = 256) | if (is(C == char) || is(C == wchar) || is(C == dchar)) |{ | import mir.appender: ScopedBuffer; | | /// | ScopedBuffer!(C, scopeSize) buffer; | | /// | alias buffer this; | | /// | mixin StreamFormatOp!C; |} | |///ditto |auto stringBuf(C = char, uint scopeSize = 256)() | @trusted pure nothrow @nogc @property | if (is(C == char) || is(C == wchar) || is(C == dchar)) |{ 21| StringBuf!(C, scopeSize) buffer = void; 21| buffer.initialize; 21| return buffer; |} | |/++ |+/ |mixin template StreamFormatOp(C) |{ | /// | ref typeof(this) opBinary(string op : "<<", T)(scope ref const T c) scope | { 13| print!C(this, c); 13| return this; | } | | /// | ref typeof(this) opBinary(string op : "<<", T)(scope const T c) scope | { 19| print!C(this, c); 19| return this; | } | | /// ditto | const(C)[] opBinary(string op : "<<", T : GetData)(scope const T c) scope | { 12| return buffer.data; | } |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ 1| auto name = "D"; 1| auto ver = 2.0; 1| assert(stringBuf << "Hi " << name << ver << "!\n" << getData == "Hi D2.0!\n"); |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ 1| auto name = "D"w; 1| auto ver = 2; 1| assert(stringBuf!wchar << "Hi "w << name << ver << "!\n"w << getData == "Hi D2!\n"w); |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ 1| auto name = "D"d; 1| auto ver = 2UL; 1| assert(stringBuf!dchar << "Hi "d << name << ver << "!\n"d << getData == "Hi D2!\n"d); |} | |@safe pure nothrow @nogc |version (mir_test) unittest |{ 1| assert(stringBuf << -1234567890 << getData == "-1234567890"); |} | |/++ |Mir's numeric format specification | |Note: the specification isn't complete an may be extended in the future. |+/ |struct NumericSpec |{ | /// | enum Format | { | /++ | Human-frindly precise output. | Examples: `0.000001`, `600000.0`, but `1e-7` and `6e7`. | +/ | human, | /++ | Precise output with explicit exponent. | Examples: `1e-6`, `6e6`, `1.23456789e-100`. | +/ | exponent, | } | | /// | Format format; | | /// Default valus is '\0' (no separators) | char separatorChar = '\0'; | | /// Defaults to 'e' | char exponentChar = 'e'; | | /// Adds '+' to positive numbers and `+0`. | bool plus; | | /// Separator count | ubyte separatorCount = 3; | | /++ | Precise output with explicit exponent. | Examples: `1e-6`, `6e6`, `1.23456789e-100`. | +/ | enum NumericSpec exponent = NumericSpec(Format.exponent); | | /++ | Human-frindly precise output. | +/ | enum NumericSpec human = NumericSpec(Format.human); |} | |// 16-bytes |/// C's compatible format specifier. |struct FormatSpec |{ | /// | bool dash; | /// | bool plus; | /// | bool space; | /// | bool hash; | /// | bool zero; | /// | char format = 's'; | /// | char separator = '\0'; | /// | ubyte unitSize; | /// | int width; | /// | int precision = -1; |} | |/++ |+/ |enum SwitchLU : bool |{ | /// | lower, | /// | upper, |} | |/++ |Wrapper to format floating point numbers using C's library. |+/ |struct FormattedFloating(T) | if(is(T == float) || is(T == double) || is(T == real)) |{ | /// | T value; | /// | FormatSpec spec; | | /// | void toString(C = char, W)(scope ref W w) scope const | if (isSomeChar!C) | { | C[512] buf = void; | auto n = printFloatingPoint(value, spec, buf); | w.put(buf[0 .. n]); | } |} | |/// ditto |FormattedFloating!T withFormat(T)(const T value, FormatSpec spec) |{ | version(LDC) pragma(inline); | return typeof(return)(value, spec); |} | |/++ |+/ |struct HexAddress(T) | if (isUnsigned!T && !is(T == enum)) |{ | /// | T value; | /// | SwitchLU switchLU = SwitchLU.upper; | | /// | void toString(C = char, W)(scope ref W w) scope const | if (isSomeChar!C) | { | enum N = T.sizeof * 2; | static if(isFastBuffer!W) | { | w.advance(printHexAddress(value, w.getStaticBuf!N, cast(bool) switchLU)); | } | else | { 4| C[N] buf = void; 4| printHexAddress(value, buf, cast(bool) switchLU); 4| w.put(buf[]); | } | } |} | |///ditto |HexAddress!T hexAddress(T)(const T value, SwitchLU switchLU = SwitchLU.upper) | if (isUnsigned!T && !is(T == enum)) |{ 2| return typeof(return)(value, switchLU); |} | |/++ |Escaped string formats |+/ |enum EscapeFormat |{ | /// JSON escaped string format | json, | /// Amzn Ion CLOB format | ionClob, | /// Amzn Ion symbol format | ionSymbol, | /// Amzn Ion string format | ion, |} | |enum escapeFormatQuote(EscapeFormat escapeFormat) = escapeFormat == EscapeFormat.ionSymbol ? '\'' : '\"'; | |/++ |+/ |void printEscaped(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope ref W w, scope const(C)[] str) | if (isOutputRange!(W, C)) |{ | import mir.utility: _expect; 99| foreach (C c; str) | { 52| if (_expect(c == escapeFormatQuote!escapeFormat || c == '\\', false)) 3| goto E; 24| if (_expect(c < ' ', false)) 10| goto C; | static if (escapeFormat == EscapeFormat.ionClob) | { | if (c >= 127) | goto A; | } | P: 14| w.put(c); 14| continue; | E: | { 12| C[2] pair; 12| pair[0] = '\\'; 12| pair[1] = c; 12| w.printStaticString!C(pair); 12| continue; | } | C: 10| switch (c) | { | static if (escapeFormat != EscapeFormat.json) | { 1| case '\0': 1| c = '0'; 1| goto E; 1| case '\a': 1| c = 'a'; 1| goto E; 1| case '\v': 1| c = 'v'; 1| goto E; | } 1| case '\b': 1| c = 'b'; 1| goto E; 1| case '\t': 1| c = 't'; 1| goto E; 2| case '\n': 2| c = 'n'; 2| goto E; 1| case '\f': 1| c = 'f'; 1| goto E; 1| case '\r': 1| c = 'r'; 1| goto E; 1| default: | A: | static if (escapeFormat == EscapeFormat.json) | put_uXXXX!C(w, cast(char)c); | else 1| put_xXX!C(w, cast(char)c); | } | } 6| return; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | import mir.format: stringBuf; 2| auto w = stringBuf; 1| w.printEscaped("Hi \a\v\0\f\t\b \\\r\n" ~ `"@nogc"`); 1| assert(w.data == `Hi \a\v\0\f\t\b \\\r\n\"@nogc\"`); 1| w.reset; 1| w.printEscaped("\x03"); 1| assert(w.data == `\x03`); |} | |/// |void printReplaced(C, W)(scope ref W w, scope const(C)[] str, C c, scope const(C)[] to) |{ | import mir.string: scanLeftAny; | 3| while (str.length) | { 3| auto tailLen = str.scanLeftAny(c).length; 3| print(w, str[0 .. $ - tailLen]); 3| if (tailLen == 0) 1| break; 2| print(w, to); 2| str = str[$ - tailLen + 1 .. $]; | } |} | |/// |@safe pure nothrow |unittest |{ | import mir.test: should; 2| auto csv = stringBuf; 1| csv.put('"'); 1| csv.printReplaced(`some string with " double quotes "!`, '"', `""`); 1| csv.put('"'); 1| csv.data.should == `"some string with "" double quotes ""!"`; |} | |/++ |Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters. |+/ |void put_xXX(C = char, W)(scope ref W w, char c) | if (isSomeChar!C) |{ 1| ubyte[2] spl; 1| spl[0] = c >> 4; 1| spl[1] = c & 0xF; 1| C[4] buffer; 1| buffer[0] = '\\'; 1| buffer[1] = 'x'; 1| buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); 1| buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); 2| return w.printStaticString(buffer); |} | |/++ |Decodes `char` `c` to the form `\u00XX`, where `XX` is 2 hexadecimal characters. |+/ |void put_uXXXX(C = char, W)(scope ref W w, char c) | if (isSomeChar!C) |{ | ubyte[2] spl; | spl[0] = c >> 4; | spl[1] = c & 0xF; | C[6] buffer; | buffer[0] = '\\'; | buffer[1] = 'u'; | buffer[2] = '0'; | buffer[3] = '0'; | buffer[4] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); | buffer[5] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); | return w.printStaticString(buffer); |} | |/++ |Decodes ushort `c` to the form `\uXXXX`, where `XXXX` is 2 hexadecimal characters. |+/ |void put_uXXXX(C = char, W)(scope ref W w, ushort c) | if (isSomeChar!C) |{ 1| ubyte[4] spl; 1| spl[0] = (c >> 12) & 0xF; 1| spl[1] = (c >> 8) & 0xF; 1| spl[2] = (c >> 4) & 0xF; 1| spl[3] = c & 0xF; 1| C[6] buffer; 1| buffer[0] = '\\'; 1| buffer[1] = 'u'; 1| buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); 1| buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); 1| buffer[4] = cast(ubyte)(spl[2] < 10 ? spl[2] + '0' : spl[2] - 10 + 'A'); 1| buffer[5] = cast(ubyte)(spl[3] < 10 ? spl[3] + '0' : spl[3] - 10 + 'A'); 2| return w.printStaticString(buffer); |} | |/++ |Decodes uint `c` to the form `\UXXXXXXXX`, where `XXXXXXXX` is 2 hexadecimal characters. |+/ |void put_UXXXXXXXX(C = char, W)(scope ref W w, uint c) | if (isSomeChar!C) |{ 1| w.printStaticString!C(`\U`); 1| w.print!C(HexAddress!uint(cast(uint)c)); |} | |/// |void printElement(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope ref W w, scope const(C)[] c) | if (isSomeChar!C) |{ | static immutable C[1] quote = '\"'; 4| w.printStaticString!C(quote); 4| w.printEscaped!(C, escapeFormat)(c); 4| w.printStaticString!C(quote); |} | |/// |void printElement(C = char, EscapeFormat escapeFormat = EscapeFormat.ion, W, T)(scope ref W w, scope auto ref const T c) | if (!isSomeString!T) |{ 8| return w.print!C(c); |} | |/++ |Multiargument overload. |+/ |void print(C = char, W, Args...)(scope ref W w, auto scope ref const Args args) | if (isSomeChar!C && Args.length > 1) |{ | foreach(i, ref c; args) | static if (i < Args.length - 1) | w.print!C(c); | else | return w.print!C(c); |} | |/// Prints enums |void print(C = char, W, T)(scope ref W w, scope const T c) @nogc | if (isSomeChar!C && is(T == enum)) |{ | import mir.enums: getEnumIndex, enumStrings; | import mir.utility: _expect; | | static assert(!is(OriginalType!T == enum)); 1| uint index = void; 1| if (getEnumIndex(c, index)._expect(true)) | { 1| w.put(enumStrings!T[index]); 1| return; | } | static immutable C[] str = T.stringof ~ "("; 0000000| w.put(str[]); 0000000| print!C(w, cast(OriginalType!T) c); 0000000| w.put(')'); 0000000| return; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | enum Flag | { | no, | yes, | } | | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| w.print(Flag.yes); 1| assert(w.data == "yes"); |} | |/// Prints boolean |void print(C = char, W)(scope ref W w, bool c) | if (isSomeChar!C) |{ | enum N = 5; | static if(isFastBuffer!W) | { | w.advance(printBoolean(c, w.getStaticBuf!N)); | } | else | { 4| C[N] buf = void; 4| auto n = printBoolean(c, buf); 4| w.put(buf[0 .. n]); | } 4| return; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| w.print(true); 1| assert(w.data == `true`); 1| w.reset; 1| w.print(false); 1| assert(w.data == `false`); |} | |/// Prints associative array |pragma(inline, false) |void print(C = char, W, V, K)(scope ref W w, scope const V[K] c) | if (isSomeChar!C) |{ | enum C left = '['; | enum C right = ']'; | enum C[2] sep = ", "; | enum C[2] mid = ": "; 1| w.put(left); 1| bool first = true; 1| foreach (ref key, ref value; c) | { 2| if (!first) 1| w.printStaticString!C(sep); 2| first = false; 2| w.printElement!C(key); 2| w.printStaticString!C(mid); 2| w.printElement!C(value); | } 1| w.put(right); 1| return; |} | |/// |@safe pure |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| w.print(["a": 1, "b": 2]); 2| assert(w.data == `["a": 1, "b": 2]` || w.data == `["b": 2, "a": 1]`); |} | |/// Prints null |void print(C = char, W, V)(scope ref W w, const V c) | if (is(V == typeof(null))) |{ | enum C[4] Null = "null"; 4| return w.printStaticString!C(Null); |} | |/// |@safe pure @nogc |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| w.print(null); 1| assert(w.data == `null`); |} | |/// Prints array |pragma(inline, false) |void printArray(C = char, W, T)(scope ref W w, | scope const(T)[] c, | scope const(C)[] lb = "[", | scope const(C)[] rb = "]", | scope const(C)[] sep = ", ", |) | if (isSomeChar!C && !isSomeChar!T) |{ 1| w.put(lb); 1| bool first = true; 9| foreach (ref e; c) | { 2| if (!first) 1| w.put(sep); 2| first = false; 2| printElement!C(w, e); | } 1| w.put(rb); 1| return; |} | |/// ditto |pragma(inline, false) |void print(C = char, W, T)(scope ref W w, | scope const(T)[] c, |) | if (isSomeChar!C && !isSomeChar!T) |{ 2| return printArray(w, c); |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| string[2] array = ["a\na", "b"]; 1| w.print(array[]); 1| assert(w.data == `["a\na", "b"]`); |} | |/// Prints array as hex values |pragma(inline, false) |void printHexArray(C = char, W, T)(scope ref W w, | scope const(T)[] c, | scope const(C)[] lb = "", | scope const(C)[] rb = "", | scope const(C)[] sep = " ", |) | if (isSomeChar!C && !isSomeChar!T && isUnsigned!T) |{ 1| w.put(lb); 1| bool first = true; 9| foreach (ref e; c) | { 2| if (!first) 1| w.put(sep); 2| first = false; 2| printElement!C(w, e.hexAddress); | } 1| w.put(rb); 1| return; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | import mir.test; | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| ubyte[2] array = [0x34, 0x32]; 1| w.printHexArray(array[]); 1| w.data.should == `34 32`; |} | |/// Prints escaped character in the form `'c'`. |pragma(inline, false) |void print(C = char, W)(scope ref W w, char c) | if (isSomeChar!C) |{ 4| w.put('\''); 4| if (c >= ubyte.max) | { 0000000| w.printStaticString!C(`\u`); 0000000| w.print!C(HexAddress!ubyte(cast(ubyte)c)); | } | else 4| if (c >= 0x20) | { 3| if (c < 0x7F) | { 3| if (c == '\'' || c == '\\') | { | L: 2| w.put('\\'); | } 3| w.put(c); | } | else | { | M: 1| w.printStaticString!C(`\x`); 1| w.print!C(HexAddress!ubyte(cast(ubyte)c)); | } | } | else | { 1| switch(c) | { 3| case '\n': c = 'n'; goto L; 0000000| case '\r': c = 'r'; goto L; 0000000| case '\t': c = 't'; goto L; 0000000| case '\a': c = 'a'; goto L; 0000000| case '\b': c = 'b'; goto L; 0000000| case '\f': c = 'f'; goto L; 0000000| case '\v': c = 'v'; goto L; 0000000| case '\0': c = '0'; goto L; 0000000| default: goto M; | } | } 4| w.put('\''); 4| return; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| w.print('\n'); 1| w.print('\''); 1| w.print('a'); 1| w.print('\xF4'); 1| assert(w.data == `'\n''\'''a''\xF4'`); |} | |/// Prints escaped character in the form `'c'`. |pragma(inline, false) |void print(C = char, W)(scope ref W w, dchar c) | if (isSomeChar!C) |{ | import std.uni: isGraphical; 3| if (c <= ubyte.max) 0000000| return print(w, cast(char) c); 3| w.put('\''); 3| if (c.isGraphical) | { | import std.utf: encode; 1| C[dchar.sizeof / C.sizeof] buf; 1| print!C(w, buf[0 .. encode(buf, c)]); | } | else 2| if (c <= ushort.max) | { 1| w.put_uXXXX!C(cast(ushort)c); | } | else | { 1| w.put_UXXXXXXXX!C(c); | } 3| w.put('\''); |} | |/// |@safe pure |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| w.print('\u23F4'); 1| w.print('щ'); 1| w.print('\U0010FFFE'); 1| assert(w.data == `'\u23F4''щ''\U0010FFFE'`); |} | |/// Prints some string |void print(C = char, W)(scope ref W w, scope const(C)[] c) | if (isSomeChar!C) |{ 31| w.put(c); 31| return; |} | |/// Prints integers |void print(C = char, W, I)(scope ref W w, const I c) | if (isSomeChar!C && isIntegral!I && !is(I == enum)) |{ | static if (I.sizeof == 16) | enum N = 39; | else | static if (I.sizeof == 8) | enum N = 20; | else | enum N = 10; 32| C[N + !__traits(isUnsigned, I)] buf = void; | static if (__traits(isUnsigned, I)) 1| auto n = printUnsignedToTail(c, buf); | else 31| auto n = printSignedToTail(c, buf); 32| w.put(buf[$ - n .. $]); 32| return; |} | |/// Prints floating point numbers |void print(C = char, W, T)(scope ref W w, const T c, NumericSpec spec = NumericSpec.init) | if(isSomeChar!C && is(T == float) || is(T == double) || is(T == real)) |{ | import mir.bignum.decimal; 68| auto decimal = Decimal!(T.mant_dig < 64 ? 1 : 2)(c); 68| decimal.toString(w, spec); 68| return; |} | |/// Human friendly precise output (default) |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ 1| auto spec = NumericSpec.human; 2| auto buffer = stringBuf; | | void check(double num, string value) | { 59| buffer.print(num, spec); 59| assert(buffer.data == value, value); 59| buffer.reset; | } | 1| check(-0.0, "-0.0"); 1| check(0.0, "0.0"); 1| check(-0.01, "-0.01"); 1| check(0.0125, "0.0125"); 1| check(0.000003, "0.000003"); 1| check(-3e-7, "-3e-7"); 1| check(123456.0, "123456.0"); 1| check(123456.1, "123456.1"); 1| check(12.3456, "12.3456"); 1| check(-0.123456, "-0.123456"); 1| check(0.1234567, "0.1234567"); 1| check(0.01234567, "0.01234567"); 1| check(0.001234567, "0.001234567"); 1| check(1.234567e-4, "1.234567e-4"); 1| check(-1234567.0, "-1.234567e+6"); 1| check(123456.7890123, "123456.7890123"); 1| check(1234567.890123, "1.234567890123e+6"); 1| check(1234567890123.0, "1.234567890123e+12"); 1| check(0.30000000000000004, "0.30000000000000004"); 1| check(0.030000000000000002, "0.030000000000000002"); 1| check(0.0030000000000000005, "0.0030000000000000005"); 1| check(3.0000000000000003e-4, "3.0000000000000003e-4"); 1| check(+double.nan, "nan"); 1| check(-double.nan, "nan"); 1| check(+double.infinity, "+inf"); 1| check(-double.infinity, "-inf"); | 1| spec.separatorChar = ','; | 1| check(-0.0, "-0.0"); 1| check(0.0, "0.0"); 1| check(-0.01, "-0.01"); 1| check(0.0125, "0.0125"); 1| check(0.000003, "0.000003"); 1| check(-3e-7, "-3e-7"); 1| check(123456.0, "123,456.0"); 1| check(123456e5, "12,345,600,000.0"); 1| check(123456.1, "123,456.1"); 1| check(12.3456, "12.3456"); 1| check(-0.123456, "-0.123456"); 1| check(0.1234567, "0.1234567"); 1| check(0.01234567, "0.01234567"); 1| check(0.001234567, "0.001234567"); 1| check(1.234567e-4, "0.0001234567"); 1| check(-1234567.0, "-1,234,567.0"); 1| check(123456.7890123, "123,456.7890123"); 1| check(1234567.890123, "1,234,567.890123"); 1| check(123456789012.0, "123,456,789,012.0"); 1| check(1234567890123.0, "1.234567890123e+12"); 1| check(0.30000000000000004, "0.30000000000000004"); 1| check(0.030000000000000002, "0.030000000000000002"); 1| check(0.0030000000000000005, "0.0030000000000000005"); 1| check(3.0000000000000003e-4, "0.00030000000000000003"); 1| check(3.0000000000000005e-6, "0.0000030000000000000005"); 1| check(3.0000000000000004e-7, "3.0000000000000004e-7"); 1| check(+double.nan, "nan"); 1| check(-double.nan, "nan"); 1| check(+double.infinity, "+inf"); 1| check(-double.infinity, "-inf"); | 1| spec.separatorChar = '_'; 1| spec.separatorCount = 2; 1| check(123456e5, "1_23_45_60_00_00.0"); | 1| spec.plus = true; 1| check(0.0125, "+0.0125"); 1| check(-0.0125, "-0.0125"); |} | |/// Prints structs and unions |pragma(inline, false) |void print(C = char, W, T)(scope ref W w, scope ref const T c) | if (isSomeChar!C && is(T == struct) || is(T == union) && !is(T : NumericSpec)) |{ | import mir.algebraic: isVariant; | static if (__traits(hasMember, T, "toString")) | { | static if (is(typeof(c.toString!C(w)))) 12| c.toString!C(w); | else | static if (isVariant!T || is(typeof(c.toString(w)))) 2| c.toString(w); | else | static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) 2| c.toString((scope const(C)[] s) { w.put(s); }); | else | static if (is(typeof(w.put(c.toString)))) 3| w.put(c.toString); | else | { | import std.format: FormatSpec; | FormatSpec!char fmt; | | static if (is(typeof(c.toString(w, fmt)))) | c.toString(w, fmt); | else | static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); }, fmt)))) | c.toString((scope const(C)[] s) { w.put(s); }, fmt); | else | // workaround for types with mutable toString | static if (is(typeof((*cast(T*)&c).toString(w, fmt)))) | (*cast(T*)&c).toString(w, fmt); | else | static if (is(typeof((*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); }, fmt)))) | (*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); }, fmt); | else | static if (is(typeof((*cast(T*)&c).toString(w)))) | (*cast(T*)&c).toString(w); | else | static if (is(typeof((*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); })))) | (*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); }); | else | static if (is(typeof(w.put((*cast(T*)&c).toString)))) | w.put((*cast(T*)&c).toString); | else | c.toString(w); | // static assert(0, T.stringof ~ ".toString definition is wrong: 'const' qualifier may be missing."); | } | 18| return; | } | else | static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) | { 1| scope const(C)[] string_of_c = c; 2| return w.print!C(string_of_c); | } | else | static if (hasIterableLightConst!T) | { | enum C left = '['; | enum C right = ']'; | enum C[2] sep = ", "; 2| w.put(left); 2| bool first = true; 16| foreach (ref e; c.lightConst) | { 4| if (!first) 2| printStaticString!C(w, sep); 4| first = false; 4| print!C(w, e); | } 2| w.put(right); 2| return; | } | else | { | enum C left = '('; | enum C right = ')'; | enum C[2] sep = ", "; 0000000| w.put(left); 0000000| foreach (i, ref e; c.tupleof) | { | static if (i) 0000000| w.printStaticString!C(sep); 0000000| print!C(w, e); | } 0000000| w.put(right); 0000000| return; | } |} | |/// ditto |// FUTURE: remove it |pragma(inline, false) |void print(C = char, W, T)(scope ref W w, scope const T c) | if (isSomeChar!C && is(T == struct) || is(T == union)) |{ 6| return print!(C, W, T)(w, c); |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ 1| static struct A { scope void toString(C, W)(scope ref W w) const { w.put(C('a')); } } 1| static struct S { scope void toString(W)(scope ref W w) const { w.put("s"); } } 1| static struct D { scope void toString(Dg)(scope Dg sink) const { sink("d"); } } 1| static struct F { scope const(char)[] toString()() const return { return "f"; } } | static struct G { const(char)[] s = "g"; alias s this; } | | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| assert(stringBuf << A() << S() << D() << F() << G() << getData == "asdfg"); |} | |/// Prints classes and interfaces |pragma(inline, false) |void print(C = char, W, T)(scope ref W w, scope const T c) | if (isSomeChar!C && is(T == class) || is(T == interface)) |{ | static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; })) | { 5| if (c is null) 0000000| return w.print(null); | else | static if (is(typeof(c.toString!C(w)))) | { 1| c.toString!C(w); 1| return; | } | else | static if (is(typeof(c.toString(w)))) | { 1| c.toString(w); 1| return; | } | else | static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) | { 2| c.toString((scope const(C)[] s) { w.put(s); }); 1| return; | } | else | static if (is(typeof(w.put(c.toString)))) | { 1| w.put(c.toString); 1| return; | } | else | static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) | { 1| scope const(C)[] string_of_c = c; 2| return w.print!C(string_of_c); | } | else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing."); | } | else | static if (hasIterableLightConst!T) | { | enum C left = '['; | enum C right = ']'; | enum C[2] sep = ", "; | w.put(left); | bool first = true; | foreach (ref e; c.lightConst) | { | if (!first) | w.printStaticString!C(sep); | first = false; | print!C(w, e); | } | w.put(right); | return; | } | else | { | w.put(T.stringof); | return; | } |} | |/// |@safe pure nothrow |version (mir_test) unittest |{ 1| static class A { void toString(C, W)(scope ref W w) const { w.put(C('a')); } } 1| static class S { void toString(W)(scope ref W w) const { w.put("s"); } } 1| static class D { void toString(Dg)(scope Dg sink) const { sink("d"); } } 1| static class F { const(char)[] toString()() const return { return "f"; } } | static class G { const(char)[] s = "g"; alias s this; } | 1| assert(stringBuf << new A() << new S() << new D() << new F() << new G() << getData == "asdfg"); |} | |/// |void printStaticString(C, size_t N, W)(scope ref W w, scope ref const C[N] c) | if (isSomeChar!C && is(C == char) || is(C == wchar) || is(C == dchar)) |{ | static if (isFastBuffer!W) | { | enum immutable(ForeachType!(typeof(w.getBuffer(size_t.init))))[] value = c; | w.getStaticBuf!(value.length) = value; | w.advance(c.length); | } | else | { 35| w.put(c[]); | } 35| return; |} | |private template hasIterableLightConst(T) |{ | static if (__traits(hasMember, T, "lightConst")) | { | enum hasIterableLightConst = isIterable!(ReturnType!((const T t) => t.lightConst)); | } | else | { | enum hasIterableLightConst = false; | } |} | |private ref C[N] getStaticBuf(size_t N, C, W)(scope ref W w) | if (isFastBuffer!W) |{ | auto buf = w.getBuffer(N); | assert(buf.length >= N); | return buf.ptr[0 .. N]; |} | |private @trusted ref C[N] getStaticBuf(size_t N, C)(return scope ref C[] buf) |{ | assert(buf.length >= N); | return buf.ptr[0 .. N]; |} | |template isFastBuffer(W) |{ | enum isFastBuffer = __traits(hasMember, W, "getBuffer") && __traits(hasMember, W, "advance"); |} | |/// |void printZeroPad(C = char, W, I)(scope ref W w, const I c, size_t minimalLength) | if (isSomeChar!C && isIntegral!I && !is(I == enum)) |{ | static if (I.sizeof == 16) | enum N = 39; | else | static if (I.sizeof == 8) | enum N = 20; | else | enum N = 10; 365| C[N + !__traits(isUnsigned, I)] buf = void; | static if (__traits(isUnsigned, I)) 256| auto n = printUnsignedToTail(c, buf); | else 109| auto n = printSignedToTail(c, buf); 365| sizediff_t zeros = minimalLength - n; | 365| if (zeros > 0) | { | static if (!__traits(isUnsigned, I)) | { 47| if (c < 0) | { 19| n--; 19| w.put(C('-')); | } | } 270| do w.put(C('0')); 270| while(--zeros); | } 365| w.put(buf[$ - n .. $]); 365| return; |} | |/// |version (mir_test) unittest |{ | import mir.appender; 2| auto w = scopedBuffer!char; | 1| w.printZeroPad(-123, 5); 1| w.put(' '); 1| w.printZeroPad(123, 5); | 1| assert(w.data == "-0123 00123"); |} | |/// |size_t printBoolean(C)(bool c, ref C[5] buf) | if(is(C == char) || is(C == wchar) || is(C == dchar)) |{ | version(LDC) pragma(inline, true); 4| if (c) | { 3| buf[0] = 't'; 3| buf[1] = 'r'; 3| buf[2] = 'u'; 3| buf[3] = 'e'; 3| return 4; | } | else | { 1| buf[0] = 'f'; 1| buf[1] = 'a'; 1| buf[2] = 'l'; 1| buf[3] = 's'; 1| buf[4] = 'e'; 1| return 5; | } |} | | |/// Prints pointers |void print(C = char, W, T)(scope ref W w, scope const T* c) |{ | import mir.enums: getEnumIndex, enumStrings; | import mir.utility: _expect; | if (c is null) | return w.print!C(null); | return w.print!C(HexAddress!size_t((()@trusted=>cast(size_t)cast(const void*)c)())); |} source/mir/format.d is 94% covered <<<<<< EOF # path=source-mir-format_impl.lst |/// |module mir.format_impl; | |import mir.format; | |@safe pure @nogc nothrow: | | |size_t printFloatingPointExtend(T, C)(T c, scope ref const FormatSpec spec, scope ref C[512] buf) @trusted |{ 0000000| char[512] cbuf = void; 0000000| return extendASCII(cbuf[].ptr, buf[].ptr, printFloatingPoint(cast(double)c, spec, cbuf)); |} | |size_t printFloatingPointGen(T)(T c, scope ref const FormatSpec spec, scope ref char[512] buf) @trusted | if(is(T == float) || is(T == double) || is(T == real)) |{ | import mir.math.common: copysign, fabs; 0000000| bool neg = copysign(1, c) < 0; 0000000| c = fabs(c); 0000000| char specFormat = spec.format; | version (CRuntime_Microsoft) | { | if (c != c || c.fabs == c.infinity) | { | size_t i; | char s = void; | if (copysign(1, c) < 0) | s = '-'; | else | if (spec.plus) | s = '+'; | else | if (spec.space) | s = ' '; | else | goto S; | buf[0] = s; | i = 1; | S: | static immutable char[3][2][2] special = [["inf", "INF"], ["nan", "NAN"]]; | auto p = &special[c != c][(specFormat & 0xDF) == specFormat][0]; | buf[i + 0] = p[0]; | buf[i + 1] = p[1]; | buf[i + 2] = p[2]; | return i + 3; | } | } | alias T = double; | static if (is(T == real)) | align(4) char[12] fmt = "%%%%%%*.*gL\0"; | else | align(4) char[12] fmt = "%%%%%%*.*g\0\0"; | 0000000| if (specFormat && specFormat != 's' && specFormat != 'g' && specFormat != 'G') | { 0000000| assert ( | specFormat == 'e' 0000000| || specFormat == 'E' 0000000| || specFormat == 'f' 0000000| || specFormat == 'F' 0000000| || specFormat == 'a' 0000000| || specFormat == 'A', "Wrong floating point format specifier."); 0000000| fmt[9] = specFormat; | } 0000000| uint fmtRevLen = 5; 0000000| if (spec.hash) fmt[fmtRevLen--] = '#'; 0000000| if (spec.space) fmt[fmtRevLen--] = ' '; 0000000| if (spec.zero) fmt[fmtRevLen--] = '0'; 0000000| if (spec.plus) fmt[fmtRevLen--] = '+'; 0000000| if (spec.dash) fmt[fmtRevLen--] = '-'; | | import core.stdc.stdio : snprintf; 0000000| ptrdiff_t res = assumePureSafe(&snprintf)((()@trusted =>buf.ptr)(), buf.length - 1, &fmt[fmtRevLen], spec.width, spec.precision, c); 0000000| assert (res >= 0, "snprintf failed to print a floating point number"); | import mir.utility: min; 0000000| return res < 0 ? 0 : min(cast(size_t)res, buf.length - 1); |} | |auto assumePureSafe(T)(T t) @trusted | // if (isFunctionPointer!T || isDelegate!T) |{ | import std.traits; | enum attrs = (functionAttributes!T | FunctionAttribute.pure_ | FunctionAttribute.safe) & ~FunctionAttribute.system; 0000000| return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; |} | |////////// FLOATING POINT ////////// | |size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref char[512] buf) |{ 0000000| return printFloatingPoint(cast(double)c, spec, buf); |} | |size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref char[512] buf) |{ 0000000| return printFloatingPointGen(c, spec, buf); |} | |size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref char[512] buf) |{ | version (CRuntime_Microsoft) | { | return printFloatingPoint(cast(double) c, spec, buf); | } | else | { 0000000| return printFloatingPointGen(c, spec, buf); | } |} | |size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref wchar[512] buf) |{ 0000000| return printFloatingPoint(cast(double)c, spec, buf); |} | |size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref wchar[512] buf) |{ 0000000| return printFloatingPointExtend(c, spec, buf); |} | |size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref wchar[512] buf) |{ | version (CRuntime_Microsoft) | { | return printFloatingPoint(cast(double) c, spec, buf); | } | else | { 0000000| return printFloatingPointExtend(c, spec, buf); | } |} | |size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref dchar[512] buf) |{ 0000000| return printFloatingPoint(cast(double)c, spec, buf); |} | |size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref dchar[512] buf) |{ 0000000| return printFloatingPointExtend(c, spec, buf); |} | |size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref dchar[512] buf) |{ | version (CRuntime_Microsoft) | { | return printFloatingPoint(cast(double) c, spec, buf); | } | else | { 0000000| return printFloatingPointExtend(c, spec, buf); | } |} | |nothrow: | 0000000|size_t printHexadecimal(uint c, ref char[8] buf, bool upper) { return printHexadecimalGen!(uint, char)(c, buf, upper); } 0000000|size_t printHexadecimal(ulong c, ref char[16] buf, bool upper) { return printHexadecimalGen!(ulong, char)(c, buf, upper); } |static if (is(ucent)) |size_t printHexadecimal(ucent c, ref char[32] buf, bool upper) { return printHexadecimalGen!(ucent, char)(c, buf, upper); } | 0000000|size_t printHexadecimal(uint c, ref wchar[8] buf, bool upper) { return printHexadecimalGen!(uint, wchar)(c, buf, upper); } 0000000|size_t printHexadecimal(ulong c, ref wchar[16] buf, bool upper) { return printHexadecimalGen!(ulong, wchar)(c, buf, upper); } |static if (is(ucent)) |size_t printHexadecimal(ucent c, ref wchar[32] buf, bool upper) { return printHexadecimalGen!(ucent, wchar)(c, buf, upper); } | 0000000|size_t printHexadecimal(uint c, ref dchar[8] buf, bool upper) { return printHexadecimalGen!(uint, dchar)(c, buf, upper); } 0000000|size_t printHexadecimal(ulong c, ref dchar[16] buf, bool upper) { return printHexadecimalGen!(ulong, dchar)(c, buf, upper); } |static if (is(ucent)) |size_t printHexadecimal(ucent c, ref dchar[32] buf, bool upper) { return printHexadecimalGen!(ucent, dchar)(c, buf, upper); } | |size_t printHexadecimalGen(T, C)(T c, ref C[T.sizeof * 2] buf, bool upper) @trusted |{ 0000000| if (c < 10) | { 0000000| buf[0] = cast(char)('0' + c); 0000000| return 1; | } | import mir.bitop: ctlz; 0000000| immutable hexString = upper ? hexStringUpper : hexStringLower; 0000000| size_t ret = cast(size_t) ctlz(c); 0000000| ret = (ret >> 2) + ((ret & 3) != 0); 0000000| size_t i = ret; | do | { 0000000| buf.ptr[--i] = hexStringUpper[c & 0xF]; 0000000| c >>= 4; | } 0000000| while(i); 0000000| return ret; |} | 3| size_t printHexAddress(ubyte c, ref char[2] buf, bool upper) { return printHexAddressGen!(ubyte, char)(c, buf, upper); } 0000000| size_t printHexAddress(ushort c, ref char[4] buf, bool upper) { return printHexAddressGen!(ushort, char)(c, buf, upper); } 1|size_t printHexAddress(uint c, ref char[8] buf, bool upper) { return printHexAddressGen!(uint, char)(c, buf, upper); } 0000000|size_t printHexAddress(ulong c, ref char[16] buf, bool upper) { return printHexAddressGen!(ulong, char)(c, buf, upper); } |static if (is(ucent)) |size_t printHexAddress(ucent c, ref char[32] buf, bool upper) { return printHexAddressGen!(ucent, char)(c, buf, upper); } | 0000000| size_t printHexAddress(ubyte c, ref wchar[2] buf, bool upper) { return printHexAddressGen!(ubyte, wchar)(c, buf, upper); } 0000000| size_t printHexAddress(ushort c, ref wchar[4] buf, bool upper) { return printHexAddressGen!(ushort, wchar)(c, buf, upper); } 0000000|size_t printHexAddress(uint c, ref wchar[8] buf, bool upper) { return printHexAddressGen!(uint, wchar)(c, buf, upper); } 0000000|size_t printHexAddress(ulong c, ref wchar[16] buf, bool upper) { return printHexAddressGen!(ulong, wchar)(c, buf, upper); } |static if (is(ucent)) |size_t printHexAddress(ucent c, ref wchar[32] buf, bool upper) { return printHexAddressGen!(ucent, wchar)(c, buf, upper); } | 0000000| size_t printHexAddress(ubyte c, ref dchar[2] buf, bool upper) { return printHexAddressGen!(ubyte, dchar)(c, buf, upper); } 0000000| size_t printHexAddress(ushort c, ref dchar[4] buf, bool upper) { return printHexAddressGen!(ushort, dchar)(c, buf, upper); } 0000000|size_t printHexAddress(uint c, ref dchar[8] buf, bool upper) { return printHexAddressGen!(uint, dchar)(c, buf, upper); } 0000000|size_t printHexAddress(ulong c, ref dchar[16] buf, bool upper) { return printHexAddressGen!(ulong, dchar)(c, buf, upper); } |static if (is(ucent)) |size_t printHexAddress(ucent c, ref dchar[32] buf, bool upper) { return printHexAddressGen!(ucent, dchar)(c, buf, upper); } | |size_t printHexAddressGen(T, C)(T c, ref C[T.sizeof * 2] buf, bool upper) |{ | static if (T.sizeof == 16) | { | printHexAddress(cast(ulong)(c >> 64), buf[0 .. 16], upper); | printHexAddress(cast(ulong) c, buf[16 .. 32], upper); | } | else | { 4| immutable hexString = upper ? hexStringUpper : hexStringLower; 40| foreach_reverse(ref e; buf) | { 14| e = hexStringUpper[c & 0xF]; 14| c >>= 4; | } | } 4| return buf.length; |} | |static immutable hexStringUpper = "0123456789ABCDEF"; |static immutable hexStringLower = "0123456789abcdef"; | 0000000|size_t printBufferShift(size_t length, size_t shift, scope char* ptr) { return printBufferShiftGen!char(length, shift, ptr); } 0000000|size_t printBufferShift(size_t length, size_t shift, scope wchar* ptr) { return printBufferShiftGen!wchar(length, shift, ptr); } 0000000|size_t printBufferShift(size_t length, size_t shift, scope dchar* ptr) { return printBufferShiftGen!dchar(length, shift, ptr); } | |size_t printBufferShiftGen(C)(size_t length, size_t shift, scope C* ptr) @trusted |{ 0000000| size_t i; 0000000| do ptr[i] = ptr[shift + i]; 0000000| while(++i < length); 0000000| return length; |} | 0000000|size_t printSigned(int c, scope ref char[11] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } 0000000|size_t printSigned(long c, scope ref char[21] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } |static if (is(cent)) |size_t printSigned(cent c, scope ref char[40] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } | 0000000|size_t printSigned(int c, scope ref wchar[11] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } 0000000|size_t printSigned(long c, scope ref wchar[21] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } |static if (is(cent)) |size_t printSigned(cent c, scope ref wchar[40] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } | 0000000|size_t printSigned(int c, scope ref dchar[11] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } 0000000|size_t printSigned(long c, scope ref dchar[21] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } |static if (is(cent)) |size_t printSigned(cent c, scope ref dchar[40] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } | | 134|size_t printSignedToTail(int c, scope ref char[11] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } 16|size_t printSignedToTail(long c, scope ref char[21] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } |static if (is(cent)) |size_t printSignedToTail(cent c, scope ref char[40] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } | 1|size_t printSignedToTail(int c, scope ref wchar[11] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } 0000000|size_t printSignedToTail(long c, scope ref wchar[21] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } |static if (is(cent)) |size_t printSignedToTail(cent c, scope ref wchar[40] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } | 0000000|size_t printSignedToTail(int c, scope ref dchar[11] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } 0000000|size_t printSignedToTail(long c, scope ref dchar[21] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } |static if (is(cent)) |size_t printSignedToTail(cent c, scope ref dchar[40] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } | |size_t printSignedGen(T, C, size_t N)(T c, scope ref C[N] buf, C sign) @trusted |{ 0000000| auto ret = printSignedToTail(c, buf, sign); 0000000| if (auto shift = buf.length - ret) | { 0000000| return printBufferShift(ret, shift, buf[].ptr); | } 0000000| return ret; |} | |size_t printSignedToTailGen(T, C, size_t N)(T c, scope ref C[N] buf, C sign) |{ 151| if (c < 0) | { 40| sign = '-'; 40| c = -c; | } | 151| auto ret = printUnsignedToTail(c, buf[1 .. N]); | 151| if (sign != '\0') | { 44| buf[$ - ++ret] = sign; | } 151| return ret; |} | 0000000|size_t printUnsigned(uint c, scope ref char[10] buf) { return printUnsignedGen(c, buf); } 0000000|size_t printUnsigned(ulong c, scope ref char[20] buf) { return printUnsignedGen(c, buf); } |static if (is(ucent)) |size_t printUnsigned(ucent c, scope ref char[39] buf) { return printUnsignedGen(c, buf); } | 0000000|size_t printUnsigned(uint c, scope ref wchar[10] buf) { return printUnsignedGen(c, buf); } 0000000|size_t printUnsigned(ulong c, scope ref wchar[20] buf) { return printUnsignedGen(c, buf); } |static if (is(ucent)) |size_t printUnsigned(ucent c, scope ref wchar[39] buf) { return printUnsignedGen(c, buf); } | 0000000|size_t printUnsigned(uint c, scope ref dchar[10] buf) { return printUnsignedGen(c, buf); } 0000000|size_t printUnsigned(ulong c, scope ref dchar[20] buf) { return printUnsignedGen(c, buf); } |static if (is(ucent)) |size_t printUnsigned(ucent c, scope ref dchar[39] buf) { return printUnsignedGen(c, buf); } | 405|size_t printUnsignedToTail(uint c, scope ref char[10] buf) { return printUnsignedToTailGen(c, buf); } 23|size_t printUnsignedToTail(ulong c, scope ref char[20] buf) { return printUnsignedToTailGen(c, buf); } |static if (is(ucent)) |size_t printUnsignedToTail(ucent c, scope ref char[39] buf) { return printUnsignedToTailGen(c, buf); } | 1|size_t printUnsignedToTail(uint c, scope ref wchar[10] buf) { return printUnsignedToTailGen(c, buf); } 0000000|size_t printUnsignedToTail(ulong c, scope ref wchar[20] buf) { return printUnsignedToTailGen(c, buf); } |static if (is(ucent)) |size_t printUnsignedToTail(ucent c, scope ref wchar[39] buf) { return printUnsignedToTailGen(c, buf); } | 1|size_t printUnsignedToTail(uint c, scope ref dchar[10] buf) { return printUnsignedToTailGen(c, buf); } 1|size_t printUnsignedToTail(ulong c, scope ref dchar[20] buf) { return printUnsignedToTailGen(c, buf); } |static if (is(ucent)) |size_t printUnsignedToTail(ucent c, scope ref dchar[39] buf) { return printUnsignedToTailGen(c, buf); } | |size_t printUnsignedToTailGen(T, C, size_t N)(T c, scope ref C[N] buf) @trusted |{ | static if (T.sizeof == 4) | { 407| if (c < 10) | { 201| buf[$ - 1] = cast(char)('0' + c); 201| return 1; | } | static assert(N == 10); | } | else | static if (T.sizeof == 8) | { 24| if (c <= uint.max) | { 23| return printUnsignedToTail(cast(uint)c, buf[$ - 10 .. $]); | } | static assert(N == 20); | } | else | static if (T.sizeof == 16) | { | if (c <= ulong.max) | { | return printUnsignedToTail(cast(ulong)c, buf[$ - 20 .. $]); | } | static assert(N == 39); | } | else | static assert(0); 207| size_t refLen = buf.length; | do { 627| T nc = c / 10; 627| buf[].ptr[--refLen] = cast(C)('0' + c - nc * 10); 627| c = nc; | } 627| while(c); 207| return buf.length - refLen; |} | |size_t printUnsignedGen(T, C, size_t N)(T c, scope ref C[N] buf) @trusted |{ 0000000| auto ret = printUnsignedToTail(c, buf); 0000000| if (auto shift = buf.length - ret) | { 0000000| return printBufferShift(ret, shift, buf[].ptr); | } 0000000| return ret; |} | |nothrow @trusted |size_t extendASCII(char* from, wchar* to, size_t n) |{ 0000000| foreach (i; 0 .. n) 0000000| to[i] = from[i]; 0000000| return n; |} | |nothrow @trusted |size_t extendASCII(char* from, dchar* to, size_t n) |{ 0000000| foreach (i; 0 .. n) 0000000| to[i] = from[i]; 0000000| return n; |} | |version (mir_test) unittest |{ | import mir.appender; | import mir.format; | 1| assert (stringBuf() << 123L << getData == "123"); | static assert (stringBuf() << 123 << getData == "123"); |} | |void printIntegralZeroImpl(C, size_t N, W, I)(ref scope W w, I c, size_t zeroLen) |{ | static if (__traits(isUnsigned, I)) | alias impl = printUnsignedToTail; | else | alias impl = printSignedToTail; | C[N] buf = void; | size_t n = impl(c, buf); | static if (!__traits(isUnsigned, I)) | { | if (c < 0) | { | n--; | w.put(C('-')); | } | } | sizediff_t zeros = zeroLen - n; | if (zeros > 0) | { | do w.put(C('0')); | while(--zeros); | } | w.put(buf[$ - n .. $]); | return w; |} source/mir/format_impl.d is 26% covered <<<<<< EOF # path=source-mir-graph-package.lst |/++ |Basic routines to work with graphs. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, graph, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ | |module mir.graph; | |import mir.math.common: optmath; | |import mir.series; |import mir.rc.array; |import mir.ndslice.iterator: ChopIterator; | |/// |alias GraphIterator(I = uint, J = size_t) = ChopIterator!(size_t*, uint*); |/// |alias Graph(I = uint, J = size_t) = Slice!(GraphIterator!(I, J)); |/// |alias GraphSeries(T, I = uint, J = size_t) = Series!(T*, GraphIterator!(I, J)); | |/// |alias RCGraphIterator(I = uint, J = size_t) = ChopIterator!(RCI!size_t, RCI!uint); |/// |alias RCGraph(I = uint, J = size_t) = Slice!(RCGraphIterator!(I, J)); |/// |alias RCGraphSeries(T, I = uint, J = size_t) = Series!(RCI!T, RCGraphIterator!(I, J)); | |private static immutable exc_msg = "graphSeries: graph should contains keys for all vertixes"; |version(D_Exceptions) |{ | private static immutable exception = new Exception(exc_msg); |} | |/++ |Params: | aaGraph = graph that is represented as associative array |Returns: | A graph series composed of keys (sorted `.index`) and arrays of indeces (`.data`) |Complexity: `O(log(V) (V + E))` |+/ |@optmath |GraphSeries!(T, I, J) graphSeries(I = uint, J = size_t, T, Range)(in Range[T] aaGraph) |{ | import mir.array.allocation: array; | import mir.ndslice.sorting; | import mir.ndslice; 6| auto keys = aaGraph.byKey.array.sliced; 6| sort(keys); 6| size_t dataLength; 6| foreach (ref v; aaGraph) 52| dataLength += v.length; 6| auto data = uninitSlice!I(dataLength); 6| auto components = uninitSlice!J(keys.length + 1); 6| size_t dataIndex; | 174| foreach (i; 0 .. keys.length) | { 52| components[i] = cast(J) dataIndex; 633| foreach(ref elem; aaGraph[keys[i]]) | { | import mir.ndslice.sorting: transitionIndex; 159| auto index = keys.transitionIndex(elem); 159| if (index >= keys.length) | { | version(D_Exceptions) 0000000| throw exception; | else | assert(0, exc_msg); | } 159| data[dataIndex++] = cast(I) index; | } | } 6| components[keys.length] = dataIndex; 12| auto sliceable = (() @trusted => data.ptr)(); 6| return keys.series(sliceable.chopped(components)); |} | |/// |pure version(mir_test) unittest |{ 1| auto gs = [ | "b" : ["a"], | "a" : ["b", "c"], | "c" : ["b"], | ].graphSeries; | 1| assert (gs.index == ["a", "b", "c"]); // sorted 1| assert (gs.data == [ | [1, 2], // a | [0], // b | [1], // c | ]); |} | |/++ |Params: | graph = graph that is represented a series |Returns: | A graph as an arrays of indeces |Complexity: `O(log(V) (V + E))` |+/ |@optmath |RCGraph!(I, J) rcgraph(I = uint, J = size_t, KeyIterator, RangeIterator)(Series!(KeyIterator, RangeIterator) graph) |{ | import mir.array.allocation: array; | import mir.ndslice.sorting; | import mir.ndslice; 1| auto scopeGraph = graph.lightScope; 1| auto keys = scopeGraph.index; 1| auto graphData = scopeGraph.data; 1| size_t dataLength; 11| foreach (ref v; graphData) 3| dataLength += v.length; 2| auto data = rcslice!I(dataLength); 2| auto components = rcslice!J(keys.length + 1); 1| size_t dataIndex; | 12| foreach (i; 0 .. keys.length) | { 3| components[i] = cast(J) dataIndex; 21| foreach(ref elem; graphData[i]) | { | import mir.ndslice.sorting: transitionIndex; 4| auto index = keys.transitionIndex(elem); 4| if (index >= keys.length) | { | version(D_Exceptions) 0000000| throw exception; | else | assert(0, exc_msg); | } 4| data[dataIndex++] = cast(I) index; | } | } 1| components[keys.length] = dataIndex; 1| return data._iterator.chopped(components); |} | |/// |@safe pure @nogc version(mir_test) |unittest |{ | import mir.series: series; | | static immutable keys = ["a", "b", "c"]; | static immutable data = [ | ["b", "c"], | ["a"], | ["b"], | ]; | | static immutable graphValue = [ | [1, 2], // a | [0], // b | [1], // c | ]; | 1| assert (series(keys, data).rcgraph == graphValue); |} source/mir/graph/package.d is 95% covered <<<<<< EOF # path=source-mir-graph-tarjan.lst |/++ |This is a submodule of $(MREF mir,graph). | |Tarjan's strongly connected components algorithm. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ | |module mir.graph.tarjan; | |import std.traits; | |import mir.math.common: optmath; | |@optmath: | | |/++ |Classic Tarjan's strongly connected components algorithm. | |Tarjan's algorithm is an algorithm in graph theory for finding the strongly connected components of a graph. |It runs in linear time, matching the time bound for alternative methods including Kosaraju's algorithm and the path-based strong component algorithm. | |The implementation is loop based. It does not use recursion and does not have stack overflow issues. | |Complexity: worst-case `O(|V| + |E|)`. | |Params: | RC = nogc mode, refcounted output | graph = components (ndslice) sorted in the direction of traversal of the graph. Each component is an array of indeces. |Returns: | components (ndslice of arrays of indices) | |Note: | The implementation returns components sorted in the direction of traversal of the graph. | $(NOTE Most of other Tarjan implementations returns reverse order.) | |See_also: | $(SUBREF utility, graph) |+/ |pragma(inline, false) |auto tarjan(bool RC = false, G, I = Unqual!(ForeachType!(ForeachType!G)))(G graph) | if (isUnsigned!I) |{ | import mir.utility: min; | | static if (I.sizeof >= uint.sizeof) | alias S = size_t; | else | alias S = uint; | | enum undefined = I.max; | | static struct IndexNode | { | I index; | I lowlink; | |@property: | | bool isRoot()() | { 61| return index == lowlink; | } | | bool isUndefined()() | { 118| return index == undefined; | } | } | | static struct LoopNode | { | union | { | struct | { | I i; | I j; | } | S index; | } | } | | 6| sizediff_t stackIndex; 6| sizediff_t backStackIndex = graph.length; 6| sizediff_t componentBackStackIndex = graph.length + 1; | | static if (RC) | { | import mir.rc.array; 2| auto onStack = RCArray!bool(graph.length); 2| auto stack = RCArray!I(graph.length, true); 2| auto indeces = RCArray!IndexNode(graph.length, true); 2| auto loopStack = RCArray!LoopNode(componentBackStackIndex, true); | } | else | { 5| I[] stack; 5| IndexNode[] indeces; 5| LoopNode[] loopStack; | 5| bool[] onStack = new bool[graph.length]; 5| if (__ctfe) | { | | stack = new I[graph.length]; | indeces = new IndexNode[graph.length]; | loopStack = new LoopNode[componentBackStackIndex]; | } | else | { 5| () @trusted { | import std.array: uninitializedArray; | 5| stack = uninitializedArray!(I[])(graph.length); 5| indeces = uninitializedArray!(IndexNode[])(graph.length); 5| loopStack = uninitializedArray!(LoopNode[])(componentBackStackIndex); | } (); | } | } | 201| foreach(ref node; indeces) 61| node.index = undefined; | 6| I index; 201| foreach(size_t v; 0u .. graph.length) | { 61| if (indeces[v].isUndefined) | { 9| sizediff_t loopStackIndex; | loop: | // Set the depth index for v to the smallest unused index 61| indeces[v].index = cast(I) index; 61| indeces[v].lowlink = cast(I) index; 61| index++; 61| stack[stackIndex++] = cast(I) v; 61| onStack[v] = true; | | // Consider successors of v 61| auto e = graph[v]; 61| I w; 61| size_t wi; | 399| for (; wi < e.length; wi++) | { 169| w = e[wi]; 169| if (onStack[w]) | { | // Successor w is in stack S and hence in the current SCC | // If w is not on stack, then (v, w) is a cross-edge in the DFS tree and must be ignored | // Note: The next line may look odd - but is correct. | // It says w.index not w.lowlink; that is deliberate and from the original paper 112| indeces[v].lowlink = min(indeces[v].lowlink, indeces[w].index); 112| continue; | } 57| if (indeces[w].isUndefined) | { | // Successor w has not yet been visited; recurse on it | // strongconnect(w) 52| assert(loopStackIndex < loopStack.length); 52| loopStack[loopStackIndex] = LoopNode(cast(I) v, cast(I) wi); 52| ++loopStackIndex; 52| assert(componentBackStackIndex > loopStackIndex); 52| v = e[wi]; 52| goto loop; | retRec: 52| v = loopStack[loopStackIndex].i; 52| wi = loopStack[loopStackIndex].j; 52| e = graph[v]; 52| w = e[wi]; 52| indeces[v].lowlink = min(indeces[v].lowlink, indeces[w].lowlink); | } | } | | // If v is a root node, pop the stack and generate an SCC 61| if (indeces[v].isRoot) | { | // start a new strongly connected component | do | { 61| assert(stackIndex > 0); 61| assert(backStackIndex > 0); | // add w to current strongly connected component 61| --backStackIndex; 61| --stackIndex; 61| w = stack[backStackIndex] = stack[stackIndex]; 61| onStack[w] = false; | } 61| while (w != v); | | // output the current strongly connected component 21| assert(componentBackStackIndex > loopStackIndex); 21| --componentBackStackIndex; 21| loopStack[componentBackStackIndex].index = cast(S) backStackIndex; | } 61| if (--loopStackIndex >= 0) 52| goto retRec; | } | } | 6| const indexLength = graph.length + 1 - componentBackStackIndex + 1; | static if (RC) | { 2| auto pairwiseIndex = RCArray!S(indexLength, true); | } | else | { 5| S[] pairwiseIndex; 5| if (__ctfe) | { | pairwiseIndex = new S[indexLength]; | } | else | { 5| () @trusted { | import std.array: uninitializedArray; 5| pairwiseIndex = uninitializedArray!(S[])(indexLength); | } (); | } | } 102| foreach (i, ref e; loopStack[][componentBackStackIndex .. $]) | { 21| pairwiseIndex[i] = e.index; | } 6| pairwiseIndex[$ - 1] = cast(I) graph.length; | | import mir.ndslice.topology: chopped; | static if (RC) | { | import core.lifetime: move; 1| return chopped(RCI!I(stack.move), pairwiseIndex.asSlice); | } | else | { 10| return (()@trusted {return stack.ptr; }()).chopped(pairwiseIndex); | } |} | |/++ |------ | 4 <- 5 <- 6 -------> 7 -> 8 -> 11 | | ^ ^ ^ | | v | | | | | 0 -> 1 -> 2 -> 3 -> 10 9 <--- |------ |+/ |pure version(mir_test) unittest |{ | import mir.graph; | import mir.graph.tarjan; | 1| GraphSeries!(string, uint) gs = [ | "00": ["01"], | "01": ["02"], | "02": ["05", "03"], | "03": ["06", "10"], | "04": ["01"], | "05": ["04"], | "06": ["05", "07"], | "07": ["08"], | "08": ["09", "11"], | "09": ["07"], | "10": [], | "11": [], | ].graphSeries; | | | static immutable result = [ | [0], | [1, 2, 5, 4, 3, 6], | [10], | [7, 8, 9], | [11]]; | | // chec GC interface 1| auto components = gs.data.tarjan; 1| assert(components == result); | // check @nogc interface | // Note: The lambda function is used here to show @nogc mode explicitly. 3| auto rccomponents = (() @nogc => gs.data.tarjan!true )(); 1| assert(rccomponents == result); |} | |/++ |Tests that the graph `0 -> 1 -> 2 -> 3 -> 4` returns 4 components. |+/ |pure version(mir_test) unittest |{ | import mir.graph; | import mir.graph.tarjan; | 1| GraphSeries!(char, uint) gs = [ | 'a': ['b'], | 'b': ['c'], | 'c': ['d'], | 'd': ['q'], | 'q': [], | ].graphSeries; | 1| auto scc = gs.data.tarjan; | 1| assert(scc == [[0], [1], [2], [3], [4]]); |} | |/++ |---- | 0 <- 2 <-- 5 <--> 6 | | ^ ^ ^___ | v_| | | _ | 1 <- 3 <-> 4 <-- 7_| |---- |+/ |pure version(mir_test) unittest |{ | import mir.graph; | import mir.graph.tarjan; | 1| auto gs = [ | 0: [1], | 1: [2], | 2: [0], | 3: [1, 2, 4], | 4: [3, 2], | 5: [2, 6], | 6: [5], | 7: [4, 7], | ].graphSeries; | 1| auto components = gs.data.tarjan; | 1| assert(components == [ | [7], | [5, 6], | [3, 4], | [0, 1, 2], | ]); |} | |/++ |----- | 2 <-> 1 | | ^ | v_0 / | |----- |+/ |pure version(mir_test) unittest |{ | import mir.graph; | import mir.graph.tarjan; | 1| auto gs = [ | 0: [1], | 1: [2], | 2: [0, 1], | ].graphSeries; | 1| auto components = gs.data.tarjan; | 1| assert(components == [[0, 1, 2]]); |} | |/++ |Tests that a strongly connected graph, where components have |to get through previously visited components to get to the |graph root works properly | |This test demonstrates a hard to detect bug, where vertices |were being marked 'off-stack' after they were first visited, |not when they were actually removed from the stack |+/ |pure version(mir_test) unittest |{ | import mir.graph; | import mir.graph.tarjan; | 1| auto root = 0; 1| auto lvl1 = [1,2,3,4,5,6,7,8,9,10]; 1| auto lvl2 = [11,12,13,14,15,16,17,18,19,20]; | 1| int[][int] aar; 1| aar[root] = lvl1; 33| foreach(int v; lvl1) 10| aar[v] = lvl2; 33| foreach(int v; lvl2) 10| aar[v] = [root]; | 1| auto gs = aar.graphSeries; | 1| auto components = gs.data.tarjan; | 1| assert(components == [[root] ~ [lvl1[0]] ~ lvl2 ~ lvl1[1 .. $]]); |} source/mir/graph/tarjan.d is 100% covered <<<<<< EOF # path=source-mir-interpolate-constant.lst |/++ |$(H2 Constant Interpolation) | |See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate.constant; | |@optmath: | |/// |version(mir_test) |@safe pure @nogc unittest |{ | import mir.ndslice; | import mir.math.common: approxEqual; | | static immutable x = [0, 1, 2, 3]; | static immutable y = [10, 20, 30, 40]; | 2| auto interpolant = constant!int(x.rcslice, y.rcslice!(const int)); | 1| assert(interpolant(-1) == 10); 1| assert(interpolant(0) == 10); 1| assert(interpolant(0.5) == 10); | 1| assert(interpolant(1) == 20); | 1| assert(interpolant(3) == 40); 1| assert(interpolant(4) == 40); |} | | |import core.lifetime: move; |import mir.internal.utility; |import mir.functional; |import mir.interpolate; |import mir.math.common: optmath; |import mir.ndslice.slice; |import mir.primitives; |import mir.rc.array; |import mir.utility: min, max; |import std.meta: AliasSeq, staticMap; |import std.traits; | |/// |public import mir.interpolate: atInterval; | |/++ |Constructs multivariate constant interpolant with nodes on rectilinear grid. | |Params: | grid = `x` values for interpolant | values = `f(x)` values for interpolant | |Constraints: | `grid`, `values` must have the same length >= 1 | |Returns: $(LREF Constant) |+/ |Constant!(F, N, X) constant(F, size_t N = 1, X = F) | (Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) values) |{ 1| return typeof(return)(forward!grid, values.move); |} | |/++ |Multivariate constant interpolant with nodes on rectilinear grid. |+/ 0000000|struct Constant(F, size_t N = 1, X = F) | if (N && N <= 6) |{ |@optmath: | | /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) | Slice!(RCI!(const F), N) _data; | /// Grid iterators. $(RED For internal use.) | Repeat!(N, RCI!(immutable X)) _grid; | |extern(D): | | bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc | { 0000000| if (rhs._data != this._data) 0000000| return false; | static foreach (d; 0 .. N) 0000000| if (gridScopeView!d != rhs.gridScopeView!d) 0000000| return false; 0000000| return true; | } | | /++ | +/ 1| this(Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) data) @safe @nogc | { | enum msg_min = "constant interpolant: minimal allowed length for the grid equals 1."; | enum msg_eq = "constant interpolant: X and Y values length should be equal."; | version(D_Exceptions) | { | static immutable exc_min = new Exception(msg_min); | static immutable exc_eq = new Exception(msg_eq); | } 1| foreach(i, ref x; grid) | { 1| if (x.length < 1) | { 0000000| version(D_Exceptions) throw exc_min; | else assert(0, msg_min); | } 1| if (x.length != data._lengths[i]) | { 0000000| version(D_Exceptions) throw exc_eq; | else assert(0, msg_eq); | } 1| _grid[i] = x._iterator; | } 1| _data = data; | } | |@trusted: | | /// | Constant lightConst()() const @property { return *cast(Constant*)&this; } | | /// | Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property | if (dimension < N) | { | return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension < N) | { 6| return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | { 6| assert(_data._lengths[dimension] > 0); 6| return _data._lengths[dimension] - 0; | } | | /// | size_t[N] gridShape()() scope const @property | { | return _data.shape; | } | | /// | enum uint derivativeOrder = 0; | | /// | enum uint dimensionCount = N; | | /// | template opCall(uint derivative = 0) | // if (derivative <= derivativeOrder) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(in X xs) scope const @trusted | if (X.length == N) | { 6| size_t[N] indices; | foreach(i; Iota!N) | { | static if (isInterval!(typeof(xs[i]))) | indices[i] = xs[i][1]; | else 6| indices[i] = _data._lengths[i] > 1 ? this.findInterval!i(xs[i]) : 0; | } | static if (derivative == 0) | { 6| return _data[indices]; | } | else | { | F[derivative + 1] ret = 0; | ret[0] = _data[indices]; | return ret; | } | } | } |} | |/++ |Single value interpolation |+/ |SingleConstant!F singleConstant(F)(const F value) |{ 1| return typeof(return)(value); |} | |/// ditto |struct SingleConstant(F, X = F) |{ | /// | enum uint derivativeOrder = 0; | | /// | enum uint dimensionCount = 1; | | /// | F value; | | /// 1| this(F value) | { 1| this.value = value; | } | | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension == 0) | { | return null; | } | | /// | template opCall(uint derivative = 0) | { | /++ | `(x)` operator. | Complexity: | `O(1)` | +/ | auto opCall(X...)(in X xs) scope const @trusted | { | static if (derivative == 0) | { 1| return F(value); | } | else | { 1| F[derivative + 1] ret = 0; 1| ret[0] = value; 1| return ret; | } | } | } |} | |/// |@safe pure nothrow |version (mir_test) |unittest |{ 1| auto sc = singleConstant(34.1); 1| assert(sc(100) == 34.1); 1| assert(sc.opCall!2(100) == [34.1, 0, 0]); |} | |/++ |Interpolator used for non-rectiliner trapezoid-like greeds. |Params: | grid = rc-array of interpolation grid | data = rc-array of interpolator-like structures |+/ |auto metaSingleConstant(T)(T data) |{ | import core.lifetime: move; 1| return MetaSingleConstant!T(data.move); |} | |/// ditto 0000000|struct MetaSingleConstant(T, X = double) | // if (T.derivativeOrder >= 1) |{ | import mir.interpolate.utility: DeepType; | | /// | T data; | | /// 1| this(T data) | { | import core.lifetime: move; 1| this.data = data.move; | } | | /// | MetaSingleConstant lightConst()() const @property { return *cast(MetaSingleConstant*)&this; } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension == 0) | { | return null; | } | | /// | enum uint derivativeOrder = 1; | | static if (__traits(compiles, {enum N = T.dimensionCount;})) | /// | enum uint dimensionCount = T.dimensionCount + 1; | | /// | template opCall(uint derivative = 0) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(const X xs) scope const @trusted | if (xs.length >= 1) | { | static if (derivative == 0) | { 1| return data(xs[1.. $]); | } | else | { 1| auto iv = data.opCall!derivative(xs[1.. $]); 1| typeof(iv)[derivative + 1] ret = 0; 1| ret[0] = iv; 1| return ret; | } | } | } |} | |/// Ignores the first dimension parameter |version(mir_test) |unittest |{ | import mir.interpolate.linear; | 1| auto x = [0.0, 1, 2, 3, 5]; 1| auto y = [4.0, 0, 9, 23, 40]; | 1| auto g = [7.0, 10, 15]; | | import mir.ndslice.allocation: rcslice; | 2| auto d = linear!double(x.rcslice!(immutable double), y.rcslice!(const double)); | 2| auto ignoresFirstDim = d.metaSingleConstant; | 1| assert(ignoresFirstDim(9.0, 1.8) == d(1.8)); 1| assert(ignoresFirstDim.opCall!1(9.0, 1.8) == [d.opCall!1(1.8), [0.0, 0.0]]); |} source/mir/interpolate/constant.d is 83% covered <<<<<< EOF # path=source-mir-interpolate-extrapolate.lst |/++ |$(H2 Extrapolators) | |See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate.extrapolate; | |/++ |Constant extrapolator |+/ |ConstantExtrapolator!T constantExtrapolator(T)(T interpolator) |{ | import core.lifetime: move; 1| return typeof(return)(interpolator.move); |} | | |/// ditto 0000000|struct ConstantExtrapolator(T) | if (__traits(hasMember, T, "gridScopeView")) |{ | /// | T data; | | /// 1| this(T data) | { | import core.lifetime: move; 1| this.data = data.move; | } | | /// | ConstantExtrapolator lightConst()() const @property { return *cast(ConstantExtrapolator*)&this; } | | /// | auto gridScopeView(size_t dimension = 0)() return scope const @property @trusted | { 4| return data.gridScopeView!dimension; | } | | /// | enum uint derivativeOrder = 1; | | static if (__traits(compiles, {enum N = T.dimensionCount;})) | /// | enum uint dimensionCount = T.dimensionCount + 1; | | /// | template opCall(uint derivative = 0) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(const X xs) scope const @trusted | if (X.length >= 1) | { | import mir.internal.utility: Iota; | import mir.math.common: fmin, fmax; | import std.meta: staticMap; | | static if (derivative) 1| bool[X.length] extrpolated; | | auto mod(size_t i)() | { | static if (__traits(compiles, gridScopeView!i)) | { 4| auto grid = gridScopeView!i; | static if (derivative) 2| extrpolated[i] = grid.length != 0 && (xs[i] < grid[0] || grid[$ - 1] < xs[i]); 4| return grid.length ? xs[i].fmax(grid[0]).fmin(grid[$ - 1]) : xs[i]; | } | else | { | return xs[i]; | } | } | | alias xm = staticMap!(mod, Iota!(X.length)); | | static if (derivative == 0) 3| return data(xm); | else | { | static assert (X.length <= 4, "multidimensional constant exrapolation with derivatives isn't implemented"); 1| auto ret = data.opCall!derivative(xm); | | static if (X.length >= 1) | { 1| if (extrpolated[0]) 6| foreach (ref a; ret[1 .. $]) 1| a = 0; | } | | static if (X.length >= 2) | { | if (extrpolated[1]) | foreach (ref a; ret) | foreach (ref b; a[1 .. $]) | b = 0; | } | | static if (X.length >= 3) | { | if (extrpolated[2]) | foreach (ref a; ret) | foreach (ref b; a) | foreach (ref c; b[1 .. $]) | c = 0; | } | | static if (X.length >= 4) | { | if (extrpolated[2]) | foreach (ref a; ret) | foreach (ref b; a) | foreach (ref c; b) | foreach (ref d; c[1 .. $]) | d = 0; | } | 1| return ret; | } | } | } |} | | |/// |version(mir_test) |unittest |{ | import mir.interpolate.linear; | 1| auto x = [0.0, 1, 2, 3, 5]; 1| auto y = [4.0, 0, 9, 23, 40]; | 1| auto g = [7.0, 10, 15]; | | import mir.ndslice.allocation: rcslice; | 2| auto linear = linear!double( | x.rcslice!(immutable double), | y.rcslice!(const double), | ).constantExtrapolator; | 1| assert(linear(2) == 9); 1| assert(linear(-1) == 4); 1| assert(linear(100) == 40); | 1| assert(linear.opCall!1(-1) == [4, 0]); | |} | |/++ |Linear extrapolator. |+/ |LinearExtrapolator!T linearExtrapolator(T)(T interpolator) |{ | import core.lifetime: move; 1| return typeof(return)(interpolator.move); |} | | |/// ditto 0000000|struct LinearExtrapolator(T) | if (__traits(hasMember, T, "gridScopeView")) |{ | /// | T data; | | /// 1| this(T data) | { | import core.lifetime: move; 1| this.data = data.move; | } | | /// | LinearExtrapolator lightConst()() const @property { return *cast(LinearExtrapolator*)&this; } | | /// | auto gridScopeView(size_t dimension = 0)() return scope const @property @trusted | { 7| return data.gridScopeView!dimension; | } | | /// | enum uint derivativeOrder = 1; | | static if (__traits(compiles, {enum N = T.dimensionCount;})) | /// | enum uint dimensionCount = T.dimensionCount + 1; | | /// | template opCall(uint derivative = 0) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(const X xs) scope const @trusted | if (X.length >= 1) | { | import mir.internal.utility: Iota; | import mir.math.common: fmin, fmax; | import std.meta: staticMap; | 4| bool[X.length] extrpolated; | | auto mod(size_t i)() | { | static if (__traits(compiles, gridScopeView!i)) | { 7| auto grid = gridScopeView!i; 17| extrpolated[i] = grid.length != 0 && (xs[i] < grid[0] || grid[$ - 1] < xs[i]); 7| return grid.length ? xs[i].fmax(grid[0]).fmin(grid[$ - 1]) : xs[i]; | } | else | { | return xs[i]; | } | } | | alias xm = staticMap!(mod, Iota!(X.length)); | | import mir.utility: max; | | static assert (X.length <= 2, "multidimensional linear exrapolation with derivatives isn't implemented"); 4| auto ret = data.opCall!(derivative.max(1u))(xm); | | static if (X.length >= 1) | { 4| if (extrpolated[0]) | { 3| ret[0] += ret[1] * (xs[0] - xm[0]); 9| foreach (ref a; ret[2 .. $]) 0000000| a = 0; | } | } | | static if (derivative == 0) 3| return ret[0]; | else 1| return ret; | } | } |} | | |/// |version(mir_test) |unittest |{ | import mir.test; | import mir.interpolate.linear; | 1| auto x = [0.0, 1, 2, 3, 5]; 1| auto y = [4.0, 0, 9, 23, 40]; | 1| auto g = [7.0, 10, 15]; | | import mir.ndslice.allocation: rcslice; | 2| auto linear = linear!double( | x.rcslice!(immutable double), | y.rcslice!(const double), | ); | 2| auto linearLinear = linear.linearExtrapolator; | 1| linearLinear(2).should == linear(2); 1| linearLinear(-1).should == linear(-1); 1| linearLinear.opCall!1(-1).should == [8, -4]; 1| linearLinear(100).shouldApprox == linear(100); |} source/mir/interpolate/extrapolate.d is 93% covered <<<<<< EOF # path=source-mir-interpolate-generic.lst |/++ |$(H2 Generic Piecewise Interpolant) | |See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate.generic; | |@optmath: | |/// |version(mir_test) |@safe pure @nogc unittest |{ | import mir.ndslice; | import mir.math.common: approxEqual; | | struct PieceInterpolant | { | int value; | 0000000| this()(int value) | { 0000000| this.value = value; | } | | int opCall(uint derivative : 0, X)(int x0, int x1, X x) const | { 10| return value; | } | | enum uint derivativeOrder = 0; | } | | alias S = PieceInterpolant; | static immutable x = [0, 1, 2, 3]; // can be also an array of floating point numbers | static immutable y = [S(10), S(20), S(30)]; | 2| auto interpolant = generic(x.rcslice, y.rcslice!(const S)); | 1| assert(interpolant(-1) == 10); 1| assert(interpolant(0) == 10); 1| assert(interpolant(0.5) == 10); | 1| assert(interpolant(1) == 20); 1| assert(interpolant(1.5) == 20); | 1| assert(interpolant(2) == 30); 1| assert(interpolant(3) == 30); 1| assert(interpolant(3.4) == 30); 1| assert(interpolant(3) == 30); 1| assert(interpolant(4) == 30); |} | | |import core.lifetime: move; |import mir.internal.utility; |import mir.functional; |import mir.interpolate; |import mir.math.common: optmath; |import mir.ndslice.slice; |import mir.primitives; |import mir.rc.array; |import mir.utility: min, max; |import std.meta: AliasSeq, staticMap; |import std.traits; | |/// |public import mir.interpolate: atInterval; | |/++ |Constructs multivariate generic interpolant with nodes on rectilinear grid. | |Params: | grid = `x` values for interpolant | values = `f(x)` values for interpolant | |Constraints: | `grid`, `values` must have the same length >= 1 | |Returns: $(LREF Generic) |+/ |Generic!(X, F) generic(X, F) | (Slice!(RCI!(immutable X)) grid, Slice!(RCI!(const F)) values) |{ 1| return typeof(return)(forward!grid, values.move); |} | |/++ |Multivariate generic interpolant with nodes on rectilinear grid. |+/ 0000000|struct Generic(X, F) |{ |@optmath: | | /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) | Slice!(RCI!(const F)) _data; | /// Grid iterators. $(RED For internal use.) | RCI!(immutable X) _grid; | | bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc | { 0000000| if (rhs._data != this._data) 0000000| return false; | static foreach (d; 0 .. 1) 0000000| if (gridScopeView!d != rhs.gridScopeView!d) 0000000| return false; 0000000| return true; | } | |extern(D): | | /++ | +/ 1| this(Slice!(RCI!(immutable X)) grid, Slice!(RCI!(const F)) data) @safe @nogc | { | import core.lifetime: move; | enum msg_min = "generic interpolant: minimal allowed length for the grid equals 2."; | enum msg_eq = "generic interpolant: X length and Y values length + 1 should be equal."; | version(D_Exceptions) | { | static immutable exc_min = new Exception(msg_min); | static immutable exc_eq = new Exception(msg_eq); | } 1| if (grid.length < 2) | { 0000000| version(D_Exceptions) throw exc_min; | else assert(0, msg_min); | } 1| if (grid.length != data._lengths[0] + 1) | { 0000000| version(D_Exceptions) throw exc_eq; | else assert(0, msg_eq); | } 1| _grid = move(grid._iterator); 1| _data = move(data); | } | |@trusted: | | /// | Generic lightConst()() const @property { return *cast(Generic*)&this; } | | /// | Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property | if (dimension == 0) | { | return _grid.lightConst.sliced(_data._lengths[0]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension == 0) | { 10| return _grid._iterator[0 .. _data._lengths[0]]; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | if (dimension == 0) | { 10| assert(_data._lengths[0] > 1); 10| return _data._lengths[0] - 0; | } | | /// | size_t[1] gridShape()() scope const @property | { | return _data.shape; | } | | /// | enum uint derivativeOrder = F.derivativeOrder; | | /// | template opCall(uint derivative = 0) | if (derivative == 0) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X)(in X x) const | { 10| return opCall!(X)(Tuple!(size_t, X)(this.findInterval(x), x)); | } | | /// | auto opCall(X)(Tuple!(size_t, X) tuple) const | { 10| X x = tuple[1]; 10| size_t idx = tuple[0]; 10| return _data[idx].opCall!derivative(_grid[idx], _grid[idx + 1], x); | } | } |} source/mir/interpolate/generic.d is 71% covered <<<<<< EOF # path=source-mir-interpolate-linear.lst |/++ |$(H2 Linear Interpolation) | |See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate.linear; | |import core.lifetime: move; |import mir.functional; |import mir.internal.utility; |import mir.interpolate; |import mir.math.common: optmath; |import mir.ndslice.slice; |import mir.primitives; |import mir.rc.array; |import mir.utility: min, max; |import std.meta: AliasSeq, staticMap; |import std.traits; | |/// |public import mir.interpolate: atInterval; | |enum msg_min = "linear interpolant: minimal allowed length for the grid equals 2."; |enum msg_eq = "linear interpolant: X and Y values length should be equal."; |version(D_Exceptions) |{ | static immutable exc_min = new Exception(msg_min); | static immutable exc_eq = new Exception(msg_eq); |} | |@optmath: | |/++ |Constructs multivariate linear interpolant with nodes on rectilinear grid. | |Params: | grid = `x` values for interpolant | values = `f(x)` values for interpolant | |Constraints: | `grid`, `values` must have the same length >= 2 | |Returns: $(LREF Linear) |+/ |Linear!(F, N, X) linear(F, size_t N = 1, X = F) | (Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) values) |{ 15| return typeof(return)(forward!grid, values.move); |} | |/// R -> R: Linear interpolation |version(mir_test) |@safe pure @nogc unittest |{ | import mir.algorithm.iteration; | import mir.ndslice; | import mir.math.common: approxEqual; | | static immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; | static immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; | static immutable xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; | 2| auto interpolant = linear!double(x.rcslice!(immutable double), y.rcslice!(const double)); | | static immutable data = [0.0011, 0.0030, 0.0064, 0.0104, 0.0144, 0.0176, 0.0207, 0.0225, 0.0243, 0.0261, 0.0268, 0.0274, 0.0281, 0.0288, 0.0295, 0.0302, 0.0309, 0.0316, 0.0322, 0.0329, 0.0332, 0.0335, 0.0337, 0.0340, 0.0342, 0.0345, 0.0348, 0.0350, 0.0353, 0.0356]; | 31| assert(xs.sliced.vmap(interpolant).all!((a, b) => approxEqual(a, b, 1e-4, 1e-4))(data)); | 1| auto d = interpolant.withDerivative(9.0); 1| auto de = interpolant.opCall!2(9.0); 1| assert(de[0 .. 2] == d); 1| assert(de[2] == 0); |} | |/// R^2 -> R: Bilinear interpolation |version(mir_test) |@safe pure @nogc unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice; 25| alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); | | //// set test function //// | enum y_x0 = 2; | enum y_x1 = -7; | enum y_x0x1 = 3; | | // this function should be approximated very well 42| alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; | | ///// set interpolant //// | static immutable x0 = [-1.0, 2, 8, 15]; | static immutable x1 = [-4.0, 2, 5, 10, 13]; | 2| auto grid = cartesian(x0, x1) | .map!f | .rcslice | .lightConst; | 2| auto interpolant = | linear!(double, 2)( | x0.rcslice!(immutable double), | x1.rcslice!(immutable double), | grid | ); | | ///// compute test data //// 1| auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); 1| auto real_data = test_grid.map!f; 2| auto interp_data = test_grid.vmap(interpolant); | ///// verify result //// 1| assert(all!appreq(interp_data, real_data)); | | //// check derivatives //// 1| auto z0 = 1.23; 1| auto z1 = 3.21; 1| auto d = interpolant.withDerivative(z0, z1); 1| assert(appreq(interpolant(z0, z1), f(z0, z1))); 1| assert(appreq(d[0][0], f(z0, z1))); 1| assert(appreq(d[1][0], y_x0 + y_x0x1 * z1)); 1| assert(appreq(d[0][1], y_x1 + y_x0x1 * z0)); 1| assert(appreq(d[1][1], y_x0x1)); |} | |/// R^3 -> R: Trilinear interpolation |version(mir_test) |@safe pure unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice; 66| alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); | | ///// set test function //// | enum y_x0 = 2; | enum y_x1 = -7; | enum y_x2 = 5; | enum y_x0x1 = 10; | enum y_x0x1x2 = 3; | | // this function should be approximated very well | static auto f(double x0, double x1, double x2) | { 122| return y_x0 * x0 + y_x1 * x1 + y_x2 * x2 + y_x0x1 * x0 * x1 + y_x0x1x2 * x0 * x1 * x2 - 11; | } | | ///// set interpolant //// | static immutable x0 = [-1.0, 2, 8, 15]; | static immutable x1 = [-4.0, 2, 5, 10, 13]; | static immutable x2 = [3, 3.7, 5]; 2| auto grid = cartesian(x0, x1, x2) | .map!f | .as!(const double) | .rcslice; | 2| auto interpolant = linear!(double, 3)( | x0.rcslice!(immutable double), | x1.rcslice!(immutable double), | x2.rcslice!(immutable double), | grid); | | ///// compute test data //// 1| auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23, x2.sliced - 3); 1| auto real_data = test_grid.map!f; 2| auto interp_data = test_grid.vmap(interpolant); | ///// verify result //// 1| assert(all!appreq(interp_data, real_data)); | | //// check derivatives //// 1| auto z0 = 1.23; 1| auto z1 = 3.21; 1| auto z2 = 4; 1| auto d = interpolant.withDerivative(z0, z1, z2); 1| assert(appreq(interpolant(z0, z1, z2), f(z0, z1, z2))); 1| assert(appreq(d[0][0][0], f(z0, z1, z2))); 1| assert(appreq(d[1][0][0], y_x0 + y_x0x1 * z1 + y_x0x1x2 * z1 * z2)); 1| assert(appreq(d[0][1][0], y_x1 + y_x0x1 * z0 + y_x0x1x2 * z0 * z2)); 1| assert(appreq(d[1][1][0], y_x0x1 + y_x0x1x2 * z2)); 1| assert(appreq(d[1][1][1], y_x0x1x2)); |} | |/++ |Multivariate linear interpolant with nodes on rectilinear grid. |+/ 25|struct Linear(F, size_t N = 1, X = F) | if (N && N <= 6) |{ | /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) | Slice!(RCI!(const F), N) _data; | /// Grid iterators. $(RED For internal use.) | Repeat!(N, RCI!(immutable X)) _grid; | |@optmath extern(D): | | bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc | { 0000000| if (rhs._data != this._data) 0000000| return false; | static foreach (d; 0 .. N) 0000000| if (gridScopeView!d != rhs.gridScopeView!d) 0000000| return false; 0000000| return true; | } | | /++ | +/ 15| this(Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) data) @safe @nogc | { 18| foreach(i, ref x; grid) | { 18| if (x.length < 2) | { 0000000| version(D_Exceptions) throw exc_min; | else assert(0, msg_min); | } 18| if (x.length != data._lengths[i]) | { 0000000| version(D_Exceptions) throw exc_eq; | else assert(0, msg_eq); | } 18| _grid[i] = x._iterator.move; | } 15| _data = data.move; | } | |@trusted: | | /// 0000000| Linear lightConst()() const @property { return *cast(Linear*)&this; } | | /// | Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property | if (dimension < N) | { | return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension < N) | { 367| return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | { 356| assert(_data._lengths[dimension] > 1); 356| return _data._lengths[dimension] - 1; | } | | /// | size_t[N] gridShape()() scope const @property | { | return _data.shape; | } | | /// | enum uint derivativeOrder = 1; | | /// | enum uint dimensionCount = N; | | /// | template opCall(uint derivative = 0) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(const X xs) scope const @trusted | if (X.length == N) | { | static if (derivative > derivativeOrder) | { 1| auto res = this.opCall!derivativeOrder(xs); 1| typeof(res[0])[derivative + 1] ret = 0; 1| ret[0 .. derivativeOrder + 1] = res; 1| return ret; | } | else | { | import mir.functional: AliasCall; | import mir.ndslice.topology: iota; | alias Kernel = AliasCall!(LinearKernel!F, "opCall", derivative); | 209| size_t[N] indices; 209| Kernel[N] kernels; | | enum rp2d = derivative; | | foreach(i; Iota!N) | { | static if (isInterval!(typeof(xs[i]))) | { 30| indices[i] = xs[i][1]; 30| auto x = xs[i][0]; | } | else | { | alias x = xs[i]; 325| indices[i] = this.findInterval!i(x); | } 355| kernels[i] = LinearKernel!F(_grid[i][indices[i]], _grid[i][indices[i] + 1], x); | } | | align(64) F[2 ^^ N][derivative + 1] local; 209| immutable strides = _data._lengths.iota.strides; | | void load(sizediff_t i)(F* from, F* to) | { | version(LDC) pragma(inline, true); | static if (i == -1) | { 834| *to = *from; | } | else | { 625| from += strides[i] * indices[i]; 625| load!(i - 1)(from, to); 625| from += strides[i]; | enum s = 2 ^^ (N - 1 - i); 625| to += s; 625| load!(i - 1)(from, to); | } | } | 209| load!(N - 1)(cast(F*) _data.ptr, cast(F*)local[0].ptr); | | foreach(i; Iota!N) | { | enum P = 2 ^^ (N - 1 - i); | enum L = 2 ^^ (N - i * (1 - rp2d)) / 2; 355| vectorize(kernels[i], local[0][0 * L .. 1 * L], local[0][1 * L .. 2 * L], *cast(F[L][2 ^^ rp2d]*)local[rp2d].ptr); | static if (rp2d == 1) 18| shuffle3!1(local[1][0 .. L], local[1][L .. 2 * L], local[0][0 .. L], local[0][L .. 2 * L]); | static if (i + 1 == N) 209| return *cast(SplineReturnType!(F, N, 2 ^^ rp2d)*) local[0].ptr; | } | } | } | } | | /// | alias withDerivative = opCall!1; |} | |/// |struct LinearKernel(X) |{ | X step = 0; | X w0 = 0; | X w1 = 0; | |@optmath: | | /// | auto lightConst()() const @property | { | return LinearKernel!X(step, w0, w1); | } | | /// | auto lightImmutable()() immutable @property | { | return LinearKernel!X(step, w0, w1); | } | | /// 379| this()(X x0, X x1, X x) | { 379| step = x1 - x0; 379| auto c0 = x - x0; 379| auto c1 = x1 - x; 379| w0 = c0 / step; 379| w1 = c1 / step; | } | | /// | template opCall(uint derivative = 0) | // if (derivative <= 1) | { | /// | auto opCall(Y)(const Y y0, const Y y1) | if (__traits(isFloating, Y)) | { 657| auto r0 = y0 * w1; 657| auto r1 = y1 * w0; 657| auto y = r0 + r1; | static if (derivative) | { 33| auto diff = y1 - y0; 33| Y[derivative + 1] ret = 0; 33| ret[0] = y; 33| ret[1] = diff / step; 33| return ret; | } | else | { 624| return y; | } | } | | static if (derivative) | auto opCall(Y, size_t N)(scope ref const Y[N] y0, scope ref const Y[N] y1) | { 2| Y[N][derivative + 1] ret = void; 2| Y[derivative + 1][N] temp = void; | | static foreach(i; 0 .. N) 4| temp[i] = this.opCall!derivative(y0[i], y1[i]); | static foreach(i; 0 .. N) | static foreach(d; 0 .. derivative + 1) 8| ret[d][i] = temp[i][d]; 2| return ret; | } | } | | /// | alias withDerivative = opCall!1; |} | |/++ |Interpolator used for non-rectiliner trapezoid-like greeds. |Params: | grid = rc-array of interpolation grid | data = rc-array of interpolator-like structures |+/ |auto metaLinear(X, T)(RCArray!(immutable X) grid, RCArray!(const T) data) |{ | import core.lifetime: move; 2| return MetaLinear!(T, X)(grid.move, data.move); |} | |/// ditto 6|struct MetaLinear(T, X) | // if (T.derivativeOrder >= 1) |{ | import mir.interpolate.utility: DeepType; | // alias ElementInterpolator = Linear!(F, N, X); | | /// | RCArray!(immutable X) grid; | /// | RCArray!(const T) data; | | /// 2| this(RCArray!(immutable X) grid, RCArray!(const T) data) | { | import core.lifetime: move; | 2| if (grid.length < 2) | { 0000000| version(D_Exceptions) throw exc_min; | else assert(0, msg_min); | } 2| if (grid.length != data.length) | { 0000000| version(D_Exceptions) throw exc_eq; | else assert(0, msg_eq); | } | 2| this.grid = grid.move; 2| this.data = data.move; | } | | /// 0000000| MetaLinear lightConst()() const @property { return *cast(MetaLinear*)&this; } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension == 0) | { 24| return grid[]; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | if (dimension == 0) | { 24| assert(data.length > 1); 24| return data.length - 1; | } | | /// | enum uint derivativeOrder = 1; | | static if (__traits(compiles, {enum N = T.dimensionCount;})) | /// | enum uint dimensionCount = T.dimensionCount + 1; | | /// | template opCall(uint derivative = 0) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(const X xs) scope const @trusted | // if (X.length == dimensionCount) | { | static if (isInterval!(typeof(xs[0]))) | { | size_t index = xs[0][1]; | auto x = xs[0][0]; | } | else | { | alias x = xs[0]; 24| size_t index = this.findInterval(x); | } 24| auto lhs = data[index + 0].opCall!derivative(xs[1 .. $]); 24| auto rhs = data[index + 1].opCall!derivative(xs[1 .. $]); | alias E = typeof(lhs); | alias F = DeepType!E; 24| auto kernel = LinearKernel!F(grid[index], grid[index + 1], x); 24| return kernel.opCall!derivative(lhs, rhs); | } | } | | /// | alias withDerivative = opCall!1; | | /// | alias withTwoDerivatives = opCall!2; |} | |/// 2D trapezoid-like (not rectilinear) linear interpolation |version(mir_test) |unittest |{ 1| auto x = [ | [0.0, 1, 2, 3, 5], | [-4.0, 3, 4], | [0.0, 10], | ]; 1| auto y = [ | [4.0, 0, 9, 23, 40], | [9.0, 0, 3], | [4.0, 40], | ]; | 1| auto g = [7.0, 10, 15]; | | import mir.rc.array: RCArray; | import mir.ndslice.allocation: rcslice; | 2| auto d = RCArray!(Linear!double)(3); | 12| foreach (i; 0 .. x.length) 3| d[i] = linear!double(x[i].rcslice!(immutable double), y[i].rcslice!(const double)); | 2| auto trapezoidInterpolator = metaLinear(g.rcarray!(immutable double), d.lightConst); | 1| auto val = trapezoidInterpolator(9.0, 1.8); 1| auto valWithDerivative = trapezoidInterpolator.withDerivative(9.0, 1.8); |} | |version(mir_test) |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice; 25| alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); | | //// set test function //// | enum y_x0 = 2; | enum y_x1 = -7; | enum y_x0x1 = 3; | | // this function should be approximated very well 42| alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; | | ///// set interpolant //// | static immutable x0 = [-1.0, 2, 8, 15]; | static immutable x1 = [-4.0, 2, 5, 10, 13]; | 2| auto grid = cartesian(x0, x1) | .map!f | .rcslice | .lightConst; | 2| auto int0 = linear!double(x1.rcslice!(immutable double), grid[0]); 2| auto int1 = linear!double(x1.rcslice!(immutable double), grid[1]); 2| auto int2 = linear!double(x1.rcslice!(immutable double), grid[2]); 2| auto int3 = linear!double(x1.rcslice!(immutable double), grid[3]); | 2| auto interpolant = metaLinear(x0.rcarray!(immutable double), rcarray(int0, int1, int2, int3).lightConst); | | ///// compute test data //// 1| auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); 1| auto real_data = test_grid.map!f; 2| auto interp_data = test_grid.vmap(interpolant); | ///// verify result //// 1| assert(all!appreq(interp_data, real_data)); | | //// check derivatives //// 1| auto z0 = 1.23; 1| auto z1 = 3.21; 1| auto d = interpolant.withDerivative(z0, z1); 1| assert(appreq(interpolant(z0, z1), f(z0, z1))); 1| assert(appreq(d[0][0], f(z0, z1))); 1| assert(appreq(d[1][0], y_x0 + y_x0x1 * z1)); 1| assert(appreq(d[0][1], y_x1 + y_x0x1 * z0)); 1| assert(appreq(d[1][1], y_x0x1)); |} source/mir/interpolate/linear.d is 92% covered <<<<<< EOF # path=source-mir-interpolate-mod.lst |/++ |$(H2 Interpolation Modifier) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2022 Ilia, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate.mod; | |import mir.math.common; | |/++ |Applies function to the interpolated value. | |Params: | fun = two arguments `(x, derivativeOrder)` function |+/ |template interpolationMap(alias fun) |{ | /// | auto interpolationMap(T)(T interpolator) | { | import core.lifetime: move; | alias S = InterpolationMap!fun; 2| return S!T(interpolator.move); | } |} | |/// ditto |template InterpolationMap(alias fun) |{ | /// 0000000| struct InterpolationMap(T) | { | static if (__traits(hasMember, T, "derivativeOrder")) | enum derivativeOrder = T.derivativeOrder; | | static if (__traits(hasMember, T, "dimensionCount")) | enum uint dimensionCount = T.dimensionCount; | | /// | T interpolator; | | /// 2| this(T interpolator) | { | import core.lifetime: move; 2| this.interpolator = interpolator.move; | } | | /// | template opCall(uint derivative = 0) | // if (derivative <= derivativeOrder) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(const X xs) scope const @trusted | // if (X.length == dimensionCount) | { 4| auto g = interpolator.opCall!derivative(xs); | | static if (derivative == 0) | { 2| typeof(g)[1] ret; 2| fun(g, ret); 2| return ret[0]; | } | else | { | static if (X.length == 1) 2| auto g0 = g[0]; | else | static if (X.length == 2) | auto g0 = g[0][0]; | else | static if (X.length == 3) | auto g0 = g[0][0][0]; | else | static assert(0, "Not implemented"); | 2| typeof(g0)[derivative + 1] f; | 2| fun(g0, f); | | static if (X.length == 1) | { 2| typeof(g) r; 2| r[0] = f[0]; 2| r[1] = f[1] * g[1]; | | static if (derivative >= 2) | { 2| r[2] = f[2] * (g[1] * g[1]) + f[1] * g[2]; | } | static if (derivative >= 3) | { 2| r[3] = f[3] * (g[1] * g[1] * g[1]) + f[1] * g[3] + 3 * (f[2] * g[1] * g[2]); | } | static if (derivative >= 4) | { | static assert(0, "Not implemented"); | } | 2| return r; | } else static assert(0, "Not implemented"); | } | } | } | } |} | |/// |version (mir_test) |unittest |{ | import mir.interpolate.spline; | import mir.math.common: log; | import mir.ndslice.allocation: rcslice; | import mir.test; | | alias g = (double x, uint d = 0) => 8| d == 0 ? 3 * x ^^ 3 + 5 * x ^^ 2 + 0.23 * x + 2 : | d == 1 ? 9 * x ^^ 2 + 10 * x + 0.23 : | double.nan; | | alias f = (double x, ref scope y) | { 2| y[0] = log(x); | static if (y.length >= 2) 1| y[1] = 1 / x; | static if (y.length >= 3) 1| y[2] = -y[1] * y[1]; | static if (y.length >= 4) 1| y[3] = -2 * y[1] * y[2]; | static if (y.length >= 5) | static assert(0, "Not implemented"); | }; | 2| auto s = spline!double( | [0.1, 0.4, 0.5, 1.0].rcslice!(immutable double), | [g(0.1), g(0.4), g(0.5), g(1.0)].rcslice!(const double) | ); | 2| auto m = s.interpolationMap!f; | 1| m(0.7).shouldApprox == log(g(0.7)); 1| auto d = m.opCall!3(0.7); 1| d[0].shouldApprox == log(g(0.7)); 1| d[1].shouldApprox == 1 / g(0.7) * g(0.7, 1); 1| d[2].shouldApprox == -0.252301; 1| d[3].shouldApprox == -4.03705; |} | |private alias implSqrt = (x, ref scope y) |{ | import mir.math.common: sqrt; 2| y[0] = sqrt(x); | static if (y.length >= 2) 1| y[1] = 0.5f / y[0]; | static if (y.length >= 3) 1| y[2] = -0.5f * y[1] / x; | static if (y.length >= 4) 1| y[3] = -1.5f * y[2] / x; | static if (y.length >= 5) | static assert(0, "Not implemented"); |}; | |/++ |Applies square root function to the interpolated value. |+/ |alias interpolationSqrt = interpolationMap!implSqrt; |/// ditto |alias InterpolationSqrt = InterpolationMap!implSqrt; | |/// |version (mir_test) |unittest |{ | import mir.interpolate.spline; | import mir.math.common: sqrt; | import mir.ndslice.allocation: rcslice; | import mir.test; | | alias g = (double x, uint d = 0) => 8| d == 0 ? 3 * x ^^ 3 + 5 * x ^^ 2 + 0.23 * x + 2 : | d == 1 ? 9 * x ^^ 2 + 10 * x + 0.23 : | double.nan; | 2| auto s = spline!double( | [0.1, 0.4, 0.5, 1.0].rcslice!(immutable double), | [g(0.1), g(0.4), g(0.5), g(1.0)].rcslice!(const double) | ); | 2| auto m = s.interpolationSqrt; | 1| m(0.7).shouldApprox == sqrt(g(0.7)); 1| auto d = m.opCall!3(0.7); 1| d[0].shouldApprox == sqrt(g(0.7)); 1| d[1].shouldApprox == 0.5 / sqrt(g(0.7)) * g(0.7, 1); 1| d[2].shouldApprox == 2.2292836438189414; 1| d[3].shouldApprox == -3.11161; |} source/mir/interpolate/mod.d is 97% covered <<<<<< EOF # path=source-mir-interpolate-package.lst |/++ |$(H1 Interpolation Algorithms) | |$(BOOKTABLE $(H2 Interpolation modules), |$(TR $(TH Module) $(TH Interpolation kind)) |$(T2M constant, Constant Interpolant) |$(T2M generic, Generic Piecewise Interpolant) |$(T2M linear, Linear Interpolant) |$(T2M polynomial, Lagrange Barycentric Interpolant) |$(T2M spline, Piecewise Cubic Hermite Interpolant Spline: C2 (with contiguous second derivative), cardinal, monotone (aka PCHIP), double-quadratic, Akima) |) |] |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir,interpolate, $1)$(NBSP) |T2M=$(TR $(TDNW $(MREF mir,interpolate,$1)) $(TD $+)) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate; | |import mir.functional: Tuple; |import mir.math.common: optmath; |import mir.ndslice.slice: Slice, Contiguous; |import mir.primitives; |import mir.qualifier; |import std.traits: isInstanceOf; | |@optmath: | |package ref iter(alias s)() { return s._iterator; }; |package alias GridVector(It) = Slice!It; | |package enum bool isInterval(T) = isInstanceOf!(Tuple, T); |package enum bool isInterval(alias T) = isInstanceOf!(Tuple, T); | |package template Repeat(ulong n, L...) |{ | static if (n) | { | static import std.meta; | alias Repeat = std.meta.Repeat!(n, L); | } | else | alias Repeat = L[0 .. 0]; |} | |/++ |Interval index for x value given. |+/ |template findInterval(size_t dimension = 0) |{ | /++ | Interval index for x value given. | Params: | interpolant = interpolant | x = X value | +/ | size_t findInterval(Interpolant, X)(auto ref const Interpolant interpolant, in X x) @trusted | { | import mir.ndslice.slice: sliced; | import mir.ndslice.sorting: transitionIndex; | static if (dimension) | { 294| immutable sizediff_t len = interpolant.intervalCount!dimension - 1; 294| auto grid = interpolant.gridScopeView!dimension[1 .. $][0 .. len]; | } | else | { 1376| immutable sizediff_t len = interpolant.intervalCount - 1; 1376| auto grid = interpolant.gridScopeView[1 .. $][0 .. len]; | } 1670| assert(len >= 0); 1670| return grid.transitionIndex!"a <= b"(x); | } |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: rcslice; | import mir.ndslice.topology: as; | import mir.ndslice.slice: sliced; | import mir.interpolate.linear; | | static immutable x = [0.0, 1, 2]; | static immutable y = [10.0, 2, 4]; 2| auto interpolation = linear!double(x.rcslice, y.as!(const double).rcslice); 1| assert(interpolation.findInterval(1.0) == 1); |} | |/++ |Lazy interpolation shell with linear complexity. | |Params: | range = sorted range | interpolant = interpolant structure with `.gridScopeView` method. |Complexity: | `O(range.length + interpolant.gridScopeView.length)` to evaluate all elements. |Returns: | Lazy input range. |See_also: | $(SUBREF linear, linear), | $(SUBREF spline, spline). |+/ |auto interp1(Range, Interpolant)(Range range, Interpolant interpolant, size_t interval = 0) |{ 2| return Interp1!(Range, Interpolant)(range, interpolant, interval); |} | |/// ditto 2|struct Interp1(Range, Interpolant) |{ | /// Sorted range (descending). $(RED For internal use.) | private Range _range; | /// Interpolant structure. $(RED For internal use.) | private Interpolant _interpolant; | /// Current interpolation interval. $(RED For internal use.) | private size_t _interval; | | /// | auto lightScope() scope | { 0000000| return Interp1!(LightScopeOf!Range, LightScopeOf!Interpolant)(.lightScope(_range), .lightScope(_interpolant), _interval); | } | | static if (hasLength!Range) | /// Length (optional) 1| size_t length()() const @property { return _range.length; } | /// Save primitive (optional) | auto save()() @property | { | auto ret = this; | ret._range = _range.save; | return ret; | } | /// Input range primitives 61| bool empty () const @property { return _range.empty; } | /// ditto 30| void popFront() { _range.popFront; } | /// ditto | auto front() @property | | { 30| assert(!empty); 30| auto x = _range.front; 30| return (x) @trusted { 30| auto points = _interpolant.gridScopeView; 30| sizediff_t len = _interpolant.intervalCount - 1; 30| assert(len >= 0); 44| while (x > points[_interval + 1] && _interval < len) 7| _interval++; 30| return _interpolant(x.atInterval(_interval)); | } (x); | } |} | |/++ |PCHIP interpolation. |+/ |version(mir_test) |@safe unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: rcslice; | import mir.interpolate: interp1; | import mir.interpolate.spline; | | static immutable x = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; | static immutable y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; 2| auto interpolation = spline!double(x.rcslice, y.sliced, SplineType.monotone); | 1| auto xs = x[0 .. $ - 1].sliced + 0.5; | 2| auto ys = xs.interp1(interpolation); |} | |@safe pure @nogc version(mir_test) unittest |{ | import mir.interpolate.linear; | import mir.ndslice; | import mir.math.common: approxEqual; | | static immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; | static immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; | static immutable xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; | 2| auto interpolation = linear!double(x.rcslice, y.as!(const double).rcslice); | | static immutable data = [0.0011, 0.0030, 0.0064, 0.0104, 0.0144, 0.0176, 0.0207, 0.0225, 0.0243, 0.0261, 0.0268, 0.0274, 0.0281, 0.0288, 0.0295, 0.0302, 0.0309, 0.0316, 0.0322, 0.0329, 0.0332, 0.0335, 0.0337, 0.0340, 0.0342, 0.0345, 0.0348, 0.0350, 0.0353, 0.0356]; | 1| () @trusted { 31| assert(all!((a, b) => approxEqual(a, b, 1e-4, 1e-4))(xs.interp1(interpolation), data)); | }(); |} | |/++ |Optimization utility that can be used with interpolants if |x should be extrapolated at interval given. | |By default interpolants uses binary search to find appropriate interval, |it has `O(log(.gridScopeView.length))` complexity. |If an interval is given, interpolant uses it instead of binary search. |+/ |Tuple!(T, size_t) atInterval(T)(in T value, size_t intervalIndex) |{ 32| return typeof(return)(value, intervalIndex); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.slice; | import mir.interpolate.spline; | static immutable x = [0.0, 1, 2]; | static immutable y = [3.0, 4, -10]; 2| auto interpolant = spline!double(x.rcslice, y.sliced); 1| assert(interpolant(1.3) != interpolant(1.3.atInterval(0))); 1| assert(interpolant(1.3) == interpolant(1.3.atInterval(1))); |} | |version(D_AVX2) | enum _avx = true; |else |version(D_AVX) | enum _avx = true; |else | enum _avx = false; | |version(LDC) | enum LDC = true; |else | enum LDC = false; | |version(X86_64) | enum x86_64 = true; |else | enum x86_64 = false; | |auto copyvec(F, size_t N)(ref const F[N] from, ref F[N] to) |{ | import mir.internal.utility; | | static if (LDC && F.mant_dig != 64 && is(__vector(F[N]))) | { | alias V = __vector(F[N]); // @FUTURE@ vector support 1776| *cast(V*) to.ptr = *cast(V*) from.ptr; | } | else | static if (F.sizeof <= double.sizeof && F[N].sizeof >= (double[2]).sizeof && x86_64) | { | import mir.utility; | enum S = _avx ? 32u : 16u; | enum M = min(S, F[N].sizeof) / F.sizeof; | alias V = __vector(F[M]); // @FUTURE@ vector support | enum C = N / M; | foreach(i; Iota!C) | { | *cast(V*)(to.ptr + i * M) = *cast(V*)(from.ptr + i * M); | } | } | else | { | to = from; | } |} | |package template SplineReturnType(F, size_t N, size_t P) |{ | static if (P <= 1 || N == 0) | alias SplineReturnType = F; | else | alias SplineReturnType = .SplineReturnType!(F, N - 1, P)[P]; |} | |template generateShuffles3(size_t N, size_t P) |{ | enum size_t[N][2] generateShuffles3 = | () | { | auto ret = new size_t[](2 * N); | size_t[2] j; | bool f = 1; | foreach(i; 0 .. N) | { | ret[i * 2] = i; | ret[i * 2 + 1] = i + N; | } | return [ret[0 .. $ / 2], ret[$ / 2 .. $]]; | }(); |} | | |void shuffle3(size_t P, F, size_t N)(ref F[N] a, ref F[N] b, ref F[N] c, ref F[N] d) | if (P <= N && N) |{ | static if (P == 0 || N == 1) | { 13| copyvec(a, c); 13| copyvec(b, d); | } | else | version(LDC) | { | enum masks = generateShuffles3!(N, P); | import std.meta: aliasSeqOf; | import ldc.simd: shufflevector; | alias V = __vector(F[N]); // @FUTURE@ vector support 9| auto as = shufflevector!(V, aliasSeqOf!(masks[0]))(*cast(V*)a.ptr, *cast(V*)b.ptr); 9| auto bs = shufflevector!(V, aliasSeqOf!(masks[1]))(*cast(V*)a.ptr, *cast(V*)b.ptr); 9| *cast(V*)c.ptr = as; 9| *cast(V*)d.ptr = bs; | } | else | { | foreach(i; Iota!(N / P)) | { | enum j = 2 * i * P; | static if (j < N) | { | copyvec(a[i * P .. i * P + P], c[j .. j + P]); | static assert(j + 2 * P <= c.length); | copyvec(b[i * P .. i * P + P], c[j + P .. j + 2 * P]); | } | else | { | copyvec(a[i * P .. i * P + P], d[j - N .. j - N + P]); | copyvec(b[i * P .. i * P + P], d[j - N + P .. j - N + 2 * P]); | } | } | } |} | |void shuffle2(size_t P, F, size_t N)(ref F[N] a, ref F[N] b, ref F[N] c, ref F[N] d) | if (P <= N && N) |{ | static if (P == 0 || N == 1) | { 872| copyvec(a, c); 872| copyvec(b, d); | } | else | version(LDC) | { | enum masks = generateShuffles2!(N, P); | import std.meta: aliasSeqOf; | import ldc.simd: shufflevector; | alias V = __vector(F[N]); // @FUTURE@ vector support 302| auto as = shufflevector!(V, aliasSeqOf!(masks[0]))(*cast(V*)a.ptr, *cast(V*)b.ptr); 302| auto bs = shufflevector!(V, aliasSeqOf!(masks[1]))(*cast(V*)a.ptr, *cast(V*)b.ptr); 302| *cast(V*)c.ptr = as; 302| *cast(V*)d.ptr = bs; | } | else | { | foreach(i; Iota!(N / P)) | { | enum j = 2 * i * P; | static if (j < N) | copyvec(a[j .. j + P], c[i * P .. i * P + P]); | else | copyvec(b[j - N .. j - N + P], c[i * P .. i * P + P]); | } | foreach(i; Iota!(N / P)) | { | enum j = 2 * i * P + P; | static if (j < N) | copyvec(a[j .. j + P], d[i * P .. i * P + P]); | else | copyvec(b[j - N .. j - N + P], d[i * P .. i * P + P]); | } | } |} | |void shuffle1(size_t P, F, size_t N)(ref F[N] a, ref F[N] b, ref F[N] c, ref F[N] d) | if (P <= N && N) |{ | static if (P == 0 || N == 1) | { | copyvec(a, c); | copyvec(b, d); | } | else | version(LDC) | { | enum masks = generateShuffles1!(N, P); | import std.meta: aliasSeqOf; | import ldc.simd: shufflevector; | alias V = __vector(F[N]); // @FUTURE@ vector support 2| auto as = shufflevector!(V, aliasSeqOf!(masks[0]))(*cast(V*)a.ptr, *cast(V*)b.ptr); 2| auto bs = shufflevector!(V, aliasSeqOf!(masks[1]))(*cast(V*)a.ptr, *cast(V*)b.ptr); 2| *cast(V*)c.ptr = as; 2| *cast(V*)d.ptr = bs; | } | else | { | foreach(i; Iota!(N / P)) | { | enum j = i * P; | static if (i % 2 == 0) | copyvec(a[j .. j + P], c[i * P .. i * P + P]); | else | copyvec(b[j - P .. j], c[i * P .. i * P + P]); | } | foreach(i; Iota!(N / P)) | { | enum j = i * P + P; | static if (i % 2 == 0) | copyvec(a[j .. j + P], d[i * P .. i * P + P]); | else | copyvec(b[j - P .. j], d[i * P .. i * P + P]); | } | } |} | |template generateShuffles2(size_t N, size_t P) |{ | enum size_t[N][2] generateShuffles2 = | () | { | auto ret = new size_t[][](2, N); | size_t[2] j; | bool f = 1; | foreach(i; 0 .. 2 * N) | { | if (i % P == 0) | f = !f; | ret[f][j[f]++] = i; | } | return ret; | }(); |} | | |template generateShuffles1(size_t N, size_t P) |{ | enum size_t[N][2] generateShuffles1 = | () | { | auto ret = new size_t[][](2, N); | foreach(i; 0 .. N) | { | ret[0][i] = (i / P + 1) % 2 ? i : i + N - P; | ret[1][i] = ret[0][i] + P; | } | return ret; | }(); |} | |unittest |{ 1| assert(generateShuffles1!(4, 1) == [[0, 4, 2, 6], [1, 5, 3, 7]]); 1| assert(generateShuffles1!(4, 2) == [[0, 1, 4, 5], [2, 3, 6, 7]]); 1| assert(generateShuffles1!(4, 4) == [[0, 1, 2, 3], [4, 5, 6, 7]]); |} | |unittest |{ 1| assert(generateShuffles2!(4, 1) == [[0, 2, 4, 6], [1, 3, 5, 7]]); 1| assert(generateShuffles2!(4, 2) == [[0, 1, 4, 5], [2, 3, 6, 7]]); 1| assert(generateShuffles2!(4, 4) == [[0, 1, 2, 3], [4, 5, 6, 7]]); |} | |unittest |{ | enum ai = [0, 1, 2, 3]; | enum bi = [4, 5, 6, 7]; | align(32) | double[4] a = [0, 1, 2, 3], b = [4, 5, 6, 7], c, d; 1| shuffle3!1(a, b, c, d); 1| assert([c, d] == [[0.0, 4, 1, 5], [2.0, 6, 3, 7]]); |} | |unittest |{ | enum ai = [0, 1, 2, 3]; | enum bi = [4, 5, 6, 7]; | align(32) | double[4] a = [0, 1, 2, 3], b = [4, 5, 6, 7], c, d; 1| shuffle2!1(a, b, c, d); 1| assert([c, d] == [[0.0, 2, 4, 6], [1.0, 3, 5, 7]]); 1| shuffle2!2(a, b, c, d); 1| assert([c, d] == [[0.0, 1, 4, 5], [2.0, 3, 6, 7]]); | // shuffle2!4(a, b, c, d); | // assert([c, d] == [[0.0, 1, 2, 3], [4.0, 5, 6, 7]]); |} | |unittest |{ | enum ai = [0, 1, 2, 3]; | enum bi = [4, 5, 6, 7]; | align(32) | double[4] a = [0, 1, 2, 3], b = [4, 5, 6, 7], c, d; 1| shuffle1!1(a, b, c, d); 1| assert([c, d] == [[0, 4, 2, 6], [1, 5, 3, 7]]); 1| shuffle1!2(a, b, c, d); 1| assert([c, d] == [[0, 1, 4, 5], [2, 3, 6, 7]]); | // shuffle1!4(a, b, c, d); | // assert([c, d] == [[0, 1, 2, 3], [4, 5, 6, 7]]); |} | |import mir.internal.utility; | |auto vectorize(Kernel, F, size_t N, size_t R)(ref Kernel kernel, ref F[N] a0, ref F[N] b0, ref F[N] a1, ref F[N] b1, ref F[N][R] c) |{ | // static if (LDC && F.mant_dig != 64) | // { | // alias V = __vector(F[N]); // @FUTURE@ vector support | // *cast(V[R]*) c.ptr = kernel( | // *cast(V*)a0.ptr, *cast(V*)b0.ptr, | // *cast(V*)a1.ptr, *cast(V*)b1.ptr); | // } | // else | // static if (F.sizeof <= double.sizeof && F[N].sizeof >= (double[2]).sizeof) | // { | // import mir.utility; | // enum S = _avx ? 32u : 16u; | // enum M = min(S, F[N].sizeof) / F.sizeof; | // alias V = __vector(F[M]); // @FUTURE@ vector support | // enum C = N / M; | // foreach(i; Iota!C) | // { | // auto r = kernel( | // *cast(V*)(a0.ptr + i * M), *cast(V*)(b0.ptr + i * M), | // *cast(V*)(a1.ptr + i * M), *cast(V*)(b1.ptr + i * M), | // ); | // static if (R == 1) | // *cast(V*)(c[0].ptr + i * M) = r; | // else | // foreach(j; Iota!R) | // *cast(V*)(c[j].ptr + i * M) = r[j]; | // } | // } | // else | // { | foreach(i; Iota!N) | { 1794| auto r = kernel(a0[i], b0[i], a1[i], b1[i]); | static if (R == 1) 1675| c[0][i] = r; | else | foreach(j; Iota!R) 286| c[j][i] = r[j]; | } | // } |} | |auto vectorize(Kernel, F, size_t N, size_t R)(ref Kernel kernel, ref F[N] a, ref F[N] b, ref F[N][R] c) |{ | // static if (LDC && F.mant_dig != 64 && is(__vector(F[N]))) | // { | // alias V = __vector(F[N]); // @FUTURE@ vector support | // *cast(V[R]*) c.ptr = kernel(*cast(V*)a.ptr, *cast(V*)b.ptr); | // } | // else | // static if (F.sizeof <= double.sizeof && F[N].sizeof >= (double[2]).sizeof && x86_64) | // { | // import mir.utility; | // enum S = _avx ? 32u : 16u; | // enum M = min(S, F[N].sizeof) / F.sizeof; | // alias V = __vector(F[M]); // @FUTURE@ vector support | // enum C = N / M; | // foreach(i; Iota!C) | // { | // auto r = kernel( | // *cast(V*)(a.ptr + i * M), | // *cast(V*)(b.ptr + i * M), | // ); | // static if (R == 1) | // *cast(V*)(c[0].ptr + i * M) = r; | // else | // foreach(j; Iota!R) | // *cast(V*)(c[j].ptr + i * M) = r[j]; | // } | // } | // else | // { 355| F[N][R] _c;//Temporary array in case "c" overlaps "a" and/or "b". | foreach(i; Iota!N) | { 631| auto r = kernel(a[i], b[i]); | static if (R == 1) 602| _c[0][i] = r; | else | foreach(j; Iota!R) 58| _c[j][i] = r[j]; | } 355| c = _c; | // } |} | |// version(unittest) |// template _test_fun(size_t R) |// { |// auto _test_fun(T)(T a0, T b0, T a1, T b1) |// { |// static if (R == 4) |// { |// return cast(T[R])[a0, b0, a1, b1]; |// } |// else |// static if (R == 2) |// { |// return cast(T[R])[a0 + b0, a1 + b1]; |// } |// else |// return a0 + b0 + a1 + b1; |// } |// } | |// unittest |// { |// import std.meta; | |// foreach(F; AliasSeq!(float, double)) |// foreach(N; AliasSeq!(1, 2, 4, 8, 16)) |// { |// align(F[N].sizeof) F[N] a0 = 4; |// align(F[N].sizeof) F[N] b0 = 30; |// align(F[N].sizeof) F[N] a1 = 200; |// align(F[N].sizeof) F[N] b1 = 1000; |// align(F[N].sizeof) F[N][1] c1; |// align(F[N].sizeof) F[N][2] c2; |// align(F[N].sizeof) F[N][4] c4; |// vectorize!(_test_fun!(1))(a0, b0, a1, b1, c1); |// vectorize!(_test_fun!(2))(a0, b0, a1, b1, c2); |// vectorize!(_test_fun!(4))(a0, b0, a1, b1, c4); |// } |// } source/mir/interpolate/package.d is 98% covered <<<<<< EOF # path=source-mir-interpolate-polynomial.lst |/++ |$(H2 Lagrange Barycentric Interpolation) | |See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate.polynomial; | |/// |public import mir.interpolate: atInterval; |import core.lifetime: move; |import mir.functional: Tuple; |import mir.internal.utility : isFloatingPoint, Iota; |import mir.interpolate: findInterval; |import mir.math.common; |import mir.math.numeric; |import mir.math.sum; |import mir.ndslice.slice; |import mir.ndslice.topology: diff, map, member, iota, triplets; |import mir.rc.array; |import mir.utility: min, swap; |import std.traits: Unqual; | |@optmath: | |/// |version(mir_test) |@safe pure unittest |{ | import mir.algorithm.iteration: all; | import mir.math; | import mir.ndslice; | 724| auto f(double x) { return (x-2) * (x-5) * (x-9); } 480| auto fd(double x) { return (x-5) * (x-9) + (x-2) * (x-5) + (x-2) * (x-9); } 240| auto fd2(double x) { return (x-5) + (x-9) + (x-2) + (x-5) + (x-2) + (x-9); } 240| double[2] fWithDerivative(double x) { return [f(x), fd(x)]; } 240| double[3] fWithTwoDerivatives(double x) { return [f(x), fd(x), fd2(x)]; } | | // 1| auto x = [0.0, 3, 5, 10]; 1| auto y = x.map!f.slice.field; | // `!2` adds first two derivatives: f, f', and f'' | // default parameter is 0 2| auto l = x.lagrange!2(y); | 39| foreach(test; x ~ [2.0, 5, 9] ~ [double(PI), double(E), 10, 100, 1e3]) 180| foreach(scale; [-1, +1, 1 + double.epsilon, 1 + double.epsilon.sqrt]) 864| foreach(shift; [0, double.min_normal/2, double.min_normal*2, double.epsilon, double.epsilon.sqrt]) | { 240| auto testX = test * scale + shift; | 240| auto lx = l(testX); 240| auto l_ldx = l.withDerivative(testX); 240| auto l_ld_ld2x = l.withTwoDerivatives(testX); | 240| assert(lx.approxEqual(f(testX))); 240| assert(l_ldx[].all!approxEqual(fWithDerivative(testX)[])); 240| assert(l_ld_ld2x[].all!approxEqual(fWithTwoDerivatives(testX)[])); | } |} | |/++ |Constructs barycentric lagrange interpolant. |+/ |Lagrange!(T, maxDerivative) lagrange(uint maxDerivative = 0, T, X)(scope const X[] x, scope const T[] y) @trusted | if (maxDerivative < 16) |{ | import mir.ndslice.allocation: rcslice; 1| return x.rcslice!(immutable X).lagrange!maxDerivative(y.sliced); |} | |/// ditto |Lagrange!(Unqual!(Slice!(Iterator, 1, kind).DeepElement), maxDerivative, X) | lagrange(uint maxDerivative = 0, X, Iterator, SliceKind kind)(Slice!(RCI!(immutable X)) x, Slice!(Iterator, 1, kind) y) @trusted | if (maxDerivative < 16) |{ | alias T = Unqual!(Slice!(Iterator, 1, kind).DeepElement); 1| return Lagrange!(T, maxDerivative)(x.move, rcarray!T(y)); |} | |/++ |+/ 0000000|struct Lagrange(T, uint maxAdditionalFunctions = 0, X = T) | if (isFloatingPoint!T && maxAdditionalFunctions < 16) |{ | /// $(RED for internal use only.) | Slice!(RCI!(immutable X)) _grid; | /// $(RED for internal use only.) | RCArray!(immutable T) _inversedBarycentricWeights; | /// $(RED for internal use only.) | RCArray!T[maxAdditionalFunctions + 1] _normalizedValues; | /// $(RED for internal use only.) | T[maxAdditionalFunctions + 1] _asums; | |@optmath @safe pure @nogc extern(D): | | /// | enum uint derivativeOrder = maxAdditionalFunctions; | /// | enum uint dimensionCount = 1; | | /++ | Complexity: `O(N)` | +/ | pragma(inline, false) 1| this(Slice!(RCI!(immutable X)) grid, RCArray!T values, RCArray!(immutable T) inversedBarycentricWeights) | { | import mir.algorithm.iteration: all; 1| assert(grid.length > 1); 1| assert(grid.length == values.length); 1| assert(grid.length == inversedBarycentricWeights.length); | enum msg = "Lagrange: grid points are too close or have not finite values."; | version(D_Exceptions) | { | enum T md = T.min_normal / T.epsilon; | static immutable exc = new Exception(msg); 7| if (!grid.lightScope.diff.all!(a => md <= a && a <= T.max)) 0000000| throw exc; | } 1| swap(_grid, grid); 1| swap(_inversedBarycentricWeights, inversedBarycentricWeights); 1| swap(_normalizedValues[0], values); 1| auto w = _inversedBarycentricWeights[].sliced; | foreach (_; Iota!maxAdditionalFunctions) | { 2| auto y = _normalizedValues[_][].sliced; | static if (_ == 0) 1| _asums[_] = y.asumNorm; 2| _normalizedValues[_ + 1] = RCArray!T(_grid.length, true, false); 2| auto d = _normalizedValues[_ + 1][].sliced; 2| polynomialDerivativeValues(d, _grid.lightScope, y, w); 2| _asums[_ + 1] = d.asumNorm * _asums[_]; | } | } | | /++ | Complexity: `O(N^^2)` | +/ 1| this(Slice!(RCI!(immutable X)) grid, RCArray!T values) | { | import core.lifetime: move; 2| auto weights = grid.lightScope.inversedBarycentricWeights; 1| this(grid.move, values.move, weights.move); | } | |scope const: | | /// | Lagrange lightConst()() const @property @trusted { return *cast(Lagrange*)&this; } | /// | Lagrange lightImmutable()() immutable @property @trusted { return *cast(Lagrange*)&this; } | | @property | { | /// 0000000| ref const(Slice!(RCI!(immutable X))) grid() { return _grid; } | /// 720| immutable(X)[] gridScopeView() return scope const @property @trusted { return _grid.lightScope.field; } | /// 0000000| ref const(RCArray!(immutable T)) inversedBarycentricWeights() { return _inversedBarycentricWeights; } | /// 0000000| ref const(RCArray!T)[maxAdditionalFunctions + 1] normalizedValues() { return _normalizedValues; } | /// 1272| ref const(T)[maxAdditionalFunctions + 1] asums() { return _asums; } | /// | size_t intervalCount(size_t dimension = 0)() | { 720| assert(_grid.length > 1); 720| return _grid.length - 1; | } | } | | template opCall(uint derivative = 0) | if (derivative <= maxAdditionalFunctions) | { | /// | pragma(inline, false) | auto opCall(T x) | { 720| return opCall!derivative(Tuple!(size_t, T)(this.findInterval(x), x)); | } | | /// | pragma(inline, false) | auto opCall(Tuple!(size_t, T) tuple) | { | 720| const x = tuple[1]; 720| const idx = tuple[0]; 720| const inversedBarycentricWeights = _inversedBarycentricWeights[].sliced; 720| Slice!(const(T)*)[derivative + 1] values; | foreach (i; Iota!(derivative + 1)) 1440| values[i] = _normalizedValues[i][].sliced; 720| const T[2] pd = [ | T(x - _grid[idx + 0]).fabs, | T(x - _grid[idx + 1]).fabs, | ]; 720| ptrdiff_t fastIndex = | pd[0] < T.min_normal ? idx + 0 : | pd[1] < T.min_normal ? idx + 1 : | -1; 720| if (fastIndex >= 0) | { | static if (derivative == 0) | { 28| return values[0][fastIndex] * _asums[0]; | } | else | { 56| T[derivative + 1] ret = 0; | foreach (_; Iota!(derivative + 1)) 140| ret[_] = values[_][fastIndex] * _asums[_]; 56| return ret; | } | } 636| T d = 0; 636| T[derivative + 1] n = 0; 9540| foreach (i; 0 .. _grid.length) | { 2544| T w = 1 / (inversedBarycentricWeights[i] * (x - _grid[i])); 2544| d += w; | foreach (_; Iota!(derivative + 1)) 5088| n[_] += w * values[_][i]; | } | foreach (_; Iota!(derivative + 1)) | { 1272| n[_] /= d; 1272| n[_] *= asums[_]; | } | static if (derivative == 0) 212| return n[0]; | else 424| return n; | } | } | | static if (maxAdditionalFunctions) | { | /// | alias withDerivative = opCall!1; | | static if (maxAdditionalFunctions > 1) | { | /// | alias withTwoDerivatives = opCall!2; | } | } |} | | |/++ |+/ |pragma(inline, false) |@nogc |RCArray!(immutable T) inversedBarycentricWeights(T)(Slice!(const(T)*) x) | if (isFloatingPoint!T) |{ | 1| const n = x.length; 2| scope prodsa = RCArray!(ProdAccumulator!T)(n); 1| scope p = prodsa[].sliced; 14| foreach (triplet; n.iota.triplets) with(triplet) | { 26| foreach (l; left) | { 6| auto e = prod!T(x[center] - x[l]); 6| p[l] *= -e; 6| p[center] *= e; | } | } | import mir.algorithm.iteration: reduce; 1| const minExp = long.max.reduce!min(p.member!"exp"); 14| foreach (ref e; p) 4| e = e.ldexp(1 - minExp); 1| auto ret = rcarray!(immutable T)(p.member!"prod"); 1| return ret; |} | |/++ |Computes derivative values in the same points |Params: | d = derivative values (output) | y = values | x = grid | w = inversed barycentric weights |Returns: | Derivative values in the same points |+/ |@nogc |Slice!(T*) polynomialDerivativeValues(T)(return Slice!(T*) d, Slice!(const(T)*) x, Slice!(const(T)*) y, Slice!(const(T)*) w) | if (isFloatingPoint!T) |{ | pragma(inline, false); | import mir.math.sum; | 2| const n = x.length; 2| assert(n == w.length); 2| assert(n == y.length); | 2| d.fillCollapseSums!(Summation.fast, 8| (c, s1, s2) => w[c] * s1 + y[c] * s2, 16| (c, _) => y[_] / (w[_] * (x[c] - x[_])), 16| (c, _) => map!"1 / a"(x[c] - x[_])); 2| return d; |} | |/// |@nogc |Slice!(T*) polynomialDerivativeValues(T)(return Slice!(T*) d, Slice!(const(T)*) x, Slice!(const(T)*) y) | if (isFloatingPoint!T) |{ | return .polynomialDerivativeValues(d, x, y, x.inversedBarycentricWeights[].sliced); |} | |private T asumNorm(T)(Slice!(T*) v) |{ | pragma(inline, false); 3| auto n = v.map!fabs.sum!"fast"; 3| v[] /= n; 3| return n; |} source/mir/interpolate/polynomial.d is 94% covered <<<<<< EOF # path=source-mir-interpolate-spline.lst |/++ |$(H2 Cubic Spline Interpolation) | |The module provides common C2 splines, monotone (PCHIP) splines, Akima splines and others. | |See_also: $(LREF SplineType), $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.interpolate.spline; | |import core.lifetime: move; |import mir.functional; |import mir.internal.utility; |import mir.interpolate; |import mir.math.common; |import mir.ndslice.slice; |import mir.primitives; |import mir.rc.array; |import mir.utility: min, max; |import std.meta: AliasSeq, staticMap; |import std.traits: Unqual; | |/// |public import mir.interpolate: atInterval; | |static immutable msg_min = "spline interpolant: minimal allowed length for the grid equals 2."; |static immutable msg_eq = "spline interpolant: X and Y values length should be equal."; |static immutable splineConfigurationMsg = "spline configuration: .boundary method requires equal left and right boundaries"; | |version(D_Exceptions) |{ | static immutable exc_min = new Exception(msg_min); | static immutable exc_eq = new Exception(msg_eq); | static immutable splineConfigurationException = new Exception(splineConfigurationMsg); |} | |private template ValueType(T, X) |{ | static if (__traits(compiles, {enum N = T.dimensionCount;})) | alias ValueType = typeof(T.init.opCall(Repeat!(T.dimensionCount, X.init))); | else | alias ValueType = typeof(T.init.opCall(X.init)); |} | |@fmamath: | |/// |@safe pure @nogc version(mir_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: rcslice; | import mir.ndslice.topology: vmap; | | static immutable xdata = [-1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; 2| auto x = xdata.rcslice; | static immutable ydata = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; 1| auto y = ydata.sliced; | 2| auto interpolant = spline!double(x, y, SplineConfiguration!double()); // constructs Spline 2| auto xs = x + 0.5; // input X values for cubic spline | | static immutable test_data0 = [ | -0.68361541, 7.28568719, 10.490694 , 0.36192032, | 11.91572713, 16.44546433, 17.66699525, 4.52730869, | 19.22825394, -2.3242592 ]; | /// not-a-knot (default) 1| assert(xs.vmap(interpolant).all!approxEqual(test_data0)); | | static immutable test_data1 = [ | 10.85298372, 5.26255911, 10.71443229, 0.1824536 , | 11.94324989, 16.45633939, 17.59185094, 4.86340188, | 17.8565408 , 2.81856494]; | /// natural cubic spline 1| interpolant = spline!double(x, y, SplineBoundaryType.secondDerivative); 1| assert(xs.vmap(interpolant).all!approxEqual(test_data1)); | | static immutable test_data2 = [ | 9.94191781, 5.4223652 , 10.69666392, 0.1971149 , 11.93868415, | 16.46378847, 17.56521661, 4.97656997, 17.39645585, 4.54316446]; | /// set both boundary second derivatives to 3 1| interpolant = spline!double(x, y, SplineBoundaryType.secondDerivative, 3); 1| assert(xs.vmap(interpolant).all!approxEqual(test_data2)); | | static immutable test_data3 = [ | 16.45728263, 4.27981687, 10.82295092, 0.09610695, | 11.95252862, 16.47583126, 17.49964521, 5.26561539, | 16.21803478, 8.96104974]; | /// set both boundary derivatives to 3 1| interpolant = spline!double(x, y, SplineBoundaryType.firstDerivative, 3); 1| assert(xs.vmap(interpolant).all!approxEqual(test_data3)); | | static immutable test_data4 = [ | 16.45730084, 4.27966112, 10.82337171, 0.09403945, | 11.96265209, 16.44067375, 17.6374694 , 4.67438921, | 18.6234452 , -0.05582876]; | /// different boundary conditions 1| interpolant = spline!double(x, y, | SplineBoundaryCondition!double(SplineBoundaryType.firstDerivative, 3), | SplineBoundaryCondition!double(SplineBoundaryType.secondDerivative, -5)); 1| assert(xs.vmap(interpolant).all!approxEqual(test_data4)); | | | static immutable test_data5 = [ | 12.37135558, 4.99638066, 10.74362441, 0.16008641, | 11.94073593, 16.47908148, 17.49841853, 5.26600921, | 16.21796051, 8.96102894]; | // ditto 1| interpolant = spline!double(x, y, | SplineBoundaryCondition!double(SplineBoundaryType.secondDerivative, -5), | SplineBoundaryCondition!double(SplineBoundaryType.firstDerivative, 3)); 1| assert(xs.vmap(interpolant).all!approxEqual(test_data5)); | | static immutable test_data6 = [ | 11.40871379, 2.64278898, 9.55774317, 4.84791141, 11.24842121, | 16.16794267, 18.58060557, 5.2531411 , 17.45509005, 1.86992521]; | /// Akima spline 1| interpolant = spline!double(x, y, SplineType.akima); 1| assert(xs.vmap(interpolant).all!approxEqual(test_data6)); | | static immutable test_data7 = [ | 12.363463972190182, 3.066421497605127, 9.848385331143952, 4.429544487015751, 11.244211106655975, | 16.255750063889600, 18.140374440374440, 5.291585909796099, 17.722477222271888, 3.181558328118484, | ]; | /// Modified Akima spline 1| interpolant = spline!double(x, y, SplineType.makima); 1| assert(xs.vmap(interpolant).all!approxEqual(test_data7)); | | /// Double Quadratic spline 1| interpolant = spline!double(x, y, SplineType.doubleQuadratic); | import mir.interpolate.utility: ParabolaKernel; 1| auto kernel1 = ParabolaKernel!double(x[2], x[3], x[4], y[2], y[3], y[4]); 1| auto kernel2 = ParabolaKernel!double( x[3], x[4], x[5], y[3], y[4], y[5]); | // weighted sum of quadratic functions 1| auto c = 0.35; // from [0 .. 1] 1| auto xp = c * x[3] + (1 - c) * x[4]; 1| auto yp = c * kernel1(xp) + (1 - c) * kernel2(xp); 1| assert(interpolant(xp).approxEqual(yp)); | // check parabolic extrapolation of the boundary intervals 1| kernel1 = ParabolaKernel!double(x[0], x[1], x[2], y[0], y[1], y[2]); 1| kernel2 = ParabolaKernel!double(x[$ - 3], x[$ - 2], x[$ - 1], y[$ - 3], y[$ - 2], y[$ - 1]); 1| assert(interpolant(x[0] - 23.421).approxEqual(kernel1(x[0] - 23.421))); 1| assert(interpolant(x[$ - 1] + 23.421).approxEqual(kernel2(x[$ - 1] + 23.421))); |} | |/// |@safe pure version(mir_test) unittest |{ | import mir.rc.array: rcarray; | import mir.algorithm.iteration: all; | import mir.functional: aliasCall; | import mir.math.common: approxEqual; | import mir.ndslice.allocation: uninitSlice; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: vmap, map; | 2| auto x = rcarray!(immutable double)(-1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22).asSlice; 2| auto y = rcarray( | 8.77842512, | 7.96429686, | 7.77074363, | 1.10838032, | 2.69925191, | 1.84922654, | 1.48167283, | 2.8267636 , | 0.40200172, | 7.78980608).asSlice; | 2| auto interpolant = x.spline!double(y); // default boundary condition is 'not-a-knot' | 2| auto xs = x + 0.5; | 2| auto ys = xs.vmap(interpolant); | 1| auto r = | [5.56971848, | 9.30342403, | 4.44139761, | -0.74740285, | 3.00994108, | 1.50750417, | 1.73144979, | 2.64860361, | 0.64413911, | 10.81768928]; | 1| assert(all!approxEqual(ys, r)); | | // first derivative 2| auto d1 = xs.vmap(interpolant.aliasCall!"withDerivative").map!"a[1]"; 1| auto r1 = | [-4.51501279, | 2.15715986, | -7.28363308, | -2.14050449, | 0.03693092, | -0.49618999, | 0.58109933, | -0.52926703, | 0.7819035 , | 6.70632693]; 1| assert(all!approxEqual(d1, r1)); | | // second derivative 2| auto d2 = xs.vmap(interpolant.aliasCall!"withTwoDerivatives").map!"a[2]"; 1| auto r2 = | [7.07104751, | -2.62293241, | -0.01468508, | 5.70609505, | -2.02358911, | 0.72142061, | 0.25275483, | -0.6133589 , | 1.26894416, | 2.68067146]; 1| assert(all!approxEqual(d2, r2)); | | // third derivative (6 * a) 2| auto d3 = xs.vmap(interpolant.aliasCall!("opCall", 3)).map!"a[3]"; 1| auto r3 = | [-3.23132664, | -3.23132664, | 14.91047457, | -3.46891432, | 1.88520325, | -0.16559031, | -0.44056064, | 0.47057577, | 0.47057577, | 0.47057577]; 1| assert(all!approxEqual(d3, r3)); |} | |/// R -> R: Cubic interpolation |version(mir_test) |@safe unittest |{ | import mir.test; | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice; | | static immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; | static immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; 1| auto xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; | 2| auto interpolation = spline!double(x.rcslice, y.sliced); | 1| auto data = | [ 0.0011 , 0.003 , 0.0064 , 0.01042622, 0.0144 , | 0.01786075, 0.0207 , 0.02293441, 0.02467983, 0.0261 , | 0.02732764, 0.02840225, 0.0293308 , 0.03012914, 0.03081002, | 0.03138766, 0.03187161, 0.03227637, 0.03261468, 0.0329 , | 0.03314357, 0.03335896, 0.03355892, 0.03375674, 0.03396413, | 0.03419436, 0.03446018, 0.03477529, 0.03515072, 0.0356 ]; | 1| assert(all!approxEqual(xs.sliced.vmap(interpolation), data)); |} | |/// R^2 -> R: Bicubic interpolation |version(mir_test) |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice; 21| alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); | | ///// set test function //// 1| const double y_x0 = 2; 1| const double y_x1 = -7; 1| const double y_x0x1 = 3; | | // this function should be approximated very well 41| alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; | | ///// set interpolant //// | static immutable x0 = [-1.0, 2, 8, 15]; | static immutable x1 = [-4.0, 2, 5, 10, 13]; 1| auto grid = cartesian(x0, x1); | 2| auto interpolant = spline!(double, 2)(x0.rcslice, x1.rcslice, grid.map!f); | | ///// compute test data //// 1| auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); | // auto test_grid = cartesian(x0 + 0, x1 + 0); 1| auto real_data = test_grid.map!f; 2| auto interp_data = test_grid.vmap(interpolant); | | ///// verify result //// 1| assert(all!appreq(interp_data, real_data)); | | //// check derivatives //// 1| auto z0 = 1.23; 1| auto z1 = 3.21; | // writeln("-----------------"); | // writeln("-----------------"); 1| auto d = interpolant.withDerivative(z0, z1); 1| assert(appreq(interpolant(z0, z1), f(z0, z1))); | // writeln("d = ", d); | // writeln("interpolant.withTwoDerivatives(z0, z1) = ", interpolant.withTwoDerivatives(z0, z1)); | // writeln("-----------------"); | // writeln("-----------------"); | // writeln("interpolant(z0, z1) = ", interpolant(z0, z1)); | // writeln("y_x0 + y_x0x1 * z1 = ", y_x0 + y_x0x1 * z1); | // writeln("y_x1 + y_x0x1 * z0 = ", y_x1 + y_x0x1 * z0); | // writeln("-----------------"); | // writeln("-----------------"); | // assert(appreq(d[0][0], f(z0, z1))); | // assert(appreq(d[1][0], y_x0 + y_x0x1 * z1)); | // assert(appreq(d[0][1], y_x1 + y_x0x1 * z0)); | // assert(appreq(d[1][1], y_x0x1)); |} | |/// R^3 -> R: Tricubic interpolation |version(mir_test) |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice; 63| alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); | | ///// set test function //// | enum y_x0 = 2; | enum y_x1 = -7; | enum y_x2 = 5; | enum y_x0x1 = 10; | enum y_x0x1x2 = 3; | | // this function should be approximated very well 123| alias f = (x0, x1, x2) => y_x0 * x0 + y_x1 * x1 + y_x2 * x2 | + y_x0x1 * x0 * x1 + y_x0x1x2 * x0 * x1 * x2 - 11; | | ///// set interpolant //// | static immutable x0 = [-1.0, 2, 8, 15]; | static immutable x1 = [-4.0, 2, 5, 10, 13]; | static immutable x2 = [3, 3.7, 5]; 1| auto grid = cartesian(x0, x1, x2); | 2| auto interpolant = spline!(double, 3)(x0.rcslice, x1.rcslice, x2.rcslice, grid.map!f); 1| assert(interpolant.convexity == [SplineConvexity.none, SplineConvexity.none, SplineConvexity.convex]); | | ///// compute test data //// 1| auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23, x2.sliced - 3); 1| auto real_data = test_grid.map!f; 2| auto interp_data = test_grid.vmap(interpolant); | | ///// verify result //// 1| assert(all!appreq(interp_data, real_data)); | | //// check derivatives //// 1| auto z0 = 1.23; 1| auto z1 = 3.23; 1| auto z2 = -3; 1| auto d = interpolant.withDerivative(z0, z1, z2); 1| assert(appreq(interpolant(z0, z1, z2), f(z0, z1, z2))); 1| assert(appreq(d[0][0][0], f(z0, z1, z2))); | | // writeln("-----------------"); | // writeln("-----------------"); | // auto d = interpolant.withDerivative(z0, z1); 1| assert(appreq(interpolant(z0, z1, z2), f(z0, z1, z2))); | // writeln("interpolant(z0, z1, z2) = ", interpolant(z0, z1, z2)); | // writeln("d = ", d); | // writeln("interpolant.withTwoDerivatives(z0, z1, z2) = ", interpolant.withTwoDerivatives(z0, z1, z2)); | // writeln("-----------------"); | // writeln("-----------------"); | // writeln("interpolant(z0, z1) = ", interpolant(z0, z1)); | // writeln("y_x0 + y_x0x1 * z1 = ", y_x0 + y_x0x1 * z1); | // writeln("y_x1 + y_x0x1 * z0 = ", y_x1 + y_x0x1 * z0); | // writeln("-----------------"); | // writeln("-----------------"); | | // writeln("y_x0 + y_x0x1 * z1 + y_x0x1x2 * z1 * z2 = ", y_x0 + y_x0x1 * z1 + y_x0x1x2 * z1 * z2); | // assert(appreq(d[1][0][0], y_x0 + y_x0x1 * z1 + y_x0x1x2 * z1 * z2)); | // writeln("y_x1 + y_x0x1 * z0 + y_x0x1x2 * z0 * z2 = ", y_x1 + y_x0x1 * z0 + y_x0x1x2 * z0 * z2); | // assert(appreq(d[0][1][0], y_x1 + y_x0x1 * z0 + y_x0x1x2 * z0 * z2)); | // writeln("y_x0x1 + y_x0x1x2 * z2 = ", y_x0x1 + y_x0x1x2 * z2); | // assert(appreq(d[1][1][0], y_x0x1 + y_x0x1x2 * z2)); | // writeln("y_x0x1x2 = ", y_x0x1x2); | // assert(appreq(d[1][1][1], y_x0x1x2)); |} | | |/// Monotone PCHIP |version(mir_test) |@safe unittest |{ | import mir.test; | import mir.math.common: approxEqual; | import mir.algorithm.iteration: all; | import mir.ndslice.allocation: rcslice; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: vmap; | | static immutable x = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; | static immutable y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; 2| auto interpolant = spline!double(x.rcslice, y.sliced, SplineType.monotone); 1| interpolant.argmin.should == 2; 1| interpolant.withDerivative(2)[1].should == 0; 1| interpolant.argmax.should == 12; 1| interpolant.withDerivative(12)[1].should == 0; | 1| auto xs = x[0 .. $ - 1].sliced + 0.5; | 2| auto ys = xs.vmap(interpolant); | 1| assert(ys.all!approxEqual([ | 5.333333333333334, | 2.500000000000000, | 10.000000000000000, | 4.288971807628524, | 11.202580845771145, | 16.250000000000000, | 17.962962962962962, | 5.558593750000000, | 17.604662698412699, | ])); |} | |// Check direction equality |version(mir_test) |@safe unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: rcslice; | import mir.ndslice.topology: retro, vmap; | | static immutable points = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; | static immutable values = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; | 1| auto results = [ | 5.333333333333334, | 2.500000000000000, | 10.000000000000000, | 4.288971807628524, | 11.202580845771145, | 16.250000000000000, | 17.962962962962962, | 5.558593750000000, | 17.604662698412699, | ]; 2| auto interpolant = spline!double(points.rcslice, values.sliced, SplineType.monotone); | 2| auto pointsR = rcslice(-points.retro); 2| auto valuesR = values.retro.rcslice; 2| auto interpolantR = spline!double(pointsR, valuesR, SplineType.monotone); | | version(X86_64) 1| assert(vmap(points[0 .. $ - 1].sliced + 0.5, interpolant) == vmap(pointsR.retro[0 .. $ - 1] - 0.5, interpolantR)); |} | |/// argmin, +, - tests |version(mir_test) |unittest |{ | import mir.test; | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: rcslice; | | static immutable points = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; | static immutable values = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; 2| auto interpolant = spline!double(points.rcslice, values.sliced); | 1| auto argmin = 5.898317667706634; 1| interpolant.argmin.shouldApprox == argmin; 1| interpolant(argmin).shouldApprox == -0.8684781710737299; 1| interpolant.opCall!2(argmin)[1].shouldApprox == 0; | 1| auto argmax = 19.8816211020945; 1| interpolant.argmax.shouldApprox == argmax; 1| interpolant.withDerivative(argmax)[1].shouldApprox == 0; 1| interpolant(argmax).shouldApprox == 19.54377309088673; | 2| auto zeroInterpolant = interpolant - interpolant; 1| zeroInterpolant.opCall!3(13.3).should == [0.0, 0.0, 0.0, 0.0]; | | static immutable pointsR = [1.0, 3, 4,]; | static immutable valuesR = [13.0, 12, 10]; 2| auto interpolantR = spline!double(pointsR.rcslice, valuesR.sliced); | 2| auto sumInterpolant = interpolant + interpolantR; | 1| sumInterpolant(2.3).shouldApprox == interpolant(2.3) + interpolantR(2.3); 1| sumInterpolant(3.3).shouldApprox == interpolant(3.3) + interpolantR(3.3); |} | |/++ |Cubic Spline types. | |The first derivatives are guaranteed to be continuous for all cubic splines. |+/ |extern(C++, "mir", "interpolate") |enum SplineType |{ | /++ | Spline with contiguous second derivative. | +/ | c2, | /++ | $(HTTPS en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline, Cardinal) and Catmull–Rom splines. | +/ | cardinal, | /++ | The interpolant preserves monotonicity in the interpolation data and does not overshoot if the data is not smooth. | It is also known as $(HTTPS docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.interpolate.PchipInterpolator.html, PCHIP) | in numpy and Matlab. | +/ | monotone, | /++ | Weighted sum of two nearbor quadratic functions. | It is used in $(HTTPS s3-eu-west-1.amazonaws.com/og-public-downloads/smile-interpolation-extrapolation.pdf, financial analysis). | +/ | doubleQuadratic, | /++ | $(HTTPS en.wikipedia.org/wiki/Akima_spline, Akima spline). | +/ | akima, | /++ | $(HTTPS https://www.mathworks.com/help/matlab/ref/makima.html, Modified Akima spline). | +/ | makima, |} | |/++ |Spline convexity type |+/ |enum SplineConvexity |{ | /// Neither convex nor concave spline | none = 0, | /// Concave spline | concave = -1, | /// Convex spline | convex = 1, |} | |/++ |Constructs multivariate cubic spline in symmetrical form with nodes on rectilinear grid. |Result has continues second derivatives throughout the curve / nd-surface. |+/ |template spline(T, size_t N = 1, X = T) | if (isFloatingPoint!T && is(T == Unqual!T) && N <= 6) |{ | /++ | Params: | grid = immutable `x` values for interpolant | values = `f(x)` values for interpolant | typeOfBoundaries = $(LREF SplineBoundaryType) for both tails (optional). | valueOfBoundaryConditions = value of the boundary type (optional). | Constraints: | `grid` and `values` must have the same length >= 3 | Returns: $(LREF Spline) | +/ | Spline!(T, N, X) spline(yIterator, SliceKind ykind)( | Repeat!(N, Slice!(RCI!(immutable X))) grid, | Slice!(yIterator, N, ykind) values, | SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, | in T valueOfBoundaryConditions = 0, | ) | { | import core.lifetime: forward; 19| return spline(forward!grid, forward!values, SplineType.c2, 0, typeOfBoundaries, valueOfBoundaryConditions); | } | | Spline!(T, N, X) spline(yIterator, SliceKind ykind)( | Repeat!(N, Slice!(RCI!(immutable X))) grid, | Slice!(yIterator, N, ykind) values, | SplineType kind, | in T param = 0, | SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, | in T valueOfBoundaryConditions = 0, | ) | { | import core.lifetime: forward; 26| return spline(forward!grid, forward!values, SplineBoundaryCondition!T(typeOfBoundaries, valueOfBoundaryConditions), kind, param); | } | | /++ | Params: | grid = immutable `x` values for interpolant | values = `f(x)` values for interpolant | boundaries = $(LREF SplineBoundaryCondition) for both tails. | kind = $(LREF SplineType) type of cubic spline. | param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. | Constraints: | `grid` and `values` must have the same length >= 3 | Returns: $(LREF Spline) | +/ | Spline!(T, N, X) spline(yIterator, SliceKind ykind)( | Repeat!(N, Slice!(RCI!(immutable X))) grid, | Slice!(yIterator, N, ykind) values, | SplineBoundaryCondition!T boundaries, | SplineType kind = SplineType.c2, | in T param = 0, | ) | { | import core.lifetime: forward; 26| return spline(forward!grid, forward!values, boundaries, boundaries, kind, param); | } | | /++ | Params: | grid = immutable `x` values for interpolant | values = `f(x)` values for interpolant | lBoundary = $(LREF SplineBoundaryCondition) for left tail. | rBoundary = $(LREF SplineBoundaryCondition) for right tail. | kind = $(LREF SplineType) type of cubic spline. | param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. | Constraints: | `grid` and `values` must have the same length >= 3 | Returns: $(LREF Spline) | +/ | Spline!(T, N, X) spline(yIterator, SliceKind ykind)( | Repeat!(N, Slice!(RCI!(immutable X))) grid, | Slice!(yIterator, N, ykind) values, | SplineBoundaryCondition!T lBoundary, | SplineBoundaryCondition!T rBoundary, | SplineType kind = SplineType.c2, | in T param = 0, | ) | { 28| auto ret = typeof(return)(forward!grid); 28| ret._values = values; 28| ret._computeDerivatives(kind, param, lBoundary, rBoundary); 28| return ret; | } | | /++ | Params: | grid = immutable `x` values for interpolant | values = `f(x)` values for interpolant | configuration = $(LREF SplineConfiguration) | Constraints: | `grid` and `values` must have the same length >= 3 | Returns: $(LREF Spline) | +/ | Spline!(T, N, X) spline(yIterator, SliceKind ykind)( | Repeat!(N, Slice!(RCI!(immutable X))) grid, | Slice!(yIterator, N, ykind) values, | SplineConfiguration!T configuration, | ) | { 1| auto ret = typeof(return)(forward!grid); 1| ret._values = values; | with(configuration) 1| ret._computeDerivatives(kind, param, leftBoundary, rightBoundary); 1| return ret; | } |} | |/++ |Cubic Spline Boundary Condition Type. | |See_also: $(LREF SplineBoundaryCondition) $(LREF SplineType) |+/ |extern(C++, "mir", "interpolate") |enum SplineBoundaryType |{ | /++ | Not-a-knot (or cubic) boundary condition. | It is an aggresive boundary condition that is used only for C2 splines and is default for all API calls. | For other then C2 splines, `notAKnot` is changed internally to | a default boundary type for used $(LREF SplineType). | +/ | notAKnot, | /++ | Set the first derivative. | +/ | firstDerivative, | /++ | Set the second derivative. | +/ | secondDerivative, | /++ | Default for Cardinal and Double-Quadratic splines. | +/ | parabolic, | /++ | Default for monotone (aka PHCIP ) splines. | +/ | monotone, | /++ | Default for Akima splines. | +/ | akima, | /++ | Default for Modified Akima splines. | +/ | makima, | /++ | Not implemented. | +/ | periodic = -1, |} | |/++ |Cubic Spline Boundary Condition | |See_also: $(LREF SplineBoundaryType) |+/ |extern(C++, "mir", "interpolate") |struct SplineBoundaryCondition(T) | if (__traits(isFloating, T)) |{ | import mir.serde: serdeOptional, serdeIgnoreDefault; | | /// type (default is $(LREF SplineBoundaryType.notAKnot)) | SplineBoundaryType type; | |@serdeOptional @serdeIgnoreDefault: | | /// value (default is 0) | T value = 0; |} | |/// Spline configuration |struct SplineConfiguration(T) | if (__traits(isFloating, T)) |{ | import mir.serde: serdeOptional, serdeIgnoreDefault, serdeIgnoreOutIfAggregate, serdeIgnore; | | /// | @serdeOptional @serdeIgnoreDefault | SplineType kind; | /// | @serdeOptional @serdeIgnoreOutIfAggregate!"a.symmetric" | SplineBoundaryCondition!T leftBoundary; | /// | @serdeOptional @serdeIgnoreOutIfAggregate!"a.symmetric" | SplineBoundaryCondition!T rightBoundary; | | /++ | Returns: | true of `leftBoundary` equals `rightBoundary`. | +/ | @serdeIgnore | bool symmetric() const @property | { 0000000| return leftBoundary == rightBoundary; | } | | /// | @serdeOptional | void boundary(SplineBoundaryCondition!T boundary) @property | { 0000000| leftBoundary = rightBoundary = boundary; | } | | /// | @serdeIgnoreOutIfAggregate!"!a.symmetric" | SplineBoundaryCondition!T boundary() const @property | { 0000000| assert(!symmetric, splineConfigurationMsg); 0000000| return leftBoundary; | } | | /++ | Tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. | +/ | @serdeOptional @serdeIgnoreDefault | T param = 0; |} | |/// Spline configuration with two boundaries |struct SplineSymmetricConfiguration(T) | if (__traits(isFloating, T)) |{ | import mir.serde: serdeOptional, serdeIgnoreDefault; | |@serdeOptional @serdeIgnoreDefault: | | /// | SplineType type; | /// | SplineBoundaryCondition!T boundary; | /++ | Tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. | +/ | T param = 0; |} | |/++ |Multivariate cubic spline with nodes on rectilinear grid. |+/ 97|struct Spline(F, size_t N = 1, X = F) | if (N && N <= 6) |{ | import mir.rc.array; | | /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) | Slice!(RCI!(F[2 ^^ N]), N) _data; | /// Grid iterators. $(RED For internal use.) | Repeat!(N, RCI!(immutable X)) _grid; | /// | SplineConvexity[N] convexity; | | enum uint dimensionCount = N; | |@fmamath extern(D): | | bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc | { 4| if (rhs._data != this._data) 0000000| return false; | static foreach (d; 0 .. N) 4| if (gridScopeView!d != rhs.gridScopeView!d) 0000000| return false; 4| return true; | } | | /++ | +/ 35| this(Repeat!(N, Slice!(RCI!(immutable X))) grid) @safe @nogc | { 35| size_t length = 1; 35| size_t[N] shape; 38| foreach(i, ref x; grid) | { 38| if (x.length < 2) | { 0000000| version(D_Exceptions) throw exc_min; | else assert(0, msg_min); | } 38| length *= shape[i] = x.length; 38| this._grid[i] = x._iterator.move; | } | import mir.ndslice.allocation: rcslice; 35| this._data = shape.rcslice!(F[2 ^^ N]); | } | | package static auto pickDataSubslice(D)(auto scope ref D data, size_t index) @trusted | { 365| auto strides = data.strides; | foreach (i; Iota!(strides.length)) 368| strides[i] *= DeepElementType!D.length; 365| return Slice!(F*, strides.length, Universal)(data.shape, strides, data._iterator.ptr + index); | } | | static if (N == 1) | /++ | Note: defined only for 1D splines | +/ | Spline opBinary(string op)(const Spline rhs) @trusted const | if (op == "+" || op == "-") | { | import mir.ndslice.allocation: rcslice; | import core.lifetime: move; | import mir.algorithm.setops: unionLength, multiwayUnion; | 2| auto lgrid = this.gridScopeView; 2| auto rgrid = rhs.gridScopeView; 2| scope const(F)[][2] grids = [lgrid, rgrid]; 2| auto length = grids[].unionLength; 2| grids = [lgrid, rgrid]; | 2| size_t j; 4| auto grid = RCArray!X(length); 4| auto data = length.rcslice!(F[2]); 2| auto un = grids[].multiwayUnion; | 23| while (!un.empty) | { 21| auto x = un.front; 21| un.popFront; 21| auto ly = this.opCall!1(x); 21| auto ry = rhs.opCall!1(x); 21| data[j] = mixin(`[ly[0] ` ~ op ~ ` ry[0], ly[1] ` ~ op ~ ` ry[1]]`); 21| grid[j++] = x; | } | 2| size_t convexCount; 2| size_t concaveCount; 44| foreach_reverse (i; 0 .. length - 1) | { 19| auto xdiff = grid[i + 1] - grid[i]; 19| auto ydiff = data[i + 1][0] - data[i][0]; | 19| auto convex1 = xdiff * (2 * data[i][1] + data[i + 1][1]) <= 3 * ydiff; 19| auto concave1 = xdiff * (2 * data[i][1] + data[i + 1][1]) >= 3 * ydiff; 19| auto convex2 = xdiff * (data[i][1] + 2 * data[i + 1][1]) >= 3 * ydiff; 19| auto concave2 = xdiff * (data[i][1] + 2 * data[i + 1][1]) <= 3 * ydiff; 19| convexCount += convex1 & convex2; 19| convexCount += concave1 & concave2; | } | 2| Spline ret; 2| ret._data = data.move; 2| ret._grid[0] = RCI!(immutable X)(cast(RCArray!(immutable X))grid) ; | 2| ret.convexity = | // convex kind has priority for the linear spline | convexCount == length - 1 ? SplineConvexity.convex : | concaveCount == length - 1 ? SplineConvexity.concave : | SplineConvexity.none; | 2| return ret; | } | | static if (N == 1) | template argminImpl(string pred) | if (pred == "a < b" || pred == "a > b") | { | F argminImpl()() @trusted const @property | { | import mir.functional: naryFun; | | static if (pred == "a < b") 2| auto min = F.max; | else 2| auto min = -F.max; | 4| F argmin; | 4| auto grid = gridScopeView; 172| foreach (i, ref y; _data.lightScope.field) | { 40| if (naryFun!pred(y[0], min)) | { 8| min = y[0]; 8| argmin = grid[i]; | } | } | 120| foreach (i; 0 .. grid.length - 1) | { 36| auto x = grid[i + 0]; | 36| auto y = SplineKernel!F( | grid[i + 0], | grid[i + 1], | x, // any point between | ).opCall!3( | _data[i + 0][0], | _data[i + 1][0], | _data[i + 0][1], | _data[i + 1][1], | ); | | // 3 ax^2 + 2 bx + c = y[1] | // 6 ax + 2 b= y[2] | // 6 a = y[3] | 36| auto a3 = y[3] * 0.5; 36| auto b2 = y[2] - y[3] * x; 36| auto c = y[1] - (b2 + a3 * x) * x; 36| auto d = b2 * b2 - 4 * a3 * c; 36| if (d < 0) 2| continue; | | import mir.math.common: sqrt; 34| F[2] x12 = [ | (-b2 - sqrt(d)) / (2 * a3), | (-b2 + sqrt(d)) / (2 * a3), | ]; | 306| foreach (xi; x12) 110| if (grid[i + 0] < xi && xi < grid[i + 1]) | { 14| auto yi = SplineKernel!F( | grid[i + 0], | grid[i + 1], | xi, | )( | _data[i + 0][0], | _data[i + 1][0], | _data[i + 0][1], | _data[i + 1][1], | ); | 14| if (naryFun!pred(yi, min)) | { 4| min = yi; 4| argmin = xi; | } | } | } | 4| return argmin; | } | } | | static if (N == 1) | /++ | Returns: spline argmin on the interpolation interval | Note: defined only for 1D splines | +/ | alias argmin = argminImpl!"a < b"; | | static if (N == 1) | /++ | Returns: spline argmax on the interpolation interval | Note: defined only for 1D splines | +/ | alias argmax = argminImpl!"a > b"; | | /++ | Assigns function values to the internal memory. | $(RED For internal use.) | +/ | void _values(SliceKind kind, Iterator)(Slice!(Iterator, N, kind) values) scope @property @trusted | { 29| assert(values.shape == _data.shape, "'values' should have the same shape as the .gridShape"); 29| pickDataSubslice(_data.lightScope, 0)[] = values; | } | | /++ | Computes derivatives and stores them in `_data`. | `_data` is assumed to be preinitialized with function values filled in `F[2 ^^ N][0]`. | Params: | lBoundary = left boundary condition | rBoundary = right boundary condition | temp = temporal buffer length points count (optional) | | $(RED For internal use.) | +/ | void _computeDerivatives(SplineType kind, F param, SplineBoundaryCondition!F lBoundary, SplineBoundaryCondition!F rBoundary) scope @trusted nothrow @nogc | { | import mir.algorithm.iteration: maxLength; 29| auto ml = this._data.maxLength; 58| auto temp = RCArray!F(ml); 29| auto tempSlice = temp[].sliced; 29| _computeDerivativesTemp(kind, param, lBoundary, rBoundary, tempSlice); | } | | /// ditto | pragma(inline, false) | void _computeDerivativesTemp(SplineType kind, F param, SplineBoundaryCondition!F lBoundary, SplineBoundaryCondition!F rBoundary, Slice!(F*) temp) scope @system nothrow @nogc | { | import mir.algorithm.iteration: maxLength, each; | import mir.ndslice.topology: map, byDim, evertPack; | 52| assert(temp.length >= _data.maxLength); | | static if (N == 1) | { 50| convexity[0] = splineSlopes!(F, F)(_grid[0]._iterator.sliced(_data._lengths[0]), pickDataSubslice(_data.lightScope, 0), pickDataSubslice(_data.lightScope, 1), temp[0 .. _data._lengths[0]], kind, param, lBoundary, rBoundary); | } | else | foreach_reverse(i; Iota!N) | { | // if (i == _grid.length - 1) 5| _data | .lightScope | .byDim!i | .evertPack | .each!((d){ | enum L = 2 ^^ (N - 1 - i); | foreach(l; Iota!L) | { 118| auto y = pickDataSubslice(d, l); 118| auto s = pickDataSubslice(d, L + l); | // debug printf("ptr = %ld, stride = %ld, stride = %ld, d = %ld i = %ld l = %ld\n", d.iterator, d._stride!0, y._stride!0, d.length, i, l); 118| auto c = splineSlopes!(F, F)(_grid[i]._iterator.sliced(_data._lengths[i]), y, s, temp[0 .. _data._lengths[i]], kind, param, lBoundary, rBoundary); | static if (l) | { 62| if (convexity[i] != c) 27| convexity[i] = SplineConvexity.none; | } | else | { 56| convexity[i] = c; | } | // debug{ | // (cast(void delegate() @nogc)(){ | // writeln("y = ", y); | // writeln("s = ", s); | // })(); | // } | } | }); | } | } | |@trusted: | | /// 2| Spline lightConst() const @property { return *cast(Spline*)&this; } | /// 0000000| Spline lightImmutable() immutable @property { return *cast(Spline*)&this; } | | /// | Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property | if (dimension < N) | { | return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension < N) | { 602| return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | { 584| assert(_data._lengths[dimension] > 1); 584| return _data._lengths[dimension] - 1; | } | | /// | size_t[N] gridShape() scope const @property | { 0000000| return _data.shape; | } | | /// | alias withDerivative = opCall!1; | /// | alias withTwoDerivatives = opCall!2; | | /// | enum uint derivativeOrder = 3; | | /// | template opCall(uint derivative : 2) | { | auto opCall(X...)(in X xs) scope const | if (X.length == N) | // @FUTURE@ | // X.length == N || derivative == 0 && X.length && X.length <= N | { 11| auto d4 = this.opCall!3(xs); 11| SplineReturnType!(F, N, 3) d3; | void fun(size_t d, A, B)(ref A a, ref B b) | { | static if (d) | foreach(i; Iota!3) 33| fun!(d - 1)(a[i], b[i]); | else 33| b = a; | } 11| fun!N(d4, d3); 11| return d3; | } | } | | /// | template opCall(uint derivative = 0) | if (derivative == 0 || derivative == 1 || derivative == 3) | { | static if (N > 1 && derivative) pragma(msg, "Warning: multivariate cubic spline with derivatives was not tested!!!"); | | /++ | `(x)` operator. | Complexity: | `O(log(points.length))` | +/ | auto opCall(X...)(in X xs) scope const @trusted | if (X.length == N) | // @FUTURE@ | // X.length == N || derivative == 0 && X.length && X.length <= N | { | import mir.ndslice.topology: iota; | alias Kernel = AliasCall!(SplineKernel!F, "opCall", derivative); | enum rp2d = derivative == 3 ? 2 : derivative; | 438| size_t[N] indices; 438| Kernel[N] kernels; | | foreach(i; Iota!N) | { | static if (isInterval!(typeof(xs[i]))) | { 2| indices[i] = xs[i][1]; 2| auto x = xs[i][0]; | } | else | { | alias x = xs[i]; 584| indices[i] = this.findInterval!i(x); | } 586| kernels[i] = SplineKernel!F(_grid[i][indices[i]], _grid[i][indices[i] + 1], x); | } | | align(64) F[2 ^^ N * 2 ^^ N][2] local; | | void load(sizediff_t i)(const(F[2 ^^ N])* from, F[2 ^^ N]* to) | { | version(LDC) pragma(inline, true); | static if (i == -1) | { | // copyvec(*from, *to); | // not aligned: 1298| *to = *from; | } | else | { 860| from += strides[i] * indices[i]; 860| load!(i - 1)(from, to); 860| from += strides[i]; | enum s = 2 ^^ (N - 1 - i); 860| to += s; 860| load!(i - 1)(from, to); | } | } | 438| immutable strides = _data._lengths.iota.strides; 438| load!(N - 1)(_data.ptr, cast(F[2 ^^ N]*) local[0].ptr); | | // debug{ | | // printf("0local[0] = "); | // foreach(ref e; local[0][]) | // printf("%f ", e); | // printf("\n"); | // } | | foreach(i; Iota!N) | { | enum P = 2 ^^ (N - 1 - i) * 2 ^^ (i * rp2d); | enum L = (2 ^^ N) ^^ 2 / (2 ^^ (i * (2 - rp2d))) / 4; 586| shuffle2!P(local[0][0 * L .. 1 * L], local[0][1 * L .. 2 * L], local[1][0 * L .. 1 * L], local[1][1 * L .. 2 * L]); 586| shuffle2!P(local[0][2 * L .. 3 * L], local[0][3 * L .. 4 * L], local[1][2 * L .. 3 * L], local[1][3 * L .. 4 * L]); | // debug | // { | // printf("0local[1] = "); | // foreach(ref e; local[1][0 .. L* 4]) | // printf("%f ", e); | // printf("\n"); | // } 586| local[0][] = F.init; 586| vectorize( | kernels[i], | local[1][0 * L .. 1 * L], local[1][2 * L .. 3 * L], | local[1][1 * L .. 2 * L], local[1][3 * L .. 4 * L], | *cast(F[L][2 ^^ rp2d]*) local[0].ptr, | ); | | // debug{ | | // printf("1local[0] = "); | // foreach(ref e; local[0][0 .. L* 2 ^^ rp2d]) | // printf("%f ", e); | // printf("\n"); | // } | // printf("local[0][0]", local[0][0]); | static if (i + 1 == N) | { 438| return *cast(SplineReturnType!(F, N, 2 ^^ rp2d)*) local[0].ptr; | } | else | { | static if (rp2d == 1) | { 3| shuffle3!1(local[0][0 .. L], local[0][L .. 2 * L], local[1][0 .. L], local[1][L .. 2 * L]); 3| copyvec(local[1][0 .. 1 * L], local[0][0 .. 1 * L]); 3| copyvec(local[1][L .. 2 * L], local[0][L .. 2 * L]); | } | else | static if (rp2d == 2) | { | shuffle3!1(local[0][0 * L .. 1 * L], local[0][1 * L .. 2 * L], local[1][0 * L .. 1 * L], local[1][1 * L .. 2 * L]); | shuffle3!1(local[0][2 * L .. 3 * L], local[0][3 * L .. 4 * L], local[1][2 * L .. 3 * L], local[1][3 * L .. 4 * L]); | shuffle3!2(local[1][0 * L .. 1 * L], local[1][2 * L .. 3 * L], local[0][0 * L .. 1 * L], local[0][2 * L .. 3 * L]); | shuffle3!2(local[1][1 * L .. 2 * L], local[1][3 * L .. 4 * L], local[0][1 * L .. 2 * L], local[0][3 * L .. 4 * L]); | } | | // debug{ | | // printf("2local[0] = "); | // foreach(ref e; local[0][0 .. L * 2 ^^ rp2d]) | // printf("%f ", e); | // printf("\n"); | // } | | } | } | } | } |} | |/++ |Piecewise cubic hermite interpolating polynomial. |Params: | points = `x` values for interpolant | values = `f(x)` values for interpolant | slopes = uninitialized ndslice to write slopes into | temp = uninitialized temporary ndslice | kind = $(LREF SplineType) type of cubic spline. | param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. | lBoundary = left boundary condition | rBoundary = right boundary condition |Constraints: | `points`, `values`, and `slopes`, must have the same length > 3; | `temp` must have length greater or equal to points less minus one. |Returs: | Spline convexity type |+/ |SplineConvexity splineSlopes(F, T, P, IV, IS, SliceKind gkind, SliceKind vkind, SliceKind skind)( | Slice!(P*, 1, gkind) points, | Slice!(IV, 1, vkind) values, | Slice!(IS, 1, skind) slopes, | Slice!(T*) temp, | SplineType kind, | F param, | SplineBoundaryCondition!F lBoundary, | SplineBoundaryCondition!F rBoundary, | ) @trusted |{ | import mir.ndslice.topology: diff, zip, slide; | 168| assert (points.length >= 2); 168| assert (points.length == values.length); 168| assert (points.length == slopes.length); 168| assert (temp.length == points.length); | 168| auto n = points.length; | 336| typeof(slopes[0]) first, last; | 168| auto xd = points.diff; 168| auto yd = values.diff; 168| auto dd = yd / xd; 168| auto dd2 = points.zip(values).slide!(3, "(c[1] - a[1]) / (c[0] - a[0])"); | 168| with(SplineType) final switch(kind) | { 161| case c2: 161| break; 0000000| case cardinal: 0000000| if (lBoundary.type == SplineBoundaryType.notAKnot) 0000000| lBoundary.type = SplineBoundaryType.parabolic; 0000000| if (rBoundary.type == SplineBoundaryType.notAKnot) 0000000| rBoundary.type = SplineBoundaryType.parabolic; 0000000| break; 4| case monotone: 4| if (lBoundary.type == SplineBoundaryType.notAKnot) 4| lBoundary.type = SplineBoundaryType.monotone; 4| if (rBoundary.type == SplineBoundaryType.notAKnot) 4| rBoundary.type = SplineBoundaryType.monotone; 4| break; 1| case doubleQuadratic: 1| if (lBoundary.type == SplineBoundaryType.notAKnot) 1| lBoundary.type = SplineBoundaryType.parabolic; 1| if (rBoundary.type == SplineBoundaryType.notAKnot) 1| rBoundary.type = SplineBoundaryType.parabolic; 1| break; 1| case akima: 1| if (lBoundary.type == SplineBoundaryType.notAKnot) 1| lBoundary.type = SplineBoundaryType.akima; 1| if (rBoundary.type == SplineBoundaryType.notAKnot) 1| rBoundary.type = SplineBoundaryType.akima; 1| break; 1| case makima: 1| if (lBoundary.type == SplineBoundaryType.notAKnot) 1| lBoundary.type = SplineBoundaryType.makima; 1| if (rBoundary.type == SplineBoundaryType.notAKnot) 1| rBoundary.type = SplineBoundaryType.makima; 1| break; | } | 168| if (n <= 3) | { 25| if (lBoundary.type == SplineBoundaryType.notAKnot) 25| lBoundary.type = SplineBoundaryType.parabolic; 25| if (rBoundary.type == SplineBoundaryType.notAKnot) 25| rBoundary.type = SplineBoundaryType.parabolic; | 25| if (n == 2) | { 1| if (lBoundary.type == SplineBoundaryType.monotone 1| || lBoundary.type == SplineBoundaryType.makima 1| || lBoundary.type == SplineBoundaryType.akima) 0000000| lBoundary.type = SplineBoundaryType.parabolic; 1| if (rBoundary.type == SplineBoundaryType.monotone 1| || rBoundary.type == SplineBoundaryType.makima 1| || rBoundary.type == SplineBoundaryType.akima) 0000000| rBoundary.type = SplineBoundaryType.parabolic; | } | /// special case 25| if (rBoundary.type == SplineBoundaryType.parabolic 25| && lBoundary.type == SplineBoundaryType.parabolic) | { | import mir.interpolate.utility; 25| if (n == 3) | { 24| auto derivatives = parabolaDerivatives(points[0], points[1], points[2], values[0], values[1], values[2]); 24| slopes[0] = derivatives[0]; 24| slopes[1] = derivatives[1]; 24| slopes[2] = derivatives[2]; | } | else | { 1| assert(slopes.length == 2); 1| slopes.back = slopes.front = yd.front / xd.front; | } 25| return slopes.back >= slopes.front ? SplineConvexity.convex : SplineConvexity.concave; | } | } | 143| with(SplineBoundaryType) final switch(lBoundary.type) | { | case periodic: | | assert(0); | 131| case notAKnot: | 131| auto dx0 = xd[0]; 131| auto dx1 = xd[1]; 131| auto dy0 = yd[0]; 131| auto dy1 = yd[1]; 131| auto dd0 = dy0 / dx0; 131| auto dd1 = dy1 / dx1; | 131| slopes.front = dx1; 131| first = dx0 + dx1; 131| temp.front = ((dx0 + 2 * first) * dx1 * dd0 + dx0 ^^ 2 * dd1) / first; 131| break; | 2| case firstDerivative: | 2| slopes.front = 1; 2| first = 0; 2| temp.front = lBoundary.value; 2| break; | 3| case secondDerivative: | 3| slopes.front = 2; 3| first = 1; 3| temp.front = 3 * dd.front - 0.5 * lBoundary.value * xd.front; 3| break; | 1| case parabolic: | 1| slopes.front = 1; 1| first = 1; 1| temp.front = 2 * dd.front; 1| break; | 4| case monotone: | 4| slopes.front = 1; 4| first = 0; 4| temp.front = pchipTail(xd[0], xd[1], dd[0], dd[1]); 4| break; | 1| case akima: | 1| slopes.front = 1; 1| first = 0; 1| temp.front = akimaTail(dd[0], dd[1]); 1| break; | 1| case makima: | 1| slopes.front = 1; 1| first = 0; 1| temp.front = makimaTail(dd[0], dd[1]); 1| break; | | } | 143| with(SplineBoundaryType) final switch(rBoundary.type) | { | case periodic: | assert(0); | 131| case notAKnot: | 131| auto dx0 = xd[$ - 1]; 131| auto dx1 = xd[$ - 2]; 131| auto dy0 = yd[$ - 1]; 131| auto dy1 = yd[$ - 2]; 131| auto dd0 = dy0 / dx0; 131| auto dd1 = dy1 / dx1; 131| slopes.back = dx1; 131| last = dx0 + dx1; 131| temp.back = ((dx0 + 2 * last) * dx1 * dd0 + dx0 ^^ 2 * dd1) / last; 131| break; | 2| case firstDerivative: | 2| slopes.back = 1; 2| last = 0; 2| temp.back = rBoundary.value; 2| break; | 3| case secondDerivative: | 3| slopes.back = 2; 3| last = 1; 3| temp.back = 3 * dd.back + 0.5 * rBoundary.value * xd.back; 3| break; | 1| case parabolic: | 1| slopes.back = 1; 1| last = 1; 1| temp.back = 2 * dd.back; 1| break; | 4| case monotone: | 4| slopes.back = 1; 4| last = 0; 4| temp.back = pchipTail(xd[$ - 1], xd[$ - 2], dd[$ - 1], dd[$ - 2]); 4| break; | 1| case akima: | 1| slopes.back = 1; 1| last = 0; 1| temp.back = akimaTail(dd[$ - 1], dd[$ - 2]); 1| break; | 1| case makima: | 1| slopes.back = 1; 1| last = 0; 1| temp.back = makimaTail(dd[$ - 1], dd[$ - 2]); 1| break; | | } | 286| if (n > 2) with(SplineType) final switch(kind) | { 136| case c2: | 1482| foreach (i; 1 .. n - 1) | { 358| auto dx0 = xd[i - 1]; 358| auto dx1 = xd[i - 0]; 358| auto dy0 = yd[i - 1]; 358| auto dy1 = yd[i - 0]; 358| slopes[i] = 2 * (dx0 + dx1); 358| temp[i] = 3 * (dy0 / dx0 * dx1 + dy1 / dx1 * dx0); | } 136| break; | 0000000| case cardinal: | 0000000| foreach (i; 1 .. n - 1) | { 0000000| slopes[i] = 1; 0000000| temp[i] = (1 - param) * dd2[i - 1]; | } 0000000| break; | 4| case monotone: | { 4| auto step0 = cast()xd[0]; 4| auto step1 = cast()xd[1]; 4| auto diff0 = cast()yd[0]; 4| auto diff1 = cast()yd[1]; 4| diff0 /= step0; 4| diff1 /= step1; | 4| for(size_t i = 1;;) | { 32| slopes[i] = 1; 96| if (diff0 && diff1 && copysign(1f, diff0) == copysign(1f, diff1)) | { 8| auto w0 = step1 * 2 + step0; 8| auto w1 = step0 * 2 + step1; 8| temp[i] = (w0 + w1) / (w0 / diff0 + w1 / diff1); | } | else | { 24| temp[i] = 0; | } 32| if (++i == n - 1) | { 4| break; | } 28| step0 = step1; 28| diff0 = diff1; 28| step1 = xd[i]; 28| diff1 = yd[i]; 28| diff1 /= step1; | } | } 4| break; | 1| case doubleQuadratic: | 27| foreach (i; 1 .. n - 1) | { 8| slopes[i] = 1; 8| temp[i] = dd[i - 1] + dd[i] - dd2[i - 1]; | } 1| break; | 1| case akima: | { 1| auto d3 = dd[1]; 1| auto d2 = dd[0]; 1| auto d1 = 2 * d2 - d3; 1| auto d0 = d1; 27| foreach (i; 1 .. n - 1) | { 8| d0 = d1; 8| d1 = d2; 8| d2 = d3; 8| d3 = i == n - 2 ? 2 * d2 - d1 : dd[i + 1]; 8| slopes[i] = 1; 8| temp[i] = akimaSlope(d0, d1, d2, d3); | } 1| break; | } | 1| case makima: | { 1| auto d3 = dd[1]; 1| auto d2 = dd[0]; 1| auto d1 = 2 * d2 - d3; 1| auto d0 = d1; 27| foreach (i; 1 .. n - 1) | { 8| d0 = d1; 8| d1 = d2; 8| d2 = d3; 8| d3 = i == n - 2 ? 2 * d2 - d1 : dd[i + 1]; 8| slopes[i] = 1; 8| temp[i] = makimaSlope(d0, d1, d2, d3); | } 1| break; | } | } | 2100| foreach (i; 0 .. n - 1) | { 557| auto c = i == 0 ? first : kind == SplineType.c2 ? xd[i - 1] : 0; 557| auto a = i == n - 2 ? last : kind == SplineType.c2 ? xd[i + 1] : 0; 557| auto w = slopes[i] == 1 ? a : a / slopes[i]; 557| slopes[i + 1] -= w * c; 557| temp[i + 1] -= w * temp[i]; | } | 143| slopes.back = temp.back / slopes.back; | 143| size_t convexCount; 143| size_t concaveCount; 1543| foreach_reverse (i; 0 .. n - 1) | { 557| auto c = i == 0 ? first : kind == SplineType.c2 ? xd[i - 1] : 0; 557| auto v = temp[i] - c * slopes[i + 1]; 557| slopes[i] = slopes[i] == 1 ? v : v / slopes[i]; | 557| auto xdiff = points[i + 1] - points[i]; 557| auto ydiff = values[i + 1] - values[i]; | 557| auto convex1 = xdiff * (2 * slopes[i] + slopes[i + 1]) <= 3 * ydiff; 557| auto concave1 = xdiff * (2 * slopes[i] + slopes[i + 1]) >= 3 * ydiff; 557| auto convex2 = xdiff * (slopes[i] + 2 * slopes[i + 1]) >= 3 * ydiff; 557| auto concave2 = xdiff * (slopes[i] + 2 * slopes[i + 1]) <= 3 * ydiff; 557| convexCount += convex1 & convex2; 557| convexCount += concave1 & concave2; | } | 143| return | // convex kind has priority for the linear spline | convexCount == n - 1 ? SplineConvexity.convex : | concaveCount == n - 1 ? SplineConvexity.concave : | SplineConvexity.none; |} | |private F akimaTail(F)(in F d2, in F d3) |{ 2| auto d1 = 2 * d2 - d3; 2| auto d0 = 2 * d1 - d2; 2| return akimaSlope(d0, d1, d2, d3); |} | |private F akimaSlope(F)(in F d0, in F d1, in F d2, in F d3) |{ 10| if (d1 == d2) 0000000| return d1; 10| if (d0 == d1 && d2 == d3) 0000000| return (d1 + d2) * 0.5f; 10| if (d0 == d1) 0000000| return d1; 10| if (d2 == d3) 0000000| return d2; 10| auto w0 = fabs(d1 - d0); 10| auto w1 = fabs(d3 - d2); 10| auto ws = w0 + w1; 10| w0 /= ws; 10| w1 /= ws; 10| return w0 * d2 + w1 * d1; |} | |private F makimaTail(F)(in F d2, in F d3) |{ 2| auto d1 = 2 * d2 - d3; 2| auto d0 = 2 * d1 - d2; 2| return makimaSlope(d0, d1, d2, d3); |} | |private F makimaSlope(F)(in F d0, in F d1, in F d2, in F d3) |{ 10| if (d1 == d2) 0000000| return d1; 10| auto w0 = fabs(d1 - d0) + fabs(d1 + d0) * 0.5f; 10| auto w1 = fabs(d3 - d2) + fabs(d3 + d2) * 0.5f; 10| auto ws = w0 + w1; 10| w0 /= ws; 10| w1 /= ws; 10| return w0 * d2 + w1 * d1; |} | |/// |struct SplineKernel(X) |{ | X step = 0; | X w0 = 0; | X w1 = 0; | X wq = 0; | |@fmamath: | | /// 636| this(X x0, X x1, X x) | { 636| step = x1 - x0; 636| auto c0 = x - x0; 636| auto c1 = x1 - x; 636| w0 = c0 / step; 636| w1 = c1 / step; 636| wq = w0 * w1; | } | | /// | template opCall(uint derivative = 0) | if (derivative <= 3) | { | /// | auto opCall(Y)(const Y y0, const Y y1, const Y s0, const Y s1) const | { 1844| auto diff = y1 - y0; 1844| auto z0 = s0 * step - diff; 1844| auto z1 = s1 * step - diff; 1844| auto a0 = z0 * w1; 1844| auto a1 = z1 * w0; 1844| auto pr = a0 - a1; 1844| auto b0 = y0 * w1; 1844| auto b1 = y1 * w0; 1844| auto pl = b0 + b1; 1844| auto y = pl + wq * pr; | static if (derivative) | { 155| Y[derivative + 1] ret = 0; 155| ret[0] = y; 155| auto wd = w1 - w0; 155| auto zd = z1 + z0; 155| ret[1] = (diff + (wd * pr - wq * zd)) / step; | static if (derivative > 1) | { 60| auto astep = zd / (step * step); 60| ret[2] = -3 * wd * astep + (s1 - s0) / step; | static if (derivative > 2) 60| ret[3] = 6 * astep / step; | } 155| return ret; | } | else | { 1689| return y; | } | } | | static if (derivative) | auto opCall(Y, size_t N)(scope ref const Y[N] y0, scope ref const Y[N] y1, scope ref const Y[N] s0, scope ref const Y[N] s1) | { | Y[N][derivative + 1] ret = void; | Y[derivative + 1][N] temp = void; | | static foreach(i; 0 .. N) | temp[i] = this.opCall!derivative(y0[i], y1[i], s0[i], s1[i]); | static foreach(i; 0 .. N) | static foreach(d; 0 .. derivative + 1) | ret[d][i] = temp[i][d]; | return ret; | } | } | | /// | alias withDerivative = opCall!1; | /// | alias withTwoDerivatives = opCall!2; |} | |package T pchipTail(T)(in T step0, in T step1, in T diff0, in T diff1) |{ | import mir.math.common: copysign, fabs; 8| if (!diff0) | { 0000000| return 0; | } 8| auto slope = ((step0 * 2 + step1) * diff0 - step0 * diff1) / (step0 + step1); 8| if (copysign(1f, slope) != copysign(1f, diff0)) | { 0000000| return 0; | } 16| if ((copysign(1f, diff0) != copysign(1f, diff1)) && (fabs(slope) > fabs(diff0 * 3))) | { 0000000| return diff0 * 3; | } 8| return slope; |} | |/++ |Spline interpolator used for non-rectiliner trapezoid-like greeds. |Params: | grid = rc-array of interpolation grid | data = rc-array of interpolator-like structures | typeOfBoundaries = $(LREF SplineBoundaryType) for both tails (optional). | valueOfBoundaryConditions = value of the boundary type (optional). |Constraints: | `grid` and `values` must have the same length >= 3 |Returns: $(LREF Spline) |+/ |MetaSpline!(T, X) metaSpline(F, X, T)( | RCArray!(immutable X) grid, | RCArray!(const T) data, | SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, | const F valueOfBoundaryConditions = 0, | ) |{ | import core.lifetime: move; 1| return metaSpline!(F, X, T)(grid.move, data.move, SplineType.c2, 0, typeOfBoundaries, valueOfBoundaryConditions); |} | |/++ |Spline interpolator used for non-rectiliner trapezoid-like greeds. |Params: | grid = rc-array of interpolation grid | data = rc-array of interpolator-like structures | kind = $(LREF SplineType) type of cubic spline. | param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. | typeOfBoundaries = $(LREF SplineBoundaryType) for both tails (optional). | valueOfBoundaryConditions = value of the boundary type (optional). |Constraints: | `grid` and `values` must have the same length >= 3 |Returns: $(LREF Spline) |+/ |MetaSpline!(T, X) metaSpline(F, X, T)( | RCArray!(immutable X) grid, | RCArray!(const T) data, | SplineType kind, | const F param = 0, | SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, | const F valueOfBoundaryConditions = 0, | ) |{ | import core.lifetime: move; 1| return metaSpline!(F, X, T)(grid.move, data.move, SplineBoundaryCondition!F(typeOfBoundaries, valueOfBoundaryConditions), kind, param); |} | |/++ |Spline interpolator used for non-rectiliner trapezoid-like greeds. |Params: | grid = rc-array of interpolation grid | data = rc-array of interpolator-like structures | boundaries = $(LREF SplineBoundaryCondition) for both tails. | kind = $(LREF SplineType) type of cubic spline. | param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. |Constraints: | `grid` and `values` must have the same length >= 3 |Returns: $(LREF Spline) |+/ |MetaSpline!(T, X) metaSpline(F, X, T)( | RCArray!(immutable X) grid, | RCArray!(const T) data, | SplineBoundaryCondition!F boundaries, | SplineType kind = SplineType.c2, | const F param = 0, | ) |{ | import core.lifetime: move; 1| return metaSpline!(F, X, T)(grid.move, data.move, SplineConfiguration!F(kind, boundaries, boundaries, param)); |} | |/++ |Spline interpolator used for non-rectiliner trapezoid-like greeds. |Params: | grid = rc-array of interpolation grid | data = rc-array of interpolator-like structures | lBoundary = $(LREF SplineBoundaryCondition) for left tail. | rBoundary = $(LREF SplineBoundaryCondition) for right tail. | kind = $(LREF SplineType) type of cubic spline. | param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). | Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. |Constraints: | `grid` and `values` must have the same length >= 3 |Returns: $(LREF Spline) |+/ |MetaSpline!(T, X) metaSpline(F, X, T)( | RCArray!(immutable X) grid, | RCArray!(const T) data, | SplineBoundaryCondition!F lBoundary, | SplineBoundaryCondition!F rBoundary, | SplineType kind = SplineType.c2, | const F param = 0, | ) |{ | import core.lifetime: move; | return metaSpline(grid.move, data.move, SplineConfiguration!F(kind, lBoundary, rBoundary, param)); |} | |/++ |Spline interpolator used for non-rectiliner trapezoid-like greeds. |Params: | grid = rc-array of interpolation grid | data = rc-array of interpolator-like structures | configuration = $(LREF SplineConfiguration) |Constraints: | `grid` and `values` must have the same length >= 3 |Returns: $(LREF Spline) |+/ |MetaSpline!(T, X) metaSpline(F, X, T)( | RCArray!(immutable X) grid, | RCArray!(const T) data, | SplineConfiguration!F configuration, | ) |{ | import core.lifetime: move; 2| return MetaSpline!(T, X)(grid.move, data.move, configuration); |} | |/// ditto 6|struct MetaSpline(T, X) |{ | import mir.interpolate.utility: DeepType; | // alias ElementInterpolator = Linear!(F, N, X); | alias F = ValueType!(T, X); | /// | private Repeat!(3, Spline!F) splines; | /// | RCArray!(const T) data; | // | private RCArray!F _temp; | /// | SplineConfiguration!F configuration; | | /// 2| this( | RCArray!(immutable X) grid, | RCArray!(const T) data, | SplineConfiguration!F configuration, | ) | { | import core.lifetime: move; | 2| if (grid.length < 2) | { 0000000| version(D_Exceptions) throw exc_min; | else assert(0, msg_min); | } | 2| if (grid.length != data.length) | { 0000000| version(D_Exceptions) throw exc_eq; | else assert(0, msg_eq); | } | 2| this.data = data.move; 2| this._temp = grid.length; 2| this.splines[0] = grid.asSlice; 2| this.splines[1] = grid.asSlice; 2| this.splines[2] = grid.moveToSlice; 2| this.configuration = configuration; | } | | /// | bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc | { 1| return this.gridScopeView == rhs.gridScopeView 1| && this.data == rhs.data 6| && this.configuration == rhs.configuration; | } | | /// | MetaLinear lightConst()() const @property { return *cast(MetaLinear*)&this; } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted | if (dimension == 0) | { 2| return splines[0].gridScopeView; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | if (dimension == 0) | { | assert(data.length > 1); | return data.length - 1; | } | | /// | enum uint derivativeOrder = 3; | | static if (__traits(compiles, {enum N = T.dimensionCount;})) | /// | enum uint dimensionCount = T.dimensionCount + 1; | | /// | template opCall(uint derivative = 0) | // if (derivative <= derivativeOrder) | { | /++ | `(x)` operator. | Complexity: | `O(log(grid.length))` | +/ | auto opCall(X...)(const X xs) scope const @trusted | // if (X.length == dimensionCount) | { 22| F[2][][derivative + 1] mutable; | | static foreach (o; 0 .. derivative + 1) | { 23| mutable[o] = cast(F[2][]) splines[o]._data.lightScope.field; 23| assert(mutable[o].length == data.length); | } | | static if (!derivative) | { 395| foreach (i, ref d; data) 83| mutable[0][i][0] = d(xs[1 .. $]); | } | else | { 19| foreach (i, ref d; data) | { 4| auto node = d.opCall!derivative(xs[1 .. $]); | static foreach (o; 0 .. derivative + 1) 8| mutable[o][i][0] = node[o]; | } | } | | static foreach (o; 0 .. derivative + 1) | { 23| (*cast(Spline!F*)&splines[o])._computeDerivativesTemp( | configuration.kind, | configuration.param, | configuration.leftBoundary, | configuration.rightBoundary, | (cast(F[])_temp[]).sliced); | } | | static if (!derivative) | { 21| return splines[0](xs[0]); | } | else | { 1| typeof(splines[0].opCall!derivative(xs[0]))[derivative + 1] ret = void; | static foreach (o; 0 .. derivative + 1) | {{ 2| auto s = splines[o].opCall!derivative(xs[0]); | static foreach (r; 0 .. derivative + 1) 4| ret[r][o] = s[r]; | | }} 1| return ret; | } | } | } | | /// | alias withDerivative = opCall!1; | /// | alias withTwoDerivatives = opCall!2; |} | |/// 2D trapezoid-like (not rectilinear) linear interpolation |version(mir_test) |unittest |{ 1| auto x = [ | [0.0, 1, 2, 3, 5], | [-4.0, 3, 4], | [0.0, 10], | ]; 1| auto y = [ | [4.0, 0, 9, 23, 40], | [9.0, 0, 3], | [4.0, 40], | ]; | 1| auto g = [7.0, 10, 15]; | | import mir.rc.array: RCArray; | import mir.ndslice.allocation: rcslice; | 2| auto d = RCArray!(Spline!double)(3); | 12| foreach (i; 0 .. x.length) 3| d[i] = spline!double(x[i].rcslice!(immutable double), y[i].rcslice!(const double)); | 2| auto trapezoidInterpolator = metaSpline!double(g.rcarray!(immutable double), d.lightConst); | 1| auto val = trapezoidInterpolator(9.0, 1.8); |} | |version(mir_test) |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice; 24| alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); | | //// set test function //// | enum y_x0 = 2; | enum y_x1 = -7; | enum y_x0x1 = 3; | | // this function should be approximated very well 41| alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; | | ///// set interpolant //// | static immutable x0 = [-1.0, 2, 8, 15]; | static immutable x1 = [-4.0, 2, 5, 10, 13]; | 2| auto grid = cartesian(x0, x1) | .map!f | .rcslice | .lightConst; | 2| auto int0 = spline!double(x1.rcslice!(immutable double), grid[0]); 2| auto int1 = spline!double(x1.rcslice!(immutable double), grid[1]); 2| auto int2 = spline!double(x1.rcslice!(immutable double), grid[2]); 2| auto int3 = spline!double(x1.rcslice!(immutable double), grid[3]); | 2| auto interpolant = metaSpline(x0.rcarray!(immutable double), rcarray(int0, int1, int2, int3).lightConst, SplineConfiguration!double.init); 1| assert(interpolant == interpolant); | | ///// compute test data //// 1| auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); | // auto test_grid = cartesian(x0 + 0, x1 + 0); 1| auto real_data = test_grid.map!f; 2| auto interp_data = test_grid.vmap(interpolant); | | ///// verify result //// 1| assert(all!appreq(interp_data, real_data)); | 1| auto z0 = 1.23; 1| auto z1 = 3.21; 1| auto d = interpolant.withDerivative(z0, z1); 1| assert(appreq(d[0][0], f(z0, z1))); 1| assert(appreq(d[1][0], y_x0 + y_x0x1 * z1)); 1| assert(appreq(d[0][1], y_x1 + y_x0x1 * z0)); 1| assert(appreq(d[1][1], y_x0x1)); |} source/mir/interpolate/spline.d is 94% covered <<<<<< EOF # path=source-mir-interpolate-utility.lst |module mir.interpolate.utility; | |import mir.ndslice.slice; |import std.traits; |import mir.math.common: optmath; | |package template DeepType(T) |{ | static if (is(T : E[N], E, size_t N)) | alias DeepType = DeepType!E; | else | alias DeepType = T; |} | |/++ |Quadratic function structure |+/ |struct ParabolaKernel(T) |{ | /// | T a = 0; | /// | T b = 0; | /// | T c = 0; | |@optmath: | | /// 1| this(T a, T b, T c) | { 1| this.a = a; 1| this.b = b; 1| this.c = c; | } | | /// Builds parabola given three points 5| this(T x0, T x1, T x2, T y0, T y1, T y2) | { 5| auto b1 = x1 - x0; 5| auto b2 = x2 - x1; 5| auto d1 = y1 - y0; 5| auto d2 = y2 - y1; 5| auto a1 = b1 * (x1 + x0); 5| auto a2 = b2 * (x2 + x1); 5| auto bm = - (b2 / b1); 5| auto a3 = bm * a1 + a2; 5| auto d3 = bm * d1 + d2; 5| a = d3 / a3; 5| b = (d1 - a1 * a) / b1; 5| c = y1 - x1 * (a * x1 + b); | } | | /++ | Params: | x0 = `x0` | x1 = `x1` | y0 = `f(x0)` | y1 = `f(x1)` | d1 = `f'(x1)` | +/ | static ParabolaKernel fromFirstDerivative(T x0, T x1, T y0, T y1, T d1) | { 1| auto dy = y1 - y0; 1| auto dx = x1 - x0; 1| auto dd = dy / dx; 1| auto a = (d1 - dd) / dx; 1| auto b = dd - a * (x0 + x1); 1| auto c = y1 - (a * x1 + b) * x1; 1| return ParabolaKernel(a, b, c); | } | | /// | auto opCall(uint derivative = 0)(T x) const | if (derivative <= 2) | { 5| auto y = (a * x + b) * x + c; | static if (derivative == 0) 5| return y; | else | { 0000000| auto y1 = 2 * a * x + b; | static if (derivative == 1) 0000000| return cast(T[2])[y, y1]; | else 0000000| return cast(T[3])[y, y1, 2 * a]; | } | } | | /// | alias withDerivative = opCall!1; | /// | alias withTwoDerivatives = opCall!2; |} | |/// ditto |ParabolaKernel!(Unqual!(typeof(X.init - Y.init))) parabolaKernel(X, Y)(in X x0, in X x1, in X x2, const Y y0, const Y y1, const Y y2) |{ 1| return typeof(return)(x0, x1, x2, y0, y1, y2); |} | |/++ |Returns: `[f'(x0), f'(x1), f'(x2)]` |+/ |Unqual!(typeof(X.init - Y.init))[3] parabolaDerivatives(X, Y)(in X x0, in X x1, in X x2, const Y y0, const Y y1, const Y y2) |{ 24| auto d0 = (y2 - y1) / (x2 - x1); 24| auto d1 = (y0 - y2) / (x0 - x2); 24| auto d2 = (y1 - y0) / (x1 - x0); 24| return [d1 + d2 - d0, d0 + d2 - d1, d0 + d1 - d2]; |} | |version(mir_test) |/// |unittest |{ | import mir.math.common: approxEqual; | 4| alias f = (double x) => 3 * x ^^ 2 + 7 * x + 5; 1| auto x0 = 4; 1| auto x1 = 9; 1| auto x2 = 20; 1| auto p = parabolaKernel(x0, x1, x2, f(x0), f(x1), f(x2)); | 1| assert(p.a.approxEqual(3)); 1| assert(p.b.approxEqual(7)); 1| assert(p.c.approxEqual(5)); 1| assert(p(10).approxEqual(f(10))); |} | | |version(mir_test) |/// |unittest |{ | import mir.math.common: approxEqual; | 2| alias f = (double x) => 3 * x ^^ 2 + 7 * x + 5; 1| alias d = (double x) => 2 * 3 * x + 7; 1| auto x0 = 4; 1| auto x1 = 9; 1| auto p = ParabolaKernel!double.fromFirstDerivative(x0, x1, f(x0), f(x1), d(x1)); | 1| assert(p.a.approxEqual(3)); 1| assert(p.b.approxEqual(7)); 1| assert(p.c.approxEqual(5)); |} | |/++ |Cubic function structure |+/ |struct CubicKernel(T) |{ | /// | T a = 0; | /// | T b = 0; | /// | T c = 0; | /// | T d = 0; | |@optmath: | | /// 1| this(T a, T b, T c, T d) | { 1| this.a = a; 1| this.b = b; 1| this.c = c; 1| this.d = d; | } | | /++ | Params: | x0 = `x0` | x1 = `x1` | y0 = `f(x0)` | y1 = `f(x1)` | dd0 = `f''(x0)` | d1 = `f'(x1)` | +/ | static CubicKernel fromSecondAndFirstDerivative(T x0, T x1, T y0, T y1, T dd0, T d1) | { 1| auto hdd0 = 0.5f * dd0; 1| auto dy = y1 - y0; 1| auto dx = x1 - x0; 1| auto dd = dy / dx; 1| auto a = 0.5f * ((d1 - dd) / dx - hdd0) / dx; 1| auto b = hdd0 - 3 * a * x0; 1| auto c = d1 - (3 * a * x1 + 2 * b) * x1; 1| auto d = y1 - ((a * x1 + b) * x1 + c) * x1; 1| return CubicKernel!T(a, b, c, d); | } | | /// | auto opCall(uint derivative = 0)(T x) const | if (derivative <= 3) | { 4| auto y = ((a * x + b) * x + c) * x + d; | static if (derivative == 0) 1| return y; | else | { 3| T[derivative + 1] ret; 3| ret[0] = y; 3| ret[1] = (3 * a * x + 2 * b) * x + c; | static if (derivative > 1) | { 2| ret[2] = 6 * a * x + 2 * b; | static if (derivative > 2) 1| ret[3] = 6 * a; | } 3| return ret; | } | } | | /// | alias withDerivative = opCall!1; | /// | alias withTwoDerivatives = opCall!2; |} | |version(mir_test) |/// |unittest |{ | import mir.math.common: approxEqual; | 3| alias f = (double x) => 3 * x ^^ 3 + 7 * x ^^ 2 + 5 * x + 10; 2| alias d = (double x) => 3 * 3 * x ^^ 2 + 2 * 7 * x + 5; 2| alias s = (double x) => 6 * 3 * x + 2 * 7; 1| auto x0 = 4; 1| auto x1 = 9; 1| auto p = CubicKernel!double.fromSecondAndFirstDerivative(x0, x1, f(x0), f(x1), s(x0), d(x1)); | 1| assert(p.a.approxEqual(3)); 1| assert(p.b.approxEqual(7)); 1| assert(p.c.approxEqual(5)); 1| assert(p.d.approxEqual(10)); 1| assert(p(13).approxEqual(f(13))); 1| assert(p.opCall!1(13)[1].approxEqual(d(13))); 1| assert(p.opCall!2(13)[2].approxEqual(s(13))); 1| assert(p.opCall!3(13)[3].approxEqual(18)); |} | | |package auto pchipTail(T)(in T step0, in T step1, in T diff0, in T diff1) |{ | import mir.math.common: copysign, fabs; | if (!diff0) | { | return 0; | } | auto slope = ((step0 * 2 + step1) * diff0 - step0 * diff1) / (step0 + step1); | if (copysign(1f, slope) != copysign(1f, diff0)) | { | return 0; | } | if ((copysign(1f, diff0) != copysign(1f, diff1)) && (fabs(slope) > fabs(diff0 * 3))) | { | return diff0 * 3; | } | return slope; |} source/mir/interpolate/utility.d is 96% covered <<<<<< EOF # path=source-mir-lob.lst |/++ |+/ |module mir.lob; | |import mir.serde: serdeRegister; | |/++ |Values of type clob are encoded as a sequence of octets that should be interpreted as text |with an unknown encoding (and thus opaque to the application). |+/ |@serdeRegister |struct Clob |{ | /// | const(char)[] data; | | /// | int opCmp(scope const typeof(this) rhs) | @safe pure nothrow @nogc scope const | { 0000000| return __cmp(data, rhs.data); | } |} | |/++ |This is a sequence of octets with no interpretation (and thus opaque to the application). |+/ |@serdeRegister |struct Blob |{ | /// | const(ubyte)[] data; | | /// | int opCmp(scope const typeof(this) rhs) | @safe pure nothrow @nogc scope const | { 0000000| return __cmp(data, rhs.data); | } |} source/mir/lob.d is 0% covered <<<<<< EOF # path=source-mir-math-func-expdigamma.lst |/** |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |Authors: Ilia Ki |*/ |module mir.math.func.expdigamma; | |/++ |Optimized and more precise analog of `y = exp(digamma(x))`. | |Returns: | `exp(digamma(x))` |+/ |F expDigamma(F)(in F x) |{ | import mir.math.common; | | static immutable F[7] c = [ | F(1.0 / 24), | F(1.0L / 48), | F(23.0L / 5760), | F(17.0L / 3840), | F(10_099.0L / 2_903_040), | F(2501.0L / 1_161_216), | F(795_697.0L / 199_065_600), | ]; | 129| if (!(x >= 0)) 0000000| return F.nan; 129| F s = x; 129| F w = 0; 859| while ( s < F(10) ) | { 730| w += 1 / s; 730| s += 1; | } 129| F y = F(-0.5); 129| F t = 1; | import mir.internal.utility; | foreach (i; Iota!(0, c.length)) | { 903| t *= s; 903| y += c[i] / t; | } 129| y += s; 129| y /= exp(w); 129| return y; |} | |version(mir_test) |unittest |{ | import std.meta: AliasSeq; | import std.mathspecial: digamma; | import mir.math: approxEqual, exp, nextUp, nextDown; 1| assert(approxEqual(expDigamma(0.001), exp(digamma(0.001)))); 1| assert(approxEqual(expDigamma(0.1), exp(digamma(0.1)))); 1| assert(approxEqual(expDigamma(1.0), exp(digamma(1.0)))); 1| assert(approxEqual(expDigamma(2.3), exp(digamma(2.3)))); 1| assert(approxEqual(expDigamma(20.0), exp(digamma(20.0)))); 1| assert(approxEqual(expDigamma(40.0), exp(digamma(40.0)))); | foreach (F; AliasSeq!(float, double, real)) | { 3| assert(expDigamma!F(0.0) == 0); 3| assert(expDigamma!F(0.0.nextUp) >= 0); 3| assert(expDigamma!F(0.0.min_normal) >= 0); 3| assert(expDigamma!F(0.5.nextUp) >= 0); 3| assert(expDigamma!F(0.5.nextDown) >= 0); 90| foreach (i; 1 .. 10) | { 27| assert(expDigamma(F(i)) >= expDigamma(F(i).nextDown)); 27| assert(expDigamma(F(i)) <= expDigamma(F(i).nextUp)); | } | } |} source/mir/math/func/expdigamma.d is 96% covered <<<<<< EOF # path=source-mir-math-func-hermite.lst |/++ |Hermite Polynomial Coefficients | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: John Hall | |+/ | |module mir.math.func.hermite; | |/++ |Normalized (probabilist's) Hermite polynomial coefficients | |Params: | N = Degree of polynomial | |See_also: | $(LINK2 https://en.wikipedia.org/wiki/Hermite_polynomials, Hermite polynomials) |+/ |@safe pure nothrow |long[] hermiteCoefficientsNorm(size_t N) |{ 20| if (N == 0) { 10| return [1]; | } else { 10| typeof(return) output = [0, 1]; 10| if (N > 1) { 9| output.length = N + 1; 9| int K; 9| typeof(return) h_N_minus_1 = hermiteCoefficientsNorm(0); // to be copied to h_N_minus_2 in loop 9| h_N_minus_1.length = N; 9| typeof(return) h_N_minus_2; //value replaced later 9| h_N_minus_2.length = N - 1; | 162| foreach (size_t j; 2..(N + 1)) { 45| h_N_minus_2[0..(j - 1)] = h_N_minus_1[0..(j - 1)]; 45| h_N_minus_1[0..j] = output[0..j]; 45| K = -(cast(int) j - 1); 45| output[0] = K * h_N_minus_2[0]; 495| foreach (size_t i; 1..(j - 1)) { 120| output[i] = h_N_minus_1[i - 1] + K * h_N_minus_2[i]; | } 405| foreach (size_t i; (j - 1)..(j + 1)) { 90| output[i] = h_N_minus_1[i - 1]; | } | } | } 10| return output; | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.polynomial: polynomial; | import mir.rc.array: rcarray; | import mir.test: should; | 2| auto h0 = hermiteCoefficientsNorm(0).rcarray!(const double).polynomial; 2| auto h1 = hermiteCoefficientsNorm(1).rcarray!(const double).polynomial; 2| auto h2 = hermiteCoefficientsNorm(2).rcarray!(const double).polynomial; 2| auto h3 = hermiteCoefficientsNorm(3).rcarray!(const double).polynomial; 2| auto h4 = hermiteCoefficientsNorm(4).rcarray!(const double).polynomial; 2| auto h5 = hermiteCoefficientsNorm(5).rcarray!(const double).polynomial; 2| auto h6 = hermiteCoefficientsNorm(6).rcarray!(const double).polynomial; 2| auto h7 = hermiteCoefficientsNorm(7).rcarray!(const double).polynomial; 2| auto h8 = hermiteCoefficientsNorm(8).rcarray!(const double).polynomial; 2| auto h9 = hermiteCoefficientsNorm(9).rcarray!(const double).polynomial; 2| auto h10 = hermiteCoefficientsNorm(10).rcarray!(const double).polynomial; | 1| h0(3).should == 1; 1| h1(3).should == 3; 1| h2(3).should == 8; 1| h3(3).should == 18; 1| h4(3).should == 30; 1| h5(3).should == 18; 1| h6(3).should == -96; 1| h7(3).should == -396; 1| h8(3).should == -516; 1| h9(3).should == 1_620; 1| h10(3).should == 9_504; |} | |/// Also works with @nogc CTFE |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.ndslice.slice: sliced; | import mir.test: should; | | static immutable result = [-1, 0, 1]; | | static immutable hc2 = hermiteCoefficientsNorm(2); 1| hc2.sliced.should == result; |} | |/++ |Physicist's Hermite polynomial coefficients | |Params: | N = Degree of polynomial | |See_also: | $(LINK2 https://en.wikipedia.org/wiki/Hermite_polynomials, Hermite polynomials) |+/ |@safe pure nothrow |long[] hermiteCoefficients(size_t N) |{ 20| if (N == 0) { 10| return [1]; | } else { 10| typeof(return) output = [0, 2]; 10| if (N > 1) { 9| output.length = N + 1; 9| typeof(return) h_N_minus_1 = hermiteCoefficients(0); 9| h_N_minus_1.length = N; | 162| foreach (size_t j; 2..(N + 1)) { 45| h_N_minus_1[0..j] = output[0..j]; 45| output[0] = -h_N_minus_1[1]; 495| foreach (size_t i; 1..(j - 1)) { 120| output[i] = 2 * h_N_minus_1[i - 1] - (cast(int) i + 1) * h_N_minus_1[i + 1]; | } 405| foreach (size_t i; (j - 1)..(j + 1)) { 90| output[i] = 2 * h_N_minus_1[i - 1]; | } | } | } 10| return output; | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.polynomial: polynomial; | import mir.rc.array: rcarray; | import mir.test: should; | 2| auto h0 = hermiteCoefficients(0).rcarray!(const double).polynomial; 2| auto h1 = hermiteCoefficients(1).rcarray!(const double).polynomial; 2| auto h2 = hermiteCoefficients(2).rcarray!(const double).polynomial; 2| auto h3 = hermiteCoefficients(3).rcarray!(const double).polynomial; 2| auto h4 = hermiteCoefficients(4).rcarray!(const double).polynomial; 2| auto h5 = hermiteCoefficients(5).rcarray!(const double).polynomial; 2| auto h6 = hermiteCoefficients(6).rcarray!(const double).polynomial; 2| auto h7 = hermiteCoefficients(7).rcarray!(const double).polynomial; 2| auto h8 = hermiteCoefficients(8).rcarray!(const double).polynomial; 2| auto h9 = hermiteCoefficients(9).rcarray!(const double).polynomial; 2| auto h10 = hermiteCoefficients(10).rcarray!(const double).polynomial; | 1| h0(3).should == 1; 1| h1(3).should == 6; 1| h2(3).should == 34; 1| h3(3).should == 180; 1| h4(3).should == 876; 1| h5(3).should == 3_816; 1| h6(3).should == 14_136; 1| h7(3).should == 39_024; 1| h8(3).should == 36_240; 1| h9(3).should == -406_944; 1| h10(3).should == -3_093_984; |} | |/// Also works with @nogc CTFE |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.ndslice.slice: sliced; | import mir.test: should; | | static immutable result = [-2, 0, 4]; | | static immutable hc2 = hermiteCoefficients(2); 1| hc2.sliced.should == result; |} source/mir/math/func/hermite.d is 100% covered <<<<<< EOF # path=source-mir-math-func-normal.lst |/** |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |*/ |/** | * Error Functions and Normal Distribution. | * | * Copyright: Based on the CEPHES math library, which is | * Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com). | * Authors: Stephen L. Moshier, ported to D by Don Clugston and David Nadlinger. Adopted to Mir by Ilia Ki. | */ |/** | * Macros: | * NAN = $(RED NAN) | * INTEGRAL = ∫ | * POWER = $1$2 | * Special Values | * $0 | */ | |module mir.math.func.normal; | |import std.traits: isFloatingPoint; |import mir.math.common; | |@safe pure nothrow @nogc: | |/// |T normalPDF(T)(const T z) | if (isFloatingPoint!T) |{ | import mir.math.common: sqrt, exp; 1| return T(SQRT2PIINV) * exp(z * z * -0.5); |} | |/// ditto |T normalPDF(T)(const T x, const T mean, const T stdDev) |if (isFloatingPoint!T) |{ | return normalPDF((x - mean) / stdDev) / stdDev; |} | |/++ |Computes the normal distribution cumulative distribution function (CDF). |The normal (or Gaussian, or bell-shaped) distribution is |defined as: |normalDist(x) = 1/$(SQRT) π $(INTEGRAL -$(INFINITY), x) exp( - $(POWER t, 2)/2) dt | = 0.5 + 0.5 * erf(x/sqrt(2)) | = 0.5 * erfc(- x/sqrt(2)) |To maintain accuracy at high values of x, use |normalCDF(x) = 1 - normalCDF(-x). |Accuracy: |Within a few bits of machine resolution over the entire |range. |References: |$(LINK http://www.netlib.org/cephes/ldoubdoc.html), |G. Marsaglia, "Evaluating the Normal Distribution", |Journal of Statistical Software 11, (July 2004). |+/ |T normalCDF(T)(const T a) | if (isFloatingPoint!T) |{ | pragma(inline, false); | import mir.math.constant: SQRT1_2; | 9| T x = a * T(SQRT1_2); 9| T z = fabs(x); | 9| if (z < 1) | { 1| return 0.5f + 0.5f * erf(x); | } | else | { 8| T y = 0.5f * erfce(z); | /* Multiply by exp(-x^2 / 2) */ 8| z = expx2(a, -1); 8| y = y * sqrt(z); 8| if (y != y) 2| y = 0; 8| if (x > 0) 5| y = 1 - y; 8| return y; | } |} | |/// ditto |T normalCDF(T)(const T x, const T mean, const T stdDev) | if (isFloatingPoint!T) |{ | return normalCDF((x - mean) / stdDev); |} | |version(mir_test) |@safe |unittest |{ 1| assert(fabs(normalCDF(1.0L) - (0.841344746068543))< 0.0000000000000005); 1| assert(normalCDF(double.infinity) == 1); 1| assert(normalCDF(100.0) == 1); 1| assert(normalCDF(8.0) < 1); 1| assert(normalCDF(-double.infinity) == 0); 1| assert(normalCDF(-100.0) == 0); 1| assert(normalCDF(-8.0) > 0); |} | |/// |T normalInvCDF(T)(const T p) |in { 16| assert(p >= 0 && p <= 1, "Domain error"); |} |do |{ | pragma(inline, false); 15| if (p <= 0 || p >= 1) | { 2| if (p == 0) 1| return -T.infinity; 1| if ( p == 1 ) 1| return T.infinity; 0000000| return T.nan; // domain error | } 6| int code = 1; 6| T y = p; 6| if ( y > (1 - T(EXP_2)) ) | { 1| y = 1 - y; 1| code = 0; | } | 30| T x, z, y2, x0, x1; | 6| if ( y > T(EXP_2) ) | { 1| y = y - 0.5L; 1| y2 = y * y; 1| x = y + y * (y2 * rationalPoly!(P0, Q0)(y2)); 1| return x * double(SQRT2PI); | } | 5| x = sqrt( -2 * log(y) ); 5| x0 = x - log(x)/x; 5| z = 1/x; 5| if ( x < 8 ) | { 3| x1 = z * rationalPoly!(P1, Q1)(z); | } 2| else if ( x < 32 ) | { 1| x1 = z * rationalPoly!(P2, Q2)(z); | } | else | { 1| x1 = z * rationalPoly!(P3, Q3)(z); | } 5| x = x0 - x1; 5| if ( code != 0 ) | { 4| x = -x; | } 5| return x; |} | |/// ditto |T normalInvCDF(T)(const T p, const T mean, const T stdDev) | if (isFloatingPoint!T) |{ | return normalInvCDF(p) * stdDev + mean; |} | |/// |version(mir_test) |@safe unittest |{ | import std.math: feqrel; | // TODO: Use verified test points. | // The values below are from Excel 2003. 1| assert(fabs(normalInvCDF(0.001) - (-3.09023230616779))< 0.00000000000005); 1| assert(fabs(normalInvCDF(1e-50) - (-14.9333375347885))< 0.00000000000005); 1| assert(feqrel(normalInvCDF(0.999L), -normalInvCDF(0.001L)) > real.mant_dig-6); | | // Excel 2003 gets all the following values wrong! 1| assert(normalInvCDF(0.0) == -real.infinity); 1| assert(normalInvCDF(1.0) == real.infinity); 1| assert(normalInvCDF(0.5) == 0); | // (Excel 2003 returns norminv(p) = -30 for all p < 1e-200). | // The value tested here is the one the function returned in Jan 2006. 1| real unknown1 = normalInvCDF(1e-250L); 1| assert( fabs(unknown1 -(-33.79958617269L) ) < 0.00000005); |} | |/// |enum real SQRT2PI = 2.50662827463100050241576528481104525L; // sqrt(2pi) |/// |enum real SQRT2PIINV = 1 / SQRT2PI; // 1 / sqrt(2pi) | |package(mir) { | |enum real EXP_2 = 0.135335283236612691893999494972484403L; /* exp(-2) */ | |/// |enum T MAXLOG(T) = log(T.max); |/// |enum T MINLOG(T) = log(T.min_normal * T.epsilon); // log(smallest denormal); |} | |/** | * Exponential of squared argument | * | * Computes y = exp(x*x) while suppressing error amplification | * that would ordinarily arise from the inexactness of the | * exponential argument x*x. | * | * If sign < 0, the result is inverted; i.e., y = exp(-x*x) . | * | * ACCURACY: | * Relative error: | * arithmetic domain # trials peak rms | * IEEE -106.566, 106.566 10^5 1.6e-19 4.4e-20 | */ |package(mir) |T expx2(T)(const T x_, int sign) |{ | /* | Cephes Math Library Release 2.9: June, 2000 | Copyright 2000 by Stephen L. Moshier | */ 8| const T M = 32_768.0; 8| const T MINV = 3.0517578125e-5L; | 8| T x = fabs(x_); 8| if (sign < 0) 8| x = -x; | | /* Represent x as an exact multiple of M plus a residual. | M is a power of 2 chosen so that exp(m * m) does not overflow | or underflow and so that |x - m| is small. */ 8| T m = MINV * floor(M * x + 0.5f); 8| T f = x - m; | | /* x^2 = m^2 + 2mf + f^2 */ 8| T u = m * m; 8| T u1 = 2 * m * f + f * f; | 8| if (sign < 0) | { 8| u = -u; 8| u1 = -u1; | } | 8| if (u + u1 > MAXLOG!T) 0000000| return T.infinity; | | /* u is exact, u1 is small. */ 8| return exp(u) * exp(u1); |} | |/** | Exponentially scaled erfc function | exp(x^2) erfc(x) | valid for x > 1. | Use with ndtrl and expx2l. */ |package(mir) |T erfce(T)(const T x) |{ 8| T y = 1 / x; | 8| if (x < 8) | { 2| return rationalPoly!(P, Q)(y); | } | else | { 6| return y * rationalPoly!(R, S)(y * y); | } |} | |private T rationalPoly(alias numerator, alias denominator, T)(const T x) pure nothrow |{ 15| return x.poly!numerator / x.poly!denominator; |} | |private T poly(alias vec, T)(const T x) |{ | import mir.internal.utility: Iota; 30| T y = T(vec[$ - 1]); | foreach_reverse(i; Iota!(vec.length - 1)) | { 200| y *= x; 200| y += T(vec[i]); | } 30| return y; |} | |private { | |/* erfc(x) = exp(-x^2) P(1/x)/Q(1/x) | 1/8 <= 1/x <= 1 | Peak relative error 5.8e-21 */ |immutable real[10] P = [ -0x1.30dfa809b3cc6676p-17, 0x1.38637cd0913c0288p+18, | 0x1.2f015e047b4476bp+22, 0x1.24726f46aa9ab08p+25, 0x1.64b13c6395dc9c26p+27, | 0x1.294c93046ad55b5p+29, 0x1.5962a82f92576dap+30, 0x1.11a709299faba04ap+31, | 0x1.11028065b087be46p+31, 0x1.0d8ef40735b097ep+30 |]; | |immutable real[11] Q = [ 0x1.14d8e2a72dec49f4p+19, 0x1.0c880ff467626e1p+23, | 0x1.04417ef060b58996p+26, 0x1.404e61ba86df4ebap+28, 0x1.0f81887bc82b873ap+30, | 0x1.4552a5e39fb49322p+31, 0x1.11779a0ceb2a01cep+32, 0x1.3544dd691b5b1d5cp+32, | 0x1.a91781f12251f02ep+31, 0x1.0d8ef3da605a1c86p+30, 1.0 |]; | |/* erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) | 1/128 <= 1/x < 1/8 | Peak relative error 1.9e-21 */ |immutable real[5] R = [ 0x1.b9f6d8b78e22459ep-6, 0x1.1b84686b0a4ea43ap-1, | 0x1.b8f6aebe96000c2ap+1, 0x1.cb1dbedac27c8ec2p+2, 0x1.cf885f8f572a4c14p+1 |]; | |immutable real[6] S = [ | 0x1.87ae3cae5f65eb5ep-5, 0x1.01616f266f306d08p+0, 0x1.a4abe0411eed6c22p+2, | 0x1.eac9ce3da600abaap+3, 0x1.5752a9ac2faebbccp+3, 1.0 |]; | |/* erf(x) = x P(x^2)/Q(x^2) | 0 <= x <= 1 | Peak relative error 7.6e-23 */ |immutable real[7] T = [ 0x1.0da01654d757888cp+20, 0x1.2eb7497bc8b4f4acp+17, | 0x1.79078c19530f72a8p+15, 0x1.4eaf2126c0b2c23p+11, 0x1.1f2ea81c9d272a2ep+8, | 0x1.59ca6e2d866e625p+2, 0x1.c188e0b67435faf4p-4 |]; | |immutable real[7] U = [ 0x1.dde6025c395ae34ep+19, 0x1.c4bc8b6235df35aap+18, | 0x1.8465900e88b6903ap+16, 0x1.855877093959ffdp+13, 0x1.e5c44395625ee358p+9, | 0x1.6a0fed103f1c68a6p+5, 1.0 |]; | |} | |package(mir) |F erf(F)(const F x) | if(isFloatingPoint!F) |{ 1| if (x == 0) 0000000| return x; // deal with negative zero 1| if (x == -F.infinity) 0000000| return -1; 1| if (x == F.infinity) 0000000| return 1; 1| immutable ax = fabs(x); 1| if (ax > 1) 0000000| return 1 - erfc(x); | 1| return x * rationalPoly!(T, U)(x * x); |} | |/** | * Complementary error function | * | * erfc(x) = 1 - erf(x), and has high relative accuracy for | * values of x far from zero. (For values near zero, use erf(x)). | * | * 1 - erf(x) = 2/ $(SQRT)(π) | * $(INTEGRAL x, $(INFINITY)) exp( - $(POWER t, 2)) dt | * | * | * For small x, erfc(x) = 1 - erf(x); otherwise rational | * approximations are computed. | * | * A special function expx2(x) is used to suppress error amplification | * in computing exp(-x^2). | */ |package(mir) |T erfc(T)(const T a) |{ 0000000| if (a == T.infinity) 0000000| return 0; 0000000| if (a == -T.infinity) 0000000| return 2; | 0000000| immutable x = (a < 0) ? -a : a; | 0000000| if (x < 1) 0000000| return 1 - erf(a); | 0000000| if (-a * a < -MAXLOG!T) | { | // underflow 0000000| if (a < 0) return 2; 0000000| else return 0; | } | 0000000| T y; 0000000| immutable z = expx2(a, -1); | 0000000| y = 1 / x; 0000000| if (x < 8) 0000000| y = z * rationalPoly!(P, Q)(y); | else 0000000| y = z * y * rationalPoly!(R, S)(y * y); | 0000000| if (a < 0) 0000000| y = 2 - y; | 0000000| if (y == 0) | { | // underflow 0000000| if (a < 0) return 2; 0000000| else return 0; | } | 0000000| return y; |} | |private: | |static immutable real[8] P0 = |[ -0x1.758f4d969484bfdcp-7, 0x1.53cee17a59259dd2p-3, |-0x1.ea01e4400a9427a2p-1, 0x1.61f7504a0105341ap+1, -0x1.09475a594d0399f6p+2, |0x1.7c59e7a0df99e3e2p+1, -0x1.87a81da52edcdf14p-1, 0x1.1fb149fd3f83600cp-7 |]; | |static immutable real[8] Q0 = |[ -0x1.64b92ae791e64bb2p-7, 0x1.7585c7d597298286p-3, |-0x1.40011be4f7591ce6p+0, 0x1.1fc067d8430a425ep+2, -0x1.21008ffb1e7ccdf2p+3, |0x1.3d1581cf9bc12fccp+3, -0x1.53723a89fd8f083cp+2, 1.0 |]; | |static immutable real[10] P1 = |[ 0x1.20ceea49ea142f12p-13, 0x1.cbe8a7267aea80bp-7, |0x1.79fea765aa787c48p-2, 0x1.d1f59faa1f4c4864p+1, 0x1.1c22e426a013bb96p+4, |0x1.a8675a0c51ef3202p+5, 0x1.75782c4f83614164p+6, 0x1.7a2f3d90948f1666p+6, |0x1.5cd116ee4c088c3ap+5, 0x1.1361e3eb6e3cc20ap+2 |]; | |static immutable real[10] Q1 = |[ 0x1.3a4ce1406cea98fap-13, 0x1.f45332623335cda2p-7, |0x1.98f28bbd4b98db1p-2, 0x1.ec3b24f9c698091cp+1, 0x1.1cc56ecda7cf58e4p+4, |0x1.92c6f7376bf8c058p+5, 0x1.4154c25aa47519b4p+6, 0x1.1b321d3b927849eap+6, |0x1.403a5f5a4ce7b202p+4, 1.0 |]; | |static immutable real[8] P2 = |[ 0x1.8c124a850116a6d8p-21, 0x1.534abda3c2fb90bap-13, |0x1.29a055ec93a4718cp-7, 0x1.6468e98aad6dd474p-3, 0x1.3dab2ef4c67a601cp+0, |0x1.e1fb3a1e70c67464p+1, 0x1.b6cce8035ff57b02p+2, 0x1.9f4c9e749ff35f62p+1 |]; | |static immutable real[8] Q2 = |[ 0x1.af03f4fc0655e006p-21, 0x1.713192048d11fb2p-13, |0x1.4357e5bbf5fef536p-7, 0x1.7fdac8749985d43cp-3, 0x1.4a080c813a2d8e84p+0, |0x1.c3a4b423cdb41bdap+1, 0x1.8160694e24b5557ap+2, 1.0 |]; | |static immutable real[8] P3 = |[ -0x1.55da447ae3806168p-34, -0x1.145635641f8778a6p-24, |-0x1.abf46d6b48040128p-17, -0x1.7da550945da790fcp-11, -0x1.aa0b2a31157775fap-8, |0x1.b11d97522eed26bcp-3, 0x1.1106d22f9ae89238p+1, 0x1.029a358e1e630f64p+1 |]; | |static immutable real[8] Q3 = |[ -0x1.74022dd5523e6f84p-34, -0x1.2cb60d61e29ee836p-24, |-0x1.d19e6ec03a85e556p-17, -0x1.9ea2a7b4422f6502p-11, -0x1.c54b1e852f107162p-8, |0x1.e05268dd3c07989ep-3, 0x1.239c6aff14afbf82p+1, 1.0 |]; source/mir/math/func/normal.d is 75% covered <<<<<< EOF # path=source-mir-math-numeric.lst |/++ |This module contains simple numeric algorithms. |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Sponsors: This work has been sponsored by $(SUBREF http://symmetryinvestments.com, Symmetry Investments) and Kaleidic Associates. |+/ |module mir.math.numeric; | |import mir.math.common; |import mir.primitives; |import mir.primitives: isInputRange; |import std.traits: CommonType, Unqual, isIterable, ForeachType, isPointer; |import mir.internal.utility: isFloatingPoint; | |/// |struct ProdAccumulator(T) | if (isFloatingPoint!T) |{ | alias F = Unqual!T; | | /// | long exp = 1L; | /// | F x = cast(F) 0.5; | /// | alias mantissa = x; | | /// | @safe pure @nogc nothrow 0000000| this(F value) | { | import mir.math.ieee: frexp; | 0000000| int lexp; 0000000| this.x = frexp(value, lexp); 0000000| this.exp = lexp; | } | | /// | @safe pure @nogc nothrow 4| this(long exp, F x) | { 4| this.exp = exp; 4| this.x = x; | } | | /// | @safe pure @nogc nothrow | void put(U)(U e) | if (is(U : T)) | { | static if (is(U == T)) | { 456| int lexp; | import mir.math.ieee: frexp; 456| x *= frexp(e, lexp); 456| exp += lexp; 456| if (x.fabs < 0.5f) | { 382| x += x; 382| exp--; | } | } else { 482| return put(cast(T) e); | } | } | | /// | @safe pure @nogc nothrow | void put(ProdAccumulator!T value) | { 0000000| exp += value.exp; 0000000| x *= value.x; 0000000| if (x.fabs < 0.5f) | { 0000000| x += x; 0000000| exp--; | } | } | | /// | void put(Range)(Range r) | if (isIterable!Range) | { 966| foreach (ref elem; r) 265| put(elem); | } | | import mir.ndslice.slice; | | /// ditto | void put(Range: Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind)(Range r) | { | static if (N > 1 && kind == Contiguous) | { | import mir.ndslice.topology: flattened; 5| this.put(r.flattened); | } | else | static if (isPointer!Iterator && kind == Contiguous) | { 27| this.put(r.field); | } | else | { 598| foreach(elem; r) 174| this.put(elem); | } | } | | /// | @safe pure @nogc nothrow | T prod() const scope @property | { | import mir.math.ieee: ldexp; 39| int e = | exp > int.max ? int.max : | exp < int.min ? int.min : | cast(int) exp; 39| return ldexp(mantissa, e); | } | | /// | @safe pure @nogc nothrow | ProdAccumulator!T ldexp(long exp) const | { 4| return typeof(return)(this.exp + exp, mantissa); | } | | // /// | alias opOpAssign(string op : "*") = put; | | /// | @safe pure @nogc nothrow | ProdAccumulator!T opUnary(string op : "-")() const | { | return typeof(return)(exp, -mantissa); | } | | /// | @safe pure @nogc nothrow | ProdAccumulator!T opUnary(string op : "+")() const | { | return typeof(return)(exp, +mantissa); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| ProdAccumulator!float x; 1| x.put([1, 2, 3].sliced); 1| assert(x.prod == 6f); 1| x.put(4); 1| assert(x.prod == 24f); |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.slice: sliced; | | static immutable a = [1, 2, 3]; 1| ProdAccumulator!float x; 1| x.put(a); 1| assert(x.prod == 6f); 1| x.put(4); 1| assert(x.prod == 24f); | static assert(is(typeof(x.prod) == float)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| ProdAccumulator!double x; 1| x.put([1.0, 2.0, 3.0]); 1| assert(x.prod == 6.0); 1| x.put(4.0); 1| assert(x.prod == 24.0); | static assert(is(typeof(x.prod) == double)); |} | |package template prodType(T) |{ | import mir.math.sum: elementType; | | alias U = elementType!T; | | static if (__traits(compiles, { | auto temp = U.init * U.init; | temp *= U.init; | })) { | import mir.math.stat: statType; | | alias V = typeof(U.init * U.init); | alias prodType = statType!(V, false); | } else { | static assert(0, "prodType: Can't prod elements of type " ~ U.stringof); | } |} | |/++ |Calculates the product of the elements of the input. | |This function uses a separate exponential accumulation algorithm to calculate the |product. A consequence of this is that the result must be a floating point type. |To calculate the product of a type that is not implicitly convertible to a |floating point type, use $(MREF mir, algorithm, iteration, reduce) or $(MREF mir, algorithm, iteration, fold). | |/++ |Params: | r = finite iterable range |Returns: | The prduct of all the elements in `r` |+/ | |See_also: |$(MREF mir, algorithm, iteration, reduce) |$(MREF mir, algorithm, iteration, fold) |+/ |F prod(F, Range)(Range r) | if (isFloatingPoint!F && isIterable!Range) |{ | import core.lifetime: move; | 20| ProdAccumulator!F prod; 20| prod.put(r.move); 20| return prod.prod; |} | |/++ |Params: | r = finite iterable range | exp = value of exponent |Returns: | The mantissa, such that the product equals the mantissa times 2^^exp |+/ |F prod(F, Range)(Range r, ref long exp) | if (isFloatingPoint!F && isIterable!Range) |{ | import core.lifetime: move; | 16| ProdAccumulator!F prod; 16| prod.put(r.move); 16| exp = prod.exp; 16| return prod.x; |} | |/++ |Params: | r = finite iterable range |Returns: | The prduct of all the elements in `r` |+/ |prodType!Range prod(Range)(Range r) | if (isIterable!Range) |{ | import core.lifetime: move; | | alias F = typeof(return); 17| return .prod!(F, Range)(r.move); |} | |/++ |Params: | r = finite iterable range | exp = value of exponent |Returns: | The mantissa, such that the product equals the mantissa times 2^^exp |+/ |prodType!Range prod(Range)(Range r, ref long exp) | if (isIterable!Range) |{ | import core.lifetime: move; | | alias F = typeof(return); 15| return .prod!(F, Range)(r.move, exp); |} | |/++ |Params: | ar = values |Returns: | The prduct of all the elements in `ar` |+/ |prodType!T prod(T)(scope const T[] ar...) |{ | alias F = typeof(return); 9| ProdAccumulator!F prod; 9| prod.put(ar); 9| return prod.prod; |} | |/// Product of arbitrary inputs |version(mir_test) |@safe pure @nogc nothrow |unittest |{ 1| assert(prod(1.0, 3, 4) == 12.0); 1| assert(prod!float(1, 3, 4) == 12f); |} | |/// Product of arrays and ranges |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | | enum l = 2.0 ^^ (double.max_exp - 1); | enum s = 2.0 ^^ -(double.max_exp - 1); 1| auto r = [l, l, l, s, s, s, 0.8 * 2.0 ^^ 10]; | 1| assert(r.prod == 0.8 * 2.0 ^^ 10); | | // Can get the mantissa and exponent 1| long e; 1| assert(r.prod(e).approxEqual(0.8)); 1| assert(e == 10); |} | |/// Product of vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: reduce; | import mir.math.common: approxEqual; | | enum l = 2.0 ^^ (double.max_exp - 1); | enum s = 2.0 ^^ -(double.max_exp - 1); 1| auto c = 0.8; 1| auto u = c * 2.0 ^^ 10; 1| auto r = [l, l, l, s, s, s, u, u, u].sliced; | 1| assert(r.prod == reduce!"a * b"(1.0, [u, u, u])); | 1| long e; 1| assert(r.prod(e).approxEqual(reduce!"a * b"(1.0, [c, c, c]))); 1| assert(e == 30); |} | |/// Product of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | import mir.algorithm.iteration: reduce; | | enum l = 2.0 ^^ (double.max_exp - 1); | enum s = 2.0 ^^ -(double.max_exp - 1); 1| auto c = 0.8; 1| auto u = c * 2.0 ^^ 10; 1| auto r = [ | [l, l, l], | [s, s, s], | [u, u, u] | ].fuse; | 1| assert(r.prod == reduce!"a * b"(1.0, [u, u, u])); | 1| long e; 1| assert(r.prod(e) == reduce!"a * b"(1.0, [c, c, c])); 1| assert(e == 30); |} | |/// Column prod of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.topology: alongDim, byDim, map; | 1| auto x = [ | [2.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 5.0] | ].fuse; | 1| auto result = [4.0, 7.5, 7.5, 2.0, 5.25, 21.25]; | | // Use byDim or alongDim with map to compute mean of row/column. 1| assert(x.byDim!1.map!prod.all!approxEqual(result)); 1| assert(x.alongDim!0.map!prod.all!approxEqual(result)); | | // FIXME | // Without using map, computes the prod of the whole slice | // assert(x.byDim!1.prod.all!approxEqual(result)); | // assert(x.alongDim!0.prod.all!approxEqual(result)); |} | |/// Can also set output type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.math.common: approxEqual; | import mir.ndslice.topology: repeat; | 1| auto x = [1, 2, 3].sliced; 1| assert(x.prod!float == 6f); |} | |/// Product of variables whose underlying types are implicitly convertible to double also have type double |version(mir_test) |@safe pure nothrow |unittest |{ | static struct Foo | { | int x; | alias x this; | } | 1| auto x = prod(1, 2, 3); 1| assert(x == 6.0); | static assert(is(typeof(x) == double)); | 1| auto y = prod([Foo(1), Foo(2), Foo(3)]); 1| assert(y == 6.0); | static assert(is(typeof(y) == double)); |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: reduce; | import mir.math.common: approxEqual; | | enum l = 2.0 ^^ (double.max_exp - 1); | enum s = 2.0 ^^ -(double.max_exp - 1); | enum c = 0.8; | enum u = c * 2.0 ^^ 10; | static immutable r = [l, l, l, s, s, s, u, u, u]; | static immutable result1 = [u, u, u]; | static immutable result2 = [c, c, c]; | 1| assert(r.sliced.prod.approxEqual(reduce!"a * b"(1.0, result1))); | 1| long e; 1| assert(r.sliced.prod(e).approxEqual(reduce!"a * b"(1.0, result2))); 1| assert(e == 30); |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: reduce; | import mir.math.common: approxEqual; | | enum l = 2.0 ^^ (float.max_exp - 1); | enum s = 2.0 ^^ -(float.max_exp - 1); | enum c = 0.8; | enum u = c * 2.0 ^^ 10; | static immutable r = [l, l, l, s, s, s, u, u, u]; | static immutable result1 = [u, u, u]; | static immutable result2 = [c, c, c]; | 1| assert(r.sliced.prod!double.approxEqual(reduce!"a * b"(1.0, result1))); | 1| long e; 1| assert(r.sliced.prod!double(e).approxEqual(reduce!"a * b"(1.0, result2))); 1| assert(e == 30); |} | |/++ |Compute the sum of binary logarithms of the input range $(D r). |The error of this method is much smaller than with a naive sum of log2. |+/ |Unqual!(DeepElementType!Range) sumOfLog2s(Range)(Range r) | if (isFloatingPoint!(DeepElementType!Range)) |{ 11| long exp = 0; 11| auto x = .prod(r, exp); 11| return exp + log2(x); |} | |/// |version(mir_test) |@safe pure |unittest |{ 4| alias isNaN = x => x != x; | 1| assert(sumOfLog2s(new double[0]) == 0); 1| assert(sumOfLog2s([0.0L]) == -real.infinity); 1| assert(sumOfLog2s([-0.0L]) == -real.infinity); 1| assert(sumOfLog2s([2.0L]) == 1); 1| assert(isNaN(sumOfLog2s([-2.0L]))); 1| assert(isNaN(sumOfLog2s([real.nan]))); 1| assert(isNaN(sumOfLog2s([-real.nan]))); 1| assert(sumOfLog2s([real.infinity]) == real.infinity); 1| assert(isNaN(sumOfLog2s([-real.infinity]))); 1| assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); |} | |/++ |Compute the sum of binary logarithms of the input range $(D r). |The error of this method is much smaller than with a naive sum of log. |+/ |Unqual!(DeepElementType!Range) sumOfLogs(Range)(Range r) | if (isFloatingPoint!(DeepElementType!Range)) |{ | import core.lifetime: move; | import mir.math.constant: LN2; 1| return typeof(return)(LN2) * sumOfLog2s(move(r)); |} | |/// |version(mir_test) |@safe pure |unittest |{ | import mir.math: LN2, approxEqual; 1| assert([ 0.25, 0.25, 0.25, 0.125 ].sumOfLogs.approxEqual(-9 * double(LN2))); |} | |/++ |Quickly computes factorial using extended |precision floating point type $(MREF mir,bignum,fp). | |Params: | count = number of product members | start = initial member value (optional) |Returns: `(count + start - 1)! / (start - 1)!` |Complexity: O(count) |+/ |auto factorial | (uint coefficientSize = 128) | (ulong count, ulong start = 1) | if (coefficientSize % (size_t.sizeof * 8) == 0 && coefficientSize >= (size_t.sizeof * 8)) 6| in (start) |{ | import mir.bignum.fp: Fp; | import mir.checkedint: mulu; | import mir.utility: _expect; | | alias R = Fp!coefficientSize; 6| R prod = 1LU; | 6| if (count) | { 4| ulong tempProd = start; 181| while(--count) | { 177| bool overflow; 177| ulong nextTempProd = mulu(tempProd, ++start, overflow); 177| if (_expect(!overflow, true)) | { 155| tempProd = nextTempProd; 155| continue; | } | else | { 22| prod *= R(tempProd); 22| tempProd = start; | } | } 4| prod *= R(tempProd); | } | 6| return prod; |} | |/// |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.bignum.fp: Fp; | import mir.math.common: approxEqual; | import mir.math.numeric: prod; | import mir.ndslice.topology: iota; | | static assert(is(typeof(factorial(33)) == Fp!128)); | static assert(is(typeof(factorial!256(33)) == Fp!256)); | static immutable double f33 = 8.68331761881188649551819440128e+36; | static assert(approxEqual(cast(double) factorial(33), f33)); | 1| assert(cast(double) factorial(0) == 1); 1| assert(cast(double) factorial(0, 100) == 1); 1| assert(cast(double) factorial(1, 100) == 100); 1| assert(approxEqual(cast(double) factorial(100, 1000), iota([100], 1000).prod!double)); |} | |/++ |Quickly computes binomial coefficient using extended |precision floating point type $(MREF mir,bignum,fp). | |Params: | n = number elements in the set | k = number elements in the subset |Returns: n choose k |Complexity: O(min(k, n - k)) |+/ |auto binomialCoefficient | (uint coefficientSize = 128) | (ulong n, uint k) | if (coefficientSize % (size_t.sizeof * 8) == 0 && coefficientSize >= (size_t.sizeof * 8)) 1| in (k <= n) |{ 1| if (k > n - k) 0000000| k = cast(uint)(n - k); 1| auto a = factorial!coefficientSize(k, n - k + 1); 1| auto b = factorial!coefficientSize(k); 1| return a / b; |} | |/// |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.bignum.fp: Fp; | import mir.math.common: approxEqual; | | static assert(is(typeof(binomialCoefficient(30, 18)) == Fp!128), typeof(binomialCoefficient(30, 18)).stringof); | static assert(cast(double) binomialCoefficient(30, 18) == 86493225); | 1| assert(approxEqual(cast(double) binomialCoefficient(100, 40), 1.374623414580281150126736972e+28)); |} source/mir/math/numeric.d is 92% covered <<<<<< EOF # path=source-mir-math-stat.lst |/++ |This module contains base statistical algorithms. | |Note that used specialized summing algorithms execute more primitive operations |than vanilla summation. Therefore, if in certain cases maximum speed is required |at expense of precision, one can use $(REF_ALTTEXT $(TT Summation.fast), Summation.fast, mir, math, sum)$(NBSP). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |Authors: Shigeki Karita (original numir code), Ilia Ki, John Michael Hall | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, math, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) |+/ |module mir.math.stat; | |import core.lifetime: move; |import mir.internal.utility: isFloatingPoint; |import mir.math.common: fmamath; |import mir.math.sum; |import mir.ndslice.slice: Slice, SliceKind, hasAsSlice; |import mir.primitives; |import std.traits: Unqual, isArray, isMutable, isIterable, isIntegral, CommonType; | |/// |public import mir.math.sum: Summation; | |/// |package(mir) |template statType(T, bool checkComplex = true) |{ | import mir.internal.utility: isFloatingPoint; | | static if (isFloatingPoint!T) { | import std.traits: Unqual; | alias statType = Unqual!T; | } else static if (is(T : double)) { | alias statType = double; | } else static if (checkComplex) { | import mir.internal.utility: isComplex; | static if (isComplex!T) { | static if (__traits(getAliasThis, T).length == 1) | { | alias statType = .statType!(typeof(__traits(getMember, T, __traits(getAliasThis, T)[0]))); | } | else | { | alias statType = Unqual!T; | } | } else { | static assert(0, "statType: type " ~ T.stringof ~ " must be convertible to a complex floating point type"); | } | } else { | static assert(0, "statType: type " ~ T.stringof ~ " must be convertible to a floating point type"); | } |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static assert(is(statType!int == double)); | static assert(is(statType!uint == double)); | static assert(is(statType!double == double)); | static assert(is(statType!float == float)); | static assert(is(statType!real == real)); | | static assert(is(statType!(const(int)) == double)); | static assert(is(statType!(immutable(int)) == double)); | static assert(is(statType!(const(double)) == double)); | static assert(is(statType!(immutable(double)) == double)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex: Complex; | | static assert(is(statType!(Complex!float) == Complex!float)); | static assert(is(statType!(Complex!double) == Complex!double)); | static assert(is(statType!(Complex!real) == Complex!real)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | float x; | alias x this; | } | | static assert(is(statType!Foo == double)); // note: this is not float |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex; | static struct Foo { | Complex!float x; | alias x this; | } | | static assert(is(statType!Foo == Complex!float)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | double x; | alias x this; | } | | static assert(is(statType!Foo == double)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex; | static struct Foo { | Complex!double x; | alias x this; | } | | static assert(is(statType!Foo == Complex!double)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | real x; | alias x this; | } | | static assert(is(statType!Foo == double)); // note: this is not real |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex; | static struct Foo { | Complex!real x; | alias x this; | } | | static assert(is(statType!Foo == Complex!real)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | int x; | alias x this; | } | | static assert(is(statType!Foo == double)); // note: this is not ints |} | |/// |package(mir) |template meanType(T) |{ | import mir.math.sum: sumType; | | alias U = sumType!T; | | static if (__traits(compiles, { | auto temp = U.init + U.init; | auto a = temp / 2; | temp += U.init; | })) { | alias V = typeof((U.init + U.init) / 2); | alias meanType = statType!V; | } else { | static assert(0, "meanType: Can't calculate mean of elements of type " ~ U.stringof); | } |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static assert(is(meanType!(int[]) == double)); | static assert(is(meanType!(double[]) == double)); | static assert(is(meanType!(float[]) == float)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex; | static assert(is(meanType!(Complex!float[]) == Complex!float)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | float x; | alias x this; | } | | static assert(is(meanType!(Foo[]) == float)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex; | static struct Foo { | Complex!float x; | alias x this; | } | | static assert(is(meanType!(Foo[]) == Complex!float)); |} | |/++ |Output range for mean. |+/ |struct MeanAccumulator(T, Summation summation) |{ | /// | size_t count; | /// | Summator!(T, summation) summator; | | /// | F mean(F = T)() const @safe @property pure nothrow @nogc | { 1468| return cast(F) summator.sum / cast(F) count; | } | | /// | F sum(F = T)() const @safe @property pure nothrow @nogc | { 1| return cast(F) summator.sum; | } | | /// | void put(Range)(Range r) | if (isIterable!Range) | { | static if (hasShape!Range) | { 189| count += r.elementCount; 189| summator.put(r); | } | else | { | foreach(x; r) | { | count++; | summator.put(x); | } | } | } | | /// | void put()(T x) | { 851| count++; 851| summator.put(x); | } | | /// | void put(F = T)(MeanAccumulator!(F, summation) m) | { 3| count += m.count; 3| summator.put(cast(T) m.summator); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| MeanAccumulator!(double, Summation.pairwise) x; 1| x.put([0.0, 1, 2, 3, 4].sliced); 1| assert(x.mean == 2); 1| x.put(5); 1| assert(x.mean == 2.5); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| MeanAccumulator!(float, Summation.pairwise) x; 1| x.put([0, 1, 2, 3, 4].sliced); 1| assert(x.mean == 2); 1| assert(x.sum == 10); 1| x.put(5); 1| assert(x.mean == 2.5); |} | |version(mir_test) |@safe pure nothrow |unittest |{ 1| double[] x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25]; 1| double[] y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; | 1| MeanAccumulator!(float, Summation.pairwise) m0; 1| m0.put(x); 1| MeanAccumulator!(float, Summation.pairwise) m1; 1| m1.put(y); 1| m0.put(m1); 1| assert(m0.mean == 29.25 / 12); |} | |/++ |Computes the mean of the input. | |By default, if `F` is not floating point type or complex type, then the result |will have a `double` type if `F` is implicitly convertible to a floating point |type or a type for which `isComplex!F` is true. | |Params: | F = controls type of output | summation = algorithm for calculating sums (default: Summation.appropriate) |Returns: | The mean of all the elements in the input, must be floating point or complex type | |See_also: | $(SUBREF sum, Summation) |+/ |template mean(F, Summation summation = Summation.appropriate) |{ | /++ | Params: | r = range, must be finite iterable | +/ | @fmamath meanType!F mean(Range)(Range r) | if (isIterable!Range) | { | alias G = typeof(return); 175| MeanAccumulator!(G, ResolveSummationType!(summation, Range, G)) mean; 172| mean.put(r.move); 172| return mean.mean; | } | | /++ | Params: | ar = values | +/ | @fmamath meanType!F mean(scope const F[] ar...) | { | alias G = typeof(return); 5| MeanAccumulator!(G, ResolveSummationType!(summation, const(G)[], G)) mean; 5| mean.put(ar); 5| return mean.mean; | } |} | |/// ditto |template mean(Summation summation = Summation.appropriate) |{ | /++ | Params: | r = range, must be finite iterable | +/ | @fmamath meanType!Range mean(Range)(Range r) | if (isIterable!Range) | { | alias F = typeof(return); 168| return .mean!(F, summation)(r.move); | } | | /++ | Params: | ar = values | +/ | @fmamath meanType!T mean(T)(scope const T[] ar...) | { | alias F = typeof(return); 1| return .mean!(F, summation)(ar); | } |} | |/// ditto |template mean(F, string summation) |{ | mixin("alias mean = .mean!(F, Summation." ~ summation ~ ");"); |} | |/// ditto |template mean(string summation) |{ | mixin("alias mean = .mean!(Summation." ~ summation ~ ");"); |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.complex; | alias C = Complex!double; | 1| assert(mean([1.0, 2, 3]) == 2); 1| assert(mean([C(1, 3), C(2), C(3)]) == C(2, 1)); | 1| assert(mean!float([0, 1, 2, 3, 4, 5].sliced(3, 2)) == 2.5); | | static assert(is(typeof(mean!float([1, 2, 3])) == float)); |} | |/// Mean of vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; 1| assert(x.mean == 29.25 / 12); |} | |/// Mean of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | 1| assert(x.mean == 29.25 / 12); |} | |/// Column mean of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: alongDim, byDim, map; | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; 1| auto result = [1, 4.25, 3.25, 1.5, 2.5, 2.125]; | | // Use byDim or alongDim with map to compute mean of row/column. 1| assert(x.byDim!1.map!mean.all!approxEqual(result)); 1| assert(x.alongDim!0.map!mean.all!approxEqual(result)); | | // FIXME | // Without using map, computes the mean of the whole slice | // assert(x.byDim!1.mean == x.sliced.mean); | // assert(x.alongDim!0.mean == x.sliced.mean); |} | |/// Can also set algorithm or output type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: repeat; | | //Set sum algorithm or output type | 1| auto a = [1, 1e100, 1, -1e100].sliced; | 1| auto x = a * 10_000; | 1| assert(x.mean!"kbn" == 20_000 / 4); 1| assert(x.mean!"kb2" == 20_000 / 4); 1| assert(x.mean!"precise" == 20_000 / 4); 1| assert(x.mean!(double, "precise") == 20_000.0 / 4); | 1| auto y = uint.max.repeat(3); 1| assert(y.mean!ulong == 12884901885 / 3); |} | |/++ |For integral slices, pass output type as template parameter to ensure output |type is correct. |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0, 1, 1, 2, 4, 4, | 2, 7, 5, 1, 2, 0].sliced; | 1| auto y = x.mean; 1| assert(y.approxEqual(29.0 / 12, 1.0e-10)); | static assert(is(typeof(y) == double)); | 1| assert(x.mean!float.approxEqual(29f / 12, 1.0e-10)); |} | |/++ |Mean works for complex numbers and other user-defined types (provided they |can be converted to a floating point or complex type) |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.complex.math: approxEqual; | import mir.ndslice.slice: sliced; | import mir.complex; | alias C = Complex!double; | 1| auto x = [C(1.0, 2), C(2, 3), C(3, 4), C(4, 5)].sliced; 1| assert(x.mean.approxEqual(C(2.5, 3.5))); |} | |/// Compute mean tensors along specified dimention of tensors |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice: alongDim, iota, as, map; | /++ | [[0,1,2], | [3,4,5]] | +/ 1| auto x = iota(2, 3).as!double; 1| assert(x.mean == (5.0 / 2.0)); | 1| auto m0 = [(0.0+3.0)/2.0, (1.0+4.0)/2.0, (2.0+5.0)/2.0]; 1| assert(x.alongDim!0.map!mean == m0); 1| assert(x.alongDim!(-2).map!mean == m0); | 1| auto m1 = [(0.0+1.0+2.0)/3.0, (3.0+4.0+5.0)/3.0]; 1| assert(x.alongDim!1.map!mean == m1); 1| assert(x.alongDim!(-1).map!mean == m1); | 1| assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!mean == iota([3, 4, 5], 3 * 4 * 5 / 2)); |} | |/// Arbitrary mean |version(mir_test) |@safe pure nothrow @nogc |unittest |{ 1| assert(mean(1.0, 2, 3) == 2); 1| assert(mean!float(1, 2, 3) == 2); |} | |version(mir_test) |@safe pure nothrow |unittest |{ 1| assert([1.0, 2, 3, 4].mean == 2.5); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.topology: iota, alongDim, map; | 1| auto x = iota([2, 2], 1); 1| auto y = x.alongDim!1.map!mean; 1| assert(y.all!approxEqual([1.5, 3.5])); | static assert(is(meanType!(typeof(y)) == double)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.ndslice.slice: sliced; | | static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; | 1| assert(x.sliced.mean == 29.25 / 12); 1| assert(x.sliced.mean!float == 29.25 / 12); |} | |/// |package(mir) |template hmeanType(T) |{ | import mir.math.sum: sumType; | | alias U = sumType!T; | | static if (__traits(compiles, { | U t = U.init + cast(U) 1; //added for when U.init = 0 | auto temp = cast(U) 1 / t + cast(U) 1 / t; | })) { | alias V = typeof(cast(U) 1 / ((cast(U) 1 / U.init + cast(U) 1 / U.init) / cast(U) 2)); | alias hmeanType = statType!V; | } else { | static assert(0, "hmeanType: Can't calculate hmean of elements of type " ~ U.stringof); | } |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex; | static assert(is(hmeanType!(int[]) == double)); | static assert(is(hmeanType!(double[]) == double)); | static assert(is(hmeanType!(float[]) == float)); | static assert(is(hmeanType!(Complex!float[]) == Complex!float)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.complex; | static struct Foo { | float x; | alias x this; | } | | static struct Bar { | Complex!float x; | alias x this; | } | | static assert(is(hmeanType!(Foo[]) == float)); | static assert(is(hmeanType!(Bar[]) == Complex!float)); |} | |/++ |Computes the harmonic mean of the input. | |By default, if `F` is not floating point type or complex type, then the result |will have a `double` type if `F` is implicitly convertible to a floating point |type or a type for which `isComplex!F` is true. | |Params: | F = controls type of output | summation = algorithm for calculating sums (default: Summation.appropriate) |Returns: | harmonic mean of all the elements of the input, must be floating point or complex type | |See_also: | $(SUBREF sum, Summation) |+/ |template hmean(F, Summation summation = Summation.appropriate) |{ | /++ | Params: | r = range | +/ | @fmamath hmeanType!F hmean(Range)(Range r) | if (isIterable!Range) | { | import mir.ndslice.topology: map; | | alias G = typeof(return); 18| auto numerator = cast(G) 1; | | static if (summation == Summation.fast && __traits(compiles, r.move.map!"numerator / a")) | { | return numerator / r.move.map!"numerator / a".mean!(G, summation); | } | else | { 20| MeanAccumulator!(G, ResolveSummationType!(summation, Range, G)) imean; 243| foreach (e; r) 69| imean.put(numerator / e); 18| return numerator / imean.mean; | } | } | | /++ | Params: | ar = values | +/ | @fmamath hmeanType!F hmean(scope const F[] ar...) | { | alias G = typeof(return); | 2| auto numerator = cast(G) 1; | | static if (summation == Summation.fast && __traits(compiles, ar.map!"numerator / a")) | { | return numerator / ar.map!"numerator / a".mean!(G, summation); | } | else | { 2| MeanAccumulator!(G, ResolveSummationType!(summation, const(G)[], G)) imean; 42| foreach (e; ar) 12| imean.put(numerator / e); 2| return numerator / imean.mean; | } | } |} | |/// ditto |template hmean(Summation summation = Summation.appropriate) |{ | /++ | Params: | r = range | +/ | @fmamath hmeanType!Range hmean(Range)(Range r) | if (isIterable!Range) | { | alias F = typeof(return); 14| return .hmean!(F, summation)(r.move); | } | | /++ | Params: | ar = values | +/ | @fmamath hmeanType!T hmean(T)(scope const T[] ar...) | { | alias F = typeof(return); 1| return .hmean!(F, summation)(ar); | } |} | |/// ditto |template hmean(F, string summation) |{ | mixin("alias hmean = .hmean!(F, Summation." ~ summation ~ ");"); |} | |/// ditto |template hmean(string summation) |{ | mixin("alias hmean = .hmean!(Summation." ~ summation ~ ");"); |} | |/// Harmonic mean of vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [20.0, 100.0, 2000.0, 10.0, 5.0, 2.0].sliced; | 1| assert(x.hmean.approxEqual(6.97269)); |} | |/// Harmonic mean of matrix |version(mir_test) |pure @safe |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | 1| auto x = [ | [20.0, 100.0, 2000.0], | [10.0, 5.0, 2.0] | ].fuse; | 1| assert(x.hmean.approxEqual(6.97269)); |} | |/// Column harmonic mean of matrix |version(mir_test) |pure @safe |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice: fuse; | import mir.ndslice.topology: alongDim, byDim, map; | 1| auto x = [ | [20.0, 100.0, 2000.0], | [ 10.0, 5.0, 2.0] | ].fuse; | 1| auto y = [13.33333, 9.52381, 3.996004]; | | // Use byDim or alongDim with map to compute mean of row/column. 1| assert(x.byDim!1.map!hmean.all!approxEqual(y)); 1| assert(x.alongDim!0.map!hmean.all!approxEqual(y)); |} | |/// Can also pass arguments to hmean |version(mir_test) |pure @safe nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.topology: repeat; | import mir.ndslice.slice: sliced; | | //Set sum algorithm or output type 1| auto x = [1, 1e-100, 1, -1e-100].sliced; | 1| assert(x.hmean!"kb2".approxEqual(2)); 1| assert(x.hmean!"precise".approxEqual(2)); 1| assert(x.hmean!(double, "precise").approxEqual(2)); | | //Provide the summation type 1| assert(float.max.repeat(3).hmean!double.approxEqual(float.max)); |} | |/++ |For integral slices, pass output type as template parameter to ensure output |type is correct. |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [20, 100, 2000, 10, 5, 2].sliced; | 1| auto y = x.hmean; | 1| assert(y.approxEqual(6.97269)); | static assert(is(typeof(y) == double)); | 1| assert(x.hmean!float.approxEqual(6.97269)); |} | |/++ |hmean works for complex numbers and other user-defined types (provided they |can be converted to a floating point or complex type) |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.complex.math: approxEqual; | import mir.ndslice.slice: sliced; | import mir.complex; | alias C = Complex!double; | 1| auto x = [C(1, 2), C(2, 3), C(3, 4), C(4, 5)].sliced; 1| assert(x.hmean.approxEqual(C(1.97110904, 3.14849332))); |} | |/// Arbitrary harmonic mean |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = hmean(20.0, 100, 2000, 10, 5, 2); 1| assert(x.approxEqual(6.97269)); | 1| auto y = hmean!float(20, 100, 2000, 10, 5, 2); 1| assert(y.approxEqual(6.97269)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | | static immutable x = [20.0, 100.0, 2000.0, 10.0, 5.0, 2.0]; | 1| assert(x.sliced.hmean.approxEqual(6.97269)); 1| assert(x.sliced.hmean!float.approxEqual(6.97269)); |} | |private |F nthroot(F)(in F x, in size_t n) | if (isFloatingPoint!F) |{ | import mir.math.common: sqrt, pow; | 54| if (n > 2) { 25| return pow(x, cast(F) 1 / cast(F) n); 29| } else if (n == 2) { 27| return sqrt(x); 2| } else if (n == 1) { 1| return x; | } else { 1| return cast(F) 1; | } |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | 1| assert(nthroot(9.0, 0).approxEqual(1)); 1| assert(nthroot(9.0, 1).approxEqual(9)); 1| assert(nthroot(9.0, 2).approxEqual(3)); 1| assert(nthroot(9.5, 2).approxEqual(3.08220700)); 1| assert(nthroot(9.0, 3).approxEqual(2.08008382)); |} | |/++ |Output range for gmean. |+/ |struct GMeanAccumulator(T) | if (isMutable!T && isFloatingPoint!T) |{ | import mir.math.numeric: ProdAccumulator; | | /// | size_t count; | /// | ProdAccumulator!T prodAccumulator; | | /// | F gmean(F = T)() const @property | if (isFloatingPoint!F) | { | import mir.math.common: exp2; | 49| return nthroot(cast(F) prodAccumulator.mantissa, count) * exp2(cast(F) prodAccumulator.exp / count); | } | | /// | void put(Range)(Range r) | if (isIterable!Range) | { | static if (hasShape!Range) | { 47| count += r.elementCount; 47| prodAccumulator.put(r); | } | else | { | foreach(x; r) | { | count++; | prodAccumulator.put(x); | } | } | } | | /// | void put()(T x) | { 2| count++; 2| prodAccumulator.put(x); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| GMeanAccumulator!double x; 1| x.put([1.0, 2, 3, 4].sliced); 1| assert(x.gmean.approxEqual(2.21336384)); 1| x.put(5); 1| assert(x.gmean.approxEqual(2.60517108)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| GMeanAccumulator!float x; 1| x.put([1, 2, 3, 4].sliced); 1| assert(x.gmean.approxEqual(2.21336384)); 1| x.put(5); 1| assert(x.gmean.approxEqual(2.60517108)); |} | |/// |package(mir) |template gmeanType(T) |{ | import mir.math.numeric: prodType; | | alias U = prodType!T; | | static if (__traits(compiles, { | auto temp = U.init * U.init; | auto a = nthroot(temp, 2); | temp *= U.init; | })) { | alias V = typeof(nthroot(U.init * U.init, 2)); | alias gmeanType = statType!(V, false); | } else { | static assert(0, "gmeanType: Can't calculate gmean of elements of type " ~ U.stringof); | } |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static assert(is(gmeanType!int == double)); | static assert(is(gmeanType!double == double)); | static assert(is(gmeanType!float == float)); | static assert(is(gmeanType!(int[]) == double)); | static assert(is(gmeanType!(double[]) == double)); | static assert(is(gmeanType!(float[]) == float)); |} | |/++ |Computes the geometric average of the input. | |By default, if `F` is not floating point type, then the result will have a |`double` type if `F` is implicitly convertible to a floating point type. | |Params: | r = range, must be finite iterable |Returns: | The geometric average of all the elements in the input, must be floating point type | |See_also: | $(SUBREF numeric, prod) |+/ |@fmamath gmeanType!F gmean(F, Range)(Range r) | if (isFloatingPoint!F && isIterable!Range) |{ | alias G = typeof(return); 43| GMeanAccumulator!G gmean; 43| gmean.put(r.move); 43| return gmean.gmean; |} | |/// ditto |@fmamath gmeanType!Range gmean(Range)(Range r) | if (isIterable!Range) |{ | alias G = typeof(return); 38| return .gmean!(G, Range)(r.move); |} | |/++ |Params: | ar = values |+/ |@fmamath gmeanType!F gmean(F)(scope const F[] ar...) | if (isFloatingPoint!F) |{ | alias G = typeof(return); 2| GMeanAccumulator!G gmean; 2| gmean.put(ar); 2| return gmean.gmean; |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| assert(gmean([1.0, 2, 3]).approxEqual(1.81712059)); | 1| assert(gmean!float([1, 2, 3, 4, 5, 6].sliced(3, 2)).approxEqual(2.99379516)); | | static assert(is(typeof(gmean!float([1, 2, 3])) == float)); |} | |/// Geometric mean of vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [3.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 2.0].sliced; | 1| assert(x.gmean.approxEqual(2.36178395)); |} | |/// Geometric mean of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | 1| auto x = [ | [3.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 2.0] | ].fuse; | 1| assert(x.gmean.approxEqual(2.36178395)); |} | |/// Column gmean of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: alongDim, byDim, map; | 1| auto x = [ | [3.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 2.0] | ].fuse; 1| auto result = [2.44948974, 2.73861278, 2.73861278, 1.41421356, 2.29128784, 2.91547594]; | | // Use byDim or alongDim with map to compute mean of row/column. 1| assert(x.byDim!1.map!gmean.all!approxEqual(result)); 1| assert(x.alongDim!0.map!gmean.all!approxEqual(result)); | | // FIXME | // Without using map, computes the mean of the whole slice | // assert(x.byDim!1.gmean.all!approxEqual(result)); | // assert(x.alongDim!0.gmean.all!approxEqual(result)); |} | |/// Can also set output type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: repeat; | 1| auto x = [5120.0, 7340032, 32, 3758096384].sliced; | 1| assert(x.gmean!float.approxEqual(259281.45295212)); | 1| auto y = uint.max.repeat(2); 1| assert(y.gmean!float.approxEqual(cast(float) uint.max)); |} | |/++ |For integral slices, pass output type as template parameter to ensure output |type is correct. |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [5, 1, 1, 2, 4, 4, | 2, 7, 5, 1, 2, 10].sliced; | 1| auto y = x.gmean; | static assert(is(typeof(y) == double)); | 1| assert(x.gmean!float.approxEqual(2.79160522)); |} | |/// gean works for user-defined types, provided the nth root can be taken for them |version(mir_test) |@safe pure nothrow |unittest |{ | static struct Foo { | float x; | alias x this; | } | | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [Foo(1.0), Foo(2.0), Foo(3.0)].sliced; 1| assert(x.gmean.approxEqual(1.81712059)); |} | |/// Compute gmean tensors along specified dimention of tensors |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: alongDim, iota, map; | 1| auto x = [ | [1.0, 2, 3], | [4.0, 5, 6] | ].fuse; | 1| assert(x.gmean.approxEqual(2.99379516)); | 1| auto result0 = [2.0, 3.16227766, 4.24264069]; 1| assert(x.alongDim!0.map!gmean.all!approxEqual(result0)); 1| assert(x.alongDim!(-2).map!gmean.all!approxEqual(result0)); | 1| auto result1 = [1.81712059, 4.93242414]; 1| assert(x.alongDim!1.map!gmean.all!approxEqual(result1)); 1| assert(x.alongDim!(-1).map!gmean.all!approxEqual(result1)); | 1| auto y = [ | [ | [1.0, 2, 3], | [4.0, 5, 6] | ], [ | [7.0, 8, 9], | [10.0, 9, 10] | ] | ].fuse; | 1| auto result3 = [ | [2.64575131, 4.0, 5.19615242], | [6.32455532, 6.70820393, 7.74596669] | ]; 1| assert(y.alongDim!0.map!gmean.all!approxEqual(result3)); |} | |/// Arbitrary gmean |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | 1| assert(gmean(1.0, 2, 3).approxEqual(1.81712059)); 1| assert(gmean!float(1, 2, 3).approxEqual(1.81712059)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | 1| assert([1.0, 2, 3, 4].gmean.approxEqual(2.21336384)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | 1| assert(gmean([1, 2, 3]).approxEqual(1.81712059)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | | static immutable x = [3.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 2.0]; | 1| assert(x.sliced.gmean.approxEqual(2.36178395)); 1| assert(x.sliced.gmean!float.approxEqual(2.36178395)); |} | |/++ |Computes the median of `slice`. | |By default, if `F` is not floating point type or complex type, then the result |will have a `double` type if `F` is implicitly convertible to a floating point |type or a type for which `isComplex!F` is true. | |Can also pass a boolean variable, `allowModify`, that allows the input slice to |be modified. By default, a reference-counted copy is made. | |Params: | F = output type | allowModify = Allows the input slice to be modified, default is false |Returns: | the median of the slice | |See_also: | $(SUBREF stat, mean) |+/ |template median(F, bool allowModify = false) |{ | /++ | Params: | slice = slice | +/ | @nogc | meanType!F median(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { | static assert (!allowModify || | isMutable!(slice.DeepElement), | "allowModify must be false or the input must be mutable"); | alias G = typeof(return); 82| size_t len = slice.elementCount; 82| assert(len > 0, "median: slice must have length greater than zero"); | | import mir.ndslice.topology: as, flattened; | | static if (!allowModify) { | import mir.ndslice.allocation: rcslice; | 41| if (len > 2) { 39| auto view = slice.lightScope; 78| auto val = view.as!(Unqual!(slice.DeepElement)).rcslice; 39| auto temp = val.lightScope.flattened; 39| return .median!(G, true)(temp); | } else { 2| return mean!G(slice); | } | } else { | import mir.ndslice.sorting: partitionAt; | 41| auto temp = slice.flattened; | 41| if (len > 5) { 36| size_t half_n = len / 2; 36| partitionAt(temp, half_n); 36| if (len % 2 == 1) { 4| return cast(G) temp[half_n]; | } else { | //move largest value in first half of slice to half_n - 1 32| partitionAt(temp[0 .. half_n], half_n - 1); 32| return (temp[half_n - 1] + temp[half_n]) / cast(G) 2; | } | } else { 5| return smallMedianImpl!(G)(temp); | } | } | } |} | |/// ditto |template median(bool allowModify = false) |{ | /// ditto | meanType!(Slice!(Iterator, N, kind)) | median(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { | static assert (!allowModify || | isMutable!(DeepElementType!(Slice!(Iterator, N, kind))), | "allowModify must be false or the input must be mutable"); | alias F = typeof(return); 18| return .median!(F, allowModify)(slice.move); | } |} | |/++ |Params: | ar = array |+/ |meanType!(T[]) median(T)(scope const T[] ar...) |{ | import mir.ndslice.slice: sliced; | | alias F = typeof(return); 3| return median!(F, false)(ar.sliced); |} | |/++ |Params: | withAsSlice = input that satisfies hasAsSlice |+/ |auto median(T)(T withAsSlice) | if (hasAsSlice!T) |{ 1| return median(withAsSlice.asSlice); |} | |/// Median of vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5].sliced; 1| assert(x0.median == 5); | 1| auto x1 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced; 1| assert(x1.median == 5); |} | |/// Median of dynamic array |version(mir_test) |@safe pure nothrow |unittest |{ 1| auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5]; 1| assert(x0.median == 5); | 1| auto x1 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10]; 1| assert(x1.median == 5); |} | |/// Median of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | 1| auto x0 = [ | [9.0, 1, 0, 2, 3], | [4.0, 6, 8, 7, 10] | ].fuse; | 1| assert(x0.median == 5); |} | |/// Row median of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: alongDim, byDim, map; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | 1| auto result = [1.75, 1.75].sliced; | | // Use byDim or alongDim with map to compute median of row/column. 1| assert(x.byDim!0.map!median.all!approxEqual(result)); 1| assert(x.alongDim!1.map!median.all!approxEqual(result)); |} | |/// Can allow original slice to be modified or set output type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5].sliced; 1| assert(x0.median!true == 5); | 1| auto x1 = [9, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced; 1| assert(x1.median!(float, true) == 5); |} | |/// Arbitrary median |version(mir_test) |@safe pure nothrow |unittest |{ 1| assert(median(0, 1, 2, 3, 4) == 2); |} | |// @nogc test |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.ndslice.slice: sliced; | | static immutable x = [9.0, 1, 0, 2, 3]; 1| assert(x.sliced.median == 2); |} | |// withAsSlice test |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | import mir.rc.array: RCArray; | | static immutable a = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5]; | 2| auto x = RCArray!double(11); 47| foreach(i, ref e; x) 11| e = a[i]; | 1| assert(x.median.approxEqual(5)); |} | |/++ |For integral slices, can pass output type as template parameter to ensure output |type is correct |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| auto x = [9, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced; 1| assert(x.median!float == 5f); | 1| auto y = x.median; 1| assert(y == 5.0); | static assert(is(typeof(y) == double)); |} | |// additional logic tests |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [3, 3, 2, 0, 2, 0].sliced; 1| assert(x.median!float.approxEqual(2)); | 1| x[] = [2, 2, 4, 0, 4, 3]; 1| assert(x.median!float.approxEqual(2.5)); 1| x[] = [1, 4, 5, 4, 4, 3]; 1| assert(x.median!float.approxEqual(4)); 1| x[] = [1, 5, 3, 5, 2, 2]; 1| assert(x.median!float.approxEqual(2.5)); 1| x[] = [4, 3, 2, 1, 4, 5]; 1| assert(x.median!float.approxEqual(3.5)); 1| x[] = [4, 5, 3, 5, 5, 4]; 1| assert(x.median!float.approxEqual(4.5)); 1| x[] = [3, 3, 3, 0, 0, 1]; 1| assert(x.median!float.approxEqual(2)); 1| x[] = [4, 2, 2, 1, 2, 5]; 1| assert(x.median!float.approxEqual(2)); 1| x[] = [2, 3, 1, 4, 5, 5]; 1| assert(x.median!float.approxEqual(3.5)); 1| x[] = [1, 1, 4, 5, 5, 5]; 1| assert(x.median!float.approxEqual(4.5)); 1| x[] = [2, 4, 0, 5, 1, 0]; 1| assert(x.median!float.approxEqual(1.5)); 1| x[] = [3, 5, 2, 5, 4, 2]; 1| assert(x.median!float.approxEqual(3.5)); 1| x[] = [3, 5, 4, 1, 4, 3]; 1| assert(x.median!float.approxEqual(3.5)); 1| x[] = [4, 2, 0, 3, 1, 3]; 1| assert(x.median!float.approxEqual(2.5)); 1| x[] = [100, 4, 5, 0, 5, 1]; 1| assert(x.median!float.approxEqual(4.5)); 1| x[] = [100, 5, 4, 0, 5, 1]; 1| assert(x.median!float.approxEqual(4.5)); 1| x[] = [100, 5, 4, 0, 1, 5]; 1| assert(x.median!float.approxEqual(4.5)); 1| x[] = [4, 5, 100, 1, 5, 0]; 1| assert(x.median!float.approxEqual(4.5)); 1| x[] = [0, 1, 2, 2, 3, 4]; 1| assert(x.median!float.approxEqual(2)); 1| x[] = [0, 2, 2, 3, 4, 5]; 1| assert(x.median!float.approxEqual(2.5)); |} | |// smallMedianImpl tests |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x0 = [9.0, 1, 0, 2, 3].sliced; 1| assert(x0.median.approxEqual(2)); | 1| auto x1 = [9.0, 1, 0, 2].sliced; 1| assert(x1.median.approxEqual(1.5)); | 1| auto x2 = [9.0, 0, 1].sliced; 1| assert(x2.median.approxEqual(1)); | 1| auto x3 = [1.0, 0].sliced; 1| assert(x3.median.approxEqual(0.5)); | 1| auto x4 = [1.0].sliced; 1| assert(x4.median.approxEqual(1)); |} | |// Check issue #328 fixed |version(mir_test) |@safe pure nothrow |unittest { | import mir.ndslice.topology: iota; | 1| auto x = iota(18); 1| auto y = median(x); 1| assert(y == 8.5); |} | |private pure @trusted nothrow @nogc |F smallMedianImpl(F, Iterator)(Slice!Iterator slice) |{ 13| size_t n = slice.elementCount; | 13| assert(n > 0, "smallMedianImpl: slice must have elementCount greater than 0"); 13| assert(n <= 5, "smallMedianImpl: slice must have elementCount of 5 or less"); | | import mir.functional: naryFun; | import mir.ndslice.sorting: medianOf; | import mir.utility: swapStars; | 13| auto sliceI0 = slice._iterator; | 13| if (n == 1) { 1| return cast(F) *sliceI0; | } | 12| auto sliceI1 = sliceI0; 12| ++sliceI1; | 12| if (n > 2) { 11| auto sliceI2 = sliceI1; 11| ++sliceI2; | alias less = naryFun!("a < b"); | 11| if (n == 3) { 2| medianOf!less(sliceI0, sliceI1, sliceI2); 2| return cast(F) *sliceI1; | } else { 9| auto sliceI3 = sliceI2; 9| ++sliceI3; 9| if (n == 4) { | // Put min in slice[0], lower median in slice[1] 5| medianOf!less(sliceI0, sliceI1, sliceI2, sliceI3); | // Ensure slice[2] < slice[3] 5| medianOf!less(sliceI2, sliceI3); 5| return cast(F) (*sliceI1 + *sliceI2) / cast(F) 2; | } else { 4| auto sliceI4 = sliceI3; 4| ++sliceI4; 4| medianOf!less(sliceI0, sliceI1, sliceI2, sliceI3, sliceI4); 4| return cast(F) *sliceI2; | } | } | } else { 1| return cast(F) (*sliceI0 + *sliceI1) / cast(F) 2; | } |} | |// smallMedianImpl tests |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x0 = [9.0, 1, 0, 2, 3].sliced; 1| assert(x0.smallMedianImpl!double.approxEqual(2)); | 1| auto x1 = [9.0, 1, 0, 2].sliced; 1| assert(x1.smallMedianImpl!double.approxEqual(1.5)); | 1| auto x2 = [9.0, 0, 1].sliced; 1| assert(x2.smallMedianImpl!double.approxEqual(1)); | 1| auto x3 = [1.0, 0].sliced; 1| assert(x3.smallMedianImpl!double.approxEqual(0.5)); | 1| auto x4 = [1.0].sliced; 1| assert(x4.smallMedianImpl!double.approxEqual(1)); | 1| auto x5 = [2.0, 1, 0, 9].sliced; 1| assert(x5.smallMedianImpl!double.approxEqual(1.5)); | 1| auto x6 = [1.0, 2, 0, 9].sliced; 1| assert(x6.smallMedianImpl!double.approxEqual(1.5)); | 1| auto x7 = [1.0, 0, 9, 2].sliced; 1| assert(x7.smallMedianImpl!double.approxEqual(1.5)); |} | |/++ |Centers `slice`, which must be a finite iterable. | |By default, `slice` is centered by the mean. A custom function may also be |provided using `centralTendency`. | |Returns: | The elements in the slice with the average subtracted from them. |+/ |template center(alias centralTendency = mean!(Summation.appropriate)) |{ | import mir.ndslice.slice: Slice, SliceKind, sliced, hasAsSlice; | /++ | Params: | slice = slice | +/ | auto center(Iterator, size_t N, SliceKind kind)( | Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; | import mir.ndslice.internal: LeftOp, ImplicitlyUnqual; | import mir.ndslice.topology: vmap; | 25| auto m = centralTendency(slice.lightScope); | alias T = typeof(m); 25| return slice.move.vmap(LeftOp!("-", ImplicitlyUnqual!T)(m)); | } | | /// ditto | auto center(T)(T[] array) | { 2| return center(array.sliced); | } | | /// ditto | auto center(T)(T withAsSlice) | if (hasAsSlice!T) | { 1| return center(withAsSlice.asSlice); | } |} | |/// Center vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [1.0, 2, 3, 4, 5, 6].sliced; 1| assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); | | // Can center using different functions 1| assert(x.center!hmean.all!approxEqual([-1.44898, -0.44898, 0.55102, 1.55102, 2.55102, 3.55102])); 1| assert(x.center!gmean.all!approxEqual([-1.99379516, -0.99379516, 0.00620483, 1.00620483, 2.00620483, 3.00620483])); 1| assert(x.center!median.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); |} | |/// Center dynamic array |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | 1| auto x = [1.0, 2, 3, 4, 5, 6]; 1| assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); |} | |/// Center matrix |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice: fuse; | 1| auto x = [ | [0.0, 1, 2], | [3.0, 4, 5] | ].fuse; | 1| auto y = [ | [-2.5, -1.5, -0.5], | [ 0.5, 1.5, 2.5] | ].fuse; | 1| assert(x.center.all!approxEqual(y)); |} | |/// Column center matrix |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all, equal; | import mir.math.common: approxEqual; | import mir.ndslice: fuse; | import mir.ndslice.topology: alongDim, byDim, map; | 1| auto x = [ | [20.0, 100.0, 2000.0], | [10.0, 5.0, 2.0] | ].fuse; | 1| auto result = [ | [ 5.0, 47.5, 999], | [-5.0, -47.5, -999] | ].fuse; | | // Use byDim with map to compute average of row/column. 1| auto xCenterByDim = x.byDim!1.map!center; 1| auto resultByDim = result.byDim!1; 1| assert(xCenterByDim.equal!(equal!approxEqual)(resultByDim)); | 1| auto xCenterAlongDim = x.alongDim!0.map!center; 1| auto resultAlongDim = result.alongDim!0; 1| assert(xCenterByDim.equal!(equal!approxEqual)(resultAlongDim)); |} | |/// Can also pass arguments to average function used by center |version(mir_test) |pure @safe nothrow |unittest |{ | import mir.ndslice.slice: sliced; | | //Set sum algorithm or output type 1| auto a = [1, 1e100, 1, -1e100]; | 1| auto x = a.sliced * 10_000; | | //Due to Floating Point precision, subtracting the mean from the second | //and fourth numbers in `x` does not change the value of the result 1| auto result = [5000, 1e104, 5000, -1e104].sliced; | 1| assert(x.center!(mean!"kbn") == result); 1| assert(x.center!(mean!"kb2") == result); 1| assert(x.center!(mean!"precise") == result); |} | |/++ |Passing a centered input to `variance` or `standardDeviation` with the |`assumeZeroMean` algorithm is equivalent to calculating `variance` or |`standardDeviation` on the original input. |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [1.0, 2, 3, 4, 5, 6].sliced; 1| assert(x.center.variance!"assumeZeroMean".approxEqual(x.variance)); 1| assert(x.center.standardDeviation!"assumeZeroMean".approxEqual(x.standardDeviation)); |} | |// dynamic array test |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | 1| double[] x = [1.0, 2, 3, 4, 5, 6]; | 1| assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); |} | |// withAsSlice test |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.rc.array: RCArray; | | static immutable a = [1.0, 2, 3, 4, 5, 6]; | static immutable result = [-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]; | 2| auto x = RCArray!double(6); 27| foreach(i, ref e; x) 6| e = a[i]; | 1| assert(x.center.all!approxEqual(result)); |} | |/++ |Output range that applies function `fun` to each input before summing |+/ |struct MapSummator(alias fun, T, Summation summation) | if(isMutable!T) |{ | /// | Summator!(T, summation) summator; | | /// | F sum(F = T)() const @property | { 8| return cast(F) summator.sum; | } | | /// | void put(Range)(Range r) | if (isIterable!Range) | { | import mir.ndslice.topology: map; 5| summator.put(r.map!fun); | } | | /// | void put()(T x) | { 3| summator.put(fun(x)); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: powi; | import mir.ndslice.slice: sliced; | | alias f = (double x) => (powi(x, 2)); 1| MapSummator!(f, double, Summation.pairwise) x; 1| x.put([0.0, 1, 2, 3, 4].sliced); 1| assert(x.sum == 30.0); 1| x.put(5); 1| assert(x.sum == 55.0); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | | alias f = (double x) => (x + 1); 1| MapSummator!(f, double, Summation.pairwise) x; 1| x.put([0.0, 1, 2, 3, 4].sliced); 1| assert(x.sum == 15.0); 1| x.put(5); 1| assert(x.sum == 21.0); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.ndslice.slice: sliced; | | alias f = (double x) => (x + 1); 1| MapSummator!(f, double, Summation.pairwise) x; | static immutable a = [0.0, 1, 2, 3, 4]; 1| x.put(a.sliced); 1| assert(x.sum == 15.0); 1| x.put(5); 1| assert(x.sum == 21.0); |} | |version(mir_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | import mir.ndslice.slice: sliced; | | alias f = (double x) => (x + 1); 1| MapSummator!(f, double, Summation.pairwise) x; 1| auto a = [ | [0.0, 1, 2], | [3.0, 4, 5] | ].fuse; 1| auto b = [6.0, 7, 8].sliced; 1| x.put(a); 1| assert(x.sum == 21.0); 1| x.put(b); 1| assert(x.sum == 45.0); |} | |/++ |Variance algorithms. | |See Also: | $(WEB en.wikipedia.org/wiki/Algorithms_for_calculating_variance, Algorithms for calculating variance). |+/ |enum VarianceAlgo |{ | /++ | Performs Welford's online algorithm for updating variance. Can also `put` | another VarianceAccumulator of the same type, which uses the parallel | algorithm from Chan et al., described above. | +/ | online, | | /++ | Calculates variance using E(x^^2) - E(x)^2 (alowing for adjustments for | population/sample variance). This algorithm can be numerically unstable. | +/ | naive, | | /++ | Calculates variance using a two-pass algorithm whereby the input is first | centered and then the sum of squares is calculated from that. | +/ | twoPass, | | /++ | Calculates variance assuming the mean of the dataseries is zero. | +/ | assumeZeroMean |} | |/// |struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) | if (isMutable!T && varianceAlgo == VarianceAlgo.naive) |{ | import mir.functional: naryFun; | | /// 2| this(Range)(Range r) | if (isIterable!Range) | { | import core.lifetime: move; 2| this.put(r.move); | } | | /// | this()(T x) | { | this.put(x); | } | | /// | MeanAccumulator!(T, summation) meanAccumulator; | | /// | Summator!(T, summation) sumOfSquares; | | /// | void put(Range)(Range r) | if (isIterable!Range) | { 114| foreach(x; r) | { 36| this.put(x); | } | } | | /// | void put()(T x) | { 37| meanAccumulator.put(x); 37| sumOfSquares.put(x * x); | } | |const: | | /// | size_t count() @property | { 30| return meanAccumulator.count; | } | | /// | F mean(F = T)() const @property | { | return meanAccumulator.mean; | } | | /// | F variance(F = T)(bool isPopulation) @property | { 10| return cast(F) sumOfSquares.sum / (count + isPopulation - 1) - | (cast(F) meanAccumulator.mean) ^^ 2 * (cast(F) count / (count + isPopulation - 1)); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| VarianceAccumulator!(double, VarianceAlgo.naive, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); | 1| v.put(4.0); 1| assert(v.variance(PopulationTrueRT).approxEqual(57.01923 / 13)); 1| assert(v.variance(PopulationTrueCT).approxEqual(57.01923 / 13)); 1| assert(v.variance(PopulationFalseRT).approxEqual(57.01923 / 12)); 1| assert(v.variance(PopulationFalseCT).approxEqual(57.01923 / 12)); |} | |/// |struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) | if (isMutable!T && | varianceAlgo == VarianceAlgo.online) |{ | /// 213| this(Range)(Range r) | if (isIterable!Range) | { | import core.lifetime: move; 213| this.put(r.move); | } | | /// | this()(T x) | { | this.put(x); | } | | /// | MeanAccumulator!(T, summation) meanAccumulator; | | /// | Summator!(T, summation) centeredSumOfSquares; | | /// | void put(Range)(Range r) | if (isIterable!Range) | { 2719| foreach(x; r) | { 745| this.put(x); | } | } | | /// | void put()(T x) | { 733| T delta = x; 733| if (count > 0) { 512| delta -= meanAccumulator.mean; | } 733| meanAccumulator.put(x); 733| centeredSumOfSquares.put(delta * (x - meanAccumulator.mean)); | } | | /// | void put()(VarianceAccumulator!(T, varianceAlgo, summation) v) | { 2| size_t oldCount = count; 2| T delta = v.mean; 2| if (oldCount > 0) { 2| delta -= meanAccumulator.mean; | } 2| meanAccumulator.put!T(v.meanAccumulator); 2| centeredSumOfSquares.put(v.centeredSumOfSquares.sum + delta * delta * v.count * oldCount / count); | } | |const: | | /// | size_t count() @property | { 982| return meanAccumulator.count; | } | | /// | F mean(F = T)() const @property | { 2| return meanAccumulator.mean; | } | | /// | F variance(F = T)(bool isPopulation) @property | { 243| return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; 1| v.put(x); | 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); | 1| v.put(4.0); 1| assert(v.variance(PopulationTrueRT).approxEqual(57.01923 / 13)); 1| assert(v.variance(PopulationTrueCT).approxEqual(57.01923 / 13)); 1| assert(v.variance(PopulationFalseRT).approxEqual(57.01923 / 12)); 1| assert(v.variance(PopulationFalseCT).approxEqual(57.01923 / 12)); |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced; 1| auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(PopulationTrueRT).approxEqual(12.55208 / 6)); 1| assert(v.variance(PopulationTrueCT).approxEqual(12.55208 / 6)); 1| assert(v.variance(PopulationFalseRT).approxEqual(12.55208 / 5)); 1| assert(v.variance(PopulationFalseCT).approxEqual(12.55208 / 5)); | 1| v.put(y); 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced; 1| auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(PopulationTrueRT).approxEqual(12.55208 / 6)); 1| assert(v.variance(PopulationTrueCT).approxEqual(12.55208 / 6)); 1| assert(v.variance(PopulationFalseRT).approxEqual(12.55208 / 5)); 1| assert(v.variance(PopulationFalseCT).approxEqual(12.55208 / 5)); | 1| VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) w; 1| w.put(y); 1| v.put(w); 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.complex.math: approxEqual; | import mir.ndslice.slice: sliced; | import mir.complex: Complex; | 1| auto x = [Complex!double(1.0, 3), Complex!double(2), Complex!double(3)].sliced; | 1| VarianceAccumulator!(Complex!double, VarianceAlgo.online, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(true).approxEqual(Complex!double(-4.0, -6) / 3)); 1| assert(v.variance(false).approxEqual(Complex!double(-4.0, -6) / 2)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | 1| VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(false).approxEqual(54.76562 / 11)); | 1| v.put(4.0); 1| assert(v.variance(false).approxEqual(57.01923 / 12)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced; 1| auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | 1| VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(false).approxEqual(12.55208 / 5)); | 1| VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) w; 1| w.put(y); 1| v.put(w); 1| assert(v.variance(false).approxEqual(54.76562 / 11)); |} | |/// |struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) | if (isMutable!T && varianceAlgo == VarianceAlgo.twoPass) |{ | import mir.functional: naryFun; | import mir.ndslice.slice: Slice, SliceKind, hasAsSlice; | | /// | MeanAccumulator!(T, summation) meanAccumulator; | | /// | Summator!(T, summation) centeredSumOfSquares; | | /// 5| this(Iterator, size_t N, SliceKind kind)( | Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; | import mir.ndslice.internal: LeftOp; | import mir.ndslice.topology: vmap, map; | 5| meanAccumulator.put(slice.lightScope); 5| centeredSumOfSquares.put(slice.move.vmap(LeftOp!("-", T)(meanAccumulator.mean)).map!(naryFun!"a * a")); | } | | /// 1| this(U)(U[] array) | { | import mir.ndslice.slice: sliced; 1| this(array.sliced); | } | | /// 1| this(T)(T withAsSlice) | if (hasAsSlice!T) | { 1| this(withAsSlice.asSlice); | } | | /// | this()(T x) | { | meanAccumulator.put(x); | centeredSumOfSquares.put(cast(T) 0); | } | |const: | | /// | size_t count() @property | { 6| return meanAccumulator.count; | } | | /// | F mean(F = T)() const @property | { | return meanAccumulator.mean; | } | | /// | F variance(F = T)(bool isPopulation) @property | { 6| return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x); 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); |} | |// dynamic array test |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.rc.array: RCArray; | 1| double[] x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; | 1| auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x); 1| assert(v.centeredSumOfSquares.sum.approxEqual(54.76562)); |} | |// withAsSlice test |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | import mir.rc.array: RCArray; | | static immutable a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; | 2| auto x = RCArray!double(12); 51| foreach(i, ref e; x) 12| e = a[i]; | 1| auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x); 1| assert(v.centeredSumOfSquares.sum.approxEqual(54.76562)); |} | |/// |struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) | if (isMutable!T && varianceAlgo == VarianceAlgo.assumeZeroMean) |{ | import mir.ndslice.slice: Slice, SliceKind, hasAsSlice; | | private size_t _count; | | /// | Summator!(T, summation) centeredSumOfSquares; | | /// 3| this(Range)(Range r) | if (isIterable!Range) | { 3| this.put(r); | } | | /// | this()(T x) | { | this.put(x); | } | | /// | void put(Range)(Range r) | if (isIterable!Range) | { 285| foreach(x; r) | { 87| this.put(x); | } | } | | /// | void put()(T x) | { 89| _count++; 89| centeredSumOfSquares.put(x * x); | } | | /// | void put()(VarianceAccumulator!(T, varianceAlgo, summation) v) | { 2| _count += v.count; 2| centeredSumOfSquares.put(v.centeredSumOfSquares.sum); | } | |const: | | /// | size_t count() @property | { 35| return _count; | } | | /// | F mean(F = T)() const @property | { | return cast(F) 0; | } | | /// | F variance(F = T)(bool isPopulation) @property | { 33| return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1); | } |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; 1| auto x = a.center; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; 1| v.put(x); | 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); | 1| v.put(4.0); 1| assert(v.variance(PopulationTrueRT).approxEqual(70.76562 / 13)); 1| assert(v.variance(PopulationTrueCT).approxEqual(70.76562 / 13)); 1| assert(v.variance(PopulationFalseRT).approxEqual(70.76562 / 12)); 1| assert(v.variance(PopulationFalseCT).approxEqual(70.76562 / 12)); |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; 1| auto b = a.center; 1| auto x = b[0 .. 6]; 1| auto y = b[6 .. $]; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(PopulationTrueRT).approxEqual(13.492188 / 6)); 1| assert(v.variance(PopulationTrueCT).approxEqual(13.492188 / 6)); 1| assert(v.variance(PopulationFalseRT).approxEqual(13.492188 / 5)); 1| assert(v.variance(PopulationFalseCT).approxEqual(13.492188 / 5)); | 1| v.put(y); 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; 1| auto b = a.center; 1| auto x = b[0 .. 6]; 1| auto y = b[6 .. $]; | | enum PopulationTrueCT = true; | enum PopulationFalseCT = false; 1| bool PopulationTrueRT = true; 1| bool PopulationFalseRT = false; | 1| VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(PopulationTrueRT).approxEqual(13.492188 / 6)); 1| assert(v.variance(PopulationTrueCT).approxEqual(13.492188 / 6)); 1| assert(v.variance(PopulationFalseRT).approxEqual(13.492188 / 5)); 1| assert(v.variance(PopulationFalseCT).approxEqual(13.492188 / 5)); | 1| VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) w; 1| w.put(y); 1| v.put(w); 1| assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); 1| assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); 1| assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.complex.math: approxEqual; | import mir.ndslice.slice: sliced; | import mir.complex: Complex; | 1| auto a = [Complex!double(1.0, 3), Complex!double(2), Complex!double(3)].sliced; 1| auto x = a.center; | 1| VarianceAccumulator!(Complex!double, VarianceAlgo.assumeZeroMean, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(true).approxEqual(Complex!double(-4.0, -6) / 3)); 1| assert(v.variance(false).approxEqual(Complex!double(-4.0, -6) / 2)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; 1| auto x = a.center; | 1| VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(false).approxEqual(54.76562 / 11)); | 1| v.put(4.0); 1| assert(v.variance(false).approxEqual(70.76562 / 12)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; 1| auto b = a.center; 1| auto x = b[0 .. 6]; 1| auto y = b[6 .. $]; | 1| VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; 1| v.put(x); 1| assert(v.variance(false).approxEqual(13.492188 / 5)); | 1| VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) w; 1| w.put(y); 1| v.put(w); 1| assert(v.variance(false).approxEqual(54.76562 / 11)); |} | |/++ |Calculates the variance of the input | |By default, if `F` is not floating point type or complex type, then the result |will have a `double` type if `F` is implicitly convertible to a floating point |type or a type for which `isComplex!F` is true. | |Params: | F = controls type of output | varianceAlgo = algorithm for calculating variance (default: VarianceAlgo.online) | summation = algorithm for calculating sums (default: Summation.appropriate) |Returns: | The variance of the input, must be floating point or complex type |+/ |template variance( | F, | VarianceAlgo varianceAlgo = VarianceAlgo.online, | Summation summation = Summation.appropriate) |{ | /++ | Params: | r = range, must be finite iterable | isPopulation = true if population variance, false if sample variance (default) | +/ | @fmamath meanType!F variance(Range)(Range r, bool isPopulation = false) | if (isIterable!Range) | { | import core.lifetime: move; | | alias G = typeof(return); 222| auto varianceAccumulator = VarianceAccumulator!(G, varianceAlgo, ResolveSummationType!(summation, Range, G))(r.move); 216| return varianceAccumulator.variance(isPopulation); | } | | /++ | Params: | ar = values | +/ | @fmamath meanType!F variance(scope const F[] ar...) | { | alias G = typeof(return); 4| auto varianceAccumulator = VarianceAccumulator!(G, varianceAlgo, ResolveSummationType!(summation, const(G)[], G))(ar); 4| return varianceAccumulator.variance(false); | } |} | |/// ditto |template variance( | VarianceAlgo varianceAlgo = VarianceAlgo.online, | Summation summation = Summation.appropriate) |{ | /++ | Params: | r = range, must be finite iterable | isPopulation = true if population variance, false if sample variance (default) | +/ | @fmamath meanType!Range variance(Range)(Range r, bool isPopulation = false) | if(isIterable!Range) | { | import core.lifetime: move; | | alias F = typeof(return); 103| return .variance!(F, varianceAlgo, summation)(r.move, isPopulation); | } | | /++ | Params: | ar = values | +/ | @fmamath meanType!T variance(T)(scope const T[] ar...) | { | alias F = typeof(return); 1| return .variance!(F, varianceAlgo, summation)(ar); | } |} | |/// ditto |template variance(F, string varianceAlgo, string summation = "appropriate") |{ | mixin("alias variance = .variance!(F, VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); |} | |/// ditto |template variance(string varianceAlgo, string summation = "appropriate") |{ | mixin("alias variance = .variance!(VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.complex.math: capproxEqual = approxEqual; | import mir.ndslice.slice: sliced; | import mir.complex; | alias C = Complex!double; | 1| assert(variance([1.0, 2, 3]).approxEqual(2.0 / 2)); 1| assert(variance([1.0, 2, 3], true).approxEqual(2.0 / 3)); | 1| assert(variance([C(1, 3), C(2), C(3)]).capproxEqual(C(-4, -6) / 2)); | 1| assert(variance!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).approxEqual(17.5 / 5)); | | static assert(is(typeof(variance!float([1, 2, 3])) == float)); |} | |/// Variance of vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | 1| assert(x.variance.approxEqual(54.76562 / 11)); |} | |/// Variance of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | 1| assert(x.variance.approxEqual(54.76562 / 11)); |} | |/// Column variance of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: alongDim, byDim, map; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0], | [3.5, 4.25, 2.0, 7.5], | [5.0, 1.0, 1.5, 0.0] | ].fuse; 1| auto result = [13.16667 / 2, 7.041667 / 2, 0.1666667 / 2, 30.16667 / 2]; | | // Use byDim or alongDim with map to compute variance of row/column. 1| assert(x.byDim!1.map!variance.all!approxEqual(result)); 1| assert(x.alongDim!0.map!variance.all!approxEqual(result)); | | // FIXME | // Without using map, computes the variance of the whole slice | // assert(x.byDim!1.variance == x.sliced.variance); | // assert(x.alongDim!0.variance == x.sliced.variance); |} | |/// Can also set algorithm type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | 1| auto x = a + 1_000_000_000; | 1| auto y = x.variance; 1| assert(y.approxEqual(54.76562 / 11)); | | // The naive algorithm is numerically unstable in this case 1| auto z0 = x.variance!"naive"; 1| assert(!z0.approxEqual(y)); | | // But the two-pass algorithm provides a consistent answer 1| auto z1 = x.variance!"twoPass"; 1| assert(z1.approxEqual(y)); | | // And the assumeZeroMean algorithm is way off 1| auto z2 = x.variance!"assumeZeroMean"; 1| assert(z2.approxEqual(1.2e19 / 11)); |} | |/// Can also set algorithm or output type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: repeat; | | //Set population variance, variance algorithm, sum algorithm or output type | 1| auto a = [1.0, 1e100, 1, -1e100].sliced; 1| auto x = a * 10_000; | 1| bool populationTrueRT = true; 1| bool populationFalseRT = false; | enum PopulationTrueCT = true; | | /++ | Due to Floating Point precision, when centering `x`, subtracting the mean | from the second and fourth numbers has no effect. Further, after centering | and squaring `x`, the first and third numbers in the slice have precision | too low to be included in the centered sum of squares. | +/ 1| assert(x.variance(populationFalseRT).approxEqual(2.0e208 / 3)); 1| assert(x.variance(populationTrueRT).approxEqual(2.0e208 / 4)); 1| assert(x.variance(PopulationTrueCT).approxEqual(2.0e208 / 4)); | 1| assert(x.variance!("online").approxEqual(2.0e208 / 3)); 1| assert(x.variance!("online", "kbn").approxEqual(2.0e208 / 3)); 1| assert(x.variance!("online", "kb2").approxEqual(2.0e208 / 3)); 1| assert(x.variance!("online", "precise").approxEqual(2.0e208 / 3)); 1| assert(x.variance!(double, "online", "precise").approxEqual(2.0e208 / 3)); 1| assert(x.variance!(double, "online", "precise")(populationTrueRT).approxEqual(2.0e208 / 4)); | 1| auto y = uint.max.repeat(3); 1| auto z = y.variance!ulong; 1| assert(z == 0.0); | static assert(is(typeof(z) == double)); |} | |/++ |For integral slices, pass output type as template parameter to ensure output |type is correct. |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [0, 1, 1, 2, 4, 4, | 2, 7, 5, 1, 2, 0].sliced; | 1| auto y = x.variance; 1| assert(y.approxEqual(50.91667 / 11)); | static assert(is(typeof(y) == double)); | 1| assert(x.variance!float.approxEqual(50.91667 / 11)); |} | |/++ |Variance works for complex numbers and other user-defined types (provided they |can be converted to a floating point or complex type) |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.complex.math: approxEqual; | import mir.ndslice.slice: sliced; | import mir.complex; | alias C = Complex!double; | 1| auto x = [C(1, 2), C(2, 3), C(3, 4), C(4, 5)].sliced; 1| assert(x.variance.approxEqual((C(0, 10)) / 3)); |} | |/// Compute variance along specified dimention of tensors |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: as, iota, alongDim, map, repeat; | 1| auto x = [ | [0.0, 1, 2], | [3.0, 4, 5] | ].fuse; | 1| assert(x.variance.approxEqual(17.5 / 5)); | 1| auto m0 = [4.5, 4.5, 4.5]; 1| assert(x.alongDim!0.map!variance.all!approxEqual(m0)); 1| assert(x.alongDim!(-2).map!variance.all!approxEqual(m0)); | 1| auto m1 = [1.0, 1.0]; 1| assert(x.alongDim!1.map!variance.all!approxEqual(m1)); 1| assert(x.alongDim!(-1).map!variance.all!approxEqual(m1)); | 1| assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!variance.all!approxEqual(repeat(3600.0 / 2, 3, 4, 5))); |} | |/// Arbitrary variance |version(mir_test) |@safe pure nothrow @nogc |unittest |{ 1| assert(variance(1.0, 2, 3) == 1.0); 1| assert(variance!float(1, 2, 3) == 1f); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | 1| assert([1.0, 2, 3, 4].variance.approxEqual(5.0 / 3)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | import mir.ndslice.topology: iota, alongDim, map; | 1| auto x = iota([2, 2], 1); 1| auto y = x.alongDim!1.map!variance; 1| assert(y.all!approxEqual([0.5, 0.5])); | static assert(is(meanType!(typeof(y)) == double)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | | static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; | 1| assert(x.sliced.variance.approxEqual(54.76562 / 11)); 1| assert(x.sliced.variance!float.approxEqual(54.76562 / 11)); |} | |/// |package(mir) |template stdevType(T) |{ | import mir.internal.utility: isFloatingPoint; | | alias U = meanType!T; | | static if (isFloatingPoint!U) { | alias stdevType = U; | } else { | static assert(0, "stdevType: Can't calculate standard deviation of elements of type " ~ U.stringof); | } |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static assert(is(stdevType!(int[]) == double)); | static assert(is(stdevType!(double[]) == double)); | static assert(is(stdevType!(float[]) == float)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | float x; | alias x this; | } | | static assert(is(stdevType!(Foo[]) == float)); |} | |/++ |Calculates the standard deviation of the input | |By default, if `F` is not floating point type, then the result will have a |`double` type if `F` is implicitly convertible to a floating point type. | |Params: | F = controls type of output | varianceAlgo = algorithm for calculating variance (default: VarianceAlgo.online) | summation = algorithm for calculating sums (default: Summation.appropriate) |Returns: | The standard deviation of the input, must be floating point type type |+/ |template standardDeviation( | F, | VarianceAlgo varianceAlgo = VarianceAlgo.online, | Summation summation = Summation.appropriate) |{ | import mir.math.common: sqrt; | | /++ | Params: | r = range, must be finite iterable | isPopulation = true if population standard deviation, false if sample standard deviation (default) | +/ | @fmamath stdevType!F standardDeviation(Range)(Range r, bool isPopulation = false) | if (isIterable!Range) | { | import core.lifetime: move; | alias G = typeof(return); 107| return r.move.variance!(G, varianceAlgo, ResolveSummationType!(summation, Range, G))(isPopulation).sqrt; | } | | /++ | Params: | ar = values | +/ | @fmamath stdevType!F standardDeviation(scope const F[] ar...) | { | alias G = typeof(return); 2| return ar.variance!(G, varianceAlgo, ResolveSummationType!(summation, const(G)[], G)).sqrt; | } |} | |/// ditto |template standardDeviation( | VarianceAlgo varianceAlgo = VarianceAlgo.online, | Summation summation = Summation.appropriate) |{ | /++ | Params: | r = range, must be finite iterable | isPopulation = true if population standard deviation, false if sample standard deviation (default) | +/ | @fmamath stdevType!Range standardDeviation(Range)(Range r, bool isPopulation = false) | if(isIterable!Range) | { | import core.lifetime: move; | | alias F = typeof(return); 101| return .standardDeviation!(F, varianceAlgo, summation)(r.move, isPopulation); | } | | /++ | Params: | ar = values | +/ | @fmamath stdevType!T standardDeviation(T)(scope const T[] ar...) | { | alias F = typeof(return); 1| return .standardDeviation!(F, varianceAlgo, summation)(ar); | } |} | |/// ditto |template standardDeviation(F, string varianceAlgo, string summation = "appropriate") |{ | mixin("alias standardDeviation = .standardDeviation!(F, VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); |} | |/// ditto |template standardDeviation(string varianceAlgo, string summation = "appropriate") |{ | mixin("alias standardDeviation = .standardDeviation!(VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.slice: sliced; | 1| assert(standardDeviation([1.0, 2, 3]).approxEqual(sqrt(2.0 / 2))); 1| assert(standardDeviation([1.0, 2, 3], true).approxEqual(sqrt(2.0 / 3))); | 1| assert(standardDeviation!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).approxEqual(sqrt(17.5 / 5))); | | static assert(is(typeof(standardDeviation!float([1, 2, 3])) == float)); |} | |/// Standard deviation of vector |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.slice: sliced; | 1| auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | 1| assert(x.standardDeviation.approxEqual(sqrt(54.76562 / 11))); |} | |/// Standard deviation of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.fuse: fuse; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | 1| assert(x.standardDeviation.approxEqual(sqrt(54.76562 / 11))); |} | |/// Column standard deviation of matrix |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: alongDim, byDim, map; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0], | [3.5, 4.25, 2.0, 7.5], | [5.0, 1.0, 1.5, 0.0] | ].fuse; 1| auto result = [13.16667 / 2, 7.041667 / 2, 0.1666667 / 2, 30.16667 / 2].map!sqrt; | | // Use byDim or alongDim with map to compute standardDeviation of row/column. 1| assert(x.byDim!1.map!standardDeviation.all!approxEqual(result)); 1| assert(x.alongDim!0.map!standardDeviation.all!approxEqual(result)); | | // FIXME | // Without using map, computes the standardDeviation of the whole slice | // assert(x.byDim!1.standardDeviation == x.sliced.standardDeviation); | // assert(x.alongDim!0.standardDeviation == x.sliced.standardDeviation); |} | |/// Can also set algorithm type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.slice: sliced; | 1| auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; | 1| auto x = a + 1_000_000_000; | 1| auto y = x.standardDeviation; 1| assert(y.approxEqual(sqrt(54.76562 / 11))); | | // The naive algorithm is numerically unstable in this case 1| auto z0 = x.standardDeviation!"naive"; 1| assert(!z0.approxEqual(y)); | | // But the two-pass algorithm provides a consistent answer 1| auto z1 = x.standardDeviation!"twoPass"; 1| assert(z1.approxEqual(y)); |} | |/// Can also set algorithm or output type |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: repeat; | | //Set population standard deviation, standardDeviation algorithm, sum algorithm or output type | 1| auto a = [1.0, 1e100, 1, -1e100].sliced; 1| auto x = a * 10_000; | 1| bool populationTrueRT = true; 1| bool populationFalseRT = false; | enum PopulationTrueCT = true; | | /++ | Due to Floating Point precision, when centering `x`, subtracting the mean | from the second and fourth numbers has no effect. Further, after centering | and squaring `x`, the first and third numbers in the slice have precision | too low to be included in the centered sum of squares. | +/ 1| assert(x.standardDeviation(populationFalseRT).approxEqual(sqrt(2.0e208 / 3))); 1| assert(x.standardDeviation(populationTrueRT).approxEqual(sqrt(2.0e208 / 4))); 1| assert(x.standardDeviation(PopulationTrueCT).approxEqual(sqrt(2.0e208 / 4))); | 1| assert(x.standardDeviation!("online").approxEqual(sqrt(2.0e208 / 3))); 1| assert(x.standardDeviation!("online", "kbn").approxEqual(sqrt(2.0e208 / 3))); 1| assert(x.standardDeviation!("online", "kb2").approxEqual(sqrt(2.0e208 / 3))); 1| assert(x.standardDeviation!("online", "precise").approxEqual(sqrt(2.0e208 / 3))); 1| assert(x.standardDeviation!(double, "online", "precise").approxEqual(sqrt(2.0e208 / 3))); 1| assert(x.standardDeviation!(double, "online", "precise")(populationTrueRT).approxEqual(sqrt(2.0e208 / 4))); | 1| auto y = uint.max.repeat(3); 1| auto z = y.standardDeviation!ulong; 1| assert(z == 0.0); | static assert(is(typeof(z) == double)); |} | |/++ |For integral slices, pass output type as template parameter to ensure output |type is correct. |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.slice: sliced; | 1| auto x = [0, 1, 1, 2, 4, 4, | 2, 7, 5, 1, 2, 0].sliced; | 1| auto y = x.standardDeviation; 1| assert(y.approxEqual(sqrt(50.91667 / 11))); | static assert(is(typeof(y) == double)); | 1| assert(x.standardDeviation!float.approxEqual(sqrt(50.91667 / 11))); |} | |/++ |Variance works for other user-defined types (provided they |can be converted to a floating point) |+/ |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | | static struct Foo { | float x; | alias x this; | } | 1| Foo[] foo = [Foo(1f), Foo(2f), Foo(3f)]; 1| assert(foo.standardDeviation == 1f); |} | |/// Compute standard deviation along specified dimention of tensors |version(mir_test) |@safe pure |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: as, iota, alongDim, map, repeat; | 1| auto x = [ | [0.0, 1, 2], | [3.0, 4, 5] | ].fuse; | 1| assert(x.standardDeviation.approxEqual(sqrt(17.5 / 5))); | 1| auto m0 = repeat(sqrt(4.5), 3); 1| assert(x.alongDim!0.map!standardDeviation.all!approxEqual(m0)); 1| assert(x.alongDim!(-2).map!standardDeviation.all!approxEqual(m0)); | 1| auto m1 = [1.0, 1.0]; 1| assert(x.alongDim!1.map!standardDeviation.all!approxEqual(m1)); 1| assert(x.alongDim!(-1).map!standardDeviation.all!approxEqual(m1)); | 1| assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!standardDeviation.all!approxEqual(repeat(sqrt(3600.0 / 2), 3, 4, 5))); |} | |/// Arbitrary standard deviation |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: sqrt; | 1| assert(standardDeviation(1.0, 2, 3) == 1.0); 1| assert(standardDeviation!float(1, 2, 3) == 1f); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual, sqrt; 1| assert([1.0, 2, 3, 4].standardDeviation.approxEqual(sqrt(5.0 / 3))); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.topology: iota, alongDim, map; | 1| auto x = iota([2, 2], 1); 1| auto y = x.alongDim!1.map!standardDeviation; 1| assert(y.all!approxEqual([sqrt(0.5), sqrt(0.5)])); | static assert(is(meanType!(typeof(y)) == double)); |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.math.common: approxEqual, sqrt; | import mir.ndslice.slice: sliced; | | static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, | 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; | 1| assert(x.sliced.standardDeviation.approxEqual(sqrt(54.76562 / 11))); 1| assert(x.sliced.standardDeviation!float.approxEqual(sqrt(54.76562 / 11))); |} | |/++ |A linear regression model with a single explanatory variable. |+/ |template simpleLinearRegression(Summation summation = Summation.kbn) |{ | import mir.ndslice.slice; | import mir.primitives: isInputRange; | | /++ | Params: | x = `x[i]` points | y = `f(x[i])` values | Returns: | The pair of shift and slope of the linear curve. | +/ | @fmamath | sumType!YRange[2] | simpleLinearRegression(XRange, YRange)(XRange x, YRange y) @safe | if (isInputRange!XRange && isInputRange!YRange && !(isArray!XRange && isArray!YRange) && isFloatingPoint!(sumType!YRange)) | in { | static if (hasLength!XRange && hasLength!YRange) 1| assert(x.length == y.length); | } | do { | alias X = typeof(sumType!XRange.init * sumType!XRange.init); | alias Y = sumType!YRange; | enum summationX = !__traits(isIntegral, X) ? summation: Summation.naive; 1| Summator!(X, summationX) xms = 0; 1| Summator!(Y, summation) yms = 0; 1| Summator!(X, summationX) xxms = 0; 1| Summator!(Y, summation) xyms = 0; | | static if (hasLength!XRange) 1| sizediff_t n = x.length; | else | sizediff_t n = 0; | 5| while (!x.empty) | { | static if (!(hasLength!XRange && hasLength!YRange)) | assert(!y.empty); | | static if (!hasLength!XRange) | n++; | 4| auto xi = x.front; 4| auto yi = y.front; 4| xms.put(xi); 4| yms.put(yi); 4| xxms.put(xi * xi); 4| xyms.put(xi * yi); | 4| y.popFront; 4| x.popFront; | } | | static if (!(hasLength!XRange && hasLength!YRange)) | assert(y.empty); | 1| auto xm = xms.sum; 1| auto ym = yms.sum; 1| auto xxm = xxms.sum; 1| auto xym = xyms.sum; | 1| auto slope = (xym * n - xm * ym) / (xxm * n - xm * xm); | 1| return [(ym - slope * xm) / n, slope]; | } | | /// ditto | @fmamath | sumType!(Y[])[2] | simpleLinearRegression(X, Y)(scope const X[] x, scope const Y[] y) @safe | { 1| return .simpleLinearRegression!summation(x.sliced, y.sliced); | } |} | |/// ditto |template simpleLinearRegression(string summation) |{ | mixin("alias simpleLinearRegression = .simpleLinearRegression!(Summation." ~ summation ~ ");"); |} | |/// |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.math.common: approxEqual; | static immutable x = [0, 1, 2, 3]; | static immutable y = [-1, 0.2, 0.9, 2.1]; 1| auto params = x.simpleLinearRegression(y); 1| assert(params[0].approxEqual(-0.95)); // shift 1| assert(params[1].approxEqual(1)); // slope |} source/mir/math/stat.d is 100% covered <<<<<< EOF # path=source-mir-math-sum.lst |/++ |This module contains summation algorithms. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |Authors: Ilia Ki | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |+/ |module mir.math.sum; | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: map; 1| auto ar = [1, 1e100, 1, -1e100].sliced.map!"a * 10_000"; 1| const r = 20_000; 1| assert(r == ar.sum!"kbn"); 1| assert(r == ar.sum!"kb2"); 1| assert(r == ar.sum!"precise"); 1| assert(r == ar.sum!"decimal"); |} | |/// Decimal precise summation |version(mir_test) |unittest |{ 1| auto ar = [777.7, -777]; 1| assert(ar.sum!"decimal" == 0.7); 1| assert(sum!"decimal"(777.7, -777) == 0.7); | | // The exact binary reuslt is 0.7000000000000455 1| assert(ar[0] + ar[1] == 0.7000000000000455); 1| assert(ar.sum!"fast" == 0.7000000000000455); 1| assert(ar.sum!"kahan" == 0.7000000000000455); 1| assert(ar.sum!"kbn" == 0.7000000000000455); 1| assert(ar.sum!"kb2" == 0.7000000000000455); 1| assert(ar.sum!"precise" == 0.7000000000000455); | 1| assert([1e-20, 1].sum!"decimal" == 1); |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.slice: sliced, slicedField; | import mir.ndslice.topology: map, iota, retro; | import mir.ndslice.concatenation: concatenation; | import mir.math.common; 1| auto ar = 1000 | .iota 2000| .map!(n => 1.7L.pow(n+1) - 1.7L.pow(n)) | ; 1| real d = 1.7L.pow(1000); 1| assert(sum!"precise"(concatenation(ar, [-d].sliced).slicedField) == -1); 1| assert(sum!"precise"(ar.retro, -d) == -1); |} | |/++ |`Naive`, `Pairwise` and `Kahan` algorithms can be used for user defined types. |+/ |version(mir_test) |unittest |{ | import mir.internal.utility: isFloatingPoint; | static struct Quaternion(F) | if (isFloatingPoint!F) | { | F[4] rijk; | | /// + and - operator overloading | Quaternion opBinary(string op)(auto ref const Quaternion rhs) const | if (op == "+" || op == "-") | { 6| Quaternion ret ; 114| foreach (i, ref e; ret.rijk) | mixin("e = rijk[i] "~op~" rhs.rijk[i];"); 6| return ret; | } | | /// += and -= operator overloading | Quaternion opOpAssign(string op)(auto ref const Quaternion rhs) | if (op == "+" || op == "-") | { 114| foreach (i, ref e; rijk) | mixin("e "~op~"= rhs.rijk[i];"); 6| return this; | } | | ///constructor with single FP argument 1| this(F f) | { 1| rijk[] = f; | } | | ///assigment with single FP argument | void opAssign(F f) | { 0000000| rijk[] = f; | } | } | 3| Quaternion!double q, p, r; 1| q.rijk = [0, 1, 2, 4]; 1| p.rijk = [3, 4, 5, 9]; 1| r.rijk = [3, 5, 7, 13]; | 1| assert(r == [p, q].sum!"naive"); 1| assert(r == [p, q].sum!"pairwise"); 1| assert(r == [p, q].sum!"kahan"); |} | |/++ |All summation algorithms available for complex numbers. |+/ |version(mir_test) |unittest |{ | import mir.complex: Complex; | 1| auto ar = [Complex!double(1.0, 2), Complex!double(2.0, 3), Complex!double(3.0, 4), Complex!double(4.0, 5)]; 1| Complex!double r = Complex!double(10.0, 14); 1| assert(r == ar.sum!"fast"); 1| assert(r == ar.sum!"naive"); 1| assert(r == ar.sum!"pairwise"); 1| assert(r == ar.sum!"kahan"); | version(LDC) // DMD Internal error: backend/cgxmm.c 628 | { 1| assert(r == ar.sum!"kbn"); 1| assert(r == ar.sum!"kb2"); | } 1| assert(r == ar.sum!"precise"); 1| assert(r == ar.sum!"decimal"); |} | |/// |version(mir_test) |@safe pure nothrow unittest |{ | import mir.ndslice.topology: repeat, iota; | | //simple integral summation 1| assert(sum([ 1, 2, 3, 4]) == 10); | | //with initial value 1| assert(sum([ 1, 2, 3, 4], 5) == 15); | | //with integral promotion 1| assert(sum([false, true, true, false, true]) == 3); 1| assert(sum(ubyte.max.repeat(100)) == 25_500); | | //The result may overflow 1| assert(uint.max.repeat(3).sum == 4_294_967_293U ); | //But a seed can be used to change the summation primitive 1| assert(uint.max.repeat(3).sum(ulong.init) == 12_884_901_885UL); | | //Floating point summation 1| assert(sum([1.0, 2.0, 3.0, 4.0]) == 10); | | //Type overriding | static assert(is(typeof(sum!double([1F, 2F, 3F, 4F])) == double)); | static assert(is(typeof(sum!double([1F, 2F, 3F, 4F], 5F)) == double)); 1| assert(sum([1F, 2, 3, 4]) == 10); 1| assert(sum([1F, 2, 3, 4], 5F) == 15); | | //Force pair-wise floating point summation on large integers | import mir.math : approxEqual; 1| assert(iota!long([4096], uint.max / 2).sum(0.0) | .approxEqual((uint.max / 2) * 4096.0 + 4096.0 * 4096.0 / 2)); |} | |/// Precise summation |version(mir_test) |nothrow @nogc unittest |{ | import mir.ndslice.topology: iota, map; | import core.stdc.tgmath: pow; 1001| assert(iota(1000).map!(n => 1.7L.pow(real(n)+1) - 1.7L.pow(real(n))) | .sum!"precise" == -1 + 1.7L.pow(1000.0L)); |} | |/// Precise summation with output range |version(mir_test) |nothrow @nogc unittest |{ | import mir.ndslice.topology: iota, map; | import mir.math.common; 1001| auto r = iota(1000).map!(n => 1.7L.pow(n+1) - 1.7L.pow(n)); 2| Summator!(real, Summation.precise) s = 0.0; 1| s.put(r); 1| s -= 1.7L.pow(1000); 1| assert(s.sum == -1); |} | |/// Precise summation with output range |version(mir_test) |nothrow @nogc unittest |{ | import mir.math.common; 1| float M = 2.0f ^^ (float.max_exp-1); 1| double N = 2.0 ^^ (float.max_exp-1); 2| auto s = Summator!(float, Summation.precise)(0); 1| s += M; 1| s += M; 1| assert(float.infinity == s.sum); //infinity 2| auto e = cast(Summator!(double, Summation.precise)) s; 1| assert(e.sum < double.infinity); 1| assert(N+N == e.sum()); //finite number |} | |/// Moving mean |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.internal.utility: isFloatingPoint; | import mir.math.sum; | import mir.ndslice.topology: linspace; | import mir.rc.array: rcarray; | | struct MovingAverage(T) | if (isFloatingPoint!T) | { | import mir.math.stat: MeanAccumulator; | | MeanAccumulator!(T, Summation.precise) meanAccumulator; | double[] circularBuffer; | size_t frontIndex; | | @disable this(this); | | auto avg() @property const | { 2| return meanAccumulator.mean; | } | 1| this(double[] buffer) | { 1| assert(buffer.length); 1| circularBuffer = buffer; 1| meanAccumulator.put(buffer); | } | | ///operation without rounding | void put(T x) | { | import mir.utility: swap; 10| meanAccumulator.summator += x; 10| swap(circularBuffer[frontIndex++], x); 10| frontIndex = frontIndex == circularBuffer.length ? 0 : frontIndex; 10| meanAccumulator.summator -= x; | } | } | | /// ma always keeps precise average of last 1000 elements 2| auto x = linspace!double([1000], [0.0, 999]).rcarray; 2| auto ma = MovingAverage!double(x[]); 1| assert(ma.avg == (1000 * 999 / 2) / 1000.0); | /// move by 10 elements 32| foreach(e; linspace!double([10], [1000.0, 1009.0])) 10| ma.put(e); 1| assert(ma.avg == (1010 * 1009 / 2 - 10 * 9 / 2) / 1000.0); |} | |/// Arbitrary sum |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.complex; | alias C = Complex!double; 1| assert(sum(1, 2, 3, 4) == 10); 1| assert(sum!float(1, 2, 3, 4) == 10f); 1| assert(sum(1f, 2, 3, 4) == 10f); 1| assert(sum(C(1.0, 2), C(2, 3), C(3, 4), C(4, 5)) == C(10, 14)); |} | |version(X86) | version = X86_Any; |version(X86_64) | version = X86_Any; | |/++ |SIMD Vectors |Bugs: ICE 1662 (dmd only) |+/ |version(LDC) |version(X86_Any) |version(mir_test) |unittest |{ | import core.simd; | import std.meta : AliasSeq; 4| double2 a = 1, b = 2, c = 3, d = 6; | with(Summation) | { | foreach (algo; AliasSeq!(naive, fast, pairwise, kahan)) | { 4| assert([a, b, c].sum!algo.array == d.array); 4| assert([a, b].sum!algo(c).array == d.array); | } | } |} | |import std.traits; |private alias AliasSeq(T...) = T; |import mir.internal.utility: Iota, isComplex; |import mir.math.common: fabs; | 43|private alias isNaN = x => x != x; 53280|private alias isFinite = x => x.fabs < x.infinity; 523016|private alias isInfinity = x => x.fabs == x.infinity; | | |private template chainSeq(size_t n) |{ | static if (n) | alias chainSeq = AliasSeq!(n, chainSeq!(n / 2)); | else | alias chainSeq = AliasSeq!(); |} | |/++ |Summation algorithms. |+/ |enum Summation |{ | /++ | Performs `pairwise` summation for floating point based types and `fast` summation for integral based types. | +/ | appropriate, | | /++ | $(WEB en.wikipedia.org/wiki/Pairwise_summation, Pairwise summation) algorithm. | +/ | pairwise, | | /++ | Precise summation algorithm. | The value of the sum is rounded to the nearest representable | floating-point number using the $(LUCKY round-half-to-even rule). | The result can differ from the exact value on 32bit `x86`, `nextDown(proir) <= result && result <= nextUp(proir)`. | The current implementation re-establish special value semantics across iterations (i.e. handling ±inf). | | References: $(LINK2 http://www.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps, | "Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates", Jonathan Richard Shewchuk), | $(LINK2 http://bugs.python.org/file10357/msum4.py, Mark Dickinson's post at bugs.python.org). | +/ | | /+ | Precise summation function as msum() by Raymond Hettinger in | , | enhanced with the exact partials sum and roundoff from Mark | Dickinson's post at . | See those links for more details, proofs and other references. | IEEE 754R floating point semantics are assumed. | +/ | precise, | | /++ | Precise decimal summation algorithm. | | The elements of the sum are converted to a shortest decimal representation that being converted back would result the same floating-point number. | The resulting decimal elements are summed without rounding. | The decimal sum is converted back to a binary floating point representation using round-half-to-even rule. | | See_also: The $(HTTPS github.com/ulfjack/ryu, Ryu algorithm) | +/ | decimal, | | /++ | $(WEB en.wikipedia.org/wiki/Kahan_summation, Kahan summation) algorithm. | +/ | /+ | --------------------- | s := x[1] | c := 0 | FOR k := 2 TO n DO | y := x[k] - c | t := s + y | c := (t - s) - y | s := t | END DO | --------------------- | +/ | kahan, | | /++ | $(LUCKY Kahan-Babuška-Neumaier summation algorithm). `KBN` gives more accurate results then `Kahan`. | +/ | /+ | --------------------- | s := x[1] | c := 0 | FOR i := 2 TO n DO | t := s + x[i] | IF ABS(s) >= ABS(x[i]) THEN | c := c + ((s-t)+x[i]) | ELSE | c := c + ((x[i]-t)+s) | END IF | s := t | END DO | s := s + c | --------------------- | +/ | kbn, | | /++ | $(LUCKY Generalized Kahan-Babuška summation algorithm), order 2. `KB2` gives more accurate results then `Kahan` and `KBN`. | +/ | /+ | --------------------- | s := 0 ; cs := 0 ; ccs := 0 | FOR j := 1 TO n DO | t := s + x[i] | IF ABS(s) >= ABS(x[i]) THEN | c := (s-t) + x[i] | ELSE | c := (x[i]-t) + s | END IF | s := t | t := cs + c | IF ABS(cs) >= ABS(c) THEN | cc := (cs-t) + c | ELSE | cc := (c-t) + cs | END IF | cs := t | ccs := ccs + cc | END FOR | RETURN s+cs+ccs | --------------------- | +/ | kb2, | | /++ | Naive algorithm (one by one). | +/ | naive, | | /++ | SIMD optimized summation algorithm. | +/ | fast, |} | |/++ |Output range for summation. |+/ |struct Summator(T, Summation summation) | if (isMutable!T) |{ | import mir.internal.utility: isComplex; | static if (is(T == class) || is(T == interface) || hasElaborateAssign!T && !isComplex!T) | static assert (summation == Summation.naive, | "Classes, interfaces, and structures with " | ~ "elaborate constructor support only naive summation."); | | static if (summation == Summation.fast) | { | version (LDC) | { | import ldc.attributes: fastmath; | alias attr = fastmath; | } | else | { | alias attr = AliasSeq!(); | } | } | else | { | alias attr = AliasSeq!(); | } | | @attr: | | static if (summation == Summation.pairwise) { | private enum bool fastPairwise = | is(F == float) || | is(F == double) || | (isComplex!F && F.sizeof <= 16) || | is(F : __vector(W[N]), W, size_t N); | //false; | } | | alias F = T; | | static if (summation == Summation.precise) | { | import std.internal.scopebuffer; | import mir.appender; | import mir.math.ieee: signbit; | private: | enum F M = (cast(F)(2)) ^^ (T.max_exp - 1); | auto partials = scopedBuffer!(F, 8 * T.sizeof); | //sum for NaN and infinity. | F s = summationInitValue!F; | //Overflow Degree. Count of 2^^F.max_exp minus count of -(2^^F.max_exp) | sizediff_t o; | | | /++ | Compute the sum of a list of nonoverlapping floats. | On input, partials is a list of nonzero, nonspecial, | nonoverlapping floats, strictly increasing in magnitude, but | possibly not all having the same sign. | On output, the sum of partials gives the error in the returned | result, which is correctly rounded (using the round-half-to-even | rule). | Two floating point values x and y are non-overlapping if the least significant nonzero | bit of x is more significant than the most significant nonzero bit of y, or vice-versa. | +/ | static F partialsReduce(F s, in F[] partials) | in | { | debug(numeric) assert(!partials.length || .isFinite(s)); | } | do | { 777| bool _break; 2419| foreach_reverse (i, y; partials) | { 44| s = partialsReducePred(s, y, i ? partials[i-1] : 0, _break); 44| if (_break) 44| break; | debug(numeric) assert(.isFinite(s)); | } 777| return s; | } | | static F partialsReducePred(F s, F y, F z, out bool _break) | out(result) 45| { | debug(numeric) assert(.isFinite(result)); | } | do | { 45| F x = s; 45| s = x + y; 45| F d = s - x; 45| F l = y - d; | debug(numeric) | { | assert(.isFinite(x)); | assert(.isFinite(y)); | assert(.isFinite(s)); | assert(fabs(y) < fabs(x)); | } 45| if (l) | { | //Make half-even rounding work across multiple partials. | //Needed so that sum([1e-16, 1, 1e16]) will round-up the last | //digit to two instead of down to zero (the 1e-16 makes the 1 | //slightly closer to two). Can guarantee commutativity. 59| if (z && !signbit(l * z)) | { 4| l *= 2; 4| x = s + l; 4| F t = x - s; 4| if (l == t) 4| s = x; | } 45| _break = true; | } 90| return s; | } | | //Returns corresponding infinity if is overflow and 0 otherwise. | F overflow()() const | { 78| if (o == 0) 36| return 0; 138| if (partials.length && (o == -1 || o == 1) && signbit(o * partials.data[$-1])) | { | // problem case: decide whether result is representable 34| F x = o * M; 34| F y = partials.data[$-1] / 2; 34| F h = x + y; 34| F d = h - x; 34| F l = (y - d) * 2; 34| y = h * 2; 34| d = h + l; 34| F t = d - h; | version(X86) | { | if (!.isInfinity(cast(T)y) || !.isInfinity(sum())) | return 0; | } | else | { 34| if (!.isInfinity(cast(T)y) || 40| ((partials.length > 1 && !signbit(l * partials.data[$-2])) && t == l)) 18| return 0; | } | } 24| return F.infinity * o; | } | } | else | static if (summation == Summation.kb2) | { | F s = summationInitValue!F; | F cs = summationInitValue!F; | F ccs = summationInitValue!F; | } | else | static if (summation == Summation.kbn) | { | F s = summationInitValue!F; | F c = summationInitValue!F; | } | else | static if (summation == Summation.kahan) | { | F s = summationInitValue!F; | F c = summationInitValue!F; | F y = summationInitValue!F; // do not declare in the loop/put (algo can be used for matrixes and etc) | F t = summationInitValue!F; // ditto | } | else | static if (summation == Summation.pairwise) | { | package size_t counter; | size_t index; | static if (fastPairwise) | { | enum registersCount= 16; | F[size_t.sizeof * 8] partials; | } | else | { | F[size_t.sizeof * 8] partials; | } | } | else | static if (summation == Summation.naive) | { | F s = summationInitValue!F; | } | else | static if (summation == Summation.fast) | { | F s = summationInitValue!F; | } | else | static if (summation == Summation.decimal) | { | import mir.bignum.decimal; | Decimal!128 s; | T ss = 0; | } | else | static assert(0, "Unsupported summation type for std.numeric.Summator."); | | |public: | | /// 1618| this()(T n) | { | static if (summation == Summation.precise) | { 342| s = 0.0; 342| o = 0; 595| if (n) put(n); | } | else | static if (summation == Summation.kb2) | { 252| s = n; | static if (isComplex!T) | { | cs = Complex!float(0, 0); | ccs = Complex!float(0, 0); | } | else | { 252| cs = 0.0; 252| ccs = 0.0; | } | } | else | static if (summation == Summation.kbn) | { 254| s = n; | static if (isComplex!T) | c = Complex!float(0, 0); | else 254| c = 0.0; | } | else | static if (summation == Summation.kahan) | { 253| s = n; | static if (isComplex!T) | c = Complex!float(0, 0); | else 253| c = 0.0; | } | else | static if (summation == Summation.pairwise) | { 255| counter = index = 1; 255| partials[0] = n; | } | else | static if (summation == Summation.naive) | { 130| s = n; | } | else | static if (summation == Summation.fast) | { 129| s = n; | } | else | static if (summation == Summation.decimal) | { 3| ss = 0; 6| if (!(-n.infinity < n && n < n.infinity)) | { 0000000| ss = n; 0000000| n = 0; | } 3| s = n; | } | else | static assert(0); | } | | ///Adds `n` to the internal partial sums. | void put(N)(N n) | if (__traits(compiles, {T a = n; a = n; a += n;})) | { | static if (isCompesatorAlgorithm!summation) 55381| F x = n; | static if (summation == Summation.precise) | { 53280| if (.isFinite(x)) | { 53265| size_t i; 53265| auto partials_data = partials.data; 1728561| foreach (y; partials_data[]) | { 522922| F h = x + y; 522922| if (.isInfinity(cast(T)h)) | { 29| if (fabs(x) < fabs(y)) | { 6| F t = x; x = y; y = t; | } | //h == -F.infinity 29| if (signbit(h)) | { 7| x += M; 7| x += M; 7| o--; | } | //h == +F.infinity | else | { 22| x -= M; 22| x -= M; 22| o++; | } | debug(numeric) assert(x.isFinite); 29| h = x + y; | } | debug(numeric) assert(h.isFinite); 522922| F l; 522922| if (fabs(x) < fabs(y)) | { 50957| F t = h - y; 50957| l = x - t; | } | else | { 471965| F t = h - x; 471965| l = y - t; | } | debug(numeric) assert(l.isFinite); 522922| if (l) | { 471322| partials_data[i++] = l; | } 522922| x = h; | } 53265| partials.shrinkTo(i); 53265| if (x) | { 53131| partials.put(x); | } | } | else | { 15| s += x; | } | } | else | static if (summation == Summation.kb2) | { | static if (isFloatingPoint!F) | { 912| F t = s + x; 912| F c = 0; 912| if (fabs(s) >= fabs(x)) | { 507| F d = s - t; 507| c = d + x; | } | else | { 405| F d = x - t; 405| c = d + s; | } 912| s = t; 912| t = cs + c; 912| if (fabs(cs) >= fabs(c)) | { 902| F d = cs - t; 902| d += c; 902| ccs += d; | } | else | { 10| F d = c - t; 10| d += cs; 10| ccs += d; | } 912| cs = t; | } | else | { 4| F t = s + x; 4| if (fabs(s.re) < fabs(x.re)) | { 2| auto s_re = s.re; 2| auto x_re = x.re; 2| s = F(x_re, s.im); 2| x = F(s_re, x.im); | } 4| if (fabs(s.im) < fabs(x.im)) | { 2| auto s_im = s.im; 2| auto x_im = x.im; 2| s = F(s.re, x_im); 2| x = F(x.re, s_im); | } 4| F c = (s-t)+x; 4| s = t; 4| if (fabs(cs.re) < fabs(c.re)) | { 0000000| auto c_re = c.re; 0000000| auto cs_re = cs.re; 0000000| c = F(cs_re, c.im); 0000000| cs = F(c_re, cs.im); | } 4| if (fabs(cs.im) < fabs(c.im)) | { 0000000| auto c_im = c.im; 0000000| auto cs_im = cs.im; 0000000| c = F(c.re, cs_im); 0000000| cs = F(cs.re, c_im); | } 4| F d = cs - t; 4| d += c; 4| ccs += d; 4| cs = t; | } | } | else | static if (summation == Summation.kbn) | { | static if (isFloatingPoint!F) | { 790| F t = s + x; 790| if (fabs(s) >= fabs(x)) | { 381| F d = s - t; 381| d += x; 381| c += d; | } | else | { 409| F d = x - t; 409| d += s; 409| c += d; | } 790| s = t; | } | else | { 4| F t = s + x; 4| if (fabs(s.re) < fabs(x.re)) | { 2| auto s_re = s.re; 2| auto x_re = x.re; 2| s = F(x_re, s.im); 2| x = F(s_re, x.im); | } 4| if (fabs(s.im) < fabs(x.im)) | { 2| auto s_im = s.im; 2| auto x_im = x.im; 2| s = F(s.re, x_im); 2| x = F(x.re, s_im); | } 4| F d = s - t; 4| d += x; 4| c += d; 4| s = t; | } | } | else | static if (summation == Summation.kahan) | { 391| y = x - c; 391| t = s + y; 391| c = t - s; 391| c -= y; 391| s = t; | } | else | static if (summation == Summation.pairwise) | { | import mir.bitop: cttz; 7603| ++counter; 7603| partials[index] = n; 42744| foreach (_; 0 .. cttz(counter)) | { 6645| immutable newIndex = index - 1; 6645| partials[newIndex] += partials[index]; 6645| index = newIndex; | } 7603| ++index; | } | else | static if (summation == Summation.naive) | { 616| s += n; | } | else | static if (summation == Summation.fast) | { 126| s += n; | } | else | static if (summation == summation.decimal) | { | import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; 36| if (-n.infinity < n && n < n.infinity) | { 18| auto decimal = genericBinaryToDecimal(n); 18| s += decimal; | } | else | { 0000000| ss += n; | } | } | else | static assert(0); | } | | ///ditto | void put(Range)(Range r) | if (isIterable!Range && !is(Range : __vector(V[N]), V, size_t N)) | { | static if (summation == Summation.pairwise && fastPairwise && isDynamicArray!Range) | { 91| F[registersCount] v; | foreach (i, n; chainSeq!registersCount) | { 455| if (r.length >= n * 2) do | { | foreach (j; Iota!n) 400| v[j] = cast(F) r[j]; | foreach (j; Iota!n) 400| v[j] += cast(F) r[n + j]; | foreach (m; chainSeq!(n / 2)) | foreach (j; Iota!m) 268| v[j] += v[m + j]; 132| put(v[0]); 132| r = r[n * 2 .. $]; | } 9| while (!i && r.length >= n * 2); | } 91| if (r.length) | { 40| put(cast(F) r[0]); 40| r = r[1 .. $]; | } 91| assert(r.length == 0); | } | else | static if (summation == Summation.fast) | { | static if (isComplex!F) 1| F s0 = F(0, 0f); | else 46| F s0 = 0; 1761| foreach (ref elem; r) 540| s0 += elem; 47| s += s0; | } | else | { 3610| foreach (ref elem; r) 1160| put(elem); | } | } | | import mir.ndslice.slice; | | /// ditto | void put(Range: Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind)(Range r) | { | static if (N > 1 && kind == Contiguous) | { | import mir.ndslice.topology: flattened; 10| this.put(r.flattened); | } | else | static if (isPointer!Iterator && kind == Contiguous) | { 97| this.put(r.field); | } | else | static if (summation == Summation.fast && N == 1) | { | static if (isComplex!F) | F s0 = F(0, 0f); | else 737| F s0 = 0; | import mir.algorithm.iteration: reduce; 737| s0 = s0.reduce!"a + b"(r); 737| s += s0; | } | else | { 32230| foreach(elem; r) 10495| this.put(elem); | } | } | | /+ | Adds `x` to the internal partial sums. | This operation doesn't re-establish special | value semantics across iterations (i.e. handling ±inf). | Preconditions: `isFinite(x)`. | +/ | version(none) | static if (summation == Summation.precise) | package void unsafePut()(F x) | in { | assert(.isFinite(x)); | } | do { | size_t i; | foreach (y; partials.data[]) | { | F h = x + y; | debug(numeric) assert(.isFinite(h)); | F l; | if (fabs(x) < fabs(y)) | { | F t = h - y; | l = x - t; | } | else | { | F t = h - x; | l = y - t; | } | debug(numeric) assert(.isFinite(l)); | if (l) | { | partials.data[i++] = l; | } | x = h; | } | partials.length = i; | if (x) | { | partials.put(x); | } | } | | ///Returns the value of the sum. | T sum()() scope const | { | /++ | Returns the value of the sum, rounded to the nearest representable | floating-point number using the round-half-to-even rule. | The result can differ from the exact value on `X86`, `nextDown`proir) <= result && result <= nextUp(proir)). | +/ | static if (summation == Summation.precise) | { | debug(mir_sum) | { | foreach (y; partials.data[]) | { | assert(y); | assert(y.isFinite); | } | //TODO: Add Non-Overlapping check to std.math | import mir.ndslice.slice: sliced; | import mir.ndslice.sorting: isSorted; | import mir.ndslice.topology: map; | assert(partials.data[].sliced.map!fabs.isSorted); | } | 783| if (s) 6| return s; 777| auto parts = partials.data[]; 777| F y = 0.0; | //pick last 777| if (parts.length) | { 770| y = parts[$-1]; 770| parts = parts[0..$-1]; | } 777| if (o) | { 22| immutable F of = o; 70| if (y && (o == -1 || o == 1) && signbit(of * y)) | { | // problem case: decide whether result is representable 17| y /= 2; 17| F x = of * M; 17| immutable F h = x + y; 17| F t = h - x; 17| F l = (y - t) * 2; 17| y = h * 2; 17| if (.isInfinity(cast(T)y)) | { | // overflow, except in edge case... 11| x = h + l; 11| t = x - h; 23| y = parts.length && t == l && !signbit(l*parts[$-1]) ? | x * 2 : | F.infinity * of; 11| parts = null; | } 6| else if (l) | { 1| bool _break; 1| y = partialsReducePred(y, l, parts.length ? parts[$-1] : 0, _break); 1| if (_break) 1| parts = null; | } | } | else | { 5| y = F.infinity * of; 5| parts = null; | } | } 777| return partialsReduce(y, parts); | } | else | static if (summation == Summation.kb2) | { 657| return s + (cs + ccs); | } | else | static if (summation == Summation.kbn) | { 658| return s + c; | } | else | static if (summation == Summation.kahan) | { 641| return s; | } | else | static if (summation == Summation.pairwise) | { 2131| F s = summationInitValue!T; 2131| assert((counter == 0) == (index == 0)); 12373| foreach_reverse (ref e; partials[0 .. index]) | { | static if (is(F : __vector(W[N]), W, size_t N)) 3| s += cast(Unqual!F) e; //DMD bug workaround | else 2987| s += e; | } 2131| return s; | } | else | static if (summation == Summation.naive) | { 627| return s; | } | else | static if (summation == Summation.fast) | { 1054| return s; | } | else | static if (summation == Summation.decimal) | { 6| return cast(T) s + ss; | } | else | static assert(0); | } | | version(none) | static if (summation == Summation.precise) | F partialsSum()() const | { | debug(numeric) partialsDebug; | auto parts = partials.data[]; | F y = 0.0; | //pick last | if (parts.length) | { | y = parts[$-1]; | parts = parts[0..$-1]; | } | return partialsReduce(y, parts); | } | | ///Returns `Summator` with extended internal partial sums. | C opCast(C : Summator!(P, _summation), P, Summation _summation)() const | if ( | _summation == summation && | isMutable!C && | P.max_exp >= T.max_exp && | P.mant_dig >= T.mant_dig | ) | { | static if (is(P == T)) | return this; | else | static if (summation == Summation.precise) | { 85| auto ret = typeof(return).init; 85| ret.s = s; 85| ret.o = o; 507| foreach (p; partials.data[]) | { 84| ret.partials.put(p); | } | enum exp_diff = P.max_exp / T.max_exp; | static if (exp_diff) | { 85| if (ret.o) | { 1| immutable f = ret.o / exp_diff; 1| immutable t = cast(int)(ret.o % exp_diff); 1| ret.o = f; 1| ret.put((P(2) ^^ T.max_exp) * t); | } | } 85| return ret; | } | else | static if (summation == Summation.kb2) | { 84| auto ret = typeof(return).init; 84| ret.s = s; 84| ret.cs = cs; 84| ret.ccs = ccs; 84| return ret; | } | else | static if (summation == Summation.kbn) | { 84| auto ret = typeof(return).init; 84| ret.s = s; 84| ret.c = c; 84| return ret; | } | else | static if (summation == Summation.kahan) | { 84| auto ret = typeof(return).init; 84| ret.s = s; 84| ret.c = c; 84| return ret; | } | else | static if (summation == Summation.pairwise) | { 84| auto ret = typeof(return).init; 84| ret.counter = counter; 84| ret.index = index; 756| foreach (i; 0 .. index) 168| ret.partials[i] = partials[i]; 84| return ret; | } | else | static if (summation == Summation.naive) | { | auto ret = typeof(return).init; | ret.s = s; | return ret; | } | else | static if (summation == Summation.fast) | { | auto ret = typeof(return).init; | ret.s = s; | return ret; | } | else | static assert(0); | } | | /++ | `cast(C)` operator overloading. Returns `cast(C)sum()`. | See also: `cast` | +/ | C opCast(C)() const if (is(Unqual!C == T)) | { 3| return cast(C)sum(); | } | | ///Operator overloading. | // opAssign should initialize partials. | void opAssign(T rhs) | { | static if (summation == Summation.precise) | { 169| partials.reset; 169| s = 0.0; 169| o = 0; 295| if (rhs) put(rhs); | } | else | static if (summation == Summation.kb2) | { 126| s = rhs; | static if (isComplex!T) | { 0000000| cs = T(0, 0f); 0000000| ccs = T(0.0, 0f); | } | else | { 126| cs = 0.0; 126| ccs = 0.0; | } | } | else | static if (summation == Summation.kbn) | { 126| s = rhs; | static if (isComplex!T) 0000000| c = T(0, 0f); | else 126| c = 0.0; | } | else | static if (summation == Summation.kahan) | { 126| s = rhs; | static if (isComplex!T) 0000000| c = T(0, 0f); | else 126| c = 0.0; | } | else | static if (summation == Summation.pairwise) | { 126| counter = 1; 126| index = 1; 126| partials[0] = rhs; | } | else | static if (summation == Summation.naive) | { 126| s = rhs; | } | else | static if (summation == Summation.fast) | { 126| s = rhs; | } | else | static if (summation == summation.decimal) | { 3| __ctor(rhs); | } | else | static assert(0); | } | | ///ditto | void opOpAssign(string op : "+")(T rhs) | { 22524| put(rhs); | } | | ///ditto | void opOpAssign(string op : "+")(ref const Summator rhs) | { | static if (summation == Summation.precise) | { | s += rhs.s; | o += rhs.o; | foreach (f; rhs.partials.data[]) | put(f); | } | else | static if (summation == Summation.kb2) | { | put(rhs.ccs); | put(rhs.cs); | put(rhs.s); | } | else | static if (summation == Summation.kbn) | { | put(rhs.c); | put(rhs.s); | } | else | static if (summation == Summation.kahan) | { | put(rhs.s); | } | else | static if (summation == Summation.pairwise) | { | foreach_reverse (e; rhs.partials[0 .. rhs.index]) | put(e); | counter -= rhs.index; | counter += rhs.counter; | } | else | static if (summation == Summation.naive) | { | put(rhs.s); | } | else | static if (summation == Summation.fast) | { | put(rhs.s); | } | else | static assert(0); | } | | ///ditto | void opOpAssign(string op : "-")(T rhs) | { | static if (summation == Summation.precise) | { 21179| put(-rhs); | } | else | static if (summation == Summation.kb2) | { 126| put(-rhs); | } | else | static if (summation == Summation.kbn) | { 126| put(-rhs); | } | else | static if (summation == Summation.kahan) | { 252| y = 0.0; 252| y -= rhs; 252| y -= c; 252| t = s + y; 252| c = t - s; 252| c -= y; 252| s = t; | } | else | static if (summation == Summation.pairwise) | { 126| put(-rhs); | } | else | static if (summation == Summation.naive) | { 126| s -= rhs; | } | else | static if (summation == Summation.fast) | { 126| s -= rhs; | } | else | static assert(0); | } | | ///ditto | void opOpAssign(string op : "-")(ref const Summator rhs) | { | static if (summation == Summation.precise) | { 168| s -= rhs.s; 168| o -= rhs.o; 3906| foreach (f; rhs.partials.data[]) 1134| put(-f); | } | else | static if (summation == Summation.kb2) | { 126| put(-rhs.ccs); 126| put(-rhs.cs); 126| put(-rhs.s); | } | else | static if (summation == Summation.kbn) | { 126| put(-rhs.c); 126| put(-rhs.s); | } | else | static if (summation == Summation.kahan) | { 126| this -= rhs.s; | } | else | static if (summation == Summation.pairwise) | { 630| foreach_reverse (e; rhs.partials[0 .. rhs.index]) 126| put(-e); 126| counter -= rhs.index; 126| counter += rhs.counter; | } | else | static if (summation == Summation.naive) | { | s -= rhs.s; | } | else | static if (summation == Summation.fast) | { | s -= rhs.s; | } | else | static assert(0); | } | | /// | | version(mir_test) | @nogc nothrow unittest | { | import mir.math.common; | import mir.ndslice.topology: iota, map; 21042| auto r1 = iota(500).map!(a => 1.7L.pow(a+1) - 1.7L.pow(a)); 21042| auto r2 = iota([500], 500).map!(a => 1.7L.pow(a+1) - 1.7L.pow(a)); 168| Summator!(real, Summation.precise) s1 = 0, s2 = 0.0; 84084| foreach (e; r1) s1 += e; 84084| foreach (e; r2) s2 -= e; 42| s1 -= s2; 42| s1 -= 1.7L.pow(1000); 42| assert(s1.sum == -1); | } | | | version(mir_test) | @nogc nothrow unittest | { | with(Summation) | foreach (summation; AliasSeq!(kahan, kbn, kb2, precise, pairwise)) | foreach (T; AliasSeq!(float, double, real)) | { 756| Summator!(T, summation) sum = 1; 630| sum += 3; 630| assert(sum.sum == 4); 630| sum -= 10; 630| assert(sum.sum == -6); 756| Summator!(T, summation) sum2 = 3; 630| sum -= sum2; 630| assert(sum.sum == -9); 630| sum2 = 100; 630| sum += 100; 630| assert(sum.sum == 91); 756| auto sum3 = cast(Summator!(real, summation))sum; 630| assert(sum3.sum == 91); 630| sum = sum2; | } | } | | | version(mir_test) | @nogc nothrow unittest | { | import mir.math.common: approxEqual; | with(Summation) | foreach (summation; AliasSeq!(naive, fast)) | foreach (T; AliasSeq!(float, double, real)) | { 252| Summator!(T, summation) sum = 1; 252| sum += 3.5; 252| assert(sum.sum.approxEqual(4.5)); 252| sum = 2; 252| assert(sum.sum == 2); 252| sum -= 4; 252| assert(sum.sum.approxEqual(-2)); | } | } | | static if (summation == Summation.precise) | { | ///Returns `true` if current sum is a NaN. | bool isNaN()() const | { 43| return .isNaN(s); | } | | ///Returns `true` if current sum is finite (not infinite or NaN). | bool isFinite()() const | { 43| if (s) 6| return false; 37| return !overflow; | } | | ///Returns `true` if current sum is ±∞. | bool isInfinity()() const | { 84| return .isInfinity(s) || overflow(); | } | } | else static if (isFloatingPoint!F) | { | ///Returns `true` if current sum is a NaN. | bool isNaN()() const | { | return .isNaN(sum()); | } | | ///Returns `true` if current sum is finite (not infinite or NaN). | bool isFinite()() const | { | return .isFinite(sum()); | } | | ///Returns `true` if current sum is ±∞. | bool isInfinity()() const | { | return .isInfinity(sum()); | } | } | else | { | //User defined types | } |} | |version(mir_test) |unittest |{ | import mir.functional: Tuple, tuple; | import mir.ndslice.topology: map, iota, retro; | import mir.array.allocation: array; | import std.math: isInfinity, isFinite, isNaN; | 2| Summator!(double, Summation.precise) summator = 0.0; | | enum double M = (cast(double)2) ^^ (double.max_exp - 1); 1| Tuple!(double[], double)[] tests = [ | tuple(new double[0], 0.0), | tuple([0.0], 0.0), | tuple([1e100, 1.0, -1e100, 1e-100, 1e50, -1, -1e50], 1e-100), | tuple([1e308, 1e308, -1e308], 1e308), | tuple([-1e308, 1e308, 1e308], 1e308), | tuple([1e308, -1e308, 1e308], 1e308), | tuple([M, M, -2.0^^1000], 1.7976930277114552e+308), | tuple([M, M, M, M, -M, -M, -M], 8.9884656743115795e+307), | tuple([2.0^^53, -0.5, -2.0^^-54], 2.0^^53-1.0), | tuple([2.0^^53, 1.0, 2.0^^-100], 2.0^^53+2.0), | tuple([2.0^^53+10.0, 1.0, 2.0^^-100], 2.0^^53+12.0), | tuple([2.0^^53-4.0, 0.5, 2.0^^-54], 2.0^^53-3.0), | tuple([M-2.0^^970, -1, M], 1.7976931348623157e+308), | tuple([double.max, double.max*2.^^-54], double.max), | tuple([double.max, double.max*2.^^-53], double.infinity), 1000| tuple(iota([1000], 1).map!(a => 1.0/a).array , 7.4854708605503451), 1000| tuple(iota([1000], 1).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... 1000| tuple(iota([1000], 1).map!(a => 1.0/a).retro.array , 7.4854708605503451), 1000| tuple(iota([1000], 1).map!(a => (-1.0)^^a/a).retro.array, -0.69264743055982025), | tuple([double.infinity, -double.infinity, double.nan], double.nan), | tuple([double.nan, double.infinity, -double.infinity], double.nan), | tuple([double.infinity, double.nan, double.infinity], double.nan), | tuple([double.infinity, double.infinity], double.infinity), | tuple([double.infinity, -double.infinity], double.nan), | tuple([-double.infinity, 1e308, 1e308, -double.infinity], -double.infinity), | tuple([M-2.0^^970, 0.0, M], double.infinity), | tuple([M-2.0^^970, 1.0, M], double.infinity), | tuple([M, M], double.infinity), | tuple([M, M, -1], double.infinity), | tuple([M, M, M, M, -M, -M], double.infinity), | tuple([M, M, M, M, -M, M], double.infinity), | tuple([-M, -M, -M, -M], -double.infinity), | tuple([M, M, -2.^^971], double.max), | tuple([M, M, -2.^^970], double.infinity), | tuple([-2.^^970, M, M, -0X0.0000000000001P-0 * 2.^^-1022], double.max), | tuple([M, M, -2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], double.infinity), | tuple([-M, 2.^^971, -M], -double.max), | tuple([-M, -M, 2.^^970], -double.infinity), | tuple([-M, -M, 2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], -double.max), | tuple([-0X0.0000000000001P-0 * 2.^^-1022, -M, -M, 2.^^970], -double.infinity), | tuple([2.^^930, -2.^^980, M, M, M, -M], 1.7976931348622137e+308), | tuple([M, M, -1e307], 1.6976931348623159e+308), | tuple([1e16, 1., 1e-16], 10_000_000_000_000_002.0), | ]; 175| foreach (i, test; tests) | { 43| summator = 0.0; 16649| foreach (t; test.a) summator.put(t); 43| auto r = test.b; 43| auto s = summator.sum; 43| assert(summator.isNaN() == r.isNaN()); 43| assert(summator.isFinite() == r.isFinite()); 43| assert(summator.isInfinity() == r.isInfinity()); 51| assert(s == r || s.isNaN && r.isNaN); | } |} | |/++ |Sums elements of `r`, which must be a finite |iterable. | |A seed may be passed to `sum`. Not only will this seed be used as an initial |value, but its type will be used if it is not specified. | |Note that these specialized summing algorithms execute more primitive operations |than vanilla summation. Therefore, if in certain cases maximum speed is required |at expense of precision, one can use $(LREF Summation.fast). | |Returns: | The sum of all the elements in the range r. |+/ |template sum(F, Summation summation = Summation.appropriate) | if (isMutable!F) |{ | /// | template sum(Range) | if (isIterable!Range) | { | import core.lifetime: move; | | /// | F sum(Range r) | { | static if (isComplex!F && (summation == Summation.precise || summation == Summation.decimal)) | { 0000000| return sum(r, summationInitValue!F); | } | else | { | static if (summation == Summation.decimal) | { 1| Summator!(F, summation) sum = void; 1| sum = 0; | } | else | { 704| Summator!(F, ResolveSummationType!(summation, Range, sumType!Range)) sum; | } 700| sum.put(r.move); 700| return sum.sum; | } | } | | /// | F sum(Range r, F seed) | { | static if (isComplex!F && (summation == Summation.precise || summation == Summation.decimal)) | { | alias T = typeof(F.init.re); | static if (summation == Summation.decimal) | { 1| Summator!(T, summation) sumRe = void; 1| sumRe = seed.re; | 1| Summator!(T, summation) sumIm = void; 1| sumIm = seed.im; | } | else | { 2| auto sumRe = Summator!(T, Summation.precise)(seed.re); 2| auto sumIm = Summator!(T, Summation.precise)(seed.im); | } | import mir.ndslice.slice: isSlice; | static if (isSlice!Range) | { | import mir.algorithm.iteration: each; | r.each!((auto ref elem) | { | sumRe.put(elem.re); | sumIm.put(elem.im); | }); | } | else | { 30| foreach (ref elem; r) | { 8| sumRe.put(elem.re); 8| sumIm.put(elem.im); | } | } 2| return F(sumRe.sum, sumIm.sum); | } | else | { | static if (summation == Summation.decimal) | { 0000000| Summator!(F, summation) sum = void; 0000000| sum = seed; | } | else | { 11| auto sum = Summator!(F, ResolveSummationType!(summation, Range, F))(seed); | } 10| sum.put(r.move); 10| return sum.sum; | } | } | } | | /// | F sum(scope const F[] r...) | { | static if (isComplex!F && (summation == Summation.precise || summation == Summation.decimal)) | { 2| return sum(r, summationInitValue!F); | } | else | { 67| Summator!(F, ResolveSummationType!(summation, const(F)[], F)) sum; 66| sum.put(r); 66| return sum.sum; | } | } |} | |///ditto |template sum(Summation summation = Summation.appropriate) |{ | /// | sumType!Range sum(Range)(Range r) | if (isIterable!Range) | { | import core.lifetime: move; | alias F = typeof(return); 754| return .sum!(F, ResolveSummationType!(summation, Range, F))(r.move); | } | | /// | F sum(Range, F)(Range r, F seed) | if (isIterable!Range) | { | import core.lifetime: move; 10| return .sum!(F, ResolveSummationType!(summation, Range, F))(r.move, seed); | } | | /// | sumType!T sum(T)(scope const T[] ar...) | { | alias F = typeof(return); 8| return .sum!(F, ResolveSummationType!(summation, F[], F))(ar); | } |} | |///ditto |template sum(F, string summation) | if (isMutable!F) |{ | mixin("alias sum = .sum!(F, Summation." ~ summation ~ ");"); |} | |///ditto |template sum(string summation) |{ | mixin("alias sum = .sum!(Summation." ~ summation ~ ");"); |} | |private static immutable jaggedMsg = "sum: each slice should have the same length"; |version(D_Exceptions) | static immutable jaggedException = new Exception(jaggedMsg); | |/++ |Sum slices with a naive algorithm. |+/ |template sumSlices() |{ | import mir.primitives: DeepElementType; | import mir.ndslice.slice: Slice, SliceKind, isSlice; | /// | auto sumSlices(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) sliceOfSlices) | if (isSlice!(DeepElementType!(Slice!(Iterator, 1, kind)))) | { | import mir.ndslice.topology: as; | import mir.ndslice.allocation: slice; | alias T = Unqual!(DeepElementType!(DeepElementType!(Slice!(Iterator, 1, kind)))); | import mir.ndslice: slice; 2| if (sliceOfSlices.length == 0) 0000000| return typeof(slice(as!T(sliceOfSlices.front))).init; 2| auto ret = slice(as!T(sliceOfSlices.front)); 2| sliceOfSlices.popFront; 10| foreach (sl; sliceOfSlices) | { 2| if (sl.length != ret.length) | { | version (D_Exceptions) 0000000| throw jaggedException; | else | assert(0); | } 2| ret[] += sl[]; | } 2| return ret; | } |} | |/// |version(mir_test) |unittest |{ | import mir.ndslice.topology: map, byDim; | import mir.ndslice.slice: sliced; | 1| auto ar = [[1, 2, 3], [10, 20, 30]]; 1| assert(ar.map!sliced.sumSlices == [11, 22, 33]); | | import mir.ndslice.fuse: fuse; 1| auto a = [[[1.2], [2.1]], [[4.1], [5.2]]].fuse; 1| auto s = a.byDim!0.sumSlices; 1| assert(s == [[5.3], [7.300000000000001]]); |} | |version(mir_test) |@safe pure nothrow unittest |{ | static assert(is(typeof(sum([cast( byte)1])) == int)); | static assert(is(typeof(sum([cast(ubyte)1])) == int)); | static assert(is(typeof(sum([ 1, 2, 3, 4])) == int)); | static assert(is(typeof(sum([ 1U, 2U, 3U, 4U])) == uint)); | static assert(is(typeof(sum([ 1L, 2L, 3L, 4L])) == long)); | static assert(is(typeof(sum([1UL, 2UL, 3UL, 4UL])) == ulong)); | 1| int[] empty; 1| assert(sum(empty) == 0); 1| assert(sum([42]) == 42); 1| assert(sum([42, 43]) == 42 + 43); 1| assert(sum([42, 43, 44]) == 42 + 43 + 44); 1| assert(sum([42, 43, 44, 45]) == 42 + 43 + 44 + 45); |} | | |version(mir_test) |@safe pure nothrow unittest |{ | static assert(is(typeof(sum([1.0, 2.0, 3.0, 4.0])) == double)); | static assert(is(typeof(sum!double([ 1F, 2F, 3F, 4F])) == double)); 1| const(float[]) a = [1F, 2F, 3F, 4F]; | static assert(is(typeof(sum!double(a)) == double)); 1| const(float)[] b = [1F, 2F, 3F, 4F]; | static assert(is(typeof(sum!double(a)) == double)); | 1| double[] empty; 1| assert(sum(empty) == 0); 1| assert(sum([42.]) == 42); 1| assert(sum([42., 43.]) == 42 + 43); 1| assert(sum([42., 43., 44.]) == 42 + 43 + 44); 1| assert(sum([42., 43., 44., 45.5]) == 42 + 43 + 44 + 45.5); |} | |version(mir_test) |@safe pure nothrow unittest |{ | import mir.ndslice.topology: iota; 1| assert(iota(2, 3).sum == 15); |} | |version(mir_test) |@safe pure nothrow unittest |{ | import std.container; | static assert(is(typeof(sum!double(SList!float()[])) == double)); | static assert(is(typeof(sum(SList!double()[])) == double)); | static assert(is(typeof(sum(SList!real()[])) == real)); | 1| assert(sum(SList!double()[]) == 0); 1| assert(sum(SList!double(1)[]) == 1); 1| assert(sum(SList!double(1, 2)[]) == 1 + 2); 1| assert(sum(SList!double(1, 2, 3)[]) == 1 + 2 + 3); 1| assert(sum(SList!double(1, 2, 3, 4)[]) == 10); |} | | |version(mir_test) |pure nothrow unittest // 12434 |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: map; 1| immutable a = [10, 20]; 1| auto s = a.sliced; 1| auto s1 = sum(a); // Error 3| auto s2 = s.map!(x => x).sum; // Error |} | |version(mir_test) |unittest |{ | import std.bigint; | import mir.ndslice.topology: repeat; | 1| auto a = BigInt("1_000_000_000_000_000_000").repeat(10); 1| auto b = (ulong.max/2).repeat(10); 1| auto sa = a.sum(); 1| auto sb = b.sum(BigInt(0)); //reduce ulongs into bigint 1| assert(sa == BigInt("10_000_000_000_000_000_000")); 1| assert(sb == (BigInt(ulong.max/2) * 10)); |} | |version(mir_test) |unittest |{ | with(Summation) | foreach (F; AliasSeq!(float, double, real)) | { 3| F[] ar = [1, 2, 3, 4]; 3| F r = 10; 3| assert(r == ar.sum!fast()); 3| assert(r == ar.sum!pairwise()); 3| assert(r == ar.sum!kahan()); 3| assert(r == ar.sum!kbn()); 3| assert(r == ar.sum!kb2()); | } |} | |version(mir_test) |unittest |{ 1| assert(sum(1) == 1); 1| assert(sum(1, 2, 3) == 6); 1| assert(sum(1.0, 2.0, 3.0) == 6); |} | |version(mir_test) |unittest |{ 1| assert(sum!float(1) == 1f); 1| assert(sum!float(1, 2, 3) == 6f); 1| assert(sum!float(1.0, 2.0, 3.0) == 6f); |} | |version(mir_test) |unittest |{ | import mir.complex: Complex; | 1| assert(sum(Complex!float(1.0, 1.0), Complex!float(2.0, 2.0), Complex!float(3.0, 3.0)) == Complex!float(6.0, 6.0)); 1| assert(sum!(Complex!float)(Complex!float(1.0, 1.0), Complex!float(2.0, 2.0), Complex!float(3.0, 3.0)) == Complex!float(6.0, 6.0)); |} | |version(LDC) |version(X86_Any) |version(mir_test) |unittest |{ | import core.simd; | static if (__traits(compiles, double2.init + double2.init)) | { | | alias S = Summation; | alias sums = AliasSeq!(S.kahan, S.pairwise, S.naive, S.fast); | 1| double2[] ar = [double2([1.0, 2]), double2([2, 3]), double2([3, 4]), double2([4, 6])]; 1| double2 c = double2([10, 15]); | | foreach (sumType; sums) | { 4| double2 s = ar.sum!(sumType); 4| assert(s.array == c.array); | } | } |} | |version(LDC) |version(X86_Any) |version(mir_test) |unittest |{ | import core.simd; | import mir.ndslice.topology: iota, as; | | alias S = Summation; | alias sums = AliasSeq!(S.kahan, S.pairwise, S.naive, S.fast, S.precise, | S.kbn, S.kb2); | 1| int[2] ns = [9, 101]; | 9| foreach (n; ns) | { | foreach (sumType; sums) | { 14| auto ar = iota(n).as!double; 14| double c = n * (n - 1) / 2; // gauss for n=100 14| double s = ar.sum!(sumType); 14| assert(s == c); | } | } |} | |package(mir) |template ResolveSummationType(Summation summation, Range, F) |{ | static if (summation == Summation.appropriate) | { | static if (isSummable!(Range, F)) | enum ResolveSummationType = Summation.pairwise; | else | static if (is(F == class) || is(F == struct) || is(F == interface)) | enum ResolveSummationType = Summation.naive; | else | enum ResolveSummationType = Summation.fast; | } | else | { | enum ResolveSummationType = summation; | } |} | |private T summationInitValue(T)() |{ | static if (__traits(compiles, {T a = 0.0;})) | { 2130| T a = 0.0; 2130| return a; | } | else | static if (__traits(compiles, {T a = 0;})) | { 0000000| T a = 0; 0000000| return a; | } | else | static if (__traits(compiles, {T a = 0 + 0fi;})) | { | T a = 0 + 0fi; | return a; | } | else | { 3| return T.init; | } |} | |package(mir) |template elementType(T) |{ | import mir.ndslice.slice: isSlice, DeepElementType; | import std.traits: Unqual, ForeachType; | | static if (isIterable!T) { | static if (isSlice!T) | alias elementType = Unqual!(DeepElementType!(T.This)); | else | alias elementType = Unqual!(ForeachType!T); | } else { | alias elementType = Unqual!T; | } |} | |package(mir) |template sumType(Range) |{ | alias T = elementType!Range; | | static if (__traits(compiles, { | auto a = T.init + T.init; | a += T.init; | })) | alias sumType = typeof(T.init + T.init); | else | static assert(0, "sumType: Can't sum elements of type " ~ T.stringof); |} | |/++ |+/ |template fillCollapseSums(Summation summation, alias combineParts, combineElements...) |{ | import mir.ndslice.slice: Slice, SliceKind; | /++ | +/ | auto ref fillCollapseSums(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) data) @property | { | import mir.algorithm.iteration; | import mir.functional: naryFun; | import mir.ndslice.topology: iota, triplets; | foreach (triplet; data.length.iota.triplets) with(triplet) | { | auto ref ce(size_t i)() | { | static if (summation == Summation.fast) | { | return | sum!summation(naryFun!(combineElements[i])(center, left )) + | sum!summation(naryFun!(combineElements[i])(center, right)); | } | else | { | Summator!summation summator = 0; | summator.put(naryFun!(combineElements[i])(center, left)); | summator.put(naryFun!(combineElements[i])(center, right)); | return summator.sum; | } | } | alias sums = staticMap!(ce, Iota!(combineElements.length)); | data[center] = naryFun!combineParts(center, sums); | } | } |} | |package: | |template isSummable(F) |{ | enum bool isSummable = | __traits(compiles, | { | F a = 0.1, b, c; | b = 2.3; | c = a + b; | c = a - b; | a += b; | a -= b; | }); |} | |template isSummable(Range, F) |{ | enum bool isSummable = | isIterable!Range && | isImplicitlyConvertible!(sumType!Range, F) && | isSummable!F; |} | |version(mir_test) |unittest |{ | import mir.ndslice.topology: iota; | static assert(isSummable!(typeof(iota([size_t.init])), double)); |} | |private enum bool isCompesatorAlgorithm(Summation summation) = | summation == Summation.precise | || summation == Summation.kb2 | || summation == Summation.kbn | || summation == Summation.kahan; | | |version(mir_test) |unittest |{ | import mir.ndslice; | 1| auto p = iota([2, 3, 4, 5]); 1| auto a = p.as!double; 1| auto b = a.flattened; 1| auto c = a.slice; 1| auto d = c.flattened; 1| auto s = p.flattened.sum; | 1| assert(a.sum == s); 1| assert(b.sum == s); 1| assert(c.sum == s); 1| assert(d.sum == s); | 1| assert(a.canonical.sum == s); 1| assert(b.canonical.sum == s); 1| assert(c.canonical.sum == s); 1| assert(d.canonical.sum == s); | 1| assert(a.universal.transposed!3.sum == s); 1| assert(b.universal.sum == s); 1| assert(c.universal.transposed!3.sum == s); 1| assert(d.universal.sum == s); | 1| assert(a.sum!"fast" == s); 1| assert(b.sum!"fast" == s); 1| assert(c.sum!(float, "fast") == s); 1| assert(d.sum!"fast" == s); | 1| assert(a.canonical.sum!"fast" == s); 1| assert(b.canonical.sum!"fast" == s); 1| assert(c.canonical.sum!"fast" == s); 1| assert(d.canonical.sum!"fast" == s); | 1| assert(a.universal.transposed!3.sum!"fast" == s); 1| assert(b.universal.sum!"fast" == s); 1| assert(c.universal.transposed!3.sum!"fast" == s); 1| assert(d.universal.sum!"fast" == s); | |} source/mir/math/sum.d is 96% covered <<<<<< EOF # path=source-mir-ndslice-allocation.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |It contains allocation utilities. | | |$(BOOKTABLE $(H2 Common utilities), |$(T2 shape, Returns a shape of a common n-dimensional array. ) |) | |$(BOOKTABLE $(H2 GC Allocation utilities), |$(TR $(TH Function Name) $(TH Description)) |$(T2 slice, Allocates a slice using GC.) |$(T2 bitSlice, GC-Allocates a bitwise packed n-dimensional boolean slice.) |$(T2 ndarray, Allocates a common n-dimensional array from a slice. ) |$(T2 uninitSlice, Allocates an uninitialized slice using GC. ) |) | |$(BOOKTABLE $(H2 Ref counted allocation utilities), |$(T2 rcslice, Allocates an n-dimensional reference-counted (thread-safe) slice.) |$(T2 bitRcslice, Allocates a bitwise packed n-dimensional reference-counted (thread-safe) boolean slice.) |$(T2 mininitRcslice, Allocates a minimally initialized n-dimensional reference-counted (thread-safe) slice.) |) | |$(BOOKTABLE $(H2 Custom allocation utilities), |$(TR $(TH Function Name) $(TH Description)) |$(T2 makeNdarray, Allocates a common n-dimensional array from a slice using an allocator. ) |$(T2 makeSlice, Allocates a slice using an allocator. ) |$(T2 makeUninitSlice, Allocates an uninitialized slice using an allocator. ) |) | |$(BOOKTABLE $(H2 CRuntime allocation utilities), |$(TR $(TH Function Name) $(TH Description)) |$(T2 stdcSlice, Allocates a slice copy using `core.stdc.stdlib.malloc`) |$(T2 stdcUninitSlice, Allocates an uninitialized slice using `core.stdc.stdlib.malloc`.) |$(T2 stdcFreeSlice, Frees memory using `core.stdc.stdlib.free`) |) | |$(BOOKTABLE $(H2 Aligned allocation utilities), |$(TR $(TH Function Name) $(TH Description)) |$(T2 uninitAlignedSlice, Allocates an uninitialized aligned slice using GC. ) |$(T2 stdcUninitAlignedSlice, Allocates an uninitialized aligned slice using CRuntime.) |$(T2 stdcFreeAlignedSlice, Frees memory using CRuntime) |) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.allocation; | |import mir.math.common: optmath; |import mir.ndslice.concatenation; |import mir.ndslice.field: BitField; |import mir.ndslice.internal; |import mir.ndslice.iterator: FieldIterator; |import mir.ndslice.slice; |import mir.rc.array; |import std.traits; |import std.meta: staticMap; | |@optmath: | |/++ |Allocates an n-dimensional reference-counted (thread-safe) slice. |Params: | lengths = List of lengths for each dimension. | init = Value to initialize with (optional). | slice = Slice to copy shape and data from (optional). |Returns: | n-dimensional slice |+/ |Slice!(RCI!T, N) | rcslice(T, size_t N)(size_t[N] lengths...) |{ 43| immutable len = lengths.lengthsProduct; 43| auto _lengths = lengths; 43| return typeof(return)(_lengths, RCI!T(RCArray!T(len))); |} | |/// ditto |Slice!(RCI!T, N) | rcslice(T, size_t N)(size_t[N] lengths, T init) |{ 6| auto ret = (()@trusted => mininitRcslice!T(lengths))(); 3| ret.lightScope.field[] = init; 3| return ret; |} | |/// ditto |auto rcslice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) |{ | import mir.conv: emplaceRef; | alias E = slice.DeepElement; | 321| auto result = (() @trusted => slice.shape.mininitRcslice!(Unqual!E))(); | | import mir.algorithm.iteration: each; 107| each!(emplaceRef!E)(result.lightScope, slice.lightScope); | 214| return *(() @trusted => cast(Slice!(RCI!E, N)*) &result)(); |} | |/// ditto |auto rcslice(T)(T[] array) |{ 45| return rcslice(array.sliced); |} | |/// ditto |auto rcslice(T, I)(I[] array) | if (!isImplicitlyConvertible!(I[], T[])) |{ | import mir.ndslice.topology: as; 12| return rcslice(array.sliced.as!T); |} | |/// |version(mir_ndslice_test) |@safe pure nothrow @nogc unittest |{ | import mir.ndslice.slice: Slice; | import mir.rc.array: RCI; 2| auto tensor = rcslice!int(5, 6, 7); 1| assert(tensor.length == 5); 1| assert(tensor.elementCount == 5 * 6 * 7); | static assert(is(typeof(tensor) == Slice!(RCI!int, 3))); | | // creates duplicate using `rcslice` 2| auto dup = tensor.rcslice; 1| assert(dup == tensor); |} | |/// |version(mir_ndslice_test) |@safe pure nothrow @nogc unittest |{ | import mir.ndslice.slice: Slice; | import mir.rc.array: RCI; 2| auto tensor = rcslice([2, 3], 5); 1| assert(tensor.elementCount == 2 * 3); 1| assert(tensor[1, 1] == 5); | | import mir.rc.array; | static assert(is(typeof(tensor) == Slice!(RCI!int, 2))); |} | |/// ditto |auto rcslice(size_t dim, Slices...)(Concatenation!(dim, Slices) concatenation) |{ | alias T = Unqual!(concatenation.DeepElement); 2| auto ret = (()@trusted => mininitRcslice!T(concatenation.shape))(); 1| ret.lightScope.opIndexAssign(concatenation); 1| return ret; |} | |/// |version(mir_ndslice_test) |@safe pure nothrow @nogc unittest |{ | import mir.rc.array: RCI; | import mir.ndslice.slice: Slice; | import mir.ndslice.topology : iota; | import mir.ndslice.concatenation; 2| auto tensor = concatenation([2, 3].iota, [3].iota(6)).rcslice; 1| assert(tensor == [3, 3].iota); | | static assert(is(typeof(tensor) == Slice!(RCI!ptrdiff_t, 2))); |} | |/++ |Allocates an n-dimensional reference-counted (thread-safe) slice without memory initialisation. |Params: | lengths = List of lengths for each dimension. |Returns: | n-dimensional slice |+/ |Slice!(RCI!T, N) | uninitRCslice(T, size_t N)(size_t[N] lengths...) |{ 3| immutable len = lengths.lengthsProduct; 3| auto _lengths = lengths; 3| return typeof(return)(_lengths, RCI!T(RCArray!T(len, false))); |} | |/// |version(mir_ndslice_test) |@safe pure nothrow @nogc unittest |{ | import mir.ndslice.slice: Slice; | import mir.rc.array: RCI; 2| auto tensor = uninitRCslice!int(5, 6, 7); 1| tensor[] = 1; 1| assert(tensor.length == 5); 1| assert(tensor.elementCount == 5 * 6 * 7); | static assert(is(typeof(tensor) == Slice!(RCI!int, 3))); |} | |/++ |Allocates a bitwise packed n-dimensional reference-counted (thread-safe) boolean slice. |Params: | lengths = List of lengths for each dimension. |Returns: | n-dimensional bitwise rcslice |See_also: $(SUBREF topology, bitwise). |+/ |Slice!(FieldIterator!(BitField!(RCI!size_t)), N) bitRcslice(size_t N)(size_t[N] lengths...) |{ | import mir.ndslice.topology: bitwise; | enum elen = size_t.sizeof * 8; 2| immutable len = lengths.lengthsProduct; 2| immutable dlen = (len / elen + (len % elen != 0)); 2| return RCArray!size_t(dlen).asSlice.bitwise[0 .. len].sliced(lengths); |} | |/// 1D |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ 2| auto bitarray = 100.bitRcslice; // allocates 16 bytes total (plus RC context) 1| assert(bitarray.shape == cast(size_t[1])[100]); 1| assert(bitarray[72] == false); 1| bitarray[72] = true; 1| assert(bitarray[72] == true); |} | |/// 2D |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ 2| auto bitmatrix = bitRcslice(20, 6); // allocates 16 bytes total (plus RC context) 1| assert(bitmatrix.shape == cast(size_t[2])[20, 6]); 1| assert(bitmatrix[3, 4] == false); 1| bitmatrix[3, 4] = true; 1| assert(bitmatrix[3, 4] == true); |} | |/++ |Allocates a minimally initialized n-dimensional reference-counted (thread-safe) slice. |Params: | lengths = list of lengths for each dimension |Returns: | contiguous minimally initialized n-dimensional reference-counted (thread-safe) slice |+/ |Slice!(RCI!T, N) mininitRcslice(T, size_t N)(size_t[N] lengths...) |{ 112| immutable len = lengths.lengthsProduct; 112| auto _lengths = lengths; 112| return Slice!(RCI!T, N)(_lengths, RCI!T(mininitRcarray!T(len))); |} | |/// |version(mir_ndslice_test) |pure nothrow @nogc unittest |{ | import mir.ndslice.slice: Slice; | import mir.rc.array: RCI; 2| auto tensor = mininitRcslice!int(5, 6, 7); 1| assert(tensor.length == 5); 1| assert(tensor.elementCount == 5 * 6 * 7); | static assert(is(typeof(tensor) == Slice!(RCI!int, 3))); |} | |private alias Pointer(T) = T*; |private alias Pointers(Args...) = staticMap!(Pointer, Args); | |/++ |GC-Allocates an n-dimensional slice. |+/ |template slice(Args...) | if (Args.length) |{ | /// | alias LabelTypes = Args[1 .. $]; | /// | alias T = Args[0]; | | /++ | Params: | lengths = List of lengths for each dimension. | init = Value to initialize with (optional). | Returns: | initialzed n-dimensional slice | +/ | Slice!(T*, N, Contiguous, Pointers!LabelTypes) | slice(size_t N)(size_t[N] lengths...) | if (N >= LabelTypes.length) | { 80| auto shape = lengths; // DMD variadic bug workaround 80| immutable len = shape.lengthsProduct; 159| auto ret = typeof(return)(shape, len == 0 ? null : (()@trusted=>new T[len].ptr)()); | foreach (i, L; LabelTypes) // static 28| ret._labels[i] = (()@trusted=>new L[shape[i]].ptr)(); 80| return ret; | } | | /// ditto | Slice!(T*, N, Contiguous, Pointers!LabelTypes) | slice(size_t N)(size_t[N] lengths, T init) | if (N >= LabelTypes.length) | { | import mir.conv: emplaceRef; | import std.array : uninitializedArray; 5| immutable len = lengths.lengthsProduct; 5| auto arr = uninitializedArray!(Unqual!T[])(len); 135| foreach (ref e; arr) 40| emplaceRef(e, init); 10| auto ret = typeof(return)(lengths, len == 0 ? null : (()@trusted=>cast(T*)arr.ptr)()); | foreach (i, L; LabelTypes) // static | ret._labels[i] = (()@trusted=>new L[shape[i]].ptr)(); 5| return ret; | } |} | |/// |version(mir_ndslice_test) |@safe pure nothrow unittest |{ | import mir.ndslice.slice: Slice; 1| auto tensor = slice!int(5, 6, 7); 1| assert(tensor.length == 5); 1| assert(tensor.length!1 == 6); 1| assert(tensor.elementCount == 5 * 6 * 7); | static assert(is(typeof(tensor) == Slice!(int*, 3))); |} | |/// 2D DataFrame example |version(mir_ndslice_test) |@safe pure unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | import mir.date: Date; | 1| auto dataframe = slice!(double, Date, string)(4, 3); 1| assert(dataframe.length == 4); 1| assert(dataframe.length!1 == 3); 1| assert(dataframe.elementCount == 4 * 3); | | static assert(is(typeof(dataframe) == | Slice!(double*, 2, Contiguous, Date*, string*))); | | // Dataframe labels are contiguous 1-dimensional slices. | | // Fill row labels 1| dataframe.label[] = [ | Date(2019, 1, 24), | Date(2019, 2, 2), | Date(2019, 2, 4), | Date(2019, 2, 5), | ]; | 1| assert(dataframe.label!0[2] == Date(2019, 2, 4)); | | // Fill column labels 1| dataframe.label!1[] = ["income", "outcome", "balance"]; | 1| assert(dataframe.label!1[2] == "balance"); | | // Change label element 1| dataframe.label!1[2] = "total"; 1| assert(dataframe.label!1[2] == "total"); | | // Attach a newly allocated label 1| dataframe.label!1 = ["Income", "Outcome", "Balance"].sliced; | 1| assert(dataframe.label!1[2] == "Balance"); |} | |/++ |GC-Allocates an n-dimensional slice. |Params: | lengths = List of lengths for each dimension. | init = Value to initialize with (optional). |Returns: | initialzed n-dimensional slice |+/ |Slice!(T*, N) | slice(size_t N, T)(size_t[N] lengths, T init) |{ 1| return .slice!T(lengths, init); |} | |// TODO: make it a dataframe compatible. This function performs copy. |/// ditto |auto slice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) |{ 100| if (__ctfe) | { | import mir.ndslice.topology: flattened; | import mir.array.allocation: array; | return slice.flattened.array.sliced(slice.shape); | } | else | { | import mir.conv: emplaceRef; | alias E = slice.DeepElement; | 200| auto result = (() @trusted => slice.shape.uninitSlice!(Unqual!E))(); | | import mir.algorithm.iteration: each; 100| each!(emplaceRef!E)(result, slice); | 200| return (() @trusted => cast(Slice!(E*, N)) result)(); | } |} | |/// |version(mir_ndslice_test) |@safe pure nothrow unittest |{ 1| auto tensor = slice([2, 3], 5); 1| assert(tensor.elementCount == 2 * 3); 1| assert(tensor[1, 1] == 5); | | // creates duplicate using `slice` 1| auto dup = tensor.slice; 1| assert(dup == tensor); |} | |/// ditto |auto slice(size_t dim, Slices...)(Concatenation!(dim, Slices) concatenation) |{ | alias T = Unqual!(concatenation.DeepElement); | static if (hasElaborateAssign!T) | alias fun = .slice; | else | alias fun = .uninitSlice; 42| auto ret = (()@trusted => fun!T(concatenation.shape))(); 21| ret.opIndexAssign(concatenation); 21| return ret; |} | |/// |version(mir_ndslice_test) |@safe pure nothrow unittest |{ | import mir.ndslice.slice: Slice; | import mir.ndslice.topology : iota; | import mir.ndslice.concatenation; 1| auto tensor = concatenation([2, 3].iota, [3].iota(6)).slice; 1| assert(tensor == [3, 3].iota); | | static assert(is(typeof(tensor) == Slice!(ptrdiff_t*, 2))); |} | |/++ |GC-Allocates a bitwise packed n-dimensional boolean slice. |Params: | lengths = List of lengths for each dimension. |Returns: | n-dimensional bitwise slice |See_also: $(SUBREF topology, bitwise). |+/ |Slice!(FieldIterator!(BitField!(size_t*)), N) bitSlice(size_t N)(size_t[N] lengths...) |{ | import mir.ndslice.topology: bitwise; | enum elen = size_t.sizeof * 8; 10| immutable len = lengths.lengthsProduct; 10| immutable dlen = (len / elen + (len % elen != 0)); 10| return new size_t[dlen].sliced.bitwise[0 .. len].sliced(lengths); |} | |/// 1D |@safe pure version(mir_ndslice_test) unittest |{ 1| auto bitarray = bitSlice(100); // allocates 16 bytes total 1| assert(bitarray.shape == [100]); 1| assert(bitarray[72] == false); 1| bitarray[72] = true; 1| assert(bitarray[72] == true); |} | |/// 2D |@safe pure version(mir_ndslice_test) unittest |{ 1| auto bitmatrix = bitSlice(20, 6); // allocates 16 bytes total 1| assert(bitmatrix.shape == [20, 6]); 1| assert(bitmatrix[3, 4] == false); 1| bitmatrix[3, 4] = true; 1| assert(bitmatrix[3, 4] == true); |} | |/++ |GC-Allocates an uninitialized n-dimensional slice. |Params: | lengths = list of lengths for each dimension |Returns: | contiguous uninitialized n-dimensional slice |+/ |auto uninitSlice(T, size_t N)(size_t[N] lengths...) |{ 482| immutable len = lengths.lengthsProduct; | import std.array : uninitializedArray; 482| auto arr = uninitializedArray!(T[])(len); | version (mir_secure_memory) | {()@trusted{ | (cast(ubyte[])arr)[] = 0; | }();} 482| return arr.sliced(lengths); |} | |/// |version(mir_ndslice_test) |@safe pure nothrow unittest |{ | import mir.ndslice.slice: Slice; 1| auto tensor = uninitSlice!int(5, 6, 7); 1| assert(tensor.length == 5); 1| assert(tensor.elementCount == 5 * 6 * 7); | static assert(is(typeof(tensor) == Slice!(int*, 3))); |} | |/++ |GC-Allocates an uninitialized aligned an n-dimensional slice. |Params: | lengths = list of lengths for each dimension | alignment = memory alignment (bytes) |Returns: | contiguous uninitialized n-dimensional slice |+/ |auto uninitAlignedSlice(T, size_t N)(size_t[N] lengths, uint alignment) @system |{ 1| immutable len = lengths.lengthsProduct; | import std.array : uninitializedArray; 2| assert((alignment != 0) && ((alignment & (alignment - 1)) == 0), "'alignment' must be a power of two"); 1| size_t offset = alignment <= 16 ? 0 : alignment - 1; 1| void* basePtr = uninitializedArray!(byte[])(len * T.sizeof + offset).ptr; 1| T* alignedPtr = cast(T*)((cast(size_t)(basePtr) + offset) & ~offset); 1| return alignedPtr.sliced(lengths); |} | |/// |version(mir_ndslice_test) |@system pure nothrow unittest |{ | import mir.ndslice.slice: Slice; 1| auto tensor = uninitAlignedSlice!double([5, 6, 7], 64); 1| tensor[] = 0; 1| assert(tensor.length == 5); 1| assert(tensor.elementCount == 5 * 6 * 7); 1| assert(cast(size_t)(tensor.ptr) % 64 == 0); | static assert(is(typeof(tensor) == Slice!(double*, 3))); |} | |/++ |Allocates an array through a specified allocator and creates an n-dimensional slice over it. |See also $(MREF std, experimental, allocator). |Params: | alloc = allocator | lengths = list of lengths for each dimension | init = default value for array initialization | slice = slice to copy shape and data from |Returns: | a structure with fields `array` and `slice` |Note: | `makeSlice` always returns slice with mutable elements |+/ |auto makeSlice(Allocator, size_t N, Iterator)(auto ref Allocator alloc, Slice!(N, Iterator) slice) |{ | alias T = Unqual!(slice.DeepElement); | return makeSlice!(T)(alloc, slice); |} | |/// ditto |Slice!(T*, N) |makeSlice(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] lengths...) |{ | import std.experimental.allocator : makeArray; 1| return alloc.makeArray!T(lengths.lengthsProduct).sliced(lengths); |} | |/// ditto |Slice!(T*, N) |makeSlice(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] lengths, T init) |{ | import std.experimental.allocator : makeArray; 2| immutable len = lengths.lengthsProduct; 2| auto array = alloc.makeArray!T(len, init); 2| return array.sliced(lengths); |} | |/// ditto |auto makeSlice(Allocator, Iterator, size_t N, SliceKind kind) | (auto ref Allocator allocator, Slice!(Iterator, N, kind) slice) |{ | import mir.conv: emplaceRef; | alias E = slice.DeepElement; | 1| auto result = allocator.makeUninitSlice!(Unqual!E)(slice.shape); | | import mir.algorithm.iteration: each; 1| each!(emplaceRef!E)(result, slice); | 1| return cast(Slice!(E*, N)) result; |} | |/// Initialization with default value |version(mir_ndslice_test) |@nogc unittest |{ | import std.experimental.allocator; | import std.experimental.allocator.mallocator; | import mir.algorithm.iteration: all; | import mir.ndslice.topology: map; | 1| auto sl = Mallocator.instance.makeSlice([2, 3, 4], 10); 1| auto ar = sl.field; 1| assert(sl.all!"a == 10"); | 1| auto sl2 = Mallocator.instance.makeSlice(sl.map!"a * 2"); 1| auto ar2 = sl2.field; 1| assert(sl2.all!"a == 20"); | 1| Mallocator.instance.dispose(ar); 1| Mallocator.instance.dispose(ar2); |} | |version(mir_ndslice_test) |@nogc unittest |{ | import std.experimental.allocator; | import std.experimental.allocator.mallocator; | | // cast to your own type 1| auto sl = makeSlice!double(Mallocator.instance, [2, 3, 4], 10); 1| auto ar = sl.field; 1| assert(sl[1, 1, 1] == 10.0); 1| Mallocator.instance.dispose(ar); |} | |/++ |Allocates an uninitialized array through a specified allocator and creates an n-dimensional slice over it. |See also $(MREF std, experimental, allocator). |Params: | alloc = allocator | lengths = list of lengths for each dimension |Returns: | a structure with fields `array` and `slice` |+/ |Slice!(T*, N) |makeUninitSlice(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] lengths...) | if (N) |{ 8| if (immutable len = lengths.lengthsProduct) | { 8| auto mem = alloc.allocate(len * T.sizeof); 8| if (mem.length == 0) assert(0); 16| auto array = () @trusted { return cast(T[]) mem; }(); | version (mir_secure_memory) | {() @trusted { | (cast(ubyte[])array)[] = 0; | }();} 8| return array.sliced(lengths); | } | else | { 0000000| return T[].init.sliced(lengths); | } |} | |/// |version(mir_ndslice_test) |@system @nogc unittest |{ | import std.experimental.allocator; | import std.experimental.allocator.mallocator; | 1| auto sl = makeUninitSlice!int(Mallocator.instance, 2, 3, 4); 1| auto ar = sl.field; 1| assert(ar.ptr is sl.iterator); 1| assert(ar.length == 24); 1| assert(sl.elementCount == 24); | 1| Mallocator.instance.dispose(ar); |} | |/++ |Allocates a common n-dimensional array from a slice. |Params: | slice = slice |Returns: | multidimensional D array |+/ |auto ndarray(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) |{ | import mir.array.allocation : array; | static if (slice.N == 1) | { 3| return slice.array; | } | else | { | import mir.ndslice.topology: ipack, map; 1| return slice.ipack!1.map!(.ndarray).array; | } |} | |/// |version(mir_ndslice_test) |@safe pure nothrow unittest |{ | import mir.ndslice.topology : iota; 1| auto slice = iota(3, 4); 1| auto m = slice.ndarray; | static assert(is(typeof(m) == sizediff_t[][])); // sizediff_t is long for 64 bit platforms 1| assert(m == [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]); |} | |/++ |Allocates a common n-dimensional array using data from a slice. |Params: | alloc = allocator (optional) | slice = slice |Returns: | multidimensional D array |+/ |auto makeNdarray(T, Allocator, Iterator, size_t N, SliceKind kind)(auto ref Allocator alloc, Slice!(Iterator, N, kind) slice) |{ | import std.experimental.allocator : makeArray; | static if (slice.N == 1) | { 3| return makeArray!T(alloc, slice); | } | else | { | alias E = typeof(makeNdarray!T(alloc, slice[0])); 1| auto ret = makeArray!E(alloc, slice.length); 15| foreach (i, ref e; ret) 3| e = .makeNdarray!T(alloc, slice[i]); 1| return ret; | } |} | |/// |version(mir_ndslice_test) |@nogc unittest |{ | import std.experimental.allocator; | import std.experimental.allocator.mallocator; | import mir.ndslice.topology : iota; | 1| auto slice = iota(3, 4); 1| auto m = Mallocator.instance.makeNdarray!long(slice); | | static assert(is(typeof(m) == long[][])); | | static immutable ar = [[0L, 1, 2, 3], [4L, 5, 6, 7], [8L, 9, 10, 11]]; 1| assert(m == ar); | 12| foreach (ref row; m) 3| Mallocator.instance.dispose(row); 1| Mallocator.instance.dispose(m); |} | |/++ |Shape of a common n-dimensional array. |Params: | array = common n-dimensional array | err = error flag passed by reference |Returns: | static array of dimensions type of `size_t[n]` |+/ |auto shape(T)(T[] array, ref int err) |{ | static if (isDynamicArray!T) | { 4| size_t[1 + typeof(shape(T.init, err)).length] ret; | 4| if (array.length) | { 3| ret[0] = array.length; 3| ret[1..$] = shape(array[0], err); 3| if (err) 0000000| goto L; 25| foreach (ar; array) | { 6| if (shape(ar, err) != ret[1..$]) 1| err = 1; 6| if (err) 1| goto L; | } | } | } | else | { 9| size_t[1] ret; 9| ret[0] = array.length; | } 12| err = 0; |L: 13| return ret; |} | |/// |version(mir_ndslice_test) |@safe pure unittest |{ 1| int err; 1| size_t[2] shape = [[1, 2, 3], [4, 5, 6]].shape(err); 1| assert(err == 0); 1| assert(shape == [2, 3]); | 1| [[1, 2], [4, 5, 6]].shape(err); 1| assert(err == 1); |} | |/// Slice from ndarray |version(mir_ndslice_test) |unittest |{ | import mir.ndslice.allocation: slice, shape; 1| int err; 1| auto array = [[1, 2, 3], [4, 5, 6]]; 1| auto s = array.shape(err).slice!int; 1| s[] = [[1, 2, 3], [4, 5, 6]]; 1| assert(s == array); |} | |version(mir_ndslice_test) |@safe pure unittest |{ 1| int err; 1| size_t[2] shape = (int[][]).init.shape(err); 1| assert(shape[0] == 0); 1| assert(shape[1] == 0); |} | |version(mir_ndslice_test) |nothrow unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.topology : iota; | 1| auto sl = iota([0, 0], 1); | 1| assert(sl.empty!0); 1| assert(sl.empty!1); | 1| auto gcsl1 = sl.slice; 1| auto gcsl2 = slice!double(0, 0); | | import std.experimental.allocator; | import std.experimental.allocator.mallocator; | 1| auto sl2 = makeSlice!double(Mallocator.instance, 0, 0); | 1| Mallocator.instance.dispose(sl2.field); |} | |/++ |Allocates an uninitialized array using `core.stdc.stdlib.malloc` and creates an n-dimensional slice over it. |Params: | lengths = list of lengths for each dimension |Returns: | contiguous uninitialized n-dimensional slice |See_also: | $(LREF stdcSlice), $(LREF stdcFreeSlice) |+/ |Slice!(T*, N) stdcUninitSlice(T, size_t N)(size_t[N] lengths...) |{ | import core.stdc.stdlib: malloc; 2| immutable len = lengths.lengthsProduct; 2| auto p = malloc(len * T.sizeof); 2| if (p is null) assert(0); | version (mir_secure_memory) | { | (cast(ubyte*)p)[0 .. len * T.sizeof] = 0; | } 2| auto ptr = len ? cast(T*) p : null; 2| return ptr.sliced(lengths); |} | |/++ |Allocates a copy of a slice using `core.stdc.stdlib.malloc`. |Params: | slice = n-dimensional slice |Returns: | contiguous n-dimensional slice |See_also: | $(LREF stdcUninitSlice), $(LREF stdcFreeSlice) |+/ |auto stdcSlice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) |{ | alias E = slice.DeepElement; | alias T = Unqual!E; | static assert (!hasElaborateAssign!T, "stdcSlice is not miplemented for slices that have elaborate assign"); 1| auto ret = stdcUninitSlice!T(slice.shape); | | import mir.conv: emplaceRef; | import mir.algorithm.iteration: each; 1| each!(emplaceRef!E)(ret, slice); 1| return ret; |} | |/++ |Frees memory using `core.stdc.stdlib.free`. |Params: | slice = n-dimensional slice |See_also: | $(LREF stdcSlice), $(LREF stdcUninitSlice) |+/ |void stdcFreeSlice(T, size_t N)(Slice!(T*, N) slice) |{ | import core.stdc.stdlib: free; | version (mir_secure_memory) | { | (cast(ubyte[])slice.field)[] = 0; | } 2| slice._iterator.free; |} | |/// |version(mir_ndslice_test) |unittest |{ | import mir.ndslice.topology: iota; | 1| auto i = iota(3, 4); 1| auto s = i.stdcSlice; 1| auto t = s.shape.stdcUninitSlice!size_t; | 1| t[] = s; | 1| assert(t == i); | 1| s.stdcFreeSlice; 1| t.stdcFreeSlice; |} | |/++ |Allocates an uninitialized aligned array using `core.stdc.stdlib.malloc` and creates an n-dimensional slice over it. |Params: | lengths = list of lengths for each dimension | alignment = memory alignment (bytes) |Returns: | contiguous uninitialized n-dimensional slice |+/ |auto stdcUninitAlignedSlice(T, size_t N)(size_t[N] lengths, uint alignment) @system |{ 1| immutable len = lengths.lengthsProduct; | import mir.internal.memory: alignedAllocate; 1| auto arr = (cast(T*)alignedAllocate(len * T.sizeof, alignment))[0 .. len]; | version (mir_secure_memory) | { | (cast(ubyte[])arr)[] = 0; | } 1| return arr.sliced(lengths); |} | |/// |version(mir_ndslice_test) |@system pure nothrow unittest |{ | import mir.ndslice.slice: Slice; 1| auto tensor = stdcUninitAlignedSlice!double([5, 6, 7], 64); 1| assert(tensor.length == 5); 1| assert(tensor.elementCount == 5 * 6 * 7); 1| assert(cast(size_t)(tensor.ptr) % 64 == 0); | static assert(is(typeof(tensor) == Slice!(double*, 3))); 1| stdcFreeAlignedSlice(tensor); |} | |/++ |Frees aligned memory allocaged by CRuntime. |Params: | slice = n-dimensional slice |See_also: | $(LREF stdcSlice), $(LREF stdcUninitSlice) |+/ |void stdcFreeAlignedSlice(T, size_t N)(Slice!(T*, N) slice) |{ | import mir.internal.memory: alignedFree; | version (mir_secure_memory) | { | (cast(ubyte[])slice.field)[] = 0; | } 1| slice._iterator.alignedFree; |} source/mir/ndslice/allocation.d is 99% covered <<<<<< EOF # path=source-mir-ndslice-chunks.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |The module contains $(LREF _chunks) routine. |$(LREF Chunks) structure is multidimensional random access range with slicing. | |$(SUBREF slice, slicedField), $(SUBREF slice, slicedNdField) can be used to construct ndslice view on top of $(LREF Chunks). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.chunks; | |import mir.internal.utility; |import mir.math.common: optmath; |import mir.ndslice.internal; |import mir.ndslice.iterator: IotaIterator; |import mir.ndslice.slice; | |import std.meta; |import std.traits; | |/++ |Creates $(LREF Chunks). | |Params: | Dimensions = compile time list of dimensions to chunk | |See_also: $(SUBREF topology, blocks) $(SUBREF fuse, fuseCells) |+/ |template chunks(Dimensions...) | if (Dimensions.length) |{ | static if (allSatisfy!(isSize_t, Dimensions)) | /++ | Params: | slice = Slice to chunk. | chunkLengths = Chunk shape. It must not have a zero length. | Returns: $(LREF Chunks). | +/ | Chunks!([Dimensions], Iterator, N, kind == Contiguous && [Dimensions] != [0] ? Canonical : kind) | chunks(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, size_t[Dimensions.length] chunkLengths...) | { | static if (kindOf!(typeof(typeof(return).init._slice)) != kind) | { | import mir.ndslice.topology: canonical; 5| auto p = slice.canonical; | } | else | { | alias p = slice; | } 13| auto ret = typeof(return)(chunkLengths, p); | foreach (i; Iota!(Dimensions.length)) 17| ret._norm!i; 13| return ret; | } | else | alias chunks = .chunks!(staticMap!(toSize_t, Dimensions)); |} | |/// ditto |Chunks!([0], Iterator, N, kind) chunks(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, size_t chunkLength) |{ 5| return .chunks!0(slice, chunkLength); |} | | |/// 1Dx1D |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ | import mir.ndslice.chunks: chunks, isChunks; | import mir.ndslice.topology: iota; | | // 0 1 2 3 4 5 6 7 8 9 10 1| auto sl = iota(11); | // 0 1 2 | 3 4 5 | 6 7 8 | 9 10 1| auto ch = sl.chunks(3); | | static assert(isChunks!(typeof(ch)) == [0]); // isChunks returns dimension indices | 1| assert(ch.length == 4); 1| assert(ch.shape == cast(size_t[1])[4]); | | // 0 1 2 1| assert(ch.front == iota([3], 0)); 1| ch.popFront; | | // 3 4 5 1| assert(ch.front == iota([3], 3)); 1| assert(ch.length == 3); | | // 9 10 1| assert(ch[$ - 1] == ch.back); 1| assert(ch.back == iota([2], 9)); | 1| ch.popBack; 1| assert(ch.back == iota([3], 6)); | 1| assert(ch[$ - 1 .. $].length == 1); 1| assert(ch[$ .. $].length == 0); 1| assert(ch[0 .. 0].empty); | | import std.range.primitives: isRandomAccessRange; | static assert(isRandomAccessRange!(typeof(ch))); |} | |/// 2Dx2D |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.chunks: chunks, isChunks; | import mir.ndslice.topology: iota; | | // 0 1 2 3 4 5 6 7 8 9 | // 10 11 12 13 14 15 16 17 18 19 | // 20 21 22 23 24 25 26 27 28 29 | // 30 31 32 33 34 35 36 37 38 39 | // 40 41 42 43 44 45 46 47 48 49 | // 50 51 52 53 54 55 56 57 58 59 | // 60 61 62 63 64 65 66 67 68 69 | // 70 71 72 73 74 75 76 77 78 79 | // 80 81 82 83 84 85 86 87 88 89 | // 90 91 92 93 94 95 96 97 98 99 | // 100 101 102 103 104 105 106 107 108 109 1| auto sl = iota(11, 10); // [0, 1, .. 10] | | // ---------------- ---------------- -------- | // | 0 1 2 3 | | 4 5 6 7 | | 8 9 | | // | 10 11 12 13 | | 14 15 16 17 | | 18 19 | | // | 20 21 22 23 | | 24 25 26 27 | | 28 29 | | // |----------------| |----------------| |--------| | // | 30 31 32 33 | | 34 35 36 37 | | 38 39 | | // | 40 41 42 43 | | 44 45 46 47 | | 48 49 | | // | 50 51 52 53 | | 54 55 56 57 | | 58 59 | | // |----------------| |----------------| |--------| | // | 60 61 62 63 | | 64 65 66 67 | | 68 69 | | // | 70 71 72 73 | | 74 75 76 77 | | 78 79 | | // | 80 81 82 83 | | 84 85 86 87 | | 88 89 | | // |----------------| |----------------| |--------| | // | 90 91 92 93 | | 94 95 96 97 | | 98 99 | | // |100 101 102 103 | |104 105 106 107 | |108 109 | | // ---------------- ---------------- -------- | // Chunk columns first, then blocks rows. 1| auto ch = sl.chunks!(1, 0)(4, 3); | 1| assert(ch.shape == [3, 4]); 1| assert(ch.slice == sl); 1| assert(ch.front.slice == sl[0 .. $, 0 .. 4]); | 1| assert(ch.front.front == sl[0 .. 3, 0 .. 4]); | 1| assert(ch.front!0[1] == sl[3 .. 6, 0 .. 4]); 1| assert(ch.front!1[1] == sl[0 .. 3, 4 .. 8]); | 1| assert (ch[$ - 1, $ - 1] == [[98, 99], [108, 109]]); | | static assert(isChunks!(typeof(ch)) == [1, 0]); // isChunks returns dimension indices | 1| assert(ch.length == 3); 1| assert(ch.length!1 == 4); | 1| ch.popFront; 1| assert(ch.front.front == sl[0 .. 3, 4 .. 8]); 1| ch.popFront!1; 1| assert(ch.front.front == sl[3 .. 6, 4 .. 8]); | 1| assert(ch.back.slice == sl[3 .. $, 8 .. $]); 1| ch.popBack; 1| assert(ch.back.slice == sl[3 .. $, 4 .. 8]); | | import std.range.primitives: isRandomAccessRange; | static assert(isRandomAccessRange!(typeof(ch))); |} | |/// 1Dx2D |version(mir_ndslice_test) unittest |{ | import mir.ndslice.chunks: chunks, isChunks; | import mir.ndslice.topology: iota; | | // 0 1 2 3 4 5 6 7 8 9 | // 10 11 12 13 14 15 16 17 18 19 | // 20 21 22 23 24 25 26 27 28 29 | // 30 31 32 33 34 35 36 37 38 39 1| auto sl = iota(4, 10); // [0, 1, .. 10] | | // ---------------- ---------------- -------- | // | 0 1 2 3 | | 4 5 6 7 | | 8 9 | | // | 10 11 12 13 | | 14 15 16 17 | | 18 19 | | // | 20 21 22 23 | | 24 25 26 27 | | 28 29 | | // | 30 31 32 33 | | 34 35 36 37 | | 38 39 | | // ---------------- ---------------- -------- | // Chunk columns 1| auto ch = sl.chunks!1(4); | 1| assert(ch.slice == sl); 1| assert(ch.front == sl[0 .. $, 0 .. 4]); | 1| assert(ch.back == sl[0 .. $, 8 .. $]); | | import std.range.primitives: isRandomAccessRange; | static assert(isRandomAccessRange!(typeof(ch))); |} | |// conversion to ndslice |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice : slicedField; | import mir.ndslice.chunks: chunks; | import mir.ndslice.topology: iota, map; | import mir.math.sum: sum; | | // 0 1 2 3 4 5 6 7 8 9 10 1| auto sl = iota(11); | // 0 1 2 | 3 4 5 | 6 7 8 | 9 10 1| auto ch = sl.chunks(3); | // 3 | 12 | 21 | 19 1| auto s = ch.slicedField.map!sum; 1| assert(s == [3, 12, 21, 19]); |} | |/++ |+/ |struct Chunks(size_t[] dimensions, Iterator, size_t N = 1, SliceKind kind = Contiguous) |{ |@optmath: | | /++ | Chunk shape. | +/ 4| size_t[dimensions.length] chunkLengths()() @property { return _chunkLengths; } | /// ditto | size_t[dimensions.length] _chunkLengths; | | /// | auto lightConst()() const @property | { | import mir.qualifier; 1| return Chunks!(dimensions, LightConstOf!Iterator, N, kind)(_chunkLengths, _slice.lightConst); | } | | /// | auto lightImmutable()() immutable @property | { | import mir.qualifier; | return Chunks!(dimensions, LightImmutableOf!Iterator, N, kind)(_chunkLengths, _slice.lightImmutable); | } | | alias DeepElement = Slice!(Iterator, N, kind); | | /++ | Underlying ndslice. | It always correspond to current chunks state. | Its shape equal to the concatenation of the all chunks. | +/ 9| Slice!(Iterator, N, kind) slice()() @property { return _slice; } | /// | Slice!(Iterator, N, kind) _slice; | | private auto _norm(size_t dimensionIndex = 0)() @property | { 73| assert(_chunkLengths[dimensionIndex]); | enum dimension = dimensions[dimensionIndex]; 96| if (_expect(_slice._lengths[dimension] < _chunkLengths[dimensionIndex], false) && _slice._lengths[dimension]) 9| _chunkLengths[dimensionIndex] = _slice._lengths[dimension]; | } | | private auto _wrap(size_t dimensionIndex, S)(ref S ret) | { | static if (dimensions.length == 1) | { 67| return ret; | } | else | { 31| size_t[dimensions.length - 1] rcl; | foreach (i, j; AliasSeq!(Iota!dimensionIndex, Iota!(dimensionIndex + 1, dimensions.length))) 31| rcl[i] = _chunkLengths[j]; | enum newDims = dimensions[0 .. dimensionIndex] ~ dimensions[dimensionIndex + 1 .. $]; 31| return .Chunks!(newDims, Iterator, N, typeof(ret).kind)(rcl, ret); | } | } | | private ref size_t sliceLength(size_t dimensionIndex)() @property | { | enum dimension = dimensions[dimensionIndex]; 5| return _slice._lengths[dimension]; | } | | /// ndslice-like primitives | bool empty(size_t dimensionIndex = 0)() const @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; 126| return _slice.empty!(dimension); | } | | /// | size_t[dimensions.length] shape()() const @property | { 3| typeof(return) ret; | foreach(dimensionIndex; Iota!(ret.length)) | { | enum dimension = dimensions[dimensionIndex]; 5| auto l = _slice._lengths[dimension]; 5| auto n = _chunkLengths[dimensionIndex]; 5| ret[dimensionIndex] = l / n + (l % n != 0); | } 3| return ret; | } | | /// ditto | size_t length(size_t dimensionIndex = 0)() const @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; 11| auto l = _slice._lengths[dimension]; 11| auto n = _chunkLengths[dimensionIndex]; 11| return l / n + (l % n != 0); | } | | /// ditto | auto front(size_t dimensionIndex = 0)() @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; 76| assert(_chunkLengths[dimensionIndex] <= _slice._lengths[dimension]); 76| auto ret = _slice.selectFront!dimension(_chunkLengths[dimensionIndex]); 76| return _wrap!dimensionIndex(ret); | } | | /// | auto back(size_t dimensionIndex = 0)() @property | if (dimensionIndex < dimensions.length) | { 13| assert(!empty!dimensionIndex); | enum dimension = dimensions[dimensionIndex]; 13| auto l = _slice._lengths[dimension]; 13| auto n = _chunkLengths[dimensionIndex]; 13| auto rshift = l % n; 13| rshift = !rshift ? n : rshift; 13| auto len = _slice._lengths[dimension]; 13| auto ret = _slice.select!dimension(len - rshift, len); 13| return _wrap!dimensionIndex(ret); | } | | /// ditto | void popFront(size_t dimensionIndex = 0)() | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; 47| assert(!empty!dimensionIndex); 47| _slice.popFrontExactly!dimension(_chunkLengths[dimensionIndex]); 47| _norm!dimensionIndex; | } | | /// ditto | void popBack(size_t dimensionIndex = 0)() | if (dimensionIndex < dimensions.length) | { 9| assert(!empty!dimensionIndex); | enum dimension = dimensions[dimensionIndex]; 9| auto l = _slice._lengths[dimension]; 9| auto n = _chunkLengths[dimensionIndex]; 9| auto rshift = l % n; 9| rshift = !rshift ? n : rshift; 9| _slice.popBackExactly!dimension(rshift); 9| _norm!dimensionIndex; | } | | /// ditto | Slice!(IotaIterator!size_t) opSlice(size_t dimensionIndex)(size_t i, size_t j) const | if (dimensionIndex < dimensions.length) | in | { 1| assert(i <= j, | "Chunks.opSlice!" ~ dimensionIndex.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); | enum errorMsg = ": the right opSlice boundary must be less than or equal to the length of the given dimensionIndex."; 1| assert(j <= length!dimensionIndex, | "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); | } | do | { 1| return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /// ditto | ChunksSlice!() opSlice(size_t dimensionIndex)(size_t i, ChunksDollar!() j) const | if (dimensionIndex < dimensions.length) | in | { 2| assert(i <= j, | "Chunks.opSlice!" ~ dimensionIndex.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); | enum errorMsg = ": the right opSlice boundary must be less than or equal to the length of the given dimensionIndex."; 2| assert(j <= length!dimensionIndex, | "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); | } | do | { 2| return typeof(return)(i, j); | } | | /// ditto | ChunksDollar!() opDollar(size_t dimensionIndex)() @property | { | enum dimension = dimensions[dimensionIndex]; 5| return ChunksDollar!()(_slice._lengths[dimension], _chunkLengths[dimensionIndex]); | } | | /// ditto | auto opIndex(Slices...)(Slices slices) | if (Slices.length <= dimensions.length) | { | static if (slices.length == 0) | { 3| return this; | } | else | { | alias slice = slices[0]; | alias S = Slices[0]; | static if (isIndex!S) | { 9| auto next = this.select!0(slice); | } | else | static if (is_Slice!S) | { 1| auto i = slice._iterator._index; 1| auto j = i + slice._lengths[0]; 1| auto next = this.select!0(i, j); | } | else | { 2| auto next = this.select!0(slice.i, slice.j); | } | static if (slices.length > 1) | { 1| return next[slices[1 .. $]]; | } | else | { 11| return next; | } | } | } | | /// ditto | auto opIndex()(size_t[dimensions.length] index) | { | auto next = this.select!0(index[0]); | static if (dimensions.length == 1) | { | return next; | } | else | { | return next[index[1 .. $]]; | } | } | | /// ditto | auto save()() @property | { 4| return this; | } | | /// | auto select(size_t dimensionIndex = 0)(size_t index) @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; 9| auto chl = _chunkLengths[dimensionIndex]; 9| auto shiftL = chl * index; 9| assert(shiftL <= _slice._lengths[dimension]); 9| auto shiftR = shiftL + chl; 9| if (_expect(shiftR > _slice._lengths[dimension], false)) | { 4| shiftR = _slice._lengths[dimension]; | } 9| auto ret = _slice.select!dimension(shiftL, shiftR); 9| return _wrap!dimensionIndex(ret); | } | | /// ditto | auto select(size_t dimensionIndex = 0)(size_t i, size_t j) @property | if (dimensionIndex < dimensions.length) | { 3| assert(i <= j); | enum dimension = dimensions[dimensionIndex]; 3| auto chl = _chunkLengths[dimensionIndex]; 3| auto shiftL = chl * i; 3| auto shiftR = chl * j; 3| assert(shiftL <= _slice._lengths[dimension]); 3| assert(shiftR <= _slice._lengths[dimension]); 3| if (_expect(shiftR > _slice._lengths[dimension], false)) | { 0000000| shiftR = _slice._lengths[dimension]; 0000000| if (_expect(shiftL > _slice._lengths[dimension], false)) | { 0000000| shiftL = _slice._lengths[dimension]; | } | } 3| auto ret = _slice.select!dimension(shiftL, shiftR); | import std.meta: aliasSeqOf; 3| return ret.chunks!(aliasSeqOf!dimensions)(_chunkLengths); | } | | // undocumented | auto select(size_t dimensionIndex = 0)(ChunksSlice!() sl) @property | if (dimensionIndex < dimensions.length) | { | assert(sl.i <= _slice._lengths[dimension]); | assert(sl.chunkLength == _chunkLengths[dimensionIndex]); | assert(sl.length == _slice._lengths[dimension]); | | enum dimension = dimensions[dimensionIndex]; | auto chl = _chunkLengths[dimensionIndex]; | auto len = sl.i * chl; | assert(len <= _slice._lengths[dimension]); | if (_expect(len > _slice._lengths[dimension], false)) | len = _slice._lengths[dimension]; | auto ret = _slice.selectBack!dimension(len); | import std.meta: aliasSeqOf; | return ret.chunks!(aliasSeqOf!dimensions)(_chunkLengths); | } |} | |// undocumented |struct ChunksSlice() |{ | size_t i; | ChunksDollar!() j; |} | |// undocumented |struct ChunksDollar() |{ | size_t length; | size_t chunkLength; | size_t value()() @property | { 11| return length / chunkLength + (length % chunkLength != 0); | } | alias value this; |} | |/++ |Checks if T is $(LREF Chunks) type. |Returns: | array of dimension indices. |+/ |template isChunks(T) |{ | static if (is(T : Chunks!(dimensions, Iterator, N, kind), size_t[] dimensions, Iterator, size_t N, SliceKind kind)) | enum isChunks = dimensions; | else | enum isChunks = size_t[].init; |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.chunks: chunks, isChunks; | import mir.ndslice.topology: iota; | | static assert(isChunks!int == null); | static assert(isChunks!(typeof(iota(20, 30).chunks!(1, 0)(3, 7))) == [1, 0]); |} | |/++ |Evaluates `popFront!dimmensionIndex` for multiple $(LREF Chunks) structures at once. |All chunks structures must have for the appropriate dimension the same chunk lengths and the same underlying slice lengths. | |Params: | dimmensionIndex = dimensionIndex | master = the fist chunks structure | followers = following chunks structures |+/ |void popFrontTuple(size_t dimmensionIndex = 0, Master, Followers...)(ref Master master, ref Followers followers) | if (isChunks!Master && allSatisfy!(isChunks, Followers)) |in |{ 1| foreach (ref follower; followers) | { 1| assert(follower.sliceLength!dimmensionIndex == master.sliceLength!dimmensionIndex); 1| assert(follower._chunkLengths[dimmensionIndex] == master._chunkLengths[dimmensionIndex]); | } |} |do |{ 1| master._slice.popFrontExactly!(isChunks!Master[dimmensionIndex])(master._chunkLengths[dimmensionIndex]); 1| foreach (i, ref follower; followers) | { 1| follower._slice.popFrontExactly!(isChunks!(Followers[i])[dimmensionIndex])(master._chunkLengths[dimmensionIndex]); | // hint for optimizer 1| follower.sliceLength!dimmensionIndex = master.sliceLength!dimmensionIndex; | } 1| if (_expect(master.sliceLength!dimmensionIndex < master._chunkLengths[dimmensionIndex], false) && master.sliceLength!dimmensionIndex) | { 0000000| master._chunkLengths[dimmensionIndex] = master.sliceLength!dimmensionIndex; 0000000| foreach(ref follower; followers) | { 0000000| follower._chunkLengths[dimmensionIndex] = master._chunkLengths[dimmensionIndex]; | } | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.chunks: chunks; | import mir.ndslice.topology: iota; | 1| auto a = iota(10, 20).chunks!(0, 1)(3, 7); 1| auto b = iota(20, 10).chunks!(1, 0)(3, 7); | 1| auto as = a; 1| auto bs = b; | 1| as.popFront; 1| bs.popFront; | 1| popFrontTuple(a, b); | 1| assert(as.slice == a.slice); 1| assert(bs.slice == b.slice); | 1| assert(as.chunkLengths == a.chunkLengths); 1| assert(bs.chunkLengths == b.chunkLengths); |} source/mir/ndslice/chunks.d is 95% covered <<<<<< EOF # path=source-mir-ndslice-concatenation.lst |/++ |This is a submodule of $(MREF mir, ndslice). | |The module contains $(LREF ._concatenation) routine. |It construct $(LREF Concatenation) structure that can be |assigned to an ndslice of the same shape with `[] = ` or `[] op= `. | |$(SUBREF slice, slicedNdField) can be used to construct ndslice view on top of $(LREF Concatenation). | |$(SUBREF allocation, slice) has special overload for $(LREF Concatenation) that can be used to allocate new ndslice. | |$(BOOKTABLE $(H2 Concatenation constructors), |$(TR $(TH Function Name) $(TH Description)) |$(T2 ._concatenation, Creates a $(LREF Concatenation) view of multiple slices.) |$(T2 pad, Pads with a constant value.) |$(T2 padEdge, Pads with the edge values of slice.) |$(T2 padSymmetric, Pads with the reflection of the slice mirrored along the edge of the slice.) |$(T2 padWrap, Pads with the wrap of the slice along the axis. The first values are used to pad the end and the end values are used to pad the beginning.) |) | | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |See_also: $(SUBMODULE fuse) submodule. | |Macros: |SUBMODULE = $(MREF_ALTTEXT $1, mir, ndslice, $1) |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.concatenation; | |import std.traits; |import std.meta; | |import mir.internal.utility; |import mir.math.common: optmath; |import mir.ndslice.internal; |import mir.ndslice.slice; |import mir.primitives; | |@optmath: | |private template _expose(size_t maxN, size_t dim) |{ | static @optmath auto _expose(S)(S s) | { | static if (s.N == maxN) | { 4| return s; | } | else | { | static assert(s.shape.length == s.N, "Cannot create concatenation for packed slice of smaller dimension."); | import mir.ndslice.topology: repeat, unpack; 4| auto r = s.repeat(1).unpack; | static if (dim) | { | import mir.ndslice.dynamic: transposed; 1| return r.transposed!(Iota!(1, dim + 1)); | } | else | { 3| return r; | } | } | } |} | |private template _Expose(size_t maxN, size_t dim) |{ | alias _expose = ._expose!(maxN, dim); | alias _Expose(S) = ReturnType!(_expose!S); |} | | |/++ |Creates a $(LREF Concatenation) view of multiple slices. | |Can be used in combination with itself, $(LREF until), $(SUBREF allocation, slice), |and $(SUBREF slice, Slice) assignment. | |Params: | slices = tuple of slices and/or concatenations. | |Returns: $(LREF Concatenation). |+/ |auto concatenation(size_t dim = 0, Slices...)(Slices slices) |{ | static if (allSatisfy!(templateOr!(isSlice, isConcatenation), Slices)) | { | import mir.algorithm.iteration: reduce; | import mir.utility: min, max; | enum NOf(S) = S.N; | enum NArray = [staticMap!(NOf, Slices)]; | enum minN = size_t.max.reduce!min(NArray); | enum maxN = size_t.min.reduce!max(NArray); | static if (minN == maxN) | { | import core.lifetime: forward; 111| return Concatenation!(dim, Slices)(forward!slices); | } | else | { | import core.lifetime: move; | static assert(minN + 1 == maxN); | alias S = staticMap!(_Expose!(maxN, dim), Slices); 4| Concatenation!(dim, S) ret; 8| foreach (i, ref e; ret._slices) 8| e = _expose!(maxN, dim)(move(slices[i])); 4| return ret; | } | } | else | { | import core.lifetime: forward; 2| return .concatenation(toSlices!(forward!slices)); | } |} | |/// Concatenation of slices with different dimmensions. |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: repeat, iota; | | // 0 0 0 1| auto vector = size_t.init.repeat([3]); | | // 1 2 3 | // 4 5 6 1| auto matrix = iota([2, 3], 1); | 1| assert(concatenation(vector, matrix).slice == [ | [0, 0, 0], | [1, 2, 3], | [4, 5, 6], | ]); | 1| vector.popFront; 1| assert(concatenation!1(vector, matrix).slice == [ | [0, 1, 2, 3], | [0, 4, 5, 6], | ]); |} | |/// Multidimensional |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | import mir.ndslice.slice : slicedNdField; | | // 0, 1, 2 | // 3, 4, 5 1| auto a = iota(2, 3); | // 0, 1 | // 2, 3 1| auto b = iota(2, 2); | // 0, 1, 2, 3, 4 1| auto c = iota(1, 5); | | // 0, 1, 2, 0, 1 | // 3, 4, 5, 2, 3 | // | // 0, 1, 2, 3, 4 | // construction phase 1| auto s = concatenation(concatenation!1(a, b), c); | | // allocation phase 1| auto d = s.slice; 1| assert(d == [ | [0, 1, 2, 0, 1], | [3, 4, 5, 2, 3], | [0, 1, 2, 3, 4], | ]); | | // optimal fragmentation for output/writing/buffering 1| auto testData = [ | [0, 1, 2], [0, 1], | [3, 4, 5], [2, 3], | [0, 1, 2, 3, 4], | ]; 1| size_t i; 1| s.forEachFragment!((fragment) { | pragma(inline, false); //reduces template bloat 5| assert(fragment == testData[i++]); | }); 1| assert(i == testData.length); | | // lazy ndslice view 1| assert(s.slicedNdField == d); |} | |/// 1D |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | import mir.ndslice.slice : slicedNdField; | 1| size_t i; 1| auto a = 3.iota; 1| auto b = iota([6], a.length); 1| auto s = concatenation(a, b); 1| assert(s.length == a.length + b.length); | // fast iteration with until 19| s.until!((elem){ assert(elem == i++); return false; }); | // allocation with slice 1| assert(s.slice == s.length.iota); | // 1D or multidimensional assignment 1| auto d = slice!double(s.length); 1| d[] = s; 1| assert(d == s.length.iota); 1| d.opIndexOpAssign!"+"(s); 1| assert(d == iota([s.length], 0, 2)); | | // lazy ndslice view 1| assert(s.slicedNdField == s.length.iota); |} | |/// |enum bool isConcatenation(T) = is(T : Concatenation!(dim, Slices), size_t dim, Slices...); |/// |enum size_t concatenationDimension(T : Concatenation!(dim, Slices), size_t dim, Slices...) = dim; | |/// |struct Concatenation(size_t dim, Slices...) | if (Slices.length > 1) |{ | @optmath: | | | /// Slices and sub-concatenations | Slices _slices; | | package enum N = typeof(Slices[0].shape).length; | | static assert(dim < N); | | alias DeepElement = CommonType!(staticMap!(DeepElementType, Slices)); | | /// | auto lightConst()() const @property | { | import std.format; | import mir.qualifier; | import mir.ndslice.topology: iota; 3| return mixin("Concatenation!(dim, staticMap!(LightConstOf, Slices))(%(_slices[%s].lightConst,%)].lightConst)".format(_slices.length.iota)); | } | | /// | auto lightImmutable()() immutable @property | { | import std.format; | import mir.ndslice.topology: iota; | import mir.qualifier; | return mixin("Concatenation!(dim, staticMap!(LightImmutableOf, Slices))(%(_slices[%s].lightImmutable,%)].lightImmutable)".format(_slices.length.iota)); | } | | /// Length primitive | size_t length(size_t d = 0)() scope const @property | { | static if (d == dim) | { 85| size_t length; 218| foreach(ref slice; _slices) 218| length += slice.length!d; 85| return length; | } | else | { 62| return _slices[0].length!d; | } | } | | /// Total elements count in the concatenation. | size_t elementCount()() scope const @property | { | size_t count = 1; | foreach(i; Iota!N) | count *= length!i; | return count; | } | | /// Shape of the concatenation. | size_t[N] shape()() scope const @property | { 68| typeof(return) ret; | foreach(i; Iota!N) 89| ret[i] = length!i; 68| return ret; | } | | /// Multidimensional input range primitives | bool empty(size_t d = 0)() scope const @property | { | static if (d == dim) | { 45| foreach(ref slice; _slices) 45| if (!slice.empty!d) 24| return false; 2| return true; | } | else | { 3| return _slices[0].empty!d; | } | } | | /// ditto | void popFront(size_t d = 0)() scope | { | static if (d == dim) | { 602| foreach(i, ref slice; _slices) | { | static if (i != Slices.length - 1) 494| if (slice.empty!d) 294| continue; 616| return slice.popFront!d; | } | } | else | { 124| foreach_reverse (ref slice; _slices) 124| slice.popFront!d; | } | } | | /// ditto | auto ref front(size_t d = 0)() return scope | { | static if (d == dim) | { 609| foreach(i, ref slice; _slices) | { | static if (i != Slices.length - 1) 499| if (slice.empty!d) 296| continue; 313| return slice.front!d; | } | } | else | { | import mir.ndslice.internal: frontOfDim; | enum elemDim = d < dim ? dim - 1 : dim; 50| return concatenation!elemDim(frontOfDim!(d, _slices)); | } | } | | /// Simplest multidimensional random access primitive | auto opIndex()(size_t[N] indices...) | { 1035| foreach(i, ref slice; _slices[0 .. $-1]) | { 1035| ptrdiff_t diff = indices[dim] - slice.length!dim; 1035| if (diff < 0) 1019| return slice[indices]; 16| indices[dim] = diff; | } 16| assert(indices[dim] < _slices[$-1].length!dim); 16| return _slices[$-1][indices]; | } |} | | |/++ |Performs `fun(st.front!d)`. | |This functions is useful when `st.front!d` has not a common type and fails to compile. | |Can be used instead of $(LREF .Concatenation.front) |+/ |auto applyFront(size_t d = 0, alias fun, size_t dim, Slices...)(Concatenation!(dim, Slices) st) |{ | static if (d == dim) | { | foreach(i, ref slice; st._slices) | { | static if (i != Slices.length - 1) | if (slice.empty!d) | continue; | return fun(slice.front!d); | } | } | else | { | import mir.ndslice.internal: frontOfDim; | enum elemDim = d < dim ? dim - 1 : dim; 2| auto slices = st._slices; 4| return fun(concatenation!elemDim(frontOfDim!(d, slices))); | } |} | |/++ |Pads with a constant value. | |Params: | direction = padding direction. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | s = $(SUBREF slice, Slice) or ndField | value = initial value for padding | lengths = list of lengths | |Returns: $(LREF Concatenation) | |See_also: $(LREF ._concatenation) examples. |+/ |auto pad(string direction = "both", S, T, size_t N)(S s, T value, size_t[N] lengths...) | if (hasShape!S && N == typeof(S.shape).length) |{ 2| return .pad!([Iota!N], [Repeat!(N, direction)])(s, value, lengths); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([3], 1) | .pad(0, [2]) | .slice; | 1| assert(pad == [0, 0, 1, 2, 3, 0, 0]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .pad(0, [2, 1]) | .slice; | 1| assert(pad == [ | [0, 0, 0, 0], | [0, 0, 0, 0], | | [0, 1, 2, 0], | [0, 3, 4, 0], | | [0, 0, 0, 0], | [0, 0, 0, 0]]); |} | |/++ |Pads with a constant value. | |Params: | dimensions = dimensions to pad. | directions = padding directions. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | |Returns: $(LREF Concatenation) | |See_also: $(LREF ._concatenation) examples. |+/ |template pad(size_t[] dimensions, string[] directions) | if (dimensions.length && dimensions.length == directions.length) |{ | @optmath: | | /++ | Params: | s = $(SUBREF slice, Slice) or ndField | value = initial value for padding | lengths = list of lengths | Returns: $(LREF Concatenation) | See_also: $(LREF ._concatenation) examples. | +/ | auto pad(S, T)(S s, T value, size_t[dimensions.length] lengths...) | { | import mir.ndslice.topology: repeat; | | enum d = dimensions[$ - 1]; | enum q = directions[$ - 1]; | enum N = typeof(S.shape).length; | 6| size_t[N] len; 6| auto _len = s.shape; | foreach(i; Iota!(len.length)) | static if (i != d) 5| len[i] = _len[i]; | else 6| len[i] = lengths[$ - 1]; | 6| auto p = repeat(value, len); | static if (q == "both") 4| auto r = concatenation!d(p, s, p); | else | static if (q == "pre") 1| auto r = concatenation!d(p, s); | else | static if (q == "post") 1| auto r = concatenation!d(s, p); | else | static assert(0, `allowed directions are "both", "pre", and "post"`); | | static if (dimensions.length == 1) 4| return r; | else 2| return .pad!(dimensions[0 .. $ - 1], directions[0 .. $ - 1])(r, value, lengths[0 .. $ -1]); | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .pad!([1], ["pre"])(0, [2]) | .slice; | 1| assert(pad == [ | [0, 0, 1, 2], | [0, 0, 3, 4]]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .pad!([0, 1], ["both", "post"])(0, [2, 1]) | .slice; | 1| assert(pad == [ | [0, 0, 0], | [0, 0, 0], | | [1, 2, 0], | [3, 4, 0], | | [0, 0, 0], | [0, 0, 0]]); |} | |/++ |Pads with the wrap of the slice along the axis. The first values are used to pad the end and the end values are used to pad the beginning. | |Params: | direction = padding direction. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | s = $(SUBREF slice, Slice) | lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length. |Returns: $(LREF Concatenation) |See_also: $(LREF ._concatenation) examples. |+/ |auto padWrap(string direction = "both", Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[N] lengths...) |{ 2| return .padWrap!([Iota!N], [Repeat!(N, direction)])(s, lengths); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([3], 1) | .padWrap([2]) | .slice; | 1| assert(pad == [2, 3, 1, 2, 3, 1, 2]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .padWrap([2, 1]) | .slice; | 1| assert(pad == [ | [2, 1, 2, 1], | [4, 3, 4, 3], | | [2, 1, 2, 1], | [4, 3, 4, 3], | | [2, 1, 2, 1], | [4, 3, 4, 3]]); |} | |/++ |Pads with the wrap of the slice along the axis. The first values are used to pad the end and the end values are used to pad the beginning. | |Params: | dimensions = dimensions to pad. | directions = padding directions. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | |Returns: $(LREF Concatenation) | |See_also: $(LREF ._concatenation) examples. |+/ |template padWrap(size_t[] dimensions, string[] directions) | if (dimensions.length && dimensions.length == directions.length) |{ | @optmath: | | /++ | Params: | s = $(SUBREF slice, Slice) | lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length. | Returns: $(LREF Concatenation) | See_also: $(LREF ._concatenation) examples. | +/ | auto padWrap(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[dimensions.length] lengths...) | { | enum d = dimensions[$ - 1]; | enum q = directions[$ - 1]; | | static if (d == 0 || kind != Contiguous) | { | alias _s = s; | } | else | { | import mir.ndslice.topology: canonical; 3| auto _s = s.canonical; | } | 9| assert(lengths[$ - 1] <= s.length!d); | | static if (dimensions.length != 1) | alias next = .padWrap!(dimensions[0 .. $ - 1], directions[0 .. $ - 1]); | | static if (q == "pre" || q == "both") | { 8| auto _pre = _s; 8| _pre.popFrontExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias pre = _pre; | else 1| auto pre = next(_pre, lengths[0 .. $ - 1]); | } | | static if (q == "post" || q == "both") | { 8| auto _post = _s; 8| _post.popBackExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias post = _post; | else 2| auto post = next(_post, lengths[0 .. $ - 1]); | } | | static if (dimensions.length == 1) | alias r = s; | else 2| auto r = next(s, lengths[0 .. $ - 1]); | | static if (q == "both") 7| return concatenation!d(pre, r, post); | else | static if (q == "pre") 1| return concatenation!d(pre, r); | else | static if (q == "post") 1| return concatenation!d(r, post); | else | static assert(0, `allowed directions are "both", "pre", and "post"`); | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 3], 1) | .padWrap!([1], ["pre"])([1]) | .slice; | 1| assert(pad == [ | [3, 1, 2, 3], | [6, 4, 5, 6]]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .padWrap!([0, 1], ["both", "post"])([2, 1]) | .slice; | 1| assert(pad == [ | [1, 2, 1], | [3, 4, 3], | | [1, 2, 1], | [3, 4, 3], | | [1, 2, 1], | [3, 4, 3]]); |} | |/++ |Pads with the reflection of the slice mirrored along the edge of the slice. | |Params: | direction = padding direction. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | s = $(SUBREF slice, Slice) | lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length. |Returns: $(LREF Concatenation) |See_also: $(LREF ._concatenation) examples. |+/ |auto padSymmetric(string direction = "both", Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[N] lengths...) |{ 2| return .padSymmetric!([Iota!N], [Repeat!(N, direction)])(s, lengths); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([3], 1) | .padSymmetric([2]) | .slice; | 1| assert(pad == [2, 1, 1, 2, 3, 3, 2]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .padSymmetric([2, 1]) | .slice; | 1| assert(pad == [ | [3, 3, 4, 4], | [1, 1, 2, 2], | | [1, 1, 2, 2], | [3, 3, 4, 4], | | [3, 3, 4, 4], | [1, 1, 2, 2]]); |} | |/++ |Pads with the reflection of the slice mirrored along the edge of the slice. | |Params: | dimensions = dimensions to pad. | directions = padding directions. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | |Returns: $(LREF Concatenation) | |See_also: $(LREF ._concatenation) examples. |+/ |template padSymmetric(size_t[] dimensions, string[] directions) | if (dimensions.length && dimensions.length == directions.length) |{ | @optmath: | | /++ | Params: | s = $(SUBREF slice, Slice) | lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length. | Returns: $(LREF Concatenation) | See_also: $(LREF ._concatenation) examples. | +/ | auto padSymmetric(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[dimensions.length] lengths...) | { | enum d = dimensions[$ - 1]; | enum q = directions[$ - 1]; | import mir.ndslice.dynamic: reversed; | | | static if (kind == Contiguous) | { | import mir.ndslice.topology: canonical; 6| auto __s = s.canonical; | } | else | { | alias __s = s; | } | | static if (kind == Universal || d != N - 1) | { 5| auto _s = __s.reversed!d; | } | else | static if (N == 1) | { | import mir.ndslice.topology: retro; 1| auto _s = s.retro; | } | else | { | import mir.ndslice.topology: retro; 3| auto _s = __s.retro.reversed!(Iota!d, Iota!(d + 1, N)); | } | 9| assert(lengths[$ - 1] <= s.length!d); | | static if (dimensions.length != 1) | alias next = .padSymmetric!(dimensions[0 .. $ - 1], directions[0 .. $ - 1]); | | static if (q == "pre" || q == "both") | { 8| auto _pre = _s; 8| _pre.popFrontExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias pre = _pre; | else 1| auto pre = next(_pre, lengths[0 .. $ - 1]); | } | | static if (q == "post" || q == "both") | { 8| auto _post = _s; 8| _post.popBackExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias post = _post; | else 2| auto post = next(_post, lengths[0 .. $ - 1]); | } | | static if (dimensions.length == 1) | alias r = s; | else 2| auto r = next(s, lengths[0 .. $ - 1]); | | static if (q == "both") 7| return concatenation!d(pre, r, post); | else | static if (q == "pre") 1| return concatenation!d(pre, r); | else | static if (q == "post") 1| return concatenation!d(r, post); | else | static assert(0, `allowed directions are "both", "pre", and "post"`); | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 3], 1) | .padSymmetric!([1], ["pre"])([2]) | .slice; | 1| assert(pad == [ | [2, 1, 1, 2, 3], | [5, 4, 4, 5, 6]]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .padSymmetric!([0, 1], ["both", "post"])([2, 1]) | .slice; | 1| assert(pad == [ | [3, 4, 4], | [1, 2, 2], | | [1, 2, 2], | [3, 4, 4], | | [3, 4, 4], | [1, 2, 2]]); |} | |/++ |Pads with the edge values of slice. | |Params: | direction = padding direction. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | s = $(SUBREF slice, Slice) | lengths = list of lengths for each dimension. |Returns: $(LREF Concatenation) |See_also: $(LREF ._concatenation) examples. |+/ |auto padEdge(string direction = "both", Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[N] lengths...) |{ 2| return .padEdge!([Iota!N], [Repeat!(N, direction)])(s, lengths); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([3], 1) | .padEdge([2]) | .slice; | 1| assert(pad == [1, 1, 1, 2, 3, 3, 3]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .padEdge([2, 1]) | .slice; | 1| assert(pad == [ | [1, 1, 2, 2], | [1, 1, 2, 2], | | [1, 1, 2, 2], | [3, 3, 4, 4], | | [3, 3, 4, 4], | [3, 3, 4, 4]]); |} | |/++ |Pads with the edge values of slice. | |Params: | dimensions = dimensions to pad. | directions = padding directions. | Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`. | |Returns: $(LREF Concatenation) | |See_also: $(LREF ._concatenation) examples. |+/ |template padEdge(size_t[] dimensions, string[] directions) | if (dimensions.length && dimensions.length == directions.length) |{ | @optmath: | | /++ | Params: | s = $(SUBREF slice, Slice) | lengths = list of lengths for each dimension. | Returns: $(LREF Concatenation) | See_also: $(LREF ._concatenation) examples. | +/ | auto padEdge(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[dimensions.length] lengths...) | { | enum d = dimensions[$ - 1]; | enum q = directions[$ - 1]; | | static if (kind == Universal) | { | alias _s = s; | } | else | static if (d != N - 1) | { | import mir.ndslice.topology: canonical; 3| auto _s = s.canonical; | } | else | { | import mir.ndslice.topology: universal; 3| auto _s = s.universal; | } | | static if (dimensions.length != 1) | alias next = .padEdge!(dimensions[0 .. $ - 1], directions[0 .. $ - 1]); | | static if (q == "pre" || q == "both") | { 8| auto _pre = _s; 8| _pre._strides[d] = 0; 8| _pre._lengths[d] = lengths[$ - 1]; | static if (dimensions.length == 1) | alias pre = _pre; | else 1| auto pre = next(_pre, lengths[0 .. $ - 1]); | | } | | static if (q == "post" || q == "both") | { 8| auto _post = _s; 8| _post._iterator += _post.backIndex!d; 8| _post._strides[d] = 0; 8| _post._lengths[d] = lengths[$ - 1]; | static if (dimensions.length == 1) | alias post = _post; | else 2| auto post = next(_post, lengths[0 .. $ - 1]); | } | | static if (dimensions.length == 1) | alias r = s; | else 2| auto r = next( s, lengths[0 .. $ - 1]); | | static if (q == "both") 7| return concatenation!d(pre, r, post); | else | static if (q == "pre") 1| return concatenation!d(pre, r); | else | static if (q == "post") 1| return concatenation!d(r, post); | else | static assert(0, `allowed directions are "both", "pre", and "post"`); | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 3], 1) | .padEdge!([0], ["pre"])([2]) | .slice; | 1| assert(pad == [ | [1, 2, 3], | [1, 2, 3], | | [1, 2, 3], | [4, 5, 6]]); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto pad = iota([2, 2], 1) | .padEdge!([0, 1], ["both", "post"])([2, 1]) | .slice; | 1| assert(pad == [ | [1, 2, 2], | [1, 2, 2], | | [1, 2, 2], | [3, 4, 4], | | [3, 4, 4], | [3, 4, 4]]); |} | |/++ |Iterates 1D fragments in $(SUBREF slice, Slice) or $(LREF Concatenation) in optimal for buffering way. | |See_also: $(LREF ._concatenation) examples. |+/ |template forEachFragment(alias pred) |{ | @optmath: | | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | { | /++ | Specialization for slices | Params: | sl = $(SUBREF slice, Slice) | +/ | void forEachFragment(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) sl) | { | static if (N == 1) | { 4| pred(sl); | } | else | static if (kind == Contiguous) | { | import mir.ndslice.topology: flattened; 1| pred(sl.flattened); | } | else | { | if (!sl.empty) do | { | .forEachFragment!pred(sl.front); | sl.popFront; | } | while(!sl.empty); | } | } | | /++ | Specialization for concatenations | Params: | st = $(LREF Concatenation) | +/ | void forEachFragment(size_t dim, Slices...)(Concatenation!(dim, Slices) st) | { | static if (dim == 0) | { 6| foreach (i, ref slice; st._slices) 6| .forEachFragment!pred(slice); | } | else | { 1| if (!st.empty) do | { 2| st.applyFront!(0, .forEachFragment!pred); 2| st.popFront; | } 2| while(!st.empty); | } | } | } | else | alias forEachFragment = .forEachFragment!(naryFun!pred); |} | |/++ |Iterates elements in $(SUBREF slice, Slice) or $(LREF Concatenation) |until pred returns true. | |Returns: false if pred returned false for all elements and true otherwise. | |See_also: $(LREF ._concatenation) examples. |+/ |template until(alias pred) |{ | @optmath: | | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!pred, pred)) | { | /++ | Specialization for slices | Params: | sl = $(SUBREF slice, Slice) | +/ | bool until(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) sl) | { | static if (N == 1) | { | pragma(inline, false); | alias f = pred; | } | else | alias f = .until!pred; 2| if (!sl.empty) do | { 9| if (f(sl.front)) 0000000| return true; 9| sl.popFront; | } 9| while(!sl.empty); 2| return false; | } | | /++ | Specialization for concatenations | Params: | st = $(LREF Concatenation) | +/ | bool until(size_t dim, Slices...)(Concatenation!(dim, Slices) st) | { | static if (dim == 0) | { 2| foreach (i, ref slice; st._slices) | { 2| if (.until!pred(slice)) 0000000| return true; | } | } | else | { | if (!st.empty) do | { | if (st.applyFront!(0, .until!pred)) | return true; | st.popFront; | } | while(!st.empty); | } 1| return false; | } | } | else | alias until = .until!(naryFun!pred); |} source/mir/ndslice/concatenation.d is 98% covered <<<<<< EOF # path=source-mir-ndslice-connect-cpython.lst |/++ |Utilities for $(LINK2 https://docs.python.org/3/c-api/buffer.html, Python Buffer Protocol). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) |STD = $(TD $(SMALL $0)) |PGB = $(LINK2 https://docs.python.org/3/c-api/buffer.html#c.PyObject_GetBuffer, PyObject_GetBuffer()) |+/ |module mir.ndslice.connect.cpython; | |import mir.ndslice.slice; |import core.stdc.config; |import std.traits; | |/++ |Construct flags for $(PGB). |If `T` is not `const` or `immutable` then the flags require writable buffer. |If slice kind is $(SUBREF slice, Contiguous) then the flags require $(LINK2 https://docs.python.org/3/c-api/buffer.html#contiguity-requests, c_contiguous) buffer. | |Params: | kind = slice kind | T = record type |Returns: | flags for $(LREF Py_buffer) request. |+/ |enum int pythonBufferFlags(SliceKind kind, T) = (kind == Contiguous ? PyBuf_c_contiguous : PyBuf_strides) | (is(T == const) || is (T == immutable) ? PyBuf_records_ro : PyBuf_records); | |/++ |Fills the slice (structure) from the python `view`. |The view should be created by $(PGB) that was called with $(LREF pythonBufferFlags). | |Params: | slice = output ndslice | view = $(LREF Py_buffer) requested |Returns: | one of the `input_buffer_*` $(LREF PythonBufferErrorCode) on failure and `success` otherwise. |+/ |PythonBufferErrorCode fromPythonBuffer(T, size_t N, SliceKind kind)(ref Slice!(T*, N, kind) slice, ref const Py_buffer view) nothrow @nogc @trusted | if (N <= PyBuf_max_ndim) |{ | import core.stdc.string: strcmp; | import mir.internal.utility: Iota; | | static if (!(is(T == const) || is(T == immutable))) | assert(!view.readonly); | | enum N = slice.N; | enum S = slice.S; | 0000000| if (N != view.ndim) 0000000| return typeof(return).input_buffer_ndim_mismatch; 0000000| if (T.sizeof != view.itemsize) 0000000| return typeof(return).input_buffer_itemsize_mismatch; 0000000| if (pythonBufferFormat!(Unqual!T).ptr.strcmp(view.format)) 0000000| return typeof(return).input_buffer_format_mismatch; 0000000| if (kind == Canonical && view.strides[N - 1] != T.sizeof) | return typeof(return).input_buffer_strides_mismatch; | | foreach(i; Iota!N) 0000000| slice._lengths[i] = view.shape[i]; | foreach(i; Iota!S) | { | assert(view.strides[i] % T.sizeof == 0); | slice._strides[i] = view.strides[i] / T.sizeof; | } 0000000| slice._iterator = cast(T*) view.buf; | 0000000| return typeof(return).success; |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: Slice; | auto bar(ref const Py_buffer view) | { 0000000| Slice!(const(double)*, 2) mat; 0000000| if (auto error = mat.fromPythonBuffer(view)) | { | // has null pointer | } 0000000| return mat; | } |} | |/++ |Fills the python view (structure) from the slice. |Params: | slice = input ndslice | view = output $(LREF Py_buffer). | $(LREF Py_buffer.internal) is initialized with null value, | $(LREF Py_buffer.obj) is not initialized. | Other $(LREF Py_buffer) fields are initialized according to the flags and slice. | flags = requester flags | structureBuffer = Single chunk of memory with the same alignment and size as $(SUBREF _slice, Structure). | The buffer is used to store shape and strides for the view. |Returns: | one of the `cannot_create_*` $(LREF PythonBufferErrorCode) on failure and `success` otherwise. |+/ |PythonBufferErrorCode toPythonBuffer(T, size_t N, SliceKind kind)(Slice!(T*, N, kind) slice, ref Py_buffer view, int flags, ref Structure!N structureBuffer) nothrow @nogc @trusted | if (N <= PyBuf_max_ndim) |{ 0000000| structureBuffer.lengths = slice._lengths; 0000000| structureBuffer.strides = slice.strides; | 0000000| foreach(ref stride; structureBuffer.strides) 0000000| stride *= T.sizeof; | | ///////////////////// | /// always filled /// | ///////////////////// 0000000| view.buf = slice._iterator; | // skip view.obj 0000000| view.len = slice.elementCount * T.sizeof; 0000000| view.itemsize = T.sizeof; 0000000| view.ndim = N; 0000000| view.internal = null; | | static if (kind != Contiguous) | { 0000000| bool check_single_memory_block; | } | | /// shape /// 0000000| if ((flags & PyBuf_nd) == PyBuf_nd) | { 0000000| view.shape = cast(sizediff_t*) structureBuffer.lengths.ptr; | /// strides /// 0000000| if ((flags & PyBuf_strides) == PyBuf_strides) 0000000| view.strides = cast(sizediff_t*) structureBuffer.strides.ptr; | else | { 0000000| view.strides = null; | static if (kind != Contiguous) 0000000| check_single_memory_block = true; | } | } | else | { 0000000| view.shape = null; 0000000| view.strides = null; | static if (kind != Contiguous) 0000000| check_single_memory_block = true; | } 0000000| view.suboffsets = null; | | /// ! structure verification ! /// | static if (kind == Contiguous) | { | static if (N != 1) | { 0000000| if ((flags & PyBuf_f_contiguous) == PyBuf_f_contiguous) | { | import mir.ndslice.dynamic: everted; | import mir.ndslice.topology: iota; 0000000| if (slice.everted.shape.iota.everted.strides != slice.strides) 0000000| return typeof(return).cannot_create_f_contiguous_buffer; | } | } | } | else | { | import mir.ndslice.dynamic: everted, normalizeStructure; | import mir.ndslice.topology: iota; 0000000| if ((flags & PyBuf_c_contiguous) == PyBuf_c_contiguous) | { 0000000| if (slice.shape.iota.strides != slice.strides && slice.everted.shape.iota.everted.strides != slice.strides) 0000000| return typeof(return).cannot_create_c_contiguous_buffer; | } | else 0000000| if ((flags & PyBuf_any_contiguous) == PyBuf_any_contiguous) | { 0000000| if (slice.shape.iota.strides != slice.strides && slice.everted.shape.iota.everted.strides != slice.strides) 0000000| return typeof(return).cannot_create_any_contiguous_buffer; | } | else 0000000| if (check_single_memory_block) | { 0000000| if (!slice.normalizeStructure) 0000000| return typeof(return).cannot_create_a_buffer_without_strides; | } | } | | /// readonly /// | static if (is(T == const) || is(T == immutable)) | { | if (flags & PyBuf_writable) | return typeof(return).cannot_create_writable_buffer; | view.readonly = 1; | } | else 0000000| view.readonly = 0; | | /// format /// 0000000| if (flags & PyBuf_format) | { | enum fmt = pythonBufferFormat!(Unqual!T); | static if (fmt is null) | return typeof(return).cannot_create_format_string; | else 0000000| view.format = cast(char*)fmt.ptr; | } | else 0000000| view.format = null; | 0000000| return typeof(return).success; |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice : Slice, Structure, Universal, Contiguous, SliceKind; | Py_buffer bar(SliceKind kind)(Slice!(double*, 2, kind) slice) | { | import core.stdc.stdlib; | enum N = 2; | 0000000| auto structurePtr = cast(Structure!N*) Structure!N.sizeof.malloc; 0000000| if (!structurePtr) | assert(0); 0000000| Py_buffer view; | 0000000| if (auto error = slice.toPythonBuffer(view, PyBuf_records_ro, *structurePtr)) | { 0000000| view = view.init; // null buffer 0000000| structurePtr.free; | } | else | { 0000000| assert(cast(sizediff_t*)&structurePtr.lengths == view.shape); 0000000| assert(cast(sizediff_t*)&structurePtr.strides == view.strides); | } | 0000000| return view; | } | | alias barUni = bar!Universal; | alias barCon = bar!Contiguous; |} | |/// Python $(LINK2 https://docs.python.org/3/c-api/buffer.html#buffer-structure, Buffer structure). |extern(C) |struct bufferinfo |{ | /// | void *buf; | /// | void *obj; | /// | sizediff_t len; | /// | sizediff_t itemsize; | /// | int readonly; | /// | int ndim; | /// | char *format; | /// | sizediff_t *shape; | /// | sizediff_t *strides; | /// | sizediff_t *suboffsets; | /// | void *internal; |} |/// ditto |alias Py_buffer = bufferinfo; | |/++ |Error codes for ndslice - Py_buffer conversion. |+/ |enum PythonBufferErrorCode |{ | /// | success, | /// | cannot_create_format_string, | /// | cannot_create_writable_buffer, | /// | cannot_create_f_contiguous_buffer, | /// | cannot_create_c_contiguous_buffer, | /// | cannot_create_any_contiguous_buffer, | /// | cannot_create_a_buffer_without_strides, | /// | input_buffer_ndim_mismatch, | /// | input_buffer_itemsize_mismatch, | /// | input_buffer_format_mismatch, | /// | input_buffer_strides_mismatch, |} | |/// |enum PyBuf_max_ndim = 64; | |/// |enum PyBuf_simple = 0; |/// |enum PyBuf_writable = 0x0001; |/// |enum PyBuf_writeable = PyBuf_writable; |/// |enum PyBuf_format = 0x0004; |/// |enum PyBuf_nd = 0x0008; |/// |enum PyBuf_strides = (0x0010 | PyBuf_nd); |/// |enum PyBuf_c_contiguous = (0x0020 | PyBuf_strides); |/// |enum PyBuf_f_contiguous = (0x0040 | PyBuf_strides); |/// |enum PyBuf_any_contiguous = (0x0080 | PyBuf_strides); |/// |enum PyBuf_indirect = (0x0100 | PyBuf_strides); | |/// |enum PyBuf_contig = (PyBuf_nd | PyBuf_writable); |/// |enum PyBuf_contig_ro = (PyBuf_nd); | |/// |enum PyBuf_strided = (PyBuf_strides | PyBuf_writable); |/// |enum PyBuf_strided_ro = (PyBuf_strides); | |/// |enum PyBuf_records = (PyBuf_strides | PyBuf_writable | PyBuf_format); |/// |enum PyBuf_records_ro = (PyBuf_strides | PyBuf_format); | |/++ |Returns $(HTTPS docs.python.org/3/c-api/buffer.html#c.Py_buffer.format, python format (type)) string. |For example, `"O"` for `PyObject` and "B" for ubyte. |+/ |template pythonBufferFormat(T) |{ | static if (is(T == struct) && __traits(identifier, A) == "PyObject") | enum pythonBufferFormat = "O"; | else | static if (is(Unqual!T == short)) | enum pythonBufferFormat = "h"; | else | static if (is(Unqual!T == ushort)) | enum pythonBufferFormat = "H"; | else | static if (is(Unqual!T == int)) | enum pythonBufferFormat = "i"; | else | static if (is(Unqual!T == uint)) | enum pythonBufferFormat = "I"; | else | static if (is(Unqual!T == float)) | enum pythonBufferFormat = "f"; | else | static if (is(Unqual!T == double)) | enum pythonBufferFormat = "d"; | else | static if (is(Unqual!T == long)) | enum pythonBufferFormat = "q"; | else | static if (is(Unqual!T == ulong)) | enum pythonBufferFormat = "Q"; | else | static if (is(Unqual!T == ubyte)) | enum pythonBufferFormat = "B"; | else | static if (is(Unqual!T == byte)) | enum pythonBufferFormat = "b"; | else | static if (is(Unqual!T == char)) | enum pythonBufferFormat = "c"; | else | static if (is(Unqual!T == char*)) | enum pythonBufferFormat = "z"; | else | static if (is(Unqual!T == void*)) | enum pythonBufferFormat = "P"; | else | static if (is(Unqual!T == bool)) | enum pythonBufferFormat = "?"; | else | static if (is(Unqual!T == wchar*)) | enum pythonBufferFormat = "Z"; | else | static if (is(Unqual!T == wchar)) | enum pythonBufferFormat = "u"; | else | { | static if (is(cpp_long)) | { | static if (is(Unqual!T == cpp_long)) | enum pythonBufferFormat = "l"; | else | enum pythonBufferFormat = null; | } | else | static if (is(cpp_ulong)) | { | static if (is(Unqual!T == cpp_ulong)) | enum pythonBufferFormat = "L"; | else | enum pythonBufferFormat = null; | } | else | static if (is(c_long_double)) | { | static if (is(Unqual!T == c_long_double)) | enum pythonBufferFormat = "g"; | else | enum pythonBufferFormat = null; | } | else | enum pythonBufferFormat = null; | } |} source/mir/ndslice/connect/cpython.d is 0% covered <<<<<< EOF # path=source-mir-ndslice-dynamic.lst |/++ |$(SCRIPT inhibitQuickIndex = 1;) | |This is a submodule of $(MREF mir, ndslice). | |Operators only change strides and lengths of a slice. |The range of a slice remains unmodified. |All operators return slice as the type of the argument, maybe except slice kind. | |$(BOOKTABLE $(H2 Transpose operators), | |$(TR $(TH Function Name) $(TH Description)) |$(T2 transposed, Permutes dimensions. $(BR) | `iota(3, 4, 5, 6, 7).transposed!(4, 0, 1).shape` returns `[7, 3, 4, 5, 6]`.) |$(T2 swapped, Swaps dimensions $(BR) | `iota(3, 4, 5).swapped!(1, 2).shape` returns `[3, 5, 4]`.) |$(T2 everted, Reverses the order of dimensions $(BR) | `iota(3, 4, 5).everted.shape` returns `[5, 4, 3]`.) |) |See also $(SUBREF topology, evertPack). | |$(BOOKTABLE $(H2 Iteration operators), | |$(TR $(TH Function Name) $(TH Description)) |$(T2 strided, Multiplies the stride of a selected dimension by a factor.$(BR) | `iota(13, 40).strided!(0, 1)(2, 5).shape` equals to `[7, 8]`.) |$(T2 reversed, Reverses the direction of iteration for selected dimensions. $(BR) | `slice.reversed!0` returns the slice with reversed direction of iteration for top level dimension.) |$(T2 allReversed, Reverses the direction of iteration for all dimensions. $(BR) | `iota(4, 5).allReversed` equals to `20.iota.retro.sliced(4, 5)`.) |) | |$(BOOKTABLE $(H2 Other operators), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 rotated, Rotates two selected dimensions by `k*90` degrees. $(BR) | `iota(2, 3).rotated` equals to `[[2, 5], [1, 4], [0, 3]]`.) |$(T2 dropToHypercube, Returns maximal multidimensional cube of a slice.) |$(T2 normalizeStructure, Reverses iteration order for dimensions with negative strides, they become not negative; |and sorts dimensions according to the strides, dimensions with larger strides are going first.) |) | |$(H2 Bifacial operators) | |Some operators are bifacial, |i.e. they have two versions: one with template parameters, and another one |with function parameters. Versions with template parameters are preferable |because they allow compile time checks and can be optimized better. | |$(BOOKTABLE , | |$(TR $(TH Function Name) $(TH Variadic) $(TH Template) $(TH Function)) |$(T4 swapped, No, `slice.swapped!(2, 3)`, `slice.swapped(2, 3)`) |$(T4 rotated, No, `slice.rotated!(2, 3)(-1)`, `slice.rotated(2, 3, -1)`) |$(T4 strided, Yes/No, `slice.strided!(1, 2)(20, 40)`, `slice.strided(1, 20).strided(2, 40)`) |$(T4 transposed, Yes, `slice.transposed!(1, 4, 3)`, `slice.transposed(1, 4, 3)`) |$(T4 reversed, Yes, `slice.reversed!(0, 2)`, `slice.reversed(0, 2)`) |) | |Bifacial interface of $(LREF drop), $(LREF dropBack) |$(LREF dropExactly), and $(LREF dropBackExactly) |is identical to that of $(LREF strided). | |Bifacial interface of $(LREF dropOne) and $(LREF dropBackOne) |is identical to that of $(LREF reversed). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments | |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) |+/ |module mir.ndslice.dynamic; | | |import std.traits; |import std.meta; | |import mir.math.common: optmath; |import mir.internal.utility: Iota; |import mir.ndslice.internal; |import mir.ndslice.slice; |import mir.utility; | |@optmath: | |/++ |Reverses iteration order for dimensions with negative strides, they become not negative; |and sorts dimensions according to the strides, dimensions with larger strides are going first. | |Params: | slice = a slice to normalize dimension |Returns: | `true` if the slice can be safely casted to $(SUBREF slice, Contiguous) kind using $(SUBREF topology, assumeContiguous) and false otherwise. |+/ |bool normalizeStructure(Iterator, size_t N, SliceKind kind)(ref Slice!(Iterator, N, kind) slice) |{ | static if (kind == Contiguous) | { 1| return true; | } | else | { | import mir.utility: min; | enum Y = min(slice.S, N); | foreach(i; Iota!Y) 6| if (slice._stride!i < 0) 3| slice = slice.reversed!i; | static if (N == 1) | return slice._stride!0 == 1; | else | static if (N == 2 && kind == Canonical) | { 2| return slice._stride!0 == slice.length!1; | } | else | { | import mir.series: series, sort; | import mir.ndslice.topology: zip, iota; 2| auto l = slice._lengths[0 .. Y]; 2| auto s = slice._strides[0 .. Y]; 2| s.series(l).sort!"a > b"; 2| return slice.shape.iota.strides == slice.strides; | } | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; | 1| auto g = iota(2, 3); //contiguous 1| auto c = g.reversed!0; //canonical 1| auto u = g.transposed.allReversed; //universal | 1| assert(g.normalizeStructure); 1| assert(c.normalizeStructure); 1| assert(u.normalizeStructure); | 1| assert(c == g); 1| assert(u == g); | 1| c.popFront!1; 1| u.popFront!1; | 1| assert(!c.normalizeStructure); 1| assert(!u.normalizeStructure); |} | |private enum _swappedCode = q{ | with (slice) | { | auto tl = _lengths[dimensionA]; | auto ts = _strides[dimensionA]; | _lengths[dimensionA] = _lengths[dimensionB]; | _strides[dimensionA] = _strides[dimensionB]; | _lengths[dimensionB] = tl; | _strides[dimensionB] = ts; | } | return slice; |}; | |/++ |Swaps two dimensions. | |Params: | slice = input slice | dimensionA = first dimension | dimensionB = second dimension |Returns: | n-dimensional slice |See_also: $(LREF everted), $(LREF transposed) |+/ |template swapped(size_t dimensionA, size_t dimensionB) |{ | /// | @optmath auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) | { | static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N) | { | alias slice = _slice; | } | else static if (dimensionA + 1 < N && dimensionB + 1 < N) | { | import mir.ndslice.topology: canonical; 1| auto slice = _slice.canonical; | } | else | { | import mir.ndslice.topology: universal; 2| auto slice = _slice.universal; | } | { | enum i = 0; | alias dimension = dimensionA; | mixin DimensionCTError; | } | { | enum i = 1; | alias dimension = dimensionB; | mixin DimensionCTError; | } | mixin (_swappedCode); | } |} | |/// ditto |auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimensionA, size_t dimensionB) |{ | import mir.ndslice.topology: universal; 4| auto slice = _slice.universal; | { | alias dimension = dimensionA; | mixin (DimensionRTError); | } | { | alias dimension = dimensionB; | mixin (DimensionRTError); | } | mixin (_swappedCode); |} | |/// ditto |Slice!(Iterator, 2, Universal) swapped(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice) |{ 1| return slice.swapped!(0, 1); |} | |/// Template |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | 1| assert(iota(3, 4, 5, 6) | .swapped!(2, 1) | .shape == cast(size_t[4])[3, 5, 4, 6]); | 1| assert(iota(3, 4, 5, 6) | .swapped!(3, 1) | .shape == cast(size_t[4])[3, 6, 5, 4]); |} | |/// Function |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | 1| assert(iota(3, 4, 5, 6) | .swapped(1, 2) | .shape == cast(size_t[4])[3, 5, 4, 6]); | 1| assert(iota(3, 4, 5, 6) | .swapped(1, 3) | .shape == cast(size_t[4])[3, 6, 5, 4]); |} | |/// 2D |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; 1| assert(iota(3, 4) | .swapped | .shape == cast(size_t[2])[4, 3]); |} | |private enum _rotatedCode = q{ | k &= 0b11; | if (k == 0) | return slice; | if (k == 2) | return slice.allReversed; | static if (__traits(compiles, { enum _enum = dimensionA + dimensionB; })) | { | slice = slice.swapped!(dimensionA, dimensionB); | if (k == 1) | return slice.reversed!dimensionA; | else | return slice.reversed!dimensionB; | } | else | { | slice = slice.swapped (dimensionA, dimensionB); | if (k == 1) | return slice.reversed(dimensionA); | else | return slice.reversed(dimensionB); | } |}; | |/++ |Rotates two selected dimensions by `k*90` degrees. |The order of dimensions is important. |If the slice has two dimensions, the default direction is counterclockwise. | |Params: | slice = input slice | dimensionA = first dimension | dimensionB = second dimension | k = rotation counter, can be negative |Returns: | n-dimensional slice |+/ |template rotated(size_t dimensionA, size_t dimensionB) |{ | /// | @optmath auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, sizediff_t k = 1) | { | static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N) | { | alias slice = _slice; | } | else static if (dimensionA + 1 < N && dimensionB + 1 < N) | { | import mir.ndslice.topology: canonical; | auto slice = _slice.canonical; | } | else | { | import mir.ndslice.topology: universal; 8| auto slice = _slice.universal; | } | { | enum i = 0; | alias dimension = dimensionA; | mixin DimensionCTError; | } | { | enum i = 1; | alias dimension = dimensionB; | mixin DimensionCTError; | } | mixin (_rotatedCode); | } |} | |/// ditto |auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimensionA, size_t dimensionB, sizediff_t k = 1) |{ | import mir.ndslice.topology: universal; 4| auto slice = _slice.universal; | { | alias dimension = dimensionA; | mixin (DimensionRTError); | } | { | alias dimension = dimensionB; | mixin (DimensionRTError); | } | mixin (_rotatedCode); |} | |/// ditto |Slice!(Iterator, 2, Universal) rotated(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice, sizediff_t k = 1) |{ 4| return .rotated!(0, 1)(slice, k); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; 1| auto slice = iota(2, 3); | 1| auto a = [[0, 1, 2], | [3, 4, 5]]; | 1| auto b = [[2, 5], | [1, 4], | [0, 3]]; | 1| auto c = [[5, 4, 3], | [2, 1, 0]]; | 1| auto d = [[3, 0], | [4, 1], | [5, 2]]; | 1| assert(slice.rotated ( 4) == a); 1| assert(slice.rotated!(0, 1)(-4) == a); 1| assert(slice.rotated (1, 0, 8) == a); | 1| assert(slice.rotated == b); 1| assert(slice.rotated!(0, 1)(-3) == b); 1| assert(slice.rotated (1, 0, 3) == b); | 1| assert(slice.rotated ( 6) == c); 1| assert(slice.rotated!(0, 1)( 2) == c); 1| assert(slice.rotated (0, 1, -2) == c); | 1| assert(slice.rotated ( 7) == d); 1| assert(slice.rotated!(0, 1)( 3) == d); 1| assert(slice.rotated (1, 0, ) == d); |} | |/++ |Reverses the order of dimensions. | |Params: | _slice = input slice |Returns: | n-dimensional slice |See_also: $(LREF swapped), $(LREF transposed) |+/ |auto everted(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) |{ | static if (kind == Universal) | { | alias slice = _slice; | } | else | { | import mir.ndslice.topology: universal; 1| auto slice = _slice.universal; | } | with(slice) foreach (i; Iota!(N / 2)) | { 3| swap(_lengths[i], _lengths[N - i - 1]); 3| swap(_strides[i], _strides[N - i - 1]); | } 3| return slice; |} | |/// |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; 1| assert(iota(3, 4, 5) | .everted | .shape == cast(size_t[3])[5, 4, 3]); |} | |private enum _transposedCode = q{ | size_t[typeof(slice).N] lengths_; | ptrdiff_t[max(typeof(slice).S, size_t(1))] strides_; | with(slice) foreach (i; Iota!N) | { | lengths_[i] = _lengths[perm[i]]; | static if (i < typeof(slice).S) | strides_[i] = _strides[perm[i]]; | } | with(slice) foreach (i; Iota!(N, slice.N)) | { | lengths_[i] = _lengths[i]; | static if (i < typeof(slice).S) | strides_[i] = _strides[i]; | } | return typeof(slice)(lengths_, strides_[0 .. typeof(slice).S], slice._iterator); |}; | |package size_t[N] completeTranspose(size_t N)(size_t[] dimensions) |{ 4| assert(dimensions.length <= N); 4| size_t[N] ctr; 4| uint[N] mask; 44| foreach (i, ref dimension; dimensions) | { 8| mask[dimension] = true; 8| ctr[i] = dimension; | } 4| size_t j = dimensions.length; 92| foreach (i, e; mask) 20| if (e == false) 12| ctr[j++] = i; 4| return ctr; |} | |/++ |N-dimensional transpose operator. |Brings selected dimensions to the first position. |Params: | slice = input slice | Dimensions = indices of dimensions to be brought to the first position | dimensions = indices of dimensions to be brought to the first position |Returns: | n-dimensional slice |See_also: $(LREF swapped), $(LREF everted) |+/ |template transposed(Dimensions...) | if (Dimensions.length) |{ | static if (allSatisfy!(isSize_t, Dimensions)) | /// | @optmath auto transposed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) | { | import mir.algorithm.iteration: any; | enum s = N; | static if ([Dimensions] == [Iota!(Dimensions.length)]) | { 75| return _slice; | } | else | { | import core.lifetime: move; 0000000| enum hasRowStride = [Dimensions].any!(a => a + 1 == s); | static if (kind == Universal || kind == Canonical && !hasRowStride) | { | alias slice = _slice; | } | else | static if (hasRowStride) | { | import mir.ndslice.topology: universal; 91| auto slice = _slice.move.universal; | } | else | { | import mir.ndslice.topology: canonical; 3| auto slice = _slice.move.canonical; | } | mixin DimensionsCountCTError; | foreach (i, dimension; Dimensions) | mixin DimensionCTError; | static assert(isValidPartialPermutation!(N)([Dimensions]), | "Failed to complete permutation of dimensions " ~ Dimensions.stringof | ~ tailErrorMessage!()); | enum perm = completeTranspose!(N)([Dimensions]); | static assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); | mixin (_transposedCode); | } | } | else | alias transposed = .transposed!(staticMap!(toSize_t, Dimensions)); |} | |///ditto |auto transposed(Iterator, size_t N, SliceKind kind, size_t M)(Slice!(Iterator, N, kind) _slice, size_t[M] dimensions...) |{ | import core.lifetime: move; | import mir.ndslice.topology: universal; 4| auto slice = _slice.move.universal; | | mixin (DimensionsCountRTError); 36| foreach (dimension; dimensions) | mixin (DimensionRTError); 4| assert(dimensions.isValidPartialPermutation!(N), | "Failed to complete permutation of dimensions." | ~ tailErrorMessage!()); 4| immutable perm = completeTranspose!(N)(dimensions); 4| assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); | mixin (_transposedCode); |} | |///ditto |Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice) |{ 12| return .transposed!(1, 0)(slice); |} | |/// Template |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | 1| assert(iota(3, 4, 5, 6, 7) | .transposed!(3, 1, 0) | .shape == cast(size_t[5])[6, 4, 3, 5, 7]); | 1| assert(iota(3, 4, 5, 6, 7) | .transposed!(4, 1, 0) | .shape == cast(size_t[5])[7, 4, 3, 5, 6]); |} | |/// Function |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | 1| assert(iota(3, 4, 5, 6, 7) | .transposed(3, 1, 0) | .shape == cast(size_t[5])[6, 4, 3, 5, 7]); | 1| assert(iota(3, 4, 5, 6, 7) | .transposed(4, 1, 0) | .shape == cast(size_t[5])[7, 4, 3, 5, 6]); |} | |/// Single-argument function |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | 1| assert(iota(3, 4, 5, 6, 7) | .transposed(3) | .shape == cast(size_t[5])[6, 3, 4, 5, 7]); | 1| assert(iota(3, 4, 5, 6, 7) | .transposed(4) | .shape == cast(size_t[5])[7, 3, 4, 5, 6]); |} | |/// _2-dimensional transpose |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; 1| assert(iota(3, 4) | .transposed | .shape == cast(size_t[2])[4, 3]); |} | |private enum _reversedCode = q{ | with (slice) | { | if (_lengths[dimension]) | _iterator += _strides[dimension] * (_lengths[dimension] - 1); | _strides[dimension] = -_strides[dimension]; | } |}; | |/++ |Reverses the direction of iteration for all dimensions. |Params: | _slice = input slice |Returns: | n-dimensional slice |+/ |Slice!(Iterator, N, Universal) allReversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) |{ | import core.lifetime: move; | import mir.ndslice.topology: universal; 9| auto slice = _slice.move.universal; | foreach (dimension; Iota!N) | { | mixin (_reversedCode); | } 9| return slice; |} | |/// |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota, retro; 1| assert(iota(4, 5).allReversed == iota(4, 5).retro); |} | |/++ |Reverses the direction of iteration for selected dimensions. | |Params: | _slice = input slice | Dimensions = indices of dimensions to reverse order of iteration | dimensions = indices of dimensions to reverse order of iteration |Returns: | n-dimensional slice |+/ |template reversed(Dimensions...) | if (Dimensions.length) |{ | static if (allSatisfy!(isSize_t, Dimensions)) | /// | @optmath auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) @trusted | { | import mir.algorithm.iteration: any; | enum s = N; 0000000| enum hasRowStride = [Dimensions].sliced.any!(a => a + 1 == s); | static if (kind == Universal || kind == Canonical && !hasRowStride) | { | alias slice = _slice; | } | else | static if (hasRowStride) | { | import mir.ndslice.topology: universal; 29| auto slice = _slice.universal; | } | else | { | import mir.ndslice.topology: canonical; 4| auto slice = _slice.canonical; | } | foreach (i, dimension; Dimensions) | { | mixin DimensionCTError; | mixin (_reversedCode); | } 63| return slice; | } | else | alias reversed = .reversed!(staticMap!(toSize_t, Dimensions)); |} | |///ditto |Slice!(Iterator, N, Universal) reversed(Iterator, size_t N, SliceKind kind, size_t M)(Slice!(Iterator, N, kind) _slice, size_t[M] dimensions...) | @trusted | if (M) |{ | import mir.ndslice.topology: universal; 16| auto slice = _slice.universal; 132| foreach (dimension; dimensions) | mixin (DimensionRTError); | foreach (i; Iota!(0, M)) | { 28| auto dimension = dimensions[i]; | mixin (_reversedCode); | } 16| return slice; |} | |/// ditto |auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) |{ 1| return .reversed!0(slice); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; | 1| auto slice = iota([2, 2], 1); 1| assert(slice == [[1, 2], [3, 4]]); | | // Default 1| assert(slice.reversed == [[3, 4], [1, 2]]); | | // Template 1| assert(slice.reversed! 0 == [[3, 4], [1, 2]]); 1| assert(slice.reversed! 1 == [[2, 1], [4, 3]]); 1| assert(slice.reversed!(0, 1) == [[4, 3], [2, 1]]); 1| assert(slice.reversed!(1, 0) == [[4, 3], [2, 1]]); 1| assert(slice.reversed!(1, 1) == [[1, 2], [3, 4]]); 1| assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]); | | // Function 1| assert(slice.reversed (0) == [[3, 4], [1, 2]]); 1| assert(slice.reversed (1) == [[2, 1], [4, 3]]); 1| assert(slice.reversed (0, 1) == [[4, 3], [2, 1]]); 1| assert(slice.reversed (1, 0) == [[4, 3], [2, 1]]); 1| assert(slice.reversed (1, 1) == [[1, 2], [3, 4]]); 1| assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota, canonical; 1| auto slice = iota([2, 2], 1).canonical; 1| assert(slice == [[1, 2], [3, 4]]); | | // Template 1| assert(slice.reversed! 0 == [[3, 4], [1, 2]]); 1| assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]); | | // Function 1| assert(slice.reversed (0) == [[3, 4], [1, 2]]); 1| assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]); |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration : equal; | import mir.ndslice.concatenation : concatenation; | import mir.ndslice.slice; | import mir.ndslice.topology; 2| auto i0 = iota([4], 0); auto r0 = i0.retro; 2| auto i1 = iota([4], 4); auto r1 = i1.retro; 2| auto i2 = iota([4], 8); auto r2 = i2.retro; 1| auto slice = iota(3, 4).universal; 1| assert(slice .flattened.equal(concatenation(i0, i1, i2))); | // Template 1| assert(slice.reversed!(0) .flattened.equal(concatenation(i2, i1, i0))); 1| assert(slice.reversed!(1) .flattened.equal(concatenation(r0, r1, r2))); 1| assert(slice.reversed!(0, 1) .flattened.equal(concatenation(r2, r1, r0))); 1| assert(slice.reversed!(1, 0) .flattened.equal(concatenation(r2, r1, r0))); 1| assert(slice.reversed!(1, 1) .flattened.equal(concatenation(i0, i1, i2))); 1| assert(slice.reversed!(0, 0, 0).flattened.equal(concatenation(i2, i1, i0))); | // Function 1| assert(slice.reversed (0) .flattened.equal(concatenation(i2, i1, i0))); 1| assert(slice.reversed (1) .flattened.equal(concatenation(r0, r1, r2))); 1| assert(slice.reversed (0, 1) .flattened.equal(concatenation(r2, r1, r0))); 1| assert(slice.reversed (1, 0) .flattened.equal(concatenation(r2, r1, r0))); 1| assert(slice.reversed (1, 1) .flattened.equal(concatenation(i0, i1, i2))); 1| assert(slice.reversed (0, 0, 0).flattened.equal(concatenation(i2, i1, i0))); |} | |private enum _stridedCode = q{ | assert(factor > 0, "factor must be positive" | ~ tailErrorMessage!()); | immutable rem = slice._lengths[dimension] % factor; | slice._lengths[dimension] /= factor; | if (slice._lengths[dimension]) //do not remove `if (...)` | slice._strides[dimension] *= factor; | if (rem) | slice._lengths[dimension]++; |}; | |/++ |Multiplies the stride of the selected dimension by a factor. | |Params: | Dimensions = indices of dimensions to be strided | dimension = indexe of a dimension to be strided | factor = step extension factors |Returns: | n-dimensional slice |+/ |auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, ptrdiff_t factor) |{ | import core.lifetime: move; | import std.meta: Repeat; 1| return move(slice).strided!(Iota!N)(Repeat!(N, factor)); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; | // 0 1 2 3 | // 4 5 6 7 | // 8 9 10 11 1| assert(iota(3, 4).strided(2) == [[0, 2], [8, 10]]); |} | |/// ditto |template strided(Dimensions...) | if (Dimensions.length) |{ | static if (allSatisfy!(isSize_t, Dimensions)) | /++ | Params: | _slice = input slice | factors = list of step extension factors | +/ | @optmath auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, Repeat!(Dimensions.length, ptrdiff_t) factors) | { | import mir.algorithm.iteration: any; | enum s = N; 0000000| enum hasRowStride = [Dimensions].sliced.any!(a => a + 1 == s); | static if (kind == Universal || kind == Canonical && !hasRowStride) | { | alias slice = _slice; | } | else | static if (hasRowStride) | { | import mir.ndslice.topology: universal; 6| auto slice = _slice.universal; | } | else | { | import mir.ndslice.topology: canonical; 1| auto slice = _slice.canonical; | } | foreach (i, dimension; Dimensions) | { | mixin DimensionCTError; 31| immutable factor = factors[i]; | mixin (_stridedCode); | } 27| return slice; | } | else | alias strided = .strided!(staticMap!(toSize_t, Dimensions)); |} | |///ditto |Slice!(Iterator, N, Universal) strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimension, ptrdiff_t factor) |{ | import mir.ndslice.topology: universal; 9| auto slice = _slice.universal; | mixin (DimensionRTError); | mixin (_stridedCode); 9| return slice; |} | |/// |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; 1| auto slice = iota(3, 4); | 1| assert(slice | == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]); | | // Template 1| assert(slice.strided!0(2) | == [[0,1,2,3], [8,9,10,11]]); | 1| assert(slice.strided!1(3) | == [[0, 3], [4, 7], [8, 11]]); | 1| assert(slice.strided!(0, 1)(2, 3) | == [[0, 3], [8, 11]]); | | // Function 1| assert(slice.strided(0, 2) | == [[0,1,2,3], [8,9,10,11]]); | 1| assert(slice.strided(1, 3) | == [[0, 3], [4, 7], [8, 11]]); | 1| assert(slice.strided(0, 2).strided(1, 3) | == [[0, 3], [8, 11]]); |} | |/// |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota, universal; | static assert(iota(13, 40).universal.strided!(0, 1)(2, 5).shape == [7, 8]); | static assert(iota(93).universal.strided!(0, 0)(7, 3).shape == [5]); |} | |/// |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota, canonical; 1| auto slice = iota(3, 4).canonical; | 1| assert(slice | == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]); | | // Template 1| assert(slice.strided!0(2) | == [[0,1,2,3], [8,9,10,11]]); | | // Function 1| assert(slice.strided(0, 2) | == [[0,1,2,3], [8,9,10,11]]); |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice; | import mir.algorithm.iteration : equal; | | import std.range : chain; 2| auto i0 = iota([4], 0); auto s0 = stride(i0, 3); 2| auto i1 = iota([4], 4); auto s1 = stride(i1, 3); 2| auto i2 = iota([4], 8); auto s2 = stride(i2, 3); 1| auto slice = iota(3, 4).universal; 1| assert(slice .flattened.equal(concatenation(i0, i1, i2))); | // Template 1| assert(slice.strided!0(2) .flattened.equal(concatenation(i0, i2))); 1| assert(slice.strided!1(3) .flattened.equal(concatenation(s0, s1, s2))); 1| assert(slice.strided!(0, 1)(2, 3).flattened.equal(concatenation(s0, s2))); | // Function 1| assert(slice.strided(0, 2).flattened.equal(concatenation(i0, i2))); 1| assert(slice.strided(1, 3).flattened.equal(concatenation(s0, s1, s2))); 1| assert(slice.strided(0, 2).strided(1, 3).flattened.equal(concatenation(s0, s2))); |} | |/++ |Returns maximal multidimensional cube. | |Params: | slice = input slice |Returns: | n-dimensional slice |+/ |Slice!(Iterator, N, kind) dropToHypercube(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | if (kind == Canonical || kind == Universal || N == 1) |do |{ 28| size_t length = slice._lengths[0]; | foreach (i; Iota!(1, N)) 31| if (length > slice._lengths[i]) 2| length = slice._lengths[i]; | foreach (i; Iota!N) 59| slice._lengths[i] = length; 28| return slice; |} | |/// ditto |Slice!(Iterator, N, Canonical) dropToHypercube(Iterator, size_t N)(Slice!(Iterator, N) slice) | if (N > 1) |{ | import mir.ndslice.topology: canonical; 26| return slice.canonical.dropToHypercube; |} | |/// |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota, canonical, universal; | 1| assert(iota(5, 3, 6, 7) | .dropToHypercube | .shape == cast(size_t[4])[3, 3, 3, 3]); | 1| assert(iota(5, 3, 6, 7) | .universal | .dropToHypercube | .shape == cast(size_t[4])[3, 3, 3, 3]); | 1| assert(4.iota.dropToHypercube == 4.iota); |} source/mir/ndslice/dynamic.d is 98% covered <<<<<< EOF # path=source-mir-ndslice-field.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |Field is a type with `opIndex()(ptrdiff_t index)` primitive. |An iterator can be created on top of a field using $(SUBREF iterator, FieldIterator). |An ndslice can be created on top of a field using $(SUBREF slice, slicedField). | |$(BOOKTABLE $(H2 Fields), |$(TR $(TH Field Name) $(TH Used By)) |$(T2 BitField, $(SUBREF topology, bitwise)) |$(T2 BitpackField, $(SUBREF topology, bitpack)) |$(T2 CycleField, $(SUBREF topology, cycle) (2 kinds)) |$(T2 LinspaceField, $(SUBREF topology, linspace)) |$(T2 MagicField, $(SUBREF topology, magic)) |$(T2 MapField, $(SUBREF topology, map) and $(SUBREF topology, mapField)) |$(T2 ndIotaField, $(SUBREF topology, ndiota)) |$(T2 OrthogonalReduceField, $(SUBREF topology, orthogonalReduceField)) |$(T2 RepeatField, $(SUBREF topology, repeat)) |$(T2 SparseField, Used for mutable DOK sparse matrixes ) |) | | | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.field; | |import mir.internal.utility: Iota; |import mir.math.common: optmath; |import mir.ndslice.internal; |import mir.qualifier; | |@optmath: | |package template ZeroShiftField(T) |{ | static if (hasZeroShiftFieldMember!T) | alias ZeroShiftField = typeof(T.init.assumeFieldsHaveZeroShift()); | else | alias ZeroShiftField = T; |} | |package enum hasZeroShiftFieldMember(T) = __traits(hasMember, T, "assumeFieldsHaveZeroShift"); | |package auto applyAssumeZeroShift(Types...)() |{ | import mir.ndslice.topology; 0000000| string str; | foreach(i, T; Types) | static if (hasZeroShiftFieldMember!T) 0000000| str ~= "_fields[" ~ i.stringof ~ "].assumeFieldsHaveZeroShift, "; | else 0000000| str ~= "_fields[" ~ i.stringof ~ "], "; 0000000| return str; |} | |auto MapField__map(Field, alias fun, alias fun1)(ref MapField!(Field, fun) f) |{ | import core.lifetime: move; | import mir.functional: pipe; 1| return MapField!(Field, pipe!(fun, fun1))(move(f._field)); |} | | |/++ |`MapField` is used by $(SUBREF topology, map). |+/ |struct MapField(Field, alias _fun) |{ |@optmath: | /// | Field _field; | | /// | auto lightConst()() const @property | { 32| return MapField!(LightConstOf!Field, _fun)(.lightConst(_field)); | } | | /// | auto lightImmutable()() immutable @property | { | return MapField!(LightImmutableOf!Field, _fun)(.lightImmutable(_field)); | } | | /++ | User defined constructor used by $(LREF mapField). | +/ | static alias __map(alias fun1) = MapField__map!(Field, _fun, fun1); | | auto ref opIndex(T...)(auto ref T index) | { | import mir.functional: Tuple, unref; | static if (is(typeof(_field[index]) : Tuple!K, K...)) | { 199| auto t = _field[index]; 199| return mixin("_fun(" ~ _iotaArgs!(K.length, "t.expand[", "].unref, ") ~ ")"); | } | else 1254| return _fun(_field[index]); | } | | static if (__traits(hasMember, Field, "length")) | auto length() const @property | { 0000000| return _field.length; | } | | static if (__traits(hasMember, Field, "shape")) | auto shape() const @property | { 0000000| return _field.shape; | } | | static if (__traits(hasMember, Field, "elementCount")) | auto elementCount() const @property | { 0000000| return _field.elementCount; | } | | static if (hasZeroShiftFieldMember!Field) | /// Defined if `Field` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | return _mapField!_fun(_field.assumeFieldsHaveZeroShift); | } |} | |/++ |`VmapField` is used by $(SUBREF topology, map). |+/ |struct VmapField(Field, Fun) |{ |@optmath: | /// | Field _field; | /// | Fun _fun; | | /// | auto lightConst()() const @property | { | return VmapField!(LightConstOf!Field, _fun)(.lightConst(_field)); | } | | /// | auto lightImmutable()() immutable @property | { | return VmapField!(LightImmutableOf!Field, _fun)(.lightImmutable(_field)); | } | | auto ref opIndex(T...)(auto ref T index) | { | import mir.functional: Tuple, unref; | static if (is(typeof(_field[index]) : Tuple!K, K...)) | { | auto t = _field[index]; | return mixin("_fun(" ~ _iotaArgs!(K.length, "t.expand[", "].unref, ") ~ ")"); | } | else | return _fun(_field[index]); | } | | static if (__traits(hasMember, Field, "length")) | auto length() const @property | { | return _field.length; | } | | static if (__traits(hasMember, Field, "shape")) | auto shape() const @property | { | return _field.shape; | } | | static if (__traits(hasMember, Field, "elementCount")) | auto elementCount()const @property | { | return _field.elementCount; | } | | static if (hasZeroShiftFieldMember!Field) | /// Defined if `Field` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | return _vmapField(_field.assumeFieldsHaveZeroShift, _fun); | } |} | |/+ |Creates a mapped field. Uses `__map` if possible. |+/ |auto _mapField(alias fun, Field)(Field field) |{ | import mir.functional: naryFun; | static if (( | __traits(isSame, fun, naryFun!"a|b") || | __traits(isSame, fun, naryFun!"a^b") || | __traits(isSame, fun, naryFun!"a&b") || | __traits(isSame, fun, naryFun!"a | b") || | __traits(isSame, fun, naryFun!"a ^ b") || | __traits(isSame, fun, naryFun!"a & b")) && | is(Field : ZipField!(BitField!(LeftField, I), BitField!(RightField, I)), LeftField, RightField, I)) | { | import mir.ndslice.topology: bitwiseField; 1| auto f = ZipField!(LeftField, RightField)(field._fields[0]._field, field._fields[1]._field)._mapField!fun; 1| return f.bitwiseField!(typeof(f), I); | } | else | static if (__traits(hasMember, Field, "__map")) 7| return Field.__map!fun(field); | else 16| return MapField!(Field, fun)(field); |} | |/+ |Creates a mapped field. Uses `__vmap` if possible. |+/ |auto _vmapField(Field, Fun)(Field field, Fun fun) |{ | static if (__traits(hasMember, Field, "__vmap")) | return Field.__vmap(field, fun); | else | return VmapField!(Field, Fun)(field, fun); |} | |/++ |Iterates multiple fields in lockstep. | |`ZipField` is used by $(SUBREF topology, zipFields). |+/ |struct ZipField(Fields...) | if (Fields.length > 1) |{ |@optmath: | import mir.functional: Tuple, Ref, _ref; | import std.meta: anySatisfy; | | /// | Fields _fields; | | /// | auto lightConst()() const @property | { | import std.format; | import mir.ndslice.topology: iota; | import std.meta: staticMap; 0000000| return mixin("ZipField!(staticMap!(LightConstOf, Fields))(%(_fields[%s].lightConst,%)].lightConst)".format(_fields.length.iota)); | } | | /// | auto lightImmutable()() immutable @property | { | import std.format; | import mir.ndslice.topology: iota; | import std.meta: staticMap; | return mixin("ZipField!(staticMap!(LightImmutableOf, Fields))(%(_fields[%s].lightImmutable,%)].lightImmutable)".format(_fields.length.iota)); | } | | auto opIndex()(ptrdiff_t index) | { | alias Iterators = Fields; | alias _iterators = _fields; | import mir.ndslice.iterator: _zip_types, _zip_index; 0000000| return mixin("Tuple!(_zip_types!Fields)(" ~ _zip_index!Fields ~ ")"); | } | | auto opIndexAssign(Types...)(Tuple!(Types) value, ptrdiff_t index) | if (Types.length == Fields.length) | { | foreach(i, ref val; value.expand) | { | _fields[i][index] = val; | } | return opIndex(index); | } | | static if (anySatisfy!(hasZeroShiftFieldMember, Fields)) | /// Defined if at least one of `Fields` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | import std.meta: staticMap; | return mixin("ZipField!(staticMap!(ZeroShiftField, Fields))(" ~ applyAssumeZeroShift!Fields ~ ")"); | } |} | |/++ |`RepeatField` is used by $(SUBREF topology, repeat). |+/ |struct RepeatField(T) |{ | import std.traits: Unqual; | |@optmath: | alias UT = Unqual!T; | | /// | UT _value; | | /// | auto lightConst()() const @property @trusted | { 55| return RepeatField!(const T)(cast(UT) _value); | } | | /// | auto lightImmutable()() immutable @property @trusted | { | return RepeatField!(immutable T)(cast(UT) _value); | } | | auto ref T opIndex()(ptrdiff_t) @trusted 906| { return cast(T) _value; } |} | |/++ |`BitField` is used by $(SUBREF topology, bitwise). |+/ 2|struct BitField(Field, I = typeof(cast()Field.init[size_t.init])) | if (__traits(isUnsigned, I)) |{ |@optmath: | import mir.bitop: ctlz; | package(mir) alias E = I; | package(mir) enum shift = ctlz(I.sizeof) + 3; | | /// | Field _field; | | /// optimization for bitwise operations | auto __vmap(Fun : LeftOp!(op, bool), string op)(Fun fun) | if (op == "|" || op == "&" || op == "^") | { | import mir.ndslice.topology: bitwiseField; | return _vmapField(_field, RightOp!(op, I)(I(0) - fun.value)).bitwiseField; | } | | /// ditto | auto __vmap(Fun : RightOp!(op, bool), string op)(Fun fun) | if (op == "|" || op == "&" || op == "^") | { | import mir.ndslice.topology: bitwiseField; | return _vmapField(_field, RightOp!(op, I)(I(0) - fun.value)).bitwiseField; | } | | /// ditto | auto __vmap(Fun)(Fun fun) | { | return VmapField!(typeof(this), Fun)(this, fun); | } | | /// ditto | alias __map(alias fun) = BitField__map!(Field, I, fun); | | /// | auto lightConst()() const @property | { 8| return BitField!(LightConstOf!Field, I)(mir.qualifier.lightConst(_field)); | } | | /// | auto lightImmutable()() immutable @property | { | return BitField!(LightImmutableOf!Field, I)(mir.qualifier.lightImmutable(_field)); | } | | bool opIndex()(size_t index) | { | import mir.bitop: bt; 3729| return bt!(Field, I)(_field, index) != 0; | } | | bool opIndexAssign()(bool value, size_t index) | { | import mir.bitop: bta; 1210| bta!(Field, I)(_field, index, value); 1210| return value; | } | | static if (hasZeroShiftFieldMember!Field) | /// Defined if `Field` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | return BitField!(ZeroShiftField!Field, I)(_field.assumeFieldsHaveZeroShift); | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.iterator: FieldIterator; 1| ushort[10] data; 1| auto f = FieldIterator!(BitField!(ushort*))(0, BitField!(ushort*)(data.ptr)); 1| f[123] = true; 1| f++; 1| assert(f[122]); |} | |auto BitField__map(Field, I, alias fun)(BitField!(Field, I) field) |{ | import core.lifetime: move; | import mir.functional: naryFun; | static if (__traits(isSame, fun, naryFun!"~a") || __traits(isSame, fun, naryFun!"!a")) | { | import mir.ndslice.topology: bitwiseField; 5| auto f = _mapField!(naryFun!"~a")(move(field._field)); 5| return f.bitwiseField!(typeof(f), I); | } | else | { 1| return MapField!(BitField!(Field, I), fun)(move(field)); | } |} | |/++ |`BitpackField` is used by $(SUBREF topology, bitpack). |+/ |struct BitpackField(Field, uint pack, I = typeof(cast()Field.init[size_t.init])) | if (__traits(isUnsigned, I)) |{ | //static assert(); |@optmath: | package(mir) alias E = I; | package(mir) enum mask = (I(1) << pack) - 1; | package(mir) enum bits = I.sizeof * 8; | | /// | Field _field; | | /// | auto lightConst()() const @property | { 0000000| return BitpackField!(LightConstOf!Field, pack)(.lightConst(_field)); | } | | /// | auto lightImmutable()() immutable @property | { | return BitpackField!(LightImmutableOf!Field, pack)(.lightImmutable(_field)); | } | | I opIndex()(size_t index) | { 25| index *= pack; 25| size_t start = index % bits; 25| index /= bits; 25| auto ret = (_field[index] >>> start) & mask; | static if (bits % pack) | { 25| sizediff_t end = start - (bits - pack); 25| if (end > 0) 7| ret ^= cast(I)(_field[index + 1] << (bits - end)) >>> (bits - pack); | } 25| return cast(I) ret; | } | | I opIndexAssign()(I value, size_t index) | { | import std.traits: Unsigned; 18| assert(cast(Unsigned!I)value <= mask); 18| index *= pack; 18| size_t start = index % bits; 18| index /= bits; 18| _field[index] = cast(I)((_field[index] & ~(mask << start)) ^ (value << start)); | static if (bits % pack) | { 18| sizediff_t end = start - (bits - pack); 18| if (end > 0) 5| _field[index + 1] = cast(I)((_field[index + 1] & ~((I(1) << end) - 1)) ^ (value >>> (pack - end))); | } 18| return value; | } | | static if (hasZeroShiftFieldMember!Field) | /// Defined if `Field` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | return BitpackField!(ZeroShiftField!Field, pack, I)(_field.assumeFieldsHaveZeroShift); | } |} | |/// |version(mir_ndslice_test) |unittest |{ | import mir.ndslice.iterator: FieldIterator; 1| ushort[10] data; 1| auto f = FieldIterator!(BitpackField!(ushort*, 6))(0, BitpackField!(ushort*, 6)(data.ptr)); 1| f[0] = cast(ushort) 31; 1| f[1] = cast(ushort) 13; 1| f[2] = cast(ushort) 8; 1| f[3] = cast(ushort) 43; 1| f[4] = cast(ushort) 28; 1| f[5] = cast(ushort) 63; 1| f[6] = cast(ushort) 39; 1| f[7] = cast(ushort) 23; 1| f[8] = cast(ushort) 44; | 1| assert(f[0] == 31); 1| assert(f[1] == 13); 1| assert(f[2] == 8); 1| assert(f[3] == 43); 1| assert(f[4] == 28); 1| assert(f[5] == 63); 1| assert(f[6] == 39); 1| assert(f[7] == 23); 1| assert(f[8] == 44); 1| assert(f[9] == 0); 1| assert(f[10] == 0); 1| assert(f[11] == 0); |} | |version(mir_ndslice_test) |unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology; | import mir.ndslice.sorting; 1| uint[2] data; 1| auto packed = data[].sliced.bitpack!18; 1| assert(packed.length == 3); 1| packed[0] = 5; 1| packed[1] = 3; 1| packed[2] = 2; 1| packed.sort; 1| assert(packed[0] == 2); 1| assert(packed[1] == 3); 1| assert(packed[2] == 5); |} | |/// |struct OrthogonalReduceField(FieldsIterator, alias fun, T) |{ | import mir.ndslice.slice: Slice; | |@optmath: | /// non empty slice | | Slice!FieldsIterator _fields; | | /// | T _initialValue; | | /// | auto lightConst()() const @property | { 1| auto fields = _fields.lightConst; 1| return OrthogonalReduceField!(fields.Iterator, fun, T)(fields, _initialValue); | } | | /// | auto lightImmutable()() immutable @property | { | auto fields = _fields.lightImmutable; | return OrthogonalReduceField!(fields.Iterator, fun, T)(fields, _initialValue); | } | | /// `r = fun(r, fields[i][index]);` reduction by `i` | auto opIndex()(size_t index) | { | import std.traits: Unqual; 100| auto fields = _fields; 100| T r = _initialValue; 100| if (!fields.empty) do | { 300| r = cast(T) fun(r, fields.front[index]); 300| fields.popFront; | } 300| while(!fields.empty); 100| return r; | } |} | |/// |struct CycleField(Field) |{ | import mir.ndslice.slice: Slice; | |@optmath: | /// Cycle length | size_t _length; | /// | Field _field; | | /// | auto lightConst()() const @property | { 2| auto field = .lightConst(_field); 2| return CycleField!(typeof(field))(_length, field); | } | | /// | auto lightImmutable()() immutable @property | { | auto field = .lightImmutable(_field); | return CycleField!(typeof(field))(_length, field); | } | | /// | auto ref opIndex()(size_t index) | { 14| return _field[index % _length]; | } | | /// | static if (!__traits(compiles, &opIndex(size_t.init))) | { | auto ref opIndexAssign(T)(auto ref T value, size_t index) | { | return _field[index % _length] = value; | } | } | | static if (hasZeroShiftFieldMember!Field) | /// Defined if `Field` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | return CycleField!(ZeroShiftField!Field)(_length, _field.assumeFieldsHaveZeroShift); | } |} | |/// |struct CycleField(Field, size_t length) |{ | import mir.ndslice.slice: Slice; | |@optmath: | /// Cycle length | enum _length = length; | /// | Field _field; | | /// | auto lightConst()() const @property | { 2| auto field = .lightConst(_field); 2| return CycleField!(typeof(field), _length)(field); | } | | /// | auto lightImmutable()() immutable @property | { | auto field = .lightImmutable(_field); | return CycleField!(typeof(field), _length)(field); | } | | /// | auto ref opIndex()(size_t index) | { 14| return _field[index % _length]; | } | | /// | static if (!__traits(compiles, &opIndex(size_t.init))) | { | auto ref opIndexAssign(T)(auto ref T value, size_t index) | { | return _field[index % _length] = value; | } | } | | static if (hasZeroShiftFieldMember!Field) | /// Defined if `Field` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | return CycleField!(ZeroShiftField!Field, _length)(_field.assumeFieldsHaveZeroShift); | } |} | |/++ |`ndIotaField` is used by $(SUBREF topology, ndiota). |+/ |struct ndIotaField(size_t N) | if (N) |{ |@optmath: | /// | size_t[N - 1] _lengths; | | /// | auto lightConst()() const @property | { 59| return ndIotaField!N(_lengths); | } | | /// | auto lightImmutable()() const @property | { | return ndIotaField!N(_lengths); | } | | /// | size_t[N] opIndex()(size_t index) const | { 897| size_t[N] indices; | foreach_reverse (i; Iota!(N - 1)) | { 1271| indices[i + 1] = index % _lengths[i]; 1271| index /= _lengths[i]; | } 897| indices[0] = index; 897| return indices; | } |} | |/++ |`LinspaceField` is used by $(SUBREF topology, linspace). |+/ |struct LinspaceField(T) |{ | /// | size_t _length; | | /// | T _start = cast(T) 0, _stop = cast(T) 0; | | /// | auto lightConst()() scope const @property | { 29| return LinspaceField!T(_length, _start, _stop); | } | | /// | auto lightImmutable()() scope const @property | { | return LinspaceField!T(_length, _start, _stop); | } | | // no fastmath | /// | T opIndex()(sizediff_t index) scope const | { 1160| sizediff_t d = _length - 1; 1160| auto v = typeof(T.init.re)(d - index); 1160| auto w = typeof(T.init.re)(index); 1160| v /= d; 1160| w /= d; 1160| auto a = v * _start; 1160| auto b = w * _stop; 1160| return a + b; | } | |@optmath: | | /// | size_t length(size_t dimension = 0)() scope const @property | if (dimension == 0) | { 28| return _length; | } | | /// | size_t[1] shape()() scope const @property @nogc | { 8| return [_length]; | } |} | |/++ |Magic square field. |+/ |struct MagicField |{ |@optmath: |@safe pure nothrow @nogc: | | /++ | Magic Square size. | +/ | size_t _n; | |scope const: | | /// | MagicField lightConst()() @property | { 45| return this; | } | | /// | MagicField lightImmutable()() @property | { | return this; | } | | /// | size_t length(size_t dimension = 0)() @property | if(dimension <= 2) | { 25| return _n * _n; | } | | /// | size_t[1] shape() @property | { 0000000| return [_n * _n]; | } | | /// | size_t opIndex(size_t index) | { | pragma(inline, false); 9224| auto d = index / _n; 9224| auto m = index % _n; 9224| if (_n & 1) | { | //d = _n - 1 - d; // MATLAB synchronization | //index = d * _n + m; // ditto 4922| auto r = (index + 1 - d + (_n - 3) / 2) % _n; 4922| auto c = (_n * _n - index + 2 * d) % _n; 4922| return r * _n + c + 1; | } | else 4302| if ((_n & 2) == 0) | { 1880| auto a = (d + 1) & 2; 1880| auto b = (m + 1) & 2; 1880| return a != b ? index + 1: _n * _n - index; | } | else | { 2422| auto n = _n / 2 ; 2422| size_t shift; 2422| ptrdiff_t q; 2422| ptrdiff_t p = m - n; 2422| if (p >= 0) | { 1211| m = p; 1211| shift = n * n; 1211| auto mul = m <= n / 2 + 1; 1211| q = d - n; 1211| if (q >= 0) | { 605| d = q; 605| mul = !mul; | } 1211| if (mul) | { 606| shift *= 2; | } | } | else | { 1211| auto mul = m < n / 2; 1211| q = d - n; 1211| if (q >= 0) | { 605| d = q; 605| mul = !mul; | } 1492| if (d == n / 2 && (m == 0 || m == n / 2)) | { 51| mul = !mul; | } 1211| if (mul) | { 606| shift = n * n * 3; | } | } 2422| index = d * n + m; 2422| auto r = (index + 1 - d + (n - 3) / 2) % n; 2422| auto c = (n * n - index + 2 * d) % n; 2422| return r * n + c + 1 + shift; | } | } |} | |/++ |`SparseField` is used to represent Sparse ndarrays in mutable DOK format. |+/ |struct SparseField(T) |{ | /// | T[size_t] _table; | | /// | auto lightConst()() const @trusted | { | return SparseField!(const T)(cast(const(T)[size_t])_table); | } | | /// | auto lightImmutable()() immutable @trusted | { | return SparseField!(immutable T)(cast(immutable(T)[size_t])_table); | } | | /// | T opIndex()(size_t index) | { | import std.traits: isScalarType; | static if (isScalarType!T) | return _table.get(index, cast(T)0); | else | return _table.get(index, null); | } | | /// | T opIndexAssign()(T value, size_t index) | { | import std.traits: isScalarType; | static if (isScalarType!T) | { | if (value != 0) | _table[index] = value; | else | _table.remove(index); | } | else | { | if (value !is null) | _table[index] = value; | else | _table.remove(index); | } | return value; | } | | /// | T opIndexUnary(string op)(size_t index) | if (op == `++` || op == `--`) | { | import std.traits: isScalarType; | mixin (`auto value = ` ~ op ~ `_table[index];`); | static if (isScalarType!T) | { | if (value == 0) | _table.remove(index); | } | else | { | if (value is null) | _table.remove(index); | } | return value; | } | | /// | T opIndexOpAssign(string op)(T value, size_t index) | if (op == `+` || op == `-`) | { | import std.traits: isScalarType; | mixin (`value = _table[index] ` ~ op ~ `= value;`); // this works | static if (isScalarType!T) | { | if (value == 0) | _table.remove(index); | } | else | { | if (value is null) | _table.remove(index); | } | return value; | } |} source/mir/ndslice/field.d is 92% covered <<<<<< EOF # path=source-mir-ndslice-filling.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |Initialisation routines. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.filling; | |import mir.ndslice.slice: Slice, SliceKind; | |/++ |Fills a matrix with the terms of a geometric progression in each row. |Params: | matrix = `m × n` matrix to fill | vec = vector of progression coefficients length of `m` |See_also: $(LINK2 https://en.wikipedia.org/wiki/Vandermonde_matrix, Vandermonde matrix) |+/ |void fillVandermonde(F, SliceKind matrixKind, SliceKind kind)(Slice!(F*, 2, matrixKind) matrix, Slice!(const(F)*, 1, kind) vec) |in { 1| assert(matrix.length == vec.length); |} |do { | import mir.conv: to; | 17| foreach (v; matrix) | { 5| F a = vec.front; 5| vec.popFront; 5| F x = to!F(1); 85| foreach (ref e; v) | { 25| e = x; 25| x *= a; | } | } |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: uninitSlice; 1| auto x = [1.0, 2, 3, 4, 5].sliced; 1| auto v = uninitSlice!double(x.length, x.length); 1| v.fillVandermonde(x); 1| assert(v == | [[ 1.0, 1, 1, 1, 1], | [ 1.0, 2, 4, 8, 16], | [ 1.0, 3, 9, 27, 81], | [ 1.0, 4, 16, 64, 256], | [ 1.0, 5, 25, 125, 625]]); |} source/mir/ndslice/filling.d is 100% covered <<<<<< EOF # path=source-mir-ndslice-fuse.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |Allocation routines that construct ndslices from ndranges. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |See_also: $(SUBMODULE concatenation) submodule. | |Macros: |SUBMODULE = $(MREF_ALTTEXT $1, mir, ndslice, $1) |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.fuse; | |import mir.internal.utility; |import mir.ndslice.slice; |import mir.primitives; |import mir.qualifier; |import std.meta; |import std.traits; | |/++ |Fuses ndrange `r` into GC-allocated ($(LREF fuse)) or RC-allocated ($(LREF rcfuse)) ndslice. |Can be used to join rows or columns into a matrix. | |Params: | Dimensions = (optional) indices of dimensions to be brought to the first position |Returns: | ndslice |+/ |alias fuse(Dimensions...) = fuseImpl!(false, void, Dimensions); |/// ditto |alias rcfuse(Dimensions...) = fuseImpl!(true, void, Dimensions); | |/// |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.slice : Contiguous, Slice; | import mir.ndslice.topology: iota, map, as; | import mir.rc.array: RCI; | | enum ror = [ | [0, 1, 2, 3], | [4, 5, 6, 7], | [8, 9,10,11]]; | | // 0 1 2 3 | // 4 5 6 7 | // 8 9 10 11 1| auto matrix = ror.fuse; | 2| auto rcmatrix = ror.rcfuse; // nogc version | 1| assert(matrix == [3, 4].iota); 1| assert(rcmatrix == [3, 4].iota); | static assert(ror.fuse == [3, 4].iota); // CTFE-able | | // matrix is contiguos | static assert(is(typeof(matrix) == Slice!(int*, 2))); | static assert(is(typeof(rcmatrix) == Slice!(RCI!int, 2))); | | /// also works with strings 1| auto strMatrix = ror.map!(as!string).fuse; | static assert(is(typeof(strMatrix) == Slice!(string*, 2))); 1| assert(strMatrix.as!int == matrix); |} | |/// Transposed |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | import mir.ndslice.dynamic: transposed; | import mir.ndslice.slice : Contiguous, Slice; | | enum ror = [ | [0, 1, 2, 3], | [4, 5, 6, 7], | [8, 9,10,11]]; | | // 0 4 8 | // 1 5 9 | // 2 6 10 | // 3 7 11 | | // `!1` brings dimensions under index 1 to the front (0 index). 1| auto matrix = ror.fuse!1; | 1| assert(matrix == [3, 4].iota.transposed!1); | // TODO: CTFE | // static assert(ror.fuse!1 == [3, 4].iota.transposed!1); // CTFE-able | // matrix is contiguos | static assert(is(typeof(matrix) == Slice!(int*, 2))); |} | |/// 3D |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.topology: iota; | import mir.ndslice.dynamic: transposed; | 1| auto ror = | [[[ 0, 1, 2, 3], | [ 4, 5, 6, 7]], | [[ 8, 9,10,11], | [12,13,14,15]]]; | 1| auto nd = [2, 2, 4].iota; | 1| assert(ror.fuse == nd); 1| assert(ror.fuse!2 == nd.transposed!2); 1| assert(ror.fuse!(1, 2) == nd.transposed!(1, 2)); 1| assert(ror.fuse!(2, 1) == nd.transposed!(2, 1)); |} | |/// Work with RC Arrays of RC Arrays |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.slice; | import mir.ndslice.topology: map; | import mir.rc.array; | | Slice!(const(double)*, 2) conv(RCArray!(const RCArray!(const double)) a) | { 0000000| return a[].map!"a[]".fuse; | } |} | |/++ |Fuses ndrange `r` into GC-allocated ($(LREF fuseAs)) or RC-allocated ($(LREF rcfuseAs)) ndslice. |Can be used to join rows or columns into a matrix. | |Params: | T = output type of ndslice elements | Dimensions = (optional) indices of dimensions to be brought to the first position |Returns: | ndslice |+/ |alias fuseAs(T, Dimensions...) = fuseImpl!(false, T, Dimensions); |/// ditto |alias rcfuseAs(T, Dimensions...) = fuseImpl!(true, T, Dimensions); | |/// |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.fuse; | import mir.ndslice.slice : Contiguous, Slice; | import mir.ndslice.topology: iota; | import mir.rc.array: RCI; | | enum ror = [ | [0, 1, 2, 3], | [4, 5, 6, 7], | [8, 9,10,11]]; | | // 0 1 2 3 | // 4 5 6 7 | // 8 9 10 11 1| auto matrix = ror.fuseAs!double; | 2| auto rcmatrix = ror.rcfuseAs!double; // nogc version | 1| assert(matrix == [3, 4].iota); 1| assert(rcmatrix == [3, 4].iota); | static assert(ror.fuseAs!double == [3, 4].iota); // CTFE-able | | // matrix is contiguos | static assert(is(typeof(matrix) == Slice!(double*, 2))); | static assert(is(typeof(rcmatrix) == Slice!(RCI!double, 2))); |} | |/// |template fuseImpl(bool RC, T_, Dimensions...) |{ | import mir.ndslice.internal: isSize_t, toSize_t; | static if (allSatisfy!(isSize_t, Dimensions)) | /++ | Params: | r = parallelotope (ndrange) with length/shape and input range primitives. | +/ | auto fuseImpl(NDRange)(NDRange r) | if (isFusable!NDRange) | { | import mir.conv: emplaceRef; | import mir.algorithm.iteration: each; | import mir.ndslice.allocation; 113| auto shape = fuseShape(r); | static if (is(T_ == void)) | alias T = FuseElementType!NDRange; | else | alias T = T_; | alias UT = Unqual!T; | static if (RC) | { | import mir.rc.array: RCI; | alias R = Slice!(RCI!T, fuseDimensionCount!NDRange); 4| Slice!(RCI!UT, fuseDimensionCount!NDRange) ret; | } | else | { | alias R = Slice!(T*, fuseDimensionCount!NDRange); 111| Slice!(UT*, fuseDimensionCount!NDRange) ret; | } | static if (Dimensions.length) | { | import mir.ndslice.topology: iota; | import mir.ndslice.dynamic: transposed, completeTranspose; | enum perm = completeTranspose!(shape.length)([Dimensions]); 10| size_t[shape.length] shapep; | foreach(i; Iota!(shape.length)) 23| shapep[i] = shape[perm[i]]; | // enum iperm = perm.length.iota[completeTranspose!(shape.length)([Dimensions])[].sliced].slice; | alias InverseDimensions = aliasSeqOf!( | (size_t[] perm){ | auto ar = new size_t[perm.length]; | ar.sliced[perm.sliced] = perm.length.iota; | return ar; | }(perm) | ); | static if (RC) | { | ret = shapep.uninitRcslice!UT; | ret.lightScope.transposed!InverseDimensions.each!(emplaceRef!T)(r); | } | else | { 10| if (__ctfe) | { | ret = shapep.slice!UT; | ret.transposed!InverseDimensions.each!"a = b"(r); | } 10| else () @trusted | { 10| ret = shapep.uninitSlice!UT; 10| ret.transposed!InverseDimensions.each!(emplaceRef!T)(r); | } (); | | } | } | else | { | static if (RC) | { 2| ret = shape.uninitRCslice!UT; 2| ret.lightScope.each!(emplaceRef!T)(r); | } | else | { 101| if (__ctfe) | { | ret = shape.slice!UT; | ret.each!"a = b"(r); | } 101| else () @trusted | { 101| ret = shape.uninitSlice!UT; 101| ret.each!(emplaceRef!T)(r); | } (); | } | } | static if (RC) | { | import core.lifetime: move; 4| return move(*(() @trusted => cast(R*)&ret)()); | } | else | { 222| return *(() @trusted => cast(R*)&ret)(); | } | } | else | alias fuseImpl = .fuseImpl!(RC, T_, staticMap!(toSize_t, Dimensions)); |} | |private template fuseDimensionCount(R) |{ | static if (!isFusable!R) | enum size_t fuseDimensionCount = 0; | else | static if (is(typeof(R.init.shape) : size_t[N], size_t N) && (isDynamicArray!R || __traits(hasMember, R, "front"))) | { | static if (N == 1) | static if (isSomeChar!(typeof(R.init.front))) | enum size_t fuseDimensionCount = 0; | else | enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); | else | enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); | } | else | enum size_t fuseDimensionCount = 0; |} | |/// |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice; | 1| auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); | static assert(fuseDimensionCount!(typeof(m)) == 3); | 1| auto p = m.pack!1; | static assert(fuseDimensionCount!(typeof(p)) == 3); | 1| auto q = m.pack!2; | static assert(fuseDimensionCount!(typeof(q)) == 3); |} | |private static immutable shapeExceptionMsg = "fuseShape Exception: elements have different shapes/lengths"; | |version(D_Exceptions) | static immutable shapeException = new Exception(shapeExceptionMsg); | |private template isFusable(Range) |{ | static if (hasShape!Range) | { | enum isFusable = !isSomeChar!(typeof(Range.init.front)); | } | else | { | enum isFusable = false; | } |} | |/+ |TODO docs |+/ |size_t[fuseDimensionCount!Range] fuseShape(Range)(Range r) | if (isFusable!Range) |{ | enum N = r.shape.length; | enum RN = typeof(return).length; | static if (RN == N) | { 308| return r.shape; | } | else | { 143| typeof(return) ret; 143| ret[0 .. N] = r.shape; 143| if (!ret[0 .. N].anyEmptyShape) | { | static if (isSlice!Range) | { 28| ret[N .. $] = fuseShape(r.first); | } | else static if (isArray!Range) | { 115| bool next; 1089| foreach (ref elem; r) | { 248| const elemShape = fuseShape(elem); 248| if (next) | { 133| if (elemShape != ret[N .. $]) | { | version (D_Exceptions) 0000000| throw shapeException; | else | assert(0, shapeExceptionMsg); | } | } | else | { 115| ret[N .. $] = elemShape; 115| next = true; | } | } | } | else | static assert(false); | } 143| return ret; | } |} | |/// basic |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice; | 1| auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); 1| auto s = fuseShape(m); 1| assert(s == [2, 3, 2]); |} | |/// pack!1 |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice; | 1| auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); 1| auto p = m.pack!1; 1| auto s = fuseShape(p); 1| assert(s == [2, 3, 2]); |} | |/// pack!2 |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice; | 1| auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); 1| auto p = m.pack!2; 1| auto s = fuseShape(p); 1| assert(s == [2, 3, 2]); |} | |private template FuseElementType(NDRange) | if (isFusable!NDRange) |{ | static assert (fuseDimensionCount!NDRange); | static if (fuseDimensionCount!NDRange == 1) | alias FuseElementType = typeof(NDRange.init.front); | else | static if (isFusable!(typeof(NDRange.init.front))) | alias FuseElementType = FuseElementType!(typeof(NDRange.init.front)); | else | alias FuseElementType = typeof(NDRange.init.front); |} | |/++ |Fuses `cells` into GC-allocated ndslice. | |Params: | cells = ndrange of ndcells, ndrange and ndcell should have `shape` and multidimensional input range primivies (`front!d`, `empty!d`, `popFront!d`). |Returns: ndslice composed of fused cells. |See_also: $(SUBREF chunks, chunks) |+/ |auto fuseCells(S)(S cells) |{ | alias T = DeepElementType!(DeepElementType!S); | alias UT = Unqual!T; 2| if (__ctfe) | { | import mir.ndslice.allocation: slice; | auto ret = cells.fuseCellsShape.slice!UT; | ret.fuseCellsAssign!"a = b" = cells; | static if (is(T == immutable)) | return (() @trusted => cast(immutable) ret)()[]; | else | static if (is(T == const)) | return (() @trusted => cast(const) ret)()[]; | else | return ret; | } 2| else return () @trusted | { | import mir.ndslice.allocation: uninitSlice; | import mir.conv; 2| auto ret = cells.fuseCellsShape.uninitSlice!UT; 2| ret.fuseCellsAssign!(emplaceRef!T) = cells; | alias R = Slice!(T*, ret.N); 2| return R(ret._structure, cast(T*)ret._iterator); | } (); |} | |/// 1D |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; | enum ar = [[0, 1], [], [2, 3, 4, 5], [6], [7, 8, 9]]; | static assert ([[0, 1], [], [2, 3, 4, 5], [6], [7, 8, 9]].fuseCells == 10.iota); 1| assert (ar.fuseCells == 10.iota); |} | |/// 2D |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; | import mir.ndslice.chunks; | 1| auto sl = iota(11, 17); 1| assert(sl.chunks!(0, 1)(3, 4).fuseCells == sl); |} | |/+ |TODO docs |+/ |auto fuseCellsAssign(alias fun = "a = b", Iterator, size_t N, SliceKind kind, S)(Slice!(Iterator, N, kind) to, S cells) |{ 2| assert(to.shape == cells.fuseCellsShape, "'cells.fuseCellsShape' should be equal to 'to.shape'"); | 2| if (cells.anyEmpty) 0000000| goto R; | | import mir.functional: naryFun; | import mir.ndslice.topology: canonical; | static if (kind == Contiguous) 2| fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to.canonical, cells); | else | fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to, cells); 2| R: return to; |} | |/+ |TODO docs |+/ |size_t[S.init.shape.length] fuseCellsShape(S)(S cells) @property |{ 4| typeof(return) ret; | enum N = ret.length; | static if (N == 1) | { 36| foreach (ref e; cells) 10| ret[0] += e.length; | } | else | { | import mir.ndslice.topology: repeat; | enum expr = "e" ~ ".front".repeat(N).fuseCells.field; | foreach (i; Iota!N) 44| for (auto e = cells.save; !e.empty!i; e.popFront!i) 18| ret[i] += mixin(expr).length!i; | } 4| return ret; |} | |private auto fuseCellsEmplaceImpl(alias fun, size_t i, size_t M, Iterator, size_t N, SliceKind kind, S)(Slice!(Iterator, N, kind) to, S cells) |{ | do | { 29| auto from = cells.front; | static if (M == 1) | { 5| auto n = from.length!i; | } | else | { | import mir.ndslice.topology: repeat; | enum expr = "from" ~ ".front".repeat(N - 1 - i).fuseCells.field; 24| auto n = mixin(expr).length!i; | } 29| assert (to.length!i >= n); | static if (i + 1 == M) | { | import mir.algorithm.iteration: each; 25| each!fun(to.selectFront!i(n), from); | } | else | { 4| .fuseCellsEmplaceImpl!(fun, i + 1, N)(to.selectFront!i(n), from); | } 29| to.popFrontExactly!i(n); 29| cells.popFront; | } 29| while(!cells.empty); 6| return to; |} source/mir/ndslice/fuse.d is 96% covered <<<<<< EOF # path=source-mir-ndslice-internal.lst |module mir.ndslice.internal; | |import mir.internal.utility : isFloatingPoint, Iota; |import mir.math.common: optmath; |import mir.ndslice.iterator: IotaIterator; |import mir.ndslice.slice; |import mir.primitives; |import std.meta; |import std.traits; | |@optmath: | |template ConstIfPointer(T) |{ | static if (isPointer!T) | alias ConstIfPointer = const(PointerTarget!T)*; | else | alias ConstIfPointer = T; |} | |/// |public import mir.utility: _expect; | |struct RightOp(string op, T) |{ | T value; | | auto lightConst()() const @property | { | import mir.qualifier; 11| return RightOp!(op, LightConstOf!T)(value.lightConst); | } | | auto lightImmutable()() immutable @property | { | import mir.qualifier; | return RightOp!(op, LightImmutableOf!T)(value.lightImmutable); | } | 26| this()(ref T v) { value = v; } 86| this()(T v) { value = v; } | auto ref opCall(F)(auto ref F right) | { | static if (op == "^^" && isNumeric!T && isFloatingPoint!F) | { | import mir.math.common: pow; | return pow(value, right); | } | else | { 98| return mixin("value " ~ op ~ " right"); | } | } |} | |struct LeftOp(string op, T) |{ | T value; | | auto lightConst()() const @property | { | import mir.qualifier; 15| return LeftOp!(op, LightConstOf!T)(value.lightConst); | } | | auto lightImmutable()() immutable @property | { | import mir.qualifier; | return LeftOp!(op, LightImmutableOf!T)(value.lightImmutable); | } | 132| this()(ref T v) { value = v; } 40| this()(T v) { value = v; } | auto ref opCall(F)(auto ref F left) | { | static if (op == "^^" && isFloatingPoint!T && isNumeric!F) | { | import mir.math.common: pow; 8| return pow(left, value); | } | else | { 1688| return mixin("left " ~ op ~ " value"); | } | } |} | |private template _prod(size_t len) | if (len) |{ | static if (len == 1) | enum _prod = "elems[0]"; | else | { | enum i = len - 1; | enum _prod = ._prod!i ~ " * elems[" ~ i.stringof ~ "]"; | } |} | |auto product(Elems...)(auto ref Elems elems) |{ 134| return mixin(_prod!(Elems.length)); |} | | |template _iotaArgs(size_t length, string prefix, string suffix) |{ | static if (length) | { | enum i = length - 1; | enum _iotaArgs = _iotaArgs!(i, prefix, suffix) ~ prefix ~ i.stringof ~ suffix; | } | else | enum _iotaArgs = ""; |} | |alias _IteratorOf(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind) = Iterator; | |E maxElem(E)(E[] arr...) |{ 0000000| auto ret = Unqual!E.min; 0000000| foreach(e; arr) 0000000| if (e > ret) 0000000| ret = e; 0000000| return ret; |} | |E minElem(E)(E[] arr...) |{ 0000000| auto ret = Unqual!E.max; 0000000| foreach(e; arr) 0000000| if (e < ret) 0000000| ret = e; 0000000| return ret; |} | |size_t sum()(size_t[] packs) |{ 0000000| size_t s; 0000000| foreach(pack; packs) 0000000| s += pack; 0000000| return s; |} | | |size_t[] reverse()(size_t[] ar) |{ | foreach(i, e; ar[0..$/2]) | { | ar[i] = ar[$ - i - 1]; | ar[$ - i - 1] = e; | } | return ar; |} | |enum indexError(size_t pos, size_t N) = | "index at position " ~ pos.stringof | ~ " from the range [0 .." ~ N.stringof ~ ")" | ~ " must be less than corresponding length."; | |enum string tailErrorMessage( | string fun = __FUNCTION__, | string pfun = __PRETTY_FUNCTION__) = |" |- - - |Error in function |" ~ fun ~ " |- - - |Function prototype |" ~ pfun ~ " |_____"; | |mixin template DimensionsCountCTError() |{ | static assert(Dimensions.length <= N, | "Dimensions list length = " ~ Dimensions.length.stringof | ~ " should be less than or equal to N = " ~ N.stringof | ~ tailErrorMessage!()); |} | |enum DimensionsCountRTError = q{ | assert(dimensions.length <= N, | "Dimensions list length should be less than or equal to N = " ~ N.stringof | ~ tailErrorMessage!()); |}; | |mixin template DimensionCTError() |{ | static assert(dimension >= 0, | "dimension = " ~ dimension.stringof ~ " at position " | ~ i.stringof ~ " should be greater than or equal to 0" | ~ tailErrorMessage!()); | static assert(dimension < N, | "dimension = " ~ dimension.stringof ~ " at position " | ~ i.stringof ~ " should be less than N = " ~ N.stringof | ~ tailErrorMessage!()); | static assert(dimension < slice.S, | "dimension = " ~ dimension.stringof ~ " at position " | ~ i.stringof ~ " should be less than " ~ (slice.S).stringof ~ ". " | ~ "`universal` and `canonical` from `mir.ndslice.topology` can be used to relax slice kind." | ~ tailErrorMessage!()); |} | |enum DimensionRTError = q{ | static if (isSigned!(typeof(dimension))) | assert(dimension >= 0, "dimension should be greater than or equal to 0" | ~ tailErrorMessage!()); | assert(dimension < N, "dimension should be less than N = " ~ N.stringof | ~ tailErrorMessage!()); | assert(dimension < slice.S, | "dimension should be less than " ~ slice.S.stringof ~ ". " | ~ "`universal` and `canonical` from `mir.ndslice.topology` can be used to relax slice kind." | ~ tailErrorMessage!()); |}; | |private alias IncFront(Seq...) = AliasSeq!(Seq[0] + 1, Seq[1 .. $]); | |private alias DecFront(Seq...) = AliasSeq!(Seq[0] - 1, Seq[1 .. $]); | |private enum bool isNotZero(alias t) = t != 0; | |alias NSeqEvert(Seq...) = Filter!(isNotZero, DecFront!(Reverse!(IncFront!Seq))); | |//alias Parts(Seq...) = DecAll!(IncFront!Seq); | |alias Snowball(Seq...) = AliasSeq!(size_t.init, SnowballImpl!(size_t.init, Seq)); | |private template SnowballImpl(size_t val, Seq...) |{ | static if (Seq.length == 0) | alias SnowballImpl = AliasSeq!(); | else | alias SnowballImpl = AliasSeq!(Seq[0] + val, SnowballImpl!(Seq[0] + val, Seq[1 .. $])); |} | |private template DecAll(Seq...) |{ | static if (Seq.length == 0) | alias DecAll = AliasSeq!(); | else | alias DecAll = AliasSeq!(Seq[0] - 1, DecAll!(Seq[1 .. $])); |} | |//template SliceFromSeq(Range, Seq...) |//{ |// static if (Seq.length == 0) |// alias SliceFromSeq = Range; |// else |// { |// import mir.ndslice.slice : Slice; |// alias SliceFromSeq = SliceFromSeq!(Slice!(Seq[$ - 1], Range), Seq[0 .. $ - 1]); |// } |//} | |template DynamicArrayDimensionsCount(T) |{ | static if (isDynamicArray!T) | enum size_t DynamicArrayDimensionsCount = 1 + DynamicArrayDimensionsCount!(typeof(T.init[0])); | else | enum size_t DynamicArrayDimensionsCount = 0; |} | |bool isPermutation(size_t N)(auto ref const scope size_t[N] perm) |{ 9| int[N] mask; 9| return isValidPartialPermutationImpl(perm, mask); |} | |version(mir_ndslice_test) unittest |{ 1| assert(isPermutation([0, 1])); | // all numbers 0..N-1 need to be part of the permutation 1| assert(!isPermutation([1, 2])); 1| assert(!isPermutation([0, 2])); | // duplicates are not allowed 1| assert(!isPermutation([0, 1, 1])); | 1| size_t[0] emptyArr; | // empty permutations are not allowed either 1| assert(!isPermutation(emptyArr)); |} | |bool isValidPartialPermutation(size_t N)(in size_t[] perm) |{ 4| int[N] mask; 4| return isValidPartialPermutationImpl(perm, mask); |} | |private bool isValidPartialPermutationImpl(size_t N)(in size_t[] perm, ref int[N] mask) |{ 13| if (perm.length == 0) 1| return false; 141| foreach (j; perm) | { 37| if (j >= N) 2| return false; 35| if (mask[j]) //duplicate 1| return false; 34| mask[j] = true; | } 9| return true; |} | |template ShiftNegativeWith(size_t N) |{ | enum ShiftNegativeWith(sizediff_t i) = i < 0 ? i + N : i; |} | |enum toSize_t(size_t i) = i; |enum toSizediff_t(sizediff_t i) = i; |enum isSize_t(alias i) = is(typeof(i) == size_t); |enum isSizediff_t(alias i) = is(typeof(i) == sizediff_t); |enum isIndex(I) = is(I : size_t); |template is_Slice(S) |{ | static if (is(S : Slice!(IotaIterator!I), I)) | enum is_Slice = __traits(isIntegral, I); | else | enum is_Slice = false; |} | |alias Repeat(size_t N : 0, T...) = AliasSeq!(); | |private enum isReference(P) = | hasIndirections!P | || isFunctionPointer!P | || is(P == interface); | |alias ImplicitlyUnqual(T) = Select!(isImplicitlyConvertible!(T, Unqual!T), Unqual!T, T); |alias ImplicitlyUnqual(T : T*) = T*; | |size_t lengthsProduct(size_t N)(auto ref const scope size_t[N] lengths) |{ 1368| size_t length = lengths[0]; | foreach (i; Iota!(1, N)) 689| length *= lengths[i]; 1368| return length; |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| const size_t[3] lengths = [3, 4, 5]; 1| assert(lengthsProduct(lengths) == 60); 1| assert(lengthsProduct([3, 4, 5]) == 60); |} | |package(mir) template strideOf(args...) |{ | static if (args.length == 0) | enum strideOf = args; | else | { | @optmath @property auto ref ls()() | { | import mir.ndslice.topology: stride; | return stride(args[0]); | } | alias strideOf = AliasSeq!(ls, strideOf!(args[1..$])); | } |} | |package(mir) template frontOf(size_t n) |{ | enum frontOf = () { | string ret; | static foreach (i; 0 .. n) | { | if (i) | ret ~= `, `; | ret ~= "slices[" ~ i.stringof ~ `].front`; | } | return ret; | } (); |} | |package(mir) template frontOf2(args...) |{ | static if (args.length == 0) | enum frontOf2 = args; | else | { | @optmath @property auto frontOf2Mod()() | { | return args[0].front; | } | alias frontOf2 = AliasSeq!(frontOf2Mod, frontOf2!(args[1..$])); | } |} | |package(mir) template backOf(args...) |{ | static if (args.length == 0) | enum backOf = args; | else | { | @optmath @property auto ref backOfMod()() | { | return args[0].back; | } | alias backOf = AliasSeq!(backOfMod, backOf!(args[1..$])); | } |} | |package(mir) template frontOfD(size_t dimension, args...) |{ | static if (args.length == 0) | enum frontOfD = args; | else | { | @optmath @property auto ref frontOfDMod()() | { | return args[0].front!dimension; | } | alias frontOfD = AliasSeq!(frontOfDMod, frontOfD!(dimension, args[1..$])); | } |} | |package(mir) template backOfD(size_t dimension, args...) |{ | static if (args.length == 0) | enum backOfD = args; | else | { | @optmath @property auto ref backOfDMod()() | { | return args[0].back!dimension; | } | alias backOfD = AliasSeq!(backOfDMod, backOfD!(dimension, args[1..$])); | } |} | |package(mir) template frontOfDim(size_t dim, args...) |{ | static if (args.length == 0) | enum frontOfDim = args; | else | { | alias arg = args[0]; | @optmath @property auto ref frontOfDimMod() | { | return arg.front!dim; | } | alias frontOfDim = AliasSeq!(frontOfDimMod, frontOfDim!(dim, args[1..$])); | } |} | |package(mir) template selectFrontOf(alias input, args...) |{ | static if (args.length == 0) | enum selectFrontOf = args; | else | { | alias arg = args[0]; | @optmath @property auto ref selectFrontOfMod()() | { | return arg.lightScope.selectFront!0(input); | } | alias selectFrontOf = AliasSeq!(selectFrontOfMod, selectFrontOf!(input, args[1..$])); | } |} | |package(mir) template selectBackOf(alias input, args...) |{ | static if (args.length == 0) | enum selectBackOf = args; | else | { | alias arg = args[0]; | @optmath @property auto ref selectBackOfMod()() | { | return arg.selectBack!0(input); | } | alias selectBackOf = AliasSeq!(selectBackOfMod, selectBackOf!(input, args[1..$])); | } |} | |package(mir) template frontSelectFrontOf(alias input, args...) |{ | static if (args.length == 0) | enum frontSelectFrontOf = args; | else | { | alias arg = args[0]; | @optmath @property auto ref frontSelectFrontOfMod()() | { | return arg.lightScope.front.selectFront!0(input); | } | alias frontSelectFrontOf = AliasSeq!(frontSelectFrontOfMod, frontSelectFrontOf!(input, args[1..$])); | } |} | |package(mir) template frontSelectBackOf(alias input, args...) |{ | static if (args.length == 0) | enum frontSelectBackOf = args; | else | { | alias arg = args[0]; | @optmath @property auto ref frontSelectBackOfMod | ()() | { | return arg.lightScope.front.selectBack!0(input); | } | alias frontSelectBackOf = AliasSeq!(frontSelectBackOfMod | , frontSelectBackOf!(input, args[1..$])); | } |} source/mir/ndslice/internal.d is 71% covered <<<<<< EOF # path=source-mir-ndslice-iterator.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |Iterator is a type with a pointer like behavior. |An ndslice can be created on top of an iterator using $(SUBREF slice, sliced). | |$(BOOKTABLE $(H2 Iterators), |$(TR $(TH Iterator Name) $(TH Used By)) |$(T2 BytegroupIterator, $(SUBREF topology, bytegroup).) |$(T2 CachedIterator, $(SUBREF topology, cached), $(SUBREF topology, cachedGC).) |$(T2 ChopIterator, $(SUBREF topology, chopped)) |$(T2 FieldIterator, $(SUBREF slice, slicedField), $(SUBREF topology, bitwise), $(SUBREF topology, ndiota), and others.) |$(T2 FlattenedIterator, $(SUBREF topology, flattened)) |$(T2 IndexIterator, $(SUBREF topology, indexed)) |$(T2 IotaIterator, $(SUBREF topology, iota)) |$(T2 MapIterator, $(SUBREF topology, map)) |$(T2 MemberIterator, $(SUBREF topology, member)) |$(T2 NeighboursIterator, $(SUBREF topology, withNeighboursSum)) |$(T2 RetroIterator, $(SUBREF topology, retro)) |$(T2 SliceIterator, $(SUBREF topology, map) in composition with $(LREF MapIterator) for packed slices.) |$(T2 SlideIterator, $(SUBREF topology, diff), $(SUBREF topology, pairwise), and $(SUBREF topology, slide).) |$(T2 StairsIterator, $(SUBREF topology, stairs)) |$(T2 StrideIterator, $(SUBREF topology, stride)) |$(T2 SubSliceIterator, $(SUBREF topology, subSlices)) |$(T2 TripletIterator, $(SUBREF topology, triplets)) |$(T2 ZipIterator, $(SUBREF topology, zip)) |) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.iterator; | |import mir.internal.utility: Iota; |import mir.math.common: optmath; |import mir.ndslice.field; |import mir.ndslice.internal; |import mir.ndslice.slice: SliceKind, Slice, Universal, Canonical, Contiguous, isSlice; |import mir.qualifier; |import mir.conv; |import std.traits; | |private static immutable assumeZeroShiftExceptionMsg = "*.assumeFieldsHaveZeroShift: shift is not zero!"; |version(D_Exceptions) | private static immutable assumeZeroShiftException = new Exception(assumeZeroShiftExceptionMsg); | |@optmath: | |enum std_ops = q{ | void opUnary(string op)() scope | if (op == "--" || op == "++") | { mixin(op ~ "_iterator;"); } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { mixin("_iterator " ~ op ~ "= index;"); } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { | auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); | return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const | { return this._iterator - right._iterator; } | | bool opEquals()(scope ref const typeof(this) right) scope const | { return this._iterator == right._iterator; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!Iterator) | return this._iterator - right._iterator; | else | return this._iterator.opCmp(right._iterator); | } |}; | |/++ |Step counter. | |`IotaIterator` is used by $(SUBREF topology, iota). |+/ |struct IotaIterator(I) | if (isIntegral!I || isPointer!I) |{ |@optmath: | | /// | I _index; | | static if (isPointer!I) | /// | auto lightConst()() const @property | { | static if (isIntegral!I) | return IotaIterator!I(_index); | else 0000000| return IotaIterator!(LightConstOf!I)(_index); | } | | static if (isPointer!I) | /// | auto lightImmutable()() immutable @property | { | static if (isIntegral!I) | return IotaIterator!I(_index); | else | return IotaIterator!(LightImmutableOf!I)(_index); | } | |pure: | | I opUnary(string op : "*")() 62536| { return _index; } | | void opUnary(string op)() | if (op == "--" || op == "++") | { mixin(op ~ `_index;`); } | | I opIndex()(ptrdiff_t index) const 5842| { return cast(I)(_index + index); } | | void opOpAssign(string op)(ptrdiff_t index) | if (op == `+` || op == `-`) | { mixin(`_index ` ~ op ~ `= index;`); } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 153| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 153| return ret; | } | | ptrdiff_t opBinary(string op : "-")(const typeof(this) right) const 6| { return cast(ptrdiff_t)(this._index - right._index); } | | bool opEquals()(const typeof(this) right) const 166| { return this._index == right._index; } | | auto opCmp()(const typeof(this) right) const 6| { return this._index - right._index; } |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| IotaIterator!int iota; 1| assert(*iota == 0); | | // iteration 1| ++iota; 1| assert(*iota == 1); | 1| assert(iota[2] == 3); 1| assert(iota[-1] == 0); | 1| --iota; 1| assert(*iota == 0); | | // opBinary 1| assert(*(iota + 2) == 2); 1| assert(*(iota - 3) == -3); 1| assert((iota - 3) - iota == -3); | | // construction 1| assert(*IotaIterator!int(3) == 3); 1| assert(iota - 1 < iota); |} | |/// |pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| int[32] data; 1| auto iota = IotaIterator!(int*)(data.ptr); 1| assert(*iota == data.ptr); | | // iteration 1| ++iota; 1| assert(*iota == 1 + data.ptr); | 1| assert(iota[2] == 3 + data.ptr); 1| assert(iota[-1] == 0 + data.ptr); | 1| --iota; 1| assert(*iota == 0 + data.ptr); | | // opBinary 1| assert(*(iota + 2) == 2 + data.ptr); 1| assert(*(iota - 3) == -3 + data.ptr); 1| assert((iota - 3) - iota == -3); | | // construction 1| assert(*IotaIterator!(int*)(data.ptr) == data.ptr); 1| assert(iota - 1 < iota); |} | |auto RetroIterator__map(Iterator, alias fun)(ref RetroIterator!Iterator it) |{ 4| auto iterator = it._iterator._mapIterator!fun; 4| return RetroIterator!(typeof(iterator))(iterator); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology; | import mir.ndslice.allocation; 10| auto v = iota(9).retro.map!(a => a).slice; 1| uint r; 19| auto w = iota(9).retro.map!(a => a).map!(a => a * r).slice; |} | |/++ |Reverse directions for an iterator. | |`RetroIterator` is used by $(SUBREF topology, retro). |+/ 5|struct RetroIterator(Iterator) |{ |@optmath: | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 19| return RetroIterator!(LightConstOf!Iterator)(.lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return RetroIterator!(LightImmutableOf!Iterator)(.lightImmutable(_iterator)); | } | | /// | static alias __map(alias fun) = RetroIterator__map!(Iterator, fun); | | auto ref opUnary(string op : "*")() 3316| { return *_iterator; } | | void opUnary(string op : "--")() 1| { ++_iterator; } | | void opUnary(string op : "++")() pure 3277| { --_iterator; } | | auto ref opIndex()(ptrdiff_t index) 744| { return _iterator[-index]; } | | void opOpAssign(string op : "-")(ptrdiff_t index) scope 3| { _iterator += index; } | | void opOpAssign(string op : "+")(ptrdiff_t index) scope 89| { _iterator -= index; } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 43| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 43| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const 1| { return right._iterator - this._iterator; } | | bool opEquals()(scope ref const typeof(this) right) scope const 0000000| { return right._iterator == this._iterator; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!Iterator) 0000000| return right._iterator - this._iterator; | else 1| return right._iterator.opCmp(this._iterator); | } |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| IotaIterator!int iota; 1| RetroIterator!(IotaIterator!int) retro; | 1| ++iota; 1| --retro; 1| assert(*retro == *iota); | 1| --iota; 1| ++retro; 1| assert(*retro == *iota); | 1| assert(retro[-7] == iota[7]); | 1| iota += 100; 1| retro -= 100; 1| assert(*retro == *iota); | 1| iota -= 100; 1| retro += 100; 1| assert(*retro == *iota); | 1| assert(*(retro + 10) == *(iota - 10)); | 1| assert(retro - 1 < retro); | 1| assert((retro - 5) - retro == -5); | 1| iota = IotaIterator!int(3); 1| retro = RetroIterator!(IotaIterator!int)(iota); 1| assert(*retro == *iota); |} | |auto StrideIterator__map(Iterator, alias fun)(StrideIterator!Iterator it) |{ 3| auto iterator = it._iterator._mapIterator!fun; 3| return StrideIterator!(typeof(iterator))(it._stride, iterator); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology; | import mir.ndslice.allocation; 4| auto v = iota([3], 0, 3).map!(a => a).slice; 1| uint r; 7| auto w = iota([3], 0, 3).map!(a => a).map!(a => a * r).slice; |} | |/++ |Iterates an iterator with a fixed strides. | |`StrideIterator` is used by $(SUBREF topology, stride). |+/ |struct StrideIterator(Iterator) |{ |@optmath: | /// | ptrdiff_t _stride; | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 45| return StrideIterator!(LightConstOf!Iterator)(_stride, .lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return StrideIterator!(LightImmutableOf!Iterator)(_stride, .lightImmutable(_iterator)); | } | | /// | static alias __map(alias fun) = StrideIterator__map!(Iterator, fun); | | auto ref opUnary(string op : "*")() 296| { return *_iterator; } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { mixin("_iterator " ~ op[0] ~ "= _stride;"); } | | auto ref opIndex()(ptrdiff_t index) 3227| { return _iterator[index * _stride]; } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { mixin("_iterator " ~ op ~ "= index * _stride;"); } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 3| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 3| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const 1| { return (this._iterator - right._iterator) / _stride; } | | bool opEquals()(scope ref const typeof(this) right) scope const 0000000| { return this._iterator == right._iterator; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!Iterator) 0000000| ptrdiff_t ret = this._iterator - right._iterator; | else 1| ptrdiff_t ret = this._iterator.opCmp(right._iterator); 1| return _stride >= 0 ? ret : -ret; | } |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| IotaIterator!int iota; 1| StrideIterator!(IotaIterator!int) stride; 1| stride._stride = -3; | 1| iota -= stride._stride; 1| --stride; 1| assert(*stride == *iota); | 1| iota += stride._stride; 1| ++stride; 1| assert(*stride == *iota); | 1| assert(stride[7] == iota[7 * stride._stride]); | 1| iota -= 100 * stride._stride; 1| stride -= 100; 1| assert(*stride == *iota); | 1| iota += 100 * stride._stride; 1| stride += 100; 1| assert(*stride == *iota); | 1| assert(*(stride + 10) == *(iota + 10 * stride._stride)); | 1| assert(stride - 1 < stride); | 1| assert((stride - 5) - stride == -5); | 1| iota = IotaIterator!int(3); 1| stride = StrideIterator!(IotaIterator!int)(3, iota); 1| assert(*stride == *iota); |} | |auto StrideIterator__map(Iterator, size_t factor, alias fun)(StrideIterator!(Iterator, factor) it) |{ | auto iterator = it._iterator._mapIterator!fun; | return StrideIterator!(typeof(iterator), factor)(iterator); |} | |/++ |Iterates an iterator with a fixed strides. | |`StrideIterator` is used by $(SUBREF topology, stride). |+/ |struct StrideIterator(Iterator, ptrdiff_t factor) |{ |@optmath: | /// | enum _stride = factor; | | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 6| return StrideIterator!(LightConstOf!Iterator, _stride)(.lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return StrideIterator!(LightImmutableOf!Iterator, _stride)(.lightImmutable(_iterator)); | } | | /// | static alias __map(alias fun) = StrideIterator__map!(Iterator, _stride, fun); | | auto ref opUnary(string op : "*")() 43| { return *_iterator; } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { mixin("_iterator " ~ op[0] ~ "= _stride;"); } | | auto ref opIndex()(ptrdiff_t index) 1| { return _iterator[index * _stride]; } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { mixin("_iterator " ~ op ~ "= index * _stride;"); } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 3| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 3| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const 1| { return (this._iterator - right._iterator) / _stride; } | | bool opEquals()(scope ref const typeof(this) right) scope const 0000000| { return this._iterator == right._iterator; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!Iterator) 0000000| ptrdiff_t ret = this._iterator - right._iterator; | else 1| ptrdiff_t ret = this._iterator.opCmp(right._iterator); 1| return _stride >= 0 ? ret : -ret; | } |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| IotaIterator!int iota; 1| StrideIterator!(IotaIterator!int, -3) stride; | 1| iota -= stride._stride; 1| --stride; 1| assert(*stride == *iota); | 1| iota += stride._stride; 1| ++stride; 1| assert(*stride == *iota); | 1| assert(stride[7] == iota[7 * stride._stride]); | 1| iota -= 100 * stride._stride; 1| stride -= 100; 1| assert(*stride == *iota); | 1| iota += 100 * stride._stride; 1| stride += 100; 1| assert(*stride == *iota); | 1| assert(*(stride + 10) == *(iota + 10 * stride._stride)); | 1| assert(stride - 1 < stride); | 1| assert((stride - 5) - stride == -5); |} | |package template _zip_types(Iterators...) |{ | alias AliasSeq(T...) = T; | static if (Iterators.length) | { | enum i = Iterators.length - 1; | alias T = typeof(Iterators[i].init[sizediff_t.init]); | static if (__traits(compiles, &Iterators[i].init[sizediff_t.init])) | { | import mir.functional: Ref; | alias _zip_types = AliasSeq!(_zip_types!(Iterators[0 .. i]), Ref!T); | } | else | alias _zip_types = AliasSeq!(_zip_types!(Iterators[0 .. i]), T); | } | else | alias _zip_types = AliasSeq!(); |} | |package template _zip_fronts(Iterators...) |{ | static if (Iterators.length) | { | enum i = Iterators.length - 1; | static if (__traits(compiles, &Iterators[i].init[sizediff_t.init])) | enum _zip_fronts = _zip_fronts!(Iterators[0 .. i]) ~ "_ref(*_iterators[" ~ i.stringof ~ "]), "; | else | enum _zip_fronts = _zip_fronts!(Iterators[0 .. i]) ~ "*_iterators[" ~ i.stringof ~ "], "; | } | else | enum _zip_fronts = ""; |} | |package template _zip_index(Iterators...) |{ | static if (Iterators.length) | { | enum i = Iterators.length - 1; | static if (__traits(compiles, &Iterators[i].init[sizediff_t.init])) | enum _zip_index = _zip_index!(Iterators[0 .. i]) ~ "_ref(_iterators[" ~ i.stringof ~ "][index]), "; | else | enum _zip_index = _zip_index!(Iterators[0 .. i]) ~ "_iterators[" ~ i.stringof ~ "][index], "; | } | else | enum _zip_index = ""; |} | |/++ |Iterates multiple iterators in lockstep. | |`ZipIterator` is used by $(SUBREF topology, zip). |+/ |struct ZipIterator(Iterators...) | if (Iterators.length > 1) |{ |@optmath: | import std.traits: ConstOf, ImmutableOf; | import std.meta: staticMap; | import mir.functional: Tuple, Ref, _ref; | /// | Iterators _iterators; | | /// | auto lightConst()() const @property | { | import std.format; | import mir.ndslice.topology: iota; | import std.meta: staticMap; | alias Ret = ZipIterator!(staticMap!(LightConstOf, Iterators)); | enum ret = "Ret(%(.lightConst(_iterators[%s]),%)]))".format(_iterators.length.iota); 30| return mixin(ret); | } | | /// | auto lightImmutable()() immutable @property | { | import std.format; | import mir.ndslice.topology: iota; | import std.meta: staticMap; | alias Ret = ZipIterator!(staticMap!(LightImmutableOf, Iterators)); | enum ret = "Ret(%(.lightImmutable(_iterators[%s]),%)]))".format(_iterators.length.iota); | return mixin(ret); | } | | auto opUnary(string op : "*")() 693| { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } | | | auto opUnary(string op : "*")() const | { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } | | auto opUnary(string op : "*")() immutable | { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } | | void opUnary(string op)() scope | if (op == "++" || op == "--") | { 1394| foreach (ref _iterator; _iterators) | mixin(op ~ `_iterator;`); | } | | auto opIndex()(ptrdiff_t index) 170| { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_index!Iterators ~ ")"); } | | auto opIndexAssign(Types...)(Tuple!(Types) value, ptrdiff_t index) | if (Types.length == Iterators.length) | { 158| foreach(i, ref val; value.expand) | { | import mir.functional: unref; 158| _iterators[i][index] = unref(val); | } 79| return opIndex(index); | } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "+" || op == "-") | { 152| foreach (ref _iterator; _iterators) | mixin(`_iterator ` ~ op ~ `= index;`); | } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 7| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 7| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const 1| { return this._iterators[0] - right._iterators[0]; } | | bool opEquals()(scope ref const typeof(this) right) scope const 100| { return this._iterators[0] == right._iterators[0]; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!(Iterators[0])) 0000000| return this._iterators[0] - right._iterators[0]; | else 1| return this._iterators[0].opCmp(right._iterators[0]); | } | | import std.meta: anySatisfy; | static if (anySatisfy!(hasZeroShiftFieldMember, Iterators)) | /// Defined if at least one of `Iterators` has member `assumeFieldsHaveZeroShift`. | auto assumeFieldsHaveZeroShift() @property | { | import std.meta: staticMap; | alias _fields = _iterators; 1| return mixin("ZipField!(staticMap!(ZeroShiftField, Iterators))(" ~ applyAssumeZeroShift!Iterators ~ ")"); | } |} | |/// |pure nothrow @nogc version(mir_ndslice_test) unittest |{ | import mir.ndslice.traits: isIterator; | 1| double[10] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | alias ItA = IotaIterator!int; | alias ItB = double*; | alias ItZ = ZipIterator!(ItA, ItB); 1| auto zip = ItZ(ItA(3), data.ptr); 1| assert((*zip).a == 3); 1| assert((*zip).b == 1); | | // iteration 1| ++zip; 1| assert((*zip).a == 3 + 1); 1| assert((*zip).b == 1 + 1); 1| assert(&(*zip).b() == data.ptr + 1); | 1| assert(zip[4].a == 3 + 5); 1| assert(zip[4].b == 1 + 5); 1| assert(&zip[4].b() == data.ptr + 5); | 1| --zip; 1| assert((*zip).a == 3); 1| assert((*zip).b == 1); | 1| assert((*(zip + 2)).a == 3 + 2); 1| assert((*(zip - 3)).a == 3 + -3); 1| assert((*(zip + 2)).b == 1 + 2); 1| assert((*(zip + 3 - 3)).b == 1); 1| assert((zip - 3).opBinary!"-"(zip) == -3); | 1| assert(zip == zip); 1| assert(zip - 1 < zip); | | static assert(isIterator!(ZipIterator!(double*, int*))); | static assert(isIterator!(ZipIterator!(immutable(double)*, immutable(int)*))); |} | |/// |struct CachedIterator(Iterator, CacheIterator, FlagIterator) |{ | /// | Iterator _iterator; | /// | CacheIterator _caches; | /// | FlagIterator _flags; | |@optmath: | | /// | auto lightScope()() return scope @property | { 2| return CachedIterator!(LightScopeOf!Iterator, LightScopeOf!CacheIterator, LightScopeOf!FlagIterator)( | .lightScope(_iterator), | .lightScope(_caches), | .lightScope(_flags), | ); | } | | /// | auto lightScope()() return scope const @property | { 0000000| return lightConst.lightScope; | } | | /// | auto lightScope()() return scope immutable @property | { | return lightImmutable.lightScope; | } | | /// | auto lightConst()() const @property | { 0000000| return CachedIterator!(LightConstOf!Iterator, CacheIterator, FlagIterator)( | .lightConst(_iterator), | *cast(CacheIterator*)&_caches, | *cast(FlagIterator*)&_flags, | ); | } | | /// | auto lightImmutable()() immutable @property @trusted | { | return CachedIterator!(LightImmutableOf!Iterator, CacheIterator, FlagIterator)( | .lightImmutable(_iterator), | *cast(CacheIterator*)&_caches, | *cast(FlagIterator*)&_flags, | ); | } | | private alias T = typeof(Iterator.init[0]); | private alias UT = Unqual!T; | | auto opUnary(string op : "*")() | { 0000000| if (_expect(!*_flags, false)) | { 0000000| _flags[0] = true; 0000000| emplaceRef!T(*cast(UT*)&*_caches, *_iterator); | } 0000000| return *_caches; | } | | auto opIndex()(ptrdiff_t index) | { 16| if (_expect(!_flags[index], false)) | { 8| _flags[index] = true; 8| emplaceRef!T(*cast(UT*)&(_caches[index]), _iterator[index]); | } 16| return _caches[index]; | } | | auto ref opIndexAssign(T)(auto ref T val, ptrdiff_t index) | { 4| _flags[index] = true; 4| return _caches[index] = val; | } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { | mixin(op ~ "_iterator;"); | mixin(op ~ "_caches;"); | mixin(op ~ "_flags;"); | } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { | mixin("_iterator" ~ op ~ "= index;"); | mixin("_caches" ~ op ~ "= index;"); | mixin("_flags" ~ op ~ "= index;"); | } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 2| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 2| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const | { return this._iterator - right._iterator; } | | bool opEquals()(scope ref const typeof(this) right) scope const 0000000| { return this._iterator == right._iterator; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!Iterator) | return this._iterator - right._iterator; | else 0000000| return this._iterator.opCmp(right._iterator); | } |} | |private enum map_primitives = q{ | | import mir.functional: Tuple, autoExpandAndForward; | | auto ref opUnary(string op : "*")() | { | static if (is(typeof(*_iterator) : Tuple!T, T...)) | { | auto t = *_iterator; | return _fun(autoExpandAndForward!t); | } | else | return _fun(*_iterator); | } | | auto ref opIndex(ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return _fun(autoExpandAndForward!t); | } | else | return _fun(_iterator[index]); | } | | static if (!__traits(compiles, &opIndex(ptrdiff_t.init))) | { | auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return _fun(autoExpandAndForward!t) = value; | } | else | return _fun(_iterator[index]) = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return _fun(autoExpandAndForward!t); | } | else | return mixin(op ~ "_fun(_iterator[index])"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value"); | } | else | return mixin("_fun(_iterator[index])" ~ op ~ "= value"); | } | } |}; | |/++ |`VmapIterator` is used by $(SUBREF topology, map). |+/ 117|struct VmapIterator(Iterator, Fun) |{ |@optmath: | | /// | Iterator _iterator; | /// | Fun _fun; | | /// | auto lightConst()() const @property | { 36| return VmapIterator!(LightConstOf!Iterator, LightConstOf!Fun)(.lightConst(_iterator), .lightConst(_fun)); | } | | /// | auto lightImmutable()() immutable @property | { | return VmapIterator!(LightImmutableOf!Iterator, LightImmutableOf!Fun)(.lightImmutable(_iterator), .lightImmutable(_fun)); | } | | import mir.functional: Tuple, autoExpandAndForward; | | auto ref opUnary(string op : "*")() | { | static if (is(typeof(*_iterator) : Tuple!T, T...)) | { 206| auto t = *_iterator; 206| return _fun(autoExpandAndForward!t); | } | else 1003| return _fun(*_iterator); | } | | auto ref opIndex(ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { 0000000| auto t = _iterator[index]; 0000000| return _fun(autoExpandAndForward!t); | } | else 1040| return _fun(_iterator[index]); | } | | static if (!__traits(compiles, &opIndex(ptrdiff_t.init))) | { | auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return _fun(autoExpandAndForward!t) = value; | } | else | return _fun(_iterator[index]) = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return mixin(op ~ "_fun(autoExpandAndForward!t)"); | } | else | return mixin(op ~ "_fun(_iterator[index])"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value"); | } | else | return mixin("_fun(_iterator[index])" ~ op ~ "= value"); | } | } | | mixin(std_ops); | | static if (hasZeroShiftFieldMember!Iterator) | /// | auto assumeFieldsHaveZeroShift() @property | { | return _vmapField(_iterator.assumeFieldsHaveZeroShift, _fun); | } |} | |auto MapIterator__map(Iterator, alias fun0, alias fun)(ref MapIterator!(Iterator, fun0) it) |{ 7| return MapIterator!(Iterator, fun)(it._iterator); |} | |/++ |`MapIterator` is used by $(SUBREF topology, map). |+/ 10|struct MapIterator(Iterator, alias _fun) |{ |@optmath: | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 125| return MapIterator!(LightConstOf!Iterator, _fun)(.lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return MapIterator!(LightImmutableOf!Iterator, _fun)(.lightImmutable(_iterator)); | } | | import mir.functional: pipe, autoExpandAndForward; | /// | static alias __map(alias fun1) = MapIterator__map!(Iterator, _fun, pipe!(_fun, fun1)); | | import mir.functional: Tuple, autoExpandAndForward; | | auto ref opUnary(string op : "*")() | { | static if (is(typeof(*_iterator) : Tuple!T, T...)) | { 470| auto t = *_iterator; 470| return _fun(autoExpandAndForward!t); | } | else 4362| return _fun(*_iterator); | } | | auto ref opIndex(ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { 63| auto t = _iterator[index]; 63| return _fun(autoExpandAndForward!t); | } | else 8| return _fun(_iterator[index]); | } | | static if (!__traits(compiles, &opIndex(ptrdiff_t.init))) | { | auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return _fun(autoExpandAndForward!t) = value; | } | else | return _fun(_iterator[index]) = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return mixin(op ~ "_fun(autoExpandAndForward!t)"); | } | else | return mixin(op ~ "_fun(_iterator[index])"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value"); | } | else | return mixin("_fun(_iterator[index])" ~ op ~ "= value"); | } | } | | mixin(std_ops); | | static if (hasZeroShiftFieldMember!Iterator) | /// | auto assumeFieldsHaveZeroShift() @property | { 1| return _mapField!_fun(_iterator.assumeFieldsHaveZeroShift); | } |} | |/+ |Creates a mapped iterator. Uses `__map` if possible. |+/ |auto _mapIterator(alias fun, Iterator)(Iterator iterator) |{ | import core.lifetime: move; | static if (__traits(hasMember, Iterator, "__map")) | { | static if (is(Iterator : MapIterator!(Iter0, fun0), Iter0, alias fun0) | && !__traits(compiles, Iterator.__map!fun(iterator))) | { | // https://github.com/libmir/mir-algorithm/issues/111 | debug(mir) pragma(msg, __FUNCTION__~" not coalescing chained map calls into a single lambda, possibly because of multiple embedded context pointers"); 3| return MapIterator!(Iterator, fun)(move(iterator)); | } | else 40| return Iterator.__map!fun(iterator); | } | else 533| return MapIterator!(Iterator, fun)(move(iterator)); |} | | |/+ |Creates a mapped iterator. Uses `__vmap` if possible. |+/ |auto _vmapIterator(Iterator, Fun)(Iterator iterator, Fun fun) |{ | static if (__traits(hasMember, Iterator, "__vmap")) | return Iterator.__vmap(iterator, fun); | else | return MapIterator!(Iterator, fun)(iterator); |} | |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ | // https://github.com/libmir/mir-algorithm/issues/111 | import mir.ndslice.topology : iota, map; | import mir.functional : pipe; | | static auto foo(T)(T x) | { 6| return x.map!(a => a + 1); | } | | static auto bar(T)(T x) | { 6| return foo(x).map!(a => a + 2); | } | 1| auto data = iota(5); 1| auto result = iota([5], 3); | 11| auto x = data.map!(a => a + 1).map!(a => a + 2); 1| assert(x == result); | 1| auto y = bar(data); 1| assert(y == result); |} | |/++ |`NeighboursIterator` is used by $(SUBREF topology, map). |+/ |struct NeighboursIterator(Iterator, size_t N, alias _fun, bool around) |{ | import std.meta: AliasSeq; |@optmath: | /// | Iterator _iterator; | static if (N) | Iterator[2][N] _neighbours; | else alias _neighbours = AliasSeq!(); | | /// | auto lightConst()() const @property | { 4| LightConstOf!Iterator[2][N] neighbours; 24| foreach (i; 0 .. N) | { 4| neighbours[i][0] = .lightConst(_neighbours[i][0]); 4| neighbours[i][1] = .lightConst(_neighbours[i][1]); | } 4| return NeighboursIterator!(LightConstOf!Iterator, N, _fun, around)(.lightConst(_iterator), neighbours); | } | | /// | auto lightImmutable()() immutable @property | { | LightImmutableOf!Iterator[2][N] neighbours; | foreach (i; 0 .. N) | { | neighbours[i][0] = .lightImmutable(_neighbours[i][0]); | neighbours[i][1] = .lightImmutable(_neighbours[i][1]); | } | return NeighboursIterator!(LightImmutableOf!Iterator, N, _fun, around)(.lightImmutable(_iterator), neighbours); | } | | import mir.functional: Tuple, _ref; | | private alias RA = Unqual!(typeof(_fun(_iterator[-1], _iterator[+1]))); | private alias Result = Tuple!(_zip_types!Iterator, RA); | | auto ref opUnary(string op : "*")() | { 36| return opIndex(0); | } | | auto ref opIndex(ptrdiff_t index) scope | { | static if (around) 36| RA result = _fun(_iterator[index - 1], _iterator[index + 1]); | | foreach (i; Iota!N) | { | static if (i == 0 && !around) | RA result = _fun(_neighbours[i][0][index], _neighbours[i][1][index]); | else 36| result = _fun(result, _fun(_neighbours[i][0][index], _neighbours[i][1][index])); | } | static if (__traits(compiles, &_iterator[index])) | return Result(_ref(_iterator[index]), result); | else 36| return Result(_iterator[index], result); | } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { | mixin(op ~ "_iterator;"); | foreach (i; Iota!N) | { | mixin(op ~ "_neighbours[i][0];"); | mixin(op ~ "_neighbours[i][1];"); | } | } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { | | mixin("_iterator " ~ op ~ "= index;"); | foreach (i; Iota!N) | { | mixin("_neighbours[i][0] " ~ op ~ "= index;"); | mixin("_neighbours[i][1] " ~ op ~ "= index;"); | } | } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { | auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); | return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const | { return this._iterator - right._iterator; } | | bool opEquals()(scope ref const typeof(this) right) scope const 0000000| { return this._iterator == right._iterator; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!Iterator) | return this._iterator - right._iterator; | else 0000000| return this._iterator.opCmp(right._iterator); | } |} | |/++ |`MemberIterator` is used by $(SUBREF topology, member). |+/ |struct MemberIterator(Iterator, string member) |{ |@optmath: | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 2| return MemberIterator!(LightConstOf!Iterator, member)(.lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return MemberIterator!(LightImmutableOf!Iterator, member)(.lightImmutable(_iterator)); | } | | auto ref opUnary(string op : "*")() | { 30| return __traits(getMember, *_iterator, member); | } | | auto ref opIndex()(ptrdiff_t index) | { 0000000| return __traits(getMember, _iterator[index], member); | } | | static if (!__traits(compiles, &opIndex(ptrdiff_t.init))) | { | auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope | { 12| return __traits(getMember, _iterator[index], member) = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | return mixin(op ~ "__traits(getMember, _iterator[index], member)"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | return mixin("__traits(getMember, _iterator[index], member)" ~ op ~ "= value"); | } | } | | mixin(std_ops); |} | |/++ |`BytegroupIterator` is used by $(SUBREF topology, Bytegroup) and $(SUBREF topology, bytegroup). |+/ |struct BytegroupIterator(Iterator, size_t count, DestinationType) | if (count) |{ |@optmath: | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 0000000| return BytegroupIterator!(LightConstOf!Iterator, count, DestinationType)(.lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return BytegroupIterator!(LightImmutableOf!Iterator, count, DestinationType)(.lightImmutable(_iterator)); | } | | package(mir) alias Byte = Unqual!(typeof(_iterator[0])); | | version(LittleEndian) | private enum BE = false; | else | private enum BE = true; | | private union U | { | DestinationType value; | static if (DestinationType.sizeof > Byte[count].sizeof && BE && isScalarType!DestinationType) | { | struct | { | ubyte[DestinationType.sizeof - Byte[count].sizeof] shiftPayload; | Byte[count] bytes; | } | } | else | { | Byte[count] bytes; | } | } | | DestinationType opUnary(string op : "*")() | { 68| U ret = { value: DestinationType.init }; | foreach (i; Iota!count) 204| ret.bytes[i] = _iterator[i]; 68| return ret.value; | } | | DestinationType opIndex()(ptrdiff_t index) | { 4| return *(this + index); | } | | DestinationType opIndexAssign(T)(T val, ptrdiff_t index) scope | { 2| auto it = this + index; 2| U ret = { value: val }; | foreach (i; Iota!count) 6| it._iterator[i] = ret.bytes[i]; 2| return ret.value; | } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { mixin("_iterator " ~ op[0] ~ "= count;"); } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { mixin("_iterator " ~ op ~ "= index * count;"); } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 6| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 6| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const | { return (this._iterator - right._iterator) / count; } | | bool opEquals()(scope ref const typeof(this) right) scope const 0000000| { return this._iterator == right._iterator; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | static if (isPointer!Iterator) 0000000| return this._iterator - right._iterator; | else | return this._iterator.opCmp(right._iterator); | } |} | |auto SlideIterator__map(Iterator, size_t params, alias fun0, alias fun)(SlideIterator!(Iterator, params, fun0) it) |{ | return SlideIterator!(Iterator, params, fun)(it._iterator); |} | |/++ |`SlideIterator` is used by $(SUBREF topology, diff) and $(SUBREF topology, slide). |+/ |struct SlideIterator(Iterator, size_t params, alias fun) | if (params > 1) |{ |@optmath: | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 17| return SlideIterator!(LightConstOf!Iterator, params, fun)(.lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return SlideIterator!(LightImmutableOf!Iterator, params, fun)(.lightImmutable(_iterator)); | } | | import mir.functional: pipe; | /// | static alias __map(alias fun1) = SlideIterator__map!(Iterator, params, fun, pipe!(fun, fun1)); | | auto ref opUnary(string op : "*")() | { 127| return mixin("fun(" ~ _iotaArgs!(params, "_iterator[", "], ") ~ ")"); | } | | auto ref opIndex()(ptrdiff_t index) | { 3779| return mixin("fun(" ~ _iotaArgs!(params, "_iterator[index + ", "], ") ~ ")"); | } | | mixin(std_ops); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.functional: naryFun; 1| auto data = [1, 3, 8, 18]; 1| auto diff = SlideIterator!(int*, 2, naryFun!"b - a")(data.ptr); 1| assert(*diff == 2); 1| assert(diff[1] == 5); 1| assert(diff[2] == 10); |} | |auto IndexIterator__map(Iterator, Field, alias fun)(ref IndexIterator!(Iterator, Field) it) |{ 11| auto field = it._field._mapField!fun; 11| return IndexIterator!(Iterator, typeof(field))(it._iterator, field); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology; | import mir.ndslice.allocation; | import mir.ndslice.slice; 1| auto indices = [4, 3, 1, 2, 0, 4].sliced; 7| auto v = iota(5).indexed(indices).map!(a => a).slice; 1| uint r; 13| auto w = iota(5).indexed(indices).map!(a => a).map!(a => a * r).slice; |} | |/++ |Iterates a field using an iterator. | |`IndexIterator` is used by $(SUBREF topology, indexed). |+/ |struct IndexIterator(Iterator, Field) |{ | import mir.functional: Tuple, autoExpandAndForward; | |@optmath: | /// | Iterator _iterator; | /// | Field _field; | | /// | auto lightConst()() const @property | { 76| return IndexIterator!(LightConstOf!Iterator, LightConstOf!Field)(.lightConst(_iterator), .lightConst(_field)); | } | | /// | auto lightImmutable()() immutable @property | { | return IndexIterator!(LightImmutableOf!Iterator, LightImmutableOf!Field)(.lightImmutable(_iterator), _field.lightImmutable); | } | | /// | static alias __map(alias fun) = IndexIterator__map!(Iterator, Field, fun); | | auto ref opUnary(string op : "*")() | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { 18| auto t = *_iterator; 18| return _field[autoExpandAndForward!t]; | } | else 1929| return _field[*_iterator]; | } | | auto ref opIndex()(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { 0000000| auto t = _iterator[index]; 0000000| return _field[autoExpandAndForward!t]; | } | else 136| return _field[_iterator[index]]; | } | | static if (!__traits(compiles, &opIndex(ptrdiff_t.init))) | { | auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return _field[autoExpandAndForward!t] = value; | } | else | return _field[_iterator[index]] = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return mixin(op ~ "_field[autoExpandAndForward!t]"); | } | else | return mixin(op ~ "_field[_iterator[index]]"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : Tuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_field[autoExpandAndForward!t]" ~ op ~ "= value"); | } | else | return mixin("_field[_iterator[index]]" ~ op ~ "= value"); | } | } | | mixin(std_ops); |} | |/++ |Iterates chunks in a sliceable using an iterator composed of indices. | |Definition: |---- |auto index = iterator[i]; |auto elem = sliceable[index[0] .. index[1]]; |---- |+/ |struct SubSliceIterator(Iterator, Sliceable) |{ |@optmath: | /// | Iterator _iterator; | /// | Sliceable _sliceable; | | /// | auto lightConst()() const @property | { 1| return SubSliceIterator!(LightConstOf!Iterator, LightConstOf!Sliceable)(.lightConst(_iterator), _sliceable.lightConst); | } | | /// | auto lightImmutable()() immutable @property | { | return SubSliceIterator!(LightImmutableOf!Iterator, LightImmutableOf!Sliceable)(.lightImmutable(_iterator), _sliceable.lightImmutable); | } | | auto ref opUnary(string op : "*")() | { 2| auto i = *_iterator; 2| return _sliceable[i[0] .. i[1]]; | } | | auto ref opIndex()(ptrdiff_t index) | { 0000000| auto i = _iterator[index]; 0000000| return _sliceable[i[0] .. i[1]]; | } | | mixin(std_ops); |} | |/++ |Iterates chunks in a sliceable using an iterator composed of indices stored consequently. | |Definition: |---- |auto elem = _sliceable[_iterator[index] .. _iterator[index + 1]]; |---- |+/ 0000000|struct ChopIterator(Iterator, Sliceable) |{ |@optmath: | /// | Iterator _iterator; | /// | Sliceable _sliceable; | | /// | auto lightConst()() const @property | { 9| return ChopIterator!(LightConstOf!Iterator, LightConstOf!Sliceable)(.lightConst(_iterator), _sliceable.lightConst); | } | | /// | auto lightImmutable()() immutable @property | { | return ChopIterator!(LightImmutableOf!Iterator, LightImmutableOf!Sliceable)(.lightImmutable(_iterator), _sliceable.lightImmutable); | } | | auto ref opUnary(string op : "*")() | { 29| return _sliceable[*_iterator .. _iterator[1]]; | } | | auto ref opIndex()(ptrdiff_t index) | { 113| return _sliceable[_iterator[index] .. _iterator[index + 1]]; | } | | mixin(std_ops); |} | |/++ |Iterates on top of another iterator and returns a slice |as a multidimensional window at the current position. | |`SliceIterator` is used by $(SUBREF topology, map) for packed slices. |+/ |struct SliceIterator(Iterator, size_t N = 1, SliceKind kind = Contiguous) |{ |@optmath: | /// | alias Element = Slice!(Iterator, N, kind); | /// | Element._Structure _structure; | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 100| return SliceIterator!(LightConstOf!Iterator, N, kind)(_structure, .lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return SliceIterator!(LightImmutableOf!Iterator, N, kind)(_structure, .lightImmutable(_iterator)); | } | | auto opUnary(string op : "*")() | { 1411| return Element(_structure, _iterator); | } | | auto opIndex()(ptrdiff_t index) | { 34| return Element(_structure, _iterator + index); | } | | mixin(std_ops); |} | |public auto FieldIterator__map(Field, alias fun)(FieldIterator!(Field) it) |{ | import mir.ndslice.field: _mapField; 12| auto field = it._field._mapField!fun; 12| return FieldIterator!(typeof(field))(it._index, field); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology; | import mir.ndslice.allocation; 10| auto v = ndiota(3, 3).map!(a => a).slice; 1| uint r; 19| auto w = ndiota(3, 3).map!(a => a).map!(a => a[0] * r).slice; |} | |/++ |Creates an iterator on top of a field. | |`FieldIterator` is used by $(SUBREF slice, slicedField), $(SUBREF topology, bitwise), $(SUBREF topology, ndiota), and others. |+/ 2|struct FieldIterator(Field) |{ |@optmath: | /// | ptrdiff_t _index; | /// | Field _field; | | /// | auto lightConst()() const @property | { 226| return FieldIterator!(LightConstOf!Field)(_index, .lightConst(_field)); | } | | /// | auto lightImmutable()() immutable @property | { 2| return FieldIterator!(LightImmutableOf!Field)(_index, .lightImmutable(_field)); | } | | /// | static alias __map(alias fun) = FieldIterator__map!(Field, fun); | | /// | Slice!(IotaIterator!size_t) opSlice(size_t dimension)(size_t i, size_t j) scope const | { 8| assert(i <= j); 8| return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /++ | Returns: | `_field[_index + sl.i .. _index + sl.j]`. | +/ | auto opIndex()(Slice!(IotaIterator!size_t) sl) | { 8| auto idx = _index + sl._iterator._index; 8| return _field[idx .. idx + sl.length]; | } | | auto ref opUnary(string op : "*")() 16663| { return _field[_index]; } | | void opUnary(string op)() scope | if (op == "++" || op == "--") | { mixin(op ~ `_index;`); } | | auto ref opIndex()(ptrdiff_t index) 923| { return _field[_index + index]; } | | static if (!__traits(compiles, &_field[_index])) | { | auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) 1228| { return _field[_index + index] = value; } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { mixin (`return ` ~ op ~ `_field[_index + index];`); } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { mixin (`return _field[_index + index] ` ~ op ~ `= value;`); } | } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "+" || op == "-") | { mixin(`_index ` ~ op ~ `= index;`); } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 65| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 65| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const 0000000| { return this._index - right._index; } | | bool opEquals()(scope ref const typeof(this) right) scope const 8| { return this._index == right._index; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const 0000000| { return this._index - right._index; } | | /// | auto assumeFieldsHaveZeroShift() @property | { 2| if (_expect(_index != 0, false)) | { | version (D_Exceptions) 0000000| throw assumeZeroShiftException; | else | assert(0, assumeZeroShiftExceptionMsg); | } | static if (hasZeroShiftFieldMember!Field) | return _field.assumeFieldsHaveZeroShift; | else 2| return _field; | } |} | |auto FlattenedIterator__map(Iterator, size_t N, SliceKind kind, alias fun)(FlattenedIterator!(Iterator, N, kind) it) |{ | import mir.ndslice.topology: map; 3| auto slice = it._slice.map!fun; 3| return FlattenedIterator!(TemplateArgsOf!(typeof(slice)))(it._indices, slice); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology; | import mir.ndslice.allocation; 10| auto v = iota(3, 3).universal.flattened.map!(a => a).slice; 1| uint r; 19| auto w = iota(3, 3).universal.flattened.map!(a => a).map!(a => a * r).slice; |} | |/++ |Creates an iterator on top of all elements in a slice. | |`FieldIterator` is used by $(SUBREF topology, bitwise), $(SUBREF topology, ndiota), and others. |+/ |struct FlattenedIterator(Iterator, size_t N, SliceKind kind) | if (N > 1 && (kind == Universal || kind == Canonical)) |{ |@optmath: | /// | ptrdiff_t[N] _indices; | /// | Slice!(Iterator, N, kind) _slice; | | /// | auto lightConst()() const @property | { 3| return FlattenedIterator!(LightConstOf!Iterator, N, kind)(_indices, _slice.lightConst); | } | | /// | auto lightImmutable()() immutable @property | { | return FlattenedIterator!(LightImmutableOf!Iterator, N, kind)(_indices, _slice.lightImmutable); | } | | /// | static alias __map(alias fun) = FlattenedIterator__map!(Iterator, N, kind, fun); | | private ptrdiff_t getShift()(ptrdiff_t n) | { 137| ptrdiff_t _shift; 137| n += _indices[$ - 1]; | foreach_reverse (i; Iota!(1, N)) | { 242| immutable v = n / ptrdiff_t(_slice._lengths[i]); 242| n %= ptrdiff_t(_slice._lengths[i]); | static if (i == _slice.S) 27| _shift += (n - _indices[i]); | else 215| _shift += (n - _indices[i]) * _slice._strides[i]; 242| n = _indices[i - 1] + v; | } 137| _shift += (n - _indices[0]) * _slice._strides[0]; 137| return _shift; | } | | auto ref opUnary(string op : "*")() | { 505| return *_slice._iterator; | } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { | foreach_reverse (i; Iota!N) | { | static if (i == _slice.S) | mixin(op ~ `_slice._iterator;`); | else | mixin(`_slice._iterator ` ~ op[0] ~ `= _slice._strides[i];`); | mixin (op ~ `_indices[i];`); | static if (i) | { | static if (op == "++") | { 486| if (_indices[i] < _slice._lengths[i]) 356| return; | static if (i == _slice.S) 4| _slice._iterator -= _slice._lengths[i]; | else 126| _slice._iterator -= _slice._lengths[i] * _slice._strides[i]; 130| _indices[i] = 0; | } | else | { 82| if (_indices[i] >= 0) 64| return; | static if (i == _slice.S) | _slice._iterator += _slice._lengths[i]; | else 18| _slice._iterator += _slice._lengths[i] * _slice._strides[i]; 18| _indices[i] = _slice._lengths[i] - 1; | } | } | } | } | | auto ref opIndex()(ptrdiff_t index) | { 137| return _slice._iterator[getShift(index)]; | } | | static if (isMutable!(_slice.DeepElement) && !_slice.hasAccessByRef) | /// | auto ref opIndexAssign(E)(scope ref E elem, size_t index) return scope | { | return _slice._iterator[getShift(index)] = elem; | } | | void opOpAssign(string op : "+")(ptrdiff_t n) scope | { 10| ptrdiff_t _shift; 10| n += _indices[$ - 1]; | foreach_reverse (i; Iota!(1, N)) | { 14| immutable v = n / ptrdiff_t(_slice._lengths[i]); 14| n %= ptrdiff_t(_slice._lengths[i]); | static if (i == _slice.S) | _shift += (n - _indices[i]); | else 14| _shift += (n - _indices[i]) * _slice._strides[i]; 14| _indices[i] = n; 14| n = _indices[i - 1] + v; | } 10| _shift += (n - _indices[0]) * _slice._strides[0]; 10| _indices[0] = n; | foreach_reverse (i; Iota!(1, N)) | { 10| if (_indices[i] >= 0) 9| break; 1| _indices[i] += _slice._lengths[i]; 1| _indices[i - 1]--; | } 10| _slice._iterator += _shift; | } | | void opOpAssign(string op : "-")(ptrdiff_t n) scope 2| { this += -n; } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 1| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 1| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const | { 6| ptrdiff_t ret = this._indices[0] - right._indices[0]; | foreach (i; Iota!(1, N)) | { 6| ret *= _slice._lengths[i]; 6| ret += this._indices[i] - right._indices[i]; | } 6| return ret; | } | | bool opEquals()(scope ref const typeof(this) right) scope const | { | foreach_reverse (i; Iota!N) 8| if (this._indices[i] != right._indices[i]) 0000000| return false; 4| return true; | } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const | { | foreach (i; Iota!(N - 1)) 6| if (auto ret = this._indices[i] - right._indices[i]) 1| return ret; 5| return this._indices[$ - 1] - right._indices[$ - 1]; | } |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology; | import mir.ndslice.slice; | 1| auto it0 = iota(3, 4).universal.flattened._iterator; 1| auto it1 = it0; 1| assert(it0 == it1); 1| it0 += 5; 1| assert(it0 > it1); 1| it0 -= 5; 1| assert(*it0 == *it1); 1| assert(it0 == it1); 1| it0 += 5; 1| it0 += 7; 1| it0 -= 9; 1| assert(it0 > it1); 1| it1 += 3; 1| assert(*it0 == *it1); 1| assert(it0 == it1); 1| assert(it0 <= it1); 1| assert(it0 >= it1); | 1| ++it0; 1| ++it0; 1| ++it0; 1| ++it0; 1| ++it0; 1| ++it0; 1| ++it0; 1| ++it0; 1| ++it0; | 1| assert(it0 - it1 == 9); 1| assert(it1 - it0 == -9); | 1| ++it0; | 1| assert(it0 - it1 == 10); 1| assert(it1 - it0 == -10); | 1| --it0; | 1| assert(it0 - it1 == 9); 1| assert(it1 - it0 == -9); 1| assert(it0[-9] == *it1); 1| assert(*it0 == it1[9]); | 1| --it0; 1| --it0; 1| --it0; 1| --it0; 1| --it0; 1| --it0; 1| --it0; 1| --it0; 1| --it0; 1| assert(*it0 == *it1); 1| assert(it0 == it1); 1| assert(it0 <= it1); 1| assert(it0 >= it1); |} | |/++ |`StairsIterator` is used by $(SUBREF topology, stairs). |+/ |struct StairsIterator(Iterator, string direction) | if (direction == "+" || direction == "-") |{ | /// | size_t _length; | | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 2| return StairsIterator!(LightConstOf!Iterator, direction)(_length, .lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return StairsIterator!(LightImmutableOf!Iterator, direction)(_length, .lightImmutable(_iterator)); | } | |@optmath: | | /// | Slice!Iterator opUnary(string op : "*")() | { | import mir.ndslice.slice: sliced; 28| return _iterator.sliced(_length); | } | | /// | Slice!Iterator opIndex()(ptrdiff_t index) | { | import mir.ndslice.slice: sliced; | static if (direction == "+") | { 4| auto newLength = _length + index; 4| auto shift = ptrdiff_t(_length + newLength - 1) * index / 2; | } | else | { 4| auto newLength = _length - index; 4| auto shift = ptrdiff_t(_length + newLength + 1) * index / 2; | } 8| assert(ptrdiff_t(newLength) >= 0); 8| return (_iterator + shift).sliced(newLength); | } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { | static if (op == "++") | { 18| _iterator += _length; | static if (direction == "+") 9| ++_length; | else 9| --_length; | } | else | { 2| assert(_length); | static if (direction == "+") 1| --_length; | else 1| ++_length; 2| _iterator -= _length; | } | } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { | static if (op == direction) 8| auto newLength = _length + index; | else 8| auto newLength = _length - index; | static if (direction == "+") 8| auto shift = ptrdiff_t(_length + newLength - 1) * index / 2; | else 8| auto shift = ptrdiff_t(_length + newLength + 1) * index / 2; 16| assert(ptrdiff_t(newLength) >= 0); 16| _length = newLength; | static if (op == "+") 10| _iterator += shift; | else 6| _iterator -= shift; | } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 14| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 14| return ret; | } | | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const | { | static if (direction == "+") 2| return this._length - right._length; | else 2| return right._length - this._length; | } | | bool opEquals()(scope ref const typeof(this) right) scope const 2| { return this._length == right._length; } | | ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const 2| { return this - right; } |} | |/// |version(mir_ndslice_test) unittest |{ | // 0 | // 1 2 | // 3 4 5 | // 6 7 8 9 | // 10 11 12 13 14 1| auto it = StairsIterator!(IotaIterator!size_t, "+")(1, IotaIterator!size_t()); 1| assert(*it == [0]); 1| assert(it[4] == [10, 11, 12, 13, 14]); 1| assert(*(it + 4) == [10, 11, 12, 13, 14]); 1| ++it; 1| assert(*it == [1, 2]); 1| it += 3; 1| assert(*it == [10, 11, 12, 13, 14]); 1| assert(it[-3] == [1, 2]); 1| assert(*(it - 3) == [1, 2]); 1| assert(it + 1 > it); 1| assert(it + 1 - 1 == it); 1| assert(it - 3 - it == -3); 1| --it; 1| assert(*it == [6, 7, 8, 9]); |} | |/// |version(mir_ndslice_test) unittest |{ | // [0, 1, 2, 3, 4], | // [5, 6, 7, 8], | // [9, 10, 11], | // [12, 13], | // [14]]); | 1| auto it = StairsIterator!(IotaIterator!size_t, "-")(5, IotaIterator!size_t()); 1| assert(*it == [0, 1, 2, 3, 4]); 1| assert(it[4] == [14]); 1| assert(*(it + 4) == [14]); 1| ++it; 1| assert(*it == [5, 6, 7, 8]); 1| it += 3; 1| assert(*it == [14]); 1| assert(it[-3] == [5, 6, 7, 8]); 1| assert(*(it - 3) == [5, 6, 7, 8]); 1| assert(it + 1 > it); 1| assert(it + 1 - 1 == it); 1| assert(it - 3 - it == -3); 1| --it; 1| assert(*it == [12, 13]); |} | |/++ |Element type of $(LREF TripletIterator). |+/ |struct Triplet(Iterator, SliceKind kind = Contiguous) |{ |@optmath: | /// | size_t _iterator; | /// | Slice!(Iterator, 1, kind) _slice; | | /// | auto lightConst()() const @property | { | return Triplet!(LightConstOf!Iterator, kind)(_iterator, slice.lightConst); | } | | /// | auto lightImmutable()() immutable @property | { | return Triplet!(LightImmutableOf!Iterator, kind)(_iterator, slice.lightImmutable); | } | | @property | { | /// | auto ref center() | { 67| assert(_iterator < _slice.length); 67| return _slice[_iterator]; | } | | /// | Slice!(Iterator, 1, kind) left() | { 22| assert(_iterator < _slice.length); 22| return _slice[0 .. _iterator]; | } | | /// | Slice!(Iterator, 1, kind) right() | { 18| assert(_iterator < _slice.length); 18| return _slice[_iterator + 1 .. $]; | } | } |} | |/++ |Iterates triplets position in a slice. | |`TripletIterator` is used by $(SUBREF topology, triplets). |+/ |struct TripletIterator(Iterator, SliceKind kind = Contiguous) |{ |@optmath: | | /// | size_t _iterator; | /// | Slice!(Iterator, 1, kind) _slice; | | /// | auto lightConst()() const @property | { 1| return TripletIterator!(LightConstOf!Iterator, kind)(_iterator, _slice.lightConst); | } | | /// | auto lightImmutable()() immutable @property | { | return TripletIterator!(LightImmutableOf!Iterator, kind)(_iterator, _slice.lightImmutable); | } | | /// | Triplet!(Iterator, kind) opUnary(string op : "*")() | { 16| return typeof(return)(_iterator, _slice); | } | | /// | Triplet!(Iterator, kind) opIndex()(ptrdiff_t index) | { 5| return typeof(return)(_iterator + index, _slice); | } | | mixin(std_ops); |} source/mir/ndslice/iterator.d is 93% covered <<<<<< EOF # path=source-mir-ndslice-mutation.lst |/++ |$(H2 Multidimensional mutation algorithms) | |This is a submodule of $(MREF mir,ndslice). | |$(BOOKTABLE $(H2 Function), |$(TR $(TH Function Name) $(TH Description)) |) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.mutation; | |import mir.ndslice.slice: Slice, SliceKind; | |/++ |Copies n-dimensional minor. |+/ |void copyMinor(size_t N, IteratorFrom, SliceKind KindFrom, IteratorTo, SliceKind KindTo, IndexIterator)( | Slice!(IteratorFrom, N, KindFrom) from, | Slice!(IteratorTo, N, KindTo) to, | Slice!IndexIterator[N] indices... |) |in { | import mir.internal.utility: Iota; | static foreach (i; Iota!N) 4| assert(indices[i].length == to.length!i); |} |do { | static if (N == 1) 2| to[] = from[indices[0]]; | else 9| foreach (i; 0 .. indices[0].length) | { 2| copyMinor!(N - 1)(from[indices[0][i]], to[i], indices[1 .. N]); | } |} | |/// |version(mir_ndslice_test) |@safe pure nothrow |unittest |{ | import mir.ndslice; | // 0 1 2 3 | // 4 5 6 7 | // 8 9 10 11 1| auto a = iota!int(3, 4); 1| auto b = slice!int(2, 2); 1| copyMinor(a, b, [2, 1].sliced, [0, 3].sliced); 1| assert(b == [[8, 11], [4, 7]]); |} | |/++ |Reverses data in the 1D slice. |+/ |void reverseInPlace(Iterator)(Slice!Iterator slice) |{ | import mir.utility : swap; 9| foreach (i; 0 .. slice.length / 2) 2| swap(slice[i], slice[$ - (i + 1)]); |} | |/// |version(mir_ndslice_test) |@safe pure nothrow |unittest |{ | import mir.ndslice; 1| auto s = 5.iota.slice; 1| s.reverseInPlace; 1| assert([4, 3, 2, 1, 0]); |} source/mir/ndslice/mutation.d is 100% covered <<<<<< EOF # path=source-mir-ndslice-ndfield.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |NdField is a type with `opIndex(size_t[N] index...)` primitive. |An ndslice can be created on top of a ndField using $(SUBREF slice, slicedNdField). | |$(BOOKTABLE $(H2 NdFields), |$(TR $(TH NdField Name) $(TH Used By)) |$(T2 Cartesian, $(SUBREF topology, cartesian)) |$(T2 Kronecker, $(SUBREF topology, kronecker)) |) | |See_also: $(SUBREF concatenation, concatenation). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.ndslice.ndfield; | |import mir.qualifier; |import mir.internal.utility; |import mir.ndslice.internal; |import mir.ndslice.slice; |import mir.primitives; |import std.meta; | |private template _indices(NdFields...) |{ | static if (NdFields.length == 0) | enum _indices = ""; | else | { | alias Next = NdFields[0 .. $ - 1]; | enum i = Next.length; | enum _indices = ._indices!Next ~ | "_fields[" ~ i.stringof ~ "][" ~ _indices_range!([staticMap!(DimensionCount, Next)].sum, DimensionCount!(NdFields[$ - 1])) ~ "], "; | } |} | |private template _indices_range(size_t begin, size_t count) |{ | static if (count == 0) | enum _indices_range = ""; | else | { | enum next = count - 1; | enum elem = begin + next; | enum _indices_range = ._indices_range!(begin, next) ~ "indices[" ~ elem.stringof ~ "], "; | } |} | |/// |struct Cartesian(NdFields...) | if (NdFields.length > 1) |{ | /// | NdFields _fields; | | package(mir) enum size_t M(size_t f) = [staticMap!(DimensionCount, NdFields[0..f])].sum; | package(mir) enum size_t N = M!(NdFields.length); | | /// | auto lightConst()() const @property | { | import std.format; | import mir.ndslice.topology: iota; 35| return mixin("Cartesian!(staticMap!(LightConstOf, NdFields))(%(_fields[%s].lightConst,%)].lightConst)".format(_fields.length.iota)); | } | | /// | auto lightImmutable()() immutable @property | { | import std.format; | import mir.ndslice.topology: iota; | return mixin("Cartesian!(staticMap!(LightImmutableOf, NdFields))(%(_fields[%s].lightImmutable,%)].lightImmutable)".format(_fields.length.iota)); | } | | /// | size_t length(size_t d = 0)() @safe scope const @property | { | foreach(f, NdField; NdFields) | static if (M!f <= d && M!(f + 1) > d) | { | enum d = d - M!f; | static if (d) | return _fields[f].length!(d - M!f); | else 0000000| return _fields[f].length; | } | } | | /// | size_t[N] shape()() @safe scope const @property | { 38| typeof(return) ret; | foreach(f, NdField; NdFields) | { | static if (hasShape!NdField) | { 86| auto s = _fields[f].shape; | foreach(j; Iota!(s.length)) 88| ret[M!f + j] = s[j]; | } | else | { | ret[M!f] = _fields[f].length; | } | } 38| return ret; | } | | /// | size_t elementCount()() @safe scope const @property | { 0000000| size_t ret = 1; | foreach (f, NdField; NdFields) 0000000| ret *= _fields[f].elementCount; 0000000| return ret; | } | | /// | auto opIndex(size_t[N] indices...) | { | import mir.functional : tuple; 712| return mixin("tuple(" ~ _indices!(NdFields) ~ ")"); | } |} | |private template _kr_indices(size_t n) |{ | static if (n == 0) | enum _kr_indices = ""; | else | { | enum i = n - 1; | enum _kr_indices = ._kr_indices!i ~ "_fields[" ~ i.stringof ~ "][ind[" ~ i.stringof ~ "]], "; | } |} | |/// |struct Kronecker(alias fun, NdFields...) | if (NdFields.length > 1 && allSatisfy!(templateOr!(hasShape, hasLength), NdFields[1 .. $])) |{ | /// | NdFields _fields; | | /// | auto lightConst()() const @property | { | import std.format; | import mir.ndslice.topology: iota; 19| return mixin("Kronecker!(fun, staticMap!(LightConstOf, NdFields))(%(_fields[%s].lightConst,%)].lightConst)".format(_fields.length.iota)); | } | | /// | auto lightImmutable()() immutable @property | { | import std.format; | import mir.ndslice.topology: iota; | return mixin("Kronecker!(fun, staticMap!(LightImmutableOf, NdFields))(%(_fields[%s].lightImmutable,%)].lightImmutable)".format(_fields.length.iota)); | } | | private enum N = DimensionCount!(NdFields[$-1]); | | /// | size_t length(size_t d = 0)() scope const @property | { | static if (d == 0) | { | size_t ret = 1; | foreach (f, NdField; NdFields) | ret *= _fields[f].length; | } | else | { | size_t ret = 1; | foreach (f, NdField; NdFields) | ret *= _fields[f].length!d; | } | return ret; | } | | | /// | size_t[N] shape()() scope const @property | { | static if (N > 1) | { 4| size_t[N] ret = 1; | foreach (f, NdField; NdFields) | { 10| auto s = _fields[f].shape; | foreach(i; Iota!N) 20| ret[i] *= s[i]; | } 4| return ret; | } | else | { 2| size_t[1] ret = 1; | foreach (f, NdField; NdFields) 4| ret[0] *= _fields[f].length; 2| return ret; | } | } | | /// | size_t elementCount()() scope const @property | { | size_t ret = 1; | foreach (f, NdField; NdFields) | ret *= _fields[f].elementCount; | ret; | } | | /// | auto ref opIndex()(size_t[N] indices...) | { | static if (N > 1) 128| size_t[N][NdFields.length] ind; | else 6| size_t[NdFields.length] ind; | foreach_reverse (f, NdField; NdFields) | { | static if (f) | { | static if (hasShape!(NdFields[f])) | { 198| auto s = _fields[f].shape; | } | else | { | size_t[1] s; | s[0] = _fields[f].length; | } | static if (N > 1) | { | foreach(i; Iota!N) | { 384| ind[f][i] = indices[i] % s[i]; 384| indices[i] /= s[i]; | } | } | else | { 6| ind[f] = indices[0] % s[0]; 6| indices[0] /= s[0]; | } | } | else | { | static if (N > 1) | { | foreach(i; Iota!N) 256| ind[f][i] = indices[i]; | } | else | { 6| ind[f] = indices[0]; | } | } | } 134| return mixin("fun(" ~ _kr_indices!(ind.length) ~ ")"); | } |} source/mir/ndslice/ndfield.d is 85% covered <<<<<< EOF # path=source-mir-ndslice-package.lst |/+ |## Guide for Slice/BLAS contributors | |1. Make sure functions are | a. inlined(!), | b. `@nogc`, | c. `nothrow`, | d. `pure`. | For this reason, it is preferable to use _simple_ `assert`s with messages | that can be computed at compile time. | The goals are: | 1. to reduce executable size for _any_ compilation mode | 2. to reduce template bloat in object files | 3. to reduce compilation time | 4. to allow users to write extern C bindings for code libraries on `Slice` type. | |2. `std.format`, `std.string`, and `std.conv` should not be used in error | message formatting.`"Use" ~ Concatenation.stringof`. | |3. `mixin template`s may be used for pretty error message formatting. | |4. `Exception`s/`enforce`s should no be used to check indices and lengths. | Exceptions are only allowed for algorithms where validation of input data is | too complicated for the user. `reshape` function is a good example of a case | where Exceptions are required. | If a function might throw an exception, an example with exception handing should be added. | |5. For simple checks like matrix transposition, compile time flags should not be used. | It is much better to opt for runtime matrix transposition. | Furthermore, Slice type provides runtime matrix transposition out of the box. | |6. _Fortran_VS_C_ flags should not be used. They are about notation, | but not about the algorithm itself. For math world users, | a corresponding code example might be included in the documentation. | `transposed` / `everted` can be used in cache-friendly codes. | |7. Compile time evaluation should not be used to produce dummy types like `IdentityMatrix`. | |8. Memory allocation and algorithm logic should be separated whenever possible. | |9. CTFE version(mir_ndslice_test) unittests should be added to new functions. |+/ | |/** |$(H1 Multidimensional Random Access Ranges) | |The package provides a multidimensional array implementation. |It would be well suited to creating machine learning and image |processing algorithms, but should also be general enough for use anywhere with |homogeneously-typed multidimensional data. |In addition, it includes various functions for iteration, accessing, and manipulation. | |Quick_Start: |$(SUBREF slice, sliced) is a function designed to create |a multidimensional view over a range. |Multidimensional view is presented by $(SUBREF slice, Slice) type. | |------ |import mir.ndslice; | |auto matrix = slice!double(3, 4); |matrix[] = 0; |matrix.diagonal[] = 1; | |auto row = matrix[2]; |row[3] = 6; |assert(matrix[2, 3] == 6); // D & C index order |------ | |Note: |In many examples $(REF iota, mir,_ndslice,topology) is used |instead of a regular array, which makes it |possible to carry out tests without memory allocation. | |$(SCRIPT inhibitQuickIndex = 1;) | |$(DIVC quickindex, |$(BOOKTABLE, | |$(TR $(TH Submodule) $(TH Declarations)) | |$(TR $(TDNW $(SUBMODULE slice) $(BR) | $(SMALL $(SUBREF slice, Slice) structure | $(BR) Basic constructors)) | $(TD | $(SUBREF slice, Canonical) | $(SUBREF slice, Contiguous) | $(SUBREF slice, DeepElementType) | $(SUBREF slice, isSlice) | $(SUBREF slice, kindOf) | $(SUBREF slice, Slice) | $(SUBREF slice, sliced) | $(SUBREF slice, slicedField) | $(SUBREF slice, slicedNdField) | $(SUBREF slice, SliceKind) | $(SUBREF slice, Structure) | $(SUBREF slice, Universal) | ) |) | |$(TR $(TDNW $(SUBMODULE allocation) $(BR) | $(SMALL Allocation utilities)) | $(TD | $(SUBREF allocation, bitRcslice) | $(SUBREF allocation, bitSlice) | $(SUBREF allocation, makeNdarray) | $(SUBREF allocation, makeSlice) | $(SUBREF allocation, makeUninitSlice) | $(SUBREF allocation, mininitRcslice) | $(SUBREF allocation, ndarray) | $(SUBREF allocation, rcslice) | $(SUBREF allocation, shape) | $(SUBREF allocation, slice) | $(SUBREF allocation, stdcFreeAlignedSlice) | $(SUBREF allocation, stdcFreeSlice) | $(SUBREF allocation, stdcSlice) | $(SUBREF allocation, stdcUninitAlignedSlice) | $(SUBREF allocation, stdcUninitSlice) | $(SUBREF allocation, uninitAlignedSlice) | $(SUBREF allocation, uninitSlice) | ) |) | |$(TR $(TDNW $(SUBMODULE topology) $(BR) | $(SMALL Subspace manipulations | $(BR) Advanced constructors | $(BR) SliceKind conversion utilities)) | $(TD | $(SUBREF topology, alongDim) | $(SUBREF topology, as) | $(SUBREF topology, asKindOf) | $(SUBREF topology, assumeCanonical) | $(SUBREF topology, assumeContiguous) | $(SUBREF topology, assumeHypercube) | $(SUBREF topology, assumeSameShape) | $(SUBREF topology, bitpack) | $(SUBREF topology, bitwise) | $(SUBREF topology, blocks) | $(SUBREF topology, byDim) | $(SUBREF topology, bytegroup) | $(SUBREF topology, cached) | $(SUBREF topology, cachedGC) | $(SUBREF topology, canonical) | $(SUBREF topology, cartesian) | $(SUBREF topology, chopped) | $(SUBREF topology, cycle) | $(SUBREF topology, diagonal) | $(SUBREF topology, diff) | $(SUBREF topology, dropBorders) | $(SUBREF topology, evertPack) | $(SUBREF topology, flattened) | $(SUBREF topology, indexed) | $(SUBREF topology, iota) | $(SUBREF topology, ipack) | $(SUBREF topology, kronecker) | $(SUBREF topology, linspace) | $(SUBREF topology, magic) | $(SUBREF topology, map) | $(SUBREF topology, member) | $(SUBREF topology, ndiota) | $(SUBREF topology, orthogonalReduceField) | $(SUBREF topology, pack) | $(SUBREF topology, pairwise) | $(SUBREF topology, repeat) | $(SUBREF topology, reshape) | $(SUBREF topology, ReshapeError) | $(SUBREF topology, retro) | $(SUBREF topology, slide) | $(SUBREF topology, slideAlong) | $(SUBREF topology, squeeze) | $(SUBREF topology, stairs) | $(SUBREF topology, stride) | $(SUBREF topology, subSlices) | $(SUBREF topology, triplets) | $(SUBREF topology, universal) | $(SUBREF topology, unsqueeze) | $(SUBREF topology, unzip) | $(SUBREF topology, vmap) | $(SUBREF topology, windows) | $(SUBREF topology, zip) | ) |) | |$(TR $(TDNW $(SUBMODULE filling) $(BR) | $(SMALL Specialized initialisation routines)) | $(TD | $(SUBREF filling, fillVandermonde) | ) |) | |$(TR $(TDNW $(SUBMODULE fuse) $(BR) | $(SMALL Data fusing (stacking) | $(BR) See also $(SUBMODULE concatenation) submodule. | )) | $(TD | $(SUBREF fuse, fuse) | $(SUBREF fuse, fuseAs) | $(SUBREF fuse, rcfuse) | $(SUBREF fuse, rcfuseAs) | $(SUBREF fuse, fuseCells) | ) |) | |$(TR $(TDNW $(SUBMODULE concatenation) $(BR) | $(SMALL Concatenation, padding, and algorithms | $(BR) See also $(SUBMODULE fuse) submodule. | )) | $(TD | $(SUBREF concatenation, forEachFragment) | $(SUBREF concatenation, isConcatenation) | $(SUBREF concatenation, pad) | $(SUBREF concatenation, padEdge) | $(SUBREF concatenation, padWrap) | $(SUBREF concatenation, padSymmetric) | $(SUBREF concatenation, concatenation) | $(SUBREF concatenation, Concatenation) | $(SUBREF concatenation, concatenationDimension) | $(SUBREF concatenation, until) | ) |) | |$(TR $(TDNW $(SUBMODULE dynamic) | $(BR) $(SMALL Dynamic dimension manipulators)) | $(TD | $(SUBREF dynamic, allReversed) | $(SUBREF dynamic, dropToHypercube) | $(SUBREF dynamic, everted) | $(SUBREF dynamic, normalizeStructure) | $(SUBREF dynamic, reversed) | $(SUBREF dynamic, rotated) | $(SUBREF dynamic, strided) | $(SUBREF dynamic, swapped) | $(SUBREF dynamic, transposed) | ) |) | |$(TR $(TDNW $(SUBMODULE sorting) | $(BR) $(SMALL Sorting utilities)) | $(TD | $(SUBREF sorting, sort) | Examples for `isSorted`, `isStrictlyMonotonic`, `makeIndex`, and `schwartzSort`. | ) |) | |$(TR $(TDNW $(SUBMODULE mutation) | $(BR) $(SMALL Mutation utilities)) | $(TD | $(SUBREF mutation, copyMinor) | $(SUBREF mutation, reverseInPlace) | ) |) | |$(TR $(TDNW $(SUBMODULE iterator) | $(BR) $(SMALL Declarations)) | $(TD | $(SUBREF iterator, BytegroupIterator) | $(SUBREF iterator, CachedIterator) | $(SUBREF iterator, ChopIterator) | $(SUBREF iterator, FieldIterator) | $(SUBREF iterator, FlattenedIterator) | $(SUBREF iterator, IndexIterator) | $(SUBREF iterator, IotaIterator) | $(SUBREF iterator, MapIterator) | $(SUBREF iterator, MemberIterator) | $(SUBREF iterator, RetroIterator) | $(SUBREF iterator, SliceIterator) | $(SUBREF iterator, SlideIterator) | $(SUBREF iterator, StairsIterator) | $(SUBREF iterator, StrideIterator) | $(SUBREF iterator, SubSliceIterator) | $(SUBREF iterator, Triplet) | $(SUBREF iterator, TripletIterator) | $(SUBREF iterator, ZipIterator) | ) |) | |$(TR $(TDNW $(SUBMODULE field) | $(BR) $(SMALL Declarations)) | $(TD | $(SUBREF field, BitField) | $(SUBREF field, BitpackField) | $(SUBREF field, CycleField) | $(SUBREF field, LinspaceField) | $(SUBREF field, MagicField) | $(SUBREF field, MapField) | $(SUBREF field, ndIotaField) | $(SUBREF field, OrthogonalReduceField) | $(SUBREF field, RepeatField) | ) |) | |$(TR $(TDNW $(SUBMODULE ndfield) | $(BR) $(SMALL Declarations)) | $(TD | $(SUBREF ndfield, Cartesian) | $(SUBREF ndfield, Kronecker) | ) |) | |$(TR $(TDNW $(SUBMODULE chunks) | $(BR) $(SMALL Declarations)) | $(TD | $(SUBREF field, chunks) | $(SUBREF field, Chunks) | $(SUBREF field, isChunks) | $(SUBREF field, popFrontTuple) | ) |) | |$(TR $(TDNW $(SUBMODULE traits) | $(BR) $(SMALL Declarations)) | $(TD | $(SUBREF traits, isIterator) | $(SUBREF traits, isVector) | $(SUBREF traits, isMatrix) | $(SUBREF traits, isContiguousSlice) | $(SUBREF traits, isCanonicalSlice) | $(SUBREF traits, isUniversalSlice) | $(SUBREF traits, isContiguousVector) | $(SUBREF traits, isUniversalVector) | $(SUBREF traits, isContiguousMatrix) | $(SUBREF traits, isCanonicalMatrix) | $(SUBREF traits, isUniversalMatrix) | ) |) | |)) | |$(H2 Example: Image Processing) | |A median filter is implemented as an example. The function |`movingWindowByChannel` can also be used with other filters that use a sliding |window as the argument, in particular with convolution matrices such as the |$(LINK2 https://en.wikipedia.org/wiki/Sobel_operator, Sobel operator). | |`movingWindowByChannel` iterates over an image in sliding window mode. |Each window is transferred to a `filter`, which calculates the value of the |pixel that corresponds to the given window. | |This function does not calculate border cases in which a window overlaps |the image partially. However, the function can still be used to carry out such |calculations. That can be done by creating an amplified image, with the edges |reflected from the original image, and then applying the given function to the |new file. | |Note: You can find the example at |$(LINK2 https://github.com/libmir/mir/blob/master/examples/median_filter.d, GitHub). | |------- |/++ |Params: | filter = unary function. Dimension window 2D is the argument. | image = image dimensions `(h, w, c)`, | where с is the number of channels in the image | nr = number of rows in the window | nс = number of columns in the window | |Returns: | image dimensions `(h - nr + 1, w - nc + 1, c)`, | where с is the number of channels in the image. | Dense data layout is guaranteed. |+/ |Slice!(ubyte*, 3) movingWindowByChannel |(Slice!(Universal, [3], ubyte*) image, size_t nr, size_t nc, ubyte delegate(Slice!(Universal, [2], ubyte*)) filter) |{ | // 0. 3D | // The last dimension represents the color channel. | return image | // 1. 2D composed of 1D | // Packs the last dimension. | .pack!1 | // 2. 2D composed of 2D composed of 1D | // Splits image into overlapping windows. | .windows(nr, nc) | // 3. 5D | // Unpacks the windows. | .unpack | .transposed!(0, 1, 4) | // 4. 5D | // Brings the color channel dimension to the third position. | .pack!2 | // 2D to pixel lazy conversion. | .map!filter | // Creates the new image. The only memory allocation in this function. | .slice; |} |------- | |A function that calculates the value of iterator median is also necessary. | |------- |/++ | |Params: | r = input range | buf = buffer with length no less than the number of elements in `r` |Returns: | median value over the range `r` |+/ |T median(Range, T)(Slice!(Universal, [2], Range) sl, T[] buf) |{ | import std.algorithm.sorting : topN; | // copy sl to the buffer | auto retPtr = reduce!( | (ptr, elem) { *ptr = elem; return ptr + 1;} )(buf.ptr, sl); | auto n = retPtr - buf.ptr; | buf[0 .. n].topN(n / 2); | return buf[n / 2]; |} |------- | |The `main` function: | |------- |void main(string[] args) |{ | import std.conv : to; | import std.getopt : getopt, defaultGetoptPrinter; | import std.path : stripExtension; | | uint nr, nc, def = 3; | auto helpInformation = args.getopt( | "nr", "number of rows in window, default value is " ~ def.to!string, &nr, | "nc", "number of columns in window, default value is equal to nr", &nc); | if (helpInformation.helpWanted) | { | defaultGetoptPrinter( | "Usage: median-filter [] []\noptions:", | helpInformation.options); | return; | } | if (!nr) nr = def; | if (!nc) nc = nr; | | auto buf = new ubyte[nr * nc]; | | foreach (name; args[1 .. $]) | { | import imageformats; // can be found at code.dlang.org | | IFImage image = read_image(name); | | auto ret = image.pixels | .sliced(cast(size_t)image.h, cast(size_t)image.w, cast(size_t)image.c) | .movingWindowByChannel | !(window => median(window, buf)) | (nr, nc); | | write_image( | name.stripExtension ~ "_filtered.png", | ret.length!1, | ret.length!0, | (&ret[0, 0, 0])[0 .. ret.elementCount]); | } |} |------- | |This program works both with color and grayscale images. | |------- |$ median-filter --help |Usage: median-filter [] [] |options: | --nr number of rows in window, default value is 3 | --nc number of columns in window default value equals to nr |-h --help This help information. |------- | |$(H2 Compared with `numpy.ndarray`) | |numpy is undoubtedly one of the most effective software packages that has |facilitated the work of many engineers and scientists. However, due to the |specifics of implementation of Python, a programmer who wishes to use the |functions not represented in numpy may find that the built-in functions |implemented specifically for numpy are not enough, and their Python |implementations work at a very low speed. Extending numpy can be done, but |is somewhat laborious as even the most basic numpy functions that refer |directly to `ndarray` data must be implemented in C for reasonable performance. | |At the same time, while working with `ndslice`, an engineer has access to the |whole set of standard D library, so the functions he creates will be as |efficient as if they were written in C. | | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki |Acknowledgements: John Loughran Colvin | |Macros: |SUBMODULE = $(MREF_ALTTEXT $1, mir, ndslice, $1) |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |TDNW2 = $0 |*/ |module mir.ndslice; | |public import mir.algorithm.iteration; |public import mir.ndslice.allocation; |public import mir.ndslice.chunks; |public import mir.ndslice.concatenation; |public import mir.ndslice.dynamic; |public import mir.ndslice.field; |public import mir.ndslice.filling; |public import mir.ndslice.fuse; |public import mir.ndslice.iterator; |public import mir.ndslice.mutation; |public import mir.ndslice.ndfield; |public import mir.ndslice.slice; |public import mir.ndslice.topology; |public import mir.ndslice.traits; | | |version(mir_ndslice_test) unittest |{ 1| auto matrix = new double[12].sliced(3, 4); 1| matrix[] = 0; 1| matrix.diagonal[] = 1; | 1| auto row = matrix[2]; 1| row[3] = 6; 1| assert(matrix[2, 3] == 6); // D & C index order | //assert(matrix(3, 2) == 6); // Math & Fortran index order |} | |// relaxed example |version(mir_ndslice_test) unittest |{ | import mir.qualifier; | | static Slice!(ubyte*, 3) movingWindowByChannel | (Slice!(ubyte*, 3, Universal) image, size_t nr, size_t nc, ubyte delegate(LightConstOf!(Slice!(ubyte*, 2, Universal))) filter) | { 0000000| return image | .pack!1 | .windows(nr, nc) | .unpack | .unpack | .transposed!(0, 1, 4) | .pack!2 | .map!filter | .slice; | } | | static T median(Iterator, T)(Slice!(Iterator, 2, Universal) sl, T[] buf) | { | import std.algorithm.sorting : topN; | // copy sl to the buffer 0000000| auto retPtr = reduce!( | (ptr, elem) { 0000000| *ptr = elem; 0000000| return ptr + 1; | } )(buf.ptr, sl); 0000000| auto n = retPtr - buf.ptr; 0000000| buf[0 .. n].topN(n / 2); 0000000| return buf[n / 2]; | } | | import std.conv : to; | import std.getopt : getopt, defaultGetoptPrinter; | import std.path : stripExtension; | 1| auto args = ["std"]; 3| uint nr, nc, def = 3; 1| auto helpInformation = args.getopt( | "nr", "number of rows in window, default value is " ~ def.to!string, &nr, | "nc", "number of columns in window default value equals to nr", &nc); 1| if (helpInformation.helpWanted) | { 0000000| defaultGetoptPrinter( | "Usage: median-filter [] []\noptions:", | helpInformation.options); 0000000| return; | } 2| if (!nr) nr = def; 2| if (!nc) nc = nr; | 1| auto buf = new ubyte[nr * nc]; | 3| foreach (name; args[1 .. $]) | { 0000000| auto ret = | movingWindowByChannel 0000000| (new ubyte[300].sliced(10, 10, 3).universal, nr, nc, window => median(window, buf)); | } |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ 1| immutable r = 1000.iota; | 1| auto t0 = r.sliced(1000); 1| assert(t0.front == 0); 1| assert(t0.back == 999); 1| assert(t0[9] == 9); | 1| auto t1 = t0[10 .. 20]; 1| assert(t1.front == 10); 1| assert(t1.back == 19); 1| assert(t1[9] == 19); | 1| t1.popFront(); 1| assert(t1.front == 11); 1| t1.popFront(); 1| assert(t1.front == 12); | 1| t1.popBack(); 1| assert(t1.back == 18); 1| t1.popBack(); 1| assert(t1.back == 17); | 1| assert(t1 == iota([6], 12)); |} | |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; | import mir.array.allocation : array; 1| auto r = 1000.iota.array; | 1| auto t0 = r.sliced(1000); 1| assert(t0.length == 1000); 1| assert(t0.front == 0); 1| assert(t0.back == 999); 1| assert(t0[9] == 9); | 1| auto t1 = t0[10 .. 20]; 1| assert(t1.front == 10); 1| assert(t1.back == 19); 1| assert(t1[9] == 19); | 1| t1.popFront(); 1| assert(t1.front == 11); 1| t1.popFront(); 1| assert(t1.front == 12); | 1| t1.popBack(); 1| assert(t1.back == 18); 1| t1.popBack(); 1| assert(t1.back == 17); | 1| assert(t1 == iota([6], 12)); | 1| t1.front = 13; 1| assert(t1.front == 13); 1| t1.front++; 1| assert(t1.front == 14); 1| t1.front += 2; 1| assert(t1.front == 16); 1| t1.front = 12; 1| assert((t1.front = 12) == 12); | 1| t1.back = 13; 1| assert(t1.back == 13); 1| t1.back++; 1| assert(t1.back == 14); 1| t1.back += 2; 1| assert(t1.back == 16); 1| t1.back = 12; 1| assert((t1.back = 12) == 12); | 1| t1[3] = 13; 1| assert(t1[3] == 13); 1| t1[3]++; 1| assert(t1[3] == 14); 1| t1[3] += 2; 1| assert(t1[3] == 16); 1| t1[3] = 12; 1| assert((t1[3] = 12) == 12); | 1| t1[3 .. 5] = 100; 1| assert(t1[2] != 100); 1| assert(t1[3] == 100); 1| assert(t1[4] == 100); 1| assert(t1[5] != 100); | 1| t1[3 .. 5] += 100; 1| assert(t1[2] < 100); 1| assert(t1[3] == 200); 1| assert(t1[4] == 200); 1| assert(t1[5] < 100); | 1| --t1[3 .. 5]; | 1| assert(t1[2] < 100); 1| assert(t1[3] == 199); 1| assert(t1[4] == 199); 1| assert(t1[5] < 100); | 1| --t1[]; 1| assert(t1[3] == 198); 1| assert(t1[4] == 198); | 1| t1[] += 2; 1| assert(t1[3] == 200); 1| assert(t1[4] == 200); | 1| t1[].opIndexOpAssign!"*"(t1); 1| assert(t1[3] == 40000); 1| assert(t1[4] == 40000); | | 1| assert(&t1[$ - 1] is &(t1.back())); |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import std.range : iota; 1| auto r = (10_000L * 2 * 3 * 4).iota; | 1| auto t0 = r.slicedField(10, 20, 30, 40); 1| assert(t0.length == 10); 1| assert(t0.length!0 == 10); 1| assert(t0.length!1 == 20); 1| assert(t0.length!2 == 30); 1| assert(t0.length!3 == 40); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto tensor = new int[3 * 4 * 8].sliced(3, 4, 8); 1| assert(&(tensor.back.back.back()) is &tensor[2, 3, 7]); 1| assert(&(tensor.front.front.front()) is &tensor[0, 0, 0]); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto slice = new int[24].sliced(2, 3, 4); 1| auto r0 = slice.pack!1[1, 2]; 1| slice.pack!1[1, 2][] = 4; 1| auto r1 = slice[1, 2]; 1| assert(slice[1, 2, 3] == 4); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto ar = new int[3 * 8 * 9]; | 1| auto tensor = ar.sliced(3, 8, 9); 1| tensor[0, 1, 2] = 4; 1| tensor[0, 1, 2]++; 1| assert(tensor[0, 1, 2] == 5); 1| tensor[0, 1, 2]--; 1| assert(tensor[0, 1, 2] == 4); 1| tensor[0, 1, 2] += 2; 1| assert(tensor[0, 1, 2] == 6); | 1| auto matrix = tensor[0 .. $, 1, 0 .. $]; 1| matrix[] = 10; 1| assert(tensor[0, 1, 2] == 10); 1| assert(matrix[0, 2] == tensor[0, 1, 2]); 1| assert(&matrix[0, 2] is &tensor[0, 1, 2]); |} | |version(mir_ndslice_test) |pure nothrow |unittest |{ 1| auto x = iota(2, 3); 1| auto y = iota([2, 3], 1); 1| auto combine1 = x.zip(y).map!"b"; 2| auto combine2 = x.zip(y).map!("b", "a * b").map!(a => a[0]); | 1| assert(combine1[0, 0] == 1); 1| assert(combine2[0, 0] == 1); | static assert(is(typeof(combine2[0, 0]) == sizediff_t)); |} source/mir/ndslice/package.d is 92% covered <<<<<< EOF # path=source-mir-ndslice-slice.lst |/++ |This is a submodule of $(MREF mir, ndslice). | |Safety_note: | User-defined iterators should care about their safety except bounds checks. | Bounds are checked in ndslice code. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |$(BOOKTABLE $(H2 Definitions), |$(TR $(TH Name) $(TH Description)) |$(T2 Slice, N-dimensional slice.) |$(T2 SliceKind, SliceKind of $(LREF Slice) enumeration.) |$(T2 Universal, Alias for $(LREF .SliceKind.universal).) |$(T2 Canonical, Alias for $(LREF .SliceKind.canonical).) |$(T2 Contiguous, Alias for $(LREF .SliceKind.contiguous).) |$(T2 sliced, Creates a slice on top of an iterator, a pointer, or an array's pointer.) |$(T2 slicedField, Creates a slice on top of a field, a random access range, or an array.) |$(T2 slicedNdField, Creates a slice on top of an ndField.) |$(T2 kindOf, Extracts $(LREF SliceKind).) |$(T2 isSlice, Checks if the type is `Slice` instance.) |$(T2 Structure, A tuple of lengths and strides.) |) | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) |STD = $(TD $(SMALL $0)) |+/ |module mir.ndslice.slice; | |import mir.internal.utility : Iota; |import mir.math.common : optmath; |import mir.ndslice.concatenation; |import mir.ndslice.field; |import mir.ndslice.internal; |import mir.ndslice.iterator; |import mir.ndslice.traits: isIterator; |import mir.primitives; |import mir.qualifier; |import mir.utility; |import std.meta; |import std.traits; | |/// |public import mir.primitives: DeepElementType; | |/++ |Checks if type T has asSlice property and its returns a slices. |Aliases itself to a dimension count |+/ |template hasAsSlice(T) |{ | static if (__traits(hasMember, T, "asSlice")) | enum size_t hasAsSlice = typeof(T.init.asSlice).N; | else | enum size_t hasAsSlice = 0; |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.series; | static assert(!hasAsSlice!(int[])); | static assert(hasAsSlice!(SeriesMap!(int, string)) == 1); |} | |/++ |Check if $(LREF toConst) function can be called with type T. |+/ |enum isConvertibleToSlice(T) = isSlice!T || isDynamicArray!T || hasAsSlice!T; | |/// |version(mir_ndslice_test) unittest |{ | import mir.series: SeriesMap; | static assert(isConvertibleToSlice!(immutable int[])); | static assert(isConvertibleToSlice!(string[])); | static assert(isConvertibleToSlice!(SeriesMap!(string, int))); | static assert(isConvertibleToSlice!(Slice!(int*))); |} | |/++ |Reurns: | Ndslice view in the same data. |See_also: $(LREF isConvertibleToSlice). |+/ |auto toSlice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) val) |{ | import core.lifetime: move; | return val.move; |} | |/// ditto |auto toSlice(Iterator, size_t N, SliceKind kind)(const Slice!(Iterator, N, kind) val) |{ | return val[]; |} | |/// ditto |auto toSlice(Iterator, size_t N, SliceKind kind)(immutable Slice!(Iterator, N, kind) val) |{ | return val[]; |} | |/// ditto |auto toSlice(T)(T[] val) |{ 12| return val.sliced; |} | |/// ditto |auto toSlice(T)(T val) | if (hasAsSlice!T || __traits(hasMember, T, "moveToSlice")) |{ | static if (__traits(hasMember, T, "moveToSlice")) | return val.moveToSlice; | else | return val.asSlice; |} | |/// ditto |auto toSlice(T)(ref T val) | if (hasAsSlice!T) |{ | return val.asSlice; |} | |/// |template toSlices(args...) |{ | static if (args.length) | { | alias arg = args[0]; | alias Arg = typeof(arg); | static if (isMutable!Arg && isSlice!Arg) | alias slc = arg; | else | @optmath @property auto ref slc()() | { | return toSlice(arg); | } | alias toSlices = AliasSeq!(slc, toSlices!(args[1..$])); | } | else | alias toSlices = AliasSeq!(); |} | |/++ |Checks if the type is `Slice` instance. |+/ |enum isSlice(T) = is(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind); | |/// |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias A = uint[]; | alias S = Slice!(int*); | | static assert(isSlice!S); | static assert(!isSlice!A); |} | |/++ |SliceKind of $(LREF Slice). |See_also: | $(SUBREF topology, universal), | $(SUBREF topology, canonical), | $(SUBREF topology, assumeCanonical), | $(SUBREF topology, assumeContiguous). |+/ |enum mir_slice_kind |{ | /// A slice has strides for all dimensions. | universal, | /// A slice has >=2 dimensions and row dimension is contiguous. | canonical, | /// A slice is a flat contiguous data without strides. | contiguous, |} |/// ditto |alias SliceKind = mir_slice_kind; | |/++ |Alias for $(LREF .SliceKind.universal). | |See_also: | Internal Binary Representation section in $(LREF Slice). |+/ |alias Universal = SliceKind.universal; |/++ |Alias for $(LREF .SliceKind.canonical). | |See_also: | Internal Binary Representation section in $(LREF Slice). |+/ |alias Canonical = SliceKind.canonical; |/++ |Alias for $(LREF .SliceKind.contiguous). | |See_also: | Internal Binary Representation section in $(LREF Slice). |+/ |alias Contiguous = SliceKind.contiguous; | |/// Extracts $(LREF SliceKind). |enum kindOf(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind) = kind; | |/// |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | static assert(kindOf!(Slice!(int*, 1, Universal)) == Universal); |} | |/// Extracts iterator type from a $(LREF Slice). |alias IteratorOf(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind) = Iterator; | |private template SkipDimension(size_t dimension, size_t index) |{ | static if (index < dimension) | enum SkipDimension = index; | else | static if (index == dimension) | static assert (0, "SkipInex: wrong index"); | else | enum SkipDimension = index - 1; |} | |/++ |Creates an n-dimensional slice-shell over an iterator. |Params: | iterator = An iterator, a pointer, or an array. | lengths = A list of lengths for each dimension |Returns: | n-dimensional slice |+/ |auto sliced(size_t N, Iterator)(return scope Iterator iterator, size_t[N] lengths...) | if (!__traits(isStaticArray, Iterator) && N | && !is(Iterator : Slice!(_Iterator, _N, kind), _Iterator, size_t _N, SliceKind kind)) |{ | alias C = ImplicitlyUnqual!(typeof(iterator)); 4478| size_t[N] _lengths; | foreach (i; Iota!N) 8466| _lengths[i] = lengths[i]; 4478| ptrdiff_t[1] _strides = 0; | static if (isDynamicArray!Iterator) | { 554| assert(lengthsProduct(_lengths) <= iterator.length, | "array length should be greater or equal to the product of constructed ndslice lengths"); 554| auto ptr = iterator.length ? &iterator[0] : null; 554| return Slice!(typeof(C.init[0])*, N)(_lengths, ptr); | } | else | { | // break safety 3924| if (false) | { | ++iterator; | --iterator; | iterator += 34; | iterator -= 34; | } | import core.lifetime: move; 3924| return Slice!(C, N)(_lengths, iterator.move); | } |} | |/// Random access range primitives for slices over user defined types |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ | struct MyIota | { | //`[index]` operator overloading | auto opIndex(size_t index) @safe nothrow | { 2| return index; | } | 0000000| auto lightConst()() const @property { return MyIota(); } | auto lightImmutable()() immutable @property { return MyIota(); } | } | | import mir.ndslice.iterator: FieldIterator; | alias Iterator = FieldIterator!MyIota; | alias S = Slice!(Iterator, 2); | import std.range.primitives; | static assert(hasLength!S); | static assert(hasSlicing!S); | static assert(isRandomAccessRange!S); | 1| auto slice = Iterator().sliced(20, 10); 1| assert(slice[1, 2] == 12); 1| auto sCopy = slice.save; 1| assert(slice[1, 2] == 12); |} | |/++ |Creates an 1-dimensional slice-shell over an array. |Params: | array = An array. |Returns: | 1-dimensional slice |+/ |Slice!(T*) sliced(T)(T[] array) @trusted |{ | version(LDC) pragma(inline, true); 5459| return Slice!(T*)([array.length], array.ptr); |} | |/// Creates a slice from an array. |@safe pure nothrow version(mir_ndslice_test) unittest |{ 1| auto slice = new int[10].sliced; 1| assert(slice.length == 10); | static assert(is(typeof(slice) == Slice!(int*))); |} | |/++ |Creates an n-dimensional slice-shell over the 1-dimensional input slice. |Params: | slice = slice | lengths = A list of lengths for each dimension. |Returns: | n-dimensional slice |+/ |Slice!(Iterator, N, kind) | sliced | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, 1, kind) slice, size_t[N] lengths...) | if (N) |{ 22| auto structure = typeof(return)._Structure.init; 22| structure[0] = lengths; | static if (kind != Contiguous) | { | import mir.ndslice.topology: iota; | structure[1] = structure[0].iota.strides; | } | import core.lifetime: move; 22| return typeof(return)(structure, slice._iterator.move); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; 1| auto data = new int[24]; 99| foreach (i, ref e; data) 24| e = cast(int)i; 1| auto a = data[0..10].sliced(10)[0..6].sliced(2, 3); 1| auto b = iota!int(10)[0..6].sliced(2, 3); 1| assert(a == b); 1| a[] += b; 27| foreach (i, e; data[0..6]) 6| assert(e == 2*i); 75| foreach (i, e; data[6..$]) 18| assert(e == i+6); |} | |/++ |Creates an n-dimensional slice-shell over a field. |Params: | field = A field. The length of the | array should be equal to or less then the product of | lengths. | lengths = A list of lengths for each dimension. |Returns: | n-dimensional slice |+/ |Slice!(FieldIterator!Field, N) |slicedField(Field, size_t N)(Field field, size_t[N] lengths...) | if (N) |{ | static if (hasLength!Field) 60| assert(lengths.lengthsProduct <= field.length, "Length product should be less or equal to the field length."); 66| return FieldIterator!Field(0, field).sliced(lengths); |} | |///ditto |auto slicedField(Field)(Field field) | if(hasLength!Field) |{ 19| return .slicedField(field, field.length); |} | |/// Creates an 1-dimensional slice over a field, array, or random access range. |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; 1| auto slice = 10.iota.slicedField; 1| assert(slice.length == 10); |} | |/++ |Creates an n-dimensional slice-shell over an ndField. |Params: | field = A ndField. Lengths should fit into field's shape. | lengths = A list of lengths for each dimension. |Returns: | n-dimensional slice |See_also: $(SUBREF concatenation, concatenation) examples. |+/ |Slice!(IndexIterator!(FieldIterator!(ndIotaField!N), ndField), N) |slicedNdField(ndField, size_t N)(ndField field, size_t[N] lengths...) | if (N) |{ | static if(hasShape!ndField) | { 24| auto shape = field.shape; 228| foreach (i; 0 .. N) 52| assert(lengths[i] <= shape[i], "Lengths should fit into ndfield's shape."); | } | import mir.ndslice.topology: indexed, ndiota; 24| return indexed(field, ndiota(lengths)); |} | |///ditto |auto slicedNdField(ndField)(ndField field) | if(hasShape!ndField) |{ 24| return .slicedNdField(field, field.shape); |} | |/++ |Combination of coordinate(s) and value. |+/ |struct CoordinateValue(T, size_t N = 1) |{ | /// | size_t[N] index; | | /// | T value; | | /// | int opCmp()(scope auto ref const typeof(this) rht) const | { | return cmpCoo(this.index, rht.index); | } |} | |private int cmpCoo(size_t N)(scope const auto ref size_t[N] a, scope const auto ref size_t[N] b) |{ | foreach (i; Iota!(0, N)) | if (a[i] != b[i]) | return a[i] > b[i] ? 1 : -1; | return 0; |} | |/++ |Presents $(LREF .Slice.structure). |+/ |struct Structure(size_t N) |{ | /// | size_t[N] lengths; | /// | sizediff_t[N] strides; |} | |package(mir) alias LightConstOfLightScopeOf(Iterator) = LightConstOf!(LightScopeOf!Iterator); |package(mir) alias LightImmutableOfLightConstOf(Iterator) = LightImmutableOf!(LightScopeOf!Iterator); |package(mir) alias ImmutableOfUnqualOfPointerTarget(Iterator) = immutable(Unqual!(PointerTarget!Iterator))*; |package(mir) alias ConstOfUnqualOfPointerTarget(Iterator) = const(Unqual!(PointerTarget!Iterator))*; | |package(mir) template allLightScope(args...) |{ | static if (args.length) | { | alias Arg = typeof(args[0]); | static if(!isDynamicArray!Arg) | { | static if(!is(LightScopeOf!Arg == Arg)) | @optmath @property auto allLightScopeMod()() | { | import mir.qualifier: lightScope; | return args[0].lightScope; | } | else alias allLightScopeMod = args[0]; | } | else alias allLightScopeMod = args[0]; | alias allLightScope = AliasSeq!(allLightScopeMod, allLightScope!(args[1..$])); | } | else | alias allLightScope = AliasSeq!(); |} | |/++ |Presents an n-dimensional view over a range. | |$(H3 Definitions) | |In order to change data in a slice using |overloaded operators such as `=`, `+=`, `++`, |a syntactic structure of type |`[]` must be used. |It is worth noting that just like for regular arrays, operations `a = b` |and `a[] = b` have different meanings. |In the first case, after the operation is carried out, `a` simply points at the same data as `b` |does, and the data which `a` previously pointed at remains unmodified. |Here, `а` and `b` must be of the same type. |In the second case, `a` points at the same data as before, |but the data itself will be changed. In this instance, the number of dimensions of `b` |may be less than the number of dimensions of `а`; and `b` can be a Slice, |a regular multidimensional array, or simply a value (e.g. a number). | |In the following table you will find the definitions you might come across |in comments on operator overloading. | |$(BOOKTABLE |$(TR $(TH Operator Overloading) $(TH Examples at `N == 3`)) |$(TR $(TD An $(B interval) is a part of a sequence of type `i .. j`.) | $(STD `2..$-3`, `0..4`)) |$(TR $(TD An $(B index) is a part of a sequence of type `i`.) | $(STD `3`, `$-1`)) |$(TR $(TD A $(B partially defined slice) is a sequence composed of | $(B intervals) and $(B indices) with an overall length strictly less than `N`.) | $(STD `[3]`, `[0..$]`, `[3, 3]`, `[0..$,0..3]`, `[0..$,2]`)) |$(TR $(TD A $(B fully defined index) is a sequence | composed only of $(B indices) with an overall length equal to `N`.) | $(STD `[2,3,1]`)) |$(TR $(TD A $(B fully defined slice) is an empty sequence | or a sequence composed of $(B indices) and at least one | $(B interval) with an overall length equal to `N`.) | $(STD `[]`, `[3..$,0..3,0..$-1]`, `[2,0..$,1]`)) |$(TR $(TD An $(B indexed slice) is syntax sugar for $(SUBREF topology, indexed) and $(SUBREF topology, cartesian).) | $(STD `[anNdslice]`, `[$.iota, anNdsliceForCartesian1, $.iota]`)) |) | |See_also: | $(SUBREF topology, iota). | |$(H3 Internal Binary Representation) | |Multidimensional Slice is a structure that consists of lengths, strides, and a iterator (pointer). | |$(SUBREF topology, FieldIterator) shell is used to wrap fields and random access ranges. |FieldIterator contains a shift of the current initial element of a multidimensional slice |and the field itself. | |With the exception of $(MREF mir,ndslice,allocation) module, no functions in this |package move or copy data. The operations are only carried out on lengths, strides, |and pointers. If a slice is defined over a range, only the shift of the initial element |changes instead of the range. | |Mir n-dimensional Slices can be one of the three kinds. | |$(H4 Contiguous slice) | |Contiguous in memory (or in a user-defined iterator's field) row-major tensor that doesn't store strides because they can be computed on the fly using lengths. |The row stride is always equaled 1. | |$(H4 Canonical slice) | |Canonical slice as contiguous in memory (or in a user-defined iterator's field) rows of a row-major tensor, it doesn't store the stride for row dimension because it is always equaled 1. |BLAS/LAPACK matrices are Canonical but originally have column-major order. |In the same time you can use 2D Canonical Slices with LAPACK assuming that rows are columns and columns are rows. | |$(H4 Universal slice) | |A row-major tensor that stores the strides for all dimensions. |NumPy strides are Universal. | |$(H4 Internal Representation for Universal Slices) | |Type definition | |------- |Slice!(Iterator, N, Universal) |------- | |Schema | |------- |Slice!(Iterator, N, Universal) | size_t[N] _lengths | sizediff_t[N] _strides | Iterator _iterator |------- | |$(H5 Example) | |Definitions | |------- |import mir.ndslice; |auto a = new double[24]; |Slice!(double*, 3, Universal) s = a.sliced(2, 3, 4).universal; |Slice!(double*, 3, Universal) t = s.transposed!(1, 2, 0); |Slice!(double*, 3, Universal) r = t.reversed!1; |------- | |Representation | |------- |s________________________ | lengths[0] ::= 2 | lengths[1] ::= 3 | lengths[2] ::= 4 | | strides[0] ::= 12 | strides[1] ::= 4 | strides[2] ::= 1 | | iterator ::= &a[0] | |t____transposed!(1, 2, 0) | lengths[0] ::= 3 | lengths[1] ::= 4 | lengths[2] ::= 2 | | strides[0] ::= 4 | strides[1] ::= 1 | strides[2] ::= 12 | | iterator ::= &a[0] | |r______________reversed!1 | lengths[0] ::= 2 | lengths[1] ::= 3 | lengths[2] ::= 4 | | strides[0] ::= 12 | strides[1] ::= -4 | strides[2] ::= 1 | | iterator ::= &a[8] // (old_strides[1] * (lengths[1] - 1)) = 8 |------- | |$(H4 Internal Representation for Canonical Slices) | |Type definition | |------- |Slice!(Iterator, N, Canonical) |------- | |Schema | |------- |Slice!(Iterator, N, Canonical) | size_t[N] _lengths | sizediff_t[N-1] _strides | Iterator _iterator |------- | |$(H4 Internal Representation for Contiguous Slices) | |Type definition | |------- |Slice!(Iterator, N) |------- | |Schema | |------- |Slice!(Iterator, N, Contiguous) | size_t[N] _lengths | sizediff_t[0] _strides | Iterator _iterator |------- |+/ 432|struct mir_slice(Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous, Labels_...) | if (0 < N_ && N_ < 255 && !(kind_ == Canonical && N_ == 1) && Labels_.length <= N_ && isIterator!Iterator_) |{ |@optmath: | | /// $(LREF SliceKind) | enum SliceKind kind = kind_; | | /// Dimensions count | enum size_t N = N_; | | /// Strides count | enum size_t S = kind == Universal ? N : kind == Canonical ? N - 1 : 0; | | /// Labels count. | enum size_t L = Labels_.length; | | /// Data iterator type | alias Iterator = Iterator_; | | /// This type | alias This = Slice!(Iterator, N, kind); | | /// Data element type | alias DeepElement = typeof(Iterator.init[size_t.init]); | | /// | alias serdeKeysProxy = Unqual!DeepElement; | | /// Label Iterators types | alias Labels = Labels_; | | /// | template Element(size_t dimension) | if (dimension < N) | { | static if (N == 1) | alias Element = DeepElement; | else | { | static if (kind == Universal || dimension == N - 1) | alias Element = mir_slice!(Iterator, N - 1, Universal); | else | static if (N == 2 || kind == Contiguous && dimension == 0) | alias Element = mir_slice!(Iterator, N - 1); | else | alias Element = mir_slice!(Iterator, N - 1, Canonical); | } | } | |package(mir): | | enum doUnittest = is(Iterator == int*) && (N == 1 || N == 2) && kind == Contiguous; | | enum hasAccessByRef = __traits(compiles, &_iterator[0]); | | enum PureIndexLength(Slices...) = Filter!(isIndex, Slices).length; | | enum isPureSlice(Slices...) = | Slices.length == 0 | || Slices.length <= N | && PureIndexLength!Slices < N | && Filter!(isIndex, Slices).length < Slices.length | && allSatisfy!(templateOr!(isIndex, is_Slice), Slices); | | | enum isFullPureSlice(Slices...) = | Slices.length == 0 | || Slices.length == N | && PureIndexLength!Slices < N | && allSatisfy!(templateOr!(isIndex, is_Slice), Slices); | | enum isIndexedSlice(Slices...) = | Slices.length | && Slices.length <= N | && allSatisfy!(isSlice, Slices) | && anySatisfy!(templateNot!is_Slice, Slices); | | static if (S) | { | /// | public alias _Structure = AliasSeq!(size_t[N], ptrdiff_t[S]); | /// | public _Structure _structure; | /// | public alias _lengths = _structure[0]; | /// | public alias _strides = _structure[1]; | } | else | { | /// | public alias _Structure = AliasSeq!(size_t[N]); | /// | public _Structure _structure; | /// | public alias _lengths = _structure[0]; | /// | public enum ptrdiff_t[S] _strides = ptrdiff_t[S].init; | } | | /// Data Iterator | public Iterator _iterator; | /// Labels iterators | public Labels _labels; | | sizediff_t backIndex(size_t dimension = 0)() @safe @property scope const | if (dimension < N) | { 872| return _stride!dimension * (_lengths[dimension] - 1); | } | | size_t indexStride(size_t I)(size_t[I] _indices) @safe scope const | { | static if (_indices.length) | { | static if (kind == Contiguous) | { | enum E = I - 1; 31271| assert(_indices[E] < _lengths[E], indexError!(E, N)); 31271| ptrdiff_t ball = this._stride!E; 31271| ptrdiff_t stride = _indices[E] * ball; | foreach_reverse (i; Iota!E) //static | { 693| ball *= _lengths[i + 1]; 693| assert(_indices[i] < _lengths[i], indexError!(i, N)); 693| stride += ball * _indices[i]; | } | } | else | static if (kind == Canonical) | { | enum E = I - 1; 10| assert(_indices[E] < _lengths[E], indexError!(E, N)); | static if (I == N) 7| size_t stride = _indices[E]; | else 3| size_t stride = _strides[E] * _indices[E]; | foreach_reverse (i; Iota!E) //static | { 9| assert(_indices[i] < _lengths[i], indexError!(i, N)); 9| stride += _strides[i] * _indices[i]; | } | } | else | { | enum E = I - 1; 9942| assert(_indices[E] < _lengths[E], indexError!(E, N)); 9942| size_t stride = _strides[E] * _indices[E]; | foreach_reverse (i; Iota!E) //static | { 26| assert(_indices[i] < _lengths[i], indexError!(i, N)); 26| stride += _strides[i] * _indices[i]; | } | } 41223| return stride; | } | else | { | return 0; | } | } | |public: | | // static if (S == 0) | // { | /// Defined for Contiguous Slice only | // this()(size_t[N] lengths, in ptrdiff_t[] empty, Iterator iterator, Labels labels) | // { | // version(LDC) pragma(inline, true); | // assert(empty.length == 0); | // this._lengths = lengths; | // this._iterator = iterator; | // } | | // /// ditto | // this()(size_t[N] lengths, Iterator iterator, Labels labels) | // { | // version(LDC) pragma(inline, true); | // this._lengths = lengths; | // this._iterator = iterator; | // } | | // /// ditto | // this()(size_t[N] lengths, in ptrdiff_t[] empty, Iterator iterator, Labels labels) | // { | // version(LDC) pragma(inline, true); | // assert(empty.length == 0); | // this._lengths = lengths; | // this._iterator = iterator; | // } | | // /// ditto | // this()(size_t[N] lengths, Iterator iterator, Labels labels) | // { | // version(LDC) pragma(inline, true); | // this._lengths = lengths; | // this._iterator = iterator; | // } | // } | | // version(LDC) | // private enum classicConstructor = true; | // else | // private enum classicConstructor = S > 0; | | // static if (classicConstructor) | // { | /// Defined for Canonical and Universal Slices (DMD, GDC, LDC) and for Contiguous Slices (LDC) | // this()(size_t[N] lengths, ptrdiff_t[S] strides, Iterator iterator, Labels labels) | // { | // version(LDC) pragma(inline, true); | // this._lengths = lengths; | // this._strides = strides; | // this._iterator = iterator; | // this._labels = labels; | // } | | // /// ditto | // this()(size_t[N] lengths, ptrdiff_t[S] strides, ref Iterator iterator, Labels labels) | // { | // version(LDC) pragma(inline, true); | // this._lengths = lengths; | // this._strides = strides; | // this._iterator = iterator; | // this._labels = labels; | // } | // } | | // /// Construct from null | // this()(typeof(null)) | // { | // version(LDC) pragma(inline, true); | // } | | // static if (doUnittest) | // /// | // @safe pure version(mir_ndslice_test) unittest | // { | // import mir.ndslice.slice; | // alias Array = Slice!(double*); | // Array a = null; | // auto b = Array(null); | // assert(a.empty); | // assert(b.empty); | | // auto fun(Array a = null) | // { | | // } | // } | | static if (doUnittest) | /// Creates a 2-dimentional slice with custom strides. | nothrow pure | version(mir_ndslice_test) unittest | { 2| uint[8] array = [1, 2, 3, 4, 5, 6, 7, 8]; 2| auto slice = Slice!(uint*, 2, Universal)([2, 2], [4, 1], array.ptr); | 2| assert(&slice[0, 0] == &array[0]); 2| assert(&slice[0, 1] == &array[1]); 2| assert(&slice[1, 0] == &array[4]); 2| assert(&slice[1, 1] == &array[5]); 2| assert(slice == [[1, 2], [5, 6]]); | 2| array[2] = 42; 2| assert(slice == [[1, 2], [5, 6]]); | 2| array[1] = 99; 2| assert(slice == [[1, 99], [5, 6]]); | } | | /++ | Returns: View with stripped out reference counted context. | The lifetime of the result mustn't be longer then the lifetime of the original slice. | +/ | auto lightScope()() return scope @property | { 1264| auto ret = Slice!(LightScopeOf!Iterator, N, kind, staticMap!(LightScopeOf, Labels)) | (_structure, .lightScope(_iterator)); | foreach(i; Iota!L) 4| ret._labels[i] = .lightScope(_labels[i]); 1264| return ret; | } | | /// ditto | auto lightScope()() return scope const @property | { 1147| auto ret = Slice!(LightConstOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightConstOfLightScopeOf, Labels)) | (_structure, .lightScope(_iterator)); | foreach(i; Iota!L) | ret._labels[i] = .lightScope(_labels[i]); 1147| return ret; | } | | /// ditto | auto lightScope()() return scope immutable @property | { | auto ret = Slice!(LightImmutableOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightImmutableOfLightConstOf(Labels))) | (_structure, .lightScope(_iterator)); | foreach(i; Iota!L) | ret._labels[i] = .lightScope(_labels[i]); | return ret; | } | | /// Returns: Mutable slice over immutable data. | Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightImmutable()() return scope immutable @property | { 9| auto ret = typeof(return)(_structure, .lightImmutable(_iterator)); | foreach(i; Iota!L) 2| ret._labels[i] = .lightImmutable(_labels[i]); 9| return ret; | } | | /// Returns: Mutable slice over const data. | Slice!(LightConstOf!Iterator, N, kind, staticMap!(LightConstOf, Labels)) lightConst()() return scope const @property @trusted | { 1832| auto ret = typeof(return)(_structure, .lightConst(_iterator)); | foreach(i; Iota!L) 2| ret._labels[i] = .lightConst(_labels[i]); 1832| return ret; | } | | /// ditto | Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightConst()() return scope immutable @property | { | return this.lightImmutable; | } | | /// Label for the dimensions 'd'. By default returns the row label. | Slice!(Labels[d]) | label(size_t d = 0)() @property | if (d <= L) | { 35| return typeof(return)(_lengths[d], _labels[d]); | } | | /// ditto | void label(size_t d = 0)(Slice!(Labels[d]) rhs) @property | if (d <= L) | { | import core.lifetime: move; 1| assert(rhs.length == _lengths[d], "ndslice: labels dimension mismatch"); 1| _labels[d] = rhs._iterator.move; | } | | /// ditto | Slice!(LightConstOf!(Labels[d])) | label(size_t d = 0)() @property const | if (d <= L) | { | return typeof(return)(_lengths[d].lightConst, _labels[d]); | } | | /// ditto | Slice!(LightImmutableOf!(Labels[d])) | label(size_t d = 0)() @property immutable | if (d <= L) | { | return typeof(return)(_lengths[d].lightImmutable, _labels[d]); | } | | /// Strips label off the DataFrame | auto values()() @property | { 2| return Slice!(Iterator, N, kind)(_structure, _iterator); | } | | /// ditto | auto values()() @property const | { | return Slice!(LightConstOf!Iterator, N, kind)(_structure, .lightConst(_iterator)); | } | | /// ditto | auto values()() @property immutable | { 1| return Slice!(LightImmutableOf!Iterator, N, kind)(_structure, .lightImmutable(_iterator)); | } | | /// `opIndex` overload for const slice | auto ref opIndex(Indexes...)(Indexes indices) const @trusted | if (isPureSlice!Indexes || isIndexedSlice!Indexes) | { 6| return lightConst.opIndex(indices); | } | /// `opIndex` overload for immutable slice | auto ref opIndex(Indexes...)(Indexes indices) immutable @trusted | if (isPureSlice!Indexes || isIndexedSlice!Indexes) | { 6| return lightImmutable.opIndex(indices); | } | | static if (allSatisfy!(isPointer, Iterator, Labels)) | { | private alias ConstThis = Slice!(const(Unqual!(PointerTarget!Iterator))*, N, kind); | private alias ImmutableThis = Slice!(immutable(Unqual!(PointerTarget!Iterator))*, N, kind); | | /++ | Cast to const and immutable slices in case of underlying range is a pointer. | +/ | auto toImmutable()() return scope immutable @trusted pure nothrow @nogc | { 1| return Slice!(ImmutableOfUnqualOfPointerTarget!Iterator, N, kind, staticMap!(ImmutableOfUnqualOfPointerTarget, Labels)) | (_structure, _iterator, _labels); | } | | /// ditto | auto toConst()() return scope const @trusted pure nothrow @nogc | { | version(LDC) pragma(inline, true); 441| return Slice!(ConstOfUnqualOfPointerTarget!Iterator, N, kind, staticMap!(ConstOfUnqualOfPointerTarget, Labels)) | (_structure, _iterator, _labels); | } | | static if (!is(Slice!(const(Unqual!(PointerTarget!Iterator))*, N, kind) == This)) | /// ditto | alias toConst this; | | static if (doUnittest) | /// | version(mir_ndslice_test) unittest | { | static struct Foo | { | Slice!(int*) bar; | | int get(size_t i) immutable | { 0000000| return bar[i]; | } | | int get(size_t i) const | { 0000000| return bar[i]; | } | | int get(size_t i) inout | { 0000000| return bar[i]; | } | } | } | | static if (doUnittest) | /// | version(mir_ndslice_test) unittest | { 2| Slice!(double*, 2, Universal) nn; 2| Slice!(immutable(double)*, 2, Universal) ni; 2| Slice!(const(double)*, 2, Universal) nc; | 2| const Slice!(double*, 2, Universal) cn; 2| const Slice!(immutable(double)*, 2, Universal) ci; 2| const Slice!(const(double)*, 2, Universal) cc; | 2| immutable Slice!(double*, 2, Universal) in_; 2| immutable Slice!(immutable(double)*, 2, Universal) ii; 2| immutable Slice!(const(double)*, 2, Universal) ic; | 6| nc = nc; nc = cn; nc = in_; 6| nc = nc; nc = cc; nc = ic; 6| nc = ni; nc = ci; nc = ii; | | void fun(T, size_t N)(Slice!(const(T)*, N, Universal) sl) | { | //... | } | 6| fun(nn); fun(cn); fun(in_); 6| fun(nc); fun(cc); fun(ic); 6| fun(ni); fun(ci); fun(ii); | | static assert(is(typeof(cn[]) == typeof(nc))); | static assert(is(typeof(ci[]) == typeof(ni))); | static assert(is(typeof(cc[]) == typeof(nc))); | | static assert(is(typeof(in_[]) == typeof(ni))); | static assert(is(typeof(ii[]) == typeof(ni))); | static assert(is(typeof(ic[]) == typeof(ni))); | 2| ni = ci[]; 2| ni = in_[]; 2| ni = ii[]; 2| ni = ic[]; | } | } | | /++ | Iterator | Returns: | Iterator (pointer) to the $(LREF .Slice.first) element. | +/ | auto iterator()() inout return scope @property | { 20| return _iterator; | } | | static if (kind == Contiguous && isPointer!Iterator) | /++ | `ptr` alias is available only if the slice kind is $(LREF Contiguous) contiguous and the $(LREF .Slice.iterator) is a pointers. | +/ | alias ptr = iterator; | else | { | import mir.rc.array: mir_rci; | static if (kind == Contiguous && is(Iterator : mir_rci!ET, ET)) | auto ptr() return scope inout @property | { 647| return _iterator._iterator; | } | } | | /++ | Field (array) data. | Returns: | Raw data slice. | Constraints: | Field is defined only for contiguous slices. | +/ | auto field()() return scope @trusted @property | { | static assert(kind == Contiguous, "Slice.field is defined only for contiguous slices. Slice kind is " ~ kind.stringof); | static if (is(typeof(_iterator[size_t(0) .. elementCount]))) | { 935| return _iterator[size_t(0) .. elementCount]; | } | else | { | import mir.ndslice.topology: flattened; | return this.flattened; | } | } | | /// ditto | auto field()() return scope const @trusted @property | { 2| return this.lightConst.field; | } | | /// ditto | auto field()() return immutable scope @trusted @property | { 2| return this.lightImmutable.field; | } | | static if (doUnittest) | /// | @safe version(mir_ndslice_test) unittest | { 2| auto arr = [1, 2, 3, 4]; 2| auto sl0 = arr.sliced; 2| auto sl1 = arr.slicedField; | 2| assert(sl0.field is arr); 2| assert(sl1.field is arr); | 2| arr = arr[1 .. $]; 2| sl0 = sl0[1 .. $]; 2| sl1 = sl1[1 .. $]; | 2| assert(sl0.field is arr); 2| assert(sl1.field is arr); 2| assert((cast(const)sl1).field is arr); 4| ()@trusted{ assert((cast(immutable)sl1).field is arr); }(); | } | | /++ | Returns: static array of lengths | See_also: $(LREF .Slice.structure) | +/ | size_t[N] shape()() @trusted @property scope const | { 3572| return _lengths[0 .. N]; | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| assert(iota(3, 4, 5).shape == cast(size_t[3])[3, 4, 5]); | } | | static if (doUnittest) | /// Packed slice | @safe @nogc pure nothrow | version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : pack, iota; 2| size_t[3] s = [3, 4, 5]; 2| assert(iota(3, 4, 5, 6, 7).pack!2.shape == s); | } | | /++ | Returns: static array of lengths | See_also: $(LREF .Slice.structure) | +/ | ptrdiff_t[N] strides()() @trusted @property scope const | { | static if (N <= S) 439| return _strides[0 .. N]; | else | { 1192| typeof(return) ret; | static if (kind == Canonical) | { | foreach (i; Iota!S) 123| ret[i] = _strides[i]; 110| ret[$-1] = 1; | } | else | { 1082| ret[$ - 1] = _stride!(N - 1); | foreach_reverse (i; Iota!(N - 1)) 471| ret[i] = ret[i + 1] * _lengths[i + 1]; | } 1192| return ret; | } | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow | version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| size_t[3] s = [20, 5, 1]; 2| assert(iota(3, 4, 5).strides == s); | } | | static if (doUnittest) | /// Modified regular slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : pack, iota, universal; | import mir.ndslice.dynamic : reversed, strided, transposed; 2| assert(iota(3, 4, 50) | .universal | .reversed!2 //makes stride negative | .strided!2(6) //multiplies stride by 6 and changes corresponding length | .transposed!2 //brings dimension `2` to the first position | .strides == cast(ptrdiff_t[3])[-6, 200, 50]); | } | | static if (doUnittest) | /// Packed slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : pack, iota; 2| size_t[3] s = [20 * 42, 5 * 42, 1 * 42]; 2| assert(iota(3, 4, 5, 6, 7) | .pack!2 | .strides == s); | } | | /++ | Returns: static array of lengths and static array of strides | See_also: $(LREF .Slice.shape) | +/ | Structure!N structure()() @safe @property scope const | { 6| return typeof(return)(_lengths, strides); | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| assert(iota(3, 4, 5) | .structure == Structure!3([3, 4, 5], [20, 5, 1])); | } | | static if (doUnittest) | /// Modified regular slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : pack, iota, universal; | import mir.ndslice.dynamic : reversed, strided, transposed; 2| assert(iota(3, 4, 50) | .universal | .reversed!2 //makes stride negative | .strided!2(6) //multiplies stride by 6 and changes corresponding length | .transposed!2 //brings dimension `2` to the first position | .structure == Structure!3([9, 3, 4], [-6, 200, 50])); | } | | static if (doUnittest) | /// Packed slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : pack, iota; 2| assert(iota(3, 4, 5, 6, 7) | .pack!2 | .structure == Structure!3([3, 4, 5], [20 * 42, 5 * 42, 1 * 42])); | } | | /++ | Save primitive. | +/ | auto save()() inout @property | { 6| return this; | } | | static if (doUnittest) | /// Save range | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| auto slice = iota(2, 3).save; | } | | static if (doUnittest) | /// Pointer type. | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; | //sl type is `Slice!(2, int*)` 2| auto sl = slice!int(2, 3).save; | } | | /++ | Multidimensional `length` property. | Returns: length of the corresponding dimension | See_also: $(LREF .Slice.shape), $(LREF .Slice.structure) | +/ | size_t length(size_t dimension = 0)() @safe @property scope const | if (dimension < N) | { 19266| return _lengths[dimension]; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| auto slice = iota(3, 4, 5); 2| assert(slice.length == 3); 2| assert(slice.length!0 == 3); 2| assert(slice.length!1 == 4); 2| assert(slice.length!2 == 5); | } | | alias opDollar = length; | | /++ | Multidimensional `stride` property. | Returns: stride of the corresponding dimension | See_also: $(LREF .Slice.structure) | +/ | sizediff_t _stride(size_t dimension = 0)() @safe @property scope const | if (dimension < N) | { | static if (dimension < S) | { 521| return _strides[dimension]; | } | else | static if (dimension + 1 == N) | { 34668| return 1; | } | else | { 2147| size_t ball = _lengths[$ - 1]; | foreach_reverse(i; Iota!(dimension + 1, N - 1)) 50| ball *= _lengths[i]; 2147| return ball; | } | | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| auto slice = iota(3, 4, 5); 2| assert(slice._stride == 20); 2| assert(slice._stride!0 == 20); 2| assert(slice._stride!1 == 5); 2| assert(slice._stride!2 == 1); | } | | static if (doUnittest) | /// Modified regular slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.dynamic : reversed, strided, swapped; | import mir.ndslice.topology : universal, iota; 2| assert(iota(3, 4, 50) | .universal | .reversed!2 //makes stride negative | .strided!2(6) //multiplies stride by 6 and changes the corresponding length | .swapped!(1, 2) //swaps dimensions `1` and `2` | ._stride!1 == -6); | } | | /++ | Multidimensional input range primitive. | +/ | bool empty(size_t dimension = 0)() @safe @property scope const | if (dimension < N) | { 194375| return _lengths[dimension] == 0; | } | | static if (N == 1) | { | ///ditto | auto ref front(size_t dimension = 0)() return scope @trusted @property | if (dimension == 0) | { 106797| assert(!empty!dimension); 106797| return *_iterator; | } | | ///ditto | auto ref front(size_t dimension = 0)() return scope @trusted @property const | if (dimension == 0) | { | assert(!empty!dimension); | return *_iterator.lightScope; | } | | ///ditto | auto ref front(size_t dimension = 0)() return scope @trusted @property immutable | if (dimension == 0) | { | assert(!empty!dimension); | return *_iterator.lightScope; | } | } | else | { | /// ditto | Element!dimension front(size_t dimension = 0)() return scope @property | if (dimension < N) | { 3492| typeof(return)._Structure structure_ = typeof(return)._Structure.init; | | foreach (i; Iota!(typeof(return).N)) | { | enum j = i >= dimension ? i + 1 : i; 3738| structure_[0][i] = _lengths[j]; | } | | static if (!typeof(return).S || typeof(return).S + 1 == S) | alias s = _strides; | else 25| auto s = strides; | | foreach (i; Iota!(typeof(return).S)) | { | enum j = i >= dimension ? i + 1 : i; 982| structure_[1][i] = s[j]; | } | 3492| return typeof(return)(structure_, _iterator); | } | | ///ditto | auto front(size_t dimension = 0)() return scope @trusted @property const | if (dimension < N) | { | assert(!empty!dimension); | return this.lightConst.front!dimension; | } | | ///ditto | auto front(size_t dimension = 0)() return scope @trusted @property immutable | if (dimension < N) | { | assert(!empty!dimension); | return this.lightImmutable.front!dimension; | } | } | | static if (N == 1 && isMutable!DeepElement && !hasAccessByRef) | { | ///ditto | auto ref front(size_t dimension = 0, T)(T value) return scope @trusted @property | if (dimension == 0) | { | // check assign safety | static auto ref fun(ref DeepElement t, ref T v) @safe | { 0000000| return t = v; | } 1193| assert(!empty!dimension); | static if (__traits(compiles, *_iterator = value)) | return *_iterator = value; | else 1199| return _iterator[0] = value; | } | } | | ///ditto | static if (N == 1) | auto ref Element!dimension | back(size_t dimension = 0)() return scope @trusted @property | if (dimension < N) | { 851| assert(!empty!dimension); 851| return _iterator[backIndex]; | } | else | auto ref Element!dimension | back(size_t dimension = 0)() return scope @trusted @property | if (dimension < N) | { 13| assert(!empty!dimension); 13| auto structure_ = typeof(return)._Structure.init; | | foreach (i; Iota!(typeof(return).N)) | { | enum j = i >= dimension ? i + 1 : i; 16| structure_[0][i] = _lengths[j]; | } | | static if (!typeof(return).S || typeof(return).S + 1 == S) | alias s =_strides; | else 5| auto s = strides; | | foreach (i; Iota!(typeof(return).S)) | { | enum j = i >= dimension ? i + 1 : i; 9| structure_[1][i] = s[j]; | } | 13| return typeof(return)(structure_, _iterator + backIndex!dimension); | } | | static if (N == 1 && isMutable!DeepElement && !hasAccessByRef) | { | ///ditto | auto ref back(size_t dimension = 0, T)(T value) return scope @trusted @property | if (dimension == 0) | { | // check assign safety | static auto ref fun(ref DeepElement t, ref T v) @safe | { | return t = v; | } | assert(!empty!dimension); | return _iterator[backIndex] = value; | } | } | | ///ditto | void popFront(size_t dimension = 0)() @trusted scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { 110044| assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0."); 110044| _lengths[dimension]--; | static if ((kind == Contiguous || kind == Canonical) && dimension + 1 == N) 97035| ++_iterator; | else | static if (kind == Canonical || kind == Universal) 11016| _iterator += _strides[dimension]; | else 1993| _iterator += _stride!dimension; | } | | ///ditto | void popBack(size_t dimension = 0)() @safe scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { 88| assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0."); 88| --_lengths[dimension]; | } | | ///ditto | void popFrontExactly(size_t dimension = 0)(size_t n) @trusted scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { 120| assert(n <= _lengths[dimension], | __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof); 120| _lengths[dimension] -= n; 120| _iterator += _stride!dimension * n; | } | | ///ditto | void popBackExactly(size_t dimension = 0)(size_t n) @safe scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { 29| assert(n <= _lengths[dimension], | __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof); 29| _lengths[dimension] -= n; | } | | ///ditto | void popFrontN(size_t dimension = 0)(size_t n) @trusted scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { 4| popFrontExactly!dimension(min(n, _lengths[dimension])); | } | | ///ditto | void popBackN(size_t dimension = 0)(size_t n) @safe scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { | popBackExactly!dimension(min(n, _lengths[dimension])); | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import std.range.primitives; | import mir.ndslice.topology : iota, canonical; 2| auto slice = iota(10, 20, 30).canonical; | | static assert(isRandomAccessRange!(typeof(slice))); | static assert(hasSlicing!(typeof(slice))); | static assert(hasLength!(typeof(slice))); | 2| assert(slice.shape == cast(size_t[3])[10, 20, 30]); 2| slice.popFront; 2| slice.popFront!1; 2| slice.popBackExactly!2(4); 2| assert(slice.shape == cast(size_t[3])[9, 19, 26]); | 2| auto matrix = slice.front!1; 2| assert(matrix.shape == cast(size_t[2])[9, 26]); | 2| auto column = matrix.back!1; 2| assert(column.shape == cast(size_t[1])[9]); | 2| slice.popFrontExactly!1(slice.length!1); 2| assert(slice.empty == false); 2| assert(slice.empty!1 == true); 2| assert(slice.empty!2 == false); 2| assert(slice.shape == cast(size_t[3])[9, 0, 26]); | 2| assert(slice.back.front!1.empty); | 2| slice.popFrontN!0(40); 2| slice.popFrontN!2(40); 2| assert(slice.shape == cast(size_t[3])[0, 0, 0]); | } | | package(mir) ptrdiff_t lastIndex()() @safe @property scope const | { | static if (kind == Contiguous) | { 383| return elementCount - 1; | } | else | { 4| auto strides = strides; 4| ptrdiff_t shift = 0; | foreach(i; Iota!N) 8| shift += strides[i] * (_lengths[i] - 1); 4| return shift; | } | } | | static if (N > 1) | { | /// Accesses the first deep element of the slice. | auto ref first()() return scope @trusted @property | { 6| assert(!anyEmpty); 6| return *_iterator; | } | | static if (isMutable!DeepElement && !hasAccessByRef) | ///ditto | auto ref first(T)(T value) return scope @trusted @property | { | assert(!anyEmpty); | static if (__traits(compiles, *_iterator = value)) | return *_iterator = value; | else | return _iterator[0] = value; | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_ndslice_test) unittest | { | import mir.ndslice.topology: iota, universal, canonical; 1| auto f = 5; 1| assert([2, 3].iota(f).first == f); | } | | /// Accesses the last deep element of the slice. | auto ref last()() @trusted return scope @property | { 1| assert(!anyEmpty); 1| return _iterator[lastIndex]; | } | | static if (isMutable!DeepElement && !hasAccessByRef) | ///ditto | auto ref last(T)(T value) @trusted return scope @property | { | assert(!anyEmpty); | return _iterator[lastIndex] = value; | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_ndslice_test) unittest | { | import mir.ndslice.topology: iota; 1| auto f = 5; 1| assert([2, 3].iota(f).last == f + 2 * 3 - 1); | } | | static if (kind_ != SliceKind.contiguous) | /// Peforms `popFrontAll` for all dimensions | void popFrontAll() | { 2| assert(!anyEmpty); | foreach(d; Iota!N_) 4| popFront!d; | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology: iota, canonical; 1| auto v = [2, 3].iota.canonical; 1| v.popFrontAll; 1| assert(v == [[4, 5]]); | } | | static if (kind_ != SliceKind.contiguous) | /// Peforms `popBackAll` for all dimensions | void popBackAll() | { 2| assert(!anyEmpty); | foreach(d; Iota!N_) 4| popBack!d; | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology: iota, canonical; 1| auto v = [2, 3].iota.canonical; 1| v.popBackAll; 1| assert(v == [[0, 1]]); | } | } | else | { | alias first = front; | alias last = back; | alias popFrontAll = popFront; | alias popBackAll = popBack; | } | | /+ | Returns: `true` if for any dimension of completely unpacked slice the length equals to `0`, and `false` otherwise. | +/ | private bool anyRUEmpty()() @trusted scope const | { | static if (isInstanceOf!(SliceIterator, Iterator)) | { | import mir.ndslice.topology: unpack; 22| return this.lightScope.unpack.anyRUEmpty; | } | else 371| return _lengths[0 .. N].anyEmptyShape; | } | | | /++ | Returns: `true` if for any dimension the length equals to `0`, and `false` otherwise. | +/ | bool anyEmpty()() @trusted @property scope const | { 2100| return _lengths[0 .. N].anyEmptyShape; | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota, canonical; 2| auto s = iota(2, 3).canonical; 2| assert(!s.anyEmpty); 2| s.popFrontExactly!1(3); 2| assert(s.anyEmpty); | } | | /++ | Convenience function for backward indexing. | | Returns: `this[$-index[0], $-index[1], ..., $-index[N-1]]` | +/ | auto ref backward()(size_t[N] index) return scope | { | foreach (i; Iota!N) 24| index[i] = _lengths[i] - index[i]; 12| return this[index]; | } | | /// ditto | auto ref backward()(size_t[N] index) return scope const | { | return this.lightConst.backward(index); | } | | /// ditto | auto ref backward()(size_t[N] index) return scope const | { | return this.lightConst.backward(index); | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| auto s = iota(2, 3); 2| assert(s[$ - 1, $ - 2] == s.backward([1, 2])); | } | | /++ | Returns: Total number of elements in a slice | +/ | size_t elementCount()() @safe @property scope const | { 5552| size_t len = 1; | foreach (i; Iota!N) 11900| len *= _lengths[i]; 5552| return len; | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| assert(iota(3, 4, 5).elementCount == 60); | } | | | static if (doUnittest) | /// Packed slice | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : pack, evertPack, iota; 2| auto slice = iota(3, 4, 5, 6, 7, 8); 2| auto p = slice.pack!2; 2| assert(p.elementCount == 360); 2| assert(p[0, 0, 0, 0].elementCount == 56); 2| assert(p.evertPack.elementCount == 56); | } | | /++ | Slice selected dimension. | Params: | begin = initial index of the sub-slice (inclusive) | end = final index of the sub-slice (noninclusive) | Returns: ndslice with `length!dimension` equal to `end - begin`. | +/ | auto select(size_t dimension)(size_t begin, size_t end) @trusted | { | static if (kind == Contiguous && dimension) | { | import mir.ndslice.topology: canonical; 2| auto ret = this.canonical; | } | else | { 25| auto ret = this; | } 27| auto len = end - begin; 27| assert(len <= ret._lengths[dimension]); 27| ret._lengths[dimension] = len; 27| ret._iterator += ret._stride!dimension * begin; 27| return ret; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| auto sl = iota(3, 4); 2| assert(sl.select!1(1, 3) == sl[0 .. $, 1 .. 3]); | } | | /++ | Select the first n elements for the dimension. | Params: | dimension = Dimension to slice. | n = count of elements for the dimension | Returns: ndslice with `length!dimension` equal to `n`. | +/ | auto selectFront(size_t dimension)(size_t n) return scope | { | static if (kind == Contiguous && dimension) | { | import mir.ndslice.topology: canonical; 2| auto ret = this.canonical; | } | else | { 154| auto ret = this; | } 156| assert(n <= ret._lengths[dimension]); 156| ret._lengths[dimension] = n; 156| return ret; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| auto sl = iota(3, 4); 2| assert(sl.selectFront!1(2) == sl[0 .. $, 0 .. 2]); | } | | /++ | Select the last n elements for the dimension. | Params: | dimension = Dimension to slice. | n = count of elements for the dimension | Returns: ndslice with `length!dimension` equal to `n`. | +/ | auto selectBack(size_t dimension)(size_t n) return scope | { | static if (kind == Contiguous && dimension) | { | import mir.ndslice.topology: canonical; 2| auto ret = this.canonical; | } | else | { 49| auto ret = this; | } 51| assert(n <= ret._lengths[dimension]); 51| ret._iterator += ret._stride!dimension * (ret._lengths[dimension] - n); 51| ret._lengths[dimension] = n; 51| return ret; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : iota; 2| auto sl = iota(3, 4); 2| assert(sl.selectBack!1(2) == sl[0 .. $, $ - 2 .. $]); | } | | ///ditto | bool opEquals(IteratorR, SliceKind rkind)(auto ref const Slice!(IteratorR, N, rkind) rslice) @trusted scope const | { | static if ( | __traits(isPOD, Iterator) | && __traits(isPOD, IteratorR) | && __traits(compiles, this._iterator == rslice._iterator) | ) | { 202| if (this._lengths != rslice._lengths) 2| return false; 200| if (anyEmpty) 0000000| return true; 200| if (this._iterator == rslice._iterator) | { 184| auto ls = this.strides; 184| auto rs = rslice.strides; | foreach (i; Iota!N) | { 669| if (this._lengths[i] != 1 && ls[i] != rs[i]) 0000000| goto L; | } 184| return true; | } | } | L: | | static if (is(Iterator == IotaIterator!UL, UL) && is(IteratorR == Iterator)) | { 0000000| return false; | } | else | { | import mir.algorithm.iteration : equal; 175| return equal(this.lightScope, rslice.lightScope); | } | } | | /// ditto | bool opEquals(T)(scope const(T)[] arr) @trusted scope const | { 1744| auto slice = this.lightConst; 1726| if (slice.length != arr.length) 2| return false; 1724| if (arr.length) do | { 5567| if (slice.front != arr[0]) 6| return false; 5544| slice.popFront; 5544| arr = arr[1 .. $]; | } 5544| while (arr.length); 1718| return true; | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_ndslice_test) unittest | { 2| auto a = [1, 2, 3, 4].sliced(2, 2); | 2| assert(a != [1, 2, 3, 4, 5, 6].sliced(2, 3)); 2| assert(a != [[1, 2, 3], [4, 5, 6]]); | 2| assert(a == [1, 2, 3, 4].sliced(2, 2)); 2| assert(a == [[1, 2], [3, 4]]); | 2| assert(a != [9, 2, 3, 4].sliced(2, 2)); 2| assert(a != [[9, 2], [3, 4]]); | } | | static if (doUnittest) | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation: slice; | import mir.ndslice.topology : iota; 2| assert(iota(2, 3).slice[0 .. $ - 2] == iota([4, 3], 2)[0 .. $ - 4]); | } | | /++ | `Slice!(IotaIterator!size_t)` is the basic type for `[a .. b]` syntax for all ndslice based code. | +/ | Slice!(IotaIterator!size_t) opSlice(size_t dimension)(size_t i, size_t j) @safe scope const | if (dimension < N) | in | { 1889| assert(i <= j, | "Slice.opSlice!" ~ dimension.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); | enum errorMsg = ": right opSlice boundary must be less than or equal to the length of the given dimension."; 1889| assert(j <= _lengths[dimension], | "Slice.opSlice!" ~ dimension.stringof ~ errorMsg); | } | do | { 1889| return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /++ | $(BOLD Fully defined index) | +/ | auto ref opIndex()(size_t[N] _indices...) return scope @trusted | { 30810| return _iterator[indexStride(_indices)]; | } | | /// ditto | auto ref opIndex()(size_t[N] _indices...) return scope const @trusted | { | static if (is(typeof(_iterator[indexStride(_indices)]))) 6744| return _iterator[indexStride(_indices)]; | else | return .lightConst(.lightScope(_iterator))[indexStride(_indices)]; | } | | /// ditto | auto ref opIndex()(size_t[N] _indices...) return scope immutable @trusted | { | static if (is(typeof(_iterator[indexStride(_indices)]))) 0000000| return _iterator[indexStride(_indices)]; | else | return .lightImmutable(.lightScope(_iterator))[indexStride(_indices)]; | } | | /++ | $(BOLD Partially defined index) | +/ | auto opIndex(size_t I)(size_t[I] _indices...) return scope @trusted | if (I && I < N) | { | enum size_t diff = N - I; | alias Ret = Slice!(Iterator, diff, diff == 1 && kind == Canonical ? Contiguous : kind); | static if (I < S) 7| return Ret(_lengths[I .. N], _strides[I .. S], _iterator + indexStride(_indices)); | else 84| return Ret(_lengths[I .. N], _iterator + indexStride(_indices)); | } | | /// ditto | auto opIndex(size_t I)(size_t[I] _indices...) return scope const | if (I && I < N) | { | return this.lightConst.opIndex(_indices); | } | | /// ditto | auto opIndex(size_t I)(size_t[I] _indices...) return scope immutable | if (I && I < N) | { | return this.lightImmutable.opIndex(_indices); | } | | /++ | $(BOLD Partially or fully defined slice.) | +/ | auto opIndex(Slices...)(Slices slices) return scope @trusted | if (isPureSlice!Slices) | { | static if (Slices.length) | { | enum size_t j(size_t n) = n - Filter!(isIndex, Slices[0 .. n]).length; | enum size_t F = PureIndexLength!Slices; | enum size_t S = Slices.length; | static assert(N - F > 0); 1902| size_t stride; | static if (Slices.length == 1) | enum K = kind; | else | static if (kind == Universal || Slices.length == N && isIndex!(Slices[$-1])) | enum K = Universal; | else | static if (Filter!(isIndex, Slices[0 .. $-1]).length == Slices.length - 1 || N - F == 1) | enum K = Contiguous; | else | enum K = Canonical; | alias Ret = Slice!(Iterator, N - F, K); 1902| auto structure_ = Ret._Structure.init; | | enum bool shrink = kind == Canonical && slices.length == N; | static if (shrink) | { | { | enum i = Slices.length - 1; 1| auto slice = slices[i]; | static if (isIndex!(Slices[i])) | { | assert(slice < _lengths[i], "Slice.opIndex: index must be less than length"); | stride += slice; | } | else | { 1| stride += slice._iterator._index; 1| structure_[0][j!i] = slice._lengths[0]; | } | } | } | static if (kind == Universal || kind == Canonical) | { 6| foreach_reverse (i, slice; slices[0 .. $ - shrink]) //static | { | static if (isIndex!(Slices[i])) | { 1| assert(slice < _lengths[i], "Slice.opIndex: index must be less than length"); 1| stride += _strides[i] * slice; | } | else | { 5| stride += _strides[i] * slice._iterator._index; 5| structure_[0][j!i] = slice._lengths[0]; 5| structure_[1][j!i] = _strides[i]; | } | } | } | else | { 1898| ptrdiff_t ball = this._stride!(slices.length - 1); 2028| foreach_reverse (i, slice; slices) //static | { | static if (isIndex!(Slices[i])) | { 51| assert(slice < _lengths[i], "Slice.opIndex: index must be less than length"); 51| stride += ball * slice; | } | else | { 1977| stride += ball * slice._iterator._index; 1977| structure_[0][j!i] = slice._lengths[0]; | static if (j!i < Ret.S) 88| structure_[1][j!i] = ball; | } | static if (i) 130| ball *= _lengths[i]; | } | } | foreach (i; Iota!(Slices.length, N)) 42| structure_[0][i - F] = _lengths[i]; | foreach (i; Iota!(Slices.length, N)) | static if (Ret.S > i - F) 1| structure_[1][i - F] = _strides[i]; | 1902| return Ret(structure_, _iterator + stride); | } | else | { 1320| return this; | } | } | | static if (doUnittest) | /// | pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto slice = slice!int(5, 3); | | /// Fully defined slice 2| assert(slice[] == slice); 2| auto sublice = slice[0..$-2, 1..$]; | | /// Partially defined slice 2| auto row = slice[3]; 2| auto col = slice[0..$, 1]; | } | | /++ | $(BOLD Indexed slice.) | +/ | auto opIndex(Slices...)(return scope Slices slices) return scope | if (isIndexedSlice!Slices) | { | import mir.ndslice.topology: indexed, cartesian; | static if (Slices.length == 1) | alias index = slices[0]; | else 2| auto index = slices.cartesian; 11| return this.indexed(index); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation: slice; 2| auto sli = slice!int(4, 3); 2| auto idx = slice!(size_t[2])(3); 2| idx[] = [ | cast(size_t[2])[0, 2], | cast(size_t[2])[3, 1], | cast(size_t[2])[2, 0]]; | | // equivalent to: | // import mir.ndslice.topology: indexed; | // sli.indexed(indx)[] = 1; 2| sli[idx] = 1; | 2| assert(sli == [ | [0, 0, 1], | [0, 0, 0], | [1, 0, 0], | [0, 1, 0], | ]); | 16| foreach (row; sli[[1, 3].sliced]) 4| row[] += 2; | 2| assert(sli == [ | [0, 0, 1], | [2, 2, 2], // <-- += 2 | [1, 0, 0], | [2, 3, 2], // <-- += 2 | ]); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology: iota; | import mir.ndslice.allocation: slice; 2| auto sli = slice!int(5, 6); | | // equivalent to | // import mir.ndslice.topology: indexed, cartesian; | // auto a = [0, sli.length!0 / 2, sli.length!0 - 1].sliced; | // auto b = [0, sli.length!1 / 2, sli.length!1 - 1].sliced; | // auto c = cartesian(a, b); | // auto minor = sli.indexed(c); 2| auto minor = sli[[0, $ / 2, $ - 1].sliced, [0, $ / 2, $ - 1].sliced]; | 2| minor[] = iota!int([3, 3], 1); | 2| assert(sli == [ | // ↓ ↓ ↓︎ | [1, 0, 0, 2, 0, 3], // <--- | [0, 0, 0, 0, 0, 0], | [4, 0, 0, 5, 0, 6], // <--- | [0, 0, 0, 0, 0, 0], | [7, 0, 0, 8, 0, 9], // <--- | ]); | } | | /++ | Element-wise binary operator overloading. | Returns: | lazy slice of the same kind and the same structure | Note: | Does not allocate neither new slice nor a closure. | +/ | auto opUnary(string op)() | if (op == "*" || op == "~" || op == "-" || op == "+") | { | import mir.ndslice.topology: map; | static if (op == "+") | return this; | else 5| return this.map!(op ~ "a"); | } | | static if (doUnittest) | /// | version(mir_ndslice_test) unittest | { | import mir.ndslice.topology; | 2| auto payload = [1, 2, 3, 4]; 2| auto s = iota([payload.length], payload.ptr); // slice of references; 2| assert(s[1] == payload.ptr + 1); | 2| auto c = *s; // the same as s.map!"*a" 2| assert(c[1] == *s[1]); | 2| *s[1] = 3; 2| assert(c[1] == *s[1]); | } | | /++ | Element-wise operator overloading for scalars. | Params: | value = a scalar | Returns: | lazy slice of the same kind and the same structure | Note: | Does not allocate neither new slice nor a closure. | +/ | auto opBinary(string op, T)(scope return T value) return scope | if(!isSlice!T) | { | import mir.ndslice.topology: vmap; 41| return this.vmap(LeftOp!(op, ImplicitlyUnqual!T)(value)); | } | | /// ditto | auto opBinaryRight(string op, T)(scope return T value) return scope | if(!isSlice!T) | { | import mir.ndslice.topology: vmap; 45| return this.vmap(RightOp!(op, ImplicitlyUnqual!T)(value)); | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_ndslice_test) unittest | { | import mir.ndslice.topology; | | // 0 1 2 3 2| auto s = iota([4]); | // 0 1 2 0 2| assert(s % 3 == iota([4]).map!"a % 3"); | // 0 2 4 6 2| assert(2 * s == iota([4], 0, 2)); | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_ndslice_test) unittest | { | import mir.ndslice.topology; | | // 0 1 2 3 2| auto s = iota([4]); | // 0 1 4 9 2| assert(s ^^ 2.0 == iota([4]).map!"a ^^ 2.0"); | } | | /++ | Element-wise operator overloading for slices. | Params: | rhs = a slice of the same shape. | Returns: | lazy slice the same shape that has $(LREF Contiguous) kind | Note: | Binary operator overloading is allowed if both slices are contiguous or one-dimensional. | $(BR) | Does not allocate neither new slice nor a closure. | +/ | auto opBinary(string op, RIterator, size_t RN, SliceKind rkind) | (scope return Slice!(RIterator, RN, rkind) rhs) return scope | if(N == RN && (kind == Contiguous && rkind == Contiguous || N == 1) && op != "~") | { | import mir.ndslice.topology: zip, map; 271| return zip(this, rhs).map!("a " ~ op ~ " b"); | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_ndslice_test) unittest | { | import mir.ndslice.topology: iota, map, zip; | 2| auto s = iota([2, 3]); 2| auto c = iota([2, 3], 5, 8); 2| assert(s * s + c == s.map!"a * a".zip(c).map!"a + b"); | } | | /++ | Duplicates slice. | Returns: GC-allocated Contiguous mutable slice. | See_also: $(LREF .Slice.idup) | +/ | Slice!(Unqual!DeepElement*, N) | dup()() scope @property @trusted | { 188| if (__ctfe) | { | import mir.ndslice.topology: flattened; | import mir.array.allocation: array; | return this.flattened.array.dup.sliced(this.shape); | } | else | { | import mir.ndslice.allocation: uninitSlice; | import mir.conv: emplaceRef; | alias E = this.DeepElement; | 376| auto result = (() @trusted => this.shape.uninitSlice!(Unqual!E))(); | | import mir.algorithm.iteration: each; 188| each!(emplaceRef!(Unqual!E))(result, this); | 188| return result; | } | } | | /// ditto | Slice!(immutable(DeepElement)*, N) | dup()() scope const @property | { | this.lightScope.dup; | } | | /// ditto | Slice!(immutable(DeepElement)*, N) | dup()() scope immutable @property | { | this.lightScope.dup; | } | | static if (doUnittest) | /// | @safe pure version(mir_ndslice_test) unittest | { | import mir.ndslice; 2| auto x = 3.iota!int; 2| Slice!(immutable(int)*) imm = x.idup; 2| Slice!(int*) mut = imm.dup; 2| assert(imm == x); 2| assert(mut == x); | } | | /++ | Duplicates slice. | Returns: GC-allocated Contiguous immutable slice. | See_also: $(LREF .Slice.dup) | +/ | Slice!(immutable(DeepElement)*, N) | idup()() scope @property | { 4| if (__ctfe) | { | import mir.ndslice.topology: flattened; | import mir.array.allocation: array; | return this.flattened.array.idup.sliced(this.shape); | } | else | { | import mir.ndslice.allocation: uninitSlice; | import mir.conv: emplaceRef; | alias E = this.DeepElement; | 8| auto result = (() @trusted => this.shape.uninitSlice!(Unqual!E))(); | | import mir.algorithm.iteration: each; 4| each!(emplaceRef!(immutable E))(result, this); | alias R = typeof(return); 8| return (() @trusted => cast(R) result)(); | } | } | | /// ditto | Slice!(immutable(DeepElement)*, N) | idup()() scope const @property | { | this.lightScope.idup; | } | | /// ditto | Slice!(immutable(DeepElement)*, N) | idup()() scope immutable @property | { | this.lightScope.idup; | } | | static if (doUnittest) | /// | @safe pure version(mir_ndslice_test) unittest | { | import mir.ndslice; 2| auto x = 3.iota!int; 2| Slice!(int*) mut = x.dup; 2| Slice!(immutable(int)*) imm = mut.idup; 2| assert(imm == x); 2| assert(mut == x); | } | | /++ | Provides the index location of a slice, taking into account | `Slice._strides`. Similar to `Slice.indexStride`, except the slice is | indexed by a value. Called by `Slice.accessFlat`. | | Params: | n = location in slice | Returns: | location in slice, adjusted for `Slice._strides` | See_also: | $(SUBREF topology, flattened), | $(LREF .Slice.indexStride), | $(LREF .Slice.accessFlat) | +/ | private | ptrdiff_t indexStrideValue(ptrdiff_t n) @safe scope const | { 1459| assert(n < elementCount, "indexStrideValue: n must be less than elementCount"); 1459| assert(n >= 0, "indexStrideValue: n must be greater than or equal to zero"); | | static if (kind == Contiguous) { 1390| return n; | } else { 69| ptrdiff_t _shift; | foreach_reverse (i; Iota!(1, N)) | { 105| immutable v = n / ptrdiff_t(_lengths[i]); 105| n %= ptrdiff_t(_lengths[i]); | static if (i == S) 27| _shift += n; | else 78| _shift += n * _strides[i]; 105| n = v; | } 69| _shift += n * _strides[0]; 69| return _shift; | } | } | | /++ | Provides access to a slice as if it were `flattened`. | | Params: | index = location in slice | Returns: | value of flattened slice at `index` | See_also: $(SUBREF topology, flattened) | +/ | auto ref accessFlat(size_t index) return scope @trusted | { 1459| return _iterator[indexStrideValue(index)]; | } | | /// | version(mir_ndslice_test) | @safe pure @nogc nothrow | unittest | { | import mir.ndslice.topology: iota, flattened; | 1362| auto x = iota(2, 3, 4); 1362| assert(x.accessFlat(9) == x.flattened[9]); | } | | static if (isMutable!DeepElement) | { | private void opIndexOpAssignImplSlice(string op, RIterator, size_t RN, SliceKind rkind) | (Slice!(RIterator, RN, rkind) value) scope | { | static if (N > 1 && RN == N && kind == Contiguous && rkind == Contiguous) | { | import mir.ndslice.topology : flattened; 15| this.flattened.opIndexOpAssignImplSlice!op(value.flattened); | } | else | { 469| auto ls = this; | do | { | static if (N > RN) | { 98| ls.front.opIndexOpAssignImplSlice!op(value); | } | else | { | static if (ls.N == 1) | { | static if (isInstanceOf!(SliceIterator, Iterator)) | { | static if (isSlice!(typeof(value.front))) 16| ls.front.opIndexOpAssignImplSlice!op(value.front); | else | static if (isDynamicArray!(typeof(value.front))) | ls.front.opIndexOpAssignImplSlice!op(value.front); | else 16| ls.front.opIndexOpAssignImplValue!op(value.front); | } | else | static if (op == "^^" && isFloatingPoint!(typeof(ls.front)) && isFloatingPoint!(typeof(value.front))) | { | import mir.math.common: pow; | ls.front = pow(ls.front, value.front); | } | else | mixin("ls.front " ~ op ~ "= value.front;"); | } | else | static if (RN == 1) | ls.front.opIndexOpAssignImplValue!op(value.front); | else 109| ls.front.opIndexOpAssignImplSlice!op(value.front); 1484| value.popFront; | } 1582| ls.popFront; | } 1582| while (ls._lengths[0]); | } | } | | /++ | Assignment of a value of `Slice` type to a $(B fully defined slice). | +/ | void opIndexAssign(RIterator, size_t RN, SliceKind rkind, Slices...) | (Slice!(RIterator, RN, rkind) value, Slices slices) return scope | if (isFullPureSlice!Slices || isIndexedSlice!Slices) | { 220| auto sl = this.lightScope.opIndex(slices); 220| assert(_checkAssignLengths(sl, value)); 220| if(!sl.anyRUEmpty) 220| sl.opIndexOpAssignImplSlice!""(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); 2| auto b = [1, 2, 3, 4].sliced(2, 2); | 2| a[0..$, 0..$-1] = b; 2| assert(a == [[1, 2, 0], [3, 4, 0]]); | | // fills both rows with b[0] 2| a[0..$, 0..$-1] = b[0]; 2| assert(a == [[1, 2, 0], [1, 2, 0]]); | 2| a[1, 0..$-1] = b[1]; 2| assert(a[1] == [3, 4, 0]); | 2| a[1, 0..$-1][] = b[0]; 2| assert(a[1] == [1, 2, 0]); | } | | static if (doUnittest) | /// Left slice is packed | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : blocks, iota; | import mir.ndslice.allocation : slice; 2| auto a = slice!int(4, 4); 2| a.blocks(2, 2)[] = iota!int(2, 2); | 2| assert(a == | [[0, 0, 1, 1], | [0, 0, 1, 1], | [2, 2, 3, 3], | [2, 2, 3, 3]]); | } | | static if (doUnittest) | /// Both slices are packed | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.topology : blocks, iota, pack; | import mir.ndslice.allocation : slice; 2| auto a = slice!int(4, 4); 2| a.blocks(2, 2)[] = iota!int(2, 2, 2).pack!1; | 2| assert(a == | [[0, 1, 2, 3], | [0, 1, 2, 3], | [4, 5, 6, 7], | [4, 5, 6, 7]]); | } | | void opIndexOpAssignImplArray(string op, T, Slices...)(T[] value) scope | { 108| auto ls = this; 108| assert(ls.length == value.length, __FUNCTION__ ~ ": argument must have the same length."); | static if (N == 1) | { | do | { | static if (ls.N == 1) | { | static if (isInstanceOf!(SliceIterator, Iterator)) | { | static if (isSlice!(typeof(value[0]))) | ls.front.opIndexOpAssignImplSlice!op(value[0]); | else | static if (isDynamicArray!(typeof(value[0]))) | ls.front.opIndexOpAssignImplSlice!op(value[0]); | else 16| ls.front.opIndexOpAssignImplValue!op(value[0]); | } | else | static if (op == "^^" && isFloatingPoint!(typeof(ls.front)) && isFloatingPoint!(typeof(value[0]))) | { | import mir.math.common: pow; | ls.front = pow(ls.front, value[0]); | } | else | mixin("ls.front " ~ op ~ "= value[0];"); | } | else | mixin("ls.front[] " ~ op ~ "= value[0];"); 272| value = value[1 .. $]; 272| ls.popFront; | } 272| while (ls.length); | } | else | static if (N == DynamicArrayDimensionsCount!(T[])) | { | do | { 28| ls.front.opIndexOpAssignImplArray!op(value[0]); 28| value = value[1 .. $]; 28| ls.popFront; | } 28| while (ls.length); | } | else | { | do | { 12| ls.front.opIndexOpAssignImplArray!op(value); 12| ls.popFront; | } 12| while (ls.length); | } | } | | /++ | Assignment of a regular multidimensional array to a $(B fully defined slice). | +/ | void opIndexAssign(T, Slices...)(T[] value, Slices slices) return scope | if ((isFullPureSlice!Slices || isIndexedSlice!Slices) | && (!isDynamicArray!DeepElement || isDynamicArray!T) | && DynamicArrayDimensionsCount!(T[]) - DynamicArrayDimensionsCount!DeepElement <= typeof(this.opIndex(slices)).N) | { 54| auto sl = this.lightScope.opIndex(slices); 54| sl.opIndexOpAssignImplArray!""(value); | } | | static if (doUnittest) | /// | pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); 2| auto b = [[1, 2], [3, 4]]; | 2| a[] = [[1, 2, 3], [4, 5, 6]]; 2| assert(a == [[1, 2, 3], [4, 5, 6]]); | 2| a[0..$, 0..$-1] = [[1, 2], [3, 4]]; 2| assert(a == [[1, 2, 3], [3, 4, 6]]); | 2| a[0..$, 0..$-1] = [1, 2]; 2| assert(a == [[1, 2, 3], [1, 2, 6]]); | 2| a[1, 0..$-1] = [3, 4]; 2| assert(a[1] == [3, 4, 6]); | 2| a[1, 0..$-1][] = [3, 4]; 2| assert(a[1] == [3, 4, 6]); | } | | static if (doUnittest) | /// Packed slices | pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : blocks; 2| auto a = slice!int(4, 4); 2| a.blocks(2, 2)[] = [[0, 1], [2, 3]]; | 2| assert(a == | [[0, 0, 1, 1], | [0, 0, 1, 1], | [2, 2, 3, 3], | [2, 2, 3, 3]]); | } | | | private void opIndexOpAssignImplConcatenation(string op, T)(T value) scope | { 77| auto sl = this; | static if (concatenationDimension!T) | { 13| if (!sl.empty) do | { | static if (op == "") 50| sl.front.opIndexAssign(value.front); | else | sl.front.opIndexOpAssign!op(value.front); 50| value.popFront; 50| sl.popFront; | } 50| while(!sl.empty); | } | else | { 154| foreach (ref slice; value._slices) | { | static if (op == "") 152| sl[0 .. slice.length].opIndexAssign(slice); | else 2| sl[0 .. slice.length].opIndexOpAssign!op(slice); | 154| sl = sl[slice.length .. $]; | } 64| assert(sl.empty); | } | } | | /// | void opIndexAssign(T, Slices...)(T concatenation, Slices slices) return scope | if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && isConcatenation!T) | { 76| auto sl = this.lightScope.opIndex(slices); | static assert(typeof(sl).N == T.N, "incompatible dimension count"); 76| sl.opIndexOpAssignImplConcatenation!""(concatenation); | } | | static if (!isNumeric!DeepElement) | /++ | Assignment of a value (e.g. a number) to a $(B fully defined slice). | +/ | void opIndexAssign(T, Slices...)(T value, Slices slices) scope | if ((isFullPureSlice!Slices || isIndexedSlice!Slices) | && (!isDynamicArray!T || isDynamicArray!DeepElement) | && DynamicArrayDimensionsCount!T == DynamicArrayDimensionsCount!DeepElement | && !isSlice!T | && !isConcatenation!T) | { 11| auto sl = this.lightScope.opIndex(slices); 11| if(!sl.anyRUEmpty) 11| sl.opIndexOpAssignImplValue!""(value); | } | else | void opIndexAssign(Slices...)(DeepElement value, Slices slices) scope | if (isFullPureSlice!Slices || isIndexedSlice!Slices) | { 43| auto sl = this.lightScope.opIndex(slices); 43| if(!sl.anyRUEmpty) 43| sl.opIndexOpAssignImplValue!""(value); | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); | 2| a[] = 9; 2| assert(a == [[9, 9, 9], [9, 9, 9]]); | 2| a[0..$, 0..$-1] = 1; 2| assert(a == [[1, 1, 9], [1, 1, 9]]); | 2| a[0..$, 0..$-1] = 2; 2| assert(a == [[2, 2, 9], [2, 2, 9]]); | 2| a[1, 0..$-1] = 3; | //assert(a[1] == [3, 3, 9]); | 2| a[1, 0..$-1] = 4; | //assert(a[1] == [4, 4, 9]); | 2| a[1, 0..$-1][] = 5; | 2| assert(a[1] == [5, 5, 9]); | } | | static if (doUnittest) | /// Packed slices have the same behavior. | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; | import mir.ndslice.topology : pack; 2| auto a = slice!int(2, 3).pack!1; | 2| a[] = 9; | //assert(a == [[9, 9, 9], [9, 9, 9]]); | } | | /++ | Assignment of a value (e.g. a number) to a $(B fully defined index). | +/ | auto ref opIndexAssign(T)(T value, size_t[N] _indices...) return scope @trusted | { | // check assign safety | static auto ref fun(ref DeepElement t, ref T v) @safe | { | return t = v; | } | return _iterator[indexStride(_indices)] = value; | } | ///ditto | auto ref opIndexAssign()(DeepElement value, size_t[N] _indices...) return scope @trusted | { | import mir.functional: forward; | // check assign safety | static auto ref fun(ref DeepElement t, ref DeepElement v) @safe | { 0000000| return t = v; | } 2425| return _iterator[indexStride(_indices)] = forward!value; | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); | 2| a[1, 2] = 3; 2| assert(a[1, 2] == 3); | } | | static if (doUnittest) | @safe pure nothrow version(mir_ndslice_test) unittest | { 2| auto a = new int[6].sliced(2, 3); | 2| a[[1, 2]] = 3; 2| assert(a[[1, 2]] == 3); | } | | /++ | Op Assignment `op=` of a value (e.g. a number) to a $(B fully defined index). | +/ | auto ref opIndexOpAssign(string op, T)(T value, size_t[N] _indices...) return scope @trusted | { | // check op safety | static auto ref fun(ref DeepElement t, ref T v) @safe | { 0000000| return mixin(`t` ~ op ~ `= v`); | } 1138| auto str = indexStride(_indices); | static if (op == "^^" && isFloatingPoint!DeepElement && isFloatingPoint!(typeof(value))) | { | import mir.math.common: pow; | _iterator[str] = pow(_iterator[str], value); | } | else 1150| return mixin (`_iterator[str] ` ~ op ~ `= value`); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); | 2| a[1, 2] += 3; 2| assert(a[1, 2] == 3); | } | | static if (doUnittest) | @safe pure nothrow version(mir_ndslice_test) unittest | { 2| auto a = new int[6].sliced(2, 3); | 2| a[[1, 2]] += 3; 2| assert(a[[1, 2]] == 3); | } | | /++ | Op Assignment `op=` of a value of `Slice` type to a $(B fully defined slice). | +/ | void opIndexOpAssign(string op, RIterator, SliceKind rkind, size_t RN, Slices...) | (Slice!(RIterator, RN, rkind) value, Slices slices) return scope | if (isFullPureSlice!Slices || isIndexedSlice!Slices) | { 26| auto sl = this.lightScope.opIndex(slices); 26| assert(_checkAssignLengths(sl, value)); 26| if(!sl.anyRUEmpty) 26| sl.opIndexOpAssignImplSlice!op(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); 2| auto b = [1, 2, 3, 4].sliced(2, 2); | 2| a[0..$, 0..$-1] += b; 2| assert(a == [[1, 2, 0], [3, 4, 0]]); | 2| a[0..$, 0..$-1] += b[0]; 2| assert(a == [[2, 4, 0], [4, 6, 0]]); | 2| a[1, 0..$-1] += b[1]; 2| assert(a[1] == [7, 10, 0]); | 2| a[1, 0..$-1][] += b[0]; 2| assert(a[1] == [8, 12, 0]); | } | | static if (doUnittest) | /// Left slice is packed | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : blocks, iota; 2| auto a = slice!size_t(4, 4); 2| a.blocks(2, 2)[] += iota(2, 2); | 2| assert(a == | [[0, 0, 1, 1], | [0, 0, 1, 1], | [2, 2, 3, 3], | [2, 2, 3, 3]]); | } | | static if (doUnittest) | /// Both slices are packed | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : blocks, iota, pack; 2| auto a = slice!size_t(4, 4); 2| a.blocks(2, 2)[] += iota(2, 2, 2).pack!1; | 2| assert(a == | [[0, 1, 2, 3], | [0, 1, 2, 3], | [4, 5, 6, 7], | [4, 5, 6, 7]]); | } | | /++ | Op Assignment `op=` of a regular multidimensional array to a $(B fully defined slice). | +/ | void opIndexOpAssign(string op, T, Slices...)(T[] value, Slices slices) return scope | if (isFullPureSlice!Slices | && (!isDynamicArray!DeepElement || isDynamicArray!T) | && DynamicArrayDimensionsCount!(T[]) - DynamicArrayDimensionsCount!DeepElement <= typeof(this.opIndex(slices)).N) | { 14| auto sl = this.lightScope.opIndex(slices); 14| sl.opIndexOpAssignImplArray!op(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation : slice; 2| auto a = slice!int(2, 3); | 2| a[0..$, 0..$-1] += [[1, 2], [3, 4]]; 2| assert(a == [[1, 2, 0], [3, 4, 0]]); | 2| a[0..$, 0..$-1] += [1, 2]; 2| assert(a == [[2, 4, 0], [4, 6, 0]]); | 2| a[1, 0..$-1] += [3, 4]; 2| assert(a[1] == [7, 10, 0]); | 2| a[1, 0..$-1][] += [1, 2]; 2| assert(a[1] == [8, 12, 0]); | } | | static if (doUnittest) | /// Packed slices | @safe pure nothrow | version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : blocks; 2| auto a = slice!int(4, 4); 2| a.blocks(2, 2)[] += [[0, 1], [2, 3]]; | 2| assert(a == | [[0, 0, 1, 1], | [0, 0, 1, 1], | [2, 2, 3, 3], | [2, 2, 3, 3]]); | } | | private void opIndexOpAssignImplValue(string op, T)(T value) return scope | { | static if (N > 1 && kind == Contiguous) | { | import mir.ndslice.topology : flattened; 12| this.flattened.opIndexOpAssignImplValue!op(value); | } | else | { 286| auto ls = this; | do | { | static if (N == 1) | { | static if (isInstanceOf!(SliceIterator, Iterator)) 38| ls.front.opIndexOpAssignImplValue!op(value); | else | mixin (`ls.front ` ~ op ~ `= value;`); | } | else 125| ls.front.opIndexOpAssignImplValue!op(value); 2509| ls.popFront; | } 2509| while(ls._lengths[0]); | } | } | | /++ | Op Assignment `op=` of a value (e.g. a number) to a $(B fully defined slice). | +/ | void opIndexOpAssign(string op, T, Slices...)(T value, Slices slices) return scope | if ((isFullPureSlice!Slices || isIndexedSlice!Slices) | && (!isDynamicArray!T || isDynamicArray!DeepElement) | && DynamicArrayDimensionsCount!T == DynamicArrayDimensionsCount!DeepElement | && !isSlice!T | && !isConcatenation!T) | { 37| auto sl = this.lightScope.opIndex(slices); 37| if(!sl.anyRUEmpty) 37| sl.opIndexOpAssignImplValue!op(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); | 2| a[] += 1; 2| assert(a == [[1, 1, 1], [1, 1, 1]]); | 2| a[0..$, 0..$-1] += 2; 2| assert(a == [[3, 3, 1], [3, 3, 1]]); | 2| a[1, 0..$-1] += 3; 2| assert(a[1] == [6, 6, 1]); | } | | /// | void opIndexOpAssign(string op,T, Slices...)(T concatenation, Slices slices) return scope | if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && isConcatenation!T) | { 1| auto sl = this.lightScope.opIndex(slices); | static assert(typeof(sl).N == concatenation.N); 1| sl.opIndexOpAssignImplConcatenation!op(concatenation); | } | | static if (doUnittest) | /// Packed slices have the same behavior. | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; | import mir.ndslice.topology : pack; 2| auto a = slice!int(2, 3).pack!1; | 2| a[] += 9; 2| assert(a == [[9, 9, 9], [9, 9, 9]]); | } | | | /++ | Increment `++` and Decrement `--` operators for a $(B fully defined index). | +/ | auto ref opIndexUnary(string op)(size_t[N] _indices...) return scope | @trusted | // @@@workaround@@@ for Issue 16473 | //if (op == `++` || op == `--`) | { | // check op safety | static auto ref fun(DeepElement t) @safe | { 0000000| return mixin(op ~ `t`); | } 15| return mixin (op ~ `_iterator[indexStride(_indices)]`); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); | 2| ++a[1, 2]; 2| assert(a[1, 2] == 1); | } | | // Issue 16473 | static if (doUnittest) | @safe pure nothrow version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto sl = slice!double(2, 5); 2| auto d = -sl[0, 1]; | } | | static if (doUnittest) | @safe pure nothrow version(mir_ndslice_test) unittest | { 2| auto a = new int[6].sliced(2, 3); | 2| ++a[[1, 2]]; 2| assert(a[[1, 2]] == 1); | } | | private void opIndexUnaryImpl(string op, Slices...)(Slices slices) scope | { 183| auto ls = this; | do | { | static if (N == 1) | { | static if (isInstanceOf!(SliceIterator, Iterator)) | ls.front.opIndexUnaryImpl!op; | else | mixin (op ~ `ls.front;`); | } | else 149| ls.front.opIndexUnaryImpl!op; 1105| ls.popFront; | } 1105| while(ls._lengths[0]); | } | | /++ | Increment `++` and Decrement `--` operators for a $(B fully defined slice). | +/ | void opIndexUnary(string op, Slices...)(Slices slices) return scope | if (isFullPureSlice!Slices && (op == `++` || op == `--`)) | { 34| auto sl = this.lightScope.opIndex(slices); 34| if (!sl.anyRUEmpty) 34| sl.opIndexUnaryImpl!op; | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_ndslice_test) unittest | { | import mir.ndslice.allocation; 2| auto a = slice!int(2, 3); | 2| ++a[]; 2| assert(a == [[1, 1, 1], [1, 1, 1]]); | 2| --a[1, 0..$-1]; | 2| assert(a[1] == [0, 0, 1]); | } | } |} | |/// ditto |alias Slice = mir_slice; | |/++ |Slicing, indexing, and arithmetic operations. |+/ |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.dynamic : transposed; | import mir.ndslice.topology : iota, universal; 1| auto tensor = iota(3, 4, 5).slice; | 1| assert(tensor[1, 2] == tensor[1][2]); 1| assert(tensor[1, 2, 3] == tensor[1][2][3]); | 1| assert( tensor[0..$, 0..$, 4] == tensor.universal.transposed!2[4]); 1| assert(&tensor[0..$, 0..$, 4][1, 2] is &tensor[1, 2, 4]); | 1| tensor[1, 2, 3]++; //`opIndex` returns value by reference. 1| --tensor[1, 2, 3]; //`opUnary` | 1| ++tensor[]; 1| tensor[] -= 1; | | // `opIndexAssing` accepts only fully defined indices and slices. | // Use an additional empty slice `[]`. | static assert(!__traits(compiles, tensor[0 .. 2] *= 2)); | 1| tensor[0 .. 2][] *= 2; //OK, empty slice 1| tensor[0 .. 2, 3, 0..$] /= 2; //OK, 3 index or slice positions are defined. | | //fully defined index may be replaced by a static array 1| size_t[3] index = [1, 2, 3]; 1| assert(tensor[index] == tensor[1, 2, 3]); |} | |/++ |Operations with rvalue slices. |+/ |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.topology: universal; | import mir.ndslice.dynamic: transposed, everted; | 1| auto tensor = slice!int(3, 4, 5).universal; 1| auto matrix = slice!int(3, 4).universal; 1| auto vector = slice!int(3); | 12| foreach (i; 0..3) 3| vector[i] = i; | | // fills matrix columns 1| matrix.transposed[] = vector; | | // fills tensor with vector | // transposed tensor shape is (4, 5, 3) | // vector shape is ( 3) 1| tensor.transposed!(1, 2)[] = vector; | | // transposed tensor shape is (5, 3, 4) | // matrix shape is ( 3, 4) 1| tensor.transposed!2[] += matrix; | | // transposed tensor shape is (5, 4, 3) | // transposed matrix shape is ( 4, 3) 1| tensor.everted[] ^= matrix.transposed; // XOR |} | |/++ |Creating a slice from text. |See also $(MREF std, format). |+/ |version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: filter, all; | import mir.array.allocation; | import mir.exception; | import mir.functional: not; | import mir.ndslice.allocation; | import mir.parse; | import mir.primitives: empty; | | import std.algorithm: map; | import std.string: lineSplitter, split; | | // std.functional, std.string, std.range; | | Slice!(int*, 2) toMatrix(string str) | { 1| string[][] data = str.lineSplitter.filter!(not!empty).map!split.array; | 1| size_t rows = data .length.enforce!"empty input"; 1| size_t columns = data[0].length.enforce!"empty first row"; | 3| data.all!(a => a.length == columns).enforce!"rows have different lengths"; 1| auto slice = slice!int(rows, columns); 11| foreach (i, line; data) 30| foreach (j, num; line) 6| slice[i, j] = num.fromString!int; 1| return slice; | } | 1| auto input = "\r1 2 3\r\n 4 5 6\n"; | 1| auto matrix = toMatrix(input); 1| assert(matrix == [[1, 2, 3], [4, 5, 6]]); | | // back to text | import std.format; 1| auto text2 = format("%(%(%s %)\n%)\n", matrix); 1| assert(text2 == "1 2 3\n4 5 6\n"); |} | |// Slicing |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; 1| auto a = iota(10, 20, 30, 40); 1| auto b = a[0..$, 10, 4 .. 27, 4]; 1| auto c = b[2 .. 9, 5 .. 10]; 1| auto d = b[3..$, $-2]; 1| assert(b[4, 17] == a[4, 10, 21, 4]); 1| assert(c[1, 2] == a[3, 10, 11, 4]); 1| assert(d[3] == a[6, 10, 25, 4]); |} | |// Operator overloading. # 1 |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.topology : iota; | 1| auto fun(ref sizediff_t x) { x *= 3; } | 1| auto tensor = iota(8, 9, 10).slice; | 1| ++tensor[]; 1| fun(tensor[0, 0, 0]); | 1| assert(tensor[0, 0, 0] == 3); | 1| tensor[0, 0, 0] *= 4; 1| tensor[0, 0, 0]--; 1| assert(tensor[0, 0, 0] == 11); |} | |// Operator overloading. # 2 |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: map, iota; | import mir.array.allocation : array; | //import std.bigint; | 1| auto matrix = 72 | .iota | //.map!(i => BigInt(i)) | .array | .sliced(8, 9); | 1| matrix[3 .. 6, 2] += 100; 27| foreach (i; 0 .. 8) 240| foreach (j; 0 .. 9) 144| if (i >= 3 && i < 6 && j == 2) 3| assert(matrix[i, j] >= 100); | else 69| assert(matrix[i, j] < 100); |} | |// Operator overloading. # 3 |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.topology : iota; | 1| auto matrix = iota(8, 9).slice; 1| matrix[] = matrix; 1| matrix[] += matrix; 1| assert(matrix[2, 3] == (2 * 9 + 3) * 2); | 1| auto vec = iota([9], 100); 1| matrix[] = vec; 26| foreach (v; matrix) 8| assert(v == vec); | 1| matrix[] += vec; 26| foreach (vector; matrix) 232| foreach (elem; vector) 72| assert(elem >= 200); |} | |// Type deduction |version(mir_ndslice_test) unittest |{ | // Arrays | foreach (T; AliasSeq!(int, const int, immutable int)) | static assert(is(typeof((T[]).init.sliced(3, 4)) == Slice!(T*, 2))); | | // Container Array | import std.container.array; 2| Array!int ar; 1| ar.length = 12; 2| auto arSl = ar[].slicedField(3, 4); |} | |// Test for map #1 |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: map, byDim; 1| auto slice = [1, 2, 3, 4].sliced(2, 2); | 17| auto r = slice.byDim!0.map!(a => a.map!(a => a * 6)); 1| assert(r.front.front == 6); 1| assert(r.front.back == 12); 1| assert(r.back.front == 18); 1| assert(r.back.back == 24); 1| assert(r[0][0] == 6); 1| assert(r[0][1] == 12); 1| assert(r[1][0] == 18); 1| assert(r[1][1] == 24); | | import std.range.primitives; | static assert(hasSlicing!(typeof(r))); | static assert(isForwardRange!(typeof(r))); | static assert(isRandomAccessRange!(typeof(r))); |} | |// Test for map #2 |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: map, byDim; | import std.range.primitives; 1| auto data = [1, 2, 3, 4]; | static assert(hasSlicing!(typeof(data))); | static assert(isForwardRange!(typeof(data))); | static assert(isRandomAccessRange!(typeof(data))); 1| auto slice = data.sliced(2, 2); | static assert(hasSlicing!(typeof(slice))); | static assert(isForwardRange!(typeof(slice))); | static assert(isRandomAccessRange!(typeof(slice))); 17| auto r = slice.byDim!0.map!(a => a.map!(a => a * 6)); | static assert(hasSlicing!(typeof(r))); | static assert(isForwardRange!(typeof(r))); | static assert(isRandomAccessRange!(typeof(r))); 1| assert(r.front.front == 6); 1| assert(r.front.back == 12); 1| assert(r.back.front == 18); 1| assert(r.back.back == 24); 1| assert(r[0][0] == 6); 1| assert(r[0][1] == 12); 1| assert(r[1][0] == 18); 1| assert(r[1][1] == 24); |} | |private enum bool isType(alias T) = false; | |private enum bool isType(T) = true; | |private enum isStringValue(alias T) = is(typeof(T) : string); | | |private bool _checkAssignLengths( | LIterator, RIterator, | size_t LN, size_t RN, | SliceKind lkind, SliceKind rkind, | ) | (Slice!(LIterator, LN, lkind) ls, | Slice!(RIterator, RN, rkind) rs) |{ | static if (isInstanceOf!(SliceIterator, LIterator)) | { | import mir.ndslice.topology: unpack; 8| return _checkAssignLengths(ls.unpack, rs); | } | else | static if (isInstanceOf!(SliceIterator, RIterator)) | { | import mir.ndslice.topology: unpack; 4| return _checkAssignLengths(ls, rs.unpack); | } | else | { | foreach (i; Iota!(0, RN)) 299| if (ls._lengths[i + LN - RN] != rs._lengths[i]) 3| return false; 247| return true; | } |} | |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; | 1| assert(_checkAssignLengths(iota(2, 2), iota(2, 2))); 1| assert(!_checkAssignLengths(iota(2, 2), iota(2, 3))); 1| assert(!_checkAssignLengths(iota(2, 2), iota(3, 2))); 1| assert(!_checkAssignLengths(iota(2, 2), iota(3, 3))); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto slice = new int[15].slicedField(5, 3); | | /// Fully defined slice 1| assert(slice[] == slice); 1| auto sublice = slice[0..$-2, 1..$]; | | /// Partially defined slice 1| auto row = slice[3]; 1| auto col = slice[0..$, 1]; |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); 1| auto b = [1, 2, 3, 4].sliced(2, 2); | 1| a[0..$, 0..$-1] = b; 1| assert(a == [[1, 2, 0], [3, 4, 0]]); | 1| a[0..$, 0..$-1] = b[0]; 1| assert(a == [[1, 2, 0], [1, 2, 0]]); | 1| a[1, 0..$-1] = b[1]; 1| assert(a[1] == [3, 4, 0]); | 1| a[1, 0..$-1][] = b[0]; 1| assert(a[1] == [1, 2, 0]); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); 1| auto b = [[1, 2], [3, 4]]; | 1| a[] = [[1, 2, 3], [4, 5, 6]]; 1| assert(a == [[1, 2, 3], [4, 5, 6]]); | 1| a[0..$, 0..$-1] = [[1, 2], [3, 4]]; 1| assert(a == [[1, 2, 3], [3, 4, 6]]); | 1| a[0..$, 0..$-1] = [1, 2]; 1| assert(a == [[1, 2, 3], [1, 2, 6]]); | 1| a[1, 0..$-1] = [3, 4]; 1| assert(a[1] == [3, 4, 6]); | 1| a[1, 0..$-1][] = [3, 4]; 1| assert(a[1] == [3, 4, 6]); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| a[] = 9; | //assert(a == [[9, 9, 9], [9, 9, 9]]); | 1| a[0..$, 0..$-1] = 1; | //assert(a == [[1, 1, 9], [1, 1, 9]]); | 1| a[0..$, 0..$-1] = 2; | //assert(a == [[2, 2, 9], [2, 2, 9]]); | 1| a[1, 0..$-1] = 3; | //assert(a[1] == [3, 3, 9]); | 1| a[1, 0..$-1] = 4; | //assert(a[1] == [4, 4, 9]); | 1| a[1, 0..$-1][] = 5; | //assert(a[1] == [5, 5, 9]); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| a[1, 2] = 3; 1| assert(a[1, 2] == 3); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| a[[1, 2]] = 3; 1| assert(a[[1, 2]] == 3); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| a[1, 2] += 3; 1| assert(a[1, 2] == 3); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| a[[1, 2]] += 3; 1| assert(a[[1, 2]] == 3); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); 1| auto b = [1, 2, 3, 4].sliced(2, 2); | 1| a[0..$, 0..$-1] += b; 1| assert(a == [[1, 2, 0], [3, 4, 0]]); | 1| a[0..$, 0..$-1] += b[0]; 1| assert(a == [[2, 4, 0], [4, 6, 0]]); | 1| a[1, 0..$-1] += b[1]; 1| assert(a[1] == [7, 10, 0]); | 1| a[1, 0..$-1][] += b[0]; 1| assert(a[1] == [8, 12, 0]); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| a[0..$, 0..$-1] += [[1, 2], [3, 4]]; 1| assert(a == [[1, 2, 0], [3, 4, 0]]); | 1| a[0..$, 0..$-1] += [1, 2]; 1| assert(a == [[2, 4, 0], [4, 6, 0]]); | 1| a[1, 0..$-1] += [3, 4]; 1| assert(a[1] == [7, 10, 0]); | 1| a[1, 0..$-1][] += [1, 2]; 1| assert(a[1] == [8, 12, 0]); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| a[] += 1; 1| assert(a == [[1, 1, 1], [1, 1, 1]]); | 1| a[0..$, 0..$-1] += 2; 1| assert(a == [[3, 3, 1], [3, 3, 1]]); | 1| a[1, 0..$-1] += 3; 1| assert(a[1] == [6, 6, 1]); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| ++a[1, 2]; 1| assert(a[1, 2] == 1); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| ++a[[1, 2]]; 1| assert(a[[1, 2]] == 1); |} | |pure nothrow version(mir_ndslice_test) unittest |{ 1| auto a = new int[6].slicedField(2, 3); | 1| ++a[]; 1| assert(a == [[1, 1, 1], [1, 1, 1]]); | 1| --a[1, 0..$-1]; 1| assert(a[1] == [0, 0, 1]); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota, universal; | 1| auto sl = iota(3, 4).universal; 1| assert(sl[0 .. $] == sl); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: canonical, iota; | static assert(kindOf!(typeof(iota([1, 2]).canonical[1])) == Contiguous); |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; 1| auto s = iota(2, 3); 1| assert(s.front!1 == [0, 3]); 1| assert(s.back!1 == [2, 5]); |} | |/++ |Assignment utility for generic code that works both with scalars and with ndslices. |Params: | op = assign operation (generic, optional) | lside = left side | rside = right side |Returns: | expression value |+/ |auto ndassign(string op = "", L, R)(ref L lside, auto ref R rside) @property | if (!isSlice!L && (op.length == 0 || op[$-1] != '=')) |{ 1| return mixin(`lside ` ~ op ~ `= rside`); |} | |/// ditto |auto ndassign(string op = "", L, R)(L lside, auto ref R rside) @property | if (isSlice!L && (op.length == 0 || op[$-1] != '=')) |{ | static if (op == "") 2| return lside.opIndexAssign(rside); | else 2| return lside.opIndexOpAssign!op(rside); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota; | import mir.ndslice.allocation: slice; 1| auto scalar = 3; 1| auto vector = 3.iota.slice; // [0, 1, 2] | | // scalar = 5; 1| scalar.ndassign = 5; 1| assert(scalar == 5); | | // vector[] = vector * 2; 1| vector.ndassign = vector * 2; 1| assert(vector == [0, 2, 4]); | | // vector[] += scalar; 1| vector.ndassign!"+"= scalar; 1| assert(vector == [5, 7, 9]); |} | |version(mir_ndslice_test) pure nothrow unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: universal; | 1| auto df = slice!(double, int, int)(2, 3).universal; 1| df.label[] = [1, 2]; 1| df.label!1[] = [1, 2, 3]; 1| auto lsdf = df.lightScope; 1| assert(lsdf.label!0[0] == 1); 1| assert(lsdf.label!1[1] == 2); | 1| auto immdf = (cast(immutable)df).lightImmutable; 1| assert(immdf.label!0[0] == 1); 1| assert(immdf.label!1[1] == 2); | 1| auto constdf = df.lightConst; 1| assert(constdf.label!0[0] == 1); 1| assert(constdf.label!1[1] == 2); | 1| auto constdf2 = df.toConst; 1| assert(constdf2.label!0[0] == 1); 1| assert(constdf2.label!1[1] == 2); | 1| auto immdf2 = (cast(immutable)df).toImmutable; 1| assert(immdf2.label!0[0] == 1); 1| assert(immdf2.label!1[1] == 2); |} | |version(mir_ndslice_test) pure nothrow unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: universal; | 1| auto df = slice!(double, int, int)(2, 3).universal; 1| df[] = 5; | 1| Slice!(double*, 2, Universal) values = df.values; 1| assert(values[0][0] == 5); 1| Slice!(LightConstOf!(double*), 2, Universal) constvalues = df.values; 1| assert(constvalues[0][0] == 5); 1| Slice!(LightImmutableOf!(double*), 2, Universal) immvalues = (cast(immutable)df).values; 1| assert(immvalues[0][0] == 5); |} | |version(mir_ndslice_test) @safe unittest |{ | import mir.ndslice.allocation; 2| auto a = rcslice!double([2, 3], 0); 2| auto b = rcslice!double([2, 3], 0); 1| a[1, 2] = 3; 1| b[] = a; 1| assert(a == b); |} | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | 1| auto m = iota(2, 3, 4); // Contiguous Matrix 1| auto mFlat = m.flattened; | 50| for (size_t i = 0; i < m.elementCount; i++) { 24| assert(m.accessFlat(i) == mFlat[i]); | } |} | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | 1| auto m = iota(3, 4); // Contiguous Matrix 1| auto x = m.front; // Contiguous Vector | 10| for (size_t i = 0; i < x.elementCount; i++) { 4| assert(x.accessFlat(i) == m[0, i]); | } |} | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | 1| auto m = iota(3, 4); // Contiguous Matrix 1| auto x = m[0 .. $, 0 .. $ - 1]; // Canonical Matrix 1| auto xFlat = x.flattened; | 20| for (size_t i = 0; i < x.elementCount; i++) { 9| assert(x.accessFlat(i) == xFlat[i]); | } |} | | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | 1| auto m = iota(2, 3, 4); // Contiguous Matrix 1| auto x = m[0 .. $, 0 .. $, 0 .. $ - 1]; // Canonical Matrix 1| auto xFlat = x.flattened; | 38| for (size_t i = 0; i < x.elementCount; i++) { 18| assert(x.accessFlat(i) == xFlat[i]); | } |} | | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | import mir.ndslice.dynamic: transposed; | 1| auto m = iota(2, 3, 4); // Contiguous Matrix 1| auto x = m.transposed!(2, 1, 0); // Universal Matrix 1| auto xFlat = x.flattened; | 50| for (size_t i = 0; i < x.elementCount; i++) { 24| assert(x.accessFlat(i) == xFlat[i]); | } |} | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | import mir.ndslice.dynamic: transposed; | 1| auto m = iota(3, 4); // Contiguous Matrix 1| auto x = m.transposed; // Universal Matrix 1| auto xFlat = x.flattened; | 26| for (size_t i = 0; i < x.elementCount; i++) { 12| assert(x.accessFlat(i) == xFlat[i]); | } |} | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened, diagonal; | 1| auto m = iota(3, 4); // Contiguous Matrix 1| auto x = m.diagonal; // Universal Vector | 8| for (size_t i = 0; i < x.elementCount; i++) { 3| assert(x.accessFlat(i) == m[i, i]); | } |} | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | 1| auto m = iota(3, 4); // Contiguous Matrix 1| auto x = m.front!1; // Universal Vector | 8| for (size_t i = 0; i < x.elementCount; i++) { 3| assert(x.accessFlat(i) == m[i, 0]); | } |} | |version(mir_ndslice_test) |@safe pure @nogc nothrow |unittest // check it can be compiled |{ | import mir.algebraic; | alias S = Slice!(double*, 2); | alias D = Variant!S; |} | |version(mir_ndslice_test) |unittest |{ | import mir.ndslice; | 1| auto matrix = slice!short(3, 4); 1| matrix[] = 0; 1| matrix.diagonal[] = 1; | 1| auto row = matrix[2]; 1| row[3] = 6; 1| assert(matrix[2, 3] == 6); // D & C index order |} source/mir/ndslice/slice.d is 98% covered <<<<<< EOF # path=source-mir-ndslice-sorting.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |Note: | The combination of | $(SUBREF topology, pairwise) with lambda `"a <= b"` (`"a < b"`) and $(SUBREF algorithm, all) can be used | to check if an ndslice is sorted (strictly monotonic). | $(SUBREF topology, iota) can be used to make an index. | $(SUBREF topology, map) and $(SUBREF topology, zip) can be used to create Schwartzian transform. | See also the examples in the module. | | |See_also: $(SUBREF topology, flattened) | |`isSorted` and `isStrictlyMonotonic` | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Andrei Alexandrescu (Phobos), Ilia Ki (API, rework, Mir adoptation) | |Macros: | SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |+/ |module mir.ndslice.sorting; | |/// Check if ndslice is sorted, or strictly monotonic. |@safe pure version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.slice: sliced; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: pairwise; | 1| auto arr = [1, 1, 2].sliced; | 1| assert(arr.pairwise!"a <= b".all); 1| assert(!arr.pairwise!"a < b".all); | 1| arr = [4, 3, 2, 1].sliced; | 1| assert(!arr.pairwise!"a <= b".all); 1| assert(!arr.pairwise!"a < b".all); | 1| sort(arr); | 1| assert(arr.pairwise!"a <= b".all); 1| assert(arr.pairwise!"a < b".all); |} | |/// Create index |version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.allocation: slice; | import mir.ndslice.slice: sliced; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: iota, pairwise; | 1| auto arr = [4, 2, 3, 1].sliced; | 1| auto index = arr.length.iota.slice; 7| index.sort!((a, b) => arr[a] < arr[b]); | 1| assert(arr[index].pairwise!"a <= b".all); |} | |/// Schwartzian transform |version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.allocation: slice; | import mir.ndslice.slice: sliced; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: zip, map, pairwise; | 10| alias transform = (a) => (a - 3) ^^ 2; | 1| auto arr = [4, 2, 3, 1].sliced; | 6| arr.map!transform.slice.zip(arr).sort!((l, r) => l.a < r.a); | 1| assert(arr.map!transform.pairwise!"a <= b".all); |} | |import mir.ndslice.slice; |import mir.math.common: optmath; | |@optmath: | |@safe pure version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.topology: pairwise; | 1| auto a = [1, 2, 3].sliced; 1| assert(a[0 .. 0].pairwise!"a <= b".all); 1| assert(a[0 .. 1].pairwise!"a <= b".all); 1| assert(a.pairwise!"a <= b".all); 1| auto b = [1, 3, 2].sliced; 1| assert(!b.pairwise!"a <= b".all); | | // ignores duplicates 1| auto c = [1, 1, 2].sliced; 1| assert(c.pairwise!"a <= b".all); |} | |@safe pure version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.topology: pairwise; | 1| assert([1, 2, 3][0 .. 0].sliced.pairwise!"a < b".all); 1| assert([1, 2, 3][0 .. 1].sliced.pairwise!"a < b".all); 1| assert([1, 2, 3].sliced.pairwise!"a < b".all); 1| assert(![1, 3, 2].sliced.pairwise!"a < b".all); 1| assert(![1, 1, 2].sliced.pairwise!"a < b".all); |} | | |/++ |Sorts ndslice, array, or series. | |See_also: $(SUBREF topology, flattened). |+/ |template sort(alias less = "a < b") |{ | import mir.functional: naryFun; | import mir.series: Series; | static if (__traits(isSame, naryFun!less, less)) | { |@optmath: | /++ | Sort n-dimensional slice. | +/ | Slice!(Iterator, N, kind) sort(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | { 44| if (false) // break safety | { | import mir.utility : swapStars; | auto elem = typeof(*slice._iterator).init; | elem = elem; | auto l = less(elem, elem); | } | import mir.ndslice.topology: flattened; 44| if (slice.anyEmpty) 0000000| return slice; 44| .quickSortImpl!less(slice.flattened); 44| return slice; | } | | /++ | Sort for arrays | +/ | T[] sort(T)(T[] ar) | { 1| return .sort!less(ar.sliced).field; | } | | /++ | Sort for one-dimensional Series. | +/ | Series!(IndexIterator, Iterator, N, kind) | sort(IndexIterator, Iterator, size_t N, SliceKind kind) | (Series!(IndexIterator, Iterator, N, kind) series) | if (N == 1) | { | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: zip; | with(series) 60| index.zip(data).sort!((a, b) => less(a.a, b.a)); 12| return series; | } | | /++ | Sort for n-dimensional Series. | +/ | Series!(IndexIterator, Iterator, N, kind) | sort( | IndexIterator, | Iterator, | size_t N, | SliceKind kind, | SortIndexIterator, | DataIterator, | ) | ( | Series!(IndexIterator, Iterator, N, kind) series, | Slice!SortIndexIterator indexBuffer, | Slice!DataIterator dataBuffer, | ) | { | import mir.algorithm.iteration: each; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: iota, zip, ipack, evertPack; | 1| assert(indexBuffer.length == series.length); 1| assert(dataBuffer.length == series.length); 1| indexBuffer[] = indexBuffer.length.iota!(typeof(indexBuffer.front)); 7| series.index.zip(indexBuffer).sort!((a, b) => less(a.a, b.a)); 1| series.data.ipack!1.evertPack.each!((sl){ | { 2| assert(sl.shape == dataBuffer.shape); 2| dataBuffer[] = sl[indexBuffer]; 2| sl[] = dataBuffer; | }}); 1| return series; | } | } | else | alias sort = .sort!(naryFun!less); |} | |/// |@safe pure version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.slice; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: pairwise; | 1| int[10] arr = [7,1,3,2,9,0,5,4,8,6]; | 1| auto data = arr[].sliced(arr.length); 1| data.sort(); 1| assert(data.pairwise!"a <= b".all); |} | |/// one-dimensional series |pure version(mir_ndslice_test) unittest |{ | import mir.series; | 1| auto index = [4, 2, 1, 3, 0].sliced; 1| auto data = [5.6, 3.4, 2.1, 7.8, 0.1].sliced; 1| auto series = index.series(data); 1| series.sort; 1| assert(series.index == [0, 1, 2, 3, 4]); 1| assert(series.data == [0.1, 2.1, 3.4, 7.8, 5.6]); | /// initial index and data are the same 1| assert(index.iterator is series.index.iterator); 1| assert(data.iterator is series.data.iterator); | 17| foreach(obs; series) | { | static assert(is(typeof(obs) == Observation!(int, double))); | } |} | |/// two-dimensional series |pure version(mir_ndslice_test) unittest |{ | import mir.series; | import mir.ndslice.allocation: uninitSlice; | 1| auto index = [4, 2, 3, 1].sliced; 1| auto data = | [2.1, 3.4, | 5.6, 7.8, | 3.9, 9.0, | 4.0, 2.0].sliced(4, 2); 1| auto series = index.series(data); | 1| series.sort( | uninitSlice!size_t(series.length), // index buffer | uninitSlice!double(series.length), // data buffer | ); | 1| assert(series.index == [1, 2, 3, 4]); 1| assert(series.data == | [[4.0, 2.0], | [5.6, 7.8], | [3.9, 9.0], | [2.1, 3.4]]); | /// initial index and data are the same 1| assert(index.iterator is series.index.iterator); 1| assert(data.iterator is series.data.iterator); |} | |void quickSortImpl(alias less, Iterator)(Slice!Iterator slice) @trusted |{ | import mir.utility : swap, swapStars; | | enum max_depth = 64; | enum naive_est = 1024 / slice.Element!0.sizeof; | enum size_t naive = 32 > naive_est ? 32 : naive_est; | //enum size_t naive = 1; | static assert(naive >= 1); | | for(;;) | { 1038| auto l = slice._iterator; 1038| auto r = l; 1038| r += slice.length; | 1038| if(slice.length <= 1) 3| return; | | static if (naive > 1) | { 1532| if (slice.length <= naive || __ctfe) | { 538| auto p = r; 538| --p; 9838| while(p != l) | { 9300| --p; | //static if (is(typeof(() nothrow | // { | // auto t = slice[0]; if (less(t, slice[0])) slice[0] = slice[0]; | // }))) | //{ 9300| auto d = p; | import mir.functional: unref; 9300| auto temp = unref(*d); 9300| auto c = d; 9300| ++c; 9300| if (less(*c, temp)) | { | do | { 49177| d[0] = *c; 49177| ++d; 49177| ++c; | } 97317| while (c != r && less(*c, temp)); 7736| d[0] = temp; | } | //} | //else | //{ | // auto d = p; | // auto c = d; | // ++c; | // while (less(*c, *d)) | // { | // swap(*d, *c); | // d = c; | // ++c; | // if (c == maxJ) break; | // } | //} | } 538| return; | } | } | | // partition 497| auto lessI = l; 497| --r; 497| auto pivotIdx = l + slice.length / 2; 497| setPivot!less(slice.length, l, pivotIdx, r); | import mir.functional: unref; 497| auto pivot = unref(*pivotIdx); 497| --lessI; 497| auto greaterI = r; 497| swapStars(pivotIdx, greaterI); | | outer: for (;;) | { 49075| do ++lessI; 49075| while (less(*lessI, pivot)); 21631| assert(lessI <= greaterI, "sort: invalid comparison function."); | for (;;) | { 48751| if (greaterI == lessI) 484| break outer; 48267| --greaterI; 48267| if (!less(pivot, *greaterI)) 21147| break; | } 21147| assert(lessI <= greaterI, "sort: invalid comparison function."); 21147| if (lessI == greaterI) 13| break; 21134| swapStars(lessI, greaterI); | } | 497| swapStars(r, lessI); | 497| ptrdiff_t len = lessI - l; 497| auto tail = slice[len + 1 .. $]; 497| slice = slice[0 .. len]; 497| if (tail.length > slice.length) 250| swap(slice, tail); 497| quickSortImpl!less(tail); | } |} | |void setPivot(alias less, Iterator)(size_t length, ref Iterator l, ref Iterator mid, ref Iterator r) @trusted |{ 497| if (length < 512) | { 467| if (length >= 32) 467| medianOf!less(l, mid, r); 467| return; | } 30| auto quarter = length >> 2; 30| auto b = mid - quarter; 30| auto e = mid + quarter; 30| medianOf!less(l, e, mid, b, r); |} | |void medianOf(alias less, bool leanRight = false, Iterator) | (ref Iterator a, ref Iterator b) @trusted |{ | import mir.utility : swapStars; | 5| if (less(*b, *a)) { 2| swapStars(a, b); | } 5| assert(!less(*b, *a)); |} | |void medianOf(alias less, bool leanRight = false, Iterator) | (ref Iterator a, ref Iterator b, ref Iterator c) @trusted |{ | import mir.utility : swapStars; | 710| if (less(*c, *a)) // c < a | { 342| if (less(*a, *b)) // c < a < b | { 105| swapStars(a, b); 105| swapStars(a, c); | } | else // c < a, b <= a | { 237| swapStars(a, c); 348| if (less(*b, *a)) swapStars(a, b); | } | } | else // a <= c | { 368| if (less(*b, *a)) // b < a <= c | { 104| swapStars(a, b); | } | else // a <= c, a <= b | { 385| if (less(*c, *b)) swapStars(b, c); | } | } 710| assert(!less(*b, *a)); 710| assert(!less(*c, *b)); |} | |void medianOf(alias less, bool leanRight = false, Iterator) | (ref Iterator a, ref Iterator b, ref Iterator c, ref Iterator d) @trusted |{ | import mir.utility: swapStars; | | static if (!leanRight) | { | // Eliminate the rightmost from the competition 50| if (less(*d, *c)) swapStars(c, d); // c <= d 38| if (less(*d, *b)) swapStars(b, d); // b <= d 29| medianOf!less(a, b, c); | } | else | { | // Eliminate the leftmost from the competition 75| if (less(*b, *a)) swapStars(a, b); // a <= b 64| if (less(*c, *a)) swapStars(a, c); // a <= c 50| medianOf!less(b, c, d); | } |} | |void medianOf(alias less, bool leanRight = false, Iterator) | (ref Iterator a, ref Iterator b, ref Iterator c, ref Iterator d, ref Iterator e) @trusted |{ | import mir.utility: swapStars; // Credit: Teppo Niinimäki | 34| version(unittest) scope(success) | { 34| assert(!less(*c, *a)); 34| assert(!less(*c, *b)); 34| assert(!less(*d, *c)); 34| assert(!less(*e, *c)); | } | 58| if (less(*c, *a)) swapStars(a, c); 45| if (less(*d, *b)) swapStars(b, d); 34| if (less(*d, *c)) | { 19| swapStars(c, d); 19| swapStars(a, b); | } 51| if (less(*e, *b)) swapStars(b, e); 34| if (less(*e, *c)) | { 17| swapStars(c, e); 21| if (less(*c, *a)) swapStars(a, c); | } | else | { 22| if (less(*c, *b)) swapStars(b, c); | } |} | | |/++ |Returns: `true` if a sorted array contains the value. | |Params: | test = strict ordering symmetric predicate | |For non-symmetric predicates please use a structure with two `opCall`s or an alias of two global functions, |that correponds to `(array[i], value)` and `(value, array[i])` cases. | |See_also: $(LREF transitionIndex). |+/ |template assumeSortedContains(alias test = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!test, test)) | { |@optmath: | /++ | Params: | slice = sorted one-dimensional slice or array. | v = value to test with. It is passed to second argument. | +/ | bool assumeSortedContains(Iterator, SliceKind kind, V) | (auto ref Slice!(Iterator, 1, kind) slice, auto ref scope const V v) | { | auto ti = transitionIndex!test(slice, v); | return ti < slice.length && !test(v, slice[ti]); | } | | /// ditto | bool assumeSortedContains(T, V)(scope T[] ar, auto ref scope const V v) | { | return .assumeSortedContains!test(ar.sliced, v); | } | } | else | alias assumeSortedContains = .assumeSortedContains!(naryFun!test); |} | |/++ |Returns: the smallest index of a sorted array such | that the index corresponds to the arrays element at the index according to the predicate | and `-1` if the array doesn't contain corresponding element. | |Params: | test = strict ordering symmetric predicate. | |For non-symmetric predicates please use a structure with two `opCall`s or an alias of two global functions, |that correponds to `(array[i], value)` and `(value, array[i])` cases. | |See_also: $(LREF transitionIndex). |+/ |template assumeSortedEqualIndex(alias test = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!test, test)) | { |@optmath: | /++ | Params: | slice = sorted one-dimensional slice or array. | v = value to test with. It is passed to second argument. | +/ | sizediff_t assumeSortedEqualIndex(Iterator, SliceKind kind, V) | (auto ref Slice!(Iterator, 1, kind) slice, auto ref scope const V v) | { 3| auto ti = transitionIndex!test(slice, v); 6| return ti < slice.length && !test(v, slice[ti]) ? ti : -1; | } | | /// ditto | sizediff_t assumeSortedEqualIndex(T, V)(scope T[] ar, auto ref scope const V v) | { 3| return .assumeSortedEqualIndex!test(ar.sliced, v); | } | } | else | alias assumeSortedEqualIndex = .assumeSortedEqualIndex!(naryFun!test); |} | |/// |version(mir_ndslice_test) |@safe pure unittest |{ | // sorted: a < b 1| auto a = [0, 1, 2, 3, 4, 6]; | 1| assert(a.assumeSortedEqualIndex(2) == 2); 1| assert(a.assumeSortedEqualIndex(5) == -1); | | // <= non strict predicates doesn't work 1| assert(a.assumeSortedEqualIndex!"a <= b"(2) == -1); |} | |/++ |Computes transition index using binary search. |It is low-level API for lower and upper bounds of a sorted array. | |Params: | test = ordering predicate for (`(array[i], value)`) pairs. | |See_also: $(SUBREF topology, assumeSortedEqualIndex). |+/ |template transitionIndex(alias test = "a < b") |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!test, test)) | { |@optmath: | /++ | Params: | slice = sorted one-dimensional slice or array. | v = value to test with. It is passed to second argument. | +/ | size_t transitionIndex(Iterator, SliceKind kind, V) | (auto ref Slice!(Iterator, 1, kind) slice, auto ref scope const V v) | { 3778| size_t first = 0, count = slice.length; 5775| while (count > 0) | { 7772| immutable step = count / 2, it = first + step; 3886| if (test(slice[it], v)) | { 1769| first = it + 1; 1769| count -= step + 1; | } | else | { 2117| count = step; | } | } 1889| return first; | } | | /// ditto | size_t transitionIndex(T, V)(scope T[] ar, auto ref scope const V v) | { 1674| return .transitionIndex!test(ar.sliced, v); | } | | } | else | alias transitionIndex = .transitionIndex!(naryFun!test); |} | |/// |version(mir_ndslice_test) |@safe pure unittest |{ | // sorted: a < b 1| auto a = [0, 1, 2, 3, 4, 6]; | 1| auto i = a.transitionIndex(2); 1| assert(i == 2); 1| auto lowerBound = a[0 .. i]; | 1| auto j = a.transitionIndex!"a <= b"(2); 1| assert(j == 3); 1| auto upperBound = a[j .. $]; | 1| assert(a.transitionIndex(a[$ - 1]) == a.length - 1); 1| assert(a.transitionIndex!"a <= b"(a[$ - 1]) == a.length); |} | |/++ |Computes an index for `r` based on the comparison `less`. The |index is a sorted array of indices into the original |range. | |This technique is similar to sorting, but it is more flexible |because (1) it allows "sorting" of immutable collections, (2) allows |binary search even if the original collection does not offer random |access, (3) allows multiple indices, each on a different predicate, |and (4) may be faster when dealing with large objects. However, using |an index may also be slower under certain circumstances due to the |extra indirection, and is always larger than a sorting-based solution |because it needs space for the index in addition to the original |collection. The complexity is the same as `sort`'s. | |Can be combined with $(SUBREF topology, indexed) to create a view that is sorted |based on the index. | |Params: | less = The comparison to use. | r = The slice/array to index. | |Returns: | Index slice/array. | |See_Also: | $(HTTPS numpy.org/doc/stable/reference/generated/numpy.argsort.html, numpy.argsort) |+/ |Slice!(I*) makeIndex(I = size_t, alias less = "a < b", Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) r) |{ | import mir.functional: naryFun; | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; 2| return r | .length | .iota!I | .slice 18| .sort!((a, b) => naryFun!less(r[a], r[b])); |} | |/// |I[] makeIndex(I = size_t, alias less = "a < b", T)(scope T[] r) |{ 2| return .makeIndex!(I, less)(r.sliced).field; |} | |/// |version(mir_ndslice_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: all; | import mir.ndslice.topology: indexed, pairwise; | 1| immutable arr = [ 2, 3, 1, 5, 0 ]; 1| auto index = arr.makeIndex; | 1| assert(arr.indexed(index).pairwise!"a < b".all); |} | |/// Sort based on index created from a separate array |version(mir_ndslice_test) |@safe pure nothrow |unittest |{ | import mir.algorithm.iteration: equal; | import mir.ndslice.topology: indexed; | 1| immutable arr0 = [ 2, 3, 1, 5, 0 ]; 1| immutable arr1 = [ 1, 5, 4, 2, -1 ]; | 1| auto index = makeIndex(arr0); 1| assert(index.equal([4, 2, 0, 1, 3])); 1| auto view = arr1.indexed(index); 1| assert(view.equal([-1, 4, 1, 5, 2])); |} | |/++ |Partitions `slice` around `pivot` using comparison function `less`, algorithm |akin to $(LINK2 https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme, |Hoare partition). Specifically, permutes elements of `slice` and returns |an index `k < slice.length` such that: | |$(UL | |$(LI `slice[pivot]` is swapped to `slice[k]`) | | |$(LI All elements `e` in subrange `slice[0 .. k]` satisfy `!less(slice[k], e)` |(i.e. `slice[k]` is greater than or equal to each element to its left according |to predicate `less`)) | |$(LI All elements `e` in subrange `slice[k .. $]` satisfy |`!less(e, slice[k])` (i.e. `slice[k]` is less than or equal to each element to |its right according to predicate `less`))) | |If `slice` contains equivalent elements, multiple permutations of `slice` may |satisfy these constraints. In such cases, `pivotPartition` attempts to |distribute equivalent elements fairly to the left and right of `k` such that `k` |stays close to `slice.length / 2`. | |Params: | less = The predicate used for comparison | |Returns: | The new position of the pivot | |See_Also: | $(HTTP jgrcs.info/index.php/jgrcs/article/view/142, Engineering of a Quicksort | Partitioning Algorithm), D. Abhyankar, Journal of Global Research in Computer | Science, February 2011. $(HTTPS youtube.com/watch?v=AxnotgLql0k, ACCU 2016 | Keynote), Andrei Alexandrescu. |+/ |@trusted |template pivotPartition(alias less = "a < b") |{ | import mir.functional: naryFun; | | static if (__traits(isSame, naryFun!less, less)) | { | /++ | Params: | slice = slice being partitioned | pivot = The index of the pivot for partitioning, must be less than | `slice.length` or `0` if `slice.length` is `0` | +/ | size_t pivotPartition(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice, | size_t pivot) | { 16| assert(pivot < slice.elementCount || slice.elementCount == 0 && pivot == 0, "pivotPartition: pivot must be less than the elementCount of the slice or the slice must be empty and pivot zero"); | 18| if (slice.elementCount <= 1) return 0; | | import mir.ndslice.topology: flattened; | 14| auto flattenedSlice = slice.flattened; 14| auto frontI = flattenedSlice._iterator; 14| auto lastI = frontI + flattenedSlice.length - 1; 14| auto pivotI = frontI + pivot; 14| pivotPartitionImpl!less(frontI, lastI, pivotI); 14| return pivotI - frontI; | } | } else { | alias pivotPartition = .pivotPartition!(naryFun!less); | } |} | |/// pivotPartition with 1-dimensional Slice |version(mir_ndslice_test) |@safe pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: all; | 1| auto x = [5, 3, 2, 6, 4, 1, 3, 7].sliced; 1| size_t pivot = pivotPartition(x, x.length / 2); | 5| assert(x[0 .. pivot].all!(a => a <= x[pivot])); 5| assert(x[pivot .. $].all!(a => a >= x[pivot])); |} | |/// pivotPartition with 2-dimensional Slice |version(mir_ndslice_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | import mir.ndslice.topology: flattened; | import mir.algorithm.iteration: all; | 1| auto x = [ | [5, 3, 2, 6], | [4, 1, 3, 7] | ].fuse; | 1| size_t pivot = pivotPartition(x, x.elementCount / 2); | 1| auto xFlattened = x.flattened; 5| assert(xFlattened[0 .. pivot].all!(a => a <= xFlattened[pivot])); 5| assert(xFlattened[pivot .. $].all!(a => a >= xFlattened[pivot])); |} | |version(mir_ndslice_test) |@safe |unittest |{ | void test(alias less)() | { | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: all, equal; | 2| Slice!(int*) x; 2| size_t pivot; | 2| x = [-9, -4, -2, -2, 9].sliced; 2| pivot = pivotPartition!less(x, x.length / 2); | 6| assert(x[0 .. pivot].all!(a => a <= x[pivot])); 8| assert(x[pivot .. $].all!(a => a >= x[pivot])); | 2| x = [9, 2, 8, -5, 5, 4, -8, -4, 9].sliced; 2| pivot = pivotPartition!less(x, x.length / 2); | 12| assert(x[0 .. pivot].all!(a => a <= x[pivot])); 10| assert(x[pivot .. $].all!(a => a >= x[pivot])); | 2| x = [ 42 ].sliced; 2| pivot = pivotPartition!less(x, x.length / 2); | 2| assert(pivot == 0); 2| assert(x.equal([ 42 ])); | 2| x = [ 43, 42 ].sliced; 2| pivot = pivotPartition!less(x, 0); 2| assert(pivot == 1); 2| assert(x.equal([ 42, 43 ])); | 2| x = [ 43, 42 ].sliced; 2| pivot = pivotPartition!less(x, 1); | 2| assert(pivot == 0); 2| assert(x.equal([ 42, 43 ])); | 2| x = [ 42, 42 ].sliced; 2| pivot = pivotPartition!less(x, 0); | 3| assert(pivot == 0 || pivot == 1); 2| assert(x.equal([ 42, 42 ])); | 2| pivot = pivotPartition!less(x, 1); | 3| assert(pivot == 0 || pivot == 1); 2| assert(x.equal([ 42, 42 ])); | } 1| test!"a < b"; | static bool myLess(int a, int b) | { | static bool bogus; 18| if (bogus) throw new Exception(""); // just to make it no-nothrow 18| return a < b; | } 1| test!myLess; |} | |@trusted |template pivotPartitionImpl(alias less) |{ | void pivotPartitionImpl(Iterator) | (ref Iterator frontI, | ref Iterator lastI, | ref Iterator pivotI) | { 706| assert(pivotI <= lastI && pivotI >= frontI, "pivotPartition: pivot must be less than the length of slice or slice must be empty and pivot zero"); | 353| if (frontI == lastI) return; | | import mir.utility: swapStars; | | // Pivot at the front 353| swapStars(pivotI, frontI); | | // Fork implementation depending on nothrow copy, assignment, and | // comparison. If all of these are nothrow, use the specialized | // implementation discussed at | // https://youtube.com/watch?v=AxnotgLql0k. | static if (is(typeof( | () nothrow { auto x = frontI; x = frontI; return less(*x, *x); } | ))) | { | // Plant the pivot in the end as well as a sentinel 347| auto loI = frontI; 347| auto hiI = lastI; 347| auto save = *hiI; 347| *hiI = *frontI; // Vacancy is in r[$ - 1] now | | // Start process | for (;;) | { | // Loop invariant | version(mir_ndslice_test) | { | // this used to import std.algorithm.all, but we want to | // save imports when unittests are enabled if possible. 527| size_t len = lastI - frontI + 1; 2451| foreach (x; 0 .. (loI - frontI)) 290| assert(!less(*frontI, frontI[x]), "pivotPartition: *frontI must not be less than frontI[x]"); 2760| foreach (x; (hiI - frontI + 1) .. len) 393| assert(!less(frontI[x], *frontI), "pivotPartition: frontI[x] must not be less than *frontI"); | } 2362| do ++loI; while (less(*loI, *frontI)); 527| *(hiI) = *(loI); | // Vacancy is now in slice[lo] 2276| do --hiI; while (less(*frontI, *hiI)); 874| if (loI >= hiI) break; 180| *(loI) = *(hiI); | // Vacancy is not in slice[hi] | } | // Fixup 347| assert(loI - hiI <= 2, "pivotPartition: Following compare not possible"); 347| assert(!less(*frontI, *hiI), "pivotPartition: *hiI must not be less than *frontI"); 347| if (loI - hiI == 2) | { 46| assert(!less(hiI[1], *frontI), "pivotPartition: *(hiI + 1) must not be less than *frontI"); 46| *(loI) = hiI[1]; 46| --loI; | } 347| *loI = save; 532| if (less(*frontI, save)) --loI; 347| assert(!less(*frontI, *loI), "pivotPartition: *frontI must not be less than *loI"); | } else { 6| auto loI = frontI; 6| ++loI; 6| auto hiI = lastI; | 2| loop: for (;; loI++, hiI--) | { 6| for (;; ++loI) | { 16| if (loI > hiI) break loop; 18| if (!less(*loI, *frontI)) break; | } | // found the left bound: !less(*loI, *frontI) 6| assert(loI <= hiI, "pivotPartition: loI must be less or equal than hiI"); 2| for (;; --hiI) | { 12| if (loI >= hiI) break loop; 6| if (!less(*frontI, *hiI)) break; | } | // found the right bound: !less(*frontI, *hiI), swap & make progress 2| assert(!less(*loI, *hiI), "pivotPartition: *lowI must not be less than *hiI"); 2| swapStars(loI, hiI); | } 6| --loI; | } | 353| swapStars(loI, frontI); 353| pivotI = loI; | } |} | |version(mir_ndslice_test) |@safe pure nothrow |unittest { | import mir.ndslice.sorting: partitionAt; | import mir.ndslice.allocation: rcslice; 2| auto x = rcslice!double(4); 1| x[0] = 3; 1| x[1] = 2; 1| x[2] = 1; 1| x[3] = 0; 1| partitionAt!("a > b")(x, 2); |} | | |version(mir_ndslice_test) |@trusted pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: all; | 1| auto x = [5, 3, 2, 6, 4, 1, 3, 7].sliced; 1| auto frontI = x._iterator; 1| auto lastI = x._iterator + x.length - 1; 1| auto pivotI = frontI + x.length / 2; 23| alias less = (a, b) => (a < b); 1| pivotPartitionImpl!less(frontI, lastI, pivotI); 1| size_t pivot = pivotI - frontI; | 5| assert(x[0 .. pivot].all!(a => a <= x[pivot])); 5| assert(x[pivot .. $].all!(a => a >= x[pivot])); |} | |/++ |Partitions `slice`, such that all elements `e1` from `slice[0]` to `slice[nth]` |satisfy `!less(slice[nth], e1)`, and all elements `e2` from `slice[nth]` to |`slice[slice.length]` satisfy `!less(e2, slice[nth])`. This effectively reorders |`slice` such that `slice[nth]` refers to the element that would fall there if |the range were fully sorted. Performs an expected $(BIGOH slice.length) |evaluations of `less` and `swap`, with a worst case of $(BIGOH slice.length^^2). | |This function implements the [Fast, Deterministic Selection](https://erdani.com/research/sea2017.pdf) |algorithm that is implemented in the [`topN`](https://dlang.org/library/std/algorithm/sorting/top_n.html) |function in D's standard library (as of version `2.092.0`). | |Params: | less = The predicate to sort by. | |See_Also: | $(LREF pivotPartition), https://erdani.com/research/sea2017.pdf | |+/ |template partitionAt(alias less = "a < b") |{ | import mir.functional: naryFun; | | static if (__traits(isSame, naryFun!less, less)) | { | /++ | Params: | slice = n-dimensional slice | nth = The index of the element that should be in sorted position after the | function is finished. | +/ | void partitionAt(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice, size_t nth) @trusted nothrow @nogc | { | import mir.qualifier: lightScope; | import core.lifetime: move; | import mir.ndslice.topology: flattened; | 241| assert(slice.elementCount > 0, "partitionAt: slice must have elementCount greater than 0"); 241| assert(nth >= 0, "partitionAt: nth must be greater than or equal to zero"); 241| assert(nth < slice.elementCount, "partitionAt: nth must be less than the elementCount of the slice"); | 241| bool useSampling = true; 242| auto flattenedSlice = slice.move.flattened; 241| auto frontI = flattenedSlice._iterator.lightScope; 241| auto lastI = frontI + (flattenedSlice.length - 1); 241| partitionAtImpl!less(frontI, lastI, nth, useSampling); | } | } | else | alias partitionAt = .partitionAt!(naryFun!less); |} | |/// Partition 1-dimensional slice at nth |version(mir_ndslice_test) |@safe pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 2; 1| auto x = [3, 1, 5, 2, 0].sliced; 1| x.partitionAt(nth); 1| assert(x[nth] == 2); |} | |/// Partition 2-dimensional slice |version(mir_ndslice_test) |@safe pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 4; 1| auto x = [3, 1, 5, 2, 0, 7].sliced(3, 2); 1| x.partitionAt(nth); 1| assert(x[2, 0] == 5); |} | |/// Can supply alternate ordering function |version(mir_ndslice_test) |@safe pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 2; 1| auto x = [3, 1, 5, 2, 0].sliced; 1| x.partitionAt!("a > b")(nth); 1| assert(x[nth] == 2); |} | |// Check issue #328 fixed |version(mir_ndslice_test) |@safe pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| auto slice = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17].sliced; 1| partitionAt(slice, 8); 1| partitionAt(slice, 9); |} | |version(unittest) { | template checkPartitionAtAll(alias less = "a < b") | { | import mir.functional: naryFun; | import mir.ndslice.slice: SliceKind, Slice; | | static if (__traits(isSame, naryFun!less, less)) | { | @safe pure nothrow | static bool checkPartitionAtAll | (Iterator, SliceKind kind)( | Slice!(Iterator, 1, kind) x) | { 15| auto x_sorted = x.dup; 15| x_sorted.sort!less; | 15| bool result = true; | 546| foreach (nth; 0 .. x.length) | { 167| auto x_i = x.dup; 167| x_i.partitionAt!less(nth); 167| if (x_i[nth] != x_sorted[nth]) { 0000000| result = false; 0000000| break; | } | } 15| return result; | } | } else { | alias checkPartitionAtAll = .checkPartitionAtAll!(naryFun!less); | } | } |} | |version(mir_ndslice_test) |@safe pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| assert(checkPartitionAtAll([2, 2].sliced)); | 1| assert(checkPartitionAtAll([3, 1, 5, 2, 0].sliced)); 1| assert(checkPartitionAtAll([3, 1, 5, 0, 2].sliced)); 1| assert(checkPartitionAtAll([0, 0, 4, 3, 3].sliced)); 1| assert(checkPartitionAtAll([5, 1, 5, 1, 5].sliced)); 1| assert(checkPartitionAtAll([2, 2, 0, 0, 0].sliced)); | 1| assert(checkPartitionAtAll([ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced)); 1| assert(checkPartitionAtAll([ 4, 18, 16, 0, 15, 6, 2, 17, 10, 16].sliced)); 1| assert(checkPartitionAtAll([ 7, 5, 9, 4, 4, 2, 12, 20, 15, 15].sliced)); | 1| assert(checkPartitionAtAll([17, 87, 58, 50, 34, 98, 25, 77, 88, 79].sliced)); | 1| assert(checkPartitionAtAll([ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced)); 1| assert(checkPartitionAtAll([21, 3, 11, 22, 24, 12, 14, 12, 15, 15, 1, 3, 12, 15, 25, 19, 9, 16, 16, 19].sliced)); 1| assert(checkPartitionAtAll([22, 6, 18, 0, 1, 8, 13, 13, 16, 19, 23, 17, 4, 6, 12, 24, 15, 20, 11, 17].sliced)); 1| assert(checkPartitionAtAll([19, 23, 14, 5, 12, 3, 13, 7, 25, 25, 24, 9, 21, 25, 12, 22, 15, 22, 7, 11].sliced)); 1| assert(checkPartitionAtAll([ 0, 2, 7, 16, 2, 20, 1, 11, 17, 5, 22, 17, 25, 13, 14, 5, 22, 21, 24, 14].sliced)); |} | |private @trusted pure nothrow @nogc |void partitionAtImpl(alias less, Iterator)( | Iterator loI, | Iterator hiI, | size_t n, | bool useSampling) |{ 410| assert(loI <= hiI, "partitionAtImpl: frontI must be less than or equal to lastI"); | | import mir.utility: swapStars; | import mir.functional: reverseArgs; | 410| Iterator pivotI; 410| size_t len; | | for (;;) { 818| len = hiI - loI + 1; | 818| if (len <= 1) { 125| break; | } | 693| if (n == 0) { 101| pivotI = loI; 1602| foreach (i; 1 .. len) { 433| if (less(loI[i], *pivotI)) { 84| pivotI = loI + i; | } | } 101| swapStars(loI + n, pivotI); 101| break; | } | 592| if (n + 1 == len) { 116| pivotI = loI; 1791| foreach (i; 1 .. len) { 481| if (reverseArgs!less(loI[i], *pivotI)) { 135| pivotI = loI + i; | } | } 116| swapStars(loI + n, pivotI); 116| break; | } | 476| if (len <= 12) { 338| pivotI = loI + len / 2; 338| pivotPartitionImpl!less(loI, hiI, pivotI); 138| } else if (n * 16 <= (len - 1) * 7) { 61| pivotI = partitionAtPartitionOffMedian!(less, false)(loI, hiI, n, useSampling); | // Quality check 61| if (useSampling) | { 55| auto pivot = pivotI - loI; 55| if (pivot < n) | { 8| if (pivot * 4 < len) | { 5| useSampling = false; | } | } 47| else if ((len - pivot) * 8 < len * 3) | { 1| useSampling = false; | } | } 77| } else if (n * 16 >= (len - 1) * 9) { 61| pivotI = partitionAtPartitionOffMedian!(less, true)(loI, hiI, n, useSampling); | // Quality check 61| if (useSampling) | { 49| auto pivot = pivotI - loI; 49| if (pivot < n) | { 37| if (pivot * 8 < len * 3) | { 8| useSampling = false; | } | } 12| else if ((len - pivot) * 4 < len) | { 7| useSampling = false; | } | } | } else { 16| pivotI = partitionAtPartition!less(loI, hiI, n, useSampling); | // Quality check 16| if (useSampling) { 15| auto pivot = pivotI - loI; 28| if (pivot * 9 < len * 2 || pivot * 9 > len * 7) | { | // Failed - abort sampling going forward 2| useSampling = false; | } | } | } | 476| if (n < (pivotI - loI)) { 188| hiI = pivotI - 1; 288| } else if (n > (pivotI - loI)) { 220| n -= (pivotI - loI + 1); 220| loI = pivotI; 220| ++loI; | } else { 68| break; | } | } |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 2; 1| auto x = [3, 1, 5, 2, 0].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 22| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, 1, true); 1| assert(x[nth] == 2); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 4; 1| auto x = [3, 1, 5, 2, 0, 7].sliced(3, 2); 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 20| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); 1| assert(x[2, 0] == 5); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 1; 1| auto x = [0, 0, 4, 3, 3].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 17| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); 1| assert(x[nth] == 0); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 2; 1| auto x = [0, 0, 4, 3, 3].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 20| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); 1| assert(x[nth] == 3); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 3; 1| auto x = [0, 0, 4, 3, 3].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 12| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); 1| assert(x[nth] == 3); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 4; 1| auto x = [ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 46| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); 1| assert(x[nth] == 7); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 5; 1| auto x = [ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 64| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); 1| assert(x[nth] == 8); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | 1| size_t nth = 6; 1| auto x = [ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.elementCount - 1; 59| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); 1| assert(x[nth] == 10); |} | |// Check all partitionAt |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: slice; | | static immutable raw = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22]; | | static void fill(T)(T x) { 882| for (size_t i = 0; i < x.length; i++) { 420| x[i] = raw[i]; | } | } 1| auto x = slice!int(raw.length); 1| fill(x); 1| auto x_sort = x.dup; 1| x_sort = x_sort.sort; 1| size_t i = 0; 21| while (i < raw.length) { 20| auto frontI = x._iterator; 20| auto lastI = frontI + x.length - 1; 1615| partitionAtImpl!((a, b) => (a < b))(frontI, lastI, i, true); 20| assert(x[i] == x_sort[i]); 20| fill(x); 20| i++; | } |} | |private @trusted pure nothrow @nogc |Iterator partitionAtPartition(alias less, Iterator)( | ref Iterator frontI, | ref Iterator lastI, | size_t n, | bool useSampling) |{ 17| size_t len = lastI - frontI + 1; | 34| assert(len >= 9 && n < len, "partitionAtPartition: length must be longer than 9 and n must be less than r.length"); | 17| size_t ninth = len / 9; 17| size_t pivot = ninth / 2; | // Position subrange r[loI .. hiI] to have length equal to ninth and its upper | // median r[loI .. hiI][$ / 2] in exactly the same place as the upper median | // of the entire range r[$ / 2]. This is to improve behavior for searching | // the median in already sorted ranges. 17| auto loI = frontI; 17| loI += len / 2 - pivot; 17| auto hiI = loI; 17| hiI += ninth; | | // We have either one straggler on the left, one on the right, or none. 17| assert(loI - frontI <= lastI - hiI + 1 || lastI - hiI <= loI - frontI + 1, "partitionAtPartition: straggler check failed for loI, len, hiI"); 17| assert(loI - frontI >= ninth * 4, "partitionAtPartition: loI - frontI >= ninth * 4"); 17| assert((lastI + 1) - hiI >= ninth * 4, "partitionAtPartition: (lastI + 1) - hiI >= ninth * 4"); | | // Partition in groups of 3, and the mid tertile again in groups of 3 17| if (!useSampling) { 1| auto loI_ = loI; 1| loI_ -= ninth; 1| auto hiI_ = hiI; 1| hiI_ += ninth; 1| p3!(less, Iterator)(frontI, lastI, loI_, hiI_); | } 17| p3!(less, Iterator)(frontI, lastI, loI, hiI); | | // Get the median of medians of medians | // Map the full interval of n to the full interval of the ninth 17| pivot = (n * (ninth - 1)) / (len - 1); 17| if (hiI > loI) { 17| auto hiI_minus = hiI; 17| --hiI_minus; 17| partitionAtImpl!less(loI, hiI_minus, pivot, useSampling); | } | 17| auto pivotI = loI; 17| pivotI += pivot; | 17| return expandPartition!less(frontI, lastI, loI, pivotI, hiI); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; 1| auto x = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced; 1| auto x_sort = x.dup; 1| x_sort = x_sort.sort; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.length - 1; 1| size_t n = x.length / 2; 66| partitionAtPartition!((a, b) => (a < b))(frontI, lastI, n, true); 1| assert(x[n - 1] == x_sort[n - 1]); |} | |private @trusted pure nothrow @nogc |Iterator partitionAtPartitionOffMedian(alias less, bool leanRight, Iterator)( | ref Iterator frontI, | ref Iterator lastI, | size_t n, | bool useSampling) |{ 124| size_t len = lastI - frontI + 1; | 124| assert(len >= 12, "partitionAtPartitionOffMedian: len must be greater than 11"); 124| assert(n < len, "partitionAtPartitionOffMedian: n must be less than len"); 124| auto _4 = len / 4; 124| auto leftLimitI = frontI; | static if (leanRight) 62| leftLimitI += 2 * _4; | else 62| leftLimitI += _4; | // Partition in groups of 4, and the left quartile again in groups of 3 124| if (!useSampling) | { 18| auto leftLimit_plus_4 = leftLimitI; 18| leftLimit_plus_4 += _4; 18| p4!(less, leanRight)(frontI, lastI, leftLimitI, leftLimit_plus_4); | } 124| auto _12 = _4 / 3; 124| auto loI = leftLimitI; 124| loI += _12; 124| auto hiI = loI; 124| hiI += _12; 124| p3!less(frontI, lastI, loI, hiI); | | // Get the median of medians of medians | // Map the full interval of n to the full interval of the ninth 124| auto pivot = (n * (_12 - 1)) / (len - 1); 124| if (hiI > loI) { 124| auto hiI_minus = hiI; 124| --hiI_minus; 124| partitionAtImpl!less(loI, hiI_minus, pivot, useSampling); | } 124| auto pivotI = loI; 124| pivotI += pivot; 124| return expandPartition!less(frontI, lastI, loI, pivotI, hiI); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: equal; | 1| auto x = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.length - 1; 63| partitionAtPartitionOffMedian!((a, b) => (a < b), false)(frontI, lastI, 5, true); 1| assert(x.equal([6, 7, 8, 9, 5, 0, 2, 7, 9, 15, 10, 25, 11, 10, 13, 18, 17, 13, 25, 22])); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: equal; | 1| auto x = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.length - 1; 59| partitionAtPartitionOffMedian!((a, b) => (a < b), true)(frontI, lastI, 15, true); 1| assert(x.equal([6, 7, 8, 7, 5, 2, 9, 0, 9, 15, 25, 10, 11, 10, 13, 18, 17, 13, 25, 22])); |} | |private @trusted |void p3(alias less, Iterator)( | Iterator frontI, | Iterator lastI, | Iterator loI, | Iterator hiI) |{ 286| assert(loI <= hiI && hiI <= lastI, "p3: loI must be less than or equal to hiI and hiI must be less than or equal to lastI"); 143| immutable diffI = hiI - loI; 143| Iterator lo_loI; 143| Iterator hi_loI; 467| for (; loI < hiI; ++loI) | { 162| lo_loI = loI; 162| lo_loI -= diffI; 162| hi_loI = loI; 162| hi_loI += diffI; 162| assert(lo_loI >= frontI, "p3: lo_loI must be greater than or equal to frontI"); 162| assert(hi_loI <= lastI, "p3: hi_loI must be less than or equal to lastI"); 162| medianOf!less(lo_loI, loI, hi_loI); | } |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: equal; | 1| auto x = [3, 4, 0, 5, 2, 1].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.length - 1; 1| auto loI = frontI + 2; 1| auto hiI = frontI + 4; 10| p3!((a, b) => (a < b))(frontI, lastI, loI, hiI); 1| assert(x.equal([0, 1, 2, 4, 3, 5])); |} | |private @trusted |template p4(alias less, bool leanRight) |{ | void p4(Iterator)( | Iterator frontI, | Iterator lastI, | Iterator loI, | Iterator hiI) | { 40| assert(loI <= hiI && hiI <= lastI, "p4: loI must be less than or equal to hiI and hiI must be less than or equal to lastI"); | 20| immutable diffI = hiI - loI; 20| immutable diffI2 = diffI * 2; | 20| Iterator lo_loI; 20| Iterator hi_loI; | | static if (leanRight) 13| Iterator lo2_loI; | else 7| Iterator hi2_loI; | 168| for (; loI < hiI; ++loI) | { 74| lo_loI = loI - diffI; 74| hi_loI = loI + diffI; | 74| assert(lo_loI >= frontI, "p4: lo_loI must be greater than or equal to frontI"); 74| assert(hi_loI <= lastI, "p4: hi_loI must be less than or equal to lastI"); | | static if (leanRight) { 50| lo2_loI = loI - diffI2; 50| assert(lo2_loI >= frontI, "lo2_loI must be greater than or equal to frontI"); 50| medianOf!(less, leanRight)(lo2_loI, lo_loI, loI, hi_loI); | } else { 24| hi2_loI = loI + diffI2; 24| assert(hi2_loI <= lastI, "hi2_loI must be less than or equal to lastI"); 24| medianOf!(less, leanRight)(lo_loI, loI, hi_loI, hi2_loI); | } | } | } |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: equal; | 1| auto x = [3, 4, 0, 7, 2, 6, 5, 1, 4].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.length - 1; 1| auto loI = frontI + 3; 1| auto hiI = frontI + 5; 14| p4!((a, b) => (a < b), false)(frontI, lastI, loI, hiI); 1| assert(x.equal([3, 1, 0, 4, 2, 6, 4, 7, 5])); |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest { | import mir.ndslice.slice: sliced; | import mir.algorithm.iteration: equal; | 1| auto x = [3, 4, 0, 8, 2, 7, 5, 1, 4, 3].sliced; 1| auto frontI = x._iterator; 1| auto lastI = frontI + x.length - 1; 1| auto loI = frontI + 4; 1| auto hiI = frontI + 6; 14| p4!((a, b) => (a < b), true)(frontI, lastI, loI, hiI); 1| assert(x.equal([0, 4, 2, 1, 3, 7, 5, 8, 4, 3])); |} | |private @trusted |template expandPartition(alias less) |{ | Iterator expandPartition(Iterator)( | ref Iterator frontI, | ref Iterator lastI, | ref Iterator loI, | ref Iterator pivotI, | ref Iterator hiI) | { | import mir.algorithm.iteration: all; | 142| assert(frontI <= loI, "expandPartition: frontI must be less than or equal to loI"); 142| assert(loI <= pivotI, "expandPartition: loI must be less than or equal pivotI"); 142| assert(pivotI < hiI, "expandPartition: pivotI must be less than hiI"); 142| assert(hiI <= lastI, "expandPartition: hiI must be less than or equal to lastI"); | 855| foreach(x; loI .. (pivotI + 1)) 143| assert(!less(*pivotI, *x), "expandPartition: loI .. (pivotI + 1) failed test"); 474| foreach(x; (pivotI + 1) .. hiI) 16| assert(!less(*x, *pivotI), "expandPartition: (pivotI + 1) .. hiI failed test"); | | import mir.utility: swapStars; | import mir.algorithm.iteration: all; | // We work with closed intervals! 142| --hiI; | 142| auto leftI = frontI; 142| auto rightI = lastI; 191| loop: for (;; ++leftI, --rightI) | { 596| for (;; ++leftI) | { 1015| if (leftI == loI) break loop; 1090| if (!less(*leftI, *pivotI)) break; | } 738| for (;; --rightI) | { 1041| if (rightI == hiI) break loop; 1120| if (!less(*pivotI, *rightI)) break; | } 191| swapStars(leftI, rightI); | } | 855| foreach(x; loI .. (pivotI + 1)) 143| assert(!less(*pivotI, *x), "expandPartition: loI .. (pivotI + 1) failed less than test"); 474| foreach(x; (pivotI + 1) .. (hiI + 1)) 16| assert(!less(*x, *pivotI), "expandPartition: (pivotI + 1) .. (hiI + 1) failed less than test"); 2787| foreach(x; frontI .. leftI) 787| assert(!less(*pivotI, *x), "expandPartition: frontI .. leftI failed less than test"); 3213| foreach(x; (rightI + 1) .. (lastI + 1)) 929| assert(!less(*x, *pivotI), "expandPartition: (rightI + 1) .. (lastI + 1) failed less than test"); | 142| auto oldPivotI = pivotI; | 142| if (leftI < loI) | { | // First loop: spend r[loI .. pivot] 56| for (; loI < pivotI; ++leftI) | { 0000000| if (leftI == loI) goto done; 0000000| if (!less(*oldPivotI, *leftI)) continue; 0000000| --pivotI; 0000000| assert(!less(*oldPivotI, *pivotI), "expandPartition: less check failed"); 0000000| swapStars(leftI, pivotI); | } | // Second loop: make leftI and pivot meet 169| for (;; ++leftI) | { 257| if (leftI == pivotI) goto done; 284| if (!less(*oldPivotI, *leftI)) continue; | for (;;) | { 249| if (leftI == pivotI) goto done; 201| --pivotI; 201| if (less(*pivotI, *oldPivotI)) | { 78| swapStars(leftI, pivotI); 78| break; | } | } | } | } | | // First loop: spend r[lo .. pivot] 142| for (; hiI != pivotI; --rightI) | { 34| if (rightI == hiI) goto done; 54| if (!less(*rightI, *oldPivotI)) continue; 2| ++pivotI; 2| assert(!less(*pivotI, *oldPivotI), "expandPartition: less check failed"); 2| swapStars(rightI, pivotI); | } | // Second loop: make leftI and pivotI meet 713| for (; rightI > pivotI; --rightI) | { 520| if (!less(*rightI, *oldPivotI)) continue; 190| while (rightI > pivotI) | { 155| ++pivotI; 155| if (less(*oldPivotI, *pivotI)) | { 75| swapStars(rightI, pivotI); 75| break; | } | } | } | | done: 142| swapStars(oldPivotI, pivotI); | | 4194| foreach(x; frontI .. (pivotI + 1)) 1256| assert(!less(*pivotI, *x), "expandPartition: frontI .. (pivotI + 1) failed test"); 4782| foreach(x; (pivotI + 1) .. (lastI + 1)) 1452| assert(!less(*x, *pivotI), "expandPartition: (pivotI + 1) .. (lastI + 1) failed test"); 142| return pivotI; | } |} | |version(mir_ndslice_test) |@trusted pure nothrow |unittest |{ | import mir.ndslice.slice: sliced; | 1| auto a = [ 10, 5, 3, 4, 8, 11, 13, 3, 9, 4, 10 ].sliced; 1| auto frontI = a._iterator; 1| auto lastI = frontI + a.length - 1; 1| auto loI = frontI + 4; 1| auto pivotI = frontI + 5; 1| auto hiI = frontI + 6; 30| assert(expandPartition!((a, b) => a < b)(frontI, lastI, loI, pivotI, hiI) == (frontI + 9)); |} | |version(mir_ndslice_test) |unittest |{ | import std.random; | import mir.ndslice.sorting: sort; | | static struct StructA | { | double val0; | double val1; | double val2; | } | | static struct StructB | { | ulong productId; | StructA strA; | } | | auto createStructBArray(uint nbTrades) | { 1| auto rnd = Random(42); | 1| auto p = StructA(0,0,0); | 1| StructB[] ret; 30003| foreach(i;0..nbTrades) | { 10000| ret ~= StructB(uniform(0, nbTrades, rnd), p); | } | 1| return ret; | } | 156226| auto arrayB = createStructBArray(10000).sort!((a,b) => a.productId slice._lengths[i]) 1| lengths[0] = slice._lengths[i]; | foreach (i; Iota!(1, Ret.N)) | lengths[i] = slice._lengths[i + N - 1]; 62| auto rstrides = slice.strides; 62| strides[0] = rstrides[0]; | foreach (i; Iota!(1, N)) 64| strides[0] += rstrides[i]; | foreach (i; Iota!(1, Ret.S)) | strides[i] = rstrides[i + N - 1]; 62| return Ret(lengths, strides, slice._iterator); | } |} | |/// Matrix, main diagonal |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | // ------- | // | 0 1 2 | | // | 3 4 5 | | // ------- | //-> | // | 0 4 | | static immutable d = [0, 4]; 1| assert(iota(2, 3).diagonal == d); |} | |/// Non-square matrix |@safe pure nothrow version(mir_ndslice_test) unittest |{ | // ------- | // | 0 1 | | // | 2 3 | | // | 4 5 | | // ------- | //-> | // | 0 3 | | 1| assert(iota(3, 2).diagonal == iota([2], 0, 3)); |} | |/// Loop through diagonal |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation; | 1| auto slice = slice!int(3, 3); 1| int i; 11| foreach (ref e; slice.diagonal) 3| e = ++i; 1| assert(slice == [ | [1, 0, 0], | [0, 2, 0], | [0, 0, 3]]); |} | |/// Matrix, subdiagonal |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | // ------- | // | 0 1 2 | | // | 3 4 5 | | // ------- | //-> | // | 1 5 | | static immutable d = [1, 5]; 1| auto a = iota(2, 3).canonical; 1| a.popFront!1; 1| assert(a.diagonal == d); |} | |/// 3D, main diagonal |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | // ----------- | // | 0 1 2 | | // | 3 4 5 | | // - - - - - - | // | 6 7 8 | | // | 9 10 11 | | // ----------- | //-> | // | 0 10 | | static immutable d = [0, 10]; 1| assert(iota(2, 2, 3).diagonal == d); |} | |/// 3D, subdiagonal |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | // ----------- | // | 0 1 2 | | // | 3 4 5 | | // - - - - - - | // | 6 7 8 | | // | 9 10 11 | | // ----------- | //-> | // | 1 11 | | static immutable d = [1, 11]; 1| auto a = iota(2, 2, 3).canonical; 1| a.popFront!2; 1| assert(a.diagonal == d); |} | |/// 3D, diagonal plain |@nogc @safe pure nothrow |version(mir_ndslice_test) unittest |{ | // ----------- | // | 0 1 2 | | // | 3 4 5 | | // | 6 7 8 | | // - - - - - - | // | 9 10 11 | | // | 12 13 14 | | // | 15 16 17 | | // - - - - - - | // | 18 20 21 | | // | 22 23 24 | | // | 24 25 26 | | // ----------- | //-> | // ----------- | // | 0 4 8 | | // | 9 13 17 | | // | 18 23 26 | | // ----------- | | static immutable d = | [[ 0, 4, 8], | [ 9, 13, 17], | [18, 22, 26]]; | 1| auto slice = iota(3, 3, 3) | .pack!2 | .evertPack | .diagonal | .evertPack; | 1| assert(slice == d); |} | |/++ |Returns a 1-dimensional slice over the main antidiagonal of an 2D-dimensional slice. |`antidiagonal` can be generalized with other selectors such as |$(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice). | |It runs from the top right corner to the bottom left corner. | |Pseudo_code: |------ |auto antidiagonal = slice.dropToHypercube.reversed!1.diagonal; |------ | |Params: | slice = input slice |Returns: | 1-dimensional slice composed of antidiagonal elements. |See_also: $(LREF diagonal) |+/ |Slice!(Iterator, 1, Universal) | antidiagonal | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (N == 2) |{ | import mir.ndslice.dynamic : dropToHypercube, reversed; 25| return slice.dropToHypercube.reversed!1.diagonal; |} | |/// |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | // ----- | // | 0 1 | | // | 2 3 | | // ----- | //-> | // | 1 2 | | static immutable c = [1, 2]; 1| assert(iota(2, 2).antidiagonal == c); |} | |/// |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | // ------- | // | 0 1 2 | | // | 3 4 5 | | // ------- | //-> | // | 1 3 | | static immutable d = [1, 3]; 1| assert(iota(2, 3).antidiagonal == d); |} | |/++ |Returns an n-dimensional slice of n-dimensional non-overlapping blocks. |`blocks` can be generalized with other selectors. |For example, `blocks` in combination with $(LREF diagonal) can be used to get a slice of diagonal blocks. |For overlapped blocks, combine $(LREF windows) with $(SUBREF dynamic, strided). | |Params: | N = dimension count | slice = slice to be split into blocks | rlengths_ = dimensions of block, residual blocks are ignored |Returns: | packed `N`-dimensional slice composed of `N`-dimensional slices | |See_also: $(SUBREF chunks, ._chunks) |+/ |Slice!(SliceIterator!(Iterator, N, N == 1 ? Universal : min(kind, Canonical)), N, Universal) | blocks | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice, size_t[N] rlengths_...) |in |{ 161| foreach (i, length; rlengths_) 29| assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" | ~ tailErrorMessage!()); |} |do |{ 15| size_t[N] lengths; 15| size_t[N] rlengths = rlengths_; 15| sizediff_t[N] strides; | foreach (dimension; Iota!N) 29| lengths[dimension] = slice._lengths[dimension] / rlengths[dimension]; 15| auto rstrides = slice.strides; | foreach (i; Iota!N) | { 29| strides[i] = rstrides[i]; 29| if (lengths[i]) //do not remove `if (...)` 29| strides[i] *= rlengths[i]; | } 15| return typeof(return)( | lengths, | strides, | typeof(return).Iterator( | rlengths, | rstrides[0 .. typeof(return).DeepElement.S], | slice._iterator)); |} | |/// |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation; 1| auto slice = slice!int(5, 8); 1| auto blocks = slice.blocks(2, 3); 1| int i; 8| foreach (blocksRaw; blocks) 16| foreach (block; blocksRaw) 4| block[] = ++i; | 1| assert(blocks == | [[[[1, 1, 1], [1, 1, 1]], | [[2, 2, 2], [2, 2, 2]]], | [[[3, 3, 3], [3, 3, 3]], | [[4, 4, 4], [4, 4, 4]]]]); | 1| assert( slice == | [[1, 1, 1, 2, 2, 2, 0, 0], | [1, 1, 1, 2, 2, 2, 0, 0], | | [3, 3, 3, 4, 4, 4, 0, 0], | [3, 3, 3, 4, 4, 4, 0, 0], | | [0, 0, 0, 0, 0, 0, 0, 0]]); |} | |/// Diagonal blocks |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation; 1| auto slice = slice!int(5, 8); 1| auto blocks = slice.blocks(2, 3); 1| auto diagonalBlocks = blocks.diagonal.unpack; | 1| diagonalBlocks[0][] = 1; 1| diagonalBlocks[1][] = 2; | 1| assert(diagonalBlocks == | [[[1, 1, 1], [1, 1, 1]], | [[2, 2, 2], [2, 2, 2]]]); | 1| assert(blocks == | [[[[1, 1, 1], [1, 1, 1]], | [[0, 0, 0], [0, 0, 0]]], | [[[0, 0, 0], [0, 0, 0]], | [[2, 2, 2], [2, 2, 2]]]]); | 1| assert(slice == | [[1, 1, 1, 0, 0, 0, 0, 0], | [1, 1, 1, 0, 0, 0, 0, 0], | | [0, 0, 0, 2, 2, 2, 0, 0], | [0, 0, 0, 2, 2, 2, 0, 0], | | [0, 0, 0, 0, 0, 0, 0, 0]]); |} | |/// Matrix divided into vertical blocks |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.slice; 1| auto slice = slice!int(5, 13); 1| auto blocks = slice | .pack!1 | .evertPack | .blocks(3) | .unpack; | 1| int i; 14| foreach (block; blocks) 4| block[] = ++i; | 1| assert(slice == | [[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], | [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], | [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], | [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], | [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0]]); |} | |/++ |Returns an n-dimensional slice of n-dimensional overlapping windows. |`windows` can be generalized with other selectors. |For example, `windows` in combination with $(LREF diagonal) can be used to get a multi-diagonal slice. | |Params: | N = dimension count | slice = slice to be iterated | rlengths = dimensions of windows |Returns: | packed `N`-dimensional slice composed of `N`-dimensional slices |+/ |Slice!(SliceIterator!(Iterator, N, N == 1 ? kind : min(kind, Canonical)), N, Universal) | windows | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice, size_t[N] rlengths...) |in |{ 84| foreach (i, length; rlengths) 15| assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" | ~ tailErrorMessage!()); |} |do |{ 8| size_t[N] rls = rlengths; 8| size_t[N] lengths; | foreach (dimension; Iota!N) 15| lengths[dimension] = slice._lengths[dimension] >= rls[dimension] ? | slice._lengths[dimension] - rls[dimension] + 1 : 0; 8| auto rstrides = slice.strides; | static if (typeof(return).DeepElement.S) 7| return typeof(return)( | lengths, | rstrides, | typeof(return).Iterator( | rls, | rstrides[0 .. typeof(return).DeepElement.S], | slice._iterator)); | else 1| return typeof(return)( | lengths, | rstrides, | typeof(return).Iterator( | rls, | slice._iterator)); |} | |/// |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.slice; 1| auto slice = slice!int(5, 8); 1| auto windows = slice.windows(2, 3); | 1| int i; 14| foreach (windowsRaw; windows) 80| foreach (window; windowsRaw) 24| ++window[]; | 1| assert(slice == | [[1, 2, 3, 3, 3, 3, 2, 1], | | [2, 4, 6, 6, 6, 6, 4, 2], | [2, 4, 6, 6, 6, 6, 4, 2], | [2, 4, 6, 6, 6, 6, 4, 2], | | [1, 2, 3, 3, 3, 3, 2, 1]]); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.slice; 1| auto slice = slice!int(5, 8); 1| auto windows = slice.windows(2, 3); 1| windows[1, 2][] = 1; 1| windows[1, 2][0, 1] += 1; 1| windows.unpack[1, 2, 0, 1] += 1; | 1| assert(slice == | [[0, 0, 0, 0, 0, 0, 0, 0], | | [0, 0, 1, 3, 1, 0, 0, 0], | [0, 0, 1, 1, 1, 0, 0, 0], | | [0, 0, 0, 0, 0, 0, 0, 0], | [0, 0, 0, 0, 0, 0, 0, 0]]); |} | |/// Multi-diagonal matrix |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.slice; 1| auto slice = slice!int(8, 8); 1| auto windows = slice.windows(3, 3); | 1| auto multidiagonal = windows | .diagonal | .unpack; 20| foreach (window; multidiagonal) 6| window[] += 1; | 1| assert(slice == | [[ 1, 1, 1, 0, 0, 0, 0, 0], | [ 1, 2, 2, 1, 0, 0, 0, 0], | [ 1, 2, 3, 2, 1, 0, 0, 0], | [0, 1, 2, 3, 2, 1, 0, 0], | [0, 0, 1, 2, 3, 2, 1, 0], | [0, 0, 0, 1, 2, 3, 2, 1], | [0, 0, 0, 0, 1, 2, 2, 1], | [0, 0, 0, 0, 0, 1, 1, 1]]); |} | |/// Sliding window over matrix columns |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.slice; 1| auto slice = slice!int(5, 8); 1| auto windows = slice | .pack!1 | .evertPack | .windows(3) | .unpack; | 20| foreach (window; windows) 6| window[] += 1; | 1| assert(slice == | [[1, 2, 3, 3, 3, 3, 2, 1], | [1, 2, 3, 3, 3, 3, 2, 1], | [1, 2, 3, 3, 3, 3, 2, 1], | [1, 2, 3, 3, 3, 3, 2, 1], | [1, 2, 3, 3, 3, 3, 2, 1]]); |} | |/// Overlapping blocks using windows |@safe pure nothrow version(mir_ndslice_test) unittest |{ | // ---------------- | // | 0 1 2 3 4 | | // | 5 6 7 8 9 | | // | 10 11 12 13 14 | | // | 15 16 17 18 19 | | // | 20 21 22 23 24 | | // ---------------- | //-> | // --------------------- | // | 0 1 2 | 2 3 4 | | // | 5 6 7 | 7 8 9 | | // | 10 11 12 | 12 13 14 | | // | - - - - - - - - - - | | // | 10 11 13 | 12 13 14 | | // | 15 16 17 | 17 18 19 | | // | 20 21 22 | 22 23 24 | | // --------------------- | | import mir.ndslice.slice; | import mir.ndslice.dynamic : strided; | 1| auto overlappingBlocks = iota(5, 5) | .windows(3, 3) | .universal | .strided!(0, 1)(2, 2); | 1| assert(overlappingBlocks == | [[[[ 0, 1, 2], [ 5, 6, 7], [10, 11, 12]], | [[ 2, 3, 4], [ 7, 8, 9], [12, 13, 14]]], | [[[10, 11, 12], [15, 16, 17], [20, 21, 22]], | [[12, 13, 14], [17, 18, 19], [22, 23, 24]]]]); |} | |version(mir_ndslice_test) unittest |{ 1| auto w = iota(9, 9).windows(3, 3); 1| assert(w.front == w[0]); |} | |/++ |Error codes for $(LREF reshape). |+/ |enum ReshapeError |{ | /// No error | none, | /// Slice should be not empty | empty, | /// Total element count should be the same | total, | /// Structure is incompatible with new shape | incompatible, |} | |/++ |Returns a new slice for the same data with different dimensions. | |Params: | slice = slice to be reshaped | rlengths = list of new dimensions. One of the lengths can be set to `-1`. | In this case, the corresponding dimension is inferable. | err = $(LREF ReshapeError) code |Returns: | reshaped slice |+/ |Slice!(Iterator, M, kind) reshape | (Iterator, size_t N, SliceKind kind, size_t M) | (Slice!(Iterator, N, kind) slice, ptrdiff_t[M] rlengths, ref int err) |{ | static if (kind == Canonical) | { | auto r = slice.universal.reshape(rlengths, err); | assert(err || r._strides[$-1] == 1); | r._strides[$-1] = 1; | return r.assumeCanonical; | } | else | { | alias Ret = typeof(return); 8| auto structure = Ret._Structure.init; | alias lengths = structure[0]; | foreach (i; Iota!M) 19| lengths[i] = rlengths[i]; | | /// Code size optimization 8| immutable size_t eco = slice.elementCount; 8| size_t ecn = lengths[0 .. rlengths.length].iota.elementCount; 8| if (eco == 0) | { 1| err = ReshapeError.empty; 1| goto R; | } | foreach (i; Iota!M) 13| if (lengths[i] == -1) | { 4| ecn = -ecn; 4| lengths[i] = eco / ecn; 4| ecn *= lengths[i]; 4| break; | } 7| if (eco != ecn) | { 1| err = ReshapeError.total; 1| goto R; | } | static if (kind == Universal) | { 58| for (size_t oi, ni, oj, nj; oi < N && ni < M; oi = oj, ni = nj) | { 10| size_t op = slice._lengths[oj++]; 10| size_t np = lengths[nj++]; | | for (;;) | { 14| if (op < np) 6| op *= slice._lengths[oj++]; 14| if (op > np) 6| np *= lengths[nj++]; 14| if (op == np) 10| break; | } 26| while (oj < N && slice._lengths[oj] == 1) oj++; 17| while (nj < M && lengths[nj] == 1) nj++; | 48| for (size_t l = oi, r = oi + 1; r < oj; r++) 10| if (slice._lengths[r] != 1) | { 5| if (slice._strides[l] != slice._lengths[r] * slice._strides[r]) | { 1| err = ReshapeError.incompatible; 1| goto R; | } 4| l = r; | } 9| assert((oi == N) == (ni == M)); | 9| structure[1][nj - 1] = slice._strides[oj - 1]; 39| foreach_reverse (i; ni .. nj - 1) 6| structure[1][i] = lengths[i + 1] * structure[1][i + 1]; | } | } | foreach (i; Iota!(M, Ret.N)) | lengths[i] = slice._lengths[i + N - M]; | static if (M < Ret.S) | foreach (i; Iota!(M, Ret.S)) | structure[1][i] = slice._strides[i + N - M]; 5| err = 0; 5| return Ret(structure, slice._iterator); | R: 3| return Ret(structure, slice._iterator.init); | } |} | |/// |@safe nothrow pure |version(mir_ndslice_test) unittest |{ | import mir.ndslice.dynamic : allReversed; 1| int err; 1| auto slice = iota(3, 4) | .universal | .allReversed | .reshape([-1, 3], err); 1| assert(err == 0); 1| assert(slice == | [[11, 10, 9], | [ 8, 7, 6], | [ 5, 4, 3], | [ 2, 1, 0]]); |} | |/// Reshaping with memory allocation |@safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: slice; | import mir.ndslice.dynamic : reversed; | | auto reshape2(S, size_t M)(S sl, ptrdiff_t[M] lengths) | { 1| int err; | // Tries to reshape without allocation 1| auto ret = sl.reshape(lengths, err); 1| if (!err) 0000000| return ret; 1| if (err == ReshapeError.incompatible) | // allocates, flattens, reshapes with `sliced`, converts to universal kind 1| return sl.slice.flattened.sliced(cast(size_t[M])lengths).universal; 0000000| throw new Exception("total elements count is different or equals to zero"); | } | 1| auto sl = iota!int(3, 4) | .slice | .universal | .reversed!0; | 1| assert(reshape2(sl, [4, 3]) == | [[ 8, 9, 10], | [11, 4, 5], | [ 6, 7, 0], | [ 1, 2, 3]]); |} | |nothrow @safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.dynamic : allReversed; 1| auto slice = iota(1, 1, 3, 2, 1, 2, 1).universal.allReversed; 1| int err; 1| assert(slice.reshape([1, -1, 1, 1, 3, 1], err) == | [[[[[[11], [10], [9]]]], | [[[[ 8], [ 7], [6]]]], | [[[[ 5], [ 4], [3]]]], | [[[[ 2], [ 1], [0]]]]]]); 1| assert(err == 0); |} | |// Issue 15919 |nothrow @nogc @safe pure |version(mir_ndslice_test) unittest |{ 1| int err; 1| assert(iota(3, 4, 5, 6, 7).pack!2.reshape([4, 3, 5], err)[0, 0, 0].shape == cast(size_t[2])[6, 7]); 1| assert(err == 0); |} | |nothrow @nogc @safe pure version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | 1| int err; 1| auto e = iota(1); | // resize to the wrong dimension 1| auto s = e.reshape([2], err); 1| assert(err == ReshapeError.total); 1| e.popFront; | // test with an empty slice 1| e.reshape([1], err); 1| assert(err == ReshapeError.empty); |} | |nothrow @nogc @safe pure |version(mir_ndslice_test) unittest |{ 1| auto pElements = iota(3, 4, 5, 6, 7) | .pack!2 | .flattened; 1| assert(pElements[0][0] == iota(7)); 1| assert(pElements[$-1][$-1] == iota([7], 2513)); |} | |/++ |A contiguous 1-dimensional slice of all elements of a slice. |`flattened` iterates existing data. |The order of elements is preserved. | |`flattened` can be generalized with other selectors. | |Params: | slice = slice to be iterated |Returns: | contiguous 1-dimensional slice of elements of the `slice` |+/ |Slice!(FlattenedIterator!(Iterator, N, kind)) | flattened | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (N != 1 && kind != Contiguous) |{ | import core.lifetime: move; 40| size_t[typeof(return).N] lengths; 40| sizediff_t[typeof(return)._iterator._indices.length] indices; 40| lengths[0] = slice.elementCount; 40| return typeof(return)(lengths, FlattenedIterator!(Iterator, N, kind)(indices, slice.move)); |} | |/// ditto |Slice!Iterator | flattened | (Iterator, size_t N) | (Slice!(Iterator, N) slice) |{ | static if (N == 1) | { 419| return slice; | } | else | { | import core.lifetime: move; 1746| size_t[typeof(return).N] lengths; 1746| lengths[0] = slice.elementCount; 1746| return typeof(return)(lengths, slice._iterator.move); | } |} | |/// ditto |Slice!(StrideIterator!Iterator) | flattened | (Iterator) | (Slice!(Iterator, 1, Universal) slice) |{ | import core.lifetime: move; 182| return slice.move.hideStride; |} | |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; 1| auto sl1 = iota(2, 3).slice.universal.pack!1.flattened; 1| auto sl2 = iota(2, 3).slice.canonical.pack!1.flattened; 1| auto sl3 = iota(2, 3).slice.pack!1.flattened; |} | |/// Regular slice |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ 1| assert(iota(4, 5).flattened == iota(20)); 1| assert(iota(4, 5).canonical.flattened == iota(20)); 1| assert(iota(4, 5).universal.flattened == iota(20)); |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ 1| assert(iota(4).flattened == iota(4)); 1| assert(iota(4).canonical.flattened == iota(4)); 1| assert(iota(4).universal.flattened == iota(4)); |} | |/// Packed slice |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.dynamic; 1| assert(iota(3, 4, 5, 6, 7).pack!2.flattened[1] == iota([6, 7], 6 * 7)); |} | |/// Properties |@safe pure nothrow version(mir_ndslice_test) unittest |{ 1| auto elems = iota(3, 4).universal.flattened; | 1| elems.popFrontExactly(2); 1| assert(elems.front == 2); | /// `_index` is available only for canonical and universal ndslices. 1| assert(elems._iterator._indices == [0, 2]); | 1| elems.popBackExactly(2); 1| assert(elems.back == 9); 1| assert(elems.length == 8); |} | |/// Index property |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; 1| auto slice = new long[20].sliced(5, 4); | 42| for (auto elems = slice.universal.flattened; !elems.empty; elems.popFront) | { 20| ptrdiff_t[2] index = elems._iterator._indices; 20| elems.front = index[0] * 10 + index[1] * 3; | } 1| assert(slice == | [[ 0, 3, 6, 9], | [10, 13, 16, 19], | [20, 23, 26, 29], | [30, 33, 36, 39], | [40, 43, 46, 49]]); |} | |@safe pure nothrow version(mir_ndslice_test) unittest |{ 1| auto elems = iota(3, 4).universal.flattened; 1| assert(elems.front == 0); 1| assert(elems.save[1] == 1); |} | |/++ |Random access and slicing |+/ |nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.slice: sliced; | 1| auto elems = iota(4, 5).slice.flattened; | 1| elems = elems[11 .. $ - 2]; | 1| assert(elems.length == 7); 1| assert(elems.front == 11); 1| assert(elems.back == 17); | 24| foreach (i; 0 .. 7) 7| assert(elems[i] == i + 11); | | // assign an element 1| elems[2 .. 6] = -1; 1| assert(elems[2 .. 6] == repeat(-1, 4)); | | // assign an array | static ar = [-1, -2, -3, -4]; 1| elems[2 .. 6] = ar; 1| assert(elems[2 .. 6] == ar); | | // assign a slice 1| ar[] *= 2; 1| auto sl = ar.sliced(ar.length); 1| elems[2 .. 6] = sl; 1| assert(elems[2 .. 6] == sl); |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.dynamic : allReversed; | 1| auto slice = iota(3, 4, 5); | 182| foreach (ref e; slice.universal.flattened.retro) | { | //... | } | 182| foreach_reverse (ref e; slice.universal.flattened) | { | //... | } | 182| foreach (ref e; slice.universal.allReversed.flattened) | { | //... | } |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import std.range.primitives : isRandomAccessRange, hasSlicing; 1| auto elems = iota(4, 5).flattened; | static assert(isRandomAccessRange!(typeof(elems))); | static assert(hasSlicing!(typeof(elems))); |} | |// Checks strides |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.dynamic; | import std.range.primitives : isRandomAccessRange; 1| auto elems = iota(4, 5).universal.everted.flattened; | static assert(isRandomAccessRange!(typeof(elems))); | 1| elems = elems[11 .. $ - 2]; 1| auto elems2 = elems; 24| foreach (i; 0 .. 7) | { 7| assert(elems[i] == elems2.front); 7| elems2.popFront; | } |} | |@safe @nogc pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.dynamic; | import std.range.primitives : isRandomAccessRange, hasLength; | 1| auto range = (3 * 4 * 5 * 6 * 7).iota; 1| auto slice0 = range.sliced(3, 4, 5, 6, 7).universal; 1| auto slice1 = slice0.transposed!(2, 1).pack!2; 1| auto elems0 = slice0.flattened; 1| auto elems1 = slice1.flattened; | | foreach (S; AliasSeq!(typeof(elems0), typeof(elems1))) | { | static assert(isRandomAccessRange!S); | static assert(hasLength!S); | } | 1| assert(elems0.length == slice0.elementCount); 1| assert(elems1.length == 5 * 4 * 3); | 1| auto elems2 = elems1; 17| foreach (q; slice1) 70| foreach (w; q) 220| foreach (e; w) | { 60| assert(!elems2.empty); 60| assert(e == elems2.front); 60| elems2.popFront; | } 1| assert(elems2.empty); | 1| elems0.popFront(); 1| elems0.popFrontExactly(slice0.elementCount - 14); 1| assert(elems0.length == 13); 1| assert(elems0 == range[slice0.elementCount - 13 .. slice0.elementCount]); | 41| foreach (elem; elems0) {} |} | |// Issue 15549 |version(mir_ndslice_test) unittest |{ | import std.range.primitives; | import mir.ndslice.allocation; | alias A = typeof(iota(1, 2, 3, 4).pack!1); | static assert(isRandomAccessRange!A); | static assert(hasLength!A); | static assert(hasSlicing!A); | alias B = typeof(slice!int(1, 2, 3, 4).pack!3); | static assert(isRandomAccessRange!B); | static assert(hasLength!B); | static assert(hasSlicing!B); |} | |// Issue 16010 |version(mir_ndslice_test) unittest |{ 1| auto s = iota(3, 4).flattened; 39| foreach (_; 0 .. s.length) 12| s = s[1 .. $]; |} | |/++ |Returns a slice, the elements of which are equal to the initial multidimensional index value. |For a flattened (contiguous) index, see $(LREF iota). | |Params: | N = dimension count | lengths = list of dimension lengths |Returns: | `N`-dimensional slice composed of indices |See_also: $(LREF iota) |+/ |Slice!(FieldIterator!(ndIotaField!N), N) | ndiota | (size_t N) | (size_t[N] lengths...) | if (N) |{ 29| return FieldIterator!(ndIotaField!N)(0, ndIotaField!N(lengths[1 .. $])).sliced(lengths); |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| auto slice = ndiota(2, 3); | static immutable array = | [[[0, 0], [0, 1], [0, 2]], | [[1, 0], [1, 1], [1, 2]]]; | 1| assert(slice == array); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ 1| auto im = ndiota(7, 9); | 1| assert(im[2, 1] == [2, 1]); | | //slicing works correctly 1| auto cm = im[1 .. $, 4 .. $]; 1| assert(cm[2, 1] == [3, 5]); |} | |version(mir_ndslice_test) unittest |{ 1| auto r = ndiota(1); 1| auto d = r.front; 1| r.popFront; | import std.range.primitives; | static assert(isRandomAccessRange!(typeof(r))); |} | |/++ |Evenly spaced numbers over a specified interval. | |Params: | T = floating point or complex numbers type | lengths = list of dimension lengths. Each length must be greater then 1. | intervals = list of [start, end] pairs. |Returns: | `n`-dimensional grid of evenly spaced numbers over specified intervals. |See_also: $(LREF) |+/ |auto linspace(T, size_t N)(size_t[N] lengths, T[2][N] intervals...) | if (N && (isFloatingPoint!T || isComplex!T)) |{ 16| Repeat!(N, LinspaceField!T) fields; | foreach(i; Iota!N) | { 18| assert(lengths[i] > 1, "linspace: all lengths must be greater then 1."); 18| fields[i] = LinspaceField!T(lengths[i], intervals[i][0], intervals[i][1]); | } | static if (N == 1) 14| return slicedField(fields); | else 2| return cartesian(fields); |} | |// example from readme |version(mir_ndslice_test) unittest |{ | import mir.ndslice; | | enum fmt = "%(%(%.2f %)\n%)\n"; | 1| auto a = magic(5).as!float; | // writefln(fmt, a); | 1| auto b = linspace!float([5, 5], [1f, 2f], [0f, 1f]).map!"a * a + b"; | // writefln(fmt, b); | 1| auto c = slice!float(5, 5); 1| c[] = transposed(a + b / 2); |} | |/// 1D |@safe pure nothrow |version(mir_ndslice_test) unittest |{ 1| auto s = linspace!double([5], [1.0, 2.0]); 1| assert(s == [1.0, 1.25, 1.5, 1.75, 2.0]); | | // reverse order 1| assert(linspace!double([5], [2.0, 1.0]) == s.retro); | | // remove endpoint 1| s.popBack; 1| assert(s == [1.0, 1.25, 1.5, 1.75]); |} | |/// 2D |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.functional: tuple; | 1| auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]); | 1| assert(s == [ | [tuple(1.00, 0.00), tuple(1.00, 0.5), tuple(1.00, 1.0)], | [tuple(1.25, 0.00), tuple(1.25, 0.5), tuple(1.25, 1.0)], | [tuple(1.50, 0.00), tuple(1.50, 0.5), tuple(1.50, 1.0)], | [tuple(1.75, 0.00), tuple(1.75, 0.5), tuple(1.75, 1.0)], | [tuple(2.00, 0.00), tuple(2.00, 0.5), tuple(2.00, 1.0)], | ]); | 1| assert(s.map!"a * b" == [ | [0.0, 0.500, 1.00], | [0.0, 0.625, 1.25], | [0.0, 0.750, 1.50], | [0.0, 0.875, 1.75], | [0.0, 1.000, 2.00], | ]); |} | |/// Complex numbers |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.complex; | alias C = Complex!double; 1| auto s = linspace!C([3], [C(1.0, 0), C(2.0, 4)]); 1| assert(s == [C(1.0, 0), C(1.5, 2), C(2.0, 4)]); |} | |/++ |Returns a slice with identical elements. |`RepeatSlice` stores only single value. |Params: | lengths = list of dimension lengths |Returns: | `n`-dimensional slice composed of identical values, where `n` is dimension count. |+/ |Slice!(FieldIterator!(RepeatField!T), M, Universal) | repeat(T, size_t M)(T value, size_t[M] lengths...) @trusted | if (M && !isSlice!T) |{ 72| size_t[M] ls = lengths; 72| return typeof(return)( | ls, | sizediff_t[M].init, | typeof(return).Iterator(0, RepeatField!T(cast(RepeatField!T.UT) value))); |} | |/// ditto |Slice!(SliceIterator!(Iterator, N, kind), M, Universal) | repeat | (SliceKind kind, size_t N, Iterator, size_t M) | (Slice!(Iterator, N, kind) slice, size_t[M] lengths...) | if (M) |{ | import core.lifetime: move; 7| size_t[M] ls = lengths; 7| return typeof(return)( | ls, | sizediff_t[M].init, | typeof(return).Iterator( | slice._structure, | move(slice._iterator))); |} | |/// |@safe pure nothrow |version(mir_ndslice_test) unittest |{ 1| auto sl = iota(3).repeat(4); 1| assert(sl == [[0, 1, 2], | [0, 1, 2], | [0, 1, 2], | [0, 1, 2]]); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.dynamic : transposed; | 1| auto sl = iota(3) | .repeat(4) | .unpack | .universal | .transposed; | 1| assert(sl == [[0, 0, 0, 0], | [1, 1, 1, 1], | [2, 2, 2, 2]]); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation; | 1| auto sl = iota([3], 6).slice; 1| auto slC = sl.repeat(2, 3); 1| sl[1] = 4; 1| assert(slC == [[[6, 4, 8], | [6, 4, 8], | [6, 4, 8]], | [[6, 4, 8], | [6, 4, 8], | [6, 4, 8]]]); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.primitives: DeepElementType; | 1| auto sl = repeat(4.0, 2, 3); 1| assert(sl == [[4.0, 4.0, 4.0], | [4.0, 4.0, 4.0]]); | | static assert(is(DeepElementType!(typeof(sl)) == double)); | 1| sl[1, 1] = 3; 1| assert(sl == [[3.0, 3.0, 3.0], | [3.0, 3.0, 3.0]]); |} | |/++ |Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice. |+/ |auto cycle(Field)(Field field, size_t loopLength, size_t length) | if (!isSlice!Field && !is(Field : T[], T)) |{ | return CycleField!Field(loopLength, field).slicedField(length); |} | |/// ditto |auto cycle(size_t loopLength, Field)(Field field, size_t length) | if (!isSlice!Field && !is(Field : T[], T)) |{ | static assert(loopLength); | return CycleField!(Field, loopLength)(field).slicedField(length); |} | |/// ditto |auto cycle(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length) |{ 2| assert(slice.length); | static if (kind == Universal) | return slice.hideStride.cycle(length); | else 2| return CycleField!Iterator(slice._lengths[0], slice._iterator).slicedField(length); |} | |/// ditto |auto cycle(size_t loopLength, Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length) |{ | static assert(loopLength); 2| assert(loopLength <= slice.length); | static if (kind == Universal) | return slice.hideStride.cycle!loopLength(length); | else 2| return CycleField!(Iterator, loopLength)(slice._iterator).slicedField(length); |} | |/// ditto |auto cycle(T)(T[] array, size_t length) |{ 1| return cycle(array.sliced, length); |} | |/// ditto |auto cycle(size_t loopLength, T)(T[] array, size_t length) |{ 1| return cycle!loopLength(array.sliced, length); |} | |/// ditto |auto cycle(size_t loopLength, T)(T withAsSlice, size_t length) | if (hasAsSlice!T) |{ | return cycle!loopLength(withAsSlice.asSlice, length); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ 1| auto slice = iota(3); 1| assert(slice.cycle(7) == [0, 1, 2, 0, 1, 2, 0]); 1| assert(slice.cycle!2(7) == [0, 1, 0, 1, 0, 1, 0]); 1| assert([0, 1, 2].cycle(7) == [0, 1, 2, 0, 1, 2, 0]); 1| assert([4, 3, 2, 1].cycle!4(7) == [4, 3, 2, 1, 4, 3, 2]); |} | |/++ |Strides 1-dimensional slice. |Params: | slice = 1-dimensional unpacked slice. | factor = positive stride size. |Returns: | Contiguous slice with strided iterator. |See_also: $(SUBREF dynamic, strided) |+/ |auto stride | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice, ptrdiff_t factor) | if (N == 1) |in |{ 11| assert (factor > 0, "factor must be positive."); |} |do |{ | static if (kind == Contiguous) 4| return slice.universal.stride(factor); | else | { | import mir.ndslice.dynamic: strided; 7| return slice.strided!0(factor).hideStride; | } |} | |///ditto |template stride(size_t factor = 2) | if (factor > 1) |{ | auto stride | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; | static if (N > 1) | { 2| return stride(slice.move.ipack!1.map!(.stride!factor)); | } | else | static if (kind == Contiguous) | { 16| immutable rem = slice._lengths[0] % factor; 16| slice._lengths[0] /= factor; 16| if (rem) 5| slice._lengths[0]++; 16| return Slice!(StrideIterator!(Iterator, factor), 1, kind)(slice._structure, StrideIterator!(Iterator, factor)(move(slice._iterator))); | } | else | { 2| return .stride(slice.move, factor); | } | } | | /// ditto | auto stride(T)(T[] array) | { | return stride(array.sliced); | } | | /// ditto | auto stride(T)(T withAsSlice) | if (hasAsSlice!T) | { | return stride(withAsSlice.asSlice); | } |} | |/// ditto |auto stride(T)(T[] array, ptrdiff_t factor) |{ | return stride(array.sliced, factor); |} | |/// ditto |auto stride(T)(T withAsSlice, ptrdiff_t factor) | if (hasAsSlice!T) |{ | return stride(withAsSlice.asSlice, factor); |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| auto slice = iota(6); | static immutable str = [0, 2, 4]; 1| assert(slice.stride(2) == str); // runtime factor 1| assert(slice.stride!2 == str); // compile time factor 1| assert(slice.stride == str); // default compile time factor is 2 1| assert(slice.universal.stride(2) == str); |} | |/// ND-compile time |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| auto slice = iota(4, 6); | static immutable str = [[0, 2, 4], [12, 14, 16]]; 1| assert(slice.stride!2 == str); // compile time factor 1| assert(slice.stride == str); // default compile time factor is 2 |} | |/++ |Reverses order of iteration for all dimensions. |Params: | slice = slice, range, or array. |Returns: | Slice/range with reversed order of iteration for all dimensions. |See_also: $(SUBREF dynamic, reversed), $(SUBREF dynamic, allReversed). |+/ |auto retro | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | @trusted |{ | import core.lifetime: move; | static if (kind == Contiguous || kind == Canonical) | { 386| size_t[slice.N] lengths; | foreach (i; Iota!(slice.N)) 393| lengths[i] = slice._lengths[i]; | static if (slice.S) | { 4| sizediff_t[slice.S] strides; | foreach (i; Iota!(slice.S)) 4| strides[i] = slice._strides[i]; | alias structure = AliasSeq!(lengths, strides); | } | else | { | alias structure = lengths; | } | static if (is(Iterator : RetroIterator!It, It)) | { | alias Ret = Slice!(It, N, kind); 9| slice._iterator._iterator -= slice.lastIndex; 9| return Ret(structure, slice._iterator._iterator.move); | } | else | { | alias Ret = Slice!(RetroIterator!Iterator, N, kind); 377| slice._iterator += slice.lastIndex; 377| return Ret(structure, RetroIterator!Iterator(slice._iterator.move)); | } | } | else | { | import mir.ndslice.dynamic: allReversed; 1| return slice.move.allReversed; | } |} | |/// ditto |auto retro(T)(T[] array) |{ 3| return retro(array.sliced); |} | |/// ditto |auto retro(T)(T withAsSlice) | if (hasAsSlice!T) |{ | return retro(withAsSlice.asSlice); |} | |/// ditto |auto retro(Range)(Range r) | if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) |{ | import std.traits: Unqual; | | static if (is(Unqual!Range == Range)) | { | import core.lifetime: move; | static if (is(Range : RetroRange!R, R)) | { | return move(r._source); | } | else | { 1| return RetroRange!Range(move(r)); | } | } | else | { | return .retro!(Unqual!Range)(r); | } |} | |/// ditto |struct RetroRange(Range) |{ | import mir.primitives: hasLength; | | /// | Range _source; | | private enum hasAccessByRef = __traits(compiles, &_source.front); | | @property | { 5| bool empty()() const { return _source.empty; } | static if (hasLength!Range) 2| auto length()() const { return _source.length; } 4| auto ref front()() { return _source.back; } | auto ref back()() { return _source.front; } | static if (__traits(hasMember, Range, "save")) | auto save()() { return RetroRange(_source.save); } | alias opDollar = length; | | static if (!hasAccessByRef) | { | import std.traits: ForeachType; | | void front()(ForeachType!R val) | { | import mir.functional: forward; | _source.back = forward!val; | } | | void back()(ForeachType!R val) | { | import mir.functional: forward; | _source.front = forward!val; | } | } | } | 4| void popFront()() { _source.popBack(); } | void popBack()() { _source.popFront(); } | | static if (is(typeof(_source.moveBack()))) | auto moveFront()() { return _source.moveBack(); } | | static if (is(typeof(_source.moveFront()))) | auto moveBack()() { return _source.moveFront(); } |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| auto slice = iota(2, 3); | static immutable reversed = [[5, 4, 3], [2, 1, 0]]; 1| assert(slice.retro == reversed); 1| assert(slice.canonical.retro == reversed); 1| assert(slice.universal.retro == reversed); | | static assert(is(typeof(slice.retro.retro) == typeof(slice))); | static assert(is(typeof(slice.canonical.retro.retro) == typeof(slice.canonical))); | static assert(is(typeof(slice.universal.retro) == typeof(slice.universal))); |} | |/// Ranges |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: equal; | import std.range: std_iota = iota; | 1| assert(std_iota(4).retro.equal(iota(4).retro)); | static assert(is(typeof(std_iota(4).retro.retro) == typeof(std_iota(4)))); |} | |/++ |Bitwise slice over an integral slice. |Params: | slice = a contiguous or canonical slice on top of integral iterator. |Returns: A bitwise slice. |+/ |auto bitwise | (Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init])) | (Slice!(Iterator, N, kind) slice) | if (__traits(isIntegral, I) && (kind != Universal || N == 1)) |{ | import core.lifetime: move; | static if (kind == Universal) | { 1| return slice.move.flattened.bitwise; | } | else | { | static if (is(Iterator : FieldIterator!Field, Field)) | { | enum simplified = true; | alias It = FieldIterator!(BitField!Field); | } | else | { | enum simplified = false; | alias It = FieldIterator!(BitField!Iterator); | } | alias Ret = Slice!(It, N, kind); 354| auto structure_ = Ret._Structure.init; | foreach(i; Iota!(Ret.N)) 355| structure_[0][i] = slice._lengths[i]; 354| structure_[0][$ - 1] *= I.sizeof * 8; | foreach(i; Iota!(Ret.S)) | structure_[1][i] = slice._strides[i]; | static if (simplified) 1| return Ret(structure_, It(slice._iterator._index * I.sizeof * 8, BitField!Field(slice._iterator._field.move))); | else 353| return Ret(structure_, It(0, BitField!Iterator(slice._iterator.move))); | } |} | |/// ditto |auto bitwise(T)(T[] array) |{ 335| return bitwise(array.sliced); |} | |/// ditto |auto bitwise(T)(T withAsSlice) | if (hasAsSlice!T) |{ | return bitwise(withAsSlice.asSlice); |} | |/// |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ 1| size_t[10] data; 1| auto bits = data[].bitwise; 1| assert(bits.length == data.length * size_t.sizeof * 8); 1| bits[111] = true; 1| assert(bits[111]); | 1| bits.popFront; 1| assert(bits[110]); 1| bits[] = true; 1| bits[110] = false; 1| bits = bits[10 .. $]; 1| assert(bits[100] == false); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ 1| size_t[10] data; 1| auto slice = FieldIterator!(size_t[])(0, data[]).sliced(10); 1| slice.popFrontExactly(2); 1| auto bits_normal = data[].sliced.bitwise; 1| auto bits = slice.bitwise; 1| assert(bits.length == (data.length - 2) * size_t.sizeof * 8); 1| bits[111] = true; 1| assert(bits[111]); 1| assert(bits_normal[111 + size_t.sizeof * 2 * 8]); 1| auto ubits = slice.universal.bitwise; 1| assert(bits.map!"~a" == bits.map!"!a"); | static assert (is(typeof(bits.map!"~a") == typeof(bits.map!"!a"))); 1| assert(bits.map!"~a" == bits.map!"!!!a"); | static assert (!is(typeof(bits.map!"~a") == typeof(bits.map!"!!!a"))); 1| assert(bits == ubits); | 1| bits.popFront; 1| assert(bits[110]); 1| bits[] = true; 1| bits[110] = false; 1| bits = bits[10 .. $]; 1| assert(bits[100] == false); |} | |/++ |Bitwise field over an integral field. |Params: | field = an integral field. |Returns: A bitwise field. |+/ |auto bitwiseField(Field, I = typeof(Field.init[size_t.init]))(Field field) | if (__traits(isUnsigned, I)) |{ | import core.lifetime: move; 7| return BitField!(Field, I)(field.move); |} | |/++ |Bitpack slice over an integral slice. | |Bitpack is used to represent unsigned integer slice with fewer number of bits in integer binary representation. | |Params: | pack = counts of bits in the integer. | slice = a contiguous or canonical slice on top of integral iterator. |Returns: A bitpack slice. |+/ |auto bitpack | (size_t pack, Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init])) | (Slice!(Iterator, N, kind) slice) | if (__traits(isIntegral, I) && (kind == Contiguous || kind == Canonical) && pack > 1) |{ | import core.lifetime: move; | static if (is(Iterator : FieldIterator!Field, Field) && I.sizeof * 8 % pack == 0) | { | enum simplified = true; | alias It = FieldIterator!(BitpackField!(Field, pack)); | } | else | { | enum simplified = false; | alias It = FieldIterator!(BitpackField!(Iterator, pack)); | } | alias Ret = Slice!(It, N, kind); 2| auto structure = Ret._Structure.init; | foreach(i; Iota!(Ret.N)) 2| structure[0][i] = slice._lengths[i]; 2| structure[0][$ - 1] *= I.sizeof * 8; 2| structure[0][$ - 1] /= pack; | foreach(i; Iota!(Ret.S)) | structure[1][i] = slice._strides[i]; | static if (simplified) | return Ret(structure, It(slice._iterator._index * I.sizeof * 8 / pack, BitpackField!(Field, pack)(slice._iterator._field.move))); | else 2| return Ret(structure, It(0, BitpackField!(Iterator, pack)(slice._iterator.move))); |} | |/// ditto |auto bitpack(size_t pack, T)(T[] array) |{ 1| return bitpack!pack(array.sliced); |} | |/// ditto |auto bitpack(size_t pack, T)(T withAsSlice) | if (hasAsSlice!T) |{ | return bitpack!pack(withAsSlice.asSlice); |} | |/// |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ 1| size_t[10] data; | // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. 1| auto packs = data[].bitpack!6; 1| assert(packs.length == data.length * size_t.sizeof * 8 / 6); 1| packs[$ - 1] = 24; 1| assert(packs[$ - 1] == 24); | 1| packs.popFront; 1| assert(packs[$ - 1] == 24); |} | |/++ |Bytegroup slice over an integral slice. | |Groups existing slice into fixed length chunks and uses them as data store for destination type. | |Correctly handles scalar types on both little-endian and big-endian platforms. | |Params: | group = count of iterator items used to store the destination type. | DestinationType = deep element type of the result slice. | slice = a contiguous or canonical slice. |Returns: A bytegroup slice. |+/ |Slice!(BytegroupIterator!(Iterator, group, DestinationType), N, kind) |bytegroup | (size_t group, DestinationType, Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if ((kind == Contiguous || kind == Canonical) && group) |{ | import core.lifetime: move; 15| auto structure = slice._structure; 15| structure[0][$ - 1] /= group; 15| return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator.move)); |} | |/// ditto |auto bytegroup(size_t pack, DestinationType, T)(T[] array) |{ 14| return bytegroup!(pack, DestinationType)(array.sliced); |} | |/// ditto |auto bytegroup(size_t pack, DestinationType, T)(T withAsSlice) | if (hasAsSlice!T) |{ | return bytegroup!(pack, DestinationType)(withAsSlice.asSlice); |} | |/// 24 bit integers |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: DeepElementType, sliced; | 1| ubyte[20] data; | // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. 1| auto int24ar = data[].bytegroup!(3, int); // 24 bit integers 1| assert(int24ar.length == data.length / 3); | | enum checkInt = ((1 << 20) - 1); | 1| int24ar[3] = checkInt; 1| assert(int24ar[3] == checkInt); | 1| int24ar.popFront; 1| assert(int24ar[2] == checkInt); | | static assert(is(DeepElementType!(typeof(int24ar)) == int)); |} | |/// 48 bit integers |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: DeepElementType, sliced; 1| ushort[20] data; | // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. 1| auto int48ar = data[].sliced.bytegroup!(3, long); // 48 bit integers 1| assert(int48ar.length == data.length / 3); | | enum checkInt = ((1L << 44) - 1); | 1| int48ar[3] = checkInt; 1| assert(int48ar[3] == checkInt); | 1| int48ar.popFront; 1| assert(int48ar[2] == checkInt); | | static assert(is(DeepElementType!(typeof(int48ar)) == long)); |} | |/++ |Implements the homonym function (also known as `transform`) present |in many languages of functional flavor. The call `map!(fun)(slice)` |returns a slice of which elements are obtained by applying `fun` |for all elements in `slice`. The original slices are |not changed. Evaluation is done lazily. | |Note: | $(SUBREF dynamic, transposed) and | $(SUBREF topology, pack) can be used to specify dimensions. |Params: | fun = One or more functions. |See_Also: | $(LREF cached), $(LREF vmap), $(LREF rcmap), $(LREF indexed), | $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), | $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) |+/ |template map(fun...) | if (fun.length) |{ | import mir.functional: adjoin, naryFun, pipe; | static if (fun.length == 1) | { | static if (__traits(isSame, naryFun!(fun[0]), fun[0])) | { | alias f = fun[0]; | @optmath: | /++ | Params: | slice = An ndslice, array, or an input range. | Returns: | ndslice or an input range with each fun applied to all the elements. If there is more than one | fun, the element type will be `Tuple` containing one element for each fun. | +/ | auto map(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; | alias MIterator = typeof(_mapIterator!f(slice._iterator)); | import mir.ndslice.traits: isIterator; | alias testIter = typeof(MIterator.init[0]); | static assert(isIterator!MIterator, "mir.ndslice.map: probably the lambda function contains a compile time bug."); 564| return Slice!(MIterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator.move)); | } | | /// ditto | auto map(T)(T[] array) | { 5| return map(array.sliced); | } | | /// ditto | auto map(T)(T withAsSlice) | if (hasAsSlice!T) | { | return map(withAsSlice.asSlice); | } | | /// ditto | auto map(Range)(Range r) | if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) | { | import core.lifetime: forward; | import mir.primitives: isInputRange; | static assert (isInputRange!Range, "map can work with ndslice, array, or an input range."); 4| return MapRange!(f, ImplicitlyUnqual!Range)(forward!r); | } | } | else alias map = .map!(staticMap!(naryFun, fun)); | } | else alias map = .map!(adjoin!fun); |} | |/// ditto |struct MapRange(alias fun, Range) |{ | import std.range.primitives; | | Range _input; | | static if (isInfinite!Range) | { | enum bool empty = false; | } | else | { | bool empty() @property | { 38| return _input.empty; | } | } | | void popFront() | { 10| assert(!empty, "Attempting to popFront an empty map."); 10| _input.popFront(); | } | | auto ref front() @property | { 10| assert(!empty, "Attempting to fetch the front of an empty map."); 10| return fun(_input.front); | } | | static if (isBidirectionalRange!Range) | auto ref back()() @property | { | assert(!empty, "Attempting to fetch the back of an empty map."); | return fun(_input.back); | } | | static if (isBidirectionalRange!Range) | void popBack()() | { | assert(!empty, "Attempting to popBack an empty map."); | _input.popBack(); | } | | static if (hasLength!Range) | auto length() @property | { | return _input.length; | } | | static if (isForwardRange!Range) | auto save()() @property | { | return typeof(this)(_input.save); | } |} | |/// |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; 7| auto s = iota(2, 3).map!(a => a * 3); 1| assert(s == [[ 0, 3, 6], | [ 9, 12, 15]]); |} | |/// String lambdas |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; 1| assert(iota(2, 3).map!"a * 2" == [[0, 2, 4], [6, 8, 10]]); |} | |/// Input ranges |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: filter, equal; 1| assert (6.iota.filter!"a % 2".map!"a * 10".equal([10, 30, 50])); |} | |/// Packed tensors |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota, windows; | import mir.math.sum: sum; | | // iota windows map sums ( reduce!"a + b" ) | // -------------- | // ------- | --- --- | ------ | // | 0 1 2 | => || 0 1 || 1 2 || => | 8 12 | | // | 3 4 5 | || 3 4 || 4 5 || ------ | // ------- | --- --- | | // -------------- 1| auto s = iota(2, 3) | .windows(2, 2) | .map!sum; | 1| assert(s == [[8, 12]]); |} | |/// Zipped tensors |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota, zip; | | // 0 1 2 | // 3 4 5 1| auto sl1 = iota(2, 3); | // 1 2 3 | // 4 5 6 1| auto sl2 = iota([2, 3], 1); | 1| auto z = zip(sl1, sl2); | 1| assert(zip(sl1, sl2).map!"a + b" == sl1 + sl2); 7| assert(zip(sl1, sl2).map!((a, b) => a + b) == sl1 + sl2); |} | |/++ |Multiple functions can be passed to `map`. |In that case, the element type of `map` is a tuple containing |one element for each function. |+/ |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; | 1| auto sl = iota(2, 3); 1| auto s = sl.map!("a + a", "a * a"); | 1| auto sums = [[0, 2, 4], [6, 8, 10]]; 1| auto products = [[0, 1, 4], [9, 16, 25]]; | 1| assert(s.map!"a[0]" == sl + sl); 1| assert(s.map!"a[1]" == sl * sl); |} | |/++ |`map` can be aliased to a symbol and be used separately: |+/ |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; | | alias halfs = map!"double(a) / 2"; 1| assert(halfs(iota(2, 3)) == [[0.0, 0.5, 1], [1.5, 2, 2.5]]); |} | |/++ |Type normalization |+/ |version(mir_ndslice_test) unittest |{ | import mir.functional : pipe; | import mir.ndslice.topology : iota; 1| auto a = iota(2, 3).map!"a + 10".map!(pipe!("a * 2", "a + 1")); 1| auto b = iota(2, 3).map!(pipe!("a + 10", "a * 2", "a + 1")); 1| assert(a == b); | static assert(is(typeof(a) == typeof(b))); |} | |/// Use map with byDim/alongDim to apply functions to each dimension |version(mir_ndslice_test) |@safe pure |unittest |{ | import mir.ndslice.topology: byDim, alongDim; | import mir.ndslice.fuse: fuse; | import mir.math.stat: mean; | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | | // Use byDim/alongDim with map to compute mean of row/column. 1| assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 1| assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 1| assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 1| assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); |} | |/++ |Use map with a lambda and with byDim/alongDim, but may need to allocate result. |This example uses fuse, which allocates. Note: fuse!1 will transpose the result. |+/ |version(mir_ndslice_test) |@safe pure |unittest { | import mir.ndslice.topology: iota, byDim, alongDim, map; | import mir.ndslice.fuse: fuse; | import mir.ndslice.slice: sliced; | 1| auto x = [1, 2, 3].sliced; 1| auto y = [1, 2].sliced; | 5| auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 6| auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; 1| assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); 5| auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 6| auto s4 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1; 1| assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); |} | |/// |pure version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: reduce; | import mir.math.common: fmax; | import mir.math.stat: mean; | import mir.math.sum; | /// Returns maximal column average. | auto maxAvg(S)(S matrix) { 1| return reduce!fmax(0.0, matrix.alongDim!1.map!mean); | } | // 1 2 | // 3 4 1| auto matrix = iota([2, 2], 1); 1| assert(maxAvg(matrix) == 3.5); |} | |/++ |Implements the homonym function (also known as `transform`) present |in many languages of functional flavor. The call `slice.vmap(fun)` |returns a slice of which elements are obtained by applying `fun` |for all elements in `slice`. The original slices are |not changed. Evaluation is done lazily. | |Note: | $(SUBREF dynamic, transposed) and | $(SUBREF topology, pack) can be used to specify dimensions. |Params: | slice = ndslice | callable = callable object, structure, delegate, or function pointer. |See_Also: | $(LREF cached), $(LREF map), $(LREF rcmap), $(LREF indexed), | $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), | $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) |+/ |@optmath auto vmap(Iterator, size_t N, SliceKind kind, Callable) | ( | Slice!(Iterator, N, kind) slice, | Callable callable, | ) |{ | import core.lifetime: move; | alias It = VmapIterator!(Iterator, Callable); 150| return Slice!(It, N, kind)(slice._structure, It(slice._iterator.move, callable.move)); |} | |/// ditto |auto vmap(T, Callable)(T[] array, Callable callable) |{ | import core.lifetime: move; | return vmap(array.sliced, callable.move); |} | |/// ditto |auto vmap(T, Callable)(T withAsSlice, Callable callable) | if (hasAsSlice!T) |{ | import core.lifetime: move; | return vmap(withAsSlice.asSlice, callable.move); |} | |/// |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; | | static struct Mul { 8| double factor; this(double f) { factor = f; } 6| auto opCall(long x) const {return x * factor; } 3| auto lightConst()() const @property { return Mul(factor); } | } | 1| auto callable = Mul(3); 1| auto s = iota(2, 3).vmap(callable); | 1| assert(s == [[ 0, 3, 6], | [ 9, 12, 15]]); |} | |/// Packed tensors. |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.math.sum: sum; | import mir.ndslice.topology : iota, windows; | | // iota windows vmap scaled sums | // -------------- | // ------- | --- --- | ----- | // | 0 1 2 | => || 0 1 || 1 2 || => | 4 6 | | // | 3 4 5 | || 3 4 || 4 5 || ----- | // ------- | --- --- | | // -------------- | | struct Callable | { | double factor; 6| this(double f) {factor = f;} 2| auto opCall(S)(S x) { return x.sum * factor; } | 2| auto lightConst()() const @property { return Callable(factor); } | auto lightImmutable()() immutable @property { return Callable(factor); } | } | 1| auto callable = Callable(0.5); | 1| auto s = iota(2, 3) | .windows(2, 2) | .vmap(callable); | 1| assert(s == [[4, 6]]); |} | |/// Zipped tensors |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota, zip; | | struct Callable | { | double factor; 8| this(double f) {factor = f;} 6| auto opCall(S, T)(S x, T y) { return x + y * factor; } | 3| auto lightConst()() const { return Callable(factor); } | auto lightImmutable()() immutable { return Callable(factor); } | } | 1| auto callable = Callable(10); | | // 0 1 2 | // 3 4 5 1| auto sl1 = iota(2, 3); | // 1 2 3 | // 4 5 6 1| auto sl2 = iota([2, 3], 1); | 1| auto z = zip(sl1, sl2); | 1| assert(zip(sl1, sl2).vmap(callable) == | [[10, 21, 32], | [43, 54, 65]]); |} | |// TODO |/+ |Multiple functions can be passed to `vmap`. |In that case, the element type of `vmap` is a tuple containing |one element for each function. |+/ |@safe pure nothrow |version(none) version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; | | auto s = iota(2, 3).vmap!("a + a", "a * a"); | | auto sums = [[0, 2, 4], [6, 8, 10]]; | auto products = [[0, 1, 4], [9, 16, 25]]; | | foreach (i; 0..s.length!0) | foreach (j; 0..s.length!1) | { | auto values = s[i, j]; | assert(values.a == sums[i][j]); | assert(values.b == products[i][j]); | } |} | |/// Use vmap with byDim/alongDim to apply functions to each dimension |version(mir_ndslice_test) |@safe pure |unittest |{ | import mir.ndslice.fuse: fuse; | import mir.math.stat: mean; | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | | static struct Callable | { | double factor; 2| this(double f) {factor = f;} 16| auto opCall(U)(U x) const {return x.mean + factor; } 0000000| auto lightConst()() const @property { return Callable(factor); } | } | 1| auto callable = Callable(0.0); | | // Use byDim/alongDim with map to compute callable of row/column. 1| assert(x.byDim!0.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); 1| assert(x.byDim!1.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 1| assert(x.alongDim!1.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); 1| assert(x.alongDim!0.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); |} | |/++ |Use vmap with a lambda and with byDim/alongDim, but may need to allocate result. |This example uses fuse, which allocates. Note: fuse!1 will transpose the result. |+/ |version(mir_ndslice_test) |@safe pure |unittest { | import mir.ndslice.topology: iota, alongDim, map; | import mir.ndslice.fuse: fuse; | import mir.ndslice.slice: sliced; | | static struct Mul(T) | { | T factor; 4| this(T f) { factor = f; } 18| auto opCall(U)(U x) {return x * factor; } 0000000| auto lightConst()() const @property { return Mul!(typeof(factor.lightConst))(factor.lightConst); } | } | 1| auto a = [1, 2, 3].sliced; 1| auto b = [1, 2].sliced; 1| auto A = Mul!(typeof(a))(a); 1| auto B = Mul!(typeof(b))(b); | 1| auto x = [ | [0, 1, 2], | [3, 4, 5] | ].fuse; | 1| auto s1 = x.byDim!0.vmap(A).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 1| auto s2 = x.byDim!1.vmap(B).fuse!1; 1| assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); 1| auto s3 = x.alongDim!1.vmap(A).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 1| auto s4 = x.alongDim!0.vmap(B).fuse!1; 1| assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); |} | |private auto hideStride | (Iterator, SliceKind kind) | (Slice!(Iterator, 1, kind) slice) |{ | import core.lifetime: move; | static if (kind == Universal) 396| return Slice!(StrideIterator!Iterator)( | slice._lengths, | StrideIterator!Iterator(slice._strides[0], move(slice._iterator))); | else 201| return slice; |} | |private auto unhideStride | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) |{ | static if (is(Iterator : StrideIterator!It, It)) | { | import core.lifetime: move; | static if (kind == Universal) | { | alias Ret = SliceKind!(It, N, Universal); | auto strides = slice._strides; | foreach(i; Iota!(Ret.S)) | strides[i] = slice._strides[i] * slice._iterator._stride; | return Slice!(It, N, Universal)(slice._lengths, strides, slice._iterator._iterator.move); | } | else | return slice.move.universal.unhideStride; | } | else 2| return slice; |} | |/++ |Implements the homonym function (also known as `transform`) present |in many languages of functional flavor. The call `rmap!(fun)(slice)` |returns an RC array (1D) or RC slice (ND) of which elements are obtained by applying `fun` |for all elements in `slice`. The original slices are |not changed. Evaluation is done eagerly. | |Note: | $(SUBREF dynamic, transposed) and | $(SUBREF topology, pack) can be used to specify dimensions. |Params: | fun = One or more functions. |See_Also: | $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed), | $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), | $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) |+/ |template rcmap(fun...) | if (fun.length) |{ | import mir.functional: adjoin, naryFun, pipe; | static if (fun.length == 1) | { | static if (__traits(isSame, naryFun!(fun[0]), fun[0])) | { | alias f = fun[0]; | @optmath: | /++ | Params: | slice = An ndslice, array, or an input range. | Returns: | ndslice or an input range with each fun applied to all the elements. If there is more than one | fun, the element type will be `Tuple` containing one element for each fun. | +/ | auto rcmap(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; 4| auto shape = slice.shape; 4| auto r = slice.move.flattened; 4| if (false) | { | auto e = f(r.front); | r.popFront; | auto d = r.empty; | } 4| return () @trusted | { | import mir.rc.array: RCArray; | import std.traits: Unqual; | import mir.conv: emplaceRef; | | alias T = typeof(f(r.front)); 6| auto ret = RCArray!T(r.length); 4| auto next = ret.ptr; 22| while (!r.empty) | { 18| emplaceRef(*cast(Unqual!T*)next++, f(r.front)); 18| r.popFront; | } | static if (N == 1) | { 2| return ret; | } | else | { 2| return ret.moveToSlice.sliced(shape); | } | } (); | } | | /// ditto | auto rcmap(T)(T[] array) | { | return rcmap(array.sliced); | } | | /// ditto | auto rcmap(T)(T withAsSlice) | if (hasAsSlice!T) | { | static if (__traits(hasMember, T, "moveToSlice")) | return rcmap(withAsSlice.moveToSlice); | else | return rcmap(withAsSlice.asSlice); | } | | /// ditto | auto rcmap(Range)(Range r) | if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) | { | import core.lifetime: forward; | import mir.appender: scopedBuffer; | import mir.primitives: isInputRange; | import mir.rc.array: RCArray; | alias T = typeof(f(r.front)); 4| auto buffer = scopedBuffer!T; 8| while (!r.empty) | { 6| buffer.put(f(r.front)); 6| r.popFront; | } 2| return () @trusted | { 2| auto ret = RCArray!T(buffer.length, false); 2| buffer.moveDataAndEmplaceTo(ret[]); 2| return ret; | } (); | } | } | else alias rcmap = .rcmap!(staticMap!(naryFun, fun)); | } | else alias rcmap = .rcmap!(adjoin!fun); |} | |/// Returns RCArray for input ranges and one-dimensional slices. |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: filter, equal; 1| auto factor = 10; 1| auto step = 20; 4| assert (3.iota.rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 4| assert (6.iota.filter!"a % 2".rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); |} | |/// For multidimensional case returns `Slice!(RCI!T, N)`. |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; 1| auto factor = 3; 8| auto s = iota(2, 3).rcmap!(a => a * factor); 1| assert(s == iota(2, 3) * factor); |} | |/// String lambdas |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; 1| assert(iota(2, 3).rcmap!"a * 2" == iota(2, 3) * 2); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: filter, equal; 1| auto factor = 10; 1| auto step = 20; 4| assert (3.iota.as!(const int).rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 4| assert (6.iota.filter!"a % 2".as!(immutable int).rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); |} | |/++ |Creates a random access cache for lazyly computed elements. |Params: | original = original ndslice | caches = cached values | flags = array composed of flags that indicates if values are already computed |Returns: | ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice. |See_also: $(LREF cachedGC), $(LREF map), $(LREF vmap), $(LREF indexed) |+/ |Slice!(CachedIterator!(Iterator, CacheIterator, FlagIterator), N, kind) | cached(Iterator, SliceKind kind, size_t N, CacheIterator, FlagIterator)( | Slice!(Iterator, N, kind) original, | Slice!(CacheIterator, N, kind) caches, | Slice!(FlagIterator, N, kind) flags, | ) |{ 2| assert(original.shape == caches.shape, "caches.shape should be equal to original.shape"); 2| assert(original.shape == flags.shape, "flags.shape should be equal to original.shape"); 2| return typeof(return)( | original._structure, | IteratorOf!(typeof(return))( | original._iterator, | caches._iterator, | flags._iterator, | )); |} | |/// |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: cached, iota, map; | import mir.ndslice.allocation: bitSlice, uninitSlice; | 1| int[] funCalls; | 1| auto v = 5.iota!int | .map!((i) { 2| funCalls ~= i; 2| return 2 ^^ i; | }); 1| auto flags = v.length.bitSlice; 1| auto cache = v.length.uninitSlice!int; | // cached lazy slice: 1 2 4 8 16 1| auto sl = v.cached(cache, flags); | 1| assert(funCalls == []); 1| assert(sl[1] == 2); // remember result 1| assert(funCalls == [1]); 1| assert(sl[1] == 2); // reuse result 1| assert(funCalls == [1]); | 1| assert(sl[0] == 1); 1| assert(funCalls == [1, 0]); 1| funCalls = []; | | // set values directly 1| sl[1 .. 3] = 5; 1| assert(sl[1] == 5); 1| assert(sl[2] == 5); | // no function calls 1| assert(funCalls == []); |} | |/// Cache of immutable elements |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: DeepElementType; | import mir.ndslice.topology: cached, iota, map, as; | import mir.ndslice.allocation: bitSlice, uninitSlice; | 1| int[] funCalls; | 1| auto v = 5.iota!int | .map!((i) { 2| funCalls ~= i; 2| return 2 ^^ i; | }) | .as!(immutable int); 1| auto flags = v.length.bitSlice; 1| auto cache = v.length.uninitSlice!(immutable int); | | // cached lazy slice: 1 2 4 8 16 1| auto sl = v.cached(cache, flags); | | static assert(is(DeepElementType!(typeof(sl)) == immutable int)); | 1| assert(funCalls == []); 1| assert(sl[1] == 2); // remember result 1| assert(funCalls == [1]); 1| assert(sl[1] == 2); // reuse result 1| assert(funCalls == [1]); | 1| assert(sl[0] == 1); 1| assert(funCalls == [1, 0]); |} | |/++ |Creates a random access cache for lazyly computed elements. |Params: | original = ND Contiguous or 1D Universal ndslice. |Returns: | ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice. |See_also: $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed) |+/ |Slice!(CachedIterator!(Iterator, typeof(Iterator.init[0])*, FieldIterator!(BitField!(size_t*))), N) | cachedGC(Iterator, size_t N)(Slice!(Iterator, N) original) @trusted |{ | import std.traits: hasElaborateAssign, Unqual; | import mir.ndslice.allocation: bitSlice, slice, uninitSlice; | alias C = typeof(Iterator.init[0]); | alias UC = Unqual!C; | static if (hasElaborateAssign!UC) | alias newSlice = slice; | else | alias newSlice = uninitSlice; 2| return typeof(return)( | original._structure, | IteratorOf!(typeof(return))( | original._iterator, | newSlice!C(original._lengths)._iterator, | original._lengths.bitSlice._iterator, | )); |} | |/// ditto |auto cachedGC(Iterator)(Slice!(Iterator, 1, Universal) from) |{ | return from.flattened.cachedGC; |} | |/// ditto |auto cachedGC(T)(T withAsSlice) | if (hasAsSlice!T) |{ | return cachedGC(withAsSlice.asSlice); |} | |/// |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: cachedGC, iota, map; | 1| int[] funCalls; | | // cached lazy slice: 1 2 4 8 16 1| auto sl = 5.iota!int | .map!((i) { 2| funCalls ~= i; 2| return 2 ^^ i; | }) | .cachedGC; | 1| assert(funCalls == []); 1| assert(sl[1] == 2); // remember result 1| assert(funCalls == [1]); 1| assert(sl[1] == 2); // reuse result 1| assert(funCalls == [1]); | 1| assert(sl[0] == 1); 1| assert(funCalls == [1, 0]); 1| funCalls = []; | | // set values directly 1| sl[1 .. 3] = 5; 1| assert(sl[1] == 5); 1| assert(sl[2] == 5); | // no function calls 1| assert(funCalls == []); |} | |/// Cache of immutable elements |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: DeepElementType; | import mir.ndslice.topology: cachedGC, iota, map, as; | 1| int[] funCalls; | | // cached lazy slice: 1 2 4 8 16 1| auto sl = 5.iota!int | .map!((i) { 2| funCalls ~= i; 2| return 2 ^^ i; | }) | .as!(immutable int) | .cachedGC; | | static assert(is(DeepElementType!(typeof(sl)) == immutable int)); | 1| assert(funCalls == []); 1| assert(sl[1] == 2); // remember result 1| assert(funCalls == [1]); 1| assert(sl[1] == 2); // reuse result 1| assert(funCalls == [1]); | 1| assert(sl[0] == 1); 1| assert(funCalls == [1, 0]); |} | |/++ |Convenience function that creates a lazy view, |where each element of the original slice is converted to the type `T`. |It uses $(LREF map) and $(REF_ALTTEXT $(TT to), to, mir,conv)$(NBSP) |composition under the hood. |Params: | slice = a slice to create a view on. |Returns: | A lazy slice with elements converted to the type `T`. |See_also: $(LREF map), $(LREF vmap) |+/ |template as(T) |{ | /// | @optmath auto as(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { | static if (is(slice.DeepElement == T)) 37| return slice; | else | static if (is(Iterator : T*)) 3| return slice.toConst; | else | { | import core.lifetime: move; | import mir.conv: to; 74| return map!(to!T)(slice.move); | } | } | | /// ditto | auto as(S)(S[] array) | { 7| return as(array.sliced); | } | | /// ditto | auto as(S)(S withAsSlice) | if (hasAsSlice!S) | { | return as(withAsSlice.asSlice); | } | | /// ditto | auto as(Range)(Range r) | if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) | { | static if (is(ForeachType!Range == T)) | return r; | else | { | import core.lifetime: move; | import mir.conv: to; 2| return map!(to!T)(r.move); | } | } |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: Slice; | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : diagonal, as; | 1| auto matrix = slice!double([2, 2], 0); 1| auto stringMatrixView = matrix.as!int; 1| assert(stringMatrixView == | [[0, 0], | [0, 0]]); | 1| matrix.diagonal[] = 1; 1| assert(stringMatrixView == | [[1, 0], | [0, 1]]); | | /// allocate new slice composed of strings 1| Slice!(int*, 2) stringMatrix = stringMatrixView.slice; |} | |/// Special behavior for pointers to a constant data. |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.slice: Contiguous, Slice; | 1| Slice!(double*, 2) matrix = slice!double([2, 2], 0); 1| Slice!(const(double)*, 2) const_matrix = matrix.as!(const double); |} | |/// Ranges |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.algorithm.iteration: filter, equal; 1| assert(5.iota.filter!"a % 2".as!double.map!"a / 2".equal([0.5, 1.5])); |} | |/++ |Takes a field `source` and a slice `indices`, and creates a view of source as if its elements were reordered according to indices. |`indices` may include only a subset of the elements of `source` and may also repeat elements. | |Params: | source = a filed, source of data. `source` must be an array or a pointer, or have `opIndex` primitive. Full random access range API is not required. | indices = a slice, source of indices. |Returns: | n-dimensional slice with the same kind, shape and strides. | |See_also: `indexed` is similar to $(LREF vmap), but a field (`[]`) is used instead of a function (`()`), and order of arguments is reversed. |+/ |Slice!(IndexIterator!(Iterator, Field), N, kind) | indexed(Field, Iterator, size_t N, SliceKind kind) | (Field source, Slice!(Iterator, N, kind) indices) |{ | import core.lifetime: move; 455| return typeof(return)( | indices._structure, | IndexIterator!(Iterator, Field)( | indices._iterator.move, | source)); |} | |/// ditto |auto indexed(Field, S)(Field source, S[] indices) |{ 27| return indexed(source, indices.sliced); |} | |/// ditto |auto indexed(Field, S)(Field source, S indices) | if (hasAsSlice!S) |{ | return indexed(source, indices.asSlice); |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ 1| auto source = [1, 2, 3, 4, 5]; 1| auto indices = [4, 3, 1, 2, 0, 4]; 1| auto ind = source.indexed(indices); 1| assert(ind == [5, 4, 2, 3, 1, 5]); | 1| assert(ind.retro == source.indexed(indices.retro)); | 1| ind[3] += 10; // for index 2 | // 0 1 2 3 4 1| assert(source == [1, 2, 13, 4, 5]); |} | |/++ |Maps index pairs to subslices. |Params: | sliceable = pointer, array, ndslice, series, or something sliceable with `[a .. b]`. | slices = ndslice composed of index pairs. |Returns: | ndslice composed of subslices. |See_also: $(LREF chopped), $(LREF pairwise). |+/ |Slice!(SubSliceIterator!(Iterator, Sliceable), N, kind) | subSlices(Iterator, size_t N, SliceKind kind, Sliceable)( | Sliceable sliceable, | Slice!(Iterator, N, kind) slices, | ) |{ | import core.lifetime: move; 1| return typeof(return)( | slices._structure, | SubSliceIterator!(Iterator, Sliceable)(slices._iterator.move, sliceable.move) | ); |} | |/// ditto |auto subSlices(S, Sliceable)(Sliceable sliceable, S[] slices) |{ 1| return subSlices(sliceable, slices.sliced); |} | |/// ditto |auto subSlices(S, Sliceable)(Sliceable sliceable, S slices) | if (hasAsSlice!S) |{ | return subSlices(sliceable, slices.asSlice); |} | |/// |@safe pure version(mir_ndslice_test) unittest |{ | import mir.functional: staticArray; 1| auto subs =[ | staticArray(2, 4), | staticArray(2, 10), | ]; 1| auto sliceable = 10.iota; | 1| auto r = sliceable.subSlices(subs); 1| assert(r == [ | iota([4 - 2], 2), | iota([10 - 2], 2), | ]); |} | |/++ |Maps index pairs to subslices. |Params: | bounds = ndslice composed of consequent (`a_i <= a_(i+1)`) pairwise index bounds. | sliceable = pointer, array, ndslice, series, or something sliceable with `[a_i .. a_(i+1)]`. |Returns: | ndslice composed of subslices. |See_also: $(LREF pairwise), $(LREF subSlices). |+/ |Slice!(ChopIterator!(Iterator, Sliceable)) chopped(Iterator, Sliceable)( | Sliceable sliceable, | Slice!Iterator bounds, | ) |in |{ | debug(mir) | foreach(b; bounds.pairwise!"a <= b") | assert(b); |} |do { | import core.lifetime: move; 14| sizediff_t length = bounds._lengths[0] <= 1 ? 0 : bounds._lengths[0] - 1; | static if (hasLength!Sliceable) | { 2| if (length && bounds[length - 1] > sliceable.length) | { | version (D_Exceptions) 0000000| throw choppedException; | else | assert(0, choppedExceptionMsg); | } | } | 14| return typeof(return)([size_t(length)], ChopIterator!(Iterator, Sliceable)(bounds._iterator.move, sliceable.move)); |} | |/// ditto |auto chopped(S, Sliceable)(Sliceable sliceable, S[] bounds) |{ 5| return chopped(sliceable, bounds.sliced); |} | |/// ditto |auto chopped(S, Sliceable)(Sliceable sliceable, S bounds) | if (hasAsSlice!S) |{ | return chopped(sliceable, bounds.asSlice); |} | |/// |@safe pure version(mir_ndslice_test) unittest |{ | import mir.functional: staticArray; | import mir.ndslice.slice: sliced; 1| auto pairwiseIndexes = [2, 4, 10].sliced; 1| auto sliceable = 10.iota; | 1| auto r = sliceable.chopped(pairwiseIndexes); 1| assert(r == [ | iota([4 - 2], 2), | iota([10 - 4], 4), | ]); |} | |/++ |Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional. |Params: | sameStrides = if `true` assumes that all slices has the same strides. | slices = list of slices |Returns: | n-dimensional slice of elements tuple |See_also: $(SUBREF slice, Slice.strides). |+/ |template zip(bool sameStrides = false) |{ | /++ | Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional. | Params: | slices = list of slices | Returns: | n-dimensional slice of elements tuple | See_also: $(SUBREF slice, Slice.strides). | +/ | @optmath | auto zip(Slices...)(Slices slices) | if (Slices.length > 1 && allSatisfy!(isConvertibleToSlice, Slices)) | { | static if (allSatisfy!(isSlice, Slices)) | { | enum N = Slices[0].N; | foreach(i, S; Slices[1 .. $]) | { | static assert(S.N == N, "zip: all Slices must have the same dimension count"); 682| assert(slices[i + 1]._lengths == slices[0]._lengths, "zip: all slices must have the same lengths"); | static if (sameStrides) 5| assert(slices[i + 1].strides == slices[0].strides, "zip: all slices must have the same strides when unpacked"); | } | static if (!sameStrides && minElem(staticMap!(kindOf, Slices)) != Contiguous) | { | static assert(N == 1, "zip: cannot zip canonical and universal multidimensional slices if `sameStrides` is false"); | mixin(`return .zip(` ~ _iotaArgs!(Slices.length, "slices[", "].hideStride, ") ~`);`); | } | else | { | enum kind = maxElem(staticMap!(kindOf, Slices)); | alias Iterator = ZipIterator!(staticMap!(_IteratorOf, Slices)); | alias Ret = Slice!(Iterator, N, kind); 478| auto structure = Ret._Structure.init; 478| structure[0] = slices[0]._lengths; | foreach (i; Iota!(Ret.S)) | structure[1][i] = slices[0]._strides[i]; 478| return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")")); | } | } | else | { 4| return .zip(toSlices!slices); | } | } |} | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : flattened, iota; | 1| auto alpha = iota!int(4, 3); 1| auto beta = slice!int(4, 3).universal; | 1| auto m = zip!true(alpha, beta); 14| foreach (r; m) 44| foreach (e; r) 12| e.b = e.a; 1| assert(alpha == beta); | 1| beta[] = 0; 38| foreach (e; m.flattened) 12| e.b = cast(int)e.a; 1| assert(alpha == beta); |} | |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : flattened, iota; | 1| auto alpha = iota!int(4).universal; 1| auto beta = new int[4]; | 1| auto m = zip(alpha, beta); 14| foreach (e; m) 4| e.b = e.a; 1| assert(alpha == beta); |} | |/++ |Selects a slice from a zipped slice. |Params: | name = name of a slice to unzip. | slice = zipped slice |Returns: | unzipped slice |+/ |auto unzip | (char name, size_t N, SliceKind kind, Iterators...) | (Slice!(ZipIterator!Iterators, N, kind) slice) |{ | import core.lifetime: move; | enum size_t i = name - 'a'; | static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`); | return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i].move).unhideStride; |} | |/// ditto |auto unzip | (char name, size_t N, SliceKind kind, Iterators...) | (ref Slice!(ZipIterator!Iterators, N, kind) slice) |{ | enum size_t i = name - 'a'; | static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`); 2| return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i]).unhideStride; |} | |/// |pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : iota; | 1| auto alpha = iota!int(4, 3); 1| auto beta = iota!int([4, 3], 1).slice; | 1| auto m = zip(alpha, beta); | | static assert(is(typeof(unzip!'a'(m)) == typeof(alpha))); | static assert(is(typeof(unzip!'b'(m)) == typeof(beta))); | 1| assert(m.unzip!'a' == alpha); 1| assert(m.unzip!'b' == beta); |} | |private enum TotalDim(NdFields...) = [staticMap!(DimensionCount, NdFields)].sum; | |private template applyInner(alias fun, size_t N) |{ | static if (N == 0) | alias applyInner = fun; | else | { | import mir.functional: pipe; | alias applyInner = pipe!(zip!true, map!(.applyInner!(fun, N - 1))); | } |} | |/++ |Lazy convolution for tensors. | |Suitable for advanced convolution algorithms. | |Params: | params = convolution windows length. | fun = one dimensional convolution function with `params` arity. | SDimensions = dimensions to perform lazy convolution along. Negative dimensions are supported. |See_also: $(LREF slide), $(LREF pairwise), $(LREF diff). |+/ |template slideAlong(size_t params, alias fun, SDimensions...) | if (params <= 'z' - 'a' + 1 && SDimensions.length > 0) |{ | import mir.functional: naryFun; | | static if (allSatisfy!(isSizediff_t, SDimensions) && params > 1 && __traits(isSame, naryFun!fun, fun)) | { | @optmath: | /++ | Params: slice = ndslice or array | Returns: lazy convolution result | +/ | auto slideAlong(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; | static if (N > 1 && kind == Contiguous) | { 5| return slideAlong(slice.move.canonical); | } | else | static if (N == 1 && kind == Universal) | { 169| return slideAlong(slice.move.flattened); | } | else | { | alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); | enum dimension = Dimensions[$ - 1]; 538| size_t len = slice._lengths[dimension] - (params - 1); 538| if (sizediff_t(len) <= 0) // overfow 5| len = 0; 538| slice._lengths[dimension] = len; | static if (dimension + 1 == N || kind == Universal) | { | alias I = SlideIterator!(Iterator, params, fun); 534| auto ret = Slice!(I, N, kind)(slice._structure, I(move(slice._iterator))); | } | else | { | alias Z = ZipIterator!(Repeat!(params, Iterator)); 4| Z z; | foreach_reverse (p; Iota!(1, params)) 6| z._iterators[p] = slice._iterator + slice._strides[dimension] * p; 4| z._iterators[0] = move(slice._iterator); | alias M = MapIterator!(Z, fun); 4| auto ret = Slice!(M, N, kind)(slice._structure, M(move(z))); | } | static if (Dimensions.length == 1) | { 535| return ret; | } | else | { 3| return .slideAlong!(params, fun, Dimensions[0 .. $ - 1])(ret); | } | } | } | | /// ditto | auto slideAlong(S)(S[] slice) | { | return slideAlong(slice.sliced); | } | | /// ditto | auto slideAlong(S)(S slice) | if (hasAsSlice!S) | { | return slideAlong(slice.asSlice); | } | } | else | static if (params == 1) | alias slideAlong = .map!(naryFun!fun); | else alias slideAlong = .slideAlong!(params, naryFun!fun, staticMap!(toSizediff_t, SDimensions)); |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| auto data = [4, 5].iota; | 22| alias scaled = a => a * 0.25; | 1| auto v = data.slideAlong!(3, "a + 2 * b + c", 0).map!scaled; 1| auto h = data.slideAlong!(3, "a + 2 * b + c", 1).map!scaled; | 1| assert(v == [4, 5].iota[1 .. $ - 1, 0 .. $]); 1| assert(h == [4, 5].iota[0 .. $, 1 .. $ - 1]); |} | |/++ |Lazy convolution for tensors. | |Suitable for simple convolution algorithms. | |Params: | params = windows length. | fun = one dimensional convolution function with `params` arity. |See_also: $(LREF slideAlong), $(LREF withNeighboursSum), $(LREF pairwise), $(LREF diff). |+/ |template slide(size_t params, alias fun) | if (params <= 'z' - 'a' + 1) |{ | import mir.functional: naryFun; | | static if (params > 1 && __traits(isSame, naryFun!fun, fun)) | { | @optmath: | /++ | Params: slice = ndslice or array | Returns: lazy convolution result | +/ | auto slide(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; 533| return slice.move.slideAlong!(params, fun, Iota!N); | } | | /// ditto | auto slide(S)(S[] slice) | { 0000000| return slide(slice.sliced); | } | | /// ditto | auto slide(S)(S slice) | if (hasAsSlice!S) | { | return slide(slice.asSlice); | } | } | else | static if (params == 1) | alias slide = .map!(naryFun!fun); | else alias slide = .slide!(params, naryFun!fun); |} | |/// |version(mir_ndslice_test) unittest |{ 1| auto data = 10.iota; 1| auto sw = data.slide!(3, "a + 2 * b + c"); | | import mir.utility: max; 1| assert(sw.length == max(0, cast(ptrdiff_t)data.length - 3 + 1)); 1| assert(sw == sw.length.iota.map!"(a + 1) * 4"); 1| assert(sw == [4, 8, 12, 16, 20, 24, 28, 32]); |} | |/++ |ND-use case |+/ |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ 1| auto data = [4, 5].iota; | | enum factor = 1.0 / 4 ^^ data.shape.length; 6| alias scaled = a => a * factor; | 1| auto sw = data.slide!(3, "a + 2 * b + c").map!scaled; | 1| assert(sw == [4, 5].iota[1 .. $ - 1, 1 .. $ - 1]); |} | |/++ |Pairwise map for tensors. | |The computation is performed on request, when the element is accessed. | |Params: | fun = function to accumulate | lag = an integer indicating which lag to use |Returns: lazy ndslice composed of `fun(a_n, a_n+1)` values. | |See_also: $(LREF slide), $(LREF slideAlong), $(LREF subSlices). |+/ |alias pairwise(alias fun, size_t lag = 1) = slide!(lag + 1, fun); | |/// |@safe pure nothrow version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: sliced; 1| assert([2, 4, 3, -1].sliced.pairwise!"a + b" == [6, 7, 2]); |} | |/// N-dimensional |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | // performs pairwise along each dimension | // 0 1 2 3 | // 4 5 6 7 | // 8 9 10 11 1| assert([3, 4].iota.pairwise!"a + b" == [[10, 14, 18], [26, 30, 34]]); |} | |/++ |Differences between tensor elements. | |The computation is performed on request, when the element is accessed. | |Params: | lag = an integer indicating which lag to use |Returns: lazy differences. | |See_also: $(LREF slide), $(LREF slide). |+/ |alias diff(size_t lag = 1) = pairwise!(('a' + lag) ~ " - a", lag); | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: sliced; 1| assert([2, 4, 3, -1].sliced.diff == [2, -1, -4]); |} | |/// N-dimensional |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | // 0 1 2 3 | // 4 5 6 7 => | // 8 9 10 11 | | // 1 1 1 | // 1 1 1 => | // 1 1 1 | | // 0 0 0 | // 0 0 0 | 1| assert([3, 4].iota.diff == repeat(0, [2, 3])); |} | |/// packed slices |version(mir_ndslice_test) unittest |{ | // 0 1 2 3 | // 4 5 6 7 | // 8 9 10 11 1| auto s = iota(3, 4); 1| assert(iota(3, 4).byDim!0.diff == [ | [4, 4, 4, 4], | [4, 4, 4, 4]]); 1| assert(iota(3, 4).byDim!1.diff == [ | [1, 1, 1], | [1, 1, 1], | [1, 1, 1]]); |} | |/++ |Drops borders for all dimensions. | |Params: | slice = ndslice |Returns: | Tensors with striped borders |See_also: | $(LREF universal), | $(LREF assumeCanonical), | $(LREF assumeContiguous). |+/ |Slice!(Iterator, N, N > 1 && kind == Contiguous ? Canonical : kind, Labels) | dropBorders | (Iterator, size_t N, SliceKind kind, Labels...) | (Slice!(Iterator, N, kind, Labels) slice) |{ | static if (N > 1 && kind == Contiguous) | { | import core.lifetime: move; 1| auto ret = slice.move.canonical; | } | else | { | alias ret = slice; | } 1| ret.popFrontAll; 1| ret.popBackAll; 1| return ret; |} | |/// |version(mir_ndslice_test) unittest |{ 1| assert([4, 5].iota.dropBorders == [[6, 7, 8], [11, 12, 13]]); |} | |/++ |Lazy zip view of elements packed with sum of their neighbours. | |Params: | fun = neighbours accumulation function. |See_also: $(LREF slide), $(LREF slideAlong). |+/ |template withNeighboursSum(alias fun = "a + b") |{ | import mir.functional: naryFun; | | static if (__traits(isSame, naryFun!fun, fun)) | { | @optmath: | /++ | Params: | slice = ndslice or array | Returns: | Lazy zip view of elements packed with sum of their neighbours. | +/ | auto withNeighboursSum(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | { | import core.lifetime: move; | static if (N > 1 && kind == Contiguous) | { 2| return withNeighboursSum(slice.move.canonical); | } | else | static if (N == 1 && kind == Universal) | { | return withNeighboursSum(slice.move.flattened); | } | else | { | enum around = kind != Universal; | alias Z = NeighboursIterator!(Iterator, N - around, fun, around); | 2| size_t shift; | foreach (dimension; Iota!N) | { 4| slice._lengths[dimension] -= 2; 4| if (sizediff_t(slice._lengths[dimension]) <= 0) // overfow 0000000| slice._lengths[dimension] = 0; 4| shift += slice._stride!dimension; | } | 2| Z z; 2| z._iterator = move(slice._iterator); 2| z._iterator += shift; | foreach (dimension; Iota!(N - around)) | { 2| z._neighbours[dimension][0] = z._iterator - slice._strides[dimension]; 2| z._neighbours[dimension][1] = z._iterator + slice._strides[dimension]; | } 2| return Slice!(Z, N, kind)(slice._structure, move(z)); | } | } | | /// ditto | auto withNeighboursSum(S)(S[] slice) | { | return withNeighboursSum(slice.sliced); | } | | /// ditto | auto withNeighboursSum(S)(S slice) | if (hasAsSlice!S) | { | return withNeighboursSum(slice.asSlice); | } | } | else alias withNeighboursSum = .withNeighboursSum!(naryFun!fun); |} | |/// |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.algorithm.iteration: all; | 1| auto wn = [4, 5].iota.withNeighboursSum; 1| assert(wn.all!"a[0] == a[1] * 0.25"); 1| assert(wn.map!"a" == wn.map!"b * 0.25"); |} | |@safe pure nothrow @nogc version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.algorithm.iteration: all; | 1| auto wn = [4, 5].iota.withNeighboursSum.universal; 1| assert(wn.all!"a[0] == a[1] * 0.25"); 1| assert(wn.map!"a" == wn.map!"b * 0.25"); |} | |/++ |Cartesian product. | |Constructs lazy cartesian product $(SUBREF slice, Slice) without memory allocation. | |Params: | fields = list of fields with lengths or ndFields with shapes |Returns: $(SUBREF ndfield, Cartesian)`!NdFields(fields).`$(SUBREF slice, slicedNdField)`;` |+/ |auto cartesian(NdFields...)(NdFields fields) | if (NdFields.length > 1 && allSatisfy!(templateOr!(hasShape, hasLength), NdFields)) |{ 19| return Cartesian!NdFields(fields).slicedNdField; |} | |/// 1D x 1D |version(mir_ndslice_test) unittest |{ 1| auto a = [10, 20, 30]; 1| auto b = [ 1, 2, 3]; | 1| auto c = cartesian(a, b) | .map!"a + b"; | 1| assert(c == [ | [11, 12, 13], | [21, 22, 23], | [31, 32, 33]]); |} | |/// 1D x 2D |version(mir_ndslice_test) unittest |{ 1| auto a = [10, 20, 30]; 1| auto b = iota([2, 3], 1); | 1| auto c = cartesian(a, b) | .map!"a + b"; | 1| assert(c.shape == [3, 2, 3]); | 1| assert(c == [ | [ | [11, 12, 13], | [14, 15, 16], | ], | [ | [21, 22, 23], | [24, 25, 26], | ], | [ | [31, 32, 33], | [34, 35, 36], | ]]); |} | |/// 1D x 1D x 1D |version(mir_ndslice_test) unittest |{ 1| auto u = [100, 200]; 1| auto v = [10, 20, 30]; 1| auto w = [1, 2]; | 1| auto c = cartesian(u, v, w) | .map!"a + b + c"; | 1| assert(c.shape == [2, 3, 2]); | 1| assert(c == [ | [ | [111, 112], | [121, 122], | [131, 132], | ], | [ | [211, 212], | [221, 222], | [231, 232], | ]]); |} | |/++ |$(LINK2 https://en.wikipedia.org/wiki/Kronecker_product, Kronecker product). | |Constructs lazy kronecker product $(SUBREF slice, Slice) without memory allocation. |+/ |template kronecker(alias fun = product) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun)) | | /++ | Params: | fields = list of either fields with lengths or ndFields with shapes. | All ndFields must have the same dimension count. | Returns: | $(SUBREF ndfield, Kronecker)`!(fun, NdFields)(fields).`$(SUBREF slice, slicedNdField) | +/ | @optmath auto kronecker(NdFields...)(NdFields fields) | if (allSatisfy!(hasShape, NdFields) || allSatisfy!(hasLength, NdFields)) | { 3| return Kronecker!(fun, NdFields)(fields).slicedNdField; | } | else | alias kronecker = .kronecker!(naryFun!fun); |} | |/// 2D |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.slice: sliced; | | // eye 1| auto a = slice!double([4, 4], 0); 1| a.diagonal[] = 1; | 1| auto b = [ 1, -1, | -1, 1].sliced(2, 2); | 1| auto c = kronecker(a, b); | 1| assert(c == [ | [ 1, -1, 0, 0, 0, 0, 0, 0], | [-1, 1, 0, 0, 0, 0, 0, 0], | [ 0, 0, 1, -1, 0, 0, 0, 0], | [ 0, 0, -1, 1, 0, 0, 0, 0], | [ 0, 0, 0, 0, 1, -1, 0, 0], | [ 0, 0, 0, 0, -1, 1, 0, 0], | [ 0, 0, 0, 0, 0, 0, 1, -1], | [ 0, 0, 0, 0, 0, 0, -1, 1]]); |} | |/// 1D |version(mir_ndslice_test) unittest |{ 1| auto a = iota([3], 1); | 1| auto b = [ 1, -1]; | 1| auto c = kronecker(a, b); | 1| assert(c == [1, -1, 2, -2, 3, -3]); |} | |/// 2D with 3 arguments |version(mir_ndslice_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.slice: sliced; | 1| auto a = [ 1, 2, | 3, 4].sliced(2, 2); | 1| auto b = [ 1, 0, | 0, 1].sliced(2, 2); | 1| auto c = [ 1, -1, | -1, 1].sliced(2, 2); | 1| auto d = kronecker(a, b, c); | 1| assert(d == [ | [ 1, -1, 0, 0, 2, -2, 0, 0], | [-1, 1, 0, 0, -2, 2, 0, 0], | [ 0, 0, 1, -1, 0, 0, 2, -2], | [ 0, 0, -1, 1, 0, 0, -2, 2], | [ 3, -3, 0, 0, 4, -4, 0, 0], | [-3, 3, 0, 0, -4, 4, 0, 0], | [ 0, 0, 3, -3, 0, 0, 4, -4], | [ 0, 0, -3, 3, 0, 0, -4, 4]]); |} | |/++ |$(HTTPS en.wikipedia.org/wiki/Magic_square, Magic square). |Params: | length = square matrix length. |Returns: | Lazy magic matrix. |+/ |auto magic(size_t length) |{ 25| assert(length > 0); | static if (is(size_t == ulong)) 25| assert(length <= uint.max); | else | assert(length <= ushort.max); | import mir.ndslice.field: MagicField; 25| return MagicField(length).slicedField(length, length); |} | |/// |@safe pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.math.sum; | import mir.ndslice: slice, magic, byDim, map, as, repeat, diagonal, antidiagonal; | | bool isMagic(S)(S matrix) | { 24| auto n = matrix.length; 24| auto c = n * (n * n + 1) / 2; // magic number 24| return // check shape 24| matrix.length!0 > 0 && matrix.length!0 == matrix.length!1 | && // each row sum should equal magic number 24| matrix.byDim!0.map!sum == c.repeat(n) | && // each columns sum should equal magic number 23| matrix.byDim!1.map!sum == c.repeat(n) | && // diagonal sum should equal magic number 23| matrix.diagonal.sum == c | && // antidiagonal sum should equal magic number 23| matrix.antidiagonal.sum == c; | } | 1| assert(isMagic(magic(1))); 1| assert(!isMagic(magic(2))); // 2x2 magic square does not exist 66| foreach(n; 3 .. 24) 21| assert(isMagic(magic(n))); 1| assert(isMagic(magic(3).as!double.slice)); |} | |/++ |Chops 1D input slice into n chunks with ascending or descending lengths. | |`stairs` can be used to pack and unpack symmetric and triangular matrix storage. | |Note: `stairs` is defined for 1D (packet) input and 2D (general) input. | This part of documentation is for 1D input. | |Params: | type = $(UL | $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`.) | $(LI `"+"` for stairs with lengths `1, 2, ..., n`;) | ) | slice = input slice with length equal to `n * (n + 1) / 2` | n = stairs count |Returns: | 1D contiguous slice composed of 1D contiguous slices. | |See_also: $(LREF triplets) $(LREF ._stairs.2) |+/ |Slice!(StairsIterator!(Iterator, type)) stairs(string type, Iterator)(Slice!Iterator slice, size_t n) | if (type == "+" || type == "-") |{ 4| assert(slice.length == (n + 1) * n / 2, "stairs: slice length must be equal to n * (n + 1) / 2, where n is stairs count."); | static if (type == "+") 2| size_t length = 1; | else 2| size_t length = n; 4| return StairsIterator!(Iterator, type)(length, slice._iterator).sliced(n); |} | |/// ditto |Slice!(StairsIterator!(S*, type)) stairs(string type, S)(S[] slice, size_t n) | if (type == "+" || type == "-") |{ | return stairs!type(slice.sliced, n); |} | |/// ditto |auto stairs(string type, S)(S slice, size_t n) | if (hasAsSlice!S && (type == "+" || type == "-")) |{ | return stairs!type(slice.asSlice, n); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota, stairs; | 1| auto pck = 15.iota; 1| auto inc = pck.stairs!"+"(5); 1| auto dec = pck.stairs!"-"(5); | 1| assert(inc == [ | [0], | [1, 2], | [3, 4, 5], | [6, 7, 8, 9], | [10, 11, 12, 13, 14]]); 1| assert(inc[1 .. $][2] == [6, 7, 8, 9]); | 1| assert(dec == [ | [0, 1, 2, 3, 4], | [5, 6, 7, 8], | [9, 10, 11], | [12, 13], | [14]]); 1| assert(dec[1 .. $][2] == [12, 13]); | | static assert(is(typeof(inc.front) == typeof(pck))); | static assert(is(typeof(dec.front) == typeof(pck))); |} | |/++ |Slice composed of rows of lower or upper triangular matrix. | |`stairs` can be used to pack and unpack symmetric and triangular matrix storage. | |Note: `stairs` is defined for 1D (packet) input and 2D (general) input. | This part of documentation is for 2D input. | |Params: | type = $(UL | $(LI `"+"` for stairs with lengths `1, 2, ..., n`, lower matrix;) | $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`, upper matrix.) | ) | slice = input slice with length equal to `n * (n + 1) / 2` |Returns: | 1D slice composed of 1D contiguous slices. | |See_also: $(LREF _stairs) $(SUBREF dynamic, transposed), $(LREF universal) |+/ |auto stairs(string type, Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice) | if (type == "+" || type == "-") |{ 2| assert(slice.length!0 == slice.length!1, "stairs: input slice must be a square matrix."); | static if (type == "+") | { 1| return slice | .pack!1 | .map!"a" | .zip([slice.length].iota!size_t(1)) | .map!"a[0 .. b]"; | } | else | { 1| return slice | .pack!1 | .map!"a" | .zip([slice.length].iota!size_t) | .map!"a[b .. $]"; | } |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology: iota, as, stairs; | 1| auto gen = [3, 3].iota.as!double; 1| auto inc = gen.stairs!"+"; 1| auto dec = gen.stairs!"-"; | 1| assert(inc == [ | [0], | [3, 4], | [6, 7, 8]]); | 1| assert(dec == [ | [0, 1, 2], | [4, 5], | [8]]); | | static assert(is(typeof(inc.front) == typeof(gen.front))); | static assert(is(typeof(dec.front) == typeof(gen.front))); | | ///////////////////////////////////////// | // Pack lower and upper matrix parts 1| auto n = gen.length; 1| auto m = n * (n + 1) / 2; | // allocate memory | import mir.ndslice.allocation: uninitSlice; 1| auto lowerData = m.uninitSlice!double; 1| auto upperData = m.uninitSlice!double; | // construct packed stairs 1| auto lower = lowerData.stairs!"+"(n); 1| auto upper = upperData.stairs!"-"(n); | // copy data | import mir.algorithm.iteration: each; 1| each!"a[] = b"(lower, inc); 1| each!"a[] = b"(upper, dec); | 1| assert(&lower[0][0] is &lowerData[0]); 1| assert(&upper[0][0] is &upperData[0]); | 1| assert(lowerData == [0, 3, 4, 6, 7, 8]); 1| assert(upperData == [0, 1, 2, 4, 5, 8]); |} | |/++ |Returns a slice that can be iterated along dimension. Transposes other dimensions on top and then packs them. | |Combines $(LREF byDim) and $(LREF evertPack). | |Params: | SDimensions = dimensions to iterate along, length of d, `1 <= d < n`. Negative dimensions are supported. |Returns: | `(n-d)`-dimensional slice composed of d-dimensional slices |See_also: | $(LREF byDim), | $(LREF iota), | $(SUBREF allocation, slice), | $(LREF ipack), | $(SUBREF dynamic, transposed). |+/ |template alongDim(SDimensions...) | if (SDimensions.length > 0) |{ | static if (allSatisfy!(isSizediff_t, SDimensions)) | { | /++ | Params: | slice = input n-dimensional slice, n > d | Returns: | `(n-d)`-dimensional slice composed of d-dimensional slices | +/ | @optmath auto alongDim(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (N > SDimensions.length) | { | import core.lifetime: move; 51| return slice.move.byDim!SDimensions.evertPack; | } | } | else | { | alias alongDim = .alongDim!(staticMap!(toSizediff_t, SDimensions)); | } |} | |/// 2-dimensional slice support |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice; | | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto slice = iota(3, 4); | //-> | // | 3 | | //-> | // | 4 | 1| size_t[1] shape3 = [3]; 1| size_t[1] shape4 = [4]; | | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto x = slice.alongDim!(-1); // -1 is the last dimension index, the same as 1 for this case. | static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); | 1| assert(x.shape == shape3); 1| assert(x.front.shape == shape4); 1| assert(x.front == iota(4)); 1| x.popFront; 1| assert(x.front == iota([4], 4)); | | // --------- | // | 0 4 8 | | // | 1 5 9 | | // | 2 6 10 | | // | 3 7 11 | | // --------- 1| auto y = slice.alongDim!0; // alongDim!(-2) is the same for matrices. | static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); | 1| assert(y.shape == shape4); 1| assert(y.front.shape == shape3); 1| assert(y.front == iota([3], 0, 4)); 1| y.popFront; 1| assert(y.front == iota([3], 1, 4)); |} | |/// 3-dimensional slice support, N-dimensional also supported |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice; | | // ---------------- | // | 0 1 2 3 4 | | // | 5 6 7 8 9 | | // | 10 11 12 13 14 | | // | 15 16 17 18 19 | | // - - - - - - - - | // | 20 21 22 23 24 | | // | 25 26 27 28 29 | | // | 30 31 32 33 34 | | // | 35 36 37 38 39 | | // - - - - - - - - | // | 40 41 42 43 44 | | // | 45 46 47 48 49 | | // | 50 51 52 53 54 | | // | 55 56 57 58 59 | | // ---------------- 1| auto slice = iota(3, 4, 5); | 1| size_t[2] shape45 = [4, 5]; 1| size_t[2] shape35 = [3, 5]; 1| size_t[2] shape34 = [3, 4]; 1| size_t[2] shape54 = [5, 4]; 1| size_t[1] shape3 = [3]; 1| size_t[1] shape4 = [4]; 1| size_t[1] shape5 = [5]; | | // ---------- | // | 0 20 40 | | // | 5 25 45 | | // | 10 30 50 | | // | 15 35 55 | | // - - - - - | // | 1 21 41 | | // | 6 26 46 | | // | 11 31 51 | | // | 16 36 56 | | // - - - - - | // | 2 22 42 | | // | 7 27 47 | | // | 12 32 52 | | // | 17 37 57 | | // - - - - - | // | 3 23 43 | | // | 8 28 48 | | // | 13 33 53 | | // | 18 38 58 | | // - - - - - | // | 4 24 44 | | // | 9 29 49 | | // | 14 34 54 | | // | 19 39 59 | | // ---------- 1| auto a = slice.alongDim!0.transposed; | static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal))); | 1| assert(a.shape == shape54); 1| assert(a.front.shape == shape4); 1| assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed); 1| a.popFront; 1| assert(a.front.front == iota([3], 1, 20)); | | // ---------------- | // | 0 1 2 3 4 | | // | 5 6 7 8 9 | | // | 10 11 12 13 14 | | // | 15 16 17 18 19 | | // - - - - - - - - | // | 20 21 22 23 24 | | // | 25 26 27 28 29 | | // | 30 31 32 33 34 | | // | 35 36 37 38 39 | | // - - - - - - - - | // | 40 41 42 43 44 | | // | 45 46 47 48 49 | | // | 50 51 52 53 54 | | // | 55 56 57 58 59 | | // ---------------- 1| auto x = slice.alongDim!(1, 2); | static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal))); | 1| assert(x.shape == shape3); 1| assert(x.front.shape == shape45); 1| assert(x.front == iota([4, 5])); 1| x.popFront; 1| assert(x.front == iota([4, 5], (4 * 5))); | | // ---------------- | // | 0 1 2 3 4 | | // | 20 21 22 23 24 | | // | 40 41 42 43 44 | | // - - - - - - - - | // | 5 6 7 8 9 | | // | 25 26 27 28 29 | | // | 45 46 47 48 49 | | // - - - - - - - - | // | 10 11 12 13 14 | | // | 30 31 32 33 34 | | // | 50 51 52 53 54 | | // - - - - - - - - | // | 15 16 17 18 19 | | // | 35 36 37 38 39 | | // | 55 56 57 58 59 | | // ---------------- 1| auto y = slice.alongDim!(0, 2); | static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal))); | 1| assert(y.shape == shape4); 1| assert(y.front.shape == shape35); 1| int err; 1| assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err)); 1| y.popFront; 1| assert(y.front.front == iota([5], 5)); | | // ------------- | // | 0 5 10 15 | | // | 20 25 30 35 | | // | 40 45 50 55 | | // - - - - - - - | // | 1 6 11 16 | | // | 21 26 31 36 | | // | 41 46 51 56 | | // - - - - - - - | // | 2 7 12 17 | | // | 22 27 32 37 | | // | 42 47 52 57 | | // - - - - - - - | // | 3 8 13 18 | | // | 23 28 33 38 | | // | 43 48 53 58 | | // - - - - - - - | // | 4 9 14 19 | | // | 24 29 34 39 | | // | 44 49 54 59 | | // ------------- 1| auto z = slice.alongDim!(0, 1); | static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal)))); | 1| assert(z.shape == shape5); 1| assert(z.front.shape == shape34); 1| assert(z.front == iota([3, 4], 0, 5)); 1| z.popFront; 1| assert(z.front.front == iota([4], 1, 5)); |} | |/// Use alongDim to calculate column mean/row mean of 2-dimensional slice |version(mir_ndslice_test) |@safe pure |unittest |{ | import mir.ndslice.topology: alongDim; | import mir.ndslice.fuse: fuse; | import mir.math.stat: mean; | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | | // Use alongDim with map to compute mean of row/column. 1| assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 1| assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); | | // FIXME | // Without using map, computes the mean of the whole slice | // assert(x.alongDim!1.mean == x.sliced.mean); | // assert(x.alongDim!0.mean == x.sliced.mean); |} | |/++ |Use alongDim and map with a lambda, but may need to allocate result. This example |uses fuse, which allocates. Note: fuse!1 will transpose the result. |+/ |version(mir_ndslice_test) |@safe pure |unittest { | import mir.ndslice.topology: iota, alongDim, map; | import mir.ndslice.fuse: fuse; | import mir.ndslice.slice: sliced; | 1| auto x = [1, 2, 3].sliced; 1| auto y = [1, 2].sliced; | 5| auto s1 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 6| auto s2 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1; 1| assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); |} | |/++ |Returns a slice that can be iterated by dimension. Transposes dimensions on top and then packs them. | |Combines $(SUBREF dynamic, transposed), $(LREF ipack), and SliceKind Selectors. | |Params: | SDimensions = dimensions to perform iteration on, length of d, `1 <= d <= n`. Negative dimensions are supported. |Returns: | d-dimensional slice composed of `(n-d)`-dimensional slices |See_also: | $(LREF alongDim), | $(SUBREF allocation, slice), | $(LREF ipack), | $(SUBREF dynamic, transposed). |+/ |template byDim(SDimensions...) | if (SDimensions.length > 0) |{ | static if (allSatisfy!(isSizediff_t, SDimensions)) | { | /++ | Params: | slice = input n-dimensional slice, n >= d | Returns: | d-dimensional slice composed of `(n-d)`-dimensional slices | +/ | @optmath auto byDim(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (N >= SDimensions.length) | { | | alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); | | mixin DimensionsCountCTError; | | static if (N == 1) | { 1| return slice; | } | else | { | import core.lifetime: move; | import mir.ndslice.dynamic: transposed; | import mir.algorithm.iteration: all; | 148| auto trans = slice | .move | .transposed!Dimensions; | static if (Dimensions.length == N) | { | return trans; | } | else | { 148| auto ret = trans.move.ipack!(Dimensions.length); 0000000| static if ((kind == Contiguous || kind == Canonical && N - Dimensions.length == 1) && [Dimensions].all!(a => a < Dimensions.length)) | { 74| return ret | .move | .evertPack | .assumeContiguous | .evertPack; | } | else 0000000| static if (kind == Canonical && [Dimensions].all!(a => a < N - 1)) | { | return ret | .move | .evertPack | .assumeCanonical | .evertPack; | } | else | static if ((kind == Contiguous || kind == Canonical && Dimensions.length == 1) && [Dimensions] == [Iota!(N - Dimensions.length, N)]) | { 68| return ret.assumeContiguous; | } | else | static if ((kind == Contiguous || kind == Canonical) && Dimensions[$-1] == N - 1) | { 1| return ret.assumeCanonical; | } | else | { 5| return ret; | } | } | } | } | } | else | { | alias byDim = .byDim!(staticMap!(toSizediff_t, SDimensions)); | } |} | |/// 2-dimensional slice support |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice; | | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto slice = iota(3, 4); | //-> | // | 3 | | //-> | // | 4 | 1| size_t[1] shape3 = [3]; 1| size_t[1] shape4 = [4]; | | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto x = slice.byDim!0; // byDim!(-2) is the same for matrices. | static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); | 1| assert(x.shape == shape3); 1| assert(x.front.shape == shape4); 1| assert(x.front == iota(4)); 1| x.popFront; 1| assert(x.front == iota([4], 4)); | | // --------- | // | 0 4 8 | | // | 1 5 9 | | // | 2 6 10 | | // | 3 7 11 | | // --------- 1| auto y = slice.byDim!(-1); // -1 is the last dimension index, the same as 1 for this case. | static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); | 1| assert(y.shape == shape4); 1| assert(y.front.shape == shape3); 1| assert(y.front == iota([3], 0, 4)); 1| y.popFront; 1| assert(y.front == iota([3], 1, 4)); |} | |/// 3-dimensional slice support, N-dimensional also supported |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice; | | // ---------------- | // | 0 1 2 3 4 | | // | 5 6 7 8 9 | | // | 10 11 12 13 14 | | // | 15 16 17 18 19 | | // - - - - - - - - | // | 20 21 22 23 24 | | // | 25 26 27 28 29 | | // | 30 31 32 33 34 | | // | 35 36 37 38 39 | | // - - - - - - - - | // | 40 41 42 43 44 | | // | 45 46 47 48 49 | | // | 50 51 52 53 54 | | // | 55 56 57 58 59 | | // ---------------- 1| auto slice = iota(3, 4, 5); | 1| size_t[2] shape45 = [4, 5]; 1| size_t[2] shape35 = [3, 5]; 1| size_t[2] shape34 = [3, 4]; 1| size_t[2] shape54 = [5, 4]; 1| size_t[1] shape3 = [3]; 1| size_t[1] shape4 = [4]; 1| size_t[1] shape5 = [5]; | | // ---------------- | // | 0 1 2 3 4 | | // | 5 6 7 8 9 | | // | 10 11 12 13 14 | | // | 15 16 17 18 19 | | // - - - - - - - - | // | 20 21 22 23 24 | | // | 25 26 27 28 29 | | // | 30 31 32 33 34 | | // | 35 36 37 38 39 | | // - - - - - - - - | // | 40 41 42 43 44 | | // | 45 46 47 48 49 | | // | 50 51 52 53 54 | | // | 55 56 57 58 59 | | // ---------------- 1| auto x = slice.byDim!0; | static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal))); | 1| assert(x.shape == shape3); 1| assert(x.front.shape == shape45); 1| assert(x.front == iota([4, 5])); 1| x.popFront; 1| assert(x.front == iota([4, 5], (4 * 5))); | | // ---------------- | // | 0 1 2 3 4 | | // | 20 21 22 23 24 | | // | 40 41 42 43 44 | | // - - - - - - - - | // | 5 6 7 8 9 | | // | 25 26 27 28 29 | | // | 45 46 47 48 49 | | // - - - - - - - - | // | 10 11 12 13 14 | | // | 30 31 32 33 34 | | // | 50 51 52 53 54 | | // - - - - - - - - | // | 15 16 17 18 19 | | // | 35 36 37 38 39 | | // | 55 56 57 58 59 | | // ---------------- 1| auto y = slice.byDim!1; | static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal))); | 1| assert(y.shape == shape4); 1| assert(y.front.shape == shape35); 1| int err; 1| assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err)); 1| y.popFront; 1| assert(y.front.front == iota([5], 5)); | | // ------------- | // | 0 5 10 15 | | // | 20 25 30 35 | | // | 40 45 50 55 | | // - - - - - - - | // | 1 6 11 16 | | // | 21 26 31 36 | | // | 41 46 51 56 | | // - - - - - - - | // | 2 7 12 17 | | // | 22 27 32 37 | | // | 42 47 52 57 | | // - - - - - - - | // | 3 8 13 18 | | // | 23 28 33 38 | | // | 43 48 53 58 | | // - - - - - - - | // | 4 9 14 19 | | // | 24 29 34 39 | | // | 44 49 54 59 | | // ------------- 1| auto z = slice.byDim!2; | static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal)))); | 1| assert(z.shape == shape5); 1| assert(z.front.shape == shape34); 1| assert(z.front == iota([3, 4], 0, 5)); 1| z.popFront; 1| assert(z.front.front == iota([4], 1, 5)); | | // ---------- | // | 0 20 40 | | // | 5 25 45 | | // | 10 30 50 | | // | 15 35 55 | | // - - - - - | // | 1 21 41 | | // | 6 26 46 | | // | 11 31 51 | | // | 16 36 56 | | // - - - - - | // | 2 22 42 | | // | 7 27 47 | | // | 12 32 52 | | // | 17 37 57 | | // - - - - - | // | 3 23 43 | | // | 8 28 48 | | // | 13 33 53 | | // | 18 38 58 | | // - - - - - | // | 4 24 44 | | // | 9 29 49 | | // | 14 34 54 | | // | 19 39 59 | | // ---------- 1| auto a = slice.byDim!(2, 1); | static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal))); | 1| assert(a.shape == shape54); 1| assert(a.front.shape == shape4); 1| assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed); 1| a.popFront; 1| assert(a.front.front == iota([3], 1, 20)); |} | |/// Use byDim to calculate column mean/row mean of 2-dimensional slice |version(mir_ndslice_test) |@safe pure |unittest |{ | import mir.ndslice.topology: byDim; | import mir.ndslice.fuse: fuse; | import mir.math.stat: mean; | import mir.algorithm.iteration: all; | import mir.math.common: approxEqual; | 1| auto x = [ | [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], | [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] | ].fuse; | | // Use byDim with map to compute mean of row/column. 1| assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 1| assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); | | // FIXME | // Without using map, computes the mean of the whole slice | // assert(x.byDim!0.mean == x.sliced.mean); | // assert(x.byDim!1.mean == x.sliced.mean); |} | |/++ |Use byDim and map with a lambda, but may need to allocate result. This example |uses fuse, which allocates. Note: fuse!1 will transpose the result. |+/ |version(mir_ndslice_test) |@safe pure |unittest { | import mir.ndslice.topology: iota, byDim, map; | import mir.ndslice.fuse: fuse; | import mir.ndslice.slice: sliced; | 1| auto x = [1, 2, 3].sliced; 1| auto y = [1, 2].sliced; | 5| auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 6| auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; 1| assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); |} | |// Ensure works on canonical |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota, canonical; | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto slice = iota(3, 4).canonical; | //-> | // | 3 | | //-> | // | 4 | 1| size_t[1] shape3 = [3]; 1| size_t[1] shape4 = [4]; | | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto x = slice.byDim!0; | static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); | 1| assert(x.shape == shape3); 1| assert(x.front.shape == shape4); 1| assert(x.front == iota(4)); 1| x.popFront; 1| assert(x.front == iota([4], 4)); | | // --------- | // | 0 4 8 | | // | 1 5 9 | | // | 2 6 10 | | // | 3 7 11 | | // --------- 1| auto y = slice.byDim!1; | static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); | 1| assert(y.shape == shape4); 1| assert(y.front.shape == shape3); 1| assert(y.front == iota([3], 0, 4)); 1| y.popFront; 1| assert(y.front == iota([3], 1, 4)); |} | |// Ensure works on universal |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota, universal; | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto slice = iota(3, 4).universal; | //-> | // | 3 | | //-> | // | 4 | 1| size_t[1] shape3 = [3]; 1| size_t[1] shape4 = [4]; | | // ------------ | // | 0 1 2 3 | | // | 4 5 6 7 | | // | 8 9 10 11 | | // ------------ 1| auto x = slice.byDim!0; | static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal))); | 1| assert(x.shape == shape3); 1| assert(x.front.shape == shape4); 1| assert(x.front == iota(4)); 1| x.popFront; 1| assert(x.front == iota([4], 4)); | | // --------- | // | 0 4 8 | | // | 1 5 9 | | // | 2 6 10 | | // | 3 7 11 | | // --------- 1| auto y = slice.byDim!1; | static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal))); | 1| assert(y.shape == shape4); 1| assert(y.front.shape == shape3); 1| assert(y.front == iota([3], 0, 4)); 1| y.popFront; 1| assert(y.front == iota([3], 1, 4)); |} | |// 1-dimensional slice support |@safe @nogc pure nothrow |version(mir_ndslice_test) unittest |{ | import mir.ndslice.topology : iota; | // ------- | // | 0 1 2 | | // ------- 1| auto slice = iota(3); 1| auto x = slice.byDim!0; | static assert (is(typeof(x) == typeof(slice))); 1| assert(x == slice); |} | |/++ |Constructs a new view of an n-dimensional slice with dimension `axis` removed. | |Throws: | `AssertError` if the length of the corresponding dimension doesn' equal 1. |Params: | axis = dimension to remove, if it is single-dimensional | slice = n-dimensional slice |Returns: | new view of a slice with dimension removed |See_also: $(LREF unsqueeze), $(LREF iota). |+/ |template squeeze(sizediff_t axis = 0) |{ | Slice!(Iterator, N - 1, kind != Canonical ? kind : ((axis == N - 1 || axis == -1) ? Universal : (N == 2 ? Contiguous : kind))) | squeeze(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (-sizediff_t(N) <= axis && axis < sizediff_t(N) && N > 1) | in { 18| assert(slice._lengths[axis < 0 ? N + axis : axis] == 1); | } | do { | import mir.utility: swap; | enum sizediff_t a = axis < 0 ? N + axis : axis; 18| typeof(return) ret; | foreach (i; Iota!(0, a)) 15| ret._lengths[i] = slice._lengths[i]; | foreach (i; Iota!(a + 1, N)) 12| ret._lengths[i - 1] = slice._lengths[i]; | static if (kind == Universal) | { | foreach (i; Iota!(0, a)) 5| ret._strides[i] = slice._strides[i]; | foreach (i; Iota!(a + 1, N)) 4| ret._strides[i - 1] = slice._strides[i]; | } | else | static if (kind == Canonical) | { | static if (a == N - 1) | { | foreach (i; Iota!(0, N - 1)) 4| ret._strides[i] = slice._strides[i]; | } | else | { | foreach (i; Iota!(0, a)) 1| ret._strides[i] = slice._strides[i]; | foreach (i; Iota!(a + 1, N - 1)) 1| ret._strides[i - 1] = slice._strides[i]; | } | } 18| swap(ret._iterator, slice._iterator); 18| return ret; | } |} | |/// |version(mir_ndslice_test) |unittest |{ | import mir.ndslice.topology : iota; | import mir.ndslice.allocation : slice; | | // [[0, 1, 2]] -> [0, 1, 2] 1| assert([1, 3].iota.squeeze == [3].iota); | // [[0], [1], [2]] -> [0, 1, 2] 1| assert([3, 1].iota.squeeze!1 == [3].iota); 1| assert([3, 1].iota.squeeze!(-1) == [3].iota); | 1| assert([1, 3].iota.canonical.squeeze == [3].iota); 1| assert([3, 1].iota.canonical.squeeze!1 == [3].iota); 1| assert([3, 1].iota.canonical.squeeze!(-1) == [3].iota); | 1| assert([1, 3].iota.universal.squeeze == [3].iota); 1| assert([3, 1].iota.universal.squeeze!1 == [3].iota); 1| assert([3, 1].iota.universal.squeeze!(-1) == [3].iota); | 1| assert([1, 3, 4].iota.squeeze == [3, 4].iota); 1| assert([3, 1, 4].iota.squeeze!1 == [3, 4].iota); 1| assert([3, 4, 1].iota.squeeze!(-1) == [3, 4].iota); | 1| assert([1, 3, 4].iota.canonical.squeeze == [3, 4].iota); 1| assert([3, 1, 4].iota.canonical.squeeze!1 == [3, 4].iota); 1| assert([3, 4, 1].iota.canonical.squeeze!(-1) == [3, 4].iota); | 1| assert([1, 3, 4].iota.universal.squeeze == [3, 4].iota); 1| assert([3, 1, 4].iota.universal.squeeze!1 == [3, 4].iota); 1| assert([3, 4, 1].iota.universal.squeeze!(-1) == [3, 4].iota); |} | |/++ |Constructs a view of an n-dimensional slice with a dimension added at `axis`. Used |to unsqueeze a squeezed slice. | |Params: | slice = n-dimensional slice | axis = dimension to be unsqueezed (add new dimension), default values is 0, the first dimension |Returns: | unsqueezed n+1-dimensional slice of the same slice kind |See_also: $(LREF squeeze), $(LREF iota). |+/ |Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice, sizediff_t axis) |in { 10| assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N)); |} |do { | import mir.utility: swap; 5| typeof(return) ret; 5| auto a = axis < 0 ? axis + N + 1 : axis; 39| foreach (i; 0 .. a) 8| ret._lengths[i] = slice._lengths[i]; 5| ret._lengths[a] = 1; 15| foreach (i; a .. N) 0000000| ret._lengths[i + 1] = slice._lengths[i]; | static if (kind == Universal) | { 15| foreach (i; 0 .. a) 3| ret._strides[i] = slice._strides[i]; 6| foreach (i; a .. N) 0000000| ret._strides[i + 1] = slice._strides[i]; | } | else | static if (kind == Canonical) | { 1| if (a == N) | { | foreach (i; Iota!(0, N - 1)) 1| ret._strides[i] = slice._strides[i]; 1| ret._strides[N - 1] = 1; | } | else | { 0000000| foreach (i; 0 .. a) 0000000| ret._strides[i] = slice._strides[i]; 0000000| foreach (i; a .. N - 1) 0000000| ret._strides[i + 1] = slice._strides[i]; | } | } 5| swap(ret._iterator, slice._iterator); 5| return ret; |} | |/// ditto |template unsqueeze(sizediff_t axis = 0) |{ | Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | in { 10| assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N)); | } | do { | import mir.utility: swap; 10| typeof(return) ret; | enum a = axis < 0 ? axis + N + 1 : axis; | foreach (i; Iota!a) 8| ret._lengths[i] = slice._lengths[i]; 10| ret._lengths[a] = 1; | foreach (i; Iota!(a, N)) 8| ret._lengths[i + 1] = slice._lengths[i]; | static if (kind == Universal) | { | foreach (i; Iota!a) 3| ret._strides[i] = slice._strides[i]; | foreach (i; Iota!(a, N)) 3| ret._strides[i + 1] = slice._strides[i]; | } | else | static if (kind == Canonical) | { | static if (a == N) | { | foreach (i; Iota!(0, N - 1)) 1| ret._strides[i] = slice._strides[i]; 1| ret._strides[N - 1] = 1; | } | else | { | foreach (i; Iota!(0, a)) | ret._strides[i] = slice._strides[i]; | foreach (i; Iota!(a, N - 1)) 1| ret._strides[i + 1] = slice._strides[i]; | } | } 10| swap(ret._iterator, slice._iterator); 10| return ret; | } |} | |/// |version (mir_ndslice_test) |@safe pure nothrow @nogc |unittest |{ | // [0, 1, 2] -> [[0, 1, 2]] 1| assert([3].iota.unsqueeze == [1, 3].iota); | 1| assert([3].iota.universal.unsqueeze == [1, 3].iota); 1| assert([3, 4].iota.unsqueeze == [1, 3, 4].iota); 1| assert([3, 4].iota.canonical.unsqueeze == [1, 3, 4].iota); 1| assert([3, 4].iota.universal.unsqueeze == [1, 3, 4].iota); | | // [0, 1, 2] -> [[0], [1], [2]] 1| assert([3].iota.unsqueeze(-1) == [3, 1].iota); 1| assert([3].iota.unsqueeze!(-1) == [3, 1].iota); | 1| assert([3].iota.universal.unsqueeze(-1) == [3, 1].iota); 1| assert([3].iota.universal.unsqueeze!(-1) == [3, 1].iota); 1| assert([3, 4].iota.unsqueeze(-1) == [3, 4, 1].iota); 1| assert([3, 4].iota.unsqueeze!(-1) == [3, 4, 1].iota); 1| assert([3, 4].iota.canonical.unsqueeze(-1) == [3, 4, 1].iota); 1| assert([3, 4].iota.canonical.unsqueeze!(-1) == [3, 4, 1].iota); 1| assert([3, 4].iota.universal.unsqueeze(-1) == [3, 4, 1].iota); 1| assert([3, 4].iota.universal.unsqueeze!(-1) == [3, 4, 1].iota); |} | |/++ |Field (element's member) projection. | |Params: | name = element's member name |Returns: | lazy n-dimensional slice of the same shape |See_also: | $(LREF map) |+/ | |template member(string name) | if (name.length) |{ | /++ | Params: | slice = n-dimensional slice composed of structs, classes or unions | Returns: | lazy n-dimensional slice of the same shape | +/ | Slice!(MemberIterator!(Iterator, name), N, kind) member(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | { 7| return typeof(return)(slice._structure, MemberIterator!(Iterator, name)(slice._iterator)); | } | | /// ditto | Slice!(MemberIterator!(T*, name)) member(T)(T[] array) | { | return member(array.sliced); | } | | /// ditto | auto member(T)(T withAsSlice) | if (hasAsSlice!T) | { | return member(withAsSlice.asSlice); | } |} | |/// |version(mir_ndslice_test) |@safe pure unittest |{ | // struct, union or class | struct S | { | // Property support | // Getter always must be defined. | double _x; | double x() @property | { 0000000| return x; | } | void x(double x) @property | { 6| _x = x; | } | | /// Field support | double y; | | /// Zero argument function support | double f() | { 6| return _x * 2; | } | } | | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | 1| auto matrix = slice!S(2, 3); 1| matrix.member!"x"[] = [2, 3].iota; 1| matrix.member!"y"[] = matrix.member!"f"; 1| assert(matrix.member!"y" == [2, 3].iota * 2); |} | |/++ |Functional deep-element wise reduce of a slice composed of fields or iterators. |+/ |template orthogonalReduceField(alias fun) |{ | import mir.functional: naryFun; | static if (__traits(isSame, naryFun!fun, fun)) | { | @optmath: | /++ | Params: | slice = Non empty input slice composed of fields or iterators. | Returns: | a lazy field with each element of which is reduced value of element of the same index of all iterators. | +/ | OrthogonalReduceField!(Iterator, fun, I) orthogonalReduceField(I, Iterator)(I initialValue, Slice!Iterator slice) | { 1| return typeof(return)(slice, initialValue); | } | | /// ditto | OrthogonalReduceField!(T*, fun, I) orthogonalReduceField(I, T)(I initialValue, T[] array) | { 1| return orthogonalReduceField(initialValue, array.sliced); | } | | /// ditto | auto orthogonalReduceField(I, T)(I initialValue, T withAsSlice) | if (hasAsSlice!T) | { | return orthogonalReduceField(initialValue, withAsSlice.asSlice); | } | } | else alias orthogonalReduceField = .orthogonalReduceField!(naryFun!fun); |} | |/// bit array operations |version(mir_ndslice_test) |unittest |{ | import mir.ndslice.slice: slicedField; | import mir.ndslice.allocation: bitSlice; | import mir.ndslice.dynamic: strided; | import mir.ndslice.topology: iota, orthogonalReduceField; 1| auto len = 100; 1| auto a = len.bitSlice; 1| auto b = len.bitSlice; 1| auto c = len.bitSlice; 1| a[len.iota.strided!0(7)][] = true; 1| b[len.iota.strided!0(11)][] = true; 1| c[len.iota.strided!0(13)][] = true; | | // this is valid since bitslices above are oroginal slices of allocated memory. 1| auto and = | orthogonalReduceField!"a & b"(size_t.max, [ | a.iterator._field._field, // get raw data pointers | b.iterator._field._field, | c.iterator._field._field, | ]) // operation on size_t | .bitwiseField | .slicedField(len); | 1| assert(and == (a & b & c)); |} | |/++ |Constructs a lazy view of triplets with `left`, `center`, and `right` members. | |Returns: Slice of the same length composed of $(SUBREF iterator, Triplet) triplets. |The `center` member is type of a slice element. |The `left` and `right` members has the same type as slice. | |The module contains special function $(LREF collapse) to handle |left and right side of triplets in one expression. | |Params: | slice = a slice or an array to iterate over | |Example: |------ |triplets(eeeeee) => | |||c|lllll| ||r|c|llll| ||rr|c|lll| ||rrr|c|ll| ||rrrr|c|l| ||rrrrr|c|| |------ | |See_also: $(LREF stairs). |+/ |Slice!(TripletIterator!(Iterator, kind)) triplets(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice) |{ 5| return typeof(return)(slice.length, typeof(return).Iterator(0, slice)); |} | |/// ditto |Slice!(TripletIterator!(T*)) triplets(T)(scope return T[] slice) |{ 1| return .triplets(slice.sliced); |} | |/// ditto |auto triplets(string type, S)(S slice, size_t n) | if (hasAsSlice!S) |{ | return .triplets(slice.asSlice); |} | |/// |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.topology: triplets, member, iota; | 1| auto a = [4, 5, 2, 8]; 1| auto h = a.triplets; | 1| assert(h[1].center == 5); 1| assert(h[1].left == [4]); 1| assert(h[1].right == [2, 8]); | 1| h[1].center = 9; 1| assert(a[1] == 9); | 1| assert(h.member!"center" == a); | | // `triplets` topology can be used with iota to index a slice 1| auto s = a.sliced; 1| auto w = s.length.iota.triplets[1]; | 1| assert(&s[w.center] == &a[1]); 1| assert(s[w.left].field is a[0 .. 1]); 1| assert(s[w.right].field is a[2 .. $]); |} source/mir/ndslice/topology.d is 98% covered <<<<<< EOF # path=source-mir-ndslice-traits.lst |/++ |$(H2 Multidimensional traits) | |This is a submodule of $(MREF mir,ndslice). | |$(BOOKTABLE $(H2 Function), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 isVector, Test if type is a one-dimensional slice.) |$(T2 isMatrix, Test if type is a two-dimensional slice.) |$(T2 isContiguousSlice, Test if type is a contiguous slice.) |$(T2 isCanonicalSlice, Test if type is a canonical slice.) |$(T2 isUniversalSlice, Test if type is a universal slice.) |$(T2 isContiguousVector, Test if type is a contiguous one-dimensional slice.) |$(T2 isUniversalVector, Test if type is a universal one-dimensional slice.) |$(T2 isContiguousMatrix, Test if type is a contiguous two-dimensional slice.) |$(T2 isCanonicalMatrix, Test if type is a canonical two-dimensional slice.) |$(T2 isUniversalMatrix, Test if type is a universal two-dimensional slice.) |$(T2 isIterator, Test if type is a random access iterator.) |) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: John Hall | | |Macros: |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ | |module mir.ndslice.traits; | |import mir.ndslice.slice : Slice, SliceKind, Contiguous, Universal, Canonical; | |/// Test if type is a one-dimensional slice. |enum bool isVector(T) = is(T : Slice!(Iterator, 1, kind), SliceKind kind, Iterator); | |/// Test if type is a two-dimensional slice. |enum bool isMatrix(T) = is(T : Slice!(Iterator, 2, kind), SliceKind kind, Iterator); | |/// Test if type is a contiguous slice. |enum bool isContiguousSlice(T) = is(T : Slice!(Iterator, N, Contiguous), Iterator, size_t N); | |/// Test if type is a canonical slice. |enum bool isCanonicalSlice(T) = is(T : Slice!(Iterator, N, Canonical), Iterator, size_t N); | |/// Test if type is a universal slice. |enum bool isUniversalSlice(T) = is(T : Slice!(Iterator, N, Universal), Iterator, size_t N); | |/// Test if type is a contiguous one-dimensional slice. |enum bool isContiguousVector(T) = is(T : Slice!(Iterator, 1, Contiguous), Iterator); | |/// Test if type is a universal one-dimensional slice. |enum bool isUniversalVector(T) = is(T : Slice!(Iterator, 1, Universal), Iterator); | |/// Test if type is a contiguous two-dimensional slice. |enum bool isContiguousMatrix(T) = is(T : Slice!(Iterator, 2, Contiguous), Iterator); | |/// Test if type is a canonical two-dimensional slice. |enum bool isCanonicalMatrix(T) = is(T : Slice!(Iterator, 2, Canonical), Iterator); | |/// Test if type is a universal two-dimensional slice. |enum bool isUniversalMatrix(T) = is(T : Slice!(Iterator, 2, Universal), Iterator); | |/// |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | import mir.ndslice.slice : Slice; | | alias S1 = Slice!(int*); | static assert(isContiguousVector!S1); | static assert(!isUniversalVector!S1); | | static assert(!isContiguousMatrix!S1); | static assert(!isCanonicalMatrix!S1); | static assert(!isUniversalMatrix!S1); | | static assert(isVector!S1); | static assert(!isMatrix!S1); | | static assert(isContiguousSlice!S1); | static assert(!isCanonicalSlice!S1); | static assert(!isUniversalSlice!S1); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias S2 = Slice!(float*, 1, Universal); | static assert(!isContiguousVector!S2); | static assert(isUniversalVector!S2); | | static assert(!isContiguousMatrix!S2); | static assert(!isCanonicalMatrix!S2); | static assert(!isUniversalMatrix!S2); | | static assert(isVector!S2); | static assert(!isMatrix!S2); | | static assert(!isContiguousSlice!S2); | static assert(!isCanonicalSlice!S2); | static assert(isUniversalSlice!S2); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias S3 = Slice!(byte*, 2); | static assert(!isContiguousVector!S3); | static assert(!isUniversalVector!S3); | | static assert(isContiguousMatrix!S3); | static assert(!isCanonicalMatrix!S3); | static assert(!isUniversalMatrix!S3); | | static assert(!isVector!S3); | static assert(isMatrix!S3); | | static assert(isContiguousSlice!S3); | static assert(!isCanonicalSlice!S3); | static assert(!isUniversalSlice!S3); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias S4 = Slice!(int*, 2, Canonical); | static assert(!isContiguousVector!S4); | static assert(!isUniversalVector!S4); | | static assert(!isContiguousMatrix!S4); | static assert(isCanonicalMatrix!S4); | static assert(!isUniversalMatrix!S4); | | static assert(!isVector!S4); | static assert(isMatrix!S4); | | static assert(!isContiguousSlice!S4); | static assert(isCanonicalSlice!S4); | static assert(!isUniversalSlice!S4); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias S5 = Slice!(int*, 2, Universal); | static assert(!isContiguousVector!S5); | static assert(!isUniversalVector!S5); | | static assert(!isContiguousMatrix!S5); | static assert(!isCanonicalMatrix!S5); | static assert(isUniversalMatrix!S5); | | static assert(!isVector!S5); | static assert(isMatrix!S5); | | static assert(!isContiguousSlice!S5); | static assert(!isCanonicalSlice!S5); | static assert(isUniversalSlice!S5); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias S6 = Slice!(int*, 3); | | static assert(!isContiguousVector!S6); | static assert(!isUniversalVector!S6); | | static assert(!isContiguousMatrix!S6); | static assert(!isCanonicalMatrix!S6); | static assert(!isUniversalMatrix!S6); | | static assert(!isVector!S6); | static assert(!isMatrix!S6); | | static assert(isContiguousSlice!S6); | static assert(!isCanonicalSlice!S6); | static assert(!isUniversalSlice!S6); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias S7 = Slice!(int*, 3, Canonical); | | static assert(!isContiguousVector!S7); | static assert(!isUniversalVector!S7); | | static assert(!isContiguousMatrix!S7); | static assert(!isCanonicalMatrix!S7); | static assert(!isUniversalMatrix!S7); | | static assert(!isVector!S7); | static assert(!isMatrix!S7); | | static assert(!isContiguousSlice!S7); | static assert(isCanonicalSlice!S7); | static assert(!isUniversalSlice!S7); |} | |@safe pure nothrow @nogc |version(mir_ndslice_test) unittest |{ | alias S8 = Slice!(int*, 3, Universal); | | static assert(!isContiguousVector!S8); | static assert(!isUniversalVector!S8); | | static assert(!isContiguousMatrix!S8); | static assert(!isCanonicalMatrix!S8); | static assert(!isUniversalMatrix!S8); | | static assert(!isVector!S8); | static assert(!isMatrix!S8); | | static assert(!isContiguousSlice!S8); | static assert(!isCanonicalSlice!S8); | static assert(isUniversalSlice!S8); |} | |/// |template isIterator(T) |{ | enum isIterator = __traits(compiles, (T a, T b) | { | sizediff_t diff = a - b; | ++a; | ++b; | --a; | --b; | void foo(V)(auto ref V v) | { | | } | foo(a[sizediff_t(3)]); | auto c = a + sizediff_t(3); | auto d = a - sizediff_t(3); | a += sizediff_t(3); | a -= sizediff_t(3); | foo(*a); | }); |} source/mir/ndslice/traits.d has no code <<<<<< EOF # path=source-mir-numeric.lst |/++ |Base numeric algorithms. | |Reworked part of `std.numeric`. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki (API, findLocalMin, findRoot extension), Don Clugston (findRoot), Lars Tandle Kyllingstad (diff) |+/ |module mir.numeric; | |import mir.internal.utility: isFloatingPoint; |import mir.math.common; |import mir.math.ieee; | |version(D_Exceptions) |{ | private static immutable findRoot_badBounds = new Exception("findRoot: f(ax) and f(bx) must have opposite signs to bracket the root."); | private static immutable findRoot_nanX = new Exception("findRoot/findLocalMin: ax or bx is NaN."); | private static immutable findRoot_nanY = new Exception("findRoot/findLocalMin: f(x) returned NaN."); |} | |/++ |+/ |enum mir_find_root_status |{ | /// Success | success, | /// | badBounds, | /// | nanX, | /// | nanY, |} | |/// ditto |alias FindRootStatus = mir_find_root_status; | |/++ |+/ |struct mir_find_root_result(T) |{ | /// Left bound | T ax = 0; | /// Rifht bound | T bx = 0; | /// `f(ax)` or `f(ax).fabs.fmin(T.max / 2).copysign(f(ax))`. | T ay = 0; | /// `f(bx)` or `f(bx).fabs.fmin(T.max / 2).copysign(f(bx))`. | T by = 0; | /// Amount of target function calls. | uint iterations; | |@safe pure @nogc scope const @property: | | /++ | Returns: self | Required_versions:`D_Exceptions` | Throws: `Exception` if $(LREF FindRootResult.status) isn't $(LREF mir_find_root_status.success). | +/ | version(D_Exceptions) | ref validate() inout return | { 80| with(FindRootStatus) final switch(status) | { 160| case success: return this; 0000000| case badBounds: throw findRoot_badBounds; 0000000| case nanX: throw findRoot_nanX; 0000000| case nanY: throw findRoot_nanY; | } | } | |extern(C++) nothrow: | | /++ | Returns: $(LREF mir_find_root_status) | +/ | FindRootStatus status() | { 84| with(FindRootStatus) return 84| ax != ax || bx != bx ? nanX : 84| ay != ay || by != by ? nanY : 43| ay.signbit == by.signbit && ay != 0 && by != 0 ? badBounds : | success; | } | | /++ | A bound that corresponds to the minimal absolute function value. | | Returns: `!(ay.fabs > by.fabs) ? ax : bx` | +/ | T x() | { 40| return !(ay.fabs > by.fabs) ? ax : bx; | } | | /++ | The minimal of absolute function values. | | Returns: `!(ay.fabs > by.fabs) ? ay : by` | +/ | T y() | { 0000000| return !(ay.fabs > by.fabs) ? ay : by; | } |} | |/// ditto |alias FindRootResult = mir_find_root_result; | |/++ |Find root of a real function f(x) by bracketing, allowing the |termination condition to be specified. | |Given a function `f` and a range `[a .. b]` such that `f(a)` |and `f(b)` have opposite signs or at least one of them equals ±0, |returns the value of `x` in |the range which is closest to a root of `f(x)`. If `f(x)` |has more than one root in the range, one will be chosen |arbitrarily. If `f(x)` returns NaN, NaN will be returned; |otherwise, this algorithm is guaranteed to succeed. | |Uses an algorithm based on TOMS748, which uses inverse cubic |interpolation whenever possible, otherwise reverting to parabolic |or secant interpolation. Compared to TOMS748, this implementation |improves worst-case performance by a factor of more than 100, and |typical performance by a factor of 2. For 80-bit reals, most |problems require 8 to 15 calls to `f(x)` to achieve full machine |precision. The worst-case performance (pathological cases) is |approximately twice the number of bits. | |References: "On Enclosing Simple Roots of Nonlinear Equations", |G. Alefeld, F.A. Potra, Yixun Shi, Mathematics of Computation 61, |pp733-744 (1993). Fortran code available from $(HTTP |www.netlib.org,www.netlib.org) as algorithm TOMS478. | |Params: |f = Function to be analyzed. `f(ax)` and `f(bx)` should have opposite signs, or `lowerBound` and/or `upperBound` | should be defined to perform initial interval extension. |tolerance = Defines an early termination condition. Receives the | current upper and lower bounds on the root. The | delegate must return `true` when these bounds are | acceptable. If this function always returns `false` or | it is null, full machine precision will be achieved. |ax = Left inner bound of initial range of `f` known to contain the root. |bx = Right inner bound of initial range of `f` known to contain the root. Can be equal to `ax`. |fax = Value of `f(ax)` (optional). |fbx = Value of `f(bx)` (optional). |lowerBound = Lower outer bound for interval extension (optional) |upperBound = Upper outer bound for interval extension (optional) |maxIterations = Appr. maximum allowed number of function calls (inluciding function calls for grid). |steps = Number of nodes in the left side and right side regular grids (optional). If it equals to `0` (default), | then the algorithm uses `ieeeMean` for searching. The algorithm performs searching if | `sgn(fax)` equals to `sgn(fbx)` and at least one outer bound allows to extend the inner initial range. | |Returns: $(LREF FindRootResult) |+/ |@fmamath |FindRootResult!T findRoot(alias f, alias tolerance = null, T)( | const T ax, | const T bx, | const T fax = T.nan, | const T fbx = T.nan, | const T lowerBound = T.nan, | const T upperBound = T.nan, | uint maxIterations = T.sizeof * 16, | uint steps = 0, | ) | if ( | isFloatingPoint!T && __traits(compiles, T(f(T.init))) && | ( | is(typeof(tolerance) == typeof(null)) || | __traits(compiles, {auto _ = bool(tolerance(T.init, T.init));} | ) | )) |{ 89| if (false) // break attributes | T y = f(T(1)); 89| scope funInst = delegate(T x) { 1188| return T(f(x)); | }; 89| scope fun = funInst.trustedAllAttr; | | static if (is(typeof(tolerance) == typeof(null))) | { | alias tol = tolerance; | } | else | { 2| if (false) // break attributes | bool b = tolerance(T(1), T(1)); 40| scope tolInst = delegate(T a, T b) { return bool(tolerance(a, b)); }; 2| scope tol = tolInst.trustedAllAttr; | } 89| return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, fun, tol); |} | |/// |// @nogc |version(mir_test) @safe unittest |{ | import mir.math.common: log, exp, fabs; | 1| auto logRoot = findRoot!log(0, double.infinity).validate.x; 1| assert(logRoot == 1); | 1| auto shift = 1; 4| auto expm1Root = findRoot!(x => exp(x) - shift) | (-double.infinity, double.infinity).validate.x; 1| assert(expm1Root == 0); | 15| auto approxLogRoot = findRoot!(log, (a, b) => fabs(a - b) < 1e-5)(0, double.infinity).validate.x; 1| assert(fabs(approxLogRoot - 1) < 1e-5); |} | |/// With adaptive bounds |version(mir_test) @safe unittest |{ | import mir.math.common: log, exp, fabs; | 1| auto logRoot = findRoot!log( | 10, 10, // assume we have one initial point | double.nan, double.nan, // fa, fb aren't provided by user | -double.infinity, double.infinity, // all space is available for the bounds extension. | ).validate.x; 1| assert(logRoot == 1); | 1| auto shift = 1; 4| alias expm1Fun = (double x) => exp(x) - shift; 1| auto expm1RootRet = findRoot!expm1Fun | ( | 11, 10, // reversed order for interval is always OK | expm1Fun(11), expm1Fun(10), // the order must be the same as bounds | 0, double.infinity, // space for the bounds extension. | ).validate; 1| assert(expm1Fun(expm1RootRet.x) == 0); | 25| auto approxLogRoot = findRoot!(log, (a, b) => fabs(a - b) < 1e-5)( | -1e10, +1e10, | double.nan, double.nan, | 0, double.infinity, | ).validate.x; 1| assert(fabs(approxLogRoot - 1) < 1e-5); |} | |/// ditto |version(mir_test) |unittest |{ | import core.stdc.tgmath: atan; | import mir.math; | import std.meta: AliasSeq; | 1| const double[2][3] boundaries = [ | [0.4, 0.6], | [1.4, 1.6], | [0.1, 2.1]]; | | enum root = 1.0; | | foreach(fun; AliasSeq!( 40| (double x) => x ^^ 2 - root, 40| (double x) => root - x ^^ 2, 47| (double x) => atan(x - root), | )) | { 36| foreach(ref bounds; boundaries) | { 9| auto result = findRoot!fun( | bounds[0], bounds[1], | double.nan, double.nan, // f(a) and f(b) not provided | -double.max, double.max, // user provided outer bounds | ); 9| assert(result.validate.x == root); | } | } | 12| foreach(ref bounds; boundaries) | { 27| auto result = findRoot!(x => sin(x - root))( | bounds[0], bounds[1], | double.nan, double.nan, // f(a) and f(b) not provided | -10, 10, // user provided outer bounds | 100, // max iterations, | 10, // steps for grid | ); 3| assert(result.validate.x == root); | } | // single initial point, grid, positive direction 7| auto result = findRoot!((double x ) => sin(x - root))( | 0.1, 0.1, // initial point, a = b | double.nan, double.nan, // f(a) = f(b) not provided | -100.0, 100.0, // user provided outer bounds | 150, // max iterations, | 100, // number of steps for grid | ); 1| assert(result.validate.x == root); | | // single initial point, grid, negative direction 12| result = findRoot!((double x ) => sin(x - root))( | 0.1, 0.1, // initial point a = b | double.nan, double.nan, // f(a) = f(b) not provided | 100.0, -100.0, // user provided outer bounds, reversed order | 150, // max iterations, | 100, // number of steps for grid | ); 1| assert(result.validate.x == double(root - PI)); // Left side root! |} | |/++ |With adaptive bounds and single initial point. |Reverse outer bound order controls first step direction |in case of `f(a) == f(b)`. |+/ |version(mir_test) |unittest |{ | enum root = 1.0; | | // roots are +/- `root` 42| alias fun = (double x) => x * x - root; | 1| double lowerBound = -10.0; 1| double upperBound = 10.0; | 1| assert( | findRoot!fun( | 0, 0, // initial interval | double.nan, double.nan, | lowerBound, upperBound, | // positive direction has higher priority | ).validate.x == root | ); | 1| assert( | findRoot!fun( | 0, 0, // initial interval | double.nan, double.nan, | upperBound, lowerBound, | // reversed order | ).validate.x == -root // other root | ); |} | |/// $(LREF findRoot) implementations. |export @fmamath FindRootResult!float findRootImpl( | float ax, | float bx, | float fax, | float fbx, | float lowerBound, | float upperBound, | uint maxIterations, | uint steps, | scope float delegate(float) @safe pure nothrow @nogc f, | scope bool delegate(float, float) @safe pure nothrow @nogc tolerance, //can be null |) @safe pure nothrow @nogc |{ | pragma(inline, false); 3| return findRootImplGen!float(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, f, tolerance); |} | |/// ditto |export @fmamath FindRootResult!double findRootImpl( | double ax, | double bx, | double fax, | double fbx, | double lowerBound, | double upperBound, | uint maxIterations, | uint steps, | scope double delegate(double) @safe pure nothrow @nogc f, | scope bool delegate(double, double) @safe pure nothrow @nogc tolerance, //can be null |) @safe pure nothrow @nogc |{ | pragma(inline, false); 28| return findRootImplGen!double(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, f, tolerance); |} | |/// ditto |export @fmamath FindRootResult!real findRootImpl( | real ax, | real bx, | real fax, | real fbx, | real lowerBound, | real upperBound, | uint maxIterations, | uint steps, | scope real delegate(real) @safe pure nothrow @nogc f, | scope bool delegate(real, real) @safe pure nothrow @nogc tolerance, //can be null |) @safe pure nothrow @nogc |{ | pragma(inline, false); 58| return findRootImplGen!real(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, f, tolerance); |} | |private @fmamath FindRootResult!T findRootImplGen(T)( | T a, | T b, | T fa, | T fb, | T lb, | T ub, | uint maxIterations, | uint steps, | scope const T delegate(T) @safe pure nothrow @nogc f, | scope const bool delegate(T, T) @safe pure nothrow @nogc tolerance, //can be null |) @safe pure nothrow @nogc | if (isFloatingPoint!T) |{ | version(LDC) pragma(inline, true); | // Author: Don Clugston. This code is (heavily) modified from TOMS748 | // (www.netlib.org). The changes to improve the worst-cast performance are | // entirely original. | | // Author: Ilia Ki (Bounds extension logic, | // API improvements, infinity and huge numbers handing, compiled code size reduction) | 89| T d; // [a .. b] is our current bracket. d is the third best guess. 89| T fd; // Value of f at d. 89| bool done = false; // Has a root been found? 89| uint iterations; | | static void swap(ref T a, ref T b) | { 12| T t = a; a = b; b = t; | } | | bool exit() | { | pragma(inline, false); 1134| return done 1081| || iterations >= maxIterations 1081| || b == nextUp(a) 1084| || tolerance !is null && tolerance(a, b); | } | | // Test the function at point c; update brackets accordingly | void bracket(T c) | { | pragma(inline, false); 979| T fc = f(c); 979| iterations++; 1911| if (fc == 0 || fc != fc) // Exact solution, or NaN | { 47| a = c; 47| fa = fc; 47| d = c; 47| fd = fc; 47| done = true; 47| return; | } | 932| fc = fc.fabs.fmin(T.max / 2).copysign(fc); | | // Determine new enclosing interval 932| if (signbit(fa) != signbit(fc)) | { 461| d = b; 461| fd = fb; 461| b = c; 461| fb = fc; | } | else | { 471| d = a; 471| fd = fa; 471| a = c; 471| fa = fc; | } | } | | /* Perform a secant interpolation. If the result would lie on a or b, or if | a and b differ so wildly in magnitude that the result would be meaningless, | perform a bisection instead. | */ | static T secantInterpolate(T a, T b, T fa, T fb) | { | pragma(inline, false); 202| if (a - b == a && b != 0 269| || b - a == b && a != 0) | { | // Catastrophic cancellation 13| return ieeeMean(a, b); | } | // avoid overflow 183| T m = fa - fb; 183| T wa = fa / m; 183| T wb = fb / m; 183| T c = b * wa - a * wb; 594| if (c == a || c == b || c != c || c.fabs == T.infinity) 67| c = a.half + b.half; 183| return c; | } | | /* Uses 'numsteps' newton steps to approximate the zero in [a .. b] of the | quadratic polynomial interpolating f(x) at a, b, and d. | Returns: | The approximate zero in [a .. b] of the quadratic polynomial. | */ | T newtonQuadratic(int numsteps) | { | // Find the coefficients of the quadratic polynomial. 236| const T a0 = fa; 236| const T a1 = (fb - fa) / (b - a); 236| const T a2 = ((fd - fb) / (d - b) - a1) / (d - a); | | // Determine the starting point of newton steps. 236| T c = a2.signbit != fa.signbit ? a : b; | | // start the safeguarded newton steps. 2437| foreach (int i; 0 .. numsteps) | { 577| const T pc = a0 + (a1 + a2 * (c - b))*(c - a); 577| const T pdc = a1 + a2*((2 * c) - (a + b)); 577| if (pdc == 0) 1| return a - a0 / a1; | else 576| c = c - pc / pdc; | } 235| return c; | } | | // Starting with the second iteration, higher-order interpolation can | // be used. 89| int itnum = 1; // Iteration number 89| int baditer = 1; // Num bisections to take if an iteration is bad. 178| T c, e; // e is our fourth best guess 89| T fe; 89| bool left; | | // Allow a and b to be provided in reverse order 89| if (a > b) | { 1| swap(a, b); 1| swap(fa, fb); | } | 178| if (a != a || b != b) | { 0000000| done = true; 0000000| goto whileloop; | } | 89| if (lb != lb) | { 70| lb = a; | } | 89| if (ub != ub) | { 70| ub = b; | } | 89| if (lb > ub) | { 2| swap(lb, ub); 2| left = true; | } | 89| if (lb == -T.infinity) | { 2| lb = -T.max; | } | 89| if (ub == +T.infinity) | { 6| ub = +T.max; | } | 89| if (!(lb <= a)) | { 2| a = lb; 2| fa = T.nan; | } | 89| if (!(b <= ub)) | { 3| a = lb; 3| fa = T.nan; | } | 89| if (fa != fa) | { 87| fa = f(a); 87| iterations++; | } | | // On the first iteration we take a secant step: 175| if (fa == 0 || fa != fa) | { 3| done = true; 3| b = a; 3| fb = fa; 3| goto whileloop; | } | 86| if (fb != fb) | { 84| if (a == b) | { 5| fb = fa; | } | else | { 79| fb = f(b); 79| iterations++; | } | } | 171| if (fb == 0 || fb != fb) | { 1| done = true; 1| a = b; 1| fa = fb; 1| goto whileloop; | } | 85| if (fa.fabs < fb.fabs) | { 35| left = true; | } | else 50| if (fa.fabs > fb.fabs) | { 22| left = false; | } | | // extend inner boundaries 85| if (fa.signbit == fb.signbit) | { 15| T lx = a; 15| T ux = b; 15| T ly = fa; 15| T uy = fb; 15| const sb = fa.signbit; | | import mir.ndslice.topology: linspace; | 30| typeof(linspace!T([2], [lx, lb])) lgrid, ugrid; 15| if (steps) | { 4| lgrid = linspace!T([steps + 1], [lx, lb]); 4| lgrid.popFront; 4| ugrid = linspace!T([steps + 1], [ux, ub]); 4| ugrid.popFront; | } | 15| for(T mx;;) | { 44| bool lw = lb < lx; 44| bool uw = ux < ub; | 88| if (!lw && !uw || iterations >= maxIterations) | { 1| done = true; 1| goto whileloop; | } | 129| if (lw && (!uw || left)) | { 23| if (lgrid.empty) | { 19| mx = ieeeMean(lb, lx); 19| if (mx == lx) 0000000| mx = lb; | } | else | { 4| mx = lgrid.front; 4| lgrid.popFront; 4| if (mx == lx) 0000000| continue; | } 23| T my = f(mx); 23| iterations++; 23| if (my == 0) | { 1| a = b = mx; 1| fa = fb = my; 1| done = true; 1| goto whileloop; | } 22| if (mx != mx) | { 0000000| lb = mx; 0000000| continue; | } 22| if (my.signbit == sb) | { 15| lx = mx; 15| ly = my; 15| if (lgrid.empty) | { 13| left = ly.fabs < uy.fabs; | } 15| continue; | } 7| a = mx; 7| fa = my; 7| b = lx; 7| fb = ly; 7| break; | } | else | { 20| if (ugrid.empty) | { 18| mx = ieeeMean(ub, ux); 18| if (mx == ux) 0000000| mx = ub; | } | else | { 2| mx = ugrid.front; 2| ugrid.popFront; 2| if (mx == ux) 0000000| continue; | } 20| T my = f(mx); 20| iterations++; 20| if (my == 0) | { 0000000| a = b = mx; 0000000| fa = fb = my; 0000000| done = true; 0000000| goto whileloop; | } 20| if (mx != mx) | { 0000000| ub = mx; 0000000| continue; | } 20| if (my.signbit == sb) | { 14| ux = mx; 14| uy = my; 14| if (ugrid.empty) | { 14| left = !(ly.fabs > uy.fabs); | } 14| continue; | } 6| b = mx; 6| fb = my; 6| a = ux; 6| fa = uy; 6| break; | } | } | } | 83| fa = fa.fabs.fmin(T.max / 2).copysign(fa); 83| fb = fb.fabs.fmin(T.max / 2).copysign(fb); 83| bracket(secantInterpolate(a, b, fa, fb)); | |whileloop: 354| while (!exit) | { 692| T a0 = a, b0 = b; // record the brackets | | // Do two higher-order (cubic or parabolic) interpolation steps. 2487| foreach (int QQ; 0 .. 2) | { | // Cubic inverse interpolation requires that | // all four function values fa, fb, fd, and fe are distinct; | // otherwise use quadratic interpolation. 1715| bool distinct = (fa != fb) && (fa != fd) && (fa != fe) 1604| && (fb != fd) && (fb != fe) && (fd != fe); | // The first time, cubic interpolation is impossible. 662| if (itnum<2) distinct = false; 581| bool ok = distinct; 581| if (distinct) | { | // Cubic inverse interpolation of f(x) at a, b, d, and e 452| const q11 = (d - e) * fd / (fe - fd); 452| const q21 = (b - d) * fb / (fd - fb); 452| const q31 = (a - b) * fa / (fb - fa); 452| const d21 = (b - d) * fd / (fd - fb); 452| const d31 = (a - b) * fb / (fb - fa); | 452| const q22 = (d21 - q11) * fb / (fe - fb); 452| const q32 = (d31 - q21) * fa / (fd - fa); 452| const d32 = (d31 - q21) * fd / (fd - fa); 452| const q33 = (d32 - q22) * fa / (fe - fa); 452| c = a + (q31 + q32 + q33); 1298| if (c != c || (c <= a) || (c >= b)) | { | // DAC: If the interpolation predicts a or b, it's | // probable that it's the actual root. Only allow this if | // we're already close to the root. 125| if (c == a && (a - b != a || a - b != -b)) | { 9| auto down = !(a - b != a); 9| if (down) 0000000| c = -c; 9| c = c.nextUp; 9| if (down) 0000000| c = -c; | } | else | { 107| ok = false; | } | | } | } 581| if (!ok) | { | // DAC: Alefeld doesn't explain why the number of newton steps | // should vary. 236| c = newtonQuadratic(2 + distinct); 656| if (c != c || (c <= a) || (c >= b)) | { | // Failure, try a secant step: 113| c = secantInterpolate(a, b, fa, fb); | } | } 581| ++itnum; 581| e = d; 581| fe = fd; 581| bracket(c); 581| if (exit) 66| break whileloop; 515| if (itnum == 2) 81| continue whileloop; | } | | // Now we take a double-length secant step: 199| T u; 199| T fu; 199| if (fabs(fa) < fabs(fb)) | { 108| u = a; 108| fu = fa; | } | else | { 91| u = b; 91| fu = fb; | } 199| c = u - 2 * (fu / (fb - fa)) * (b - a); | | // DAC: If the secant predicts a value equal to an endpoint, it's | // probably false. 738| if (c == a || c == b || c != c || fabs(c - u) > (b - a) * 0.5f) | { 74| if ((a - b) == a || (b - a) == b) | { 10| c = ieeeMean(a, b); | } | else | { 27| c = a.half + b.half; | } | } 199| e = d; 199| fe = fd; 199| bracket(c); 199| if (exit) 15| break; | | // IMPROVE THE WORST-CASE PERFORMANCE | // We must ensure that the bounds reduce by a factor of 2 | // in binary space! every iteration. If we haven't achieved this | // yet, or if we don't yet know what the exponent is, | // perform a binary chop. | 184| if ((a == 0 182| || b == 0 182| || fabs(a) >= 0.5f * fabs(b) 148| && fabs(b) >= 0.5f * fabs(a)) 146| && b - a < 0.25f * (b0 - a0)) | { 131| baditer = 1; 131| continue; | } | | // DAC: If this happens on consecutive iterations, we probably have a | // pathological function. Perform a number of bisections equal to the | // total number of consecutive bad iterations. | 53| if (b - a < 0.25f * (b0 - a0)) 12| baditer = 1; 507| foreach (int QQ; 0 .. baditer) | { 116| e = d; 116| fe = fd; 116| bracket(ieeeMean(a, b)); | } 53| ++baditer; | } 89| return typeof(return)(a, b, fa, fb, iterations); |} | |version(mir_test) @safe unittest |{ | import mir.math.constant; | 1| int numProblems = 0; 1| int numCalls; | | void testFindRoot(real delegate(real) @nogc @safe nothrow pure f , real x1, real x2, int line = __LINE__) //@nogc @safe nothrow pure | { | //numCalls=0; | //++numProblems; 116| assert(x1 == x1 && x2 == x2); 58| auto result = findRoot!f(x1, x2).validate; | 58| auto flo = f(result.ax); 58| auto fhi = f(result.bx); 58| if (flo != 0) | { 34| assert(flo.signbit != fhi.signbit); | } | } | | // Test functions | real cubicfn(real x) @nogc @safe nothrow pure | { | //++numCalls; 45| if (x>float.max) 1| x = float.max; 45| if (x<-float.max) 3| x = -float.max; | // This has a single real root at -59.286543284815 45| return 0.386*x*x*x + 23*x*x + 15.7*x + 525.2; | } | // Test a function with more than one root. 34| real multisine(real x) { ++numCalls; return sin(x); } 1| testFindRoot( &multisine, 6, 90); 1| testFindRoot(&cubicfn, -100, 100); 1| testFindRoot( &cubicfn, -double.max, real.max); | | |/* Tests from the paper: | * "On Enclosing Simple Roots of Nonlinear Equations", G. Alefeld, F.A. Potra, | * Yixun Shi, Mathematics of Computation 61, pp733-744 (1993). | */ | // Parameters common to many alefeld tests. 1| int n; 2| real ale_a, ale_b; | 1| int powercalls = 0; | | real power(real x) | { 0000000| ++powercalls; 0000000| ++numCalls; 0000000| return pow(x, n) + double.min_normal; | } 1| int [] power_nvals = [3, 5, 7, 9, 19, 25]; | // Alefeld paper states that pow(x, n) is a very poor case, where bisection | // outperforms his method, and gives total numcalls = | // 921 for bisection (2.4 calls per bit), 1830 for Alefeld (4.76/bit), | // 0.5f624 for brent (6.8/bit) | // ... but that is for double, not real80. | // This poor performance seems mainly due to catastrophic cancellation, | // which is avoided here by the use of ieeeMean(). | // I get: 231 (0.48/bit). | // IE this is 10X faster in Alefeld's worst case 1| numProblems=0; 21| foreach (k; power_nvals) | { 6| n = k; | //testFindRoot(&power, -1, 10); | } | 1| int powerProblems = numProblems; | | // Tests from Alefeld paper | 1| int [9] alefeldSums; | real alefeld0(real x) | { 174| ++alefeldSums[0]; 174| ++numCalls; 174| real q = sin(x) - x/2; 6960| for (int i=1; i<20; ++i) 3306| q+=(2*i-5.0)*(2*i-5.0) / ((x-i*i)*(x-i*i)*(x-i*i)); 174| return q; | } | real alefeld1(real x) | { 36| ++numCalls; 36| ++alefeldSums[1]; 36| return ale_a*x + exp(ale_b * x); | } | real alefeld2(real x) | { 202| ++numCalls; 202| ++alefeldSums[2]; 202| return pow(x, n) - ale_a; | } | real alefeld3(real x) | { 60| ++numCalls; 60| ++alefeldSums[3]; 60| return (1.0 +pow(1.0L-n, 2))*x - pow(1.0L-n*x, 2); | } | real alefeld4(real x) | { 82| ++numCalls; 82| ++alefeldSums[4]; 82| return x*x - pow(1-x, n); | } | real alefeld5(real x) | { 81| ++numCalls; 81| ++alefeldSums[5]; 81| return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4); | } | real alefeld6(real x) | { 91| ++numCalls; 91| ++alefeldSums[6]; 91| return exp(-n*x)*(x-1.01L) + pow(x, n); | } | real alefeld7(real x) | { 73| ++numCalls; 73| ++alefeldSums[7]; 73| return (n*x-1) / ((n-1)*x); | } | 1| numProblems=0; 1| testFindRoot(&alefeld0, PI_2, PI); 22| for (n=1; n <= 10; ++n) | { 10| testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); | } 2| ale_a = -40; ale_b = -1; 1| testFindRoot(&alefeld1, -9, 31); 2| ale_a = -100; ale_b = -2; 1| testFindRoot(&alefeld1, -9, 31); 2| ale_a = -200; ale_b = -3; 1| testFindRoot(&alefeld1, -9, 31); 1| int [] nvals_3 = [1, 2, 5, 10, 15, 20]; 1| int [] nvals_5 = [1, 2, 4, 5, 8, 15, 20]; 1| int [] nvals_6 = [1, 5, 10, 15, 20]; 1| int [] nvals_7 = [2, 5, 15, 20]; | 10| for (int i=4; i<12; i+=2) | { 4| n = i; 4| ale_a = 0.2; 4| testFindRoot(&alefeld2, 0, 5); 4| ale_a=1; 4| testFindRoot(&alefeld2, 0.95, 4.05); 4| testFindRoot(&alefeld2, 0, 1.5); | } 21| foreach (i; nvals_3) | { 6| n=i; 6| testFindRoot(&alefeld3, 0, 1); | } 21| foreach (i; nvals_3) | { 6| n=i; 6| testFindRoot(&alefeld4, 0, 1); | } 24| foreach (i; nvals_5) | { 7| n=i; 7| testFindRoot(&alefeld5, 0, 1); | } 18| foreach (i; nvals_6) | { 5| n=i; 5| testFindRoot(&alefeld6, 0, 1); | } 15| foreach (i; nvals_7) | { 4| n=i; 4| testFindRoot(&alefeld7, 0.01L, 1); | } | real worstcase(real x) | { 111| ++numCalls; 111| return x<0.3*real.max? -0.999e-3: 1.0; | } 1| testFindRoot(&worstcase, -real.max, real.max); | | // just check that the double + float cases compile 2| findRoot!(x => 0)(-double.max, double.max); 2| findRoot!(x => -0.0)(-float.max, float.max); |/* | int grandtotal=0; | foreach (calls; alefeldSums) | { | grandtotal+=calls; | } | grandtotal-=2*numProblems; | printf("\nALEFELD TOTAL = %d avg = %f (alefeld avg=19.3 for double)\n", | grandtotal, (1.0*grandtotal)/numProblems); | powercalls -= 2*powerProblems; | printf("POWER TOTAL = %d avg = %f ", powercalls, | (1.0*powercalls)/powerProblems); |*/ | //Issue 14231 2| auto xp = findRoot!(x => x)(0f, 1f); 3| auto xn = findRoot!(x => x)(-1f, -0f); |} | |/++ |+/ |struct FindLocalMinResult(T) |{ | /// | T x = 0; | /// | T y = 0; | /// | T error = 0; | |@safe pure @nogc scope const @property: | | /++ | Returns: self | Required_versions:`D_Exceptions` | Throws: `Exception` if $(LREF FindRootResult.status) isn't $(LREF mir_find_root_status.success). | +/ | version(D_Exceptions) | ref validate() return | { 18| with(FindRootStatus) final switch(status) | { 36| case success: return this; 0000000| case badBounds: throw findRoot_badBounds; 0000000| case nanX: throw findRoot_nanX; 0000000| case nanY: throw findRoot_nanY; | } | } | |extern(C++) nothrow: | | /++ | Returns: $(LREF mir_find_root_status) | +/ | FindRootStatus status() | { 21| with(FindRootStatus) return | x != x ? nanX : | y != y ? nanY : | success; | } |} | |/++ |Find a real minimum of a real function `f(x)` via bracketing. |Given a function `f` and a range `(ax .. bx)`, |returns the value of `x` in the range which is closest to a minimum of `f(x)`. |`f` is never evaluted at the endpoints of `ax` and `bx`. |If `f(x)` has more than one minimum in the range, one will be chosen arbitrarily. |If `f(x)` returns NaN or -Infinity, `(x, f(x), NaN)` will be returned; |otherwise, this algorithm is guaranteed to succeed. |Params: | f = Function to be analyzed | ax = Left bound of initial range of f known to contain the minimum. | bx = Right bound of initial range of f known to contain the minimum. | relTolerance = Relative tolerance. | absTolerance = Absolute tolerance. | N = number of addition inner points of uniform grid. | The algorithm computes function value for the N points. | Then reassing ax to the point that preceeds the grid's argmin, | reassing bx to the point that follows the the grid's argmin. | The search interval reduces (N + 1) / 2 times. |Preconditions: | `ax` and `bx` shall be finite reals. $(BR) | `relTolerance` shall be normal positive real. $(BR) | `absTolerance` shall be normal positive real no less then `T.epsilon*2`. |Returns: | A $(LREF FindLocalMinResult) consisting of `x`, `y = f(x)` and `error = 3 * (absTolerance * fabs(x) + relTolerance)`. | The method used is a combination of golden section search and |successive parabolic interpolation. Convergence is never much slower |than that for a Fibonacci search. |References: | "Algorithms for Minimization without Derivatives", Richard Brent, Prentice-Hall, Inc. (1973) |See_Also: $(LREF findRoot), $(REF isNormal, std,math) |+/ |FindLocalMinResult!T findLocalMin(alias f, T)( | const T ax, | const T bx, | const T relTolerance = sqrt(T.epsilon), | const T absTolerance = sqrt(T.epsilon), | size_t N = 0, |) | if (isFloatingPoint!T && __traits(compiles, T(f(T.init)))) |{ 22| if (false) // break attributes | { | T y = f(T(123)); | } 22| scope funInst = delegate(T x) { 98451| return T(f(x)); | }; 22| scope fun = funInst.trustedAllAttr; | 22| return findLocalMinImpl(ax, bx, relTolerance, absTolerance, fun, N); |} | |@fmamath |private FindLocalMinResult!float findLocalMinImpl( | const float ax, | const float bx, | const float relTolerance, | const float absTolerance, | scope const float delegate(float) @safe pure nothrow @nogc f, | size_t N, | ) @safe pure nothrow @nogc |{ | pragma(inline, false); 6| return findLocalMinImplGen!float(ax, bx, relTolerance, absTolerance, f, N); |} | |@fmamath |private FindLocalMinResult!double findLocalMinImpl( | const double ax, | const double bx, | const double relTolerance, | const double absTolerance, | scope const double delegate(double) @safe pure nothrow @nogc f, | size_t N, | ) @safe pure nothrow @nogc |{ | pragma(inline, false); 10| return findLocalMinImplGen!double(ax, bx, relTolerance, absTolerance, f, N); |} | |@fmamath |private FindLocalMinResult!real findLocalMinImpl( | const real ax, | const real bx, | const real relTolerance, | const real absTolerance, | scope const real delegate(real) @safe pure nothrow @nogc f, | size_t N, | ) @safe pure nothrow @nogc |{ | pragma(inline, false); 6| return findLocalMinImplGen!real(ax, bx, relTolerance, absTolerance, f, N); |} | |@fmamath |private FindLocalMinResult!T findLocalMinImplGen(T)( | T ax, | T bx, | const T relTolerance, | const T absTolerance, | scope const T delegate(T) @safe pure nothrow @nogc f, | size_t N, | ) | if (isFloatingPoint!T && __traits(compiles, {T _ = f(T.init);})) |in |{ 44| assert(relTolerance.fabs >= T.min_normal && relTolerance.fabs < T.infinity, "relTolerance is not normal floating point number"); 44| assert(absTolerance.fabs >= T.min_normal && absTolerance.fabs < T.infinity, "absTolerance is not normal floating point number"); 22| assert(relTolerance >= 0, "absTolerance is not positive"); 22| assert(absTolerance >= T.epsilon*2, "absTolerance is not greater then `2*T.epsilon`"); |} |do |{ 22| if (N > 1) | { | import mir.ndslice.topology: linspace; | 1| auto xPoints = linspace!T([N + 2], [ax, bx]); 1| size_t idx; 1| double value = double.infinity; 33| foreach (i; 0 .. N) | { 10| auto y = f(xPoints[i + 1]); 10| if (y < value) | { 1| value = y; 1| idx = i; | } | } 1| ax = xPoints[idx + 0]; 1| bx = xPoints[idx + 2]; | } | | version(LDC) pragma(inline, true); | // c is the squared inverse of the golden ratio | // (3 - sqrt(5))/2 | // Value obtained from Wolfram Alpha. | enum T c = 0x0.61c8864680b583ea0c633f9fa31237p+0L; | enum T cm1 = 0x0.9e3779b97f4a7c15f39cc0605cedc8p+0L; 22| T tolerance; 22| T a = ax > bx ? bx : ax; 22| T b = ax > bx ? ax : bx; 22| if (a < -T.max) 0000000| a = -T.max; 22| if (b > +T.max) 0000000| b = +T.max; | // sequence of declarations suitable for SIMD instructions 22| T v = a * cm1 + b * c; 22| assert(v.fabs < T.infinity); 22| T fv = v == v ? f(v) : v; 41| if (fv != fv || fv == -T.infinity) | { 3| return typeof(return)(v, fv, T.init); | } 19| T w = v; 19| T fw = fv; 19| T x = v; 19| T fx = fv; 19| size_t i; 38| for (T d = 0, e = 0;;) | { 98438| i++; 98438| T m = (a + b) * 0.5f; | // This fix is not part of the original algorithm 98438| if (!(m.fabs < T.infinity)) // fix infinity loop. Issue can be reproduced in R. | { 0000000| m = a.half + b.half; | } 98438| tolerance = absTolerance * fabs(x) + relTolerance; 98438| const t2 = tolerance * 2; | // check stopping criterion 98438| if (!(fabs(x - m) > t2 - (b - a) * 0.5f)) | { 19| break; | } 98419| T p = 0; 98419| T q = 0; 98419| T r = 0; | // fit parabola 98419| if (fabs(e) > tolerance) | { 98400| const xw = x - w; 98400| const fxw = fx - fw; 98400| const xv = x - v; 98400| const fxv = fx - fv; 98400| const xwfxv = xw * fxv; 98400| const xvfxw = xv * fxw; 98400| p = xv * xvfxw - xw * xwfxv; 98400| q = (xvfxw - xwfxv) * 2; 98400| if (q > 0) 86953| p = -p; | else 11447| q = -q; 98400| r = e; 98400| e = d; | } 98419| T u; | // a parabolic-interpolation step 107161| if (fabs(p) < fabs(q * r * 0.5f) && p > q * (a - x) && p < q * (b - x)) | { 4371| d = p / q; 4371| u = x + d; | // f must not be evaluated too close to a or b 8736| if (u - a < t2 || b - u < t2) 8| d = x < m ? tolerance: -tolerance; | } | // a golden-section step | else | { 94048| e = (x < m ? b : a) - x; 94048| d = c * e; | } | // f must not be evaluated too close to x 98419| u = x + (fabs(d) >= tolerance ? d: d > 0 ? tolerance: -tolerance); 98419| const fu = f(u); 196838| if (fu != fu || fu == -T.infinity) | { 0000000| return typeof(return)(u, fu, T.init); | } | // update a, b, v, w, and x 98419| if (fu <= fx) | { 83405| (u < x ? b : a) = x; 166810| v = w; fv = fw; 166810| w = x; fw = fx; 166810| x = u; fx = fu; | } | else | { 15014| (u < x ? a : b) = u; 21031| if (fu <= fw || w == x) | { 18018| v = w; fv = fw; 18018| w = u; fw = fu; | } 6362| else if (fu <= fv || v == x || v == w) | { // do not remove this braces 11662| v = u; fv = fu; | } | } | } 19| return typeof(return)(x, fx, tolerance * 3); |} | |/// |//@nogc |version(mir_test) @safe unittest |{ | import mir.math.common: approxEqual; | 1| double shift = 4; | 7| auto ret = findLocalMin!(x => (x-shift)^^2)(-1e7, 1e7).validate; 1| assert(ret.x.approxEqual(shift)); 1| assert(ret.y.approxEqual(0.0)); |} | |/// |version(mir_test) @safe unittest |{ | import mir.math.common: approxEqual, log, fabs; | alias AliasSeq(T...) = T; | static foreach (T; AliasSeq!(double, float, real)) | { | { 96| auto ret = findLocalMin!(x => (x-4)^^2)(T.min_normal, T(1e7)).validate; 3| assert(ret.x.approxEqual(T(4))); 3| assert(ret.y.approxEqual(T(0))); | } | { 22311| auto ret = findLocalMin!(x => fabs(x-1))(-T.max/4, T.max/4, T.min_normal, 2*T.epsilon).validate; 3| assert(approxEqual(ret.x, T(1))); 3| assert(approxEqual(ret.y, T(0))); 3| assert(ret.error <= 10 * T.epsilon); | } | { 6| auto ret = findLocalMin!(x => T.init)(0, 1, T.min_normal, 2*T.epsilon); 3| assert(ret.status == FindRootStatus.nanY); | } | { 3| auto ret = findLocalMin!log( 0, 1, T.min_normal, 2*T.epsilon).validate; 3| assert(ret.error < 3.00001 * ((2*T.epsilon)*fabs(ret.x)+ T.min_normal)); 6| assert(ret.x >= 0 && ret.x <= ret.error); | } | { 3| auto ret = findLocalMin!log(0, T.max, T.min_normal, 2*T.epsilon).validate; 3| assert(ret.y < -18); 3| assert(ret.error < 5e-08); 6| assert(ret.x >= 0 && ret.x <= ret.error); | } | { 199| auto ret = findLocalMin!(x => -fabs(x))(-1, 1, T.min_normal, 2*T.epsilon).validate; 3| assert(ret.x.fabs.approxEqual(T(1))); 3| assert(ret.y.fabs.approxEqual(T(1))); 3| assert(ret.error.approxEqual(T(0))); | } | } |} | |/// Tries to find a global minimum using uniform grid to reduce the search interval |version(mir_test) |unittest |{ | import mir.math.common: cos; | import mir.test; | 84| alias f = x => cos(x) + x / 10; | 1| findLocalMin!f(-4.0, 6.0, double.epsilon, 2 * double.epsilon) | .validate.x.shouldApprox == 3.041425233955413; | // Use 10 inner points on uniform grid to reduce the interval | // reduces the interval of search 5.5 = (10 + 1) / 2 times 1| findLocalMin!f(-4.0, 6.0, double.epsilon, 2 * double.epsilon, 10) | .validate.x.shouldApprox == -3.2417600767368255; |} | |/++ |A set of one or two smile roots. | |Because we assume that volatility smile is convex the equantion above can have no more then two roots. | |The `left` and `right` members are equal if the smile has only one root. |+/ |struct SmileRoots(T) | if (__traits(isFloating, T)) |{ | import mir.small_array: SmallArray; | | /// | T left; | /// | T right; | |@safe pure nothrow @nogc: | | /// 4| this(T value) | { 4| left = right = value; | } | | /// 2| this(T left, T right) | { 2| this.left = left; 2| this.right = right; | } | | /// 6| this(SmallArray!(T, 2) array) | { 6| if (array.length == 2) 2| this(array[0], array[1]); | else 4| if (array.length == 1) 4| this(array[0]); | else 0000000| this(T.nan, T.nan); | } | | /// | inout(T)[] opIndex() @trusted inout | { 0000000| return (&left)[0 .. 2]; | } | | /// | size_t count() const | { 0000000| return 1 + (left != right); | } |} | |/++ |+/ |struct FindSmileRootsResult(T) | if (__traits(isFloating, T)) |{ | import mir.algebraic: Nullable; | | /++ | Left result if any | +/ | Nullable!(FindRootResult!T) leftResult; | /++ | Right result if any | +/ | Nullable!(FindRootResult!T) rightResult; | /++ | +/ | Nullable!(FindLocalMinResult!T) localMinResult; | |@safe pure @nogc scope const @property: | | /++ | Returns: $(LREF mir_find_root_status) | +/ | FindRootStatus status() | { 0000000| if (leftResult && leftResult.get.status) 0000000| return leftResult.get.status; 0000000| if (rightResult && rightResult.get.status) 0000000| return rightResult.get.status; 0000000| if (!leftResult && !rightResult) 0000000| return FindRootStatus.badBounds; 0000000| return FindRootStatus.success; | } | | /// | version(D_Exceptions) | ref validate() inout return | { 0000000| with(FindRootStatus) final switch(status) | { 0000000| case success: return this; 0000000| case badBounds: throw findRoot_badBounds; 0000000| case nanX: throw findRoot_nanX; 0000000| case nanY: throw findRoot_nanY; | } | } | | /++ | Returns: `SmallArray!(T, 2)` of roots if any. | +/ | auto roots()() | { | import mir.small_array; 13| SmallArray!(T, 2) ret; 13| if (leftResult) 9| ret.append(leftResult.get.x); 13| if (rightResult) 9| ret.append(rightResult.get.x); 13| return ret; | } | | /++ | Returns: $(LREF SmileRoots). | +/ | SmileRoots!double smileRoots()() | { 6| return typeof(return)(roots); | } |} | |/++ |Find one or two roots of a real function f(x) using combination of $(LREF FindRoot) and $(LREF FindLocalMin). | |Params: | f = Function to be analyzed. If `f(ax)` and `f(bx)` have opposite signs one root is returned, | otherwise the implementation tries to find a local minimum and returns two roots. | At least one of `f(ax)` and `f(bx)` should be greater or equal to zero. | tolerance = Defines an early termination condition. Receives the | current upper and lower bounds on the root. The delegate must return `true` when these bounds are | acceptable. If this function always returns `false` or it is null, full machine precision will be achieved. | ax = Left inner bound of initial range of `f` known to contain the roots. | bx = Right inner bound of initial range of `f` known to contain the roots. Can be equal to `ax`. | fax = Value of `f(ax)` (optional). | fbx = Value of `f(bx)` (optional). | relTolerance = Relative tolerance used by $(LREF findLocalMin). | absTolerance = Absolute tolerance used by $(LREF findLocalMin). | lowerBound = | upperBound = | maxIterations = Appr. maximum allowed number of function calls for each $(LREF findRoot) call. | steps = | |Returns: $(LREF FindSmileRootsResult) |+/ |FindSmileRootsResult!T findSmileRoots(alias f, alias tolerance = null, T)( | const T ax, | const T bx, | const T fax = T.nan, | const T fbx = T.nan, | const T relTolerance = sqrt(T.epsilon), | const T absTolerance = sqrt(T.epsilon), | const T lowerBound = T.nan, | const T upperBound = T.nan, | uint maxIterations = T.sizeof * 16, | uint steps = 0, | ) |if (__traits(isFloating, T)) |{ 3| FindSmileRootsResult!T ret; 3| auto res = findRoot!(f, tolerance)(ax, bx, fax, fbx, T.nan, T.nan, maxIterations); 3| if (res.status == FindRootStatus.success) | { 2| (res.bx.signbit ? ret.leftResult : ret.rightResult) = res; | } | else 1| if (res.status == FindRootStatus.badBounds) | { 1| ret.localMinResult = findLocalMin!f(ax, bx, relTolerance, absTolerance); 1| ret.leftResult = findRoot!(f, tolerance)(ax, ret.localMinResult.x, T.nan, ret.localMinResult.y, T.nan, T.nan, maxIterations); 1| ret.rightResult = findRoot!(f, tolerance)(ret.localMinResult.x, bx, ret.localMinResult.y, T.nan, T.nan, T.nan, maxIterations); | } 3| return ret; |} | |/// Smile |version(mir_test) |unittest |{ | import mir.math.common: approxEqual; 31| auto result = findSmileRoots!(x => x ^^ 2 - 1)(-10.0, 10.0); 1| assert(result.roots.length == 2); 1| assert(result.roots[0].approxEqual(-1)); 1| assert(result.roots[1].approxEqual(+1)); 1| assert(result.smileRoots.left.approxEqual(-1)); 1| assert(result.smileRoots.right.approxEqual(+1)); 1| assert(result.leftResult); 1| assert(result.rightResult); 1| assert(result.localMinResult); 1| assert(result.localMinResult.get.x.approxEqual(0)); 1| assert(result.localMinResult.get.y.approxEqual(-1)); |} | |/// Skew |version(mir_test) |unittest |{ | import mir.math.common: approxEqual; 10| auto result = findSmileRoots!(x => x ^^ 2 - 1)(0.5, 10.0); 1| assert(result.roots.length == 1); 1| assert(result.roots[0].approxEqual(+1)); 1| assert(result.smileRoots.left.approxEqual(+1)); 1| assert(result.smileRoots.right.approxEqual(+1)); 1| assert(!result.leftResult); 1| assert(result.rightResult); |} | |version(mir_test) |unittest |{ 10| auto result = findSmileRoots!(x => x ^^ 2 - 1)(-10.0, -0.5); 1| assert(result.roots.length == 1); 1| assert(result.roots[0].approxEqual(-1)); 1| assert(result.smileRoots.left.approxEqual(-1)); 1| assert(result.smileRoots.right.approxEqual(-1)); 1| assert(result.leftResult); 1| assert(!result.rightResult); |} | |// force disabled FMA math |private static auto half(T)(const T x) |{ | pragma(inline, false); 188| return x * 0.5f; |} | |private auto trustedAllAttr(T)(T t) @trusted |{ | import std.traits; | enum attrs = (functionAttributes!T & ~FunctionAttribute.system) | | FunctionAttribute.pure_ | | FunctionAttribute.safe | | FunctionAttribute.nogc | | FunctionAttribute.nothrow_; 137| return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; |} | |/++ |Calculate the derivative of a function. |This function uses Ridders' method of extrapolating the results |of finite difference formulas for consecutively smaller step sizes, |with an improved stopping criterion described in the Numerical Recipes |books by Press et al. | |This method gives a much higher degree of accuracy in the answer |compared to a single finite difference calculation, but requires |more function evaluations; typically 6 to 12. The maximum number |of function evaluations is $(D 24). | |Params: | f = The function of which to take the derivative. | x = The point at which to take the derivative. | h = A "characteristic scale" over which the function changes. | factor = Stepsize is decreased by factor at each iteration. | safe = Return when error is SAFE worse than the best so far. | |References: |$(UL | $(LI | C. J. F. Ridders, | $(I Accurate computation of F'(x) and F'(x)F''(x)). | Advances in Engineering Software, vol. 4 (1982), issue 2, p. 75.) | $(LI | W. H. Press, S. A. Teukolsky, W. T. Vetterling, and B. P. Flannery, | $(I Numerical Recipes in C++) (2nd ed.). | Cambridge University Press, 2003.) |) |+/ |@fmamath |DiffResult!T diff(alias f, T)(const T x, const T h, const T factor = T(2).sqrt, const T safe = 2) |{ 1| if (false) // break attributes | T y = f(T(1)); 1| scope funInst = delegate(T x) { 14| return T(f(x)); | }; 1| scope fun = funInst.trustedAllAttr; 1| return diffImpl(fun, x, h, factor, safe); |} | |///ditto |DiffResult!T diffImpl(T) | (scope const T delegate(T) @safe pure nothrow @nogc f, const T x, const T h, const T factor = T(2).sqrt, const T safe = 2) | @trusted pure nothrow @nogc |in { 1| assert(h < T.max); 1| assert(h > T.min_normal); |} |do { | // Set up Romberg tableau. | import mir.ndslice.slice: sliced; | pragma(inline, false); | | enum tableauSize = 16; 1| T[tableauSize ^^ 2] workspace = void; 1| auto tab = workspace[].sliced(tableauSize, tableauSize); | | // From the NR book: Stop when the difference between consecutive | // approximations is bigger than SAFE*error, where error is an | // estimate of the absolute error in the current (best) approximation. | | // First approximation: A_0 1| T result = void; 1| T error = T.max; 1| T hh = h; | 1| tab[0,0] = (f(x + h) - f(x - h)) / (2 * h); 19| foreach (n; 1 .. tableauSize) | { | // Decrease h. 6| hh /= factor; | | // Compute A_n 6| tab[0, n] = (f(x + hh) - f(x - hh)) / (2 * hh); | 6| T facm = 1; 81| foreach (m; 1 .. n + 1) | { 21| facm *= factor ^^ 2; | | // Compute B_(n-1), C_(n-2), ... 21| T upLeft = tab[m - 1, n - 1]; 21| T up = tab[m - 1, n]; 21| T current = (facm * up - upLeft) / (facm - 1); 21| tab[m, n] = current; | | // Calculate and check error. 21| T currentError = fmax(fabs(current - upLeft), fabs(current - up)); 21| if (currentError <= error) | { 10| result = current; 10| error = currentError; | } | } | 6| if (fabs(tab[n, n] - tab[n - 1, n - 1]) >= safe * error) 1| break; | } | 1| return typeof(return)(result, error); |} | |/// |version(mir_test) |unittest |{ | import mir.math.common; | 14| auto f(double x) { return exp(x) / (sin(x) - x ^^ 2); } 1| auto d(double x) { return -(exp(x) * ((x - 2) * x - sin(x) + cos(x)))/(x^^2 - sin(x))^^2; } 1| auto r = diff!f(1.0, 0.01); 1| assert (approxEqual(r.value, d(1))); |} | |/++ |+/ |struct DiffResult(T) | if (__traits(isFloating, T)) |{ | /// | T value = 0; | /// | T error = 0; |} | |/++ |Integrates function on the interval `[a, b]` using adaptive Gauss-Lobatto algorithm. | |References: | W. Gander and W. Gautschi. Adaptive Quadrature — Revisited | |Params: | f = function to integrate. `f` should be valid on interval `[a, b]` including the bounds. | a = finite left interval bound | b = finite right interval bound | tolerance = (optional) relative tolerance should be greater or equal to `T.epsilon` | |Returns: | Integral value |+/ |T integrate(alias f, T)(const T a, const T b, const T tolerance = T.epsilon) | if (__traits(isFloating, T)) |{ 23| if (false) // break attributes | T y = f(T(1)); 23| scope funInst = delegate(T x) { 180449| return T(f(x)); | }; 23| scope fun = funInst.trustedAllAttr; 23| return integrateImpl(fun, a, b, tolerance); |} | |/// ditto |@optmath |T integrateImpl(T)( | scope const T delegate(T) @safe pure nothrow @nogc f, | const T a, | const T b, | const T tolerance = T.epsilon, |) @safe pure nothrow @nogc | if (__traits(isFloating, T)) |in { 23| assert(-T.infinity < a); 23| assert(b < +T.infinity); 23| assert(a < b); 23| assert(tolerance >= T.epsilon); |} |do { | pragma(inline, false); | enum T alpha = 0.816496580927726032732428024901963797322; | enum T beta = 0.447213595499957939281834733746255247088; | enum T x1 = 0.94288241569947971905635175843185720232; | enum T x2 = 0.64185334234978130978123554132903188394; | enum T x3 = 0.23638319966214988028222377349205292599; | enum T A = 0.015827191973480183087169986733305510591; | enum T B = 0.094273840218850045531282505077108171960; | enum T C = 0.19507198733658539625363597980210298680; | enum T D = 0.18882157396018245442000533937297167125; | enum T E = 0.19977340522685852679206802206648840246; | enum T F = 0.22492646533333952701601768799639508076; | enum T G = 0.24261107190140773379964095790325635233; | enum T A1 = 77 / 1470.0L; | enum T B1 = 432 / 1470.0L; | enum T C1 = 625 / 1470.0L; | enum T D1 = 672 / 1470.0L; | enum T A2 = 1 / 6.0L; | enum T B2 = 5 / 6.0L; | 23| T m = (a + b) * 0.5f; | // This fix is not part of the original algorithm 23| if (!(m.fabs < T.infinity)) | { 0000000| m = a.half + b.half; | } 23| T h = (b - a) * 0.5f; | // This fix is not part of the original algorithm 23| if (!(h.fabs < T.infinity)) | { 0000000| h = b.half - a.half; | } 23| T[13] x = [ | a, | m - x1 * h, | m - alpha * h, | m - x2 * h, | m - beta * h, | m - x3 * h, | m, | m + x3 * h, | m + beta * h, | m + x2 * h, | m + alpha * h, | m + x1 * h, | b, | ]; 23| T[13] y = [ | f(x[ 0]), | f(x[ 1]), | f(x[ 2]), | f(x[ 3]), | f(x[ 4]), | f(x[ 5]), | f(x[ 6]), | f(x[ 7]), | f(x[ 8]), | f(x[ 9]), | f(x[10]), | f(x[11]), | f(x[12]), | ]; 23| T i2 = h * ( | + A2 * (y[0] + y[12]) | + B2 * (y[4] + y[ 8]) | ); 23| T i1 = h * ( | + A1 * (y[0] + y[12]) | + B1 * (y[2] + y[10]) | + C1 * (y[4] + y[ 8]) | + D1 * (y[6] ) | ); 23| T si = h * ( | + A * (y[0] + y[12]) | + B * (y[1] + y[11]) | + C * (y[2] + y[10]) | + D * (y[3] + y[ 9]) | + E * (y[4] + y[ 8]) | + F * (y[5] + y[ 7]) | + G * (y[6] ) | ); 23| T erri1 = fabs(i1 - si); 23| T erri2 = fabs(i2 - si); 23| T R = erri1 / erri2; 23| T tol = tolerance; 23| if (tol < T.epsilon) 0000000| tol = T.epsilon; 46| if (0 < R && R < 1) 14| tol /= R; 23| si *= tol / T.epsilon; 23| if (si == 0) 0000000| si = b - a; | 23| if (!(si.fabs < T.infinity)) 0000000| return T.nan; | | T step(const T a, const T b, const T fa, const T fb) | { 36030| T m = (a + b) * 0.5f; | // This fix is not part of the original algorithm 36030| if (!(m.fabs < T.infinity)) | { 0000000| m = a.half + b.half; | } 36030| T h = (b - a) * 0.5f; 36030| if (!(h.fabs < T.infinity)) | { 0000000| h = b.half - a.half; | } 36030| T[5] x = [ | m - alpha * h, | m - beta * h, | m, | m + beta * h, | m + alpha * h, | ]; 36030| T[5] y = [ | f(x[0]), | f(x[1]), | f(x[2]), | f(x[3]), | f(x[4]), | ]; 36030| T i2 = h * ( | + A2 * (fa + fb) | + B2 * (y[1] + y[3]) | ); 36030| T i1 = h * ( | + A1 * (fa + fb) | + B1 * (y[0] + y[4]) | + C1 * (y[1] + y[3]) | + D1 * y[2] | ); 36030| auto sic = si + (i1 - i2); 83978| if (!(i1.fabs < T.infinity) || sic == si || !(a < x[0]) || !(x[4] < b)) | { 30071| return i1; | } 5959| return | + (step( a, x[0], fa, y[0]) | + step(x[0], x[1], y[0], y[1])) | + (step(x[1], x[2], y[1], y[2]) | + step(x[2], x[3], y[2], y[3])) | + (step(x[3], x[4], y[3], y[4]) | + step(x[4], b, y[4], fb)); | } | 897| foreach (i; 0 .. 12) 276| x[i] = step(x[i], x[i + 1], y[i], y[i + 1]); | 23| return | + ((x[ 0] | + x[ 1]) | + (x[ 2] | + x[ 3])) | + ((x[ 4] | + x[ 5]) | + (x[ 6] | + x[ 7])) | + ((x[ 8] | + x[ 9]) | + (x[10] | + x[11])); |} | |/// |version(mir_test) |unittest |{ | import mir.math.common; | import mir.math.constant; | 33802| alias cosh = x => 0.5 * (exp(x) + exp(-x)); | enum Pi = double(PI); | 1| assert(integrate!exp(0.0, 1.0).approxEqual(double(E - 1))); 614| assert(integrate!(x => x >= 3)(0.0, 10.0).approxEqual(7.0)); 1| assert(integrate!sqrt(0.0, 1.0).approxEqual(2.0 / 3)); 1274| assert(integrate!(x => 23.0 / 25 * cosh(x) - cos(x))(-1.0, 1.0).approxEqual(0.479428226688801667)); 2114| assert(integrate!(x => 1 / (x ^^ 4 + x ^^ 2 + 0.9))(-1.0, 1.0).approxEqual(1.5822329637294)); 1814| assert(integrate!(x => sqrt(x ^^ 3))(0.0, 1.0).approxEqual(0.4)); 10454| assert(integrate!(x => x ? 1 / sqrt(x) : 0)(0.0, 1.0).approxEqual(2)); 1424| assert(integrate!(x => 1 / (1 + x ^^ 4))(0.0, 1.0).approxEqual(0.866972987339911)); 10724| assert(integrate!(x => 2 / (2 + sin(10 * Pi * x)))(0.0, 1.0).approxEqual(1.1547005383793)); 1004| assert(integrate!(x => 1 / (1 + x))(0.0, 1.0).approxEqual(0.6931471805599)); 374| assert(integrate!(x => 1 / (1 + exp(x)))(0.0, 1.0).approxEqual(0.3798854930417)); 734| assert(integrate!(x => exp(x) - 1 ? x / (exp(x) - 1) : 0)(0.0, 1.0).approxEqual(0.777504634112248)); 51734| assert(integrate!(x => sin(100 * Pi * x) / (Pi * x))(0.1, 1.0).approxEqual(0.0090986375391668)); 2324| assert(integrate!(x => sqrt(50.0) * exp(-50 * Pi * x ^^ 2))(0.0, 10.0).approxEqual(0.5)); 2174| assert(integrate!(x => 25 * exp(-25 * x))(0.0, 10.0).approxEqual(1.0)); 74| assert(integrate!(x => 50 / Pi * (2500 * x ^^ 2 + 1))(0.0, 10.0).approxEqual(1.3263071079268e+7)); 30164| assert(integrate!(x => 50 * (sin(50 * Pi * x) / (50 * Pi * x)) ^^ 2)(0.01, 1.0).approxEqual(0.11213930374164)); 7604| assert(integrate!(x => cos(cos(x) + 3 * sin(x) + 2 * cos(2 * x) + 3 * sin(2 * x) + 3 * cos(3 * x)))(0.0, Pi).approxEqual(0.83867634269443)); 5624| assert(integrate!(x => x > 1e-15 ? log(x) : 0)(0.0, 1.0).approxEqual(-1)); 1574| assert(integrate!(x => 1 / (x ^^ 2 + 1.005))(-1.0, 1.0).approxEqual(1.5643964440690)); 10844| assert(integrate!(x => 1 / cosh(20 * (x - 0.2)) + 1 / cosh(400 * (x - 0.04)) + 1 / cosh(8000 * (x - 0.008)))(0.0, 1.0).approxEqual(0.16349495585710)); 26354| assert(integrate!(x => 4 * Pi ^^ 2 * x * sin(20 * Pi * x) * cos(2 * Pi * x))(0.0, 1.0).approxEqual(-0.6346651825434)); 7754| assert(integrate!(x => 1 / (1 + (230 * x - 30) ^^ 2))(0.0, 1.0).approxEqual(0.013492485649468)); |} source/mir/numeric.d is 92% covered <<<<<< EOF # path=source-mir-parse.lst |/++ |$(H1 @nogc and nothrow Parsing Utilities) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |+/ |module mir.parse; | |/++ |Parsing position |+/ |struct ParsePosition |{ | /// | string file; | /// 0 is for unknown | uint line; | /// 0 is for unknown | uint column; | | /// | void toString(W)(scope ref W w) scope const | { | w.put(file); | if (line) | { | import mir.format: print; | w.put("("); | w.print(line); | if (column) | { | w.put(","); | w.print(column); | } | w.put(")"); | } | } |} | |/// |enum DecimalExponentKey |{ | /// | none = 0, | /// | infinity = 1, | /// | nan = 2, | /// | dot = '.' - '0', | /// | d = 'd' - '0', | /// | e = 'e' - '0', | /// | D = 'D' - '0', | /// | E = 'E' - '0', |} | |/// |struct DecimalExponentInfo |{ | /// | long exponent; | /// | DecimalExponentKey key; |} | |/// `mir.conv: to` extension. |version(mir_bignum_test) |@safe pure @nogc |unittest |{ | import mir.test: should; | import mir.conv: to; | 1| "123.0".to!double.should == 123; 1| "123".to!int.should == 123; 1| "123".to!byte.should == 123; | | import mir.small_string; | alias S = SmallString!32; 1| "123.0".SmallString!32.to!double.should == 123; |} | |import std.traits: isMutable, isFloatingPoint, isSomeChar, isSigned, isUnsigned, Unsigned; | |/++ |Performs `nothrow` and `@nogc` string to native type conversion. | |Returns: | parsed value |Throws: | `nogc` Exception in case of parse error or non-empty remaining input. | |Floating_point: | Mir parsing supports up-to quadruple precision. |The conversion error is 0 ULP for normal numbers. | Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP.+/ |template fromString(T) | if (isMutable!T) |{ | /// | T fromString(C)(scope const(C)[] str) | if (isSomeChar!C) | { | import mir.utility: _expect; | static immutable excfp = new Exception("fromString failed to parse " ~ T.stringof); | | static if (isFloatingPoint!T) | { 114| T value; 114| if (_expect(.fromString(str, value), true)) 104| return value; | version (D_Exceptions) 10| throw excfp; | else | assert(0); | } | else | { | static immutable excne = new Exception("fromString: remaining input is not empty after parsing " ~ T.stringof); | 21| T value; 21| if (_expect(parse!T(str, value), true)) | { 21| if (_expect(str.length == 0, true)) 21| return value; | version (D_Exceptions) 0000000| throw excne; | else | assert(0); | } | else | { | version (D_Exceptions) 0000000| throw excfp; | else | assert(0); | } | } | } |} | |version(unittest) |{ | import core.stdc.stdlib: strtof, strtod, strtold; | private auto _assumePure(T)(scope return T t) { | import std.traits; | enum attrs = functionAttributes!T | FunctionAttribute.pure_; 90| return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; | } | 60| private static @trusted float _stdc_parse(T : float)(string str){ auto endPtr = str.ptr + str.length; return _assumePure(&strtof)(str.ptr, &endPtr); } 60| private static @trusted double _stdc_parse(T : double)(string str){ auto endPtr = str.ptr + str.length; return _assumePure(&strtod)(str.ptr, &endPtr); } 60| private static @trusted real _stdc_parse(T : real)(string str){ auto endPtr = str.ptr + str.length; return _assumePure(&strtold)(str.ptr, &endPtr); } |} | |/// |version(mir_bignum_test) |@safe pure @nogc unittest |{ | import mir.test; 1| "123".fromString!int.should == 123; | 1| ".5".fromString!float.should == .5; 1| "12.3".fromString!double.should == 12.3; 1| "12.3".fromString!float.should == 12.3f; 1| "12.3".fromString!real.should == 12.3L; 1| "-12.3e-30".fromString!double.should == -12.3e-30; 1| "2.9802322387695312E-8".fromString!double.should == 2.9802322387695312E-8; | | // default support of underscores 1| "123_456.789_012".fromString!double.should == 123_456.789_012; 1| "12_34_56_78_90_12e-6".fromString!double.should == 123_456.789_012; | | // default support of leading zeros 1| "010".fromString!double.should == 10.0; 1| "000010".fromString!double.should == 10.0; 1| "0000.10".fromString!double.should == 0.1; 1| "0000e10".fromString!double.should == 0; | | version(all) {} else | version (TeslAlgoM) {} else | { | /// Test CTFE support | static assert("-123".fromString!int == -123); | | static assert("-12.3e-30".fromString!double == -0x1.f2f280b2414d5p-97); | static assert("+12.3e+30".fromString!double == 0x1.367ee3119d2bap+103); | | static assert("1.448997445238699".fromString!double == 0x1.72f17f1f49aadp0); | static if (real.mant_dig >= 64) | static assert("1.448997445238699".fromString!real == 1.448997445238699L); | | static assert("3.518437208883201171875".fromString!float == 0x1.c25c26p+1); | static assert("3.518437208883201171875".fromString!double == 0x1.c25c268497684p+1); | static if (real.mant_dig >= 64) | static assert("3.518437208883201171875".fromString!real == 0xe.12e13424bb4232fp-2L); | } | | void test(string str) | { | version(CRuntime_DigitalMars) // No precise parsing at all | { | } | else | { 30| str.fromString!float.should == str._stdc_parse!float; 30| str.fromString!double.should == str._stdc_parse!double; | version (Windows) // No precise real parsing on windows | { | } | else 30| str.fromString!real.should == str._stdc_parse!real; | } | } | 1| test("2.5e-324"); | | // large 1| test("1e300"); 1| test("123456789.34567e250"); 1| test("943794359898089732078308743689303290943794359843568973207830874368930329."); | | // min normal 1| test("2.2250738585072014e-308"); | | // subnormals 1| test("5e-324"); 1| test("91e-324"); 1| test("1e-322"); 1| test("13245643e-320"); 1| test("2.22507385851e-308"); 1| test("2.1e-308"); 1| test("4.9406564584124654e-324"); | | // infinity 1| test("1e400"); 1| test("1e309"); 1| test("2e308"); 1| test("1.7976931348624e308"); | | // zero 1| test("0.0"); 1| test("1e-325"); 1| test("1e-326"); 1| test("1e-500"); | | // Triggers the tricky underflow case in AlgorithmM (for f32) 1| test("101e-33"); | // Triggers AlgorithmR 1| test("1e23"); | // Triggers another path through AlgorithmR 1| test("2075e23"); | // ... and yet another. 1| test("8713e-23"); | | // 2^65 - 3, triggers half-to-even with even significand 1| test("36893488147419103229.0"); 1| test("36893488147419103229"); | 1| test("18446744073709551615."); 1| test("-18446744073709551615."); 1| test("18446744073709551616."); 1| test("-18446744073709551616."); | |// Related DMD Issues: |// https://issues.dlang.org/show_bug.cgi?id=20951 |// https://issues.dlang.org/show_bug.cgi?id=20952 |// https://issues.dlang.org/show_bug.cgi?id=20953 |// https://issues.dlang.org/show_bug.cgi?id=20967 |} | |version(mir_bignum_test) |@safe pure unittest |{ | import std.exception: assertThrown; 2| assertThrown("1_".fromString!float); 2| assertThrown("1__2".fromString!float); 2| assertThrown("_1".fromString!float); 2| assertThrown("123_.456".fromString!float); 2| assertThrown("123_e0".fromString!float); 2| assertThrown("123._456".fromString!float); 2| assertThrown("12__34.56".fromString!float); 2| assertThrown("123.456_".fromString!float); 2| assertThrown("-_123.456".fromString!float); 2| assertThrown("_123.456".fromString!float); |} | |/++ |Performs `nothrow` and `@nogc` string to native type conversion. | |Rseturns: true if success and false otherwise. |+/ |bool fromString(T, C)(scope const(C)[] str, ref T value) | if (isSomeChar!C) |{ | static if (isFloatingPoint!T) | { | import mir.bignum.decimal: Decimal, DecimalExponentKey; | import mir.utility: _expect; | 116| Decimal!128 decimal = void; 116| DecimalExponentKey key; 116| auto ret = decimal.fromStringImpl(str, key); 116| if (_expect(ret, true)) | { 106| value = cast(T) decimal; | } 116| return ret; | } | else | { 942| return parse!T(str, value) && str.length == 0; | } |} | |/// |version(mir_test) |@safe pure nothrow @nogc unittest |{ 1| int value; 2| assert("123".fromString(value) && value == 123); |} | |/// |version(mir_test) |@safe pure nothrow @nogc unittest |{ 1| double value = 0; 2| assert("+Inf".fromString(value) && value == double.infinity); 2| assert("-nan".fromString(value) && value != value); |} | |/++ |Single character parsing utilities. | |Returns: true if success and false otherwise. |+/ |bool parse(T, C)(ref scope inout(C)[] str, ref scope T value) | if (isSomeChar!C && isSomeChar!T && T.sizeof == C.sizeof) |{ 1| if (str.length == 0) 0000000| return false; 1| value = str[0]; 1| str = str[1 .. $]; 1| return true; |} | |/// |version(mir_test) @safe pure nothrow @nogc |unittest |{ 1| auto s = "str"; 1| char c; 1| assert(parse(s, c)); 1| assert(c == 's'); 1| assert(s == "tr"); |} | |/++ |Integer parsing utilities. | |Returns: true if success and false otherwise. |+/ |bool parse(T, C)(ref scope inout(C)[] str, out scope T value) | if ((is(T == byte) || is(T == short)) && isSomeChar!C) |{ 171| int lvalue; 171| auto ret = .parse!(int, C)(str, lvalue); 171| value = cast(T) lvalue; 338| return ret && value == lvalue; |} | |bool parse(T, C)(ref scope inout(C)[] str, out scope T value) | if ((is(T == ubyte) || is(T == ushort)) && isSomeChar!C) |{ 185| uint lvalue; 185| auto ret = .parse!(uint, C)(str, lvalue); 185| value = cast(T) lvalue; 366| return ret && value == lvalue; |} | |/// |version (mir_test) unittest |{ | import mir.test: should; | import std.meta: AliasSeq; | foreach (T; AliasSeq!( | byte, ubyte, short, ushort, | int, uint, long, ulong)) | { 8| auto str = "123"; 8| T val; 8| assert(parse(str, val)); 8| val.should == 123; 8| str = "0"; 8| assert(parse(str, val)); 8| val.should == 0; 8| str = "9"; 8| assert(parse(str, val)); 8| val.should == 9; 8| str = ""; 8| assert(!parse(str, val)); 8| val.should == 0; 8| str = "text"; 8| assert(!parse(str, val)); 8| val.should == 0; | } |} | |/// |version (mir_test) unittest |{ | import mir.test: should; | import mir.conv: to; | import std.meta: AliasSeq; | foreach (T; AliasSeq!(byte, short, int, long)) | { 4| auto str = "-123"; 4| T val; 4| assert(parse(str, val)); 4| val.should == -123; 4| str = "-0"; 4| assert(parse(str, val)); 4| val.should == 0; 4| str = "-9text"; 4| assert(parse(str, val)); 4| val.should == -9; 4| assert(str == "text"); | enum m = T.min + 0; 4| str = m.to!string; 4| assert(parse(str, val)); 4| val.should == T.min; | } |} | |bool parse(T, C)(ref scope inout(C)[] str, scope out T value) | if ((isSigned!T || isUnsigned!T) && T.sizeof >= uint.sizeof && isSomeChar!C) |{ | version(LDC) pragma(inline, true); | import mir.checkedint: addu, mulu; | 754| if (str.length == 0) 10| return false; | 744| Unsigned!T x = str[0] - C('0'); | | static if (isSigned!T) 371| bool sign; | 744| if (x >= 10) | { | static if (isSigned!T) | { 108| if (x == C('-') - C('0')) | { 88| sign = true; 88| goto S; | } | } | 34| if (x != C('+') - C('0')) 21| return false; | S: 101| str = str[1 .. $]; 101| if (str.length == 0) 0000000| return false; 101| x = str[0] - C('0'); 101| if (x >= 10) 0000000| return false; | } | 723| str = str[1 .. $]; | 1908| while (str.length) | { 1306| uint c = str[0] - C('0'); 1306| if (c >= 10) 121| break; 1185| str = str[1 .. $]; 1185| bool overflow; 1185| x = x.mulu(10u, overflow); 1185| if (overflow) 0000000| return false; 1185| x = x.addu(c, overflow); 1185| if (overflow) 0000000| return false; | } | | static if (isSigned!T) | { 364| if (x > Unsigned!T(T.max + sign)) 0000000| return false; 364| x = sign ? -x : x; | } | 723| value = x; 723| return true; |} source/mir/parse.d is 95% covered <<<<<< EOF # path=source-mir-polynomial.lst |/++ |Polynomial ref-counted structure. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilia Ki |+/ |module mir.polynomial; | |import mir.math.common: optmath; |import mir.rc.array; | |@optmath: | |/++ |Polynomial callable ref-counted structure. |+/ 0000000|struct Polynomial(F) |{ | /// | RCArray!(const F) coefficients; | | /++ | Params: | coefficients = coefficients `c[i]` for polynomial function `f(x)=c[0]+c[1]*x^^1+...+c[n]*x^^n` | +/ 23| this(RCArray!(const F) coefficients) | { | import core.lifetime: move; 23| this.coefficients = coefficients.move; | } | | /++ | Params: | derivative = derivative order | +/ | template opCall(uint derivative = 0) | { | /++ | Params: | x = `x` point | +/ | @optmath typeof(F.init * X.init * 1f + F.init) opCall(X)(in X x) const | { | import mir.internal.utility: Iota; 28| auto ret = cast(typeof(return))0; 28| if (coefficients) | { 28| ptrdiff_t i = coefficients.length - 1; 28| assert(i >= 0); 28| auto c = cast()coefficients[i]; | static foreach (d; Iota!derivative) 6| c *= i - d; 28| ret = cast(typeof(return)) c; 150| while (--i >= cast(ptrdiff_t)derivative) | { 122| assert(i < coefficients.length); 122| c = cast()coefficients[i]; | static foreach (d; Iota!derivative) 8| c *= i - d; 122| ret *= x; 122| ret += c; | } | } 28| return ret; | } | } |} | |/// ditto |Polynomial!F polynomial(F)(RCArray!(const F) coefficients) |{ | import core.lifetime: move; 23| return typeof(return)(coefficients.move); |} | |/// |version (mir_test) @safe pure nothrow @nogc unittest |{ | import mir.math.common: approxEqual; | import mir.rc.array; 2| auto a = rcarray!(const double)(3.0, 4.5, 1.9, 2); 2| auto p = a.polynomial; | 2| alias f = (x) => 3.0 + 4.5 * x^^1 + 1.9 * x^^2 + 2 * x^^3; 2| alias df = (x) => 4.5 + 2 * 1.9 * x^^1 + 3 * 2 * x^^2; 2| alias d2f = (x) => 2 * 1.9 + 6 * 2 * x^^1; | 1| assert(p(3.3).approxEqual(f(3.3))); 1| assert(p(7.2).approxEqual(f(7.2))); | 1| assert(p.opCall!1(3.3).approxEqual(df(3.3))); 1| assert(p.opCall!1(7.2).approxEqual(df(7.2))); | 1| assert(p.opCall!2(3.3).approxEqual(d2f(3.3))); 1| assert(p.opCall!2(7.2).approxEqual(d2f(7.2))); |} source/mir/polynomial.d is 96% covered <<<<<< EOF # path=source-mir-range.lst |/++ |Ranges. | |See_also: $(MREF mir,_primitives). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki, Phobos Authors |+/ |module mir.range; | |/++ |Data size counter. | |Does not store anything. |+/ |struct Counter(T) |{ | import std.range: isInputRange, ElementType; | import std.traits: isImplicitlyConvertible, isSomeChar; | /// | size_t _count; | | /// Data count. | size_t count()() @property | { 4| return _count; | } | | private template canPutItem(U) | { | enum bool canPutItem = | isImplicitlyConvertible!(U, T) || | isSomeChar!T && isSomeChar!U; | } | | private template canPutRange(Range) | { | import mir.primitives: front; | enum bool canPutRange = | isInputRange!Range && | is(typeof(Counter.init.put(Range.init.front))); | } | | /// | void put(U)(auto ref U item) if (canPutItem!U) | { | static if (isSomeChar!T && isSomeChar!U && T.sizeof < U.sizeof) | { | import std.utf: codeLength; 1| _count += codeLength!T(item); | } | else | { 1| _count++; | } | } | | /// | void put(Range)(Range items) if (canPutRange!Range) | { | // note, we disable this branch for appending one type of char to | // another because we can't trust the length portion. | static if (!(isSomeChar!T && isSomeChar!(ElementType!Range) && | !is(immutable Range == immutable T[]))) | { | import mir.primitives: hasLength; | static if (hasLength!Range) | { 1| _count += items.length; | } | else | { 5| for (;!items.empty; items.popFront) 2| _count++; | } | } | else | { | import std.utf: codeLength; 1| _count += codeLength!T(items); | } | } |} | |/// |version(mir_test) unittest |{ 1| Counter!char counter; 1| counter.put("Ми"); 1| assert(counter.count == 4); 1| counter.put('р'); // Cyrillic 1| assert(counter.count == 6); |} | |/// |version(mir_test) unittest |{ 1| Counter!wchar counter; 1| counter.put("Ми"); 1| assert(counter.count == 2); 1| counter.put('р'); // Cyrillic 1| assert(counter.count == 3); |} | |/// |version(mir_test) unittest |{ 1| Counter!int counter; | import std.algorithm: until; 1| counter.put([1, 2, 3, 4, 5].until(3)); |} source/mir/range.d is 100% covered <<<<<< EOF # path=source-mir-rc-array.lst |/++ |$(H1 Thread-safe reference-counted arrays and iterators). |+/ |module mir.rc.array; | |import mir.primitives: hasLength; |import mir.qualifier; |import mir.rc.context; |import mir.type_info; |import std.traits; | |package static immutable allocationExcMsg = "mir_rcarray: out of memory error."; | |version (D_Exceptions) |{ | import core.exception: OutOfMemoryError; | package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); |} | |/++ |Thread safe reference counting array. | |The implementation never adds roots into the GC. |+/ |struct mir_rcarray(T) |{ | import mir.internal.utility: isComplex, realType; | /// | package T* _payload; | package ref mir_rc_context context() inout return scope pure nothrow @nogc @trusted @property | { 13532| assert(_payload); 13532| return (cast(mir_rc_context*)_payload)[-1]; | } 0000000| package void _reset() { _payload = null; } | | package alias ThisTemplate = .mir_rcarray; | package alias _thisPtr = _payload; | | /// | alias serdeKeysProxy = Unqual!T; | | /// | void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe | { 251| auto t = this._payload; 251| this._payload = rhs._payload; 251| rhs._payload = t; | } | | /// 1| this(typeof(null)) | { | } | | /// | mixin CommonRCImpl; | | /// | pragma(inline, true) | bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc | { 0000000| return !this; | } | | /// ditto | bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc | { | static if (isComplex!T) 0000000| return cast(const realType!T[]) opIndex() == cast(const realType!Y[]) rhs.opIndex(); | else 1| return opIndex() == rhs.opIndex(); | } | | /// | int opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc | { | static if (isComplex!T) 0000000| return __cmp(cast(const realType!T[])opIndex(), cast(const realType!Y[])rhs.opIndex()); | else 0000000| return __cmp(opIndex(), rhs.opIndex()); | } | | /// | size_t toHash() @trusted scope const pure nothrow @nogc | { | static if (isComplex!T) 0000000| return hashOf(cast(const realType!T[])opIndex()); | else 0000000| return hashOf(opIndex()); | } | | /// | ~this() nothrow | { | static if (hasElaborateDestructor!T || hasDestructor!T) | { 54| if (false) // break @safe and pure attributes | { | Unqual!T* object; | (*object).__xdtor; | } | } 1765| if (this) | { 1910| (() @trusted { mir_rc_decrease_counter(context); })(); | } | } | | /// | size_t length() @trusted scope pure nothrow @nogc const @property | { 8035| return _payload !is null ? context.length : 0; | } | | /// | inout(T)* ptr() @system scope inout | { 216| return _payload; | } | | /// | ref opIndex(size_t i) @trusted scope inout | { 1545| assert(_payload); 1545| assert(i < context.length); 1545| return _payload[i]; | } | | /// | inout(T)[] opIndex() @trusted scope inout | { 2312| return _payload !is null ? _payload[0 .. context.length] : null; | } | | /// | size_t opDollar(size_t pos : 0)() @trusted scope pure nothrow @nogc const | { 5| return length; | } | | /// | auto asSlice()() @property | { | import mir.ndslice.slice: mir_slice; | alias It = mir_rci!T; 26| return mir_slice!It([length], It(this)); | } | | /// | auto asSlice()() const @property | { | import mir.ndslice.slice: mir_slice; | alias It = mir_rci!(const T); | return mir_slice!It([length], It(this.lightConst)); | } | | /// | auto asSlice()() immutable @property | { | import mir.ndslice.slice: mir_slice; | alias It = mir_rci!(immutable T); | return mir_slice!It([length], It(this.lightImmutable)); | } | | /// | auto moveToSlice()() @property | { | import core.lifetime: move; | import mir.ndslice.slice: mir_slice; | alias It = mir_rci!T; 12| return mir_slice!It([length], It(move(this))); | } | | /++ | Params: | length = array length | initialize = Flag, don't initialize memory with default value if `false`. | deallocate = Flag, never deallocates memory if `false`. | +/ 275| this(size_t length, bool initialize = true, bool deallocate = true) @trusted @nogc | { 275| if (length == 0) 0000000| return; 275| Unqual!T[] ar; 275| () @trusted { | static if (is(T == class) || is(T == interface)) | auto ctx = mir_rc_create(mir_get_type_info!T, length, mir_get_payload_ptr!T, initialize, deallocate); | else 275| auto ctx = mir_rc_create(mir_get_type_info!T, length, mir_get_payload_ptr!T, initialize, deallocate); 275| if (!ctx) | { | version(D_Exceptions) 0000000| throw allocationError; | else | assert(0, allocationExcMsg); | } 275| _payload = cast(T*)(ctx + 1); 275| ar = cast(Unqual!T[])_payload[0 .. length]; | } (); 441| if (initialize || hasElaborateAssign!(Unqual!T)) | { | import mir.conv: uninitializedFillDefault; 109| uninitializedFillDefault(ar); | } | } | | static if (isImplicitlyConvertible!(const T, T)) | static if (isImplicitlyConvertible!(const Unqual!T, T)) | package alias V = const Unqual!T; | else | package alias V = const T; | else | package alias V = T; | | static if (is(T == const) || is(T == immutable)) 0000000| this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc | { 0000000| if (rhs) | { 0000000| this._payload = cast(typeof(this._payload))rhs._payload; 0000000| mir_rc_increase_counter(context); | } | } | | static if (is(T == immutable)) 0000000| this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc | { 0000000| if (rhs) | { 0000000| this._payload = cast(typeof(this._payload))rhs._payload; 0000000| mir_rc_increase_counter(context); | } | } | | static if (is(T == immutable)) 0000000| this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc | { 0000000| if (rhs) | { 0000000| this._payload = cast(typeof(this._payload))rhs._payload; 0000000| mir_rc_increase_counter(context); | } | } | 692| this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc | { 692| if (rhs) | { 681| this._payload = rhs._payload; 681| mir_rc_increase_counter(context); | } | } | | /// | ref opAssign(typeof(null)) scope return @trusted // pure nothrow @nogc | { 1| this = typeof(this).init; | } | | /// | ref opAssign(scope return typeof(this) rhs) scope return @trusted // pure nothrow @nogc | { 2| this.proxySwap(rhs); 2| return this; | } | | /// | ref opAssign(Q)(scope return ThisTemplate!Q rhs) scope return @trusted // pure nothrow @nogc | if (isImplicitlyConvertible!(Q*, T*)) | { 4| this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}()); 2| return this; | } |} | |/// ditto |alias RCArray = mir_rcarray; | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ 2| auto a = RCArray!double(10); 43| foreach(i, ref e; a) 10| e = i; 2| auto b = a; 1| assert(b[$ - 1] == 9); 43| foreach(i, ref e; b) 10| assert(e == i); 1| b[4] = 100; 1| assert(a[4] == 100); | | import mir.ndslice.slice; | 2| auto s = a.asSlice; // as RC random access range (ndslice) | static assert(is(typeof(s) == Slice!(RCI!double))); | static assert(is(typeof(s) == mir_slice!(mir_rci!double))); | 1| auto r = a[]; // scope array | static assert(is(typeof(r) == double[])); | 1| auto fs = r.sliced; // scope fast random access range (ndslice) | static assert(is(typeof(fs) == Slice!(double*))); |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.complex; 2| auto a = rcarray(complex(2.0, 3), complex(4.9, 2)); |} | |package template LikeArray(Range) |{ | static if (__traits(identifier, Range) == "mir_slice") | { | import mir.ndslice.slice: Slice, SliceKind; | enum LikeArray = is(Range : Slice!(T*, N, SliceKind.contiguous), T, size_t N); | } | else | { | enum LikeArray = false; | } |} | |/// |auto rcarray(T = void, Range)(ref Range range) | if (is(T == void) && !is(Range == LightScopeOf!Range)) |{ | return .rcarray(range.lightScope); |} | |/// ditto |auto rcarray(T = void, Range)(Range range) | if (is(T == void) && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) |{ | static if (LikeArray!Range) | { | return .rcarray(range.field); | } | else | { 1| return .rcarray!(ForeachType!Range)(range); | } |} | |/// ditto |RCArray!V rcarray(T = void, V)(scope V[] values...) | if (is(T == void)) |{ 5| return .rcarray(values, true); |} | |/// ditto |RCArray!V rcarray(T = void, V)(scope V[] values, bool deallocate) | if (is(T == void)) |{ 5| return .rcarray!V(values, deallocate); |} | |/// ditto |template rcarray(T) | if(!is(T == E[], E) && !is(T == void)) |{ | import mir.primitives: isInputRange, isInfinite; | | /// | auto rcarray(Range)(ref Range range) | if (!is(Range == LightScopeOf!Range)) | { 2| return .rcarray!T(range.lightScope); | } | | /// ditto | auto rcarray(Range)(Range range) | if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isArray!Range || isPointer!Range && (isInputRange!(PointerTarget!Range) || isIterable!(PointerTarget!Range))) | { | static if (LikeArray!Range) | { 1| return .rcarray!T(range.field); | } | else static if (hasLength!Range) | { | import mir.conv: emplaceRef; 2| auto ret = RCArray!T(range.length, false); 2| size_t i; | static if (isInputRange!Range) 2010| for (; !range.empty; range.popFront) 1004| ret[i++].emplaceRef!T(range.front); | else | static if (isPointer!Range) | foreach (e; *range) | ret[i++].emplaceRef!T(e); | else | foreach (e; range) | ret[i++].emplaceRef!T(e); 2| return ret; | } | else | { | import mir.appender: scopedBuffer; | import mir.conv: emplaceRef; 2| auto a = scopedBuffer!T; | static if (isInputRange!Range) 5| for (; !range.empty; range.popFront) 2| a.put(range.front); | else | static if (isPointer!Range) | foreach (e; *range) | a.put(e); | else | foreach (e; range) | a.put(e); 1| scope values = a.data; 1| return ()@trusted { 1| auto ret = RCArray!T(values.length, false); 1| a.moveDataAndEmplaceTo(ret[]); 1| return ret; | } (); | } | } | | /// ditto | RCArray!T rcarray(V)(scope V[] values...) | { 35| return .rcarray!T(values, true); | } | | /// ditto | RCArray!T rcarray(V)(scope V[] values, bool deallocate) | { 40| auto ret = mir_rcarray!T(values.length, hasElaborateDestructor!T, deallocate); | static if (!hasElaborateAssign!(Unqual!T) && is(Unqual!V == Unqual!T)) | { 13| ()@trusted { | import core.stdc.string: memcpy; 13| memcpy(cast(void*)ret.ptr, cast(const void*)values.ptr, values.length * T.sizeof); | }(); | } | else | { | import mir.conv: emplaceRef; 27| auto lhs = ret[]; 653| foreach (i, ref e; values) 143| lhs[i].emplaceRef!T(e); | } 40| return ret; | } |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ 2| RCArray!double a = rcarray!double(1.0, 2, 5, 3); 1| assert(a[0] == 1); 1| assert(a[$ - 1] == 3); | 2| auto s = rcarray!char("hello!"); 1| assert(s[0] == 'h'); 1| assert(s[$ - 1] == '!'); | | alias rcstring = rcarray!(immutable char); 2| auto r = rcstring("string"); 1| assert(r[0] == 's'); 1| assert(r[$ - 1] == 'g'); |} | |/// With Input Ranges |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.algorithm.iteration: filter; | static immutable numbers = [3, 2, 5, 2, 3, 7, 3]; | static immutable filtered = [5.0, 7]; 2| auto result = numbers.filter!"a > 3".rcarray!(immutable double); | static assert(is(typeof(result) == RCArray!(immutable double))); 1| assert (result[] == filtered); |} | |/++ |Params: | length = array length | deallocate = Flag, never deallocates memory if `false`. |Returns: minimally initialized rcarray. |+/ |RCArray!T mininitRcarray(T)(size_t length, bool deallocate = true) |{ 123| return RCArray!T(length, false, deallocate); |} | |/// |version(mir_test) |@safe pure nothrow @nogc unittest |{ 2| auto a = mininitRcarray!double(5); 1| assert(a.length == 5); 1| assert(a._counter == 1); 1| a[][] = 0; // a.opIndex()[] = 0; |} | |/++ |Thread safe reference counting iterator. |+/ 593|struct mir_rci(T) |{ | import mir.ndslice.slice: Slice; | import mir.ndslice.iterator: IotaIterator; | | /// | T* _iterator; | | /// | RCArray!T _array; | | /// 199| this(RCArray!T array) | { 398| this._iterator = (()@trusted => array.ptr)(); 199| this._array.proxySwap(array); | } | | /// 37| this(T* _iterator, RCArray!T array) | { 37| this._iterator = _iterator; 37| this._array.proxySwap(array); | } | | /// | inout(T)* lightScope()() return scope inout @property @trusted | { | debug | { 1106| assert(_array._payload <= _iterator); 2212| assert(_iterator is null || _iterator <= _array._payload + _array.length); | } 1106| return _iterator; | } | | /// | ref opAssign(typeof(null)) scope return nothrow | { | pragma(inline, true); 0000000| _iterator = null; 0000000| _array = null; 0000000| return this; | } | | /// | ref opAssign(return typeof(this) rhs) scope return @trusted | { 9| _iterator = rhs._iterator; 9| _array.proxySwap(rhs._array); 9| return this; | } | | /// | ref opAssign(Q)(return mir_rci!Q rhs) scope return nothrow | if (isImplicitlyConvertible!(Q*, T*)) | { | import core.lifetime: move; 2| _iterator = rhs._iterator; 2| _array = move(rhs._array); 2| return this; | } | | /// | mir_rci!(const T) lightConst()() return scope const nothrow @property 24| { return typeof(return)(_iterator, _array.lightConst); } | | /// | mir_rci!(immutable T) lightImmutable()() return scope immutable nothrow @property 0000000| { return typeof(return)(_iterator, _array.lightImmutable); } | | /// | ref inout(T) opUnary(string op : "*")() inout return scope | { | debug | { 269| assert(_iterator); 269| assert(_array._payload); 269| assert(_array._payload <= _iterator); 269| assert(_iterator <= _array._payload + _array.length); | } 269| return *_iterator; | } | | /// | ref inout(T) opIndex(ptrdiff_t index) inout return scope @trusted | { | debug | { 6366| assert(_iterator); 6366| assert(_array._payload); 6366| assert(_array._payload <= _iterator + index); 6366| assert(_iterator + index <= _array._payload + _array.length); | } 6366| return _iterator[index]; | } | | /// Returns: slice type of `Slice!(IotaIterator!size_t)` | Slice!(IotaIterator!size_t) opSlice(size_t dimension)(size_t i, size_t j) @safe scope const | if (dimension == 0) | in | { 8| assert(i <= j, "RCI!T.opSlice!0: the left opSlice boundary must be less than or equal to the right bound."); | } | do | { 8| return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /// Returns: ndslice on top of the refcounted iterator | auto opIndex(Slice!(IotaIterator!size_t) slice) | { | import core.lifetime: move; 16| auto it = this; 8| it += slice._iterator._index; 8| return Slice!(RCI!T)(slice.length, it.move); | } | | /// ditto | auto opIndex(Slice!(IotaIterator!size_t) slice) const | { | import core.lifetime: move; 0000000| auto it = lightConst; 0000000| it += slice._iterator._index; 0000000| return Slice!(RCI!(const T))(slice.length, it.move); | } | | /// | void opUnary(string op)() scope | if (op == "--" || op == "++") | { mixin(op ~ "_iterator;"); } | | /// | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "-" || op == "+") | { mixin("_iterator " ~ op ~ "= index;"); } | | /// | mir_rci!T opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") 13| { return mir_rci!T(_iterator + index, _array); } | | /// | mir_rci!(const T) opBinary(string op)(ptrdiff_t index) const | if (op == "+" || op == "-") | { return mir_rci!T(_iterator + index, _array); } | | /// | mir_rci!(immutable T) opBinary(string op)(ptrdiff_t index) immutable | if (op == "+" || op == "-") | { return mir_rci!T(_iterator + index, _array); } | | /// | ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const 0000000| { return this._iterator - right._iterator; } | | /// | bool opEquals()(scope ref const typeof(this) right) scope const 0000000| { return this._iterator == right._iterator; } | | /// | int opCmp()(scope ref const typeof(this) right) scope const 0000000| { auto d = this - right; return d ? d < 0 ? -1 : 1 : 0; } |} | |/// ditto |alias RCI = mir_rci; | |/// |version(mir_test) |@safe @nogc unittest |{ | | import mir.ndslice.traits: isIterator; | import mir.ndslice.slice; | import mir.rc.array; 2| auto slice = mir_rcarray!double(10).asSlice; | static assert(isIterator!(RCI!double)); | static assert(is(typeof(slice) == Slice!(RCI!double))); 2| auto matrix = slice.sliced(2, 5); | static assert(is(typeof(matrix) == Slice!(RCI!double, 2))); 1| slice[7] = 44; 1| assert(matrix[1, 2] == 44); |} | |/// |version(mir_test) |@safe @nogc unittest |{ | import mir.ndslice.slice; | import mir.rc.array; | | alias rcvec = Slice!(RCI!double); | 4| RCI!double a, b; 1| a = b; | 4| RCI!(const double) ca, cb; 1| ca = cb; 1| ca = cast(const) cb; | | void foo(scope ref rcvec x, scope ref rcvec y) | { 0000000| x[] = y[]; 0000000| x[1] = y[1]; 0000000| x[1 .. $] += y[1 .. $]; 0000000| x = x.save; | } |} | |version(mir_test) |@safe @nogc unittest |{ | import mir.ndslice; | import mir.rc.array; | import mir.series; | | @safe void bar(ref const mir_rcarray!(const double) a, ref mir_rcarray!(const double) b) | { 0000000| b = a; | } | | @safe void bari(ref immutable mir_rcarray!(immutable double) a, ref mir_rcarray!(immutable double) b) | { 0000000| b = a; | } | | @safe void foo(ref const RCI!(const double) a, ref RCI!(const double) b) | { 0000000| b = a; | } | | @safe void fooi(ref immutable RCI!(immutable double) a, ref RCI!(immutable double) b) | { 0000000| b = a; | } | | struct S | { | uint i; | @safe pure: | ~this() {} | } | | @safe void goo(ref const Series!(RCI!(const double), RCI!(const S)) a, ref Series!(RCI!(const double), RCI!(const S)) b) | { 0000000| b = a; | } | | @safe void gooi(ref immutable Series!(RCI!(immutable double), RCI!(const S)) a, ref Series!(RCI!(immutable double), RCI!(const S)) b) | { 0000000| b = a; | } | 2| struct C | { | Series!(RCI!(const S), RCI!(const S)) a; | Series!(RCI!(const S), RCI!(const S)) b; | } | 4| C a, b; 1| a = b; 1| a = cast(const) b; |} | |version(mir_test) |unittest |{ | import mir.ndslice.slice: Slice; | static RCArray!int foo() @safe | { 0000000| auto ret = RCArray!int(10); 0000000| return ret; | } | | | static Slice!(RCI!int) bat() @safe | { 0000000| auto ret = RCArray!int(10); 0000000| return ret.asSlice; | } | | static Slice!(RCI!int) bar() @safe | { 0000000| auto ret = RCArray!int(10); 0000000| auto d = ret.asSlice; 0000000| return d; | } |} | |version(mir_test) |@safe unittest |{ | struct S | { | uint s; | this(this) @nogc nothrow @safe | { | // () @trusted { | // puts("this(this)\n"); | // } (); | } | | ~this() nothrow @nogc @safe | { | // () @trusted { | // if (s) | // puts("~this()\n"); | // else | // puts("~this() - zero\n"); | // } (); | } | } | | struct C | { | S s; | } | 2| S[1] d = [S(1)]; 2| auto r = rcarray(d); |} | |version(mir_test) |unittest |{ | import mir.small_string; | alias S = SmallString!32u; 1| auto ars = [S("123"), S("422")]; | alias R = mir_rcarray!S; 2| auto rc = ars.rcarray!S; | 2| RCArray!int value = null; 1| value = null; |} source/mir/rc/array.d is 75% covered <<<<<< EOF # path=source-mir-rc-context.lst |/++ |$(H1 Thread-safe reference-counted context implementation). |+/ |module mir.rc.context; | |import mir.type_info; | |/++ |+/ |struct mir_rc_context |{ | /// | extern (C) void function(mir_rc_context*) @system nothrow @nogc pure deallocator; | /// | immutable(mir_type_info)* typeInfo; | /// | shared size_t counter; | /// | size_t length; |} | |/++ |Increase counter by 1. | |Params: | context = shared_ptr context (not null) |+/ |export extern(C) |void mir_rc_increase_counter(ref mir_rc_context context) @system nothrow @nogc pure |{ | import core.atomic: atomicOp; | with(context) | { 691| if (counter) | { 691| counter.atomicOp!"+="(1); | } | } |} | |/++ |Decrease counter by 1. |Destroys data if counter decreased from 1 to 0. | |Params: | context = shared_ptr context (not null) |+/ |export extern(C) |void mir_rc_decrease_counter(ref mir_rc_context context) @system nothrow @nogc pure |{ | pragma(inline, true); | import core.atomic: atomicOp; | with(context) | { 978| if (counter) | { 976| if (counter.atomicOp!"-="(1) == 0) | { 285| mir_rc_delete(context); | } | } | // else | // { | // assert(0); | // } | } |} | |/++ |+/ |export extern(C) |void mir_rc_delete(ref mir_rc_context context) | @system nothrow @nogc pure |{ 285| assert(context.deallocator); | with(context) | { | with(typeInfo) | { 285| if (destructor) | { 9| auto ptr = cast(void*)(&context + 1); 9| auto i = length; 9| assert(i); | do | { 19| destructor(ptr); 19| ptr += size; | } 19| while(--i); | } | } | } 285| if (context.counter) | assert(0); | version (mir_secure_memory) | { | (cast(ubyte*)(&context + 1))[0 .. context.length * context.typeInfo.size] = 0; | } 285| context.deallocator(&context); |} | |/++ |+/ |export extern(C) |mir_rc_context* mir_rc_create( | ref immutable(mir_type_info) typeInfo, | size_t length, | scope const void* payload = null, | bool initialize = true, | bool deallocate = true, | ) @system nothrow @nogc pure |{ | import mir.internal.memory: malloc, free; | import core.stdc.string: memset, memcpy; | 287| assert(length); 287| auto size = length * typeInfo.size; 287| auto fullSize = mir_rc_context.sizeof + size; 287| if (auto p = malloc(fullSize)) | { | version (mir_secure_memory) | { | (cast(ubyte*)p)[0 .. fullSize] = 0; | } 287| auto context = cast(mir_rc_context*)p; 287| context.deallocator = &free; 287| context.typeInfo = &typeInfo; 287| context.counter = deallocate; 287| context.length = length; | 287| if (initialize) | { 121| auto ptr = cast(void*)(context + 1); 121| if (payload) | { 48| switch(typeInfo.size) | { 0000000| case 1: 0000000| memset(ptr, *cast(ubyte*)payload, size); 0000000| break; 0000000| case 2: 0000000| (cast(ushort*)ptr)[0 .. length] = *cast(ushort*)payload; 0000000| break; 0000000| case 4: 0000000| (cast(uint*)ptr)[0 .. length] = *cast(uint*)payload; 0000000| break; 3| case 8: 3| (cast(ulong*)ptr)[0 .. length] = *cast(ulong*)payload; 3| break; | static if (is(ucent)) | { | case 16: | (cast(ucent*)ptr)[0 .. length] = *cast(ucent*)payload; | break; | } 45| default: 1143| foreach(i; 0 .. length) | { 336| memcpy(ptr, payload, typeInfo.size); 336| ptr += typeInfo.size; | } | } | } | else | { 73| memset(ptr, 0, size); | } | } 287| return context; | } 0000000| return null; |} | |/// |package mixin template CommonRCImpl() |{ | /// | ThisTemplate!(const T) lightConst()() return scope const @nogc nothrow @trusted @property | { return *cast(typeof(return)*) &this; } | | /// ditto | ThisTemplate!(immutable T) lightImmutable()() return scope immutable @nogc nothrow @trusted @property | { return *cast(typeof(return)*) &this; } | | /// | ThisTemplate!(const Unqual!T) moveToConst()() return scope @nogc nothrow @trusted @property | { | import core.lifetime: move; | return move(*cast(typeof(return)*) &this); | } | | /// | pragma(inline, true) | size_t _counter() @trusted scope pure nothrow @nogc const @property | { | return cast(bool)this ? context.counter : 0; | } | | /// | C opCast(C)() const | if (is(Unqual!C == bool)) | { | return _thisPtr !is null; | } | | /// | ref C opCast(C : ThisTemplate!Q, Q)() pure nothrow @nogc @trusted | if (isImplicitlyConvertible!(T*, Q*)) | { | return *cast(typeof(return)*)&this; | } | | /// ditto | C opCast(C : ThisTemplate!Q, Q)() pure nothrow @nogc const @trusted | if (isImplicitlyConvertible!(const(T)*, Q*)) | { | return *cast(typeof(return)*)&this; | } | | /// ditto | C opCast(C : ThisTemplate!Q, Q)() pure nothrow @nogc immutable @trusted | if (isImplicitlyConvertible!(immutable(T)*, Q*)) | { | return *cast(typeof(return)*)&this; | } | | /// ditto | C opCast(C : ThisTemplate!Q, Q)() pure nothrow @nogc const @system | if (isImplicitlyConvertible!(immutable(T)*, Q*) && !isImplicitlyConvertible!(const(T)*, Q*)) | { | return *cast(typeof(return)*)&this; | } |} source/mir/rc/context.d is 78% covered <<<<<< EOF # path=source-mir-rc-package.lst |/++ |$(H1 Thread-safe reference-counted arrays and pointers) | |Mir provides two kinds of ref-counting pointers and two kinds of ref-counted arrays. | |The first kind pointer is `RCPtr`, which consists of a pointer to the context and pointer to the value.`RCPtr` supports structural and object polymorphism. It allows getting members with the same context as the root. |The second kind is `SlimRCPtr`, which consist only from a pointer to the value. The context for `SlimRCPtr`is computed using a fixed-length memory shift from the pointer to the value. |`SlimRCPtr` can be converted to an `RCPtr` and to an `RCArray` of the one element. | |`RCArray` is an array type without range primitives. It's length can't be reduced after construction.In the other hand, `Slice!(RCI!(T))` is an ndslice with all random-access range primitives.`RCI` is an iterator, which consists of `RCArray` and the pointer to the current element. |`RCArray!T` can be converted or moved to `Slice!(RCI!(T))` using `.asSlice` or `.moveToSlice` methods respectively. | |$(RED `RCArray!T` aliases itself to a common D array slice. This feature may cause a segmentation fault in safe code if used without DIP1000.) | |`RCPtr!T` can be constructed from an element index and `RCArray!T` / `Slice!(RCI!(T))`. | |The package publicly imports $(MREF mir,rc,array), $(MREF mir,rc,ptr), and $(MREF mir,rc,slim_ptr). | |See_also: $(MREF mir,ndslice). |+/ |module mir.rc; | |/// |public import mir.rc.array; |/// |public import mir.rc.ptr; |/// |public import mir.rc.slim_ptr; | |import mir.ndslice.slice; | |/++ |Returns: shared pointer constructed from the slim shared pointer. | |The function has zero computation cost. |+/ |RCPtr!F toRCPtr(F)(return SlimRCPtr!F contextAndValue) @trusted |{ 1| typeof(return) ret; 1| ret._value = contextAndValue._value; 1| ret._context = &contextAndValue.context(); 1| contextAndValue._value = null; 1| return ret; |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import core.lifetime: move; | struct S | { | double e; | } | struct C | { | int i; | S s; | } | 2| auto a = createSlimRC!C(10, S(3)); 2| auto s = a.move.toRCPtr.shareMember!"s"; 1| assert(s._counter == 1); 1| assert(s.e == 3); |} | |/++ |Returns: shared pointer constructed with the `array`'s context and the value points to `array[index]`. | |The function has zero computation cost. |+/ |RCPtr!F toRCPtrAt(F)(return RCArray!F array, size_t index) @trusted | if (!is(R == class) && !is(R == interface)) |in { 1| assert(index < array.length, "toRCPtrAt: index should be less then array.length"); |} |do { 1| typeof(return) ret; 1| ret._value = array._payload + index; 1| ret._context = &array.context(); 1| array._payload = null; 1| return ret; |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | struct S { double e; } | 2| auto a = RCArray!S(10); 1| a[3].e = 4; | 2| auto s = a.toRCPtrAt(3); | 1| assert(s._counter == 2); 1| assert(s.e == 4); |} | |/// ditto |RCPtr!F toRCPtrAt(F)(return Slice!(RCI!F) array, size_t index) @trusted | if (!is(R == class) && !is(R == interface)) |in { 1| assert(index < array.length, "toRCPtrAt: index should be less then array.length"); |} |do { 1| typeof(return) ret; 1| ret._value = array._iterator._iterator + index; 1| ret._context = &array._iterator._array.context(); 1| array._iterator._array._payload = null; 1| return ret; |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | struct S { double e; } | 2| auto a = RCArray!S(10).asSlice[5 .. $]; 1| a[3].e = 4; | 2| auto s = a.toRCPtrAt(3); | 1| assert(s._counter == 2); 1| assert(s.e == 4); |} | |/++ |Returns: RC array length of one constructed from the slim shared pointer. | |The function has zero computation cost. |+/ |RCArray!F toRCArray(F)(return SlimRCPtr!F context) @trusted |{ 1| typeof(return) ret; 1| ret._payload = context._value; 1| context._value = null; 1| return ret; |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | struct S { double e; } | 2| auto a = createSlimRC!S(4).toRCArray; 1| assert(a._counter == 1); 1| assert(a.length == 1); 1| assert(a[0].e == 4); |} source/mir/rc/package.d is 100% covered <<<<<< EOF # path=source-mir-rc-ptr.lst |/++ |$(H1 Thread-safe reference-counted shared pointers). | |This implementation supports class and struct (`alias this`) polymorphism. |+/ |module mir.rc.ptr; | |import mir.rc.context; |import mir.type_info; |import std.traits; | |package static immutable allocationExcMsg = "mir_rcptr: out of memory error."; |package static immutable getExcMsg = "mir_rcptr: trying to use null value."; | |version (D_Exceptions) |{ | import core.exception: OutOfMemoryError, InvalidMemoryOperationError; | package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); |} | |/++ |Thread safe reference counting array. | |This implementation supports class and struct (`alias this`) polymorphism. | |`__xdtor` if any is used to destruct objects. | |The implementation never adds roots into the GC. |+/ |struct mir_rcptr(T) |{ | static if (is(T == class) || is(T == interface) || is(T == struct) || is(T == union)) | static assert(!__traits(isNested, T), "mir_rcptr does not support nested types."); | | /// | static if (is(T == class) || is(T == interface)) | package Unqual!T _value; | else | package T* _value; | package mir_rc_context* _context; | | package ref mir_rc_context context() inout return scope @trusted @property | { 32| return *cast(mir_rc_context*)_context; | } | | package void _reset() | { 15| _value = null; 15| _context = null; | } | | inout(void)* _thisPtr() inout return scope @trusted @property | { 70| return cast(inout(void)*) _value; | } | | package alias ThisTemplate = .mir_rcptr; | | /// ditto | alias opUnary(string op : "*") = _get_value; | /// ditto | alias _get_value this; | | static if (is(T == class) || is(T == interface)) | /// | pragma(inline, true) | inout(T) _get_value() return scope inout @property | { 11| assert(this, getExcMsg); 11| return _value; | } | else | /// | pragma(inline, true) | ref inout(T) _get_value() return scope inout @property | { 12| assert(this, getExcMsg); 12| return *_value; | } | | /// | void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe | { 0000000| auto t0 = this._value; 0000000| auto t1 = this._context; 0000000| this._value = rhs._value; 0000000| this._context = rhs._context; 0000000| rhs._value = t0; 0000000| rhs._context = t1; | } | | /// 0000000| this(typeof(null)) | { | } | | /// | mixin CommonRCImpl; | | | /// | pragma(inline, true) | bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc | { 3| return !this; | } | | /// ditto | bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc | { 0000000| if (_thisPtr is null) 0000000| return rhs._thisPtr is null; 0000000| if (rhs._thisPtr is null) 0000000| return false; 0000000| return _get_value == rhs._get_value; | } | | /// | auto opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc | { | if (_thisPtr is null) | return (rhs._thisPtr is null) - 1; | if (rhs._thisPtr is null) | return 1; | return _get_value.opCmp(rhs._get_value); | } | | /// | size_t toHash() @trusted scope const pure nothrow @nogc | { 0000000| if (_thisPtr is null) 0000000| return 0; 0000000| return hashOf(_get_value); | } | | /// | ~this() nothrow | { | static if (hasElaborateDestructor!T || hasDestructor!T) | { 1| if (false) // break @safe and pure attributes | { | Unqual!T* object; | (*object).__xdtor; | } | } 27| if (this) | { 30| (() @trusted { mir_rc_decrease_counter(context); })(); 15| debug _reset; | } | } | | static if (is(T == const) || is(T == immutable)) | this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc | { | if (rhs) | { | this._value = cast(typeof(this._value))rhs._value; | this._context = cast(typeof(this._context))rhs._context; | mir_rc_increase_counter(context); | } | } | | static if (is(T == immutable)) | this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc | { | if (rhs) | { | this._value = cast(typeof(this._value))rhs._value; | this._context = cast(typeof(this._context))rhs._context; | mir_rc_increase_counter(context); | } | } | | static if (is(T == immutable)) | this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc | { | if (rhs) | { | this._value = cast(typeof(this._value))rhs._value; | this._context = cast(typeof(this._context))rhs._context; | mir_rc_increase_counter(context); | } | } | 7| this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc | { 7| if (rhs) | { 7| this._value = rhs._value; 7| this._context = rhs._context; 7| mir_rc_increase_counter(context); | } | } | | /// | ref opAssign(typeof(null)) return @trusted // pure nothrow @nogc | { 0000000| this = typeof(this).init; | } | | /// | ref opAssign(return typeof(this) rhs) return @trusted // pure nothrow @nogc | { 0000000| this.proxySwap(rhs); 0000000| return this; | } | | /// | ref opAssign(Q)(return ThisTemplate!Q rhs) return @trusted // pure nothrow @nogc | if (isImplicitlyConvertible!(Q*, T*)) | { | this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}()); | return this; | } |} | |/// |alias RCPtr = mir_rcptr; | |/++ |Returns: shared pointer of the member and the context from the current pointer. |+/ |auto shareMember(string member, T, Args...)(return mir_rcptr!T context, auto ref Args args) @safe |{ | import core.lifetime: move; | void foo(A)(auto ref A) {} 3| assert(context != null); | static if (args.length) | { | // breaks safaty | if (false) foo(__traits(getMember, T.init, member)(forward!args)); | return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member)(forward!args), context.move))(); | } | else | { | // breaks safaty 3| if (false) foo(__traits(getMember, T.init, member)); 6| return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member), context.move))(); | } |} | |/++ |Returns: shared pointer constructed with current context. |+/ |@system .mir_rcptr!R createRCWithContext(R, F)(return R value, return const mir_rcptr!F context) | if (is(R == class) || is(R == interface)) |{ 2| typeof(return) ret; 2| ret._value = cast()value; 2| ret._context = cast(mir_rc_context*)context._context; 2| (*cast(mir_rcptr!F*)&context)._value = null; 2| (*cast(mir_rcptr!F*)&context)._context = null; 2| return ret; |} | |///ditto |@system .mir_rcptr!R createRCWithContext(R, F)(return ref R value, return const mir_rcptr!F context) | if (!is(R == class) && !is(R == interface)) |{ 4| typeof(return) ret; 4| ret._value = &value; 4| ret._context = cast(mir_rc_context*)context._context; 4| (*cast(mir_rcptr!F*)&context)._value = null; 4| (*cast(mir_rcptr!F*)&context)._context = null; 4| return ret; |} | |/++ |Construct a shared pointer of a required type with a current context. |Provides polymorphism abilities for classes and structures with `alias this` syntax. |+/ |mir_rcptr!R castTo(R, T)(return mir_rcptr!T context) @trusted | if (isImplicitlyConvertible!(T, R)) |{ | import core.lifetime: move; 3| return createRCWithContext(cast(R)context._get_value, move(context)); |} | |/// ditto |mir_rcptr!(const R) castTo(R, T)(return const mir_rcptr!T context) @trusted | if (isImplicitlyConvertible!(const T, const R)) |{ | import core.lifetime: move; | return createRCWithContext(cast(const R)context._get_value, move(*cast(mir_rcptr!T*)&context)); |} | |/// ditto |mir_rcptr!(immutable R) castTo(R, T)(return immutable mir_rcptr!T context) @trusted | if (isImplicitlyConvertible!(immutable T, immutable R)) |{ | import core.lifetime: move; | return createRCWithContext(cast(immutable R)context._get_value, move(*cast(mir_rcptr!T*)&context)); |} | |/// |template createRC(T) | if (!is(T == interface) && !__traits(isAbstractClass, T)) |{ | /// | mir_rcptr!T createRC(Args...)(auto ref Args args) | { 5| typeof(return) ret; 5| with (ret) () @trusted { 5| _context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); 5| if (!_context) | { | version(D_Exceptions) 0000000| throw allocationError; | else | assert(0, allocationExcMsg); | } 5| _value = cast(typeof(_value))(_context + 1); | } (); | import core.lifetime: forward; | import mir.conv: emplace; 5| cast(void) emplace!T(ret._value, forward!args); 5| return ret; | } |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ 2| auto a = createRC!double(10); 2| auto b = a; 1| assert(*b == 10); 1| *b = 100; 1| assert(*a == 100); |} | |/// Classes with empty constructor |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | static class C | { | int index = 34; | | override size_t toHash() const scope @safe pure nothrow @nogc | { 0000000| return index; | } | } 1| assert(createRC!C.index == 34); |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | static interface I { | ref double bar() @safe pure nothrow @nogc; | size_t toHash() @trusted scope const pure nothrow @nogc; | } | static abstract class D | { | int index; | | override size_t toHash() const scope @safe pure nothrow @nogc | { 0000000| return index; | } | } | static class C : D, I | { | double value; 2| ref double bar() @safe pure nothrow @nogc { return value; } 2| this(double d){ value = d; } | | override size_t toHash() const scope @safe pure nothrow @nogc | { 0000000| return hashOf(value, super.toHash); | } | } 2| auto a = createRC!C(10); 1| assert(a._counter == 1); 2| auto b = a; 1| assert(a._counter == 2); 1| assert((*b).value == 10); 1| b.value = 100; // access via alias this syntax 1| assert(a.value == 100); 1| assert(a._counter == 2); | 2| auto d = a.castTo!D; //RCPtr!D 1| assert(d._counter == 3); 1| d.index = 234; 1| assert(a.index == 234); 2| auto i = a.castTo!I; //RCPtr!I 1| assert(i.bar == 100); 1| assert(i._counter == 4); | 2| auto v = a.shareMember!"value"; //RCPtr!double 2| auto w = a.shareMember!"bar"; //RCPtr!double 1| assert(i._counter == 6); 1| assert(*v == 100); 2| ()@trusted{assert(&*w is &*v);}(); |} | |/// 'Alias This' support |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | struct S | { | double e; | } | struct C | { | int i; | S s; | // 'alias' should be accesable by reference | // or a class/interface | alias s this; | } | 2| auto a = createRC!C(10, S(3)); 2| auto s = a.castTo!S; // RCPtr!S 1| assert(s._counter == 2); 1| assert(s.e == 3); |} | |version(unittest): | |package struct _test_unpure_system_dest_s__ { | static int numStructs; | int i; | 2| this(this This)(int i) { 2| this.i = i; 2| ++numStructs; | } | | ~this() @system nothrow { 2| auto d = new int[2]; 2| --numStructs; | } |} | |version(mir_test) |@system nothrow |unittest |{ | import mir.rc.array; 2| auto ptr = createRC!_test_unpure_system_dest_s__(42); 2| auto arr = rcarray!_test_unpure_system_dest_s__(3); |} source/mir/rc/ptr.d is 78% covered <<<<<< EOF # path=source-mir-rc-slim_ptr.lst |/++ |$(H1 Thread-safe slim reference-counted shared pointers). | |This implementation does not support polymorphism. |+/ |module mir.rc.slim_ptr; | |import mir.rc.context; |import mir.type_info; |import std.traits; | |package static immutable allocationExcMsg = "mir_slim_rcptr: out of memory error."; |package static immutable getExcMsg = "mir_slim_rcptr: trying to use null value."; | |version (D_Exceptions) |{ | import core.exception: OutOfMemoryError, InvalidMemoryOperationError; | package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); |} | |/++ |Thread safe reference counting array. | |This implementation does not support polymorphism. | |The implementation never adds roots into the GC. |+/ |struct mir_slim_rcptr(T) |{ | static if (is(T == class) || is(T == interface) || is(T == struct) || is(T == union)) | static assert(!__traits(isNested, T), "mir_slim_rcptr does not support nested types."); | | /// | static if (is(T == class) || is(T == interface)) | package Unqual!T _value; | else | package T* _value; | | package ref mir_rc_context context() inout return scope pure nothrow @nogc @trusted @property | { 16| assert(_value); 16| return (cast(mir_rc_context*)_value)[-1]; | } | | package void _reset() | { 8| _value = null; | } | | inout(void)* _thisPtr() inout return scope @trusted @property | { 26| return cast(inout(void)*) _value; | } | | package alias ThisTemplate = .mir_slim_rcptr; | | /// ditto | alias opUnary(string op : "*") = _get_value; | /// ditto | alias _get_value this; | | static if (is(T == class) || is(T == interface)) | /// | pragma(inline, true) | inout(T) _get_value() return scope inout @property | { 4| assert(this, getExcMsg); 4| return _value; | } | else | /// | pragma(inline, true) | ref inout(T) _get_value() return scope inout @property | { 4| assert(this, getExcMsg); 4| return *_value; | } | | /// | void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe | { 0000000| auto t = this._value; 0000000| this._value = rhs._value; 0000000| rhs._value = t; | } | | /// 0000000| this(typeof(null)) | { | } | | /// | mixin CommonRCImpl; | | | /// | pragma(inline, true) | bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc | { 0000000| return !this; | } | | /// ditto | bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc | { 0000000| if (_thisPtr is null) 0000000| return rhs._thisPtr is null; 0000000| if (rhs._thisPtr is null) 0000000| return false; 0000000| return _get_value == rhs._get_value; | } | | /// | auto opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc | { | if (_thisPtr is null) | return (rhs._thisPtr is null) - 1; | if (rhs._thisPtr is null) | return 1; | return _get_value.opCmp(rhs._get_value); | } | | /// | size_t toHash() @trusted scope const pure nothrow @nogc | { 0000000| if (_thisPtr is null) 0000000| return 0; 0000000| return hashOf(_get_value); | } | | /// | ~this() nothrow | { | static if (hasElaborateDestructor!T || hasDestructor!T) | { 1| if (false) // break @safe and pure attributes | { | Unqual!T* object; | (*object).__xdtor; | } | } 11| if (this) | { 16| (() @trusted { mir_rc_decrease_counter(context); })(); 8| debug _reset; | } | } | | static if (is(T == const) || is(T == immutable)) | this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc | { | if (rhs) | { | this._value = cast(typeof(this._value))rhs._value; | mir_rc_increase_counter(context); | } | } | | static if (is(T == immutable)) | this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc | { | if (rhs) | { | this._value = cast(typeof(this._value))rhs._value; | mir_rc_increase_counter(context); | } | } | | static if (is(T == immutable)) | this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc | { | if (rhs) | { | this._value = cast(typeof(this._value))rhs._value; | mir_rc_increase_counter(context); | } | } | 3| this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc | { 3| if (rhs) | { 3| this._value = rhs._value; 3| mir_rc_increase_counter(context); | } | } | | /// | ref opAssign(typeof(null)) return @trusted // pure nothrow @nogc | { 0000000| this = typeof(this).init; | } | | /// | ref opAssign(return typeof(this) rhs) return @trusted // pure nothrow @nogc | { 0000000| this.proxySwap(rhs); 0000000| return this; | } | | /// | ref opAssign(Q)(return ThisTemplate!Q rhs) return @trusted // pure nothrow @nogc | if (isImplicitlyConvertible!(Q*, T*)) | { | this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}()); | return this; | } |} | |/// |alias SlimRCPtr = mir_slim_rcptr; | |/// |template createSlimRC(T) | if (!is(T == interface) && !__traits(isAbstractClass, T)) |{ | /// | mir_slim_rcptr!T createSlimRC(Args...)(auto ref Args args) | { 7| typeof(return) ret; 7| with (ret) () @trusted { 7| auto context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); 7| if (!context) | { | version(D_Exceptions) 0000000| throw allocationError; | else | assert(0, allocationExcMsg); | } 7| _value = cast(typeof(_value))(context + 1); | } (); | import mir.functional: forward; | import mir.conv: emplace; 7| cast(void) emplace!T(ret._value, forward!args); 7| return ret; | } |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ 2| auto a = createSlimRC!double(10); 2| auto b = a; 1| assert(*b == 10); 1| *b = 100; 1| assert(*a == 100); |} | |/// Classes |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | static class C | { | int index; | double value; 0000000| ref double bar() @safe pure nothrow @nogc { return value; } 2| this(double d) { value = d; } | | override size_t toHash() const scope @safe pure nothrow @nogc | { 0000000| return index; | } | } 2| auto a = createSlimRC!C(10); 1| assert(a._counter == 1); 2| auto b = a; 1| assert(a._counter == 2); 1| assert((*b).value == 10); 1| b.value = 100; // access via alias this syntax 1| assert(a.value == 100); 1| assert(a._counter == 2); |} | |/// Structs |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | struct S | { | double e; | } | struct C | { | int i; | S s; | // 'alias' should be accesable by reference | // or a class/interface | alias s this; | } | 2| auto a = createSlimRC!C(10, S(3)); 2| auto s = a; 1| assert(s._counter == 2); 1| assert(s.e == 3); |} | |/// Classes with empty constructor |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | static class C | { | int index = 34; | | override size_t toHash() const scope @safe pure nothrow @nogc | { 0000000| return index; | } | } 1| assert(createSlimRC!C.index == 34); |} | |version(unittest): | |package struct _test_unpure_system_dest_s__ { | static int numStructs; | int i; | 2| this(this This)(int i) { 2| this.i = i; 2| ++numStructs; | } | | ~this() @system nothrow { 2| auto d = new int[2]; 2| --numStructs; | } |} | |version(mir_test) |@system nothrow |unittest |{ | import mir.rc.array; 2| auto ptr = createSlimRC!_test_unpure_system_dest_s__(42); 2| auto arr = rcarray!_test_unpure_system_dest_s__(3); |} source/mir/rc/slim_ptr.d is 71% covered <<<<<< EOF # path=source-mir-serde.lst |/++ |This implements common de/serialization routines. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) |+/ |module mir.serde; | |import mir.functional: naryFun; |import mir.reflection; |import std.meta: AliasSeq; |import std.traits: TemplateArgsOf, EnumMembers, hasUDA, isAggregateType; | |version (D_Exceptions) |{ | /++ | Serde Exception | +/ | class SerdeException : Exception | { | /// 0000000| this( | string msg, | string file = __FILE__, | size_t line = __LINE__, | Throwable next = null) pure nothrow @nogc @safe | { 0000000| super(msg, file, line, next); | } | | /// 0000000| this( | string msg, | Throwable next, | string file = __FILE__, | size_t line = __LINE__, | ) pure nothrow @nogc @safe | { 0000000| this(msg, file, line, next); | } | | SerdeException toMutable() @trusted pure nothrow @nogc const | { 0000000| return cast() this; | } | | alias toMutable this; | } | | /++ | Serde Exception with formatting support | +/ | class SerdeMirException : SerdeException | { | import mir.exception: MirThrowableImpl, mirExceptionInitilizePayloadImpl; | | enum maxMsgLen = 447; | | /// | mixin MirThrowableImpl; | } |} | |/++ |Constructs annotated type. |+/ |template SerdeAnnotated(T, string annotation) |{ | /// | @serdeAlgebraicAnnotation(annotation) | @serdeProxy!T | struct SerdeAnnotated | { | /// | T value; | /// | alias value this; | } |} | |/++ |Helper enumeration for for serializer . |Use negative `int` values for user defined targets. |+/ |enum SerdeTarget : int |{ | /// | ion, | /// | json, | /// | cbor, | /// | msgpack, | /// | yaml, | /// | csv, | /// | excel, | /// | bloomberg, | /// | typedJson, |} | |/++ |Attribute for key overloading during Serialization and Deserialization. |The first argument overloads the key value during serialization unless `serdeKeyOut` is given. |+/ |struct serdeKeys |{ | /// | immutable(string)[] keys; | |@trusted pure nothrow @nogc: | /// 0000000| this(immutable(string)[] keys...) { this.keys = keys; } |} | |/++ |Attribute for key overloading during serialization. |+/ |struct serdeKeyOut |{ | /// | string key; | |@safe pure nothrow @nogc: | /// 0000000| this(string key) { this.key = key; } |} | |/++ |The attribute should be used as a hind for scripting languages to register type deserializer in the type system. | |The attribute should be applied to a type definition. |+/ |enum serdeRegister; | |/++ |The attribute can be applied to a string-like member that should be de/serialized as an annotation / attribute. | |Also, the attribute can be applied on a type to denote that the type should be used to de/serialize annotated value. | |This feature is used in $(MIR_PACKAGE mir-ion). |+/ |enum serdeAnnotation; | |/++ |Checks if the type marked with $(LREF serdeAnnotation). |+/ |template isAnnotated(T) |{ | import mir.serde: serdeAnnotation; | static if (is(T == enum) || isAggregateType!T) { | enum isAnnotated = hasUDA!(T, serdeAnnotation); | static if (isAnnotated) | static assert(__traits(getAliasThis, T).length == 1 || __traits(hasMember, T, "value"), "@serdeAnnotation " ~ T.stringof ~" requires alias this member or `value` member."); | } | else | enum isAnnotated = false; |} | |private template serdeIsAnnotationMemberIn(T) |{ | enum bool serdeIsAnnotationMemberIn(string member) | = hasUDA!(__traits(getMember, T, member), serdeAnnotation) | && !hasUDA!(__traits(getMember, T, member), serdeIgnore) | && !hasUDA!(__traits(getMember, T, member), serdeIgnoreIn); |} | |/++ |+/ |template serdeGetAnnotationMembersIn(T) |{ | import std.meta: aliasSeqOf, Filter; | static if (isAggregateType!T) | enum string[] serdeGetAnnotationMembersIn = [Filter!(serdeIsAnnotationMemberIn!T, aliasSeqOf!(DeserializableMembers!T))]; | else | enum string[] serdeGetAnnotationMembersIn = null; |} | | |/// |version(mir_test) unittest |{ | struct S | { | double data; | | @serdeAnnotation | string a; | @serdeAnnotation @serdeIgnoreIn | string b; | @serdeAnnotation @serdeIgnoreOut | string c; | @serdeAnnotation @serdeIgnore | string d; | } | | static assert(serdeGetAnnotationMembersIn!int == []); | static assert(serdeGetAnnotationMembersIn!S == ["a", "c"]); |} | |private template serdeIsAnnotationMemberOut(T) |{ | enum bool serdeIsAnnotationMemberOut(string member) | = hasUDA!(__traits(getMember, T, member), serdeAnnotation) | && !hasUDA!(__traits(getMember, T, member), serdeIgnore) | && !hasUDA!(__traits(getMember, T, member), serdeIgnoreOut); |} | |/++ |+/ |template serdeGetAnnotationMembersOut(T) |{ | import std.meta: aliasSeqOf, Filter; | static if (isAggregateType!T) | enum string[] serdeGetAnnotationMembersOut = [Filter!(serdeIsAnnotationMemberOut!T, aliasSeqOf!(SerializableMembers!T))]; | else | enum string[] serdeGetAnnotationMembersOut = null; |} | |/// |version(mir_test) unittest |{ | struct S | { | double data; | | @serdeAnnotation | string a; | @serdeAnnotation @serdeIgnoreIn | string b; | @serdeAnnotation @serdeIgnoreOut | string c; | @serdeAnnotation @serdeIgnore | string d; | @serdeAnnotation enum string e = "e"; | static @serdeAnnotation string f() @safe pure nothrow @nogc @property { 0000000| return "f"; | } | } | | static assert(serdeGetAnnotationMembersOut!int == []); | static assert(serdeGetAnnotationMembersOut!S == ["a", "b", "f"]); |} | |/++ |An annotation / attribute for algebraic types deserialization. | |This feature is used in $(MIR_PACKAGE mir-ion) for $(GMREF mir-core, mir,algebraic). |+/ |struct serdeAlgebraicAnnotation |{ | /// | string annotation; | |@safe pure nothrow @nogc: | /// 0000000| this(string annotation) { this.annotation = annotation; } |} | |/++ |+/ |template serdeHasAlgebraicAnnotation(T) |{ | static if (isAggregateType!T || is(T == enum)) | { | static if (hasUDA!(T, serdeAlgebraicAnnotation)) | { | enum serdeHasAlgebraicAnnotation = true; | } | else | { | enum serdeHasAlgebraicAnnotation = false; | } | } | else | { | enum serdeHasAlgebraicAnnotation = false; | } |} | |/++ |+/ |template serdeGetAlgebraicAnnotation(T) |{ | static if (hasUDA!(T, serdeAlgebraicAnnotation)) | { | enum string serdeGetAlgebraicAnnotation = getUDA!(T, serdeAlgebraicAnnotation).annotation; | } | else | { | T* aggregate; | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | enum serdeGetAlgebraicAnnotation = .serdeGetAlgebraicAnnotation!A; | } |} | |/++ |User defined attribute used to attach a function that returns a deserialization delegate. | |The attribute is usefull for scripting languages and dynamic algebraic types. |+/ |template serdeDynamicAlgebraic(alias getAlgebraicDeserializerByAnnotation) |{ | enum serdeDynamicAlgebraic; |} | |/// |version(mir_test) |unittest |{ | static struct _global | { | alias Deserializer = S delegate(string s, ubyte[] data) @safe pure; 0000000| Deserializer getDeserializer(string name) { return map[name]; } | Deserializer[string] map; | | @serdeDynamicAlgebraic!getDeserializer | struct S {} | | static assert(serdeIsDynamicAlgebraic!S); | static assert(__traits(isSame, serdeGetAlgebraicDeserializer!S, getDeserializer)); | } |} | |/++ |+/ |template serdeIsDynamicAlgebraic(T) |{ | static if (isAggregateType!T) | { | static if (hasUDA!(T, serdeDynamicAlgebraic)) | { | enum serdeIsDynamicAlgebraic = true; | } | else | static if (__traits(getAliasThis, T).length) | { | T* aggregate; | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | enum serdeIsDynamicAlgebraic = .serdeIsDynamicAlgebraic!A; | } | else | { | enum serdeIsDynamicAlgebraic = false; | } | } | else | { | enum serdeIsDynamicAlgebraic = false; | } |} | |/++ |+/ |template serdeGetAlgebraicDeserializer(T) |{ | static if (hasUDA!(T, serdeDynamicAlgebraic)) | { | alias serdeGetAlgebraicDeserializer = TemplateArgsOf!(getUDA!(T, serdeDynamicAlgebraic))[0]; | } | else | { | T* aggregate; | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | alias serdeGetAlgebraicDeserializer = .serdeGetAlgebraicDeserializer!A; | } |} | |/++ |Returns: | immutable array of the input keys for the symbol or enum value |+/ |template serdeGetKeysIn(alias symbol) |{ | static if (hasUDA!(symbol, serdeAnnotation) || hasUDA!(symbol, serdeIgnore) || hasUDA!(symbol, serdeIgnoreIn)) | enum immutable(string)[] serdeGetKeysIn = null; | else | static if (hasUDA!(symbol, serdeKeys)) | enum immutable(string)[] serdeGetKeysIn = getUDA!(symbol, serdeKeys).keys; | else | enum immutable(string)[] serdeGetKeysIn = [__traits(identifier, symbol)]; |} | |/// ditto |immutable(string)[] serdeGetKeysIn(T)(const T value) @trusted pure nothrow @nogc | if (is(T == enum)) |{ | foreach (i, member; EnumMembers!T) | {{ | alias all = __traits(getAttributes, EnumMembers!T[i]); | }} | | import std.meta: staticMap; | static immutable ret = [staticMap!(.serdeGetKeysIn, EnumMembers!T)]; | static if (__VERSION__ < 2093) | { | final switch (value) | { | foreach (i, member; EnumMembers!T) | { | case member: | return ret[i]; | } | } | } | else | { | import mir.enums: getEnumIndex; | uint index = void; | if (getEnumIndex(value, index)) | return ret[index]; | assert(0); | } |} | |/// |version(mir_test) unittest |{ | struct S | { | int f; | | @serdeKeys("D", "t") | int d; | | @serdeIgnore | int i; | | @serdeIgnoreIn | int ii; | | @serdeIgnoreOut | int io; | | void p(int) @property {} | } | | static assert(serdeGetKeysIn!(S.f) == ["f"]); | static assert(serdeGetKeysIn!(S.d) == ["D", "t"]); | static assert(serdeGetKeysIn!(S.i) == null); | static assert(serdeGetKeysIn!(S.ii) == null); | static assert(serdeGetKeysIn!(S.io) == ["io"]); | static assert(serdeGetKeysIn!(S.p) == ["p"]); |} | |/// |version(mir_test) unittest |{ | enum E | { | @serdeKeys("A", "alpha") | a, | @serdeKeys("B", "beta") | b, | c, | } | | static assert (serdeGetKeysIn(E.a) == ["A", "alpha"], serdeGetKeysIn(E.a)); | static assert (serdeGetKeysIn(E.c) == ["c"]); |} | |/++ |Returns: | output key for the symbol or enum value |+/ |template serdeGetKeyOut(alias symbol) |{ | static if (hasUDA!(symbol, serdeAnnotation) || hasUDA!(symbol, serdeIgnore) || hasUDA!(symbol, serdeIgnoreOut)) | enum string serdeGetKeyOut = null; | else | static if (hasUDA!(symbol, serdeKeyOut)) | enum string serdeGetKeyOut = getUDA!(symbol, serdeKeyOut).key; | else | static if (hasUDA!(symbol, serdeKeys)) | enum string serdeGetKeyOut = getUDA!(symbol, serdeKeys).keys[0]; | else | enum string serdeGetKeyOut = __traits(identifier, symbol); |} | |///ditto |@safe pure nothrow @nogc |string serdeGetKeyOut(T)(const T value) | if (is(T == enum)) |{ | foreach (i, member; EnumMembers!T) | {{ | alias all = __traits(getAttributes, EnumMembers!T[i]); | }} | | static if (__VERSION__ < 2093) | { | import std.meta: staticMap; | static immutable ret = [staticMap!(.serdeGetKeyOut, EnumMembers!T)]; | final switch (value) | { | foreach (i, member; EnumMembers!T) | { | case member: | return ret[i]; | } | } | } | else | { | import std.meta: staticMap; | import mir.enums: getEnumIndex; | static immutable ret = [staticMap!(.serdeGetKeyOut, EnumMembers!T)]; | uint index = void; | if (getEnumIndex(value, index)) | return ret[index]; | assert(0); | } |} | |/// |version(mir_test) unittest |{ | struct S | { | int f; | | @serdeKeys("D", "t") | int d; | | @serdeIgnore | int i; | | @serdeIgnoreIn | int ii; | | @serdeIgnoreOut | int io; | | @serdeKeys("P") | @serdeKeyOut("") | void p(int) @property {} | } | | static assert(serdeGetKeyOut!(S.f) == "f"); | static assert(serdeGetKeyOut!(S.d) == "D"); | static assert(serdeGetKeyOut!(S.i) is null); | static assert(serdeGetKeyOut!(S.ii) == "ii"); | static assert(serdeGetKeyOut!(S.io) is null); | static assert(serdeGetKeyOut!(S.p) !is null); | static assert(serdeGetKeyOut!(S.p) == ""); |} | |/// |version(mir_test) unittest |{ | enum E | { | @serdeKeys("A", "alpha") | a, | @serdeKeys("B", "beta") | @serdeKeyOut("o") | b, | c, | } | | static assert (serdeGetKeyOut(E.a) == "A"); | static assert (serdeGetKeyOut(E.b) == "o"); | static assert (serdeGetKeyOut(E.c) == "c"); |} | |/++ |Attribute used to ignore unexpected keys during an aggregate type deserialization. |+/ |enum serdeIgnoreUnexpectedKeys; | |/++ |Attribute to ignore field. | |See_also: $(LREF serdeIgnoreIn) $(LREF serdeIgnoreOut) |+/ |enum serdeIgnore; | |/++ |Attribute to ignore field during deserialization. | |See_also: $(LREF serdeIgnoreInIfAggregate) |+/ |enum serdeIgnoreIn; | |/++ |Attribute to ignore field during serialization. |+/ |enum serdeIgnoreOut; | |/++ |Attribute to ignore a field during deserialization when equals to its default value. |Do not use it on void initialized fields or aggregates with void initialized fields, recursively. |+/ |enum serdeIgnoreDefault; | |/// |version(mir_test) unittest |{ | struct S | { | @serdeIgnoreDefault | double d = 0; // skips field if 0 during deserialization | } | | import std.traits: hasUDA; | | static assert(hasUDA!(S.d, serdeIgnoreDefault)); |} | |/++ |+/ | |/++ |Serialization proxy. |+/ |struct serdeProxy(T); | |/// |version(mir_test) unittest |{ | import mir.small_string; | | struct S | { | @serdeProxy!(SmallString!32) | double d; | } | | import std.traits: hasUDA; | | static assert(hasUDA!(S.d, serdeProxy)); | static assert(hasUDA!(S.d, serdeProxy!(SmallString!32))); | static assert(is(serdeGetProxy!(S.d) == SmallString!32)); |} | |/++ |+/ |alias serdeGetProxy(alias symbol) = TemplateArgsOf!(getUDA!(symbol, serdeProxy))[0]; | |/// Can be applied to @serdeProxy types to make (de)serialization use |/// underlying type through casting. Useful for enums. |enum serdeProxyCast; | |/// Equivalent to @serdeProxy!T @serdeProxyCast |alias serdeEnumProxy(T) = AliasSeq!(serdeProxy!T, serdeProxyCast); | |/++ |Attributes to conditional ignore field during serialization. | |The predicate should be aplied to the member, to the aggregate type. | |See_also: $(LREF serdeIgnoreOutIfAggregate) |+/ |struct serdeIgnoreOutIf(alias pred); | |/++ |+/ |alias serdeGetIgnoreOutIf(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreOutIf))[0]); | |/++ |Attributes to conditional ignore field during serialization. | |The predicate should be aplied to the aggregate value, not to the member. | |See_also: $(LREF serdeIgnoreIfAggregate) $(LREF serdeIgnoreOutIf), $(LREF serdeIgnoreInIfAggregate) |+/ |struct serdeIgnoreOutIfAggregate(alias pred); | |/++ |+/ |alias serdeGetIgnoreOutIfAggregate(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreOutIfAggregate))[0]); | |/++ |Attributes to conditional ignore field during deserialization. | |The attribute should be combined with $(LREF serdeRealOrderedIn) applied on the aggregate. | |See_also: $(LREF serdeIgnoreIfAggregate) $(LREF serdeIgnoreOutIfAggregate) $(LREF serdeIgnoreIn) |+/ |struct serdeIgnoreInIfAggregate(alias pred); | |/++ |+/ |alias serdeGetIgnoreInIfAggregate(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreInIfAggregate))[0]); | |/++ |Attributes to conditional ignore field during serialization and deserialization. | |The attribute should be combined with $(LREF serdeRealOrderedIn) applied on the aggregate. | |The predicate should be aplied to the aggregate value, not to the member. | |See_also: $(LREF serdeIgnoreOutIfAggregate) $(LREF serdeIgnoreInIfAggregate) $ $(LREF serdeIgnore) |+/ |struct serdeIgnoreIfAggregate(alias pred); | |/++ |+/ |alias serdeGetIgnoreIfAggregate(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreIfAggregate))[0]); | |/++ |Allows to use flexible deserialization rules such as conversion from input string to numeric types. |+/ |enum serdeFlexible; | |/++ |Allows serialize / deserialize fields like arrays. | |A range or a container should be iterable for serialization. |Following code should compile: |------ |foreach(ref value; yourRangeOrContainer) |{ | ... |} |------ | |`put(value)` method is used for deserialization. | |See_also: $(MREF serdeIgnoreOut), $(MREF serdeIgnoreIn) |+/ |enum serdeLikeList; | |/++ |Allows serialize / deserialize fields like objects. | |Object should have `opApply` method to allow serialization. |Following code should compile: |------ |foreach(key, value; yourObject) |{ | ... |} |------ |Object should have only one `opApply` method with 2 argument to allow automatic value type deduction. | |`opIndexAssign` or `opIndex` is used for deserialization to support required syntax: |----- |yourObject["key"] = value; |----- |Multiple value types is supported for deserialization. | |See_also: $(MREF serdeIgnoreOut), $(MREF serdeIgnoreIn) |+/ |enum serdeLikeStruct; | |/++ |The attribute is used for algebraic deserialization for types like `Variant!(string, S)` |`@serdeFallbackStruct struct S {}` |+/ |enum serdeFallbackStruct; | |/++ |Force serialize / deserialize on fields instead of Range API. |+/ |enum serdeFields; | |/++ |Ignore keys for object and enum members. |Should be applied to members or enum type itself. |+/ |enum serdeIgnoreCase; | |/// |bool hasSerdeIgnoreCase(T)(T value) | if (is(T == enum)) |{ | static if (hasUDA!(T, serdeIgnoreCase)) | { | return true; | } | else | { | foreach (i, member; EnumMembers!T) | { | alias all = __traits(getAttributes, EnumMembers!T[i]); | if (value == member) | return hasUDA!(EnumMembers!T[i], serdeIgnoreCase); | } | assert(0); | } |} | |/// |version(mir_test) unittest |{ | enum E | { | @serdeIgnoreCase | a, | b, | @serdeIgnoreCase | c, | d, | } | | static assert(hasSerdeIgnoreCase(E.a)); | static assert(!hasSerdeIgnoreCase(E.b)); | static assert(hasSerdeIgnoreCase(E.c)); | static assert(!hasSerdeIgnoreCase(E.d)); |} | |/// |version(mir_test) unittest |{ | @serdeIgnoreCase | enum E | { | a, | b, | c, | d, | } | | static assert(hasSerdeIgnoreCase(E.a)); | static assert(hasSerdeIgnoreCase(E.b)); | static assert(hasSerdeIgnoreCase(E.c)); | static assert(hasSerdeIgnoreCase(E.d)); |} | |/++ |Can be applied only to strings fields. |Does not allocate new data when deserializeing. Raw data is used for strings instead of new memory allocation. |Use this attributes only for strings or arrays that would not be used after deallocation. |+/ |enum serdeScoped; | |/++ |Attribute that force deserializer to throw an exception that the field hasn't been not found in the input. |+/ |enum serdeRequired; | |/++ |Attribute that allow deserializer to do not throw an exception if the field hasn't been not found in the input. |+/ |enum serdeOptional; | |/++ |Attribute that allow deserializer to don't throw an exception that the field matches multiple keys in the object. |+/ |enum serdeAllowMultiple; | |/++ |Attributes for in transformation. |Return type of in transformation must be implicitly convertable to the type of the field. |In transformation would be applied after serialization proxy if any. | |+/ |struct serdeTransformIn(alias fun) {} | |/++ |Returns: unary function of underlaying alias of $(LREF serdeTransformIn) |+/ |alias serdeGetTransformIn(alias value) = naryFun!(TemplateArgsOf!(getUDA!(value, serdeTransformIn))[0]); | |/++ |Attributes for out transformation. |Return type of out transformation may be differ from the type of the field. |Out transformation would be applied before serialization proxy if any. |+/ |struct serdeTransformOut(alias fun) {} | |/++ |Returns: unary function of underlaying alias of $(LREF serdeTransformOut) |+/ |alias serdeGetTransformOut(alias value) = naryFun!(TemplateArgsOf!(getUDA!(value, serdeTransformOut))[0]); | |/++ |+/ |bool serdeParseEnum(E)(scope const char[] str, scope ref E res) | @safe pure nothrow @nogc | if (is(E == enum)) |{ | import mir.enums: getEnumIndexFromKey, unsafeEnumFromIndex; | import mir.utility: _expect; | 12| uint index = void; 12| if (getEnumIndexFromKey!(E, hasUDA!(E, serdeIgnoreCase), serdeGetKeysIn)(str, index)._expect(true)) | { 10| res = unsafeEnumFromIndex!E(index); 10| return true; | } 2| return false; |} | |version(D_Exceptions) |/// ditto |auto serdeParseEnum(E)(scope const char[] str) | @safe pure | if (is(E == enum)) |{ | import mir.utility: max; 2| E ret; 2| if (.serdeParseEnum(str, ret)) 2| return ret; | import mir.exception: MirException; 0000000| throw new MirException("Can't deserialzie ", E.stringof, " from string", str[0 .. max($, 128u)]); |} | |/// |version(mir_test) unittest |{ | enum E | { | @serdeKeys("A", "alpha") | a, | @serdeKeys("B", "beta") | b, | c, | } | 1| auto e = E.c; 1| assert(serdeParseEnum("A", e)); 1| assert(e == E.a); 1| assert(serdeParseEnum("alpha", e)); 1| assert(e == E.a); 1| assert(serdeParseEnum("beta", e)); 1| assert(e == E.b); 1| assert("B".serdeParseEnum!E == E.b); 1| assert("c".serdeParseEnum!E == E.c); | 1| assert(!serdeParseEnum("C", e)); 1| assert(!serdeParseEnum("Alpha", e)); |} | |/// Case insensitive |version(mir_test) unittest |{ | @serdeIgnoreCase // supported for the whole type | enum E | { | @serdeKeys("A", "alpha") | a, | @serdeKeys("B", "beta") | b, | c, | } | 1| auto e = E.c; 1| assert(serdeParseEnum("a", e)); 1| assert(e == E.a); 1| assert(serdeParseEnum("alpha", e)); 1| assert(e == E.a); 1| assert(serdeParseEnum("BETA", e)); 1| assert(e == E.b); 1| assert(serdeParseEnum("b", e)); 1| assert(e == E.b); 1| assert(serdeParseEnum("C", e)); 1| assert(e == E.c); |} | |/++ |Deserialization member type |+/ |template serdeDeserializationMemberType(T, string member) |{ | import std.traits: Unqual, Parameters; | T* aggregate; | static if (hasField!(T, member)) | { | alias serdeDeserializationMemberType = typeof(__traits(getMember, *aggregate, member)); | } | else | static if (__traits(compiles, &__traits(getMember, *aggregate, member)()) || __traits(getOverloads, *aggregate, member).length > 1) | { | alias serdeDeserializationMemberType = typeof(__traits(getMember, *aggregate, member)()); | } | else | { | alias serdeDeserializationMemberType = Unqual!(Parameters!(__traits(getMember, *aggregate, member))[0]); | } |} | |/// ditto |template serdeDeserializationMemberType(T) |{ | /// | alias serdeDeserializationMemberType(string member) = .serdeDeserializationMemberType!(T, member); |} | | |/++ |Is deserializable member |+/ |template serdeIsDeserializable(T) |{ | /// | enum bool serdeIsDeserializable(string member) = serdeGetKeysIn!(__traits(getMember, T, member)).length > 0; |} | |/// |version(mir_test) unittest |{ | | static struct S | { | @serdeIgnore | int i; | | @serdeKeys("a", "b") | int a; | } | | alias serdeIsDeserializableInS = serdeIsDeserializable!S; | static assert (!serdeIsDeserializableInS!"i"); | static assert (serdeIsDeserializableInS!"a"); |} | |/++ |Serialization member type |+/ |template serdeSerializationMemberType(T, string member) |{ | import std.traits: Unqual, Parameters; | T* aggregate; | static if (hasField!(T, member)) | { | alias serdeSerializationMemberType = typeof(__traits(getMember, *aggregate, member)); | } | else | { | alias serdeSerializationMemberType = typeof(__traits(getMember, *aggregate, member)()); | } |} | |/// ditto |template serdeSerializationMemberType(T) |{ | /// | alias serdeSerializationMemberType(string member) = .serdeSerializationMemberType!(T, member); |} | | |/++ |Is deserializable member |+/ |template serdeIsSerializable(T) |{ | /// | enum bool serdeIsSerializable(string member) = serdeGetKeyOut!(__traits(getMember, T, member)) !is null; |} | |/// |version(mir_test) unittest |{ | | static struct S | { | @serdeIgnore | int i; | | @serdeKeys("a", "b") | int a; | } | | alias serdeIsSerializableInS = serdeIsSerializable!S; | static assert (!serdeIsSerializableInS!"i"); | static assert (serdeIsSerializableInS!"a"); |} | |/++ |Final proxy type |+/ |template serdeGetFinalProxy(T) |{ | import mir.timestamp: Timestamp; | import std.traits: hasUDA, isAggregateType; | static if (isAggregateType!T || is(T == enum)) | { | static if (hasUDA!(T, serdeProxy)) | { | alias serdeGetFinalProxy = .serdeGetFinalProxy!(serdeGetProxy!T); | } | else | static if (isAggregateType!T && is(typeof(Timestamp(T.init))) && __traits(getAliasThis, T).length == 0) | { | alias serdeGetFinalProxy = string; | } | else | { | alias serdeGetFinalProxy = T; | } | } | else | { | alias serdeGetFinalProxy = T; | } |} | |/// |version(mir_test) unittest |{ | | @serdeProxy!string | static struct A {} | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | static assert (is(serdeGetFinalProxy!C == string), serdeGetFinalProxy!C.stringof); | static assert (is(serdeGetFinalProxy!string == string)); |} | |/++ |Final deep proxy type |+/ |template serdeGetFinalDeepProxy(T) |{ | import mir.timestamp: Timestamp; | import std.traits: Unqual, hasUDA, isAggregateType, isArray, ForeachType; | static if (isAggregateType!T || is(T == enum)) | { | static if (hasUDA!(T, serdeProxy)) | { | alias serdeGetFinalDeepProxy = .serdeGetFinalDeepProxy!(serdeGetProxy!T); | } | else | static if (isAggregateType!T && is(typeof(Timestamp(T.init)))) | { | alias serdeGetFinalDeepProxy = string; | } | else | static if (__traits(hasMember, T, "serdeKeysProxy")) | { | alias serdeGetFinalDeepProxy = .serdeGetFinalDeepProxy!(T.serdeKeysProxy); | } | else | // static if (is(T == enum)) | // { | // alias serdeGetFinalDeepProxy = typeof(null); | // } | // else | { | alias serdeGetFinalDeepProxy = T; | } | } | else | static if (isArray!T) | { | alias E = Unqual!(ForeachType!T); | static if (isAggregateType!E || is(E == enum)) | alias serdeGetFinalDeepProxy = .serdeGetFinalDeepProxy!E; | else | alias serdeGetFinalDeepProxy = T; | } | else | static if (is(immutable T == immutable V[K], K, V)) | { | alias E = serdeGetFinalDeepProxy!(Unqual!V); | static if (isAggregateType!E || is(E == enum)) | alias serdeGetFinalDeepProxy = E; | else | alias serdeGetFinalDeepProxy = T; | } | else | { | alias serdeGetFinalDeepProxy = T; | } |} | |/// |version(mir_test) unittest |{ | | @serdeProxy!string | static struct A {} | | enum E {a,b,c} | | @serdeProxy!(A[E]) | static struct B {} | | @serdeProxy!(B[]) | static struct C {} | | static assert (is(serdeGetFinalDeepProxy!C == A[E])); | static assert (is(serdeGetFinalDeepProxy!string == string)); |} | |/++ |Final proxy type deserializable members |+/ |template serdeFinalProxyDeserializableMembers(T) |{ | import std.meta: Filter, aliasSeqOf; | alias P = serdeGetFinalProxy!T; | static if (isAggregateType!P || is(P == enum)) | enum string[] serdeFinalProxyDeserializableMembers = [Filter!(serdeIsDeserializable!P, aliasSeqOf!(DeserializableMembers!P))]; | else | // static if (is(P == enum)) | // enum string[] serdeFinalProxyDeserializableMembers = serdeGetKeysIn!P; | // else | enum string[] serdeFinalProxyDeserializableMembers = null; |} | |/// |version(mir_test) unittest |{ | | static struct A | { | @serdeIgnore | int i; | | @serdeKeys("a", "b") | int m; | } | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | static assert (serdeFinalProxyDeserializableMembers!C == ["m"]); |} | |/++ |Final deep proxy type serializable members |+/ |template serdeFinalDeepProxySerializableMembers(T) |{ | import std.traits: isAggregateType; | import std.meta: Filter, aliasSeqOf; | alias P = serdeGetFinalDeepProxy!T; | static if (isAggregateType!P || is(P == enum)) | enum string[] serdeFinalDeepProxySerializableMembers = [Filter!(serdeIsSerializable!P, aliasSeqOf!(SerializableMembers!P))]; | else | // static if (is(P == enum)) | // enum string[] serdeFinalDeepProxySerializableMembers = [serdeGetKeyOut!P]; | // else | enum string[] serdeFinalDeepProxySerializableMembers = null; |} | |/// |version(mir_test) unittest |{ | | static struct A | { | @serdeIgnore | int i; | | @serdeKeys("a", "b") | int m; | } | | @serdeProxy!(A[string]) | static struct B {} | | @serdeProxy!(B[]) | static struct C {} | | static assert (serdeFinalDeepProxySerializableMembers!C == ["m"]); |} | |/++ |Final proxy type deserializable members |+/ |template serdeFinalProxySerializableMembers(T) |{ | import std.meta: Filter, aliasSeqOf; | alias P = serdeGetFinalProxy!T; | static if (isAggregateType!P || is(P == enum)) | enum string[] serdeFinalProxySerializableMembers = [Filter!(serdeIsSerializable!P, aliasSeqOf!(SerializableMembers!P))]; | else | // static if (is(P == enum)) | // enum string[] serdeFinalProxySerializableMembers = [serdeGetKeyOut!P]; | // else | enum string[] serdeFinalProxySerializableMembers = null; |} | |/// |version(mir_test) unittest |{ | | static struct A | { | @serdeIgnore | int i; | | @serdeKeys("a", "b") | int m; | } | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | static assert (serdeFinalProxySerializableMembers!C == ["m"]); |} | |/++ |Final deep proxy type serializable members |+/ |template serdeFinalDeepProxyDeserializableMembers(T) |{ | import std.traits: isAggregateType; | import std.meta: Filter, aliasSeqOf; | alias P = serdeGetFinalDeepProxy!T; | static if (isAggregateType!P || is(P == enum)) | enum string[] serdeFinalDeepProxyDeserializableMembers = [Filter!(serdeIsDeserializable!P, aliasSeqOf!(DeserializableMembers!P))]; | else | // static if (is(P == enum)) | // enum string[] serdeFinalDeepProxyDeserializableMembers = serdeGetKeysIn!P; | // else | enum string[] serdeFinalDeepProxyDeserializableMembers = null; |} | |/// |version(mir_test) unittest |{ | static struct A | { | @serdeIgnore | int i; | | @serdeKeys("a", "b") | int m; | } | | @serdeProxy!(A[string]) | static struct B {} | | @serdeProxy!(B[]) | static struct C {} | | static assert (serdeFinalDeepProxyDeserializableMembers!C == ["m"]); |} | |/++ |Deserialization member final proxy type |+/ |template serdeFinalDeserializationMemberType(T, string member) |{ | import std.traits: hasUDA; | static if (hasUDA!(__traits(getMember, T, member), serdeProxy)) | { | alias serdeFinalDeserializationMemberType = serdeGetFinalProxy!(serdeGetProxy!(__traits(getMember, T, member))); | } | else | { | alias serdeFinalDeserializationMemberType = serdeGetFinalProxy!(serdeDeserializationMemberType!(T, member)); | } |} | |/// ditto |template serdeFinalDeserializationMemberType(T) |{ | /// | alias serdeFinalDeserializationMemberType(string member) = .serdeFinalDeserializationMemberType!(T, member); |} | |/// |version(mir_test) unittest |{ | | static struct A | { | | } | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | | @serdeProxy!double | struct E {} | | struct D | { | C c; | | @serdeProxy!E | int d; | } | | static assert (is(serdeFinalDeserializationMemberType!(D, "c") == A)); | static assert (is(serdeFinalDeserializationMemberType!(D, "d") == double)); |} | |/++ |Deserialization members final proxy types |+/ |template serdeDeserializationFinalProxyMemberTypes(T) |{ | import std.meta: NoDuplicates, staticMap, aliasSeqOf; | alias serdeDeserializationFinalProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalProxy, staticMap!(serdeFinalDeserializationMemberType!T, aliasSeqOf!(serdeFinalProxyDeserializableMembers!T)))); |} | |/// |version(mir_test) unittest |{ | | static struct A {} | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | @serdeProxy!B | static struct E {} | | static struct D | { | C c; | | @serdeProxy!E | int d; | } | | import std.meta: AliasSeq; | static assert (is(serdeDeserializationFinalProxyMemberTypes!D == AliasSeq!A)); |} | |/++ |Serialization member final proxy type |+/ |template serdeFinalSerializationMemberType(T, string member) |{ | import std.traits: hasUDA; | static if (hasUDA!(__traits(getMember, T, member), serdeProxy)) | { | alias serdeFinalSerializationMemberType = serdeGetFinalProxy!(serdeGetProxy!(__traits(getMember, T, member))); | } | else | { | alias serdeFinalSerializationMemberType = serdeGetFinalProxy!(serdeSerializationMemberType!(T, member)); | } |} | |/// ditto |template serdeFinalSerializationMemberType(T) |{ | /// | alias serdeFinalSerializationMemberType(string member) = .serdeFinalSerializationMemberType!(T, member); |} | |/// |version(mir_test) unittest |{ | | static struct A | { | | } | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | | @serdeProxy!double | struct E {} | | struct D | { | C c; | | @serdeProxy!E | int d; | } | | static assert (is(serdeFinalSerializationMemberType!(D, "c") == A), serdeFinalSerializationMemberType!(D, "c")); | static assert (is(serdeFinalSerializationMemberType!(D, "d") == double)); |} | |/++ |Serialization members final proxy types |+/ |template serdeSerializationFinalProxyMemberTypes(T) |{ | import std.meta: NoDuplicates, staticMap, aliasSeqOf; | alias serdeSerializationFinalProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalProxy, staticMap!(serdeFinalSerializationMemberType!T, aliasSeqOf!(serdeFinalProxySerializableMembers!T)))); |} | |/// |version(mir_test) unittest |{ | | static struct A {} | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | @serdeProxy!B | static struct E {} | | static struct D | { | C c; | | @serdeProxy!E | int d; | } | | import std.meta: AliasSeq; | static assert (is(serdeSerializationFinalProxyMemberTypes!D == AliasSeq!A)); |} | |/++ |Deserialization members final deep proxy types |+/ |template serdeDeserializationFinalDeepProxyMemberTypes(T) |{ | import std.meta: NoDuplicates, staticMap, aliasSeqOf; | import mir.algebraic: isVariant; | static if (isVariant!T) | alias serdeDeserializationFinalDeepProxyMemberTypes = NoDuplicates!(T, staticMap!(serdeGetFinalDeepProxy, T.AllowedTypes)); | else | static if (isAlgebraicAliasThis!T) | { | T* aggregate; | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | alias serdeDeserializationFinalDeepProxyMemberTypes = .serdeDeserializationFinalDeepProxyMemberTypes!A; | } | else | alias serdeDeserializationFinalDeepProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalDeepProxy, staticMap!(serdeFinalDeserializationMemberType!T, aliasSeqOf!(serdeFinalDeepProxyDeserializableMembers!T)))); |} | |/// |version(mir_test) unittest |{ | | static struct A {} | | @serdeProxy!(A[]) | static struct B {} | | enum R {a, b, c} | | @serdeProxy!(B[R]) | static struct C {} | | @serdeProxy!(B[string]) | static struct E {} | | static struct D | { | C c; | | @serdeProxy!E | int d; | } | | import std.meta: AliasSeq; | static assert (is(serdeDeserializationFinalDeepProxyMemberTypes!D == AliasSeq!A), serdeDeserializationFinalDeepProxyMemberTypes!D); |} | |/++ |Serialization members final deep proxy types |+/ |template serdeSerializationFinalDeepProxyMemberTypes(T) |{ | import std.meta: NoDuplicates, staticMap, aliasSeqOf; | import mir.algebraic: isVariant; | static if (isVariant!T) | alias serdeSerializationFinalDeepProxyMemberTypes = NoDuplicates!(T, staticMap!(serdeGetFinalDeepProxy, T.AllowedTypes)); | else | static if (isAlgebraicAliasThis!T) | { | T* aggregate; | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | alias serdeSerializationFinalDeepProxyMemberTypes = .serdeSerializationFinalDeepProxyMemberTypes!A; | } | else | alias serdeSerializationFinalDeepProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalDeepProxy, staticMap!(serdeFinalSerializationMemberType!T, aliasSeqOf!(serdeFinalDeepProxySerializableMembers!T)))); |} | |/// |version(mir_test) unittest |{ | | static struct A {} | | @serdeProxy!(A[]) | static struct B {} | | enum R {a, b, c} | | @serdeProxy!(B[R]) | static struct C {} | | @serdeProxy!(B[string]) | static struct E {} | | static struct D | { | C c; | | @serdeProxy!E | int d; | } | | import std.meta: AliasSeq; | static assert (is(serdeSerializationFinalDeepProxyMemberTypes!D == AliasSeq!A), serdeSerializationFinalDeepProxyMemberTypes!D); |} | |private template serdeDeserializationFinalProxyMemberTypesRecurseImpl(T...) |{ | import std.meta: NoDuplicates, staticMap; | alias F = NoDuplicates!(T, staticMap!(serdeDeserializationFinalProxyMemberTypes, T)); | static if (F.length == T.length) | alias serdeDeserializationFinalProxyMemberTypesRecurseImpl = T; | else | alias serdeDeserializationFinalProxyMemberTypesRecurseImpl = .serdeDeserializationFinalProxyMemberTypesRecurseImpl!F; |} | |/++ |Deserialization members final proxy types (recursive) |+/ |alias serdeDeserializationFinalProxyMemberTypesRecurse(T) = serdeDeserializationFinalProxyMemberTypesRecurseImpl!(serdeGetFinalProxy!T); | |/// |version(mir_test) unittest |{ | | static struct A { double g; } | | @serdeProxy!A | static struct B {} | | @serdeProxy!B | static struct C {} | | @serdeProxy!B | static struct E {} | | static struct D | { | C c; | | @serdeProxy!E | int d; | } | | @serdeProxy!D | static struct F {} | | import std.meta: AliasSeq; | static assert (is(serdeDeserializationFinalProxyMemberTypesRecurse!F == AliasSeq!(D, A, double))); |} | |private template serdeSerializationFinalDeepProxyMemberTypesRecurseImpl(T...) |{ | import std.meta: NoDuplicates, staticMap; | alias F = NoDuplicates!(T, staticMap!(serdeSerializationFinalDeepProxyMemberTypes, T)); | static if (F.length == T.length) | alias serdeSerializationFinalDeepProxyMemberTypesRecurseImpl = T; | else | alias serdeSerializationFinalDeepProxyMemberTypesRecurseImpl = .serdeSerializationFinalDeepProxyMemberTypesRecurseImpl!F; |} | |private template serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl(T...) |{ | import std.meta: NoDuplicates, staticMap; | alias F = NoDuplicates!(T, staticMap!(serdeDeserializationFinalDeepProxyMemberTypes, T)); | static if (F.length == T.length) | alias serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl = T; | else | alias serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl = .serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl!F; |} | |/++ |Deserialization members final deep proxy types (recursive) |+/ |alias serdeDeserializationFinalDeepProxyMemberTypesRecurse(T) = serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl!(serdeGetFinalDeepProxy!T); | |/// |version(mir_test) unittest |{ | | static struct A { double g; } | | @serdeProxy!(A[]) | static struct B {} | | @serdeProxy!(B[string]) | static struct C {} | | @serdeProxy!B | static struct E {} | | static struct D | { | C c; | | @serdeProxy!(E[]) | int d; | } | | @serdeProxy!D | static struct F {} | | import std.meta: AliasSeq; | static assert (is(serdeDeserializationFinalDeepProxyMemberTypesRecurse!F == AliasSeq!(D, A, double))); |} | |/++ |Serialization members final deep proxy types (recursive) |+/ |alias serdeSerializationFinalDeepProxyMemberTypesRecurse(T) = serdeSerializationFinalDeepProxyMemberTypesRecurseImpl!(serdeGetFinalDeepProxy!T); | |/// |version(mir_test) unittest |{ | | static struct A { double g; } | | @serdeProxy!(A[]) | static struct B {} | | @serdeProxy!(B[string]) | static struct C {} | | @serdeProxy!B | static struct E {} | | static struct D | { | C c; | | @serdeProxy!(E[]) | int d; | } | | @serdeProxy!D | static struct F {} | | import std.meta: AliasSeq; | static assert (is(serdeSerializationFinalDeepProxyMemberTypesRecurse!F == AliasSeq!(D, A, double)), serdeSerializationFinalDeepProxyMemberTypesRecurse!F); |} | |package string[] sortUniqKeys()(string[] keys) | @safe pure nothrow |{ | import mir.algorithm.iteration: uniq; | import mir.array.allocation: array; | import mir.ndslice.sorting: sort; | | return keys | .sort!((a, b) { | if (sizediff_t d = a.length - b.length) | return d < 0; | return a < b; | }) | .uniq | .array; |} | | |private template serdeGetKeysIn2(T) |{ | // T* value; | enum string[] serdeGetKeysIn2(string member) = serdeGetKeysIn!(__traits(getMember, T, member)); |} | |private template serdeGetKeyOut2(T) |{ | enum string[] serdeGetKeyOut2(string member) = serdeGetKeyOut!(__traits(getMember, T, member)) is null ? null : [serdeGetKeyOut!(__traits(getMember, T, member))]; |} | |private template serdeFinalDeepProxyDeserializableMemberKeys(T) |{ | import std.meta: staticMap, aliasSeqOf; | import std.traits: isAggregateType; | | static if (isAggregateType!T) | { | import mir.algebraic: isVariant; | static if (isVariant!T) | enum string[] serdeFinalDeepProxyDeserializableMemberKeys = getAlgebraicAnnotationsOfVariant!T; | else | enum string[] serdeFinalDeepProxyDeserializableMemberKeys = [staticMap!(aliasSeqOf, staticMap!(serdeGetKeysIn2!T, aliasSeqOf!(serdeFinalDeepProxyDeserializableMembers!T)))]; | } | else | static if (is(T == enum)) | { | enum string[] serdeFinalDeepProxyDeserializableMemberKeys = enumAllKeysIn!T; | } | else | enum string[] serdeFinalDeepProxyDeserializableMemberKeys = null; |} | |package template getAlgebraicAnnotationsOfVariant(T) |{ | import std.meta: staticMap, Filter; | enum string[] getAlgebraicAnnotationsOfVariant = [staticMap!(serdeGetAlgebraicAnnotation, Filter!(serdeHasAlgebraicAnnotation, T.AllowedTypes))]; |} | |private template serdeFinalDeepProxySerializableMemberKeys(T) |{ | import std.meta: staticMap, aliasSeqOf; | import std.traits: isAggregateType; | | static if (isAggregateType!T) | { | import mir.algebraic: isVariant; | static if (isVariant!T) | enum string[] serdeFinalDeepProxySerializableMemberKeys = getAlgebraicAnnotationsOfVariant!T; | else | enum string[] serdeFinalDeepProxySerializableMemberKeys = [staticMap!(aliasSeqOf, staticMap!(serdeGetKeyOut2!T, aliasSeqOf!(serdeFinalDeepProxySerializableMembers!T)))]; | } | else | static if (is(T == enum)) | { | enum string[] serdeFinalDeepProxySerializableMemberKeys = enumAllKeysOut!T; | } | else | enum string[] serdeFinalDeepProxySerializableMemberKeys = null; |} | |private template serdeGetAlgebraicAnnotations(T) |{ | static if (isAggregateType!T || is(T == enum)) | static if (hasUDA!(T, serdeAlgebraicAnnotation)) | enum string[] serdeGetAlgebraicAnnotations = [getUDA!(T, serdeAlgebraicAnnotation).annotation]; | else | enum string[] serdeGetAlgebraicAnnotations = null; | else | enum string[] serdeGetAlgebraicAnnotations = null; |} | |package template serdeIsComplexVariant(T) |{ | import mir.algebraic: isVariant, isNullable; | static if (isVariant!T) | { | enum serdeIsComplexVariant = (T.AllowedTypes.length - isNullable!T) > 1; | } | else | { | enum bool serdeIsComplexVariant = false; | } |} | |package template isAlgebraicAliasThis(T) |{ | static if (__traits(getAliasThis, T).length) | { | import mir.algebraic: isVariant; | T* aggregate; | alias A = AliasSeq!(typeof(__traits(getMember, aggregate, __traits(getAliasThis, T)))); | static if (A.length != 1) | enum isAlgebraicAliasThis = false; | else | static if (isVariant!(A[0])) | enum isAlgebraicAliasThis = true; | else | enum isAlgebraicAliasThis = serdeIsDynamicAlgebraic!(A[0]); | } | else | { | enum isAlgebraicAliasThis = false; | } |} | |/++ |Serialization members final proxy keys (recursive) |+/ |template serdeGetSerializationKeysRecurse(T) |{ | import std.meta: staticMap, aliasSeqOf; | enum string[] serdeGetSerializationKeysRecurse = [staticMap!(aliasSeqOf, staticMap!(serdeFinalDeepProxySerializableMemberKeys, serdeSerializationFinalDeepProxyMemberTypesRecurse!T))].sortUniqKeys; |} | |/// |version(mir_test) unittest |{ | enum Y | { | a, | b, | c, | } | | static struct A { double g; float d; } | | @serdeProxy!A | static struct B { int f; } | | @serdeProxy!(B[Y][string]) | static union C { int f; } | | @serdeProxy!(B[]) | static interface E { int f() @property; } | | enum N { a, b } | | static class D | { | C c; | | @serdeProxy!(E[]) | int d; | | N e; | } | | @serdeAlgebraicAnnotation("$F") | @serdeProxy!D | static struct F { int f; } | | static assert (serdeGetSerializationKeysRecurse!F == ["a", "b", "c", "d", "e", "g"]); | | import mir.algebraic; | static assert (serdeGetSerializationKeysRecurse!(Nullable!(F, int)) == ["a", "b", "c", "d", "e", "g", "$F"]); |} | |/++ |Deserialization members final proxy keys (recursive) |+/ |template serdeGetDeserializationKeysRecurse(T) |{ | import std.meta: staticMap, aliasSeqOf; | enum string[] serdeGetDeserializationKeysRecurse = [staticMap!(aliasSeqOf, staticMap!(serdeFinalDeepProxyDeserializableMemberKeys, serdeDeserializationFinalDeepProxyMemberTypesRecurse!T))].sortUniqKeys; |} | |/// |version(mir_test) unittest |{ | | static struct A { double g; float d; } | | @serdeProxy!A | static struct B { int f; } | | @serdeProxy!(B[string]) | static union C { int f; } | | @serdeProxy!(B[]) | static interface E { int f() @property; } | | enum N { a, b } | | static class D | { | C c; | | @serdeProxy!(E[]) | int d; | | N e; | } | | @serdeAlgebraicAnnotation("$F") | @serdeProxy!D | static struct F { int f; } | | static assert (serdeGetDeserializationKeysRecurse!N == ["a", "b"], serdeGetDeserializationKeysRecurse!N); | | static assert (serdeGetDeserializationKeysRecurse!F == ["a", "b", "c", "d", "e", "g"]); | | import mir.algebraic; | static assert (serdeGetDeserializationKeysRecurse!(Nullable!(F, int)) == ["a", "b", "c", "d", "e", "g", "$F"]); |} | |/++ |UDA used to force deserializer to initilize members in the order of their definition in the target object/structure. | |The attribute force deserializer to create a dummy type (recursively), initializer its fields and then assign them to |to the object members (fields and setters) in the order of their definition. | |See_also: $(LREF SerdeOrderedDummy), $(LREF serdeRealOrderedIn), $(LREF serdeIgnoreInIfAggregate). |+/ |enum serdeOrderedIn; | |/++ |UDA used to force deserializer to initilize members in the order of their definition in the target object/structure. | |Unlike $(LREF serdeOrderedIn) `serdeRealOrderedDummy` force deserialzier to iterate all DOM keys for each object deserialization member. |It is slower but more universal approach. | |See_also: $(LREF serdeOrderedIn), $(LREF serdeIgnoreInIfAggregate) |+/ |enum serdeRealOrderedIn; | |/++ |UDA used to force deserializer to skip the member final deserialization. |A user should finalize the member deserialize using the dummy object provided in `serdeFinalizeWithDummy(ref SerdeOrderedDummy!(typeof(this)) dummy)` struct method |and dummy method `serdeFinalizeTargetMember`. |+/ |enum serdeFromDummyByUser; | |/++ |UDA used to force serializer to output members in the alphabetical order of their output keys. |+/ |enum serdeAlphabetOut; | |/++ |A dummy structure usefull $(LREF serdeOrderedIn) support. |+/ |struct SerdeOrderedDummy(T, bool __optionalByDefault = false) | if (is(serdeGetFinalProxy!T == T) && isAggregateType!T) |{ | import std.traits: hasUDA; | | @serdeIgnore | SerdeFlags!(typeof(this)) __serdeFlags; | | static if (__optionalByDefault) | alias __serdeOptionalRequired = serdeRequired; | else | alias __serdeOptionalRequired = serdeOptional; | | this()(T value) | { | static foreach (member; serdeFinalProxyDeserializableMembers!T) | { | static if (hasField!(T, member)) | { | static if (__traits(compiles, {__traits(getMember, this, member) = __traits(getMember, value, member);})) | __traits(getMember, this, member) = __traits(getMember, value, member); | } | } | } | |public: | | static foreach (i, member; serdeFinalProxyDeserializableMembers!T) | { | static if (hasField!(T, member)) | { | static if (hasUDA!(__traits(getMember, T, member), serdeProxy)) | { | mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ ";"); | } | else | static if (isAggregateType!(typeof(__traits(getMember, T, member)))) | { | static if (hasUDA!(typeof(__traits(getMember, T, member)), serdeProxy)) | { | mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ " = T.init." ~ member ~ ";"); | } | else | static if (__traits(compiles, { | mixin("enum SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`)) " ~ member ~ " = SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`))(T.init." ~ member ~ ");"); | })) | { | mixin("@(__traits(getAttributes, T." ~ member ~ ")) SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`)) " ~ member ~ " = SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`))(T.init." ~ member ~ ");"); | } | else | { | mixin("@(__traits(getAttributes, T." ~ member ~ ")) SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`)) " ~ member ~ ";"); | } | } | else | { | mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ " = T.init." ~ member ~ ";"); | } | } | else | { | mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ ";"); | } | } | | /// Initialize target members | void serdeFinalizeWithFlags(ref scope const SerdeFlags!(typeof(this)) flags) | { | __serdeFlags = flags; | } | | /// Initialize target members | void serdeFinalizeTarget(ref T value, ref scope SerdeFlags!T flags) | { | import std.traits: hasElaborateAssign; | static foreach (member; serdeFinalProxyDeserializableMembers!T) | __traits(getMember, flags, member) = __traits(getMember, __serdeFlags, member); | static foreach (member; serdeFinalProxyDeserializableMembers!T) | static if (!hasUDA!(__traits(getMember, T, member), serdeFromDummyByUser)) | {{ | if (hasUDA!(__traits(getMember, T, member), __serdeOptionalRequired) == __optionalByDefault || __traits(getMember, __serdeFlags, member)) | { | static if (is(typeof(__traits(getMember, this, member)) : SerdeOrderedDummy!I, I)) | { | alias M = typeof(__traits(getMember, value, member)); | SerdeFlags!M memberFlags; | __traits(getMember, this, member).serdeFinalizeTarget(__traits(getMember, value, member), memberFlags); | static if (__traits(hasMember, M, "serdeFinalizeWithFlags")) | { | __traits(getMember, value, member).serdeFinalizeWithFlags(memberFlags); | } | static if (__traits(hasMember, M, "serdeFinalize")) | { | __traits(getMember, value, member).serdeFinalize(); | } | } | else | { | static if (hasElaborateAssign!(typeof(__traits(getMember, this, member)))) | { | import core.lifetime: move; | __traits(getMember, value, member) = move(__traits(getMember, this, member)); | } | else | __traits(getMember, value, member) = __traits(getMember, this, member); | } | } | }} | static if (__traits(hasMember, T, "serdeFinalizeWithDummy")) | { | value.serdeFinalizeWithDummy(this); | } | } | | /// Initialize target member | void serdeFinalizeTargetMember(string member)(ref T value) | { | if (hasUDA!(__traits(getMember, T, member), __serdeOptionalRequired) == __optionalByDefault || __traits(getMember, __serdeFlags, member)) | { | static if (is(typeof(__traits(getMember, this, member)) : SerdeOrderedDummy!I, I)) | { | alias M = typeof(__traits(getMember, value, member)); | SerdeFlags!M memberFlags; | __traits(getMember, this, member).serdeFinalizeTarget(__traits(getMember, value, member), memberFlags); | static if (__traits(hasMember, M, "serdeFinalizeWithFlags")) | { | __traits(getMember, value, member).serdeFinalizeWithFlags(memberFlags); | } | static if (__traits(hasMember, M, "serdeFinalize")) | { | __traits(getMember, value, member).serdeFinalize(); | } | } | else | { | static if (hasElaborateAssign!(typeof(__traits(getMember, this, member)))) | { | import core.lifetime: move; | __traits(getMember, value, member) = move(__traits(getMember, this, member)); | } | else | __traits(getMember, value, member) = __traits(getMember, this, member); | } | } | } |} | |/// |version(mir_test) unittest |{ | import std.traits; | | static struct S | { | private double _d; | | @serdeProxy!int 0000000| void d(double v) @property { _d = v; } | | string s; | } | | static assert(is(typeof(SerdeOrderedDummy!S.init.d) == double), SerdeOrderedDummy!S.init.d); | static assert(is(typeof(SerdeOrderedDummy!S.init.s) == string)); | static assert(hasUDA!(S.d, serdeProxy)); | static assert(hasUDA!(SerdeOrderedDummy!S.d, serdeProxy)); |} | |/++ |A dummy structure passed to `.serdeFinalizeWithFlags` finalizer method. |+/ |struct SerdeFlags(T) |{ | static if (is(T : SerdeOrderedDummy!I, I)) | static foreach(member; serdeFinalProxyDeserializableMembers!I) | mixin("bool " ~ member ~ ";"); | else | static foreach(member; serdeFinalProxyDeserializableMembers!T) | mixin("bool " ~ member ~ ";"); |} | |/++ |The UDA used on struct or class definitions to denote an discriminated field and its tag |for algebraic deserialization. | |Discriminated field is ignored during the annotated structure/class deseriliazation. |+/ |struct serdeDiscriminatedField |{ | /// Name of the field in the JSON like data object. | string field; | /// Value of the field for the current type. | string tag; |} | |template deserializeValueMemberImpl(alias deserializeValue, alias deserializeScoped) |{ | /// | SerdeException deserializeValueMemberImpl(string member, Data, T, Context...)(Data data, scope ref T value, scope ref SerdeFlags!T requiredFlags, scope ref Context context) | { | import core.lifetime: move; | import mir.conv: to; | | enum likeList = hasUDA!(__traits(getMember, value, member), serdeLikeList); | enum likeStruct = hasUDA!(__traits(getMember, value, member), serdeLikeStruct); | enum hasProxy = hasUDA!(__traits(getMember, value, member), serdeProxy); | enum hasScoped = hasUDA!(__traits(getMember, value, member), serdeScoped); | | static assert (likeList + likeStruct <= 1, T.stringof ~ "." ~ member ~ " can't have both @serdeLikeStruct and @serdeLikeList attributes"); | static assert (hasProxy >= likeStruct, T.stringof ~ "." ~ member ~ " should have a Proxy type for deserialization"); | static assert (hasProxy >= likeList, T.stringof ~ "." ~ member ~ " should have a Proxy type for deserialization"); | | alias Member = serdeDeserializationMemberType!(T, member); | | static if (hasProxy) | alias Temporal = serdeGetProxy!(__traits(getMember, value, member)); | else | alias Temporal = Member; | | static if (hasScoped) | static if (__traits(compiles, { Temporal temporal; deserializeScoped(data, temporal); })) | alias impl = deserializeScoped; | else | alias impl = deserializeValue; | else | alias impl = deserializeValue; | | static immutable excm(string member) = new SerdeException("ASDF deserialisation: multiple keys for member '" ~ member ~ "' in " ~ T.stringof ~ " are not allowed."); | | static if (!hasUDA!(__traits(getMember, value, member), serdeAllowMultiple)) | if (__traits(getMember, requiredFlags, member)) | return excm!member; | | __traits(getMember, requiredFlags, member) = true; | | static if (likeList) | { | foreach(elem; data.byElement) | { | Temporal temporal; | if (auto exc = impl(elem, temporal, context)) | return exc; | __traits(getMember, value, member).put(move(temporal)); | } | } | else | static if (likeStruct) | { | foreach(v; data.byKeyValue(context)) | { | Temporal temporal; | if (auto exc = impl(v.value, temporal, context)) | return exc; | __traits(getMember, value, member)[v.key.idup] = move(temporal); | } | } | else | static if (hasProxy) | { | Temporal temporal; | if (auto exc = impl(data, temporal, context)) | return exc; | __traits(getMember, value, member) = to!(serdeDeserializationMemberType!(T, member))(move(temporal)); | } | else | static if (hasField!(T, member)) | { | if (auto exc = impl(data, __traits(getMember, value, member), context)) | return exc; | } | else | { | Member temporal; | if (auto exc = impl(data, temporal, context)) | return exc; | __traits(getMember, value, member) = move(temporal); | } | | static if (hasUDA!(__traits(getMember, value, member), serdeTransformIn)) | { | alias transform = serdeGetTransformIn!(__traits(getMember, value, member)); | static if (hasField!(T, member)) | { | transform(__traits(getMember, value, member)); | } | else | { | auto temporal = __traits(getMember, value, member); | transform(temporal); | __traits(getMember, value, member) = move(temporal); | } | } | | return null; | } |} | |private: | |auto fastLazyToUpper()(return scope const(char)[] name) |{ | import mir.ndslice.topology: map; | return name.map!fastToUpper; |} | |auto fastToUpper()(char a) |{ // std.ascii may not be inlined | return 'a' <= a && a <= 'z' ? cast(char)(a ^ 0x20) : a; |} | |@safe pure nothrow @nogc |char[] fastToUpperInPlace()(return scope char[] a) |{ | foreach(ref char e; a) | e = e.fastToUpper; | return a; |} | |template enumAllKeysIn(T) | if (is(T == enum)) |{ | import std.traits: EnumMembers; | import std.meta: staticMap, aliasSeqOf; | enum string[] enumAllKeysIn = [staticMap!(aliasSeqOf, staticMap!(.serdeGetKeysIn, EnumMembers!T))]; |} | |template enumAllKeysOut(T) | if (is(T == enum)) |{ | import std.traits: EnumMembers; | import std.meta: staticMap, aliasSeqOf; | enum string[] enumAllKeysOut = [staticMap!(.serdeGetKeyOut, EnumMembers!T)]; |} source/mir/serde.d is 71% covered <<<<<< EOF # path=source-mir-series.lst |/++ |$(H1 Index-series) | |The module contains $(LREF Series) data structure with special iteration and indexing methods. |It is aimed to construct index or time-series using Mir and Phobos algorithms. | |Public_imports: $(MREF mir,ndslice,slice). | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.series; | |import mir.ndslice.iterator: IotaIterator; |import mir.ndslice.sorting: transitionIndex; |import mir.qualifier; |import mir.serde: serdeIgnore, serdeFields; |import std.traits; |/// |public import mir.ndslice.slice; |/// |public import mir.ndslice.sorting: sort; | |/++ |See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). |+/ |@safe version(mir_test) unittest |{ | import mir.ndslice; | import mir.series; | | import mir.array.allocation: array; | import mir.algorithm.setops: multiwayUnion; | | import mir.date: Date; | import core.lifetime: move; | import std.exception: collectExceptionMsg; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// 1| auto index0 = [ | Date(2017, 01, 01), | Date(2017, 03, 01), | Date(2017, 04, 01)]; | 1| auto data0 = [1.0, 3, 4]; 1| auto series0 = index0.series(data0); | 1| auto index1 = [ | Date(2017, 01, 01), | Date(2017, 02, 01), | Date(2017, 05, 01)]; | 1| auto data1 = [10.0, 20, 50]; 1| auto series1 = index1.series(data1); | | ////////////////////////////////////// | // asSlice method | ////////////////////////////////////// 1| assert(series0 | .asSlice | // ref qualifier is optional 3| .map!((ref key, ref value) => key.yearMonthDay.month == value) | .all); | | ////////////////////////////////////// | // get* methods | ////////////////////////////////////// | 1| auto refDate = Date(2017, 03, 01); 1| auto missingDate = Date(2016, 03, 01); | | // default value 1| double defaultValue = 100; 1| assert(series0.get(refDate, defaultValue) == 3); 1| assert(series0.get(missingDate, defaultValue) == defaultValue); | | // Exceptions handlers 1| assert(series0.get(refDate) == 3); 1| assert(series0.get(refDate, new Exception("My exception msg")) == 3); 1| assert(series0.getVerbose(refDate) == 3); 1| assert(series0.getExtraVerbose(refDate, "My exception msg") == 3); | 1| assert(collectExceptionMsg!Exception( 1| series0.get(missingDate) | ) == "Series double[date]: Missing required key"); | 1| assert(collectExceptionMsg!Exception( 2| series0.get(missingDate, new Exception("My exception msg")) | ) == "My exception msg"); | 1| assert(collectExceptionMsg!Exception( 1| series0.getVerbose(missingDate) | ) == "Series double[date]: Missing 2016-03-01 key"); | 1| assert(collectExceptionMsg!Exception( 1| series0.getExtraVerbose(missingDate, "My exception msg") | ) == "My exception msg. Series double[date]: Missing 2016-03-01 key"); | | // assign with get* 1| series0.get(refDate) = 100; 1| assert(series0.get(refDate) == 100); 1| series0.get(refDate) = 3; | | // tryGet 1| double val; 1| assert(series0.tryGet(refDate, val)); 1| assert(val == 3); 1| assert(!series0.tryGet(missingDate, val)); 1| assert(val == 3); // val was not changed | | ////////////////////////////////////// | // Merges multiple series into one. | // Allocates using GC. M | // Makes exactly two allocations per merge: | // one for index/time and one for data. | ////////////////////////////////////// 1| auto m0 = unionSeries(series0, series1); 1| auto m1 = unionSeries(series1, series0); // order is matter | 1| assert(m0.index == [ | Date(2017, 01, 01), | Date(2017, 02, 01), | Date(2017, 03, 01), | Date(2017, 04, 01), | Date(2017, 05, 01)]); | 1| assert(m0.index == m1.index); 1| assert(m0.data == [ 1, 20, 3, 4, 50]); 1| assert(m1.data == [10, 20, 3, 4, 50]); | | ////////////////////////////////////// | // Joins two time-series into a one with two columns. | ////////////////////////////////////// 1| auto u = [index0, index1].multiwayUnion; 1| auto index = u.move.array; 1| auto data = slice!double([index.length, 2], 0); // initialized to 0 value 1| auto series = index.series(data); | 1| series[0 .. $, 0][] = series0; // fill first column 1| series[0 .. $, 1][] = series1; // fill second column | 1| assert(data == [ | [1, 10], | [0, 20], | [3, 0], | [4, 0], | [0, 50]]); |} | |/// |version(mir_test) |unittest{ | | import mir.series; | 1| double[int] map; 1| map[1] = 4.0; 1| map[2] = 5.0; 1| map[4] = 6.0; 1| map[5] = 10.0; 1| map[10] = 11.0; | 1| const s = series(map); | 1| double value; 1| int key; 2| assert(s.tryGet(2, value) && value == 5.0); 1| assert(!s.tryGet(8, value)); | 2| assert(s.tryGetNext(2, value) && value == 5.0); 2| assert(s.tryGetPrev(2, value) && value == 5.0); 2| assert(s.tryGetNext(8, value) && value == 11.0); 2| assert(s.tryGetPrev(8, value) && value == 10.0); 1| assert(!s.tryGetFirst(8, 9, value)); 2| assert(s.tryGetFirst(2, 10, value) && value == 5.0); 2| assert(s.tryGetLast(2, 10, value) && value == 11.0); 2| assert(s.tryGetLast(2, 8, value) && value == 10.0); | 4| key = 2; assert(s.tryGetNextUpdateKey(key, value) && key == 2 && value == 5.0); 4| key = 2; assert(s.tryGetPrevUpdateKey(key, value) && key == 2 && value == 5.0); 4| key = 8; assert(s.tryGetNextUpdateKey(key, value) && key == 10 && value == 11.0); 4| key = 8; assert(s.tryGetPrevUpdateKey(key, value) && key == 5 && value == 10.0); 4| key = 2; assert(s.tryGetFirstUpdateLower(key, 10, value) && key == 2 && value == 5.0); 4| key = 10; assert(s.tryGetLastUpdateKey(2, key, value) && key == 10 && value == 11.0); 4| key = 8; assert(s.tryGetLastUpdateKey(2, key, value) && key == 5 && value == 10.0); |} | |import mir.ndslice.slice; |import mir.ndslice.internal: is_Slice, isIndex; |import mir.math.common: optmath; | |import std.meta; | |@optmath: | |/++ |Plain index/time observation data structure. |Observation are used as return tuple for for indexing $(LREF Series). |+/ |struct mir_observation(Index, Data) |{ | /// Date, date-time, time, or index. | Index index; | /// Value or ndslice. | Data data; |} | |/// ditto |alias Observation = mir_observation; | |/// Convenient function for $(LREF Observation) construction. |auto observation(Index, Data)(Index index, Data data) |{ | alias R = mir_observation!(Index, Data); 8| return R(index, data); |} | |/++ |Convinient alias for 1D Contiguous $(LREF Series). |+/ |alias SeriesMap(K, V) = mir_series!(K*, V*); | |/// |version(mir_test) unittest |{ | import std.traits; | import mir.series; | | static assert (is(SeriesMap!(string, double) == Series!(string*, double*))); | | /// LHS, RHS | static assert (isAssignable!(SeriesMap!(string, double), SeriesMap!(string, double))); | static assert (isAssignable!(SeriesMap!(string, double), typeof(null))); | | static assert (isAssignable!(SeriesMap!(const string, double), SeriesMap!(string, double))); | static assert (isAssignable!(SeriesMap!(string, const double), SeriesMap!(string, double))); | static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, double))); | | static assert (isAssignable!(SeriesMap!(immutable string, double), SeriesMap!(immutable string, double))); | static assert (isAssignable!(SeriesMap!(immutable string, const double), SeriesMap!(immutable string, double))); | static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(immutable string, double))); | static assert (isAssignable!(SeriesMap!(string, immutable double), SeriesMap!(string, immutable double))); | static assert (isAssignable!(SeriesMap!(const string, immutable double), SeriesMap!(string, immutable double))); | static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, immutable double))); | // etc |} | |/++ |Plain index series data structure. | |`*.index[i]`/`*.key[i]`/`*.time` corresponds to `*.data[i]`/`*.value`. | |Index is assumed to be sorted. |$(LREF sort) can be used to normalise a series. |+/ |@serdeFields 15|struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous) |{ | private enum doUnittest = is(typeof(this) == mir_series!(int*, double*)); | | /// | alias IndexIterator = IndexIterator_; | | /// | alias Iterator = Iterator_; | | /// | @serdeIgnore enum size_t N = N_; | | /// | @serdeIgnore enum SliceKind kind = kind_; | | /++ | Index series is assumed to be sorted. | | `IndexIterator` is an iterator on top of date, date-time, time, or numbers or user defined types with defined `opCmp`. | For example, `Date*`, `DateTime*`, `immutable(long)*`, `mir.ndslice.iterator.IotaIterator`. | +/ | auto index() @property @trusted | { 670| return _index.sliced(this.data._lengths[0]); | } | | /// ditto | auto index() @property @trusted const | { 4| return _index.lightConst.sliced(this.data._lengths[0]); | } | | /// ditto | auto index() @property @trusted immutable | { 0000000| return _index.lightImmutable.sliced(this.data._lengths[0]); | } | | /// ditto | void index()(Slice!IndexIterator index) @property @trusted | { | import core.lifetime: move; 1| this._index = move(index._iterator); | } | | /// ditto | static if (doUnittest) | @safe version(mir_test) unittest | { | import mir.ndslice.slice: sliced; 1| auto s = ["a", "b"].series([5, 6]); 1| assert(s.index == ["a", "b"]); 1| s.index = ["c", "d"].sliced; 1| assert(s.index == ["c", "d"]); | } | | /++ | Data is any ndslice with only one constraints, | `data` and `index` lengths should be equal. | +/ | Slice!(Iterator, N, kind) data; | |@serdeIgnore: | | /// | IndexIterator _index; | | /// Index / Key / Time type aliases | alias Index = typeof(typeof(this).init.index.front); | /// Data / Value type aliases | alias Data = typeof(typeof(this).init.data.front); | | private enum defaultMsg() = "Series " ~ Unqual!(this.Data).stringof ~ "[" ~ Unqual!(this.Index).stringof ~ "]: Missing"; | private static immutable defaultExc() = new Exception(defaultMsg!() ~ " required key"); | | /// | void serdeFinalize()() @trusted scope | { | import mir.algorithm.iteration: any; | import mir.ndslice.topology: pairwise; | import std.traits: Unqual; | if (length <= 1) | return; | auto mutableOf = cast(Series!(Unqual!Index*, Unqual!Data*)) this.lightScope(); | if (any(pairwise!"a > b"(mutableOf.index))) | sort(mutableOf); | } | |@optmath: | | /// 149| this()(Slice!IndexIterator index, Slice!(Iterator, N, kind) data) | { 149| assert(index.length == data.length, "Series constructor: index and data lengths must be equal."); 149| this.data = data; 149| _index = index._iterator; | } | | | /// Construct from null 2| this(typeof(null)) | { 2| this.data = this.data.init; 2| _index = _index.init; | } | | /// | bool opEquals(RIndexIterator, RIterator, size_t RN, SliceKind rkind, )(Series!(RIndexIterator, RIterator, RN, rkind) rhs) const | { 12| return this.lightScopeIndex == rhs.lightScopeIndex && this.data.lightScope == rhs.data.lightScope; | } | | private auto lightScopeIndex()() return scope @trusted | { 97| return .lightScope(_index).sliced(this.data._lengths[0]); | } | | private auto lightScopeIndex()() return scope @trusted const | { 32| return .lightScope(_index).sliced(this.data._lengths[0]); | } | | private auto lightScopeIndex()() return scope @trusted immutable | { | return .lightScope(_index).sliced(this.data._lengths[0]); | } | | /// | typeof(this) opBinary(string op : "~")(typeof(this) rhs) | { 2| scope typeof(this.lightScope)[2] lhsAndRhs = [this.lightScope, rhs.lightScope]; 2| return unionSeriesImplPrivate!false(lhsAndRhs); | } | | /// ditto | auto opBinary(string op : "~")(const typeof(this) rhs) const @trusted | { 2| scope typeof(this.lightScope)[2] lhsAndRhs = [this.lightScope, rhs.lightScope]; 2| return unionSeriesImplPrivate!false(lhsAndRhs); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.date: Date; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// 1| auto index0 = [1,3,4]; 1| auto data0 = [1.0, 3, 4]; 1| auto series0 = index0.series(data0); | 1| auto index1 = [1,2,5]; 1| auto data1 = [10.0, 20, 50]; 1| auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. 1| auto m0 = series0 ~ series1; 1| auto m1 = series1 ~ series0; | 1| assert(m0.index == m1.index); 1| assert(m0.data == [ 1, 20, 3, 4, 50]); 1| assert(m1.data == [10, 20, 3, 4, 50]); | } | | static if (doUnittest) | @safe pure nothrow version(mir_test) unittest | { | import mir.date: Date; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// 1| auto index0 = [1,3,4]; 1| auto data0 = [1.0, 3, 4]; 1| auto series0 = index0.series(data0); | 1| auto index1 = [1,2,5]; 1| auto data1 = [10.0, 20, 50]; 1| const series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. 1| auto m0 = series0 ~ series1; 1| auto m1 = series1 ~ series0; | 1| assert(m0.index == m1.index); 1| assert(m0.data == [ 1, 20, 3, 4, 50]); 1| assert(m1.data == [10, 20, 3, 4, 50]); | } | | /++ | Special `[] =` index-assign operator for index-series. | Assigns data from `r` with index intersection. | If a index index in `r` is not in the index index for this series, then no op-assign will take place. | This and r series are assumed to be sorted. | | Params: | r = rvalue index-series | +/ | void opIndexAssign(IndexIterator_, Iterator_, size_t N_, SliceKind kind_) | (Series!(IndexIterator_, Iterator_, N_, kind_) r) | { 3| opIndexOpAssign!("", IndexIterator_, Iterator_, N_, kind_)(r); | } | | static if (doUnittest) | /// | version(mir_test) unittest | { 1| auto index = [1, 2, 3, 4]; 1| auto data = [10.0, 10, 10, 10]; 1| auto series = index.series(data); | 1| auto rindex = [0, 2, 4, 5]; 1| auto rdata = [1.0, 2, 3, 4]; 1| auto rseries = rindex.series(rdata); | | // series[] = rseries; 1| series[] = rseries; 1| assert(series.data == [10, 2, 10, 3]); | } | | /++ | Special `[] op=` index-op-assign operator for index-series. | Op-assigns data from `r` with index intersection. | If a index index in `r` is not in the index index for this series, then no op-assign will take place. | This and r series are assumed to be sorted. | | Params: | rSeries = rvalue index-series | +/ | void opIndexOpAssign(string op, IndexIterator_, Iterator_, size_t N_, SliceKind kind_) | (auto ref Series!(IndexIterator_, Iterator_, N_, kind_) rSeries) | { 4| scope l = this.lightScope; 4| scope r = rSeries.lightScope; 4| if (r.empty) 0000000| return; 4| if (l.empty) 0000000| return; 4| Unqual!(typeof(*r._index)) rf = *r._index; 4| Unqual!(typeof(*l._index)) lf = *l._index; 4| goto Begin; | R: 2| r.popFront; 2| if (r.empty) 0000000| goto End; 2| rf = *r._index; | Begin: 6| if (lf > rf) 2| goto R; 4| if (lf < rf) 2| goto L; | E: | static if (N != 1) | mixin("l.data.front[] " ~ op ~ "= r.data.front;"); | else | mixin("l.data.front " ~ op ~ "= r.data.front;"); | 10| r.popFront; 10| if (r.empty) 2| goto End; 8| rf = *r._index; | L: 15| l.popFront; 15| if (l.empty) 2| goto End; 13| lf = *l._index; | 13| if (lf < rf) 5| goto L; 8| if (lf == rf) 8| goto E; 0000000| goto R; | End: | } | | static if (doUnittest) | /// | version(mir_test) unittest | { 1| auto index = [1, 2, 3, 4]; 1| auto data = [10.0, 10, 10, 10]; 1| auto series = index.series(data); | 1| auto rindex = [0, 2, 4, 5]; 1| auto rdata = [1.0, 2, 3, 4]; 1| auto rseries = rindex.series(rdata); | 1| series[] += rseries; 1| assert(series.data == [10, 12, 10, 13]); | } | | /++ | This function uses a search with policy sp to find the largest left subrange on which | `t < key` is true for all `t`. | The search schedule and its complexity are documented in `std.range.SearchPolicy`. | +/ | auto lowerBound(Index)(auto ref scope const Index key) | { 1| return opIndex(opSlice(0, lightScopeIndex.transitionIndex(key))); | } | | /// ditto | auto lowerBound(Index)(auto ref scope const Index key) const | { 1| return opIndex(opSlice(0, lightScopeIndex.transitionIndex(key))); | } | | | /++ | This function uses a search with policy sp to find the largest right subrange on which | `t > key` is true for all `t`. | The search schedule and its complexity are documented in `std.range.SearchPolicy`. | +/ | auto upperBound(Index)(auto ref scope const Index key) | { 1| return opIndex(opSlice(lightScopeIndex.transitionIndex!"a <= b"(key), length)); | } | | /// ditto | auto upperBound(Index)(auto ref scope const Index key) const | { 1| return opIndex(opSlice(lightScopeIndex.transitionIndex!"a <= b"(key), length)); | } | | /** | Gets data for the index. | Params: | key = index | _default = default value is returned if the series does not contains the index. | Returns: | data that corresponds to the index or default value. | */ | ref get(Index, Value)(auto ref scope const Index key, return ref Value _default) @trusted | if (!is(Value : const(Exception))) | { 2| size_t idx = lightScopeIndex.transitionIndex(key); 4| return idx < this.data._lengths[0] && _index[idx] == key ? this.data[idx] : _default; | } | | /// ditto | ref get(Index, Value)(auto ref scope const Index key, return ref Value _default) const | if (!is(Value : const(Exception))) | { | return this.lightScope.get(key, _default); | } | | /// ditto | ref get(Index, Value)(auto ref scope const Index key, return ref Value _default) immutable | if (!is(Value : const(Exception))) | { | return this.lightScope.get(key, _default); | } | | auto get(Index, Value)(auto ref scope const Index key, Value _default) @trusted | if (!is(Value : const(Exception))) | { | size_t idx = lightScopeIndex.transitionIndex(key); | return idx < this.data._lengths[0] && _index[idx] == key ? this.data[idx] : _default; | } | | /// ditto | auto get(Index, Value)(auto ref scope const Index key, Value _default) const | if (!is(Value : const(Exception))) | { | import core.lifetime: forward; | return this.lightScope.get(key, forward!_default); | } | | /// ditto | auto get(Index, Value)(auto ref scope const Index key, Value _default) immutable | if (!is(Value : const(Exception))) | { | import core.lifetime: forward; | return this.lightScope.get(key, forward!_default); | } | | /** | Gets data for the index. | Params: | key = index | exc = (lazy, optional) exception to throw if the series does not contains the index. | Returns: data that corresponds to the index. | Throws: | Exception if the series does not contains the index. | See_also: $(LREF Series.getVerbose), $(LREF Series.tryGet) | */ | auto ref get(Index)(auto ref scope const Index key) @trusted | { 5| size_t idx = lightScopeIndex.transitionIndex(key); 10| if (idx < this.data._lengths[0] && _index[idx] == key) | { 4| return this.data[idx]; | } 1| throw defaultExc!(); | } | | /// ditto | auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) @trusted | { 6| size_t idx = lightScopeIndex.transitionIndex(key); 12| if (idx < this.data._lengths[0] && _index[idx] == key) | { 3| return this.data[idx]; | } 3| throw exc; | } | | /// ditto | auto ref get(Index)(auto ref scope const Index key) const | { | return this.lightScope.get(key); | } | | /// ditto | auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) const | { | return this.lightScope.get(key, exc); | } | | | /// ditto | auto ref get(Index)(auto ref scope const Index key) immutable | { | return this.lightScope.get(key); | } | | /// ditto | auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) immutable | { | return this.lightScope.get(key, exc); | } | | /** | Gets data for the index (verbose exception). | Params: | key = index | Returns: data that corresponds to the index. | Throws: | Detailed exception if the series does not contains the index. | See_also: $(LREF Series.get), $(LREF Series.tryGet) | */ | auto ref getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__) | { | import std.format: format; 3| return this.get(key, new Exception(format("%s %s key", defaultMsg!(), key), file, line)); | } | | /// ditto | auto ref getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__) const | { | return this.lightScope.getVerbose(key, file, line); | } | | /// ditto | auto ref getVerbose(Index)(auto ref scope const Index key, string file = __FILE__, int line = __LINE__) immutable | { | return this.lightScope.getVerbose(key, file, line); | } | | /** | Gets data for the index (extra verbose exception). | Params: | key = index | Returns: data that corresponds to the index. | Throws: | Detailed exception if the series does not contains the index. | See_also: $(LREF Series.get), $(LREF Series.tryGet) | */ | auto ref getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__) | { | import std.format: format; 3| return this.get(key, new Exception(format("%s. %s %s key", exceptionInto, defaultMsg!(), key), file, line)); | } | | /// ditto | auto ref getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__) const | { | return this.lightScope.getExtraVerbose(key, exceptionInto, file, line); | } | | /// ditto | auto ref getExtraVerbose(Index)(auto ref scope const Index key, string exceptionInto, string file = __FILE__, int line = __LINE__) immutable | { | return this.lightScope.getExtraVerbose(key, exceptionInto, file, line); | } | | /// | bool contains(Index)(auto ref scope const Index key) const @trusted | { 2| size_t idx = lightScopeIndex.transitionIndex(key); 3| return idx < this.data._lengths[0] && _index[idx] == key; | } | | /// | auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) @trusted | { 2| size_t idx = lightScopeIndex.transitionIndex(key); 3| bool cond = idx < this.data._lengths[0] && _index[idx] == key; | static if (__traits(compiles, &this.data[size_t.init])) | { 2| if (cond) 1| return &this.data[idx]; 1| return null; | } | else | { | return bool(cond); | } | } | | /// ditto | auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) const | { | auto val = key in this.lightScope; | return val; | } | | /// ditto | auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) immutable | { | auto val = key in this.lightScope; | return val; | } | | /++ | Tries to get the first value, such that `key_i == key`. | | Returns: `true` on success. | +/ | bool tryGet(Index, Value)(Index key, scope ref Value val) @trusted | { 4| size_t idx = lightScopeIndex.transitionIndex(key); 8| auto cond = idx < this.data._lengths[0] && _index[idx] == key; 4| if (cond) 2| val = this.data[idx]; 4| return cond; | } | | /// ditto | bool tryGet(Index, Value)(Index key, scope ref Value val) const | { 2| return this.lightScope.tryGet(key, val); | } | | /// ditto | bool tryGet(Index, Value)(Index key, scope ref Value val) immutable | { | return this.lightScope.tryGet(key, val); | } | | /++ | Tries to get the first value, such that `key_i >= key`. | | Returns: `true` on success. | +/ | bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) | { 2| size_t idx = lightScopeIndex.transitionIndex(key); 2| auto cond = idx < this.data._lengths[0]; 2| if (cond) 2| val = this.data[idx]; 2| return cond; | } | | /// ditto | bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) const | { 2| return this.lightScope.tryGetNext(key, val); | } | | /// ditto | bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) immutable | { | return this.lightScope.tryGetNext(key, val); | } | | /++ | Tries to get the first value, such that `key_i >= key`. | Updates `key` with `key_i`. | | Returns: `true` on success. | +/ | bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) @trusted | { 2| size_t idx = lightScopeIndex.transitionIndex(key); 2| auto cond = idx < this.data._lengths[0]; 2| if (cond) | { 2| key = _index[idx]; 2| val = this.data[idx]; | } 2| return cond; | } | | /// ditto | bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) const | { 2| return this.lightScope.tryGetNextUpdateKey(key, val); | } | | /// ditto | bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) immutable | { | return this.lightScope.tryGetNextUpdateKey(key, val); | } | | /++ | Tries to get the last value, such that `key_i <= key`. | | Returns: `true` on success. | +/ | bool tryGetPrev(Index, Value)(auto ref scope const Index key, scope ref Value val) | { 2| size_t idx = lightScopeIndex.transitionIndex!"a <= b"(key) - 1; 2| auto cond = 0 <= sizediff_t(idx); 2| if (cond) 2| val = this.data[idx]; 2| return cond; | } | | /// ditto | bool tryGetPrev(Index, Value)(auto ref scope const Index key, scope ref Value val) const | { 2| return this.lightScope.tryGetPrev(key, val); | } | | /// ditto | bool tryGetPrev(Index, Value)(auto ref scope const Index key, scope ref Value val) immutable | { | return this.lightScope.tryGetPrev(key, val); | } | | /++ | Tries to get the last value, such that `key_i <= key`. | Updates `key` with `key_i`. | | Returns: `true` on success. | +/ | bool tryGetPrevUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) @trusted | { 2| size_t idx = lightScopeIndex.transitionIndex!"a <= b"(key) - 1; 2| auto cond = 0 <= sizediff_t(idx); 2| if (cond) | { 2| key = _index[idx]; 2| val = this.data[idx]; | } 2| return cond; | } | | /// ditto | bool tryGetPrevUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) const | { 2| return this.lightScope.tryGetPrevUpdateKey(key, val); | } | | /// ditto | bool tryGetPrevUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) immutable | { | return this.lightScope.tryGetPrevUpdateKey(key, val); | } | | /++ | Tries to get the first value, such that `lowerBound <= key_i <= upperBound`. | | Returns: `true` on success. | +/ | bool tryGetFirst(Index, Value)(auto ref scope const Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted | { 2| size_t idx = lightScopeIndex.transitionIndex(lowerBound); 4| auto cond = idx < this.data._lengths[0] && _index[idx] <= upperBound; 2| if (cond) 1| val = this.data[idx]; 2| return cond; | } | | /// ditto | bool tryGetFirst(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const | { 2| return this.lightScope.tryGetFirst(lowerBound, upperBound, val); | } | | /// ditto | bool tryGetFirst(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) immutable | { | return this.lightScope.tryGetFirst(lowerBound, upperBound, val); | } | | /++ | Tries to get the first value, such that `lowerBound <= key_i <= upperBound`. | Updates `lowerBound` with `key_i`. | | Returns: `true` on success. | +/ | bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted | { 1| size_t idx = lightScopeIndex.transitionIndex(lowerBound); 2| auto cond = idx < this.data._lengths[0] && _index[idx] <= upperBound; 1| if (cond) | { 1| lowerBound = _index[idx]; 1| val = this.data[idx]; | } 1| return cond; | } | | /// ditto | bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const | { 1| return this.lightScope.tryGetFirstUpdateLower(lowerBound, upperBound, val); | } | | /// ditto | bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) immutable | { | return this.lightScope.tryGetFirstUpdateLower(lowerBound, upperBound, val); | } | | /++ | Tries to get the last value, such that `lowerBound <= key_i <= upperBound`. | | Returns: `true` on success. | +/ | bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted | { 2| size_t idx = lightScopeIndex.transitionIndex!"a <= b"(upperBound) - 1; 4| auto cond = 0 <= sizediff_t(idx) && _index[idx] >= lowerBound; 2| if (cond) 2| val = this.data[idx]; 2| return cond; | } | | /// ditto | bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const | { 2| return this.lightScope.tryGetLast(lowerBound, upperBound, val); | } | | /// ditto | bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) immutable | { | return this.lightScope.tryGetLast(lowerBound, upperBound, val); | } | | /++ | Tries to get the last value, such that `lowerBound <= key_i <= upperBound`. | Updates `upperBound` with `key_i`. | | Returns: `true` on success. | +/ | bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, scope ref Value val) @trusted | { 2| size_t idx = lightScopeIndex.transitionIndex!"a <= b"(upperBound) - 1; 4| auto cond = 0 <= sizediff_t(idx) && _index[idx] >= lowerBound; 2| if (cond) | { 2| upperBound = _index[idx]; 2| val = this.data[idx]; | } 2| return cond; | } | | /// ditto | bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, scope ref Value val) const | { 2| return this.lightScope.tryGetLastUpdateKey(lowerBound, upperBound, val); | } | | /// ditto | bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, scope ref Value val) immutable | { | return this.lightScope.tryGetLastUpdateKey(lowerBound, upperBound, val); | } | | /++ | Returns: | 1D Slice with creared with $(NDSLICE topology, zip) ([0] - key, [1] - value). | See_also: | $(NDSLICE topology, map) uses multiargument lambdas to handle zipped slices. | +/ | auto asSlice()() @property | { | import mir.ndslice.topology: zip, map, ipack; | static if (N == 1) 1| return index.zip(data); | else | return index.zip(data.ipack!1.map!"a"); | } | | /// ditto | auto asSlice()() const @property | { | return opIndex.asSlice; | } | | /// ditto | auto asSlice()() immutable @property | { | return opIndex.asSlice; | } | | /// ndslice-like primitives | bool empty(size_t dimension = 0)() const @property | if (dimension < N) | { 623| return !length!dimension; | } | | /// ditto | size_t length(size_t dimension = 0)() const @property | if (dimension < N) | { 673| return this.data.length!dimension; | } | | /// ditto | auto front(size_t dimension = 0)() @property | if (dimension < N) | { 166| assert(!empty!dimension); | static if (dimension) | { | return index.series(data.front!dimension); | } | else | { 166| return Observation!(Index, Data)(index.front, data.front); | } | } | | /// ditto | auto back(size_t dimension = 0)() @property | if (dimension < N) | { | assert(!empty!dimension); | static if (dimension) | { | return index.series(this.data.back!dimension); | } | else | { | return index.back.observation(this.data.back); | } | } | | /// ditto | void popFront(size_t dimension = 0)() @trusted | if (dimension < N) | { 264| assert(!empty!dimension); | static if (dimension == 0) 263| _index++; 264| this.data.popFront!dimension; | } | | /// ditto | void popBack(size_t dimension = 0)() | if (dimension < N) | { | assert(!empty!dimension); | this.data.popBack!dimension; | } | | /// ditto | void popFrontExactly(size_t dimension = 0)(size_t n) @trusted | if (dimension < N) | { | assert(length!dimension >= n); | static if (dimension == 0) | _index += n; | this.data.popFrontExactly!dimension(n); | } | | /// ditto | void popBackExactly(size_t dimension = 0)(size_t n) | if (dimension < N) | { 1| assert(length!dimension >= n); 1| this.data.popBackExactly!dimension(n); | } | | /// ditto | void popFrontN(size_t dimension = 0)(size_t n) | if (dimension < N) | { | auto len = length!dimension; | n = n <= len ? n : len; | popFrontExactly!dimension(n); | } | | /// ditto | void popBackN(size_t dimension = 0)(size_t n) | if (dimension < N) | { 1| auto len = length!dimension; 1| n = n <= len ? n : len; 1| popBackExactly!dimension(n); | } | | /// ditto | Slice!(IotaIterator!size_t) opSlice(size_t dimension = 0)(size_t i, size_t j) const | if (dimension < N) | in | { 13| assert(i <= j, | "Series.opSlice!" ~ dimension.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); | enum errorMsg = ": difference between the right and the left bounds" | ~ " must be less than or equal to the length of the given dimension."; 13| assert(j - i <= this.data._lengths[dimension], | "Series.opSlice!" ~ dimension.stringof ~ errorMsg); | } | do | { 13| return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /// ditto | size_t opDollar(size_t dimension = 0)() const | { 6| return this.data.opDollar!dimension; | } | | /// ditto | auto opIndex(Slices...)(Slices slices) | if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) | { | static if (Slices.length == 0) | { 1| return this; | } | else | static if (is_Slice!(Slices[0])) | { 12| return index[slices[0]].series(data[slices]); | } | else | { 3| return index[slices[0]].observation(data[slices]); | } | } | | /// ditto | auto opIndex(Slices...)(Slices slices) const | if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) | { 4| return lightConst.opIndex(slices); | } | | /// ditto | auto opIndex(Slices...)(Slices slices) immutable | if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) | { | return lightImmutable.opIndex(slices); | } | | /// | ref opAssign(typeof(this) rvalue) return @trusted | { | import mir.utility: swap; 8| this.data._structure = rvalue.data._structure; 8| swap(this.data._iterator, rvalue.data._iterator); 8| swap(this._index, rvalue._index); 8| return this; | } | | /// ditto | ref opAssign(RIndexIterator, RIterator)(Series!(RIndexIterator, RIterator, N, kind) rvalue) return | if (isAssignable!(IndexIterator, RIndexIterator) && isAssignable!(Iterator, RIterator)) | { | import core.lifetime: move; 1| this.data._structure = rvalue.data._structure; 1| this.data._iterator = rvalue.data._iterator.move; 1| this._index = rvalue._index.move; 1| return this; | } | | /// ditto | ref opAssign(RIndexIterator, RIterator)(auto ref const Series!(RIndexIterator, RIterator, N, kind) rvalue) return | if (isAssignable!(IndexIterator, LightConstOf!RIndexIterator) && isAssignable!(Iterator, LightConstOf!RIterator)) | { | return this = rvalue.opIndex; | } | | /// ditto | ref opAssign(RIndexIterator, RIterator)(auto ref immutable Series!(RIndexIterator, RIterator, N, kind) rvalue) return | if (isAssignable!(IndexIterator, LightImmutableOf!RIndexIterator) && isAssignable!(Iterator, LightImmutableOf!RIterator)) | { | return this = rvalue.opIndex; | } | | /// ditto | ref opAssign(typeof(null)) return | { 0000000| return this = this.init; | } | | /// ditto | auto save()() @property | { | return this; | } | | /// | Series!(LightScopeOf!IndexIterator, LightScopeOf!Iterator, N, kind) lightScope()() return scope @trusted @property | { 40| return typeof(return)(lightScopeIndex, this.data.lightScope); | } | | /// ditto | Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return scope @trusted const @property | { 22| return typeof(return)(lightScopeIndex, this.data.lightScope); | } | | /// ditto | Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return scope @trusted immutable @property | { | return typeof(return)(lightScopeIndex, this.data.lightScope); | } | | /// | Series!(LightConstOf!IndexIterator, LightConstOf!Iterator, N, kind) lightConst()() const @property @trusted | { 4| return index[].series(data[]); | } | | /// | Series!(LightImmutableOf!IndexIterator, LightImmutableOf!Iterator, N, kind) lightImmutable()() immutable @property @trusted | { | return index[].series(data[]); | } | | /// | auto toConst()() const @property | { | return index.toConst.series(data.toConst); | } | | /// | void toString(Writer)(scope ref Writer w) scope const @safe | { | import mir.format: print; 1| scope ls = lightScope; 1| print(w, "{ index: "); 1| print(w, ls.index); 1| print(w, ", data: "); 1| print(w, ls.data); 1| print(w, " }"); | } | |} | | |/// ditto |alias Series = mir_series; | |/// 1-dimensional data |@safe pure version(mir_test) unittest |{ 1| auto index = [1, 2, 3, 4]; 1| auto data = [2.1, 3.4, 5.6, 7.8]; 1| auto series = index.series(data); 1| const cseries = series; | 1| assert(series.contains(2)); 2| assert( ()@trusted{ return (2 in series) is &data[1]; }() ); | 1| assert(!series.contains(5)); 2| assert( ()@trusted{ return (5 in series) is null; }() ); | 1| assert(series.lowerBound(2) == series[0 .. 1]); 1| assert(series.upperBound(2) == series[2 .. $]); | 1| assert(cseries.lowerBound(2) == cseries[0 .. 1]); 1| assert(cseries.upperBound(2) == cseries[2 .. $]); | | // slicing type deduction for const / immutable series | static assert(is(typeof(series[]) == | Series!(int*, double*))); | static assert(is(typeof(cseries[]) == | Series!(const(int)*, const(double)*))); | static assert(is(typeof((cast(immutable) series)[]) == | Series!(immutable(int)*, immutable(double)*))); | | /// slicing 1| auto seriesSlice = series[1 .. $ - 1]; 1| assert(seriesSlice.index == index[1 .. $ - 1]); 1| assert(seriesSlice.data == data[1 .. $ - 1]); | static assert(is(typeof(series) == typeof(seriesSlice))); | | /// indexing 2| assert(series[1] == observation(2, 3.4)); | | /// range primitives 1| assert(series.length == 4); 2| assert(series.front == observation(1, 2.1)); | 1| series.popFront; 2| assert(series.front == observation(2, 3.4)); | 1| series.popBackN(10); 1| assert(series.empty); |} | |/// 2-dimensional data |@safe pure version(mir_test) unittest |{ | import mir.date: Date; | import mir.ndslice.topology: canonical, iota; | 1| size_t row_length = 5; | 1| auto index = [ | Date(2017, 01, 01), | Date(2017, 02, 01), | Date(2017, 03, 01), | Date(2017, 04, 01)]; | | // 1, 2, 3, 4, 5 | // 6, 7, 8, 9, 10 | // 11, 12, 13, 14, 15 | // 16, 17, 18, 19, 20 1| auto data = iota!int([index.length, row_length], 1); | | // canonical and universal ndslices are more flexible then contiguous 1| auto series = index.series(data.canonical); | | /// slicing 1| auto seriesSlice = series[1 .. $ - 1, 2 .. 4]; 1| assert(seriesSlice.index == index[1 .. $ - 1]); 1| assert(seriesSlice.data == data[1 .. $ - 1, 2 .. 4]); | | static if (kindOf!(typeof(series.data)) != Contiguous) | static assert(is(typeof(series) == typeof(seriesSlice))); | | /// indexing 1| assert(series[1, 4] == observation(Date(2017, 02, 01), 10)); 2| assert(series[2] == observation(Date(2017, 03, 01), iota!int([row_length], 11))); | | /// range primitives 1| assert(series.length == 4); 1| assert(series.length!1 == 5); | 1| series.popFront!1; 1| assert(series.length!1 == 4); |} | |/// Construct from null |@safe pure nothrow @nogc version(mir_test) unittest |{ | import mir.series; | alias Map = Series!(string*, double*); 1| Map a = null; 1| auto b = Map(null); 1| assert(a.empty); 1| assert(b.empty); | | auto fun(Map a = null) | { | | } |} | | version(mir_test) |/// |@safe unittest |{ | import mir.series: series, sort; 1| auto s = ["b", "a"].series([9, 8]).sort; | | import mir.format : text; 1| assert(s.text == `{ index: [a, b], data: [8, 9] }`); |} | |/++ |Convenient function for $(LREF Series) construction. |See_also: $(LREF assocArray) |Attention: | This overloads do not sort the data. | User should call $(LREF directly) if index was not sorted. |+/ |auto series(IndexIterator, Iterator, size_t N, SliceKind kind) | ( | Slice!IndexIterator index, | Slice!(Iterator, N, kind) data, | ) |{ 87| assert(index.length == data.length); 87| return Series!(IndexIterator, Iterator, N, kind)(index, data); |} | |/// ditto |auto series(Index, Data)(Index[] index, Data[] data) |{ 29| assert(index.length == data.length); 29| return .series(index.sliced, data.sliced); |} | |/// ditto |auto series(IndexIterator, Data)(Slice!IndexIterator index, Data[] data) |{ | assert(index.length == data.length); | return .series(index, data.sliced); |} | |/// ditto |auto series(Index, Iterator, size_t N, SliceKind kind)(Index[] index, Slice!(Iterator, N, kind) data) |{ 3| assert(index.length == data.length); 3| return .series(index.sliced, data); |} | |/** |Constructs a GC-allocated series from an associative array. |Performs exactly two allocations. | |Params: | aa = associative array or a pointer to associative array |Returns: | sorted GC-allocated series. |See_also: $(LREF assocArray) |*/ |Series!(K*, V*) series(RK, RV, K = RK, V = RV)(RV[RK] aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | import mir.conv: to; 5| const size_t length = aa.length; | alias R = typeof(return); 5| if (__ctfe) | { | K[] keys; | V[] values; | foreach(ref kv; aa.byKeyValue) | { | keys ~= kv.key.to!K; | values ~= kv.value.to!V; | } | auto ret = series(keys, values); | .sort((()@trusted=>cast(Series!(Unqual!K*, Unqual!V*))ret)()); | static if (is(typeof(ret) == typeof(return))) | return ret; | else | return ()@trusted{ return *cast(R*) &ret; }(); | } | import mir.ndslice.allocation: uninitSlice; 5| Series!(Unqual!K*, Unqual!V*) ret = series(length.uninitSlice!(Unqual!K), length.uninitSlice!(Unqual!V)); 5| auto it = ret; 67| foreach(ref kv; aa.byKeyValue) | { | import mir.conv: emplaceRef; 19| emplaceRef!K(it.index.front, kv.key.to!K); 19| emplaceRef!V(it.data.front, kv.value.to!V); 19| it.popFront; | } 5| .sort(ret); | static if (is(typeof(ret) == typeof(return))) 4| return ret; | else 2| return ()@trusted{ return *cast(R*) &ret; }(); |} | |/// ditto |Series!(RK*, RV*) series(K, V, RK = const K, RV = const V)(const V[K] aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | return .series!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); |} | |/// ditto |Series!(RK*, RV*) series( K, V, RK = immutable K, RV = immutable V)(immutable V[K] aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ 2| return .series!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); |} | |/// ditto |auto series(K, V)(V[K]* aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | return series(*a); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ 1| auto s = [1: 1.5, 3: 3.3, 2: 20.9].series; 1| assert(s.index == [1, 2, 3]); 1| assert(s.data == [1.5, 20.9, 3.3]); 1| assert(s.data[s.findIndex(2)] == 20.9); |} | |pure nothrow version(mir_test) unittest |{ 1| immutable aa = [1: 1.5, 3: 3.3, 2: 2.9]; 1| auto s = aa.series; 1| s = cast() s; 1| s = cast(const) s; 1| s = cast(immutable) s; 1| s = s; 1| assert(s.index == [1, 2, 3]); 1| assert(s.data == [1.5, 2.9, 3.3]); 1| assert(s.data[s.findIndex(2)] == 2.9); |} | | |/** |Constructs a RC-allocated series from an associative array. |Performs exactly two allocations. | |Params: | aa = associative array or a pointer to associative array |Returns: | sorted RC-allocated series. |See_also: $(LREF assocArray) |*/ |auto rcseries(RK, RV, K = RK, V = RV)(RV[RK] aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | import mir.rc.array; | import mir.conv: to; | alias R = Series!(RCI!K, RCI!V); 2| const size_t length = aa.length; 3| auto ret = series(length.mininitRcarray!(Unqual!K).asSlice, length.mininitRcarray!(Unqual!V).asSlice); 2| auto it = ret.lightScope; 22| foreach(ref kv; aa.byKeyValue) | { | import mir.conv: emplaceRef; 6| emplaceRef!K(it.lightScopeIndex.front, kv.key.to!K); 6| emplaceRef!V(it.data.front, kv.value.to!V); 6| it.popFront; | } | import core.lifetime: move; 2| .sort(ret.lightScope); | static if (is(typeof(ret) == R)) 1| return ret; | else 2| return ()@trusted{ return (*cast(R*) &ret); }(); |} | |/// ditto |auto rcseries(K, V, RK = const K, RV = const V)(const V[K] aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | return .rcseries!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); |} | |/// ditto |auto rcseries( K, V, RK = immutable K, RV = immutable V)(immutable V[K] aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ 2| return .rcseries!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); |} | |/// ditto |auto rcseries(K, V)(V[K]* aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | return rcseries(*a); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ 2| auto s = [1: 1.5, 3: 3.3, 2: 20.9].rcseries; 1| assert(s.index == [1, 2, 3]); 1| assert(s.data == [1.5, 20.9, 3.3]); 1| assert(s.data[s.findIndex(2)] == 20.9); |} | |// pure nothrow |version(mir_test) unittest |{ | import mir.rc.array; 1| immutable aa = [1: 1.5, 3: 3.3, 2: 2.9]; 2| auto s = aa.rcseries; 2| Series!(RCI!(const int), RCI!(const double)) c; 1| s = cast() s; 1| c = s; 1| s = cast(const) s; 1| s = cast(immutable) s; 1| s = s; 1| assert(s.index == [1, 2, 3]); 1| assert(s.data == [1.5, 2.9, 3.3]); 1| assert(s.data[s.findIndex(2)] == 2.9); |} | |/++ |Constructs a manually allocated series from an associative array. |Performs exactly two allocations. | |Params: | aa == associative array or a pointer to associative array |Returns: | sorted manually allocated series. |+/ |Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K] aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | import mir.ndslice.allocation: makeUninitSlice; | import mir.conv: emplaceRef; | 1| immutable size_t length = aa.length; | 1| auto ret = series( | allocator.makeUninitSlice!(Unqual!K)(length), | allocator.makeUninitSlice!(Unqual!V)(length)); | 1| auto it = ret; 11| foreach(ref kv; aa.byKeyValue) | { 3| it.index.front.emplaceRef!K(kv.key); 3| it.data.front.emplaceRef!V(kv.value); 3| it.popFront; | } | 1| ret.sort; | static if (is(typeof(ret) == typeof(return))) 1| return ret; | else | return ()@trusted{ return cast(typeof(return)) ret; }(); |} | |/// ditto |Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K]* aa) | if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) |{ | return makeSeries(allocator, *a); |} | |/// |pure nothrow version(mir_test) unittest |{ | import std.experimental.allocator; | import std.experimental.allocator.building_blocks.region; | 1| InSituRegion!(1024) allocator; 1| auto aa = [1: 1.5, 3: 3.3, 2: 2.9]; | 1| auto s = (double[int] aa) @nogc @trusted pure nothrow { 1| return allocator.makeSeries(aa); | }(aa); | 1| auto indexArray = s.index.field; 1| auto dataArray = s.data.field; | 1| assert(s.index == [1, 2, 3]); 1| assert(s.data == [1.5, 2.9, 3.3]); 1| assert(s.data[s.findIndex(2)] == 2.9); | 1| allocator.dispose(indexArray); 1| allocator.dispose(dataArray); |} | |/++ |Returns a newly allocated associative array from a range of key/value tuples. | |Params: | series = index / time $(LREF Series), may not be sorted | |Returns: A newly allocated associative array out of elements of the input |_series. Returns a null associative |array reference when given an empty _series. | |Duplicates: Associative arrays have unique keys. If r contains duplicate keys, |then the result will contain the value of the last pair for that key in r. |+/ |auto assocArray(IndexIterator, Iterator, size_t N, SliceKind kind) | (Series!(IndexIterator, Iterator, N, kind) series) |{ | alias SK = series.Index; | alias SV = series.Data; | alias UK = Unqual!SK; | alias UV = Unqual!SV; | static if (isImplicitlyConvertible!(SK, UK)) | alias K = UK; | else | alias K = SK; | static if (isImplicitlyConvertible!(SV, UV)) | alias V = UV; | else | alias V = SV; | static assert(isMutable!V, "mir.series.assocArray: value type ( " ~ V.stringof ~ " ) must be mutable"); | 1| V[K] aa; 1| aa.insertOrAssign = series; 1| return aa; |} | |/// |@safe pure version(mir_test) unittest |{ | import mir.ndslice; //iota and etc | import mir.series; | 1| auto s = ["c", "a", "b"].series(3.iota!int); 1| assert(s.assocArray == [ | "c": 0, | "a": 1, | "b": 2, | ]); |} | |/// Returns: true if `U` is a $(LREF Series); |enum isSeries(U) = is(U : Series!(IndexIterator, Iterator, N, kind), IndexIterator, Iterator, size_t N, SliceKind kind); | |/++ |Finds an index such that `series.index[index] == key`. | |Params: | series = series | key = index to find in the series |Returns: | `size_t.max` if the series does not contain the key and appropriate index otherwise. |+/ |size_t findIndex(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key) |{ 7| auto idx = series.lightScopeIndex.transitionIndex(key); 14| if (idx < series.data._lengths[0] && series.index[idx] == key) | { 6| return idx; | } 1| return size_t.max; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ 1| auto index = [1, 2, 3, 4].sliced; 1| auto data = [2.1, 3.4, 5.6, 7.8].sliced; 1| auto series = index.series(data); | 1| assert(series.data[series.findIndex(3)] == 5.6); 1| assert(series.findIndex(0) == size_t.max); |} | |/++ |Finds a backward index such that `series.index[$ - backward_index] == key`. | |Params: | series = series | key = index key to find in the series |Returns: | `0` if the series does not contain the key and appropriate backward index otherwise. |+/ |size_t find(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key) |{ 2| auto idx = series.lightScopeIndex.transitionIndex(key); 2| auto bidx = series.data._lengths[0] - idx; 4| if (bidx && series.index[idx] == key) | { 1| return bidx; | } 1| return 0; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ 1| auto index = [1, 2, 3, 4].sliced; 1| auto data = [2.1, 3.4, 5.6, 7.8].sliced; 1| auto series = index.series(data); | 1| if (auto bi = series.find(3)) | { 1| assert(series.data[$ - bi] == 5.6); | } | else | { | assert(0); | } | 1| assert(series.find(0) == 0); |} | |/++ |Iterates union using three functions to handle each intersection case separately. |Params: | lfun = binary function that accepts left side key (and left side value) | cfun = trinary function that accepts left side key, (left side value,) and right side value | rfun = binary function that accepts right side key (and right side value) |+/ |template troykaGalop(alias lfun, alias cfun, alias rfun) |{ | import mir.primitives: isInputRange; | | /++ | Params: | lhs = left hand series | rhs = right hand series | +/ | pragma(inline, false) | void troykaGalop( | IndexIterL, IterL, size_t LN, SliceKind lkind, | IndexIterR, IterR, size_t RN, SliceKind rkind, | )( | Series!(IndexIterL, IterL, LN, lkind) lhs, | Series!(IndexIterR, IterR, RN, rkind) rhs, | ) | { 14| if (lhs.empty) 0000000| goto R0; 14| if (rhs.empty) 0000000| goto L1; | for(;;) | { 60| if (lhs.index.front < rhs.index.front) | { 22| lfun(lhs.index.front, lhs.data.front); 22| lhs.popFront; 22| if (lhs.empty) 6| goto R1; 16| continue; | } | else 38| if (lhs.index.front > rhs.index.front) | { 22| rfun(rhs.index.front, rhs.data.front); 22| rhs.popFront; 22| if (rhs.empty) 6| goto L1; 16| continue; | } | else | { 16| cfun(lhs.index.front, lhs.data.front, rhs.data.front); 16| lhs.popFront; 16| rhs.popFront; 16| if (rhs.empty) 2| goto L0; 14| if (lhs.empty) 0000000| goto R1; 14| continue; | } | } | | L0: 2| if (lhs.empty) 2| return; | L1: | do | { 6| lfun(lhs.index.front, lhs.data.front); 6| lhs.popFront; 6| } while(!lhs.empty); 6| return; | | R0: 0000000| if (rhs.empty) 0000000| return; | R1: | do | { 6| rfun(rhs.index.front, rhs.data.front); 6| rhs.popFront; 6| } while(!rhs.empty); 6| return; | } | | /++ | Params: | lhs = left hand input range | rhs = right hand input range | +/ | pragma(inline, false) | void troykaGalop (LeftRange, RightRange)(LeftRange lhs, RightRange rhs) | if (isInputRange!LeftRange && isInputRange!RightRange && !isSeries!LeftRange && !isSeries!RightRange) | { 2| if (lhs.empty) 0000000| goto R0; 2| if (rhs.empty) 0000000| goto L1; | for(;;) | { 12| if (lhs.front < rhs.front) | { 4| lfun(lhs.front); 4| lhs.popFront; 4| if (lhs.empty) 0000000| goto R1; 4| continue; | } | else 8| if (lhs.front > rhs.front) | { 4| rfun(rhs.front); 4| rhs.popFront; 4| if (rhs.empty) 0000000| goto L1; 4| continue; | } | else | { 4| cfun(lhs.front, rhs.front); 4| lhs.popFront; 4| rhs.popFront; 4| if (rhs.empty) 2| goto L0; 2| if (lhs.empty) 0000000| goto R1; 2| continue; | } | } | | L0: 2| if (lhs.empty) 2| return; | L1: | do | { 0000000| lfun(lhs.front); 0000000| lhs.popFront; 0000000| } while(!lhs.empty); 0000000| return; | | R0: 0000000| if (rhs.empty) 0000000| return; | R1: | do | { 0000000| rfun(rhs.front); 0000000| rhs.popFront; 0000000| } while(!rhs.empty); 0000000| return; | } |} | |/++ |Constructs union using three functions to handle each intersection case separately. |Params: | lfun = binary function that accepts left side key and left side value | cfun = trinary function that accepts left side key, left side value, and right side value | rfun = binary function that accepts right side key and right side value |+/ |template troykaSeries(alias lfun, alias cfun, alias rfun) |{ | /++ | Params: | lhs = left hand series | rhs = right hand series | Returns: | GC-allocated union series with length equal to $(LREF troykaLength) | +/ | auto troykaSeries | ( | IndexIterL, IterL, size_t LN, SliceKind lkind, | IndexIterR, IterR, size_t RN, SliceKind rkind, | )( | Series!(IndexIterL, IterL, LN, lkind) lhs, | Series!(IndexIterR, IterR, RN, rkind) rhs, | ) | { | alias I = CommonType!(typeof(lhs.index.front), typeof(rhs.index.front)); | alias E = CommonType!( | typeof(lfun(lhs.index.front, lhs.data.front)), | typeof(cfun(lhs.index.front, lhs.data.front, rhs.data.front)), | typeof(rfun(rhs.index.front, rhs.data.front)), | ); | alias R = Series!(I*, E*); | alias UI = Unqual!I; | alias UE = Unqual!E; 1| const length = troykaLength(lhs.index, rhs.index); | import mir.ndslice.allocation: uninitSlice; 1| auto index = length.uninitSlice!UI; 1| auto data = length.uninitSlice!UE; 1| auto ret = index.series(data); | alias algo = troykaSeriesImpl!(lfun, cfun, rfun); 1| algo!(I, E)(lhs.lightScope, rhs.lightScope, ret); 2| return (()@trusted => cast(R) ret)(); | } |} | |/// |version(mir_test) unittest |{ | import mir.ndslice; 1| auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1)); 1| auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0); | alias unionAlgorithm = troykaSeries!( 2| (key, left) => left, 2| (key, left, right) => left + right, 2| (key, right) => -right, | ); 1| auto c = unionAlgorithm(a, b); 1| assert(c.index == [0, 1, 2, 3, 4, 9]); 1| assert(c.data == [-10, 1, 22, 3, -30, 44]); |} | |/++ |Constructs union using three functions to handle each intersection case separately. |Params: | lfun = binary function that accepts left side key and left side value | cfun = trinary function that accepts left side key, left side value, and right side value | rfun = binary function that accepts right side key and right side value |+/ |template rcTroykaSeries(alias lfun, alias cfun, alias rfun) |{ | /++ | Params: | lhs = left hand series | rhs = right hand series | Returns: | RC-allocated union series with length equal to $(LREF troykaLength) | +/ | auto rcTroykaSeries | ( | IndexIterL, IterL, size_t LN, SliceKind lkind, | IndexIterR, IterR, size_t RN, SliceKind rkind, | )( | auto ref Series!(IndexIterL, IterL, LN, lkind) lhs, | auto ref Series!(IndexIterR, IterR, RN, rkind) rhs, | ) | { | import mir.rc.array; | alias I = CommonType!(typeof(lhs.index.front), typeof(rhs.index.front)); | alias E = CommonType!( | typeof(lfun(lhs.index.front, lhs.data.front)), | typeof(cfun(lhs.index.front, lhs.data.front, rhs.data.front)), | typeof(rfun(rhs.index.front, rhs.data.front)), | ); | alias R = Series!(RCI!I, RCI!E); | alias UI = Unqual!I; | alias UE = Unqual!E; 1| const length = troykaLength(lhs.index, rhs.index); | import mir.ndslice.allocation: uninitSlice; 2| auto ret = length.mininitRcarray!UI.asSlice.series(length.mininitRcarray!UE.asSlice); | alias algo = troykaSeriesImpl!(lfun, cfun, rfun); 1| algo!(I, E)(lhs.lightScope, rhs.lightScope, ret.lightScope); 2| return (()@trusted => *cast(R*) &ret)(); | } |} | |/// |version(mir_test) unittest |{ | import mir.ndslice; 1| auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1)); 1| auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0); | alias unionAlgorithm = rcTroykaSeries!( 2| (key, left) => left, 2| (key, left, right) => left + right, 2| (key, right) => -right, | ); 2| auto c = unionAlgorithm(a, b); 1| assert(c.index == [0, 1, 2, 3, 4, 9]); 1| assert(c.data == [-10, 1, 22, 3, -30, 44]); |} | | |/++ |Length for Troyka union handlers. |Params: | lhs = left hand side series/range | rhs = right hand side series/range |Returns: Total count of lambda function calls in $(LREF troykaGalop) union handler. |+/ |size_t troykaLength( | IndexIterL, IterL, size_t LN, SliceKind lkind, | IndexIterR, IterR, size_t RN, SliceKind rkind, |)( | Series!(IndexIterL, IterL, LN, lkind) lhs, | Series!(IndexIterR, IterR, RN, rkind) rhs, |) |{ | return troykaLength(lhs.index, rhs.index); |} | |/// ditto |size_t troykaLength(LeftRange, RightRange)(LeftRange lhs, RightRange rhs) | if (!isSeries!LeftRange && !isSeries!RightRange) |{ 2| size_t length; 8| alias counter = (scope auto ref _) => ++length; 4| alias ccounter = (scope auto ref _l, scope auto ref _r) => ++length; 2| troykaGalop!(counter, ccounter, counter)(lhs, rhs); 2| return length; |} | |/// |template troykaSeriesImpl(alias lfun, alias cfun, alias rfun) |{ | /// | void troykaSeriesImpl | ( | I, E, | IndexIterL, IterL, size_t LN, SliceKind lkind, | IndexIterR, IterR, size_t RN, SliceKind rkind, | UI, UE, | )( | Series!(IndexIterL, IterL, LN, lkind) lhs, | Series!(IndexIterR, IterR, RN, rkind) rhs, | Series!(UI*, UE*) uninitSlice, | ) | { | import mir.conv: emplaceRef; 14| troykaGalop!( | (auto ref key, auto ref value) { 28| uninitSlice.index.front.emplaceRef!I(key); 28| uninitSlice.data.front.emplaceRef!E(lfun(key, value)); 28| uninitSlice.popFront; | }, | (auto ref key, auto ref lvalue, auto ref rvalue) { 16| uninitSlice.index.front.emplaceRef!I(key); 16| uninitSlice.data.front.emplaceRef!E(cfun(key, lvalue, rvalue)); 16| uninitSlice.popFront; | }, | (auto ref key, auto ref value) { 28| uninitSlice.index.front.emplaceRef!I(key); 28| uninitSlice.data.front.emplaceRef!E(rfun(key, value)); 28| uninitSlice.popFront; | }, | )(lhs, rhs); 14| assert(uninitSlice.length == 0); | } |} | |/** |Merges multiple (time) series into one. |Makes exactly one memory allocation for two series union |and two memory allocation for three and more series union. | |Returns: sorted GC-allocated series. |See_also $(LREF Series.opBinary) $(LREF makeUnionSeries) |*/ |auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( | Series!(IndexIterator, Iterator, N, kind) a, | Series!(IndexIterator, Iterator, N, kind) b, | ) @safe |{ | import core.lifetime: move; 4| Series!(IndexIterator, Iterator, N, kind)[2] ar = [move(a), move(b)]; 4| return unionSeriesImplPrivate!false(move(ar)); |} | |/// ditto |auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( | Series!(IndexIterator, Iterator, N, kind) a, | Series!(IndexIterator, Iterator, N, kind) b, | Series!(IndexIterator, Iterator, N, kind) c, | ) @safe |{ | import core.lifetime: move; 3| Series!(IndexIterator, Iterator, N, kind)[3] ar = [move(a), move(b), move(c)]; 3| return unionSeriesImplPrivate!false(move(ar)); |} | |/// ditto |auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( | Series!(IndexIterator, Iterator, N, kind) a, | Series!(IndexIterator, Iterator, N, kind) b, | Series!(IndexIterator, Iterator, N, kind) c, | Series!(IndexIterator, Iterator, N, kind) d, | ) @safe |{ | import core.lifetime: move; | Series!(IndexIterator, Iterator, N, kind)[4] ar = [move(a), move(b), move(c), move(d)]; | return unionSeriesImplPrivate!false(move(ar)); |} | | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.date: Date; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// 1| auto index0 = [1,3,4]; 1| auto data0 = [1.0, 3, 4]; 1| auto series0 = index0.series(data0); | 1| auto index1 = [1,2,5]; 1| auto data1 = [10.0, 20, 50]; 1| auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. 1| auto m0 = unionSeries(series0, series1); 1| auto m1 = unionSeries(series1, series0); | 1| assert(m0.index == m1.index); 1| assert(m0.data == [ 1, 20, 3, 4, 50]); 1| assert(m1.data == [10, 20, 3, 4, 50]); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.date: Date; | | ////////////////////////////////////// | // Constructs three time-series. | ////////////////////////////////////// 1| auto index0 = [1,3,4]; 1| auto data0 = [1.0, 3, 4]; 1| auto series0 = index0.series(data0); | 1| auto index1 = [1,2,5]; 1| auto data1 = [10.0, 20, 50]; 1| auto series1 = index1.series(data1); | 1| auto index2 = [1, 6]; 1| auto data2 = [100.0, 600]; 1| auto series2 = index2.series(data2); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. 1| auto m0 = unionSeries(series0, series1, series2); 1| auto m1 = unionSeries(series1, series0, series2); 1| auto m2 = unionSeries(series2, series0, series1); | 1| assert(m0.index == m1.index); 1| assert(m0.index == m2.index); 1| assert(m0.data == [ 1, 20, 3, 4, 50, 600]); 1| assert(m1.data == [ 10, 20, 3, 4, 50, 600]); 1| assert(m2.data == [100, 20, 3, 4, 50, 600]); |} | |/** |Merges multiple (time) series into one. | |Params: | allocator = memory allocator | seriesTuple = variadic static array of composed of series. |Returns: sorted manually allocated series. |See_also $(LREF unionSeries) |*/ |auto makeUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C, Allocator)(auto ref Allocator allocator, Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...) | if (C > 1) |{ 2| return unionSeriesImplPrivate!false(seriesTuple, allocator); |} | |/// |@system pure nothrow version(mir_test) unittest |{ | import std.experimental.allocator; | import std.experimental.allocator.building_blocks.region; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// 1| auto index0 = [1,3,4]; | 1| auto data0 = [1.0, 3, 4]; 1| auto series0 = index0.series(data0); | 1| auto index1 = [1,2,5]; | 1| auto data1 = [10.0, 20, 50]; 1| auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | 1| InSituRegion!(1024) allocator; | 1| auto m0 = allocator.makeUnionSeries(series0, series1); 1| auto m1 = allocator.makeUnionSeries(series1, series0); // order is matter | 1| assert(m0.index == m1.index); 1| assert(m0.data == [ 1, 20, 3, 4, 50]); 1| assert(m1.data == [10, 20, 3, 4, 50]); | | /// series should have the same sizes as after allocation 1| allocator.dispose(m0.index.field); 1| allocator.dispose(m0.data.field); 1| allocator.dispose(m1.index.field); 1| allocator.dispose(m1.data.field); |} | |/** |Merges multiple (time) series into one. | |Returns: sorted manually allocated series. |See_also $(LREF unionSeries) |*/ |auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( | Series!(IndexIterator, Iterator, N, kind) a, | Series!(IndexIterator, Iterator, N, kind) b, | ) @safe |{ | import core.lifetime: move; 2| Series!(IndexIterator, Iterator, N, kind)[2] ar = [move(a), move(b)]; 2| return unionSeriesImplPrivate!true(move(ar)); |} | |///ditto |auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( | Series!(IndexIterator, Iterator, N, kind) a, | Series!(IndexIterator, Iterator, N, kind) b, | Series!(IndexIterator, Iterator, N, kind) c, | ) @safe |{ | import core.lifetime: move; | Series!(IndexIterator, Iterator, N, kind)[3] ar = [move(a), move(b), move(c)]; | return unionSeriesImplPrivate!true(move(ar)); |} | |///ditto |auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( | Series!(IndexIterator, Iterator, N, kind) a, | Series!(IndexIterator, Iterator, N, kind) b, | Series!(IndexIterator, Iterator, N, kind) c, | Series!(IndexIterator, Iterator, N, kind) d, | ) @safe |{ | import core.lifetime: move; | Series!(IndexIterator, Iterator, N, kind)[4] ar = [move(a), move(b), move(c), move(d)]; | return unionSeriesImplPrivate!true(move(ar)); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.rc.array; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// 1| auto index0 = [1,3,4]; | 1| auto data0 = [1.0, 3, 4]; 1| auto series0 = index0.series(data0); | 1| auto index1 = [1,2,5]; | 1| auto data1 = [10.0, 20, 50]; 1| auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | 2| Series!(RCI!int, RCI!double) m0 = rcUnionSeries(series0, series1); 2| Series!(RCI!int, RCI!double) m1 = rcUnionSeries(series1, series0); // order is matter | 1| assert(m0.index == m1.index); 1| assert(m0.data == [ 1, 20, 3, 4, 50]); 1| assert(m1.data == [10, 20, 3, 4, 50]); |} | |/** |Initialize preallocated series using union of multiple (time) series. |Doesn't make any allocations. | |Params: | seriesTuple = dynamic array composed of series. | uninitSeries = uninitialized series with exactly required length. |*/ |pragma(inline, false) |auto unionSeriesImpl(I, E, | IndexIterator, Iterator, size_t N, SliceKind kind, UI, UE)( | scope Series!(IndexIterator, Iterator, N, kind)[] seriesTuple, | Series!(UI*, UE*, N) uninitSeries, | ) @trusted |{ | import mir.conv: emplaceRef; | import mir.algorithm.setops: multiwayUnion; | | enum N = N; | alias I = DeepElementType!(typeof(seriesTuple[0].index)); | alias E = DeepElementType!(typeof(seriesTuple[0].data)); | 3| if(uninitSeries.length) | { 3| auto u = seriesTuple.multiwayUnion!"a.index < b.index"; | do | { 18| auto obs = u.front; 18| emplaceRef!I(uninitSeries.index.front, obs.index); | static if (N == 1) 18| emplaceRef!E(uninitSeries.data.front, obs.data); | else | each!(emplaceRef!E)(uninitSeries.data.front, obs.data); 18| u.popFront; 18| uninitSeries.popFront; | } 18| while(uninitSeries.length); | } |} | |private auto unionSeriesImplPrivate(bool rc, IndexIterator, Iterator, size_t N, SliceKind kind, size_t C, Allocator...)(scope Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple, ref Allocator allocator) @safe | if (C > 1 && Allocator.length <= 1) |{ | import mir.algorithm.setops: unionLength; | import mir.ndslice.topology: iota; | import mir.internal.utility: Iota; | import mir.ndslice.allocation: uninitSlice, makeUninitSlice; | static if (rc) | import mir.rc.array; | 15| Slice!IndexIterator[C] indeces; | foreach (i; Iota!C) 33| indeces[i] = seriesTuple[i].index; | 30| immutable len = (()@trusted => indeces[].unionLength)(); | | alias I = typeof(seriesTuple[0].index.front); | alias E = typeof(seriesTuple[0].data.front); | static if (rc) | alias R = Series!(RCI!I, RCI!E, N); | else | alias R = Series!(I*, E*, N); | alias UI = Unqual!I; | alias UE = Unqual!E; | | static if (N > 1) | { | auto shape = seriesTuple[0].data._lengths; | shape[0] = len; | | foreach (ref sl; seriesTuple[1 .. $]) | foreach (i; Iota!(1, N)) | if (seriesTuple.data[0]._lengths[i] != sl.data._lengths[i]) | assert(0, "shapes mismatch"); | } | else | { | alias shape = len; | } | | static if (rc == false) | { | static if (Allocator.length) 4| auto ret = (()@trusted => allocator[0].makeUninitSlice!UI(len).series(allocator[0].makeUninitSlice!UE(shape)))(); | else 22| auto ret = (()@trusted => len.uninitSlice!UI.series(shape.uninitSlice!UE))(); | } | else | { | static if (Allocator.length) | static assert(0, "rcUnionSeries with allocators is not implemented."); | else 4| auto ret = (()@trusted => 2| len | .mininitRcarray!UI | .asSlice | .series( | shape | .iota | .elementCount | .mininitRcarray!UE | .asSlice | .sliced(shape)))(); | } | | static if (C == 2) // fast path | { | alias algo = troykaSeriesImpl!( 24| ref (scope ref key, scope return ref left) => left, 12| ref (scope ref key, scope return ref left, scope return ref right) => left, 24| ref (scope ref key, scope return ref right) => right, | ); 12| algo!(I, E)(seriesTuple[0], seriesTuple[1], ret.lightScope); | } | else | { 6| unionSeriesImpl!(I, E)((()@trusted => seriesTuple[])(), ret.lightScope); | } | 30| return () @trusted {return *cast(R*) &ret; }(); |} | |/** |Inserts or assigns a series to the associative array `aa`. |Params: | aa = associative array | series = series |Returns: | associative array |*/ |ref V[K] insertOrAssign(V, K, IndexIterator, Iterator, size_t N, SliceKind kind)(return ref V[K] aa, auto ref Series!(IndexIterator, Iterator, N, kind) series) @property |{ 2| auto s = series.lightScope; 24| foreach (i; 0 .. s.length) | { 6| aa[s.index[i]] = s.data[i]; | } 2| return aa; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ 1| auto a = [1: 3.0, 4: 2.0]; 1| auto s = series([1, 2, 3], [10, 20, 30]); 1| a.insertOrAssign = s; 1| assert(a.series == series([1, 2, 3, 4], [10.0, 20, 30, 2])); |} | |/** |Inserts a series to the associative array `aa`. |Params: | aa = associative array | series = series |Returns: | associative array |*/ |ref V[K] insert(V, K, IndexIterator, Iterator, size_t N, SliceKind kind)(return ref V[K] aa, auto ref Series!(IndexIterator, Iterator, N, kind) series) @property |{ 1| auto s = series.lightScope; 12| foreach (i; 0 .. s.length) | { 3| if (s.index[i] in aa) 1| continue; 2| aa[s.index[i]] = s.data[i]; | } 1| return aa; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ 1| auto a = [1: 3.0, 4: 2.0]; 1| auto s = series([1, 2, 3], [10, 20, 30]); 1| a.insert = s; 1| assert(a.series == series([1, 2, 3, 4], [3.0, 20, 30, 2])); |} | | |static if (__VERSION__ < 2078) |//////////////////// OBJECT.d |{ | |private: | |extern (C) |{ | // from druntime/src/rt/aaA.d | | // size_t _aaLen(in void* p) pure nothrow @nogc; | private void* _aaGetY(void** paa, const TypeInfo_AssociativeArray ti, in size_t valuesize, in void* pkey) pure nothrow; | // inout(void)* _aaGetRvalueX(inout void* p, in TypeInfo keyti, in size_t valuesize, in void* pkey); | inout(void)[] _aaValues(inout void* p, in size_t keysize, in size_t valuesize, const TypeInfo tiValArray) pure nothrow; | inout(void)[] _aaKeys(inout void* p, in size_t keysize, const TypeInfo tiKeyArray) pure nothrow; | void* _aaRehash(void** pp, in TypeInfo keyti) pure nothrow; | void _aaClear(void* p) pure nothrow; | | // alias _dg_t = extern(D) int delegate(void*); | // int _aaApply(void* aa, size_t keysize, _dg_t dg); | | // alias _dg2_t = extern(D) int delegate(void*, void*); | // int _aaApply2(void* aa, size_t keysize, _dg2_t dg); | | // private struct AARange { void* impl; size_t idx; } | alias AARange = ReturnType!(object._aaRange); | AARange _aaRange(void* aa) pure nothrow @nogc @safe; | bool _aaRangeEmpty(AARange r) pure nothrow @nogc @safe; | void* _aaRangeFrontKey(AARange r) pure nothrow @nogc @safe; | void* _aaRangeFrontValue(AARange r) pure nothrow @nogc @safe; | void _aaRangePopFront(ref AARange r) pure nothrow @nogc @safe; | |} | |auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe |{ | import core.internal.traits : substInout; | | static struct Result | { | AARange r; | | pure nothrow @nogc: | @property bool empty() @safe { return _aaRangeEmpty(r); } | @property auto front() | { | static struct Pair | { | // We save the pointers here so that the Pair we return | // won't mutate when Result.popFront is called afterwards. | private void* keyp; | private void* valp; | | @property ref key() inout | { | auto p = (() @trusted => cast(substInout!K*) keyp) (); | return *p; | }; | @property ref value() inout | { | auto p = (() @trusted => cast(substInout!V*) valp) (); | return *p; | }; | } | return Pair(_aaRangeFrontKey(r), | _aaRangeFrontValue(r)); | } | void popFront() @safe { return _aaRangePopFront(r); } | @property Result save() { return this; } | } | | return Result(_aaToRange(aa)); |} | |auto byKeyValue(T : V[K], K, V)(T* aa) pure nothrow @nogc |{ | return (*aa).byKeyValue(); |} | |// this should never be made public. |private AARange _aaToRange(T: V[K], K, V)(ref T aa) pure nothrow @nogc @safe |{ | // ensure we are dealing with a genuine AA. | static if (is(const(V[K]) == const(T))) | alias realAA = aa; | else | const(V[K]) realAA = aa; | return _aaRange(() @trusted { return cast(void*)realAA; } ()); |} | |} source/mir/series.d is 95% covered <<<<<< EOF # path=source-mir-small_array.lst |/++ |$(H1 Small Array) | |The module contains self-contained generic small array implementaton. | |$(LREF SmallArray) supports ASDF - Json Serialisation Library. | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki |+/ |module mir.small_array; | |private static immutable errorMsg = "Cannot create SmallArray: input range exceeds maximum allowed length."; |version(D_Exceptions) | private static immutable exception = new Exception(errorMsg); | |/// |template SmallArray(T, uint maxLength) | if (maxLength) |{ | import mir.serde: serdeProxy, serdeScoped; | import std.traits: Unqual, isIterable, isImplicitlyConvertible; | | static if (isImplicitlyConvertible!(const T, T)) | alias V = const T; | else | alias V = T; | | /// | @serdeScoped | @serdeProxy!(V[]) | struct SmallArray | { | uint _length; | T[maxLength] _data; | | /// | alias serdeKeysProxy = T; | | @safe pure @nogc: | | /// Constructor 0000000| this(typeof(null)) | { | } | | /// ditto 2| this(scope V[] array) | { 2| this.opAssign(array); | } | | /// ditto 0000000| this(const SmallArray array) nothrow | { 0000000| this.opAssign(array); | } | | /// ditto | this(uint n)(const SmallArray!(T, n) array) | { | this.opAssign(array); | } | | /// ditto | this(uint n)(ref const SmallArray!(T, n) array) | { | this.opAssign(array); | } | | /// ditto | this(Range)(auto ref Range array) | if (isIterable!Range) | { | foreach(ref c; array) | { | if (_length > _data.length) | { | version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } | _data[_length++] = c; | } | } | | /// `=` operator | ref typeof(this) opAssign(typeof(null)) return | { 1| _length = 0; 1| _data = T.init; 1| return this; | } | | /// ditto | ref typeof(this) opAssign(V[] array) return @trusted | { 2| if (array.length > maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 2| _data[0 .. _length] = T.init; 2| _length = cast(uint) array.length; 2| _data[0 .. _length] = array; 2| return this; | } | | /// ditto | ref typeof(this) opAssign(ref const SmallArray rhs) return nothrow | { 0000000| _length = rhs._length; 0000000| _data = rhs._data; 0000000| return this; | } | | /// ditto | ref typeof(this) opAssign(const SmallArray rhs) return nothrow | { 0000000| _length = rhs._length; 0000000| _data = rhs._data; 0000000| return this; | } | | /// ditto | ref typeof(this) opAssign(uint n)(ref const SmallArray!(T, n) rhs) return | if (n != maxLength) | { | static if (n < maxLength) | { 1| _data[0 .. n] = rhs._data; 1| if (_length > n) 0000000| _data[n .. _length] = T.init; | } | else | { 1| if (rhs._length > maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 1| _data = rhs._data[0 .. maxLength]; | } 2| _length = rhs._length; 2| return this; | } | | /// ditto | ref typeof(this) opAssign(uint n)(const SmallArray!(T, n) rhs) return | if (n != maxLength) | { | static if (n < maxLength) | { | _data[0 .. n] = rhs._data; | if (_length > n) | _data[n .. _length] = T.init; | } | else | { | if (rhs._length > maxLength) | { | version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } | _data = rhs._data[0 .. maxLength]; | } | _length = rhs._length; | return this; | } | | /// | void trustedAssign(V[] array) @trusted | { 0000000| _data[0 .. _length] = T.init; 0000000| _length = cast(uint) array.length; 0000000| _data[0 .. _length] = array; | } | | /// | ref typeof(this) append(T elem) | { | import core.lifetime: move; 19| if (_length == maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 19| _data[_length++] = move(elem); 19| return this; | } | | /// | void trustedAppend(T elem) | { | import core.lifetime: move; 0000000| _data[_length++] = move(elem); | } | | /// | ref typeof(this) append(V[] array) | { 3| if (_length + array.length > maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 3| _data[_length .. _length + array.length] = array; 3| _length += array.length; 3| return this; | } | | /// ditto | alias put = append; | | /// ditto | alias opOpAssign(string op : "~") = append; | | /// | SmallArray concat(V[] array) scope const | { 1| SmallArray c = this; 1| c.append(array); 1| return c; | } | | /// ditto | alias opBinary(string op : "~") = concat; | | /++ | Returns an scope common array. | | The property is used as common array representation self alias. | | The alias helps with `[]`, `[i]`, `[i .. j]`, `==`, and `!=` operations and implicit conversion to strings. | +/ | inout(T)[] opIndex() inout @trusted scope return | { 27| return _data[0 .. _length]; | } | | /// | ref inout(T) opIndex(size_t index) inout scope return | { 13| return opIndex[index]; | } | | scope const: | | /// | bool empty() @property | { 3| return _length == 0; | } | | /// | size_t length() @property | { 13| return _length; | } | | /// Hash implementation | size_t toHash() | { 0000000| return hashOf(opIndex); | } | | /// Comparisons operator overloads | bool opEquals(scope ref const SmallArray rhs) | { 0000000| return opIndex == rhs.opIndex; | } | | /// ditto | bool opEquals(SmallArray rhs) | { 0000000| return opIndex == rhs.opIndex; | } | | /// ditto | bool opEquals()(const scope V[] array) | { 7| return opIndex == array; | } | | /// ditto | bool opEquals(uint rhsMaxLength)(auto ref scope SmallArray!(T, rhsMaxLength) array) | { 1| return opIndex == array.opIndex; | } | | /// ditto | auto opCmp()(const scope V[] array) | { 3| return __cmp(opIndex, array); | } | | /// ditto | auto opCmp(uint rhsMaxLength)(auto ref scope const SmallArray!(T, rhsMaxLength) array) | { 1| return __cmp(opIndex, array.opIndex); | } | } |} | |/// |@safe pure @nogc version(mir_test) unittest |{ 1| SmallArray!(char, 16) s16; 1| assert(s16.empty); | 1| auto s8 = SmallArray!(char, 8)("Hellow!!"); 1| assert(!s8.empty); 1| assert(s8 == "Hellow!!"); | 1| s16 = s8; 1| assert(s16 == "Hellow!!"); 1| s16[7] = '@'; 1| s8 = null; 1| assert(s8.empty); 1| assert(s8 == null); 1| s8 = s16; 1| assert(s8 == "Hellow!@"); | 1| auto s8_2 = s8; 1| assert(s8_2 == "Hellow!@"); 1| assert(s8_2 == s8); | 1| assert(s8 < "Hey"); 1| assert(s8 > "Hellow!"); | 1| assert(s8.opCmp("Hey") < 0); 1| assert(s8.opCmp(s8) == 0); |} | |/// Concatenation |@safe pure @nogc version(mir_test) unittest |{ 1| auto a = SmallArray!(char, 16)("asdf"); 1| a ~= " "; 1| auto b = a ~ "qwerty"; | static assert(is(typeof(b) == SmallArray!(char, 16))); 1| assert(b == "asdf qwerty"); 1| b.put('!'); 1| b.put("!"); 1| assert(b == "asdf qwerty!!"); |} source/mir/small_array.d is 74% covered <<<<<< EOF # path=source-mir-small_string.lst |/++ |$(H1 Small String) | |The module contains self-contained generic small string implementaton. | |$(LREF SmallString) supports ASDF - Json Serialisation Library. | |See also `include/mir/small_series.h` for a C++ version of this type. |Both C++ and D implementations have the same ABI and name mangling. | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki |+/ |module mir.small_string; | |import mir.serde: serdeScoped, serdeProxy; | |private extern (C) @system nothrow @nogc pure size_t strnlen_s(scope const char* s, size_t n); | |private static immutable errorMsg = "Cannot create SmallString: input string exceeds maximum allowed length."; |version(D_Exceptions) | private static immutable exception = new Exception(errorMsg); | |/++ |Self-contained generic Small String implementaton. |+/ |extern(C++, "mir") |@serdeScoped @serdeProxy!(const(char)[]) |struct SmallString(uint maxLength) | if (maxLength) |{ | | import core.stdc.string: memcmp, memcpy, strlen; | import std.traits: Unqual, isIterable; | | // maxLength bytes | char[maxLength] _data = '\0'; | |extern(D) @safe pure @nogc: | | /// Constructor 0000000| this(typeof(null)) | { | } | | /// ditto 8| this(scope const(char)[] str) | { 8| this.opAssign(str); | } | | /// ditto | this(uint n)(auto ref scope const SmallString!n str) | { | this.opAssign(str); | } | | /// ditto | this(Range)(auto ref Range str) | if (isIterable!Range) | { | size_t i = 0; | foreach(char c; str) | { | if (i > _data.length) | { | version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } | _data[i++] = c; | } | } | | /// `=` operator | ref typeof(this) opAssign(typeof(null)) return | { 1| _data = '\0'; 1| return this; | } | | /// ditto | ref typeof(this) opAssign(scope const(char)[] str) return @trusted | { 8| if (str.length > _data.sizeof) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 8| if (__ctfe) | _data[0 .. str.length] = str; | else 8| memcpy(_data.ptr, str.ptr, str.length); 8| _data[str.length .. $] = '\0'; 8| return this; | } | | /// ditto | ref typeof(this) opAssign(uint n)(auto ref scope const SmallString!n rhs) return | if (n != maxLength) | { | static if (n < maxLength) | { | version (LDC) 1| cast(char[n])(_data[0 .. n]) = rhs._data; | else | _data[0 .. n] = rhs._data; 1| _data[n .. maxLength] = '\0'; | } | else | { 1| if (rhs._data[maxLength]) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 1| _data = cast(char[maxLength])(rhs._data[0 .. maxLength]); | } 2| return this; | } | | /// ditto | ref typeof(this) opAssign(uint n)(const SmallString!n rhs) return | if (n != maxLength) | { | static if (n < maxLength) | { | version (LDC) | cast(char[n])(_data[0 .. n]) = rhs._data; | else | _data[0 .. n] = rhs._data; | _data[n .. maxLength] = '\0'; | } | else | { | if (rhs._data[maxLength]) | { | version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } | _data = cast(char[maxLength])(rhs._data[0 .. maxLength]); | } | return this; | } | | /// ditto | void trustedAssign(scope const(char)[] str) return @trusted nothrow | { 0000000| _data = '\0'; 0000000| if (__ctfe) | _data[0 .. str.length] = str; | else 0000000| memcpy(_data.ptr, str.ptr, str.length); | } | | /// | ref typeof(this) append(char c) @trusted | { 6| auto length = opIndex.length; 6| if (length == maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 6| _data[length] = c; 6| return this; | } | | /// | ref typeof(this) append(scope const(char)[] str) @trusted | { 10| auto length = opIndex.length; 10| if (length + str.length > maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 10| if (__ctfe) | _data[length .. str.length + length] = str; | else 10| memcpy(_data.ptr + length, str.ptr, str.length); 10| return this; | } | | /// ditto | alias put = append; | | /// ditto | alias opOpAssign(string op : "~") = append; | | /// | SmallString concat(scope const(char)[] str) scope const | { 1| SmallString c = this; 1| c.append(str); 1| return c; | } | | /// ditto | alias opBinary(string op : "~") = concat; | | |scope nothrow: | | /++ | Returns an scope common string. | | The method implement with `[]` operation. | +/ | inout(char)[] opIndex() inout @trusted scope return | { 44| size_t i; 44| if (__ctfe) | while (i < maxLength && _data[i]) i++; | else 44| i = _data[$ - 1] ? _data.length : strlen(_data.ptr); 44| return _data[0 .. i]; | } | | /// | ref inout(char) opIndex(size_t index) inout scope return | { 1| return opIndex[index]; | } | | /++ | Returns a common scope string. | | The method implement with `[i .. j]` operation. | +/ | inout(char)[] opIndex(size_t[2] range) inout @trusted scope return 2| in (range[0] <= range[1]) 2| in (range[1] <= this.length) | { 2| return opIndex()[range[0] .. range[1]]; | } | |scope const: | | /// ditto | size_t[2] opSlice(size_t pos : 0)(size_t i, size_t j) { 2| return [i, j]; | } | | /// | bool empty() @property | { 2| return _data[0] == 0; | } | | /// | size_t length() @property | { 3| return opIndex.length; | } | | /// ditto | alias opDollar(size_t pos : 0) = length; | | /// | alias toString = opIndex; | | /// Hash implementation | size_t toHash() | { 0000000| return hashOf(opIndex); | } | | /// Comparisons operator overloads | bool opEquals(ref scope const SmallString rhs) | { 1| return _data == rhs._data; | } | | /// ditto | bool opEquals(SmallString rhs) | { 0000000| return _data == rhs._data; | } | | /// ditto | bool opEquals(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) | if (rhsMaxLength != maxLength) | { | return opIndex == rhs.opIndex; | } | | /// ditto | bool opEquals()(scope const(char)[] str) | { 9| return opIndex == str; | } | | /// ditto | int opCmp(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) | { 1| return __cmp(opIndex, rhs.opIndex); | } | | /// ditto | int opCmp()(scope const(char)[] str) | { 3| return __cmp(opIndex, str); | } |} | |/// |@safe pure @nogc version(mir_test) unittest |{ 1| SmallString!16 s16; 1| assert(s16.empty); | 1| auto s8 = SmallString!8("Hellow!!"); 1| assert(s8 == "Hellow!!"); 1| assert(s8[] == "Hellow!!"); 1| assert(s8[0 .. $] == "Hellow!!"); 1| assert(s8[1 .. 4] == "ell"); | 1| s16 = s8; 1| assert(s16 == "Hellow!!"); 1| s16[7] = '@'; 1| s8 = null; 1| assert(s8.empty); 1| s8 = s16; 1| assert(s8 == "Hellow!@"); | 1| auto s8_2 = s8; 1| assert(s8_2 == "Hellow!@"); 1| assert(s8_2 == s8); | 1| assert(s8 < "Hey"); 1| assert(s8 > "Hellow!"); | 1| assert(s8.opCmp("Hey") < 0); 1| assert(s8.opCmp(s8) == 0); |} | |/// Concatenation |@safe pure @nogc version(mir_test) unittest |{ 1| auto a = SmallString!16("asdf"); 1| a ~= " "; 1| auto b = a ~ "qwerty"; | static assert(is(typeof(b) == SmallString!16)); 1| assert(b == "asdf qwerty"); 1| b.put('!'); 1| b.put("!"); 1| assert(b == "asdf qwerty!!"); |} | |@safe pure @nogc nothrow version(mir_test) unittest |{ | import mir.conv: emplaceRef; 2| SmallString!32 a, b; 1| emplaceRef!(const SmallString!32)(a, cast(const)b); |} source/mir/small_string.d is 87% covered <<<<<< EOF # path=source-mir-stdio.lst |/++ |A simple I/O routines around ``. | |The implementation is CTFE-friendly. |+/ |module mir.stdio; | |static import core.stdc.stdio; | |/// Writes values in a text form |void writeln(string separator = "", Args...)(auto ref const Args args) | if (Args.length > 0) |{ 0000000| dout.write!separator(args); 0000000| dout << endl; |} | |/// ditto |void write(string separator = "", Args...)(auto ref const Args args) | if (Args.length > 0) |{ | dout.write!separator(args); |} | |/// Writes values in a text form using nothrow $(LREF tout) |void dump(string separator = " ", Args...)(auto ref const Args args) | if (Args.length > 0) |{ | tout.write!separator(args); | tout << endl; |} | |/// Standart output |File dout()() @trusted nothrow @nogc @property |{ | version(LDC) | pragma(inline, true); 1| return File(__ctfe ? null : core.stdc.stdio.stdout); |} | |/// ditto |File derr()() @trusted nothrow @nogc @property |{ | version(LDC) | pragma(inline, true); 1| return File(__ctfe ? null : core.stdc.stdio.stderr); |} | |/// |version(mir_test) |@safe @nogc |unittest |{ 1| dout << "mir.stdio.dout test! - @nogc I/O" << endl; 1| derr << "mir.stdio.derr test! - @nogc I/O" << endl; |} | |/++ |Nothrow standart output to use in pair with `debug` expression in nothrow |and pure code for testing purpose. | |See_also: $(LREF AssumeNothrowFile) |+/ |AssumeNothrowFile tout()() @trusted nothrow @nogc @property |{ | version(LDC) | pragma(inline, true); 1| return AssumeNothrowFile(__ctfe ? null : core.stdc.stdio.stdout); |} | |/// ditto |AssumeNothrowFile terr()() @trusted nothrow @nogc @property |{ | version(LDC) | pragma(inline, true); 1| return AssumeNothrowFile(__ctfe ? null : core.stdc.stdio.stderr); |} | |/// |version(mir_test) |pure @safe @nogc nothrow |unittest |{ 1| debug tout << "mir.stdio.tout test! - @nogc nothrow I/O" << endl; 1| debug terr << "mir.stdio.terr test! - @nogc nothrow I/O" << endl; |} | |/++ |When used as `file << endl` it adds new line flushes the stream. |+/ |enum NewLine |{ | lf = "\n", | lf_cf = "\r\n", |} | |/// ditto |enum endl = NewLine.lf; | |/++ |+/ |struct File |{ | /// | core.stdc.stdio.FILE* fp; | | mixin FileMembers; | |@trusted @nogc: | | /++ | Throws: $(LREF FileException) | +/ | void rawWrite(scope const(void)[] data) scope 8| in (__ctfe || fp !is null) | { 4| if (__ctfe) | return; 4| core.stdc.stdio.fwrite(data.ptr, 1, data.length, fp); 4| if (core.stdc.stdio.ferror(fp)) 0000000| throw writeException; | } | | /++ | Throws: $(LREF FileException) | +/ | void flush() scope 4| in (__ctfe || fp !is null) | { 2| if (__ctfe) | return; 2| core.stdc.stdio.fflush(fp); 2| if (core.stdc.stdio.ferror(fp)) 0000000| throw writeException; | } |} | |/++ |Nothrow File implementation for testing purposes. |See_also: $(LREF tout), $(LREF terr) |+/ |struct AssumeNothrowFile |{ | /// | core.stdc.stdio.FILE* fp; | | mixin FileMembers; | |@trusted @nogc nothrow: | | /++ | Throws: $(LREF FileError) | +/ | void rawWrite(scope const(void)[] data) scope 8| in (__ctfe || fp !is null) | { 4| if (__ctfe) | return; 4| core.stdc.stdio.fwrite(data.ptr, 1, data.length, fp); 4| if (core.stdc.stdio.ferror(fp)) 0000000| throw writeError; | } | | /++ | Throws: $(LREF FileError) | +/ | void flush() scope 4| in (__ctfe || fp !is null) | { 2| if (__ctfe) | return; 2| core.stdc.stdio.fflush(fp); 2| if (core.stdc.stdio.ferror(fp)) 0000000| throw writeError; | } |} | |/// |mixin template FileMembers() |{ | /// | void put(C)(const C data) scope | if (is(C == char) || is(C == wchar) | is(C == dchar)) | { 0000000| C[1] array = [data]; 0000000| this.rawWrite(array); | } | | /// | void put(C)(scope const(C)[] data) scope | if (is(C == char) || is(C == wchar) | is(C == dchar)) | { 8| this.rawWrite(data); | } | | /// | template opBinary(string op : "<<") | { | /// | ref opBinary(T)(auto ref const T value) scope return | { 4| if (__ctfe) | return this; | import mir.format: print; 4| print!char(this, value); 4| return this; | } | | /// Prints new line and flushes the stream | ref opBinary(NewLine endl) scope return | { 4| if (__ctfe) | return this; | import mir.format: print; 4| this.put(endl); 4| this.flush; 4| return this; | } | } | | /// Writes values in a text form | void writeln(string separator = "", Args...)(auto ref const Args args) scope | if (Args.length > 0) | { | write(args); | this << endl; | } | | /// ditto | void write(string separator = "", Args...)(auto ref const Args args) scope | if (Args.length > 0) | { | pragma(inline, false); 0000000| if (__ctfe) | return; | import mir.format: print, printStaticString; 0000000| foreach (i, ref arg; args) | { 0000000| print!char(this, arg); | static if (separator.length && i + 1 < args.length) | { | printStaticString!char(this, separator); | } | } | } |} | |/++ |File Exception |+/ |class FileException : Exception |{ | /// 0000000| this( | string msg, | string file = __FILE__, | size_t line = __LINE__, | Throwable next = null) pure nothrow @nogc @safe | { 0000000| super(msg, file, line, next); | } | | /// 0000000| this( | string msg, | Throwable next, | string file = __FILE__, | size_t line = __LINE__, | ) pure nothrow @nogc @safe | { 0000000| this(msg, file, line, next); | } | | FileException toMutable() @trusted pure nothrow @nogc const | { 0000000| return cast() this; | } | | alias toMutable this; |} | |/++ |File Error |+/ |class FileError : Error |{ | /// 0000000| this( | string msg, | string file = __FILE__, | size_t line = __LINE__, | Throwable next = null) pure nothrow @nogc @safe | { 0000000| super(msg, file, line, next); | } | | /// 0000000| this( | string msg, | Throwable next, | string file = __FILE__, | size_t line = __LINE__, | ) pure nothrow @nogc @safe | { 0000000| this(msg, file, line, next); | } | | FileError toMutable() @trusted pure nothrow @nogc const | { 0000000| return cast() this; | } | | alias toMutable this; |} | |private static immutable writeException = new FileException("Error on file write"); |private static immutable flushException = new FileException("Error on file flush"); |private static immutable writeError = new FileError("Error on file write"); |private static immutable flushError = new FileError("Error on file flush"); | |version(mir_test) |@safe unittest |{ | static struct ParameterSpec | { | string name; | string type; | string default_; | } | | static struct FunctionOverloadSpec | { | ParameterSpec[] parameters; | string doc; | string test; | size_t minArgs; | } | | import mir.algebraic; | import mir.stdio; 1| if (false) | Algebraic!(FunctionOverloadSpec, ParameterSpec).init.writeln; // error |} source/mir/stdio.d is 61% covered <<<<<< EOF # path=source-mir-string.lst |/++ |$(H1 String routines) | |The module contains SIMD-accelerated string routines. | |Copyright: 2022 Ilia Ki, Symmetry Investments | |Authors: Ilia Ki |+/ |module mir.string; | |import std.traits: isSomeChar; | |private alias Representation(T : char) = byte; |private alias Representation(T : wchar) = short; |private alias Representation(T : dchar) = int; | |private enum size_t ScanVecSize = 16; | |/// |bool containsAny(C, size_t L) | (scope const(C)[] str, const C[L] chars...) | @trusted pure nothrow @nogc | if (isSomeChar!C && L) |{ | enum size_t N = ScanVecSize / C.sizeof; | | alias U = Representation!C; | | // version(none) | version (LittleEndian) | version (LDC) | static if (N <= 8) | static if (is(__vector(U[N]))) | if (!__ctfe) | { | alias V = __vector(U[N]); | pragma(msg, V); | V[L] charsv; | static foreach (i; 0 .. L) | charsv[i] = chars[i]; | | while (str.length >= N) | { | auto a = cast(V) *cast(const U[N]*) str.ptr; | | import ldc.simd: mask = equalMask; | | V[L] masked; | static foreach (i; 0 .. L) | masked[i] = mask!(__vector(U[N]))(a, charsv[i]); | | static foreach (i; 0 .. L) | static if (i == 0) | V m = masked[i]; | else | m |= masked[i]; | | if (m != V.init) | return true; | | str = str[N .. $]; | } | } | 147| foreach (C c; str) | static foreach (i; 0 .. L) 61| if (c == chars[i]) 3| return true; 1| return false; |} | |/// |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.test: should; | 1| assert(" hello world ".containsAny('w')); 1| assert(!" hello world ".containsAny('W')); 1| assert(" hello world ".containsAny('W', 'e')); 1| assert(" hello world ".containsAny("We")); |} | |/// |template scanLeftAny(string op = "==") | if (op == "==" || op == "!=") |{ | /// | inout(C)[] | scanLeftAny(C, size_t L) | (return scope inout(C)[] str, const C[L] chars...) | @trusted pure nothrow @nogc | if (isSomeChar!C && L) | { | enum size_t N = ScanVecSize / C.sizeof; | | alias U = Representation!C; | | // version(none) | version (LittleEndian) | version (LDC) | static if (N <= 8) | static if (is(__vector(U[N]))) | if (!__ctfe) | { | import mir.bitop: cttz; | | alias V = __vector(U[N]); | pragma(msg, V); | V[L] charsv; | static foreach (i; 0 .. L) | charsv[i] = chars[i]; | | while (str.length >= N) | { | auto a = cast(V) *cast(const U[N]*) str.ptr; | | import ldc.simd: mask = equalMask; | | V[L] masked; | static foreach (i; 0 .. L) | masked[i] = mask!(__vector(U[N]))(a, charsv[i]); | | static foreach (i; 0 .. L) | static if (i == 0) | V m = masked[i]; | else | m |= masked[i]; | | static if (op == "!=") | m = ~m; | | auto words = (cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array; | | static foreach (i; 0 .. words.length) | { | if (words[i]) | { | enum p = i * size_t.sizeof / U.sizeof; | return str[p + (cttz(words[i]) / (U.sizeof * 8)) .. $]; | } | } | str = str[N .. $]; | } | } | 248| Loop: for (; str.length; str = str[1 .. $]) | { 128| auto c = str[0]; | static foreach (i; 0 .. L) | { 146| if (c == chars[i]) | { | static if (op == "==") 3| break Loop; | else 53| continue Loop; | } | } | static if (op == "==") 65| continue Loop; | else 7| break Loop; | } 12| return str; | } |} | |/// |alias stripLeft = scanLeftAny!"!="; | |/// |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.test: should; | 1| " hello world ".stripLeft(' ').should == "hello world "; 1| " hello world ".scanLeftAny('w').should == "world "; 1| " hello world ".scanLeftAny('!').should == ""; 1| "\t\n\thello world\n\t___".stripLeft('\n', '\t').should == "hello world\n\t___"; 1| "hello world".stripLeft(' ').should == "hello world"; 1| "hello world ".stripLeft(' ').should == "hello world "; | 1| " _____________ hello world " | .stripLeft(' ', '_').should == "hello world "; |} | |/// |template scanRightAny(string op = "==") | if (op == "==" || op == "!=") |{ | /// | inout(C)[] | scanRightAny(C, size_t L) | (return scope inout(C)[] str, const C[L] chars...) | @trusted pure nothrow @nogc | if (isSomeChar!C && L) | { | enum size_t N = ScanVecSize / C.sizeof; | | alias U = Representation!C; | | // version(none) | version (LittleEndian) | version (LDC) | static if (N <= 8) | static if (is(__vector(U[N]))) | if (!__ctfe) | { | import mir.bitop: ctlz; | | alias V = __vector(U[N]); | pragma(msg, V); | V[L] charsv; | static foreach (i; 0 .. L) | charsv[i] = chars[i]; | | while (str.length >= N) | { | auto a = cast(V) *cast(const U[N]*) (str.ptr + str.length - N); | | import ldc.simd: mask = equalMask; | | V[L] masked; | static foreach (i; 0 .. L) | masked[i] = mask!(__vector(U[N]))(a, charsv[i]); | | static foreach (i; 0 .. L) | static if (i == 0) | V m = masked[i]; | else | m |= masked[i]; | | static if (op == "!=") | m = ~m; | | auto words = (cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array; | | static foreach (i; 0 .. words.length) | { | if (words[$ - 1 - i]) | { | enum p = i * size_t.sizeof / U.sizeof; | return str[0 .. $ - (p + (ctlz(words[$ - 1 - i]) / (U.sizeof * 8)))]; | } | } | str = str[0 .. $ - N]; | } | } | 175| Loop: for (; str.length; str = str[0 .. $ - 1]) | { 91| auto c = str[$ - 1]; | static foreach (i; 0 .. L) | { 111| if (c == chars[i]) | { | static if (op == "==") 1| break Loop; | else 53| continue Loop; | } | } | static if (op == "==") 30| continue Loop; | else 7| break Loop; | } 9| return str; | } |} | |/// |alias stripRight = scanRightAny!"!="; | |/// |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.test: should; | 1| " hello world ".stripRight(' ').should == " hello world"; 1| " hello world ".scanRightAny('w').should == " hello w"; 1| " hello world ".scanRightAny('!').should == ""; 1| "___\t\n\thello world\n\t".stripRight('\n', '\t').should == "___\t\n\thello world"; 1| "hello world".stripRight(' ').should == "hello world"; 1| " hello world".stripRight(' ').should == " hello world"; | 1| " hello world _____________ " | .stripRight(' ', '_').should == " hello world"; |} | |/// |inout(C)[] | strip(C, size_t L) | (return scope inout(C)[] str, const C[L] chars...) | @safe pure nothrow @nogc | if (isSomeChar!C && L) |{ 2| return str.stripLeft(chars).stripRight(chars); |} | |/// |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import mir.test: should; | 1| " hello world! ".strip(' ') .should == "hello world!"; 1| " hello world!!! ".strip(" !").should == "hello world"; |} source/mir/string.d is 100% covered <<<<<< EOF # path=source-mir-string_map.lst |/++ |$(H1 Ordered string-value associative array) |Macros: |AlgebraicREF = $(GREF_ALTTEXT mir-core, $(TT $1), $1, mir, algebraic)$(NBSP) |+/ | |module mir.string_map; | |import std.traits; |import mir.internal.meta: basicElementType; | |/++ |Checks if the type is instance of $(LREF StringMap). |+/ |enum isStringMap(T) = is(Unqual!T == StringMap!V, V); | |version(mir_test) |/// |unittest |{ | static assert(isStringMap!(StringMap!int)); | static assert(isStringMap!(const StringMap!int)); | static assert(!isStringMap!int); |} | |private alias U = uint; | |/++ |Ordered string-value associative array with extremely fast lookup. | |Params: | T = mutable value type, can be instance of $(AlgebraicREF Algebraic) for example. | U = an unsigned type that can hold an index of keys. `U.max` must be less then the maximum possible number of struct members. |+/ |struct StringMap(T) | // if (!is(typeof(T.opPostMove))) |{ | import mir.utility: _expect; | import core.lifetime: move; | import mir.conv: emplaceRef; | | /// | static struct KeyValue | { | /// | string key; | /// | T value; | } | | /// `hashOf` Implementation. Doesn't depend on order | size_t toHash() scope const pure @trusted nothrow @nogc | { 0000000| if (implementation is null) 0000000| return 0; 0000000| size_t hash; 0000000| foreach (i, index; implementation.indices) | { 0000000| hash = hashOf(implementation._keys[index], hash); | static if (__traits(hasMember, T, "toHash")) 0000000| hash = hashOf(implementation._values[index].toHash, hash); | else 0000000| hash = hashOf(implementation._values[index], hash); | } 0000000| return hash; | } | | /// `==` implementation. Doesn't depend on order | // current implementation is workaround for linking bugs when used in self referencing algebraic types | bool opEquals(V)(scope const StringMap!V rhs) scope const @trusted pure @nogc nothrow | { | import std.traits: isAggregateType; | // NOTE: moving this to template restriction fails with recursive template instanation 14| if (implementation is null) 3| return rhs.length == 0; 11| if (rhs.implementation is null) 3| return length == 0; 8| if (implementation._length != rhs.implementation._length) 2| return false; 54| foreach (const i, const index; implementation.indices) 9| if (implementation._keys[index] != rhs.implementation._keys[rhs.implementation._indices[i]] || 9| implementation._values[index] != rhs.implementation._values[rhs.implementation._indices[i]]) 0000000| return false; 6| return true; | } | | /// ditto | bool opEquals(K, V)(scope const const(V)[const(K)] rhs) scope const | if (is(typeof(K.init == string.init) : bool) && | is(typeof(V.init == T.init) : bool)) | { 18| if (implementation is null) 8| return rhs.length == 0; 10| if (implementation._length != rhs.length) 8| return false; 24| foreach (const i; 0 .. implementation._length) | { 6| if (const valuePtr = implementation.keys[i] in rhs) | { 6| if (*valuePtr != implementation.values[i]) 0000000| return false; | } | else 0000000| return false; | } 2| return true; | } | | // // linking bug | // version(none) | // { | // /++ | // +/ | // bool opEquals()(typeof(null)) @safe pure nothrow @nogc const | // { | // return implementation is null; | // } | | // version(mir_test) static if (is(T == int)) | // /// | // @safe pure unittest | // { | // StringMap!int map; | // assert(map == null); | // map = StringMap!int(["key" : 1]); | // assert(map != null); | // map.remove("key"); | // assert(map != null); | // } | // } | | /++ | Reset the associative array | +/ | ref opAssign()(typeof(null)) return @safe pure nothrow @nogc | { 1| implementation = null; 1| return this; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!int map = ["key" : 1]; 1| map = null; | } | | /++ | Initialize the associative array with default value. | +/ 1| this()(typeof(null) aa) @safe pure nothrow @nogc | { 1| implementation = null; | } | | version(mir_test) static if (is(T == int)) | /// Usefull for default funcion argument. | @safe pure unittest | { 1| StringMap!int map = null; // | } | | /++ | Constructs an associative array using keys and values from the builtin associative array | Complexity: `O(n log(n))` | +/ 22| this()(T[string] aa) @trusted pure nothrow | { 22| this(aa.keys, aa.values); | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!int map = ["key" : 1]; 1| assert(map.findPos("key") == 0); | } | | /// | string toString()() const scope | { | import mir.format: stringBuf; | stringBuf buffer; | toString(buffer); | return buffer.data.idup; | } | | ///ditto | void toString(W)(ref scope W w) const scope | { | bool next; | w.put('['); | import mir.format: printEscaped, EscapeFormat, print; | foreach (i, ref value; values) | { | if (next) | w.put(`, `); | next = true; | w.put('\"'); | printEscaped!(char, EscapeFormat.ion)(w, keys[i]); | w.put(`": `); | print(w, value); | } | w.put(']'); | } | | /++ | Constructs an associative array using keys and values. | Params: | keys = mutable array of keys | values = mutable array of values | Key and value arrays must have the same length. | | Complexity: `O(n log(n))` | +/ 23| this()(string[] keys, T[] values) @trusted pure nothrow | { 23| assert(keys.length == values.length); 23| implementation = new Impl(keys, values); | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| auto keys = ["ba", "a"]; 1| auto values = [1.0, 3.0]; 1| auto map = StringMap!double(keys, values); 1| assert(map.keys is keys); 1| assert(map.values is values); | } | | /++ | Returns: number of elements in the table. | +/ | size_t length()() @safe pure nothrow @nogc const @property | { 185| return implementation ? implementation.length : 0; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!double map; 1| assert(map.length == 0); 1| map["a"] = 3.0; 1| assert(map.length == 1); 1| map["c"] = 4.0; 1| assert(map.length == 2); 1| assert(map.remove("c")); 1| assert(map.length == 1); 1| assert(!map.remove("c")); 1| assert(map.length == 1); 1| assert(map.remove("a")); 1| assert(map.length == 0); | } | | /++ | Returns a dynamic array, the elements of which are the keys in the associative array. | Doesn't allocate a new copy. | | The keys returned are guaranteed to be in the ordered inserted as long as no | key removals followed by at least one key insertion has been performed. | | Complexity: `O(1)` | +/ | const(string)[] keys()() @safe pure nothrow @nogc const @property | { 21| return implementation ? implementation.keys : null; | } | /// | alias byKey = keys; | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!double map; 1| assert(map.keys == []); 1| map["c"] = 4.0; 1| assert(map.keys == ["c"]); 1| map["a"] = 3.0; 1| assert(map.keys == ["c", "a"]); 1| map.remove("c"); 1| assert(map.keys == ["a"]); 1| map.remove("a"); 1| assert(map.keys == []); 1| map["c"] = 4.0; 1| assert(map.keys == ["c"]); | } | | /++ | Returns a dynamic array, the elements of which are the values in the associative array. | Doesn't allocate a new copy. | | The values returned are guaranteed to be in the ordered inserted as long as no | key removals followed by at least one key insertion has been performed. | | Complexity: `O(1)` | +/ | inout(T)[] values()() @safe pure nothrow @nogc inout @property | { 23| return implementation ? implementation.values : null; | } | | /// ditto | alias byValue = values; | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!double map; 1| assert(map.byKeyValue == StringMap!double.KeyValue[].init); 1| map["c"] = 4.0; 1| map["a"] = 3.0; 1| assert(map.byKeyValue == [StringMap!double.KeyValue("c", 4.0), StringMap!double.KeyValue("a", 3.0)]); | } | | /** Return a range over all elements (key-values pairs) currently stored in the associative array. | | The elements returned are guaranteed to be in the ordered inserted as | long as no key removals nor no value mutations has been performed. | */ | auto byKeyValue(this This)() @trusted pure nothrow @nogc | { | import mir.ndslice.topology: zip, map; 2| return keys.zip(values).map!KeyValue; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!double map; 1| assert(map.values == []); 1| map["c"] = 4.0; 1| assert(map.values == [4.0]); 1| map["a"] = 3.0; 1| assert(map.values == [4.0, 3.0]); 1| map.values[0]++; 1| assert(map.values == [5.0, 3.0]); 1| map.remove("c"); 1| assert(map.values == [3.0]); 1| map.remove("a"); 1| assert(map.values == []); 1| map["c"] = 4.0; 1| assert(map.values == [4.0]); | } | | /++ | (Property) Gets the current capacity of an associative array. | The capacity is the size that the underlaynig slices can grow to before the underlying arrays may be reallocated or extended. | | Complexity: `O(1)` | +/ | size_t capacity()() @safe pure nothrow const @property | { | import mir.utility: min; | 15| return !implementation ? 0 : min( | implementation.keys.capacity, | implementation.values.capacity, | implementation.indices.capacity, | ); | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { 1| StringMap!double map; 1| assert(map.capacity == 0); 1| map["c"] = 4.0; 1| assert(map.capacity >= 1); 1| map["a"] = 3.0; 1| assert(map.capacity >= 2); 1| map.remove("c"); 1| map.assumeSafeAppend; 1| assert(map.capacity >= 2); | } | | /++ | Reserves capacity for an associative array. | The capacity is the size that the underlaying slices can grow to before the underlying arrays may be reallocated or extended. | +/ | size_t reserve()(size_t newcapacity) @trusted pure nothrow | { | import mir.utility: min; | 2| if (_expect(!implementation, false)) | { 1| implementation = new Impl; | } | 2| auto keysV = implementation.keys; 2| auto keysVCaacity = keysV.reserve(newcapacity); 2| implementation._keys = keysV.ptr; | 2| auto valuesV = implementation.values; 2| auto valuesCapacity = valuesV.reserve(newcapacity); 2| implementation._values = valuesV.ptr; | 2| auto indicesV = implementation.indices; 2| auto indicesCapacity = indicesV.reserve(newcapacity); 2| implementation._indices = indicesV.ptr; | 2| return min( | keysVCaacity, | valuesCapacity, | indicesCapacity, | ); | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { 1| StringMap!double map; 1| auto capacity = map.reserve(10); 1| assert(capacity >= 10); 1| assert(map.capacity == capacity); 1| map["c"] = 4.0; 1| assert(map.capacity == capacity); 1| map["a"] = 3.0; 1| assert(map.capacity >= 2); 1| assert(map.remove("c")); 1| capacity = map.reserve(20); 1| assert(capacity >= 20); 1| assert(map.capacity == capacity); | } | | /++ | Assume that it is safe to append to this associative array. | Appends made to this associative array after calling this function may append in place, even if the array was a slice of a larger array to begin with. | Use this only when it is certain there are no elements in use beyond the associative array in the memory block. If there are, those elements will be overwritten by appending to this associative array. | | Warning: Calling this function, and then using references to data located after the given associative array results in undefined behavior. | | Returns: The input is returned. | +/ | ref inout(typeof(this)) assumeSafeAppend()() nothrow inout return | { 4| if (implementation) | { 4| implementation.keys.assumeSafeAppend; 4| implementation.values.assumeSafeAppend; 4| implementation.indices.assumeSafeAppend; | } 4| return this; | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { 1| StringMap!double map; 1| map["c"] = 4.0; 1| map["a"] = 3.0; 1| assert(map.capacity >= 2); 1| map.remove("c"); 1| assert(map.capacity == 0); 1| map.assumeSafeAppend; 1| assert(map.capacity >= 2); | } | | /++ | Removes all remaining keys and values from an associative array. | | Complexity: `O(1)` | +/ | void clear()() @safe pure nothrow @nogc | { 1| if (implementation) | { 1| implementation._length = 0; 1| implementation._lengthTable = implementation._lengthTable[0 .. 0]; | } | | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { 1| StringMap!double map; 1| map["c"] = 4.0; 1| map["a"] = 3.0; 1| map.clear; 1| assert(map.length == 0); 1| assert(map.capacity == 0); 1| map.assumeSafeAppend; 1| assert(map.capacity >= 2); | } | | /++ | `remove(key)` does nothing if the given key does not exist and returns false. If the given key does exist, it removes it from the AA and returns true. | | Complexity: `O(log(s))` (not exist) or `O(n)` (exist), where `s` is the count of the strings with the same length as they key. | +/ | bool remove()(scope const(char)[] key) @trusted pure nothrow @nogc | { 17| size_t index; 34| if (implementation && implementation.findIndex(key, index)) | { 15| implementation.removeAt(index); 15| return true; | } 2| return false; | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { 1| StringMap!double map; 1| map["a"] = 3.0; 1| map["c"] = 4.0; 1| assert(map.remove("c")); 1| assert(!map.remove("c")); 1| assert(map.remove("a")); 1| assert(map.length == 0); 1| assert(map.capacity == 0); 1| assert(map.assumeSafeAppend.capacity >= 2); | } | | /++ | Finds position of the key in the associative array . | | Return: An index starting from `0` that corresponds to the key or `-1` if the associative array doesn't contain the key. | | Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. | +/ | ptrdiff_t findPos()(scope const(char)[] key) @trusted pure nothrow @nogc const | { 8| if (!implementation) 0000000| return -1; 8| size_t index; 8| if (!implementation.findIndex(key, index)) 2| return -1; 6| return implementation._indices[index]; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!double map; 1| map["c"] = 3.0; 1| map["La"] = 4.0; 1| map["a"] = 5.0; | 1| assert(map.findPos("C") == -1); 1| assert(map.findPos("c") == 0); 1| assert(map.findPos("La") == 1); 1| assert(map.findPos("a") == 2); | 1| map.remove("c"); | 1| assert(map.findPos("c") == -1); 1| assert(map.findPos("La") == 0); 1| assert(map.findPos("a") == 1); | } | | /++ | Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. | +/ | inout(T)* opBinaryRight(string op : "in")(scope const(char)[] key) pure nothrow @nogc inout | { 3| if (!implementation) 1| return null; 2| size_t index; 2| if (!implementation.findIndex(key, index)) 0000000| return null; 2| assert (index < length); 2| index = implementation.indices[index]; 2| assert (index < length); 2| return &implementation.values[index]; | } | | version(mir_test) static if (is(T == int)) | /// | @safe nothrow pure unittest | { 1| StringMap!double map; 1| assert(("c" in map) is null); 1| map["c"] = 3.0; 1| assert(*("c" in map) == 3.0); | } | | /++ | Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. | +/ | ref inout(T) opIndex()(scope const(char)[] key) @trusted pure inout //@nogc | { 20| size_t index; 40| if (implementation && implementation.findIndex(key, index)) | { 20| assert (index < length); 20| index = implementation._indices[index]; 20| assert (index < length); 20| return implementation._values[index]; | } | import mir.exception: MirException; 0000000| throw new MirException("No member: ", key); | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!double map; 1| map["c"] = 3.0; 1| map["La"] = 4.0; 1| map["a"] = 5.0; | 1| map["La"] += 10; 1| assert(map["La"] == 14.0); | } | | /++ | Complexity: `O(log(s))` (exist) or `O(n)` (not exist), where `s` is the count of the strings with the same length as they key. | +/ | ref T opIndexAssign(R)(auto ref R value, string key) @trusted pure nothrow | { | import core.lifetime: forward, move; 17| T v; 17| v = forward!value; 17| return opIndexAssign(move(v), key); | } | | /// ditto | ref T opIndexAssign()(T value, string key) @trusted pure nothrow | { 63| size_t index; 63| if (_expect(!implementation, false)) | { 21| implementation = new Impl; | } | else | { 42| if (key.length + 1 < implementation.lengthTable.length) | { 30| if (implementation.findIndex(key, index)) | { 2| assert (index < length); 2| index = implementation._indices[index]; 2| assert (index < length); 2| move(cast()value, cast()(implementation._values[index])); 2| return implementation._values[index]; | } 28| assert (index <= length); | } | else | { 12| index = length; | } | } 61| assert (index <= length); 61| implementation.insertAt(key, move(cast()value), index); 61| index = implementation._indices[index]; 61| return implementation._values[index]; | } | | /++ | Looks up key; if it exists returns corresponding value else evaluates and returns defaultValue. | | Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. | +/ | inout(T) get()(scope const(char)[] key, lazy inout(T) defaultValue) | { 2| size_t index; 4| if (implementation && implementation.findIndex(key, index)) | { 1| assert (index < length); 1| index = implementation.indices[index]; 1| assert (index < length); 1| return implementation.values[index]; | } 1| return defaultValue; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!int map; 1| map["c"] = 3; 1| assert(map.get("c", 1) == 3); 2| assert(map.get("C", 1) == 1); | } | | /++ | Looks up key; if it exists returns corresponding value else evaluates value, adds it to the associative array and returns it. | | Complexity: `O(log(s))` (exist) or `O(n)` (not exist), where `s` is the count of the strings with the same length as they key. | +/ | ref T require()(string key, lazy T value = T.init) | { 5| size_t index; 5| if (_expect(!implementation, false)) | { 0000000| implementation = new Impl; | } | else | { 5| if (key.length + 1 < implementation.lengthTable.length) | { 5| if (implementation.findIndex(key, index)) | { 3| assert (index < length); 3| index = implementation.indices[index]; 3| assert (index < length); 3| return implementation.values[index]; | } 2| assert (index <= length); | } | else | { 0000000| index = length; | } | } 2| assert (index <= length); 2| implementation.insertAt(key, value, index); 2| index = implementation.indices[index]; 2| return implementation.values[index]; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { 1| StringMap!int map = ["aa": 1]; 4| int add3(ref int x) { x += 3; return x; } 1| assert(add3(map.require("aa", 10)) == 4); 2| assert(add3(map.require("bb", 10)) == 13); 2| assert(map.require("a", 100)); 1| assert(map.require("aa") == 4); 1| assert(map.require("bb") == 13); 1| assert(map.keys == ["aa", "bb", "a"]); | } | | /++ | Converts to a builtin associative array. | | Complexity: `O(n)`. | +/ | template toAA() | { | static if (__traits(compiles, (ref const T a) { T b; b = a;})) | { | /// | T[string] toAA()() const | { 9| T[string] ret; 78| foreach (i; 0 .. length) | { 17| ret[implementation.keys[i]] = implementation.values[i]; | } 9| return ret; | } | } | else | { | /// | T[string] toAA()() | { | T[string] ret; | foreach (i; 0 .. length) | { | ret[implementation.keys[i]] = implementation.values[i]; | } | return ret; | } | | /// | const(T)[string] toAA()() const | { | const(T)[string] ret; | foreach (i; 0 .. length) | { | ret[implementation.keys[i]] = implementation.values[i]; | } | return ret; | } | } | } | | /// | @safe pure nothrow unittest | { 7| StringMap!int map = ["k": 1]; 7| int[string] aa = map.toAA; 7| assert(aa["k"] == 1); | } | | private static struct Impl | { | import core.lifetime: move; | import mir.conv: emplaceRef; | | size_t _length; | string* _keys; | T* _values; | U* _indices; | U[] _lengthTable; | | /++ | +/ 23| this()(string[] keys, T[] values) @trusted pure nothrow | { 23| assert(keys.length == values.length); 23| if (keys.length == 0) 0000000| return; 23| _length = keys.length; 23| _keys = keys.ptr; 23| _values = values.ptr; 23| _indices = new U[keys.length].ptr; 23| size_t maxKeyLength; 171| foreach(ref key; keys) 34| if (key.length > maxKeyLength) 27| maxKeyLength = key.length; 23| _lengthTable = new U[maxKeyLength + 2]; 23| sortIndices(); | } | | private void sortIndices() pure nothrow | { | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: indexed; | import mir.string_table: smallerStringFirst; 220| foreach (i, ref index; indices) 37| index = cast(U)i; | 41| indices.sort!((a, b) => smallerStringFirst(keys[a], keys[b])); 24| auto sortedKeys = _keys.indexed(indices); 24| size_t maxKeyLength = sortedKeys[$ - 1].length; | 24| size_t ski; 324| foreach (length; 0 .. maxKeyLength + 1) | { 218| while(ski < sortedKeys.length && sortedKeys[ski].length == length) 37| ski++; 84| _lengthTable[length + 1] = cast(U)ski; | } | } | | void insertAt()(string key, T value, size_t i) @trusted | { | pragma(inline, false); | 63| assert(i <= length); | { 63| auto a = keys; 63| a ~= key; 63| _keys = a.ptr; | } | { 63| auto a = values; 63| a ~= move(cast()value); 63| _values = a.ptr; | } | { 63| auto a = indices; 63| a ~= 0; 63| _indices = a.ptr; | 63| if (__ctfe) | { | foreach_reverse (idx; i .. length) | { | _indices[idx + 1] = _indices[idx]; | } | } | else | { | import core.stdc.string: memmove; 63| memmove(_indices + i + 1, _indices + i, (length - i) * U.sizeof); | } 63| assert(length <= U.max); 63| _indices[i] = cast(U)length; 63| _length++; | } | { 63| if (key.length + 2 <= lengthTable.length) | { 30| ++lengthTable[key.length + 1 .. $]; | } | else | { 33| auto oldLen = _lengthTable.length; 33| _lengthTable.length = key.length + 2; 33| auto oldVal = oldLen ? _lengthTable[oldLen - 1] : 0; 33| _lengthTable[oldLen .. key.length + 1] = oldVal; 33| _lengthTable[key.length + 1] = oldVal + 1; | } | } | } | | void removeAt()(size_t i) | { 15| assert(i < length); 15| auto j = _indices[i]; 15| assert(j < length); | { 15| --_lengthTable[_keys[j].length + 1 .. $]; | } | { 15| if (__ctfe) | { | foreach (idx; i .. length) | { | _indices[idx] = _indices[idx + 1]; | _indices[idx] = _indices[idx + 1]; | } | } | else | { | import core.stdc.string: memmove; 15| memmove(_indices + i, _indices + i + 1, (length - 1 - i) * U.sizeof); | } 108| foreach (ref elem; indices[0 .. $ - 1]) 21| if (elem > j) 18| elem--; | } | { 15| if (__ctfe) | { | foreach_reverse (idx; j .. length - 1) | { | _keys[idx] = _keys[idx + 1]; | _values[idx] = move(_values[idx + 1]); | } | } | else | { | import core.stdc.string: memmove; 15| destroy!false(_values[j]); 15| memmove(_keys + j, _keys + j + 1, (length - 1 - j) * string.sizeof); 15| memmove(_values + j, _values + j + 1, (length - 1 - j) * T.sizeof); 15| emplaceRef(_values[length - 1]); | } | } 15| _length--; 15| _lengthTable = _lengthTable[0 .. length ? _keys[_indices[length - 1]].length + 2 : 0]; | } | | size_t length()() @safe pure nothrow @nogc const @property | { 550| return _length; | } | | inout(string)[] keys()() @trusted inout @property | { 160| return _keys[0 .. _length]; | } | | inout(T)[] values()() @trusted inout @property | { 136| return _values[0 .. _length]; | } | | inout(U)[] indices()() @trusted inout @property | { 184| return _indices[0 .. _length]; | } | | inout(U)[] lengthTable()() @trusted inout @property | { 140| return _lengthTable; | } | | auto sortedKeys()() @trusted const @property | { | import mir.ndslice.topology: indexed; | return _keys.indexed(indices); | } | | bool findIndex()(scope const(char)[] key, ref size_t index) @trusted pure nothrow @nogc const | { | import mir.utility: _expect; 84| if (_expect(key.length + 1 < _lengthTable.length, true)) | { | | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 0 1 2 3 4 5 6 8 9 10 12 16 | 84| auto low = _lengthTable[key.length] + 0u; 84| auto high = _lengthTable[key.length + 1] + 0u; 129| while (low < high) | { 94| const mid = (low + high) / 2; | | import core.stdc.string: memcmp; 94| int r = void; | 94| if (__ctfe) | r = __cmp(key, _keys[_indices[mid]]); | else 94| r = memcmp(key.ptr, _keys[_indices[mid]].ptr, key.length); | 94| if (r == 0) | { 49| index = mid; 49| return true; | } 45| if (r > 0) 10| low = mid + 1; | else 35| high = mid; | } 35| index = low; | } 35| return false; | } | } | | /// Sorts table according to the keys | ref sort(alias less = "a < b")() return | { | import mir.functional: naryFun; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: zip; 1| if (length) { 4| zip(implementation.keys, implementation.values).sort!((l, r) => naryFun!less(l.a, r.a)); 1| implementation.sortIndices; | } 1| return this; | } | | import std.traits: isAssociativeArray, isAggregateType; | static if (!isAssociativeArray!(.basicElementType!T) && (!isAggregateType!(.basicElementType!T) || __traits(hasMember, .basicElementType!T, "opCmp"))) | /// `opCmp` Implementation. Doesn't depend on order | int opCmp()(ref scope const typeof(this) rhs) scope const @trusted // pure nothrow @nogc | { 0000000| if (sizediff_t d = length - rhs.length) 0000000| return d < 0 ? -1 : 1; 0000000| if (length == 0) 0000000| return 0; | 0000000| foreach (i, index; implementation.indices) 0000000| if (auto d = __cmp(implementation._keys[index], rhs.implementation._keys[rhs.implementation._indices[i]])) 0000000| return d; 0000000| foreach (i, index; implementation.indices) | static if (__traits(compiles, __cmp(implementation._values[index], rhs.implementation._values[rhs.implementation._indices[i]]))) | { | if (auto d = __cmp(implementation._values[index], rhs.implementation._values[rhs.implementation._indices[i]])) | return d; | } | else | static if (__traits(hasMember, T, "opCmp")) | { 0000000| if (auto d = implementation._values[index].opCmp(rhs.implementation._values[rhs.implementation._indices[i]])) 0000000| return d; | } | else | { 0000000| return | implementation._values[index] < rhs.implementation._values[rhs.implementation._indices[i]] ? -1 : | implementation._values[index] > rhs.implementation._values[rhs.implementation._indices[i]] ? +1 : 0; | } 0000000| return false; | } | | private Impl* implementation; | | /// | static if (is(T == const) || is(T == immutable)) | { | alias serdeKeysProxy = Unqual!T; | } | else | { | alias serdeKeysProxy = T; | } |} | |version(mir_test) |/// |@safe unittest |{ | class C | { 4| this(int x) { this.x = x; } | int x; | bool opEquals(scope const C rhs) const scope @safe pure nothrow @nogc | { 0000000| return x == rhs.x; | } | | override size_t toHash() @safe const scope pure nothrow @nogc | { 0000000| return x; | } | } 1| StringMap!(const C) table; 1| const v0 = new C(42); 1| const v1 = new C(43); 1| table["0"] = v0; 1| table["1"] = v1; 1| assert(table.keys == ["0", "1"]); | static if (__VERSION__ > 2098) // See https://github.com/libmir/mir-algorithm/runs/6809888795?check_suite_focus=true#step:5:17 | { 1| assert(table.values == [v0, v1]); // TODO: qualify unittest as `pure` when this is inferred `pure` | } | static assert(is(typeof(table.values) == const(C)[])); |} | |version(mir_test) |/// |@safe pure unittest |{ 1| StringMap!int table; 1| table["L"] = 3; 1| table["A"] = 2; 1| table["val"] = 1; 1| assert(table.keys == ["L", "A", "val"]); 1| assert(table.values == [3, 2, 1]); 1| assert(table["A"] == 2); 1| table.values[2] += 10; 1| assert(table["A"] == 2); 1| assert(table["L"] == 3); 1| assert(table["val"] == 11); 1| assert(table.keys == ["L", "A", "val"]); 1| assert(table.values == [3, 2, 11]); 1| table.remove("A"); 1| assert(table.keys == ["L", "val"]); 1| assert(table.values == [3, 11]); 1| assert(table["L"] == 3); 1| assert(table["val"] == 11); | 1| assert(table == table); | | // sorting 1| table["A"] = 2; 1| table.sort; 1| assert(table.keys == ["A", "L", "val"]); 1| assert(table.values == [2, 3, 11]); 1| assert(table["A"] == 2); 1| assert(table["L"] == 3); 1| assert(table["val"] == 11); |} | |version(mir_test) |/// |@safe pure unittest |{ | static void testEquals(X, Y)() | { 3| X x; 3| Y y; 3| assert(x == y); | 3| x["L"] = 3; 3| assert(x != y); 3| x["A"] = 2; 3| assert(x != y); 3| x["val"] = 1; 3| assert(x != y); | 3| y["val"] = 1; 3| assert(x != y); 3| y["L"] = 3; 3| assert(x != y); 3| y["A"] = 2; 3| assert(x == y); | 3| x = X.init; 3| assert(x != y); | 3| y = Y.init; 3| assert(x == y); | } | 1| testEquals!(StringMap!int, StringMap!uint)(); 1| testEquals!(StringMap!int, uint[string])(); 1| testEquals!(uint[string], StringMap!int)(); |} | |version(mir_test) |@safe pure unittest |{ | import mir.algebraic_alias.json: JsonAlgebraic; | import mir.string_map: StringMap; | 1| StringMap!JsonAlgebraic token; 1| token[`access_token`] = "secret-data"; 1| token[`expires_in`] = 3599; 1| token[`token_type`] = "Bearer"; | 1| assert(token[`access_token`] == "secret-data"); 1| assert(token[`expires_in`] == 3599); 1| assert(token[`token_type`] == "Bearer"); // mir/string_map.d(511): No member: token_type | 1| const tkType = `token_type` in token; | 1| assert((*tkType) == "Bearer"); // *tkType contains value 3599 |} source/mir/string_map.d is 92% covered <<<<<< EOF # path=source-mir-test.lst |/++ |Testing utilities | |Authors: Ilya Yaroshenko |+/ |module mir.test; | |import mir.exception: MirError; | |private noreturn assumeAllAttrAndCall(scope const void delegate() t) | @nogc pure nothrow @trusted { 0000000| (cast(const void delegate() @safe pure nothrow @nogc) t)(); | assert(0); |} | |/// |struct ShouldApprox(T) | if (__traits(isFloating, T)) |{ | /// | T value; | /// | T maxRelDiff = 0x1p-20f; | /// | T maxAbsDiff = 0x1p-20f; | | /// | void opEquals(T expected, string file = __FILE__, int line = __LINE__) @safe pure nothrow @nogc | { | import mir.format: stringBuf, getData; | import mir.math.common: approxEqual; 23| if (value.approxEqual(expected, maxRelDiff, maxAbsDiff)) 23| return; 0000000| auto buf = stringBuf; 0000000| assumeAllAttrAndCall({ 0000000| throw new MirError(buf | << "expected approximately " << expected | << ", got " << value | << ", maxRelDiff = " << maxRelDiff | << ", maxAbsDiff = " << maxAbsDiff | << getData, file, line); | }); | assert(0); | } |} | |/// ditto |ShouldApprox!T shouldApprox(T)(const T value, const T maxRelDiff = T(0x1p-20f), const T maxAbsDiff = T(0x1p-20f)) | if (__traits(isFloating, T)) |{ 23| return typeof(return)(value, maxRelDiff, maxAbsDiff); |} | |/// |version(mir_test) |unittest |{ 1| 1.0.shouldApprox == 1 + 9e-7; 1| shouldApprox(1 + 9e-7, 1e-6, 1e-6) == 1; |} | |/// |struct Should(T) |{ | /// | T value; | | static if(!is(immutable T == immutable ubyte[])) | /// | void opEquals(R)(const R expected, string file = __FILE__, int line = __LINE__) @trusted | { | import mir.format: stringBuf, getData; | static if (__traits(isFloating, R)) | { 219| if (expected != expected && value != value) 3| return; | } 391| if (value == expected) 391| return; 0000000| auto buf = stringBuf; 0000000| buf << "mir.test.should:\n"; 0000000| buf << "expected " << expected << "\n" | << " got " << value; 0000000| assumeAllAttrAndCall({ throw new MirError(buf << getData, file, line); }); | } | else | /// ditto | void opEquals(scope const ubyte[] expected, string file = __FILE__, int line = __LINE__) @trusted | pure nothrow @nogc | { | import mir.format: stringBuf, getData; 1| if (value == expected) 1| return; 0000000| auto buf = stringBuf; | import mir.format: printHexArray; | import mir.ndslice.topology: map; 0000000| buf << "mir.test.should:\n"; 0000000| buf << "expected "; 0000000| buf.printHexArray(expected); 0000000| buf << "\n"; 0000000| buf << " got "; 0000000| buf.printHexArray( value); 0000000| assumeAllAttrAndCall({ throw new MirError(buf << getData, file, line); }); | } |} | |/// ditto |Should!T should(T)(T value) |{ 395| return typeof(return)(value); |} | |/// |version(mir_test) |unittest |{ 1| 1.0.should == 1; 1| should(1) == 1; | 1| ubyte[] val = [0, 2, 3]; 1| val.should == [0, 2, 3]; |} | |/// |void should(alias fun = "a == b", T, R)(const T value, const R expected, string file = __FILE__, int line = __LINE__) |{ | import mir.functional; | import mir.format: stringBuf, getData; 1| if (naryFun!fun(value, expected)) 1| return; 0000000| auto buf = stringBuf; 0000000| buf << fun.stringof | << " returns false" | << " for a = " << value | << ", b = " << expected; 0000000| throw new MirError(buf << getData, file, line); |} | |/// |version(mir_test) |unittest |{ 1| 1.0.should!"a < b"(1.3); |} source/mir/test.d is 50% covered <<<<<< EOF # path=source-mir-timestamp.lst |/++ |Timestamp |+/ |module mir.timestamp; | 439|private alias isDigit = (dchar c) => uint(c - '0') < 10; |import mir.serde: serdeIgnore, serdeRegister; | |version(D_Exceptions) |/// |class DateTimeException : Exception |{ | /// 0000000| @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) | { 0000000| super(msg, file, line, nextInChain); | } | | /// ditto 0000000| @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) | { 0000000| super(msg, file, line, nextInChain); | } |} | |version(D_Exceptions) |{ | private static immutable InvalidMonth = new DateTimeException("Timestamp: Invalid Month"); | private static immutable InvalidDay = new DateTimeException("Timestamp: Invalid Day"); | private static immutable InvalidISOString = new DateTimeException("Timestamp: Invalid ISO String"); | private static immutable InvalidISOExtendedString = new DateTimeException("Timestamp: Invalid ISO Extended String"); | private static immutable InvalidYamlString = new DateTimeException("Timestamp: Invalid YAML String"); | private static immutable InvalidString = new DateTimeException("Timestamp: Invalid String"); | private static immutable ExpectedDuration = new DateTimeException("Timestamp: Expected Duration"); |} | |/++ |Timestamp | |Note: The component values in the binary encoding are always in UTC, while components in the text encoding are in the local time! |This means that transcoding requires a conversion between UTC and local time. | |`Timestamp` precision is up to picosecond (second/10^12). |+/ |@serdeRegister |struct Timestamp |{ | import std.traits: isSomeChar; | | /// | enum Precision : ubyte | { | /// | year, | /// | month, | /// | day, | /// | minute, | /// | second, | /// | fraction, | } | |@serdeIgnore: | | /// 15| this(scope const(char)[] str) @safe pure @nogc | { 15| this = fromString(str); | } | | /// | version (mir_test) | @safe pure @nogc unittest | { 1| assert(Timestamp("2010-07-04") == Timestamp(2010, 7, 4)); 1| assert(Timestamp("20100704") == Timestamp(2010, 7, 4)); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730")); | static assert(Timestamp(2021, 01, 29, 4, 42, 44).withOffset(- (7 * 60 + 30)) == Timestamp.fromISOExtString("2021-01-28T21:12:44-07:30")); | 1| assert(Timestamp("T0740Z") == Timestamp.onlyTime(7, 40)); 1| assert(Timestamp("T074030Z") == Timestamp.onlyTime(7, 40, 30)); 1| assert(Timestamp("T074030.056Z") == Timestamp.onlyTime(7, 40, 30, -3, 56)); | 1| assert(Timestamp("07:40Z") == Timestamp.onlyTime(7, 40)); 1| assert(Timestamp("07:40:30Z") == Timestamp.onlyTime(7, 40, 30)); 1| assert(Timestamp("T07:40:30.056Z") == Timestamp.onlyTime(7, 40, 30, -3, 56)); | } | | version(all) | { | short offset; | } | else | /+ | If the time in UTC is known, but the offset to local time is unknown, this can be represented with an offset of “-00:00”. | This differs semantically from an offset of “Z” or “+00:00”, which imply that UTC is the preferred reference point for the specified time. | RFC2822 describes a similar convention for email. | private short _offset; | +/ | { | | /++ | Timezone offset in minutes | +/ | short offset() const @safe pure nothrow @nogc @property | { | return _offset >> 1; | } | | // /++ | // Returns: true if timezone has offset | // +/ | // bool hasOffset() const @safe pure nothrow @nogc @property | // { | // return _offset & 1; | // } | } | | /++ | Year | +/ | short year; | /++ | +/ | Precision precision; | | /++ | Month | | If the value equals to thero then this and all the following members are undefined. | +/ | ubyte month; | /++ | Day | | If the value equals to thero then this and all the following members are undefined. | +/ | ubyte day; | /++ | Hour | +/ | ubyte hour; | | version(D_Ddoc) | { | | /++ | Minute | | Note: the field is implemented as property. | +/ | ubyte minute; | /++ | Second | | Note: the field is implemented as property. | +/ | ubyte second; | /++ | Fraction | | The `fractionExponent` and `fractionCoefficient` denote the fractional seconds of the timestamp as a decimal value | The fractional seconds’ value is `coefficient * 10 ^ exponent`. | It must be greater than or equal to zero and less than 1. | A missing coefficient defaults to zero. | Fractions whose coefficient is zero and exponent is greater than -1 are ignored. | | 'fractionCoefficient' allowed values are [0 ... 10^12-1]. | 'fractionExponent' allowed values are [-12 ... 0]. | | Note: the fields are implemented as property. | +/ | byte fractionExponent; | /// ditto | ulong fractionCoefficient; | } | else | { | import mir.bitmanip: bitfields; | version (LittleEndian) | { | | mixin(bitfields!( | ubyte, "minute", 8, | ubyte, "second", 8, | byte, "fractionExponent", 8, | ulong, "fractionCoefficient", 40, | )); | } | else | { | mixin(bitfields!( | ulong, "fractionCoefficient", 40, | byte, "fractionExponent", 8, | ubyte, "second", 8, | ubyte, "minute", 8, | )); | } | } | | /// | @safe pure nothrow @nogc 8| this(short year) | { 8| this.year = year; 8| this.precision = Precision.year; | } | | /// | @safe pure nothrow @nogc 8| this(short year, ubyte month) | { 8| this.year = year; 8| this.month = month; 8| this.precision = Precision.month; | } | | /// | @safe pure nothrow @nogc 65| this(short year, ubyte month, ubyte day) | { 65| this.year = year; 65| this.month = month; 65| this.day = day; 65| this.precision = Precision.day; | } | | /// | @safe pure nothrow @nogc 11| this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute) | { 11| this.year = year; 11| this.month = month; 11| this.day = day; 11| this.hour = hour; 11| this.minute = minute; 11| this.precision = Precision.minute; | } | | /// | @safe pure nothrow @nogc 32| this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute, ubyte second) | { 32| this.year = year; 32| this.month = month; 32| this.day = day; 32| this.hour = hour; 32| this.day = day; 32| this.minute = minute; 32| this.second = second; 32| this.precision = Precision.second; | } | | /// | @safe pure nothrow @nogc 7| this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute, ubyte second, byte fractionExponent, ulong fractionCoefficient) | { 7| this.year = year; 7| this.month = month; 7| this.day = day; 7| this.hour = hour; 7| this.day = day; 7| this.minute = minute; 7| this.second = second; 7| assert(fractionExponent < 0); 7| this.fractionExponent = fractionExponent; 7| this.fractionCoefficient = fractionCoefficient; 7| this.precision = Precision.fraction; | } | | /// | @safe pure nothrow @nogc | static Timestamp onlyTime(ubyte hour, ubyte minute) | { 5| return Timestamp(0, 0, 0, hour, minute); | } | | /// | @safe pure nothrow @nogc | static Timestamp onlyTime(ubyte hour, ubyte minute, ubyte second) | { 7| return Timestamp(0, 0, 0, hour, minute, second); | } | | /// | @safe pure nothrow @nogc | static Timestamp onlyTime(ubyte hour, ubyte minute, ubyte second, byte fractionExponent, ulong fractionCoefficient) | { 5| return Timestamp(0, 0, 0, hour, minute, second, fractionExponent, fractionCoefficient); | } | | /// 2| this(Date)(const Date datetime) | if (Date.stringof == "Date" || Date.stringof == "date") | { | static if (__traits(hasMember, Date, "yearMonthDay")) 2| with(datetime.yearMonthDay) this(year, cast(ubyte)month, day); | else 1| with(datetime) this(year, month, day); | } | | /// | version (mir_test) | @safe unittest { | import mir.date : Date; 1| auto dt = Date(1982, 4, 1); 1| Timestamp ts = dt; 1| assert(ts.opCmp(ts) == 0); 1| assert(dt.toISOExtString == ts.toString); 1| assert(dt == cast(Date) ts); | } | | /// | version (mir_test) | @safe unittest { | import std.datetime.date : Date; 1| auto dt = Date(1982, 4, 1); 1| Timestamp ts = dt; 1| assert(dt.toISOExtString == ts.toString); 1| assert(dt == cast(Date) ts); | } | | /// 1| this(TimeOfDay)(const TimeOfDay timeOfDay) | if (TimeOfDay.stringof == "TimeOfDay") | { 1| with(timeOfDay) this = onlyTime(hour, minute, second); | } | | /// | version (mir_test) | @safe unittest { | import std.datetime.date : TimeOfDay; 1| auto dt = TimeOfDay(7, 14, 30); 1| Timestamp ts = dt; 1| assert(dt.toISOExtString ~ "Z" == ts.toString); 1| assert(dt == cast(TimeOfDay) ts); | } | | /// 1| this(DateTime)(const DateTime datetime) | if (DateTime.stringof == "DateTime") | { 1| with(datetime) this(year, cast(ubyte)month, day, hour, minute, second); | } | | /// | version (mir_test) | @safe unittest { | import std.datetime.date : DateTime; 1| auto dt = DateTime(1982, 4, 1, 20, 59, 22); 1| Timestamp ts = dt; 1| assert(dt.toISOExtString ~ "Z" == ts.toString); 1| assert(dt == cast(DateTime) ts); | } | | /// 2| this(SysTime)(const SysTime systime) | if (SysTime.stringof == "SysTime") | { 2| auto utcTime = systime.toUTC; 2| this = fromUnixTime(utcTime.toUnixTime); 2| this.fractionExponent = -7; 2| this.fractionCoefficient = utcTime.fracSecs.total!"hnsecs"; 2| this.precision = Precision.fraction; 2| this.offset = cast(short) systime.utcOffset.total!"minutes"; | } | | /// | version (mir_test) | @safe unittest { | import core.time : hnsecs, minutes; | import std.datetime.date : DateTime; | import std.datetime.timezone : SimpleTimeZone; | import std.datetime.systime : SysTime; | 1| auto dt = DateTime(1982, 4, 1, 20, 59, 22); 1| auto tz = new immutable SimpleTimeZone(-330.minutes); 1| auto st = SysTime(dt, 1234567.hnsecs, tz); 1| Timestamp ts = st; | 1| assert(st.toISOExtString == ts.toString); 1| assert(st == cast(SysTime) ts); | } | | /++ | Creates a fake timestamp from a Duration using `total!"hnsecs"` method. | For positive and zero timestamps the format is | `wwww-dd-88Thh:mm:ss.nnnnnnn` | and for negative timestamps | `wwww-dd-99Thh:mm:ss.nnnnnnn`. | +/ 3| this(Duration)(const Duration duration) | if (Duration.stringof == "Duration") | { 3| auto hnsecs = duration.total!"hnsecs"; 3| ulong abs = hnsecs < 0 ? -hnsecs : hnsecs; 3| precision = Precision.fraction; 3| day = hnsecs >= 0 ? 88 : 99; 3| fractionExponent = -7; 3| fractionCoefficient = abs % 10_000_000U; 3| abs /= 10_000_000U; 3| second = abs % 60; 3| abs /= 60; 3| minute = abs % 60; 3| abs /= 60; 3| hour = abs % 24; 3| abs /= 24; 3| month = abs % 7; 3| abs /= 7; 3| year = cast(typeof(year)) abs; | } | | /// | version (mir_test) | @safe unittest { | import core.time : Duration, weeks, days, hours, minutes, seconds, hnsecs; | 1| auto duration = 5.weeks + 2.days + 7.hours + 40.minutes + 4.seconds + 9876543.hnsecs; 1| Timestamp ts = duration; | 1| assert(ts.toISOExtString == `0005-02-88T07:40:04.9876543Z`); 1| assert(duration == cast(Duration) ts); | 1| duration = -duration; 1| ts = Timestamp(duration); 1| assert(ts.toISOExtString == `0005-02-99T07:40:04.9876543Z`); 1| assert(duration == cast(Duration) ts); | 1| assert(Timestamp(Duration.zero).toISOExtString == `0000-00-88T00:00:00.0000000Z`); | } | | /++ | Decomposes Timestamp to an algebraic type. | Supported types up to T.stringof equivalence: | | $(UL | $(LI `Year`) | $(LI `YearMonth`) | $(LI `YearMonthDay`) | $(LI `Date`) | $(LI `date`) | $(LI `TimeOfDay`) | $(LI `DateTime`) | $(LI `SysTime`) | $(LI `Timestamp` as fallback type) | ) | | | Throws: an exception if timestamp cannot be converted to an algebraic type and there is no `Timestamp` type in the Algebraic set. | +/ | T opCast(T)() const | if (__traits(hasMember, T, "AllowedTypes")) | { | foreach (AT; T.AllowedTypes) | static if (AT.stringof == "Year") | if (precision == precision.year) | return T(opCast!AT); | | foreach (AT; T.AllowedTypes) | static if (AT.stringof == "YearMonth") | if (precision == precision.month) | return T(opCast!AT); | | foreach (AT; T.AllowedTypes) | static if (AT.stringof == "Duration") 4| if (isDuration) 0000000| return T(opCast!AT); | | foreach (AT; T.AllowedTypes) | static if (AT.stringof == "YearMonthDay" || AT.stringof == "Date" || AT.stringof == "date") 4| if (precision == precision.day) 0000000| return T(opCast!AT); | | foreach (AT; T.AllowedTypes) | static if (AT.stringof == "TimeOfDay") 4| if (isOnlyTime) 1| return T(opCast!AT); | 6| if (!isOnlyTime && precision >= precision.day) | { | foreach (AT; T.AllowedTypes) | static if (AT.stringof == "DateTime") 3| if (offset == 0 && precision <= precision.second) 1| return T(opCast!AT); | | foreach (AT; T.AllowedTypes) | static if (AT.stringof == "SysTime") 1| return T(opCast!AT); | } | | import std.meta: staticIndexOf; | static if (staticIndexOf!(Timestamp, T.AllowedTypes) < 0) | { | static immutable exc = new Exception("Cannot cast Timestamp to " ~ T.stringof); | throw exc; | } | else | { 1| return T(this); | } | } | | /// | version (mir_test) | @safe unittest | { | import core.time : hnsecs, minutes, Duration; | import mir.algebraic; | import mir.date: Date; // Can be other Date type as well | import std.datetime.date : TimeOfDay, DateTime; | import std.datetime.systime : SysTime; | import std.datetime.timezone: UTC, SimpleTimeZone; | | alias A = Variant!(Date, TimeOfDay, DateTime, Duration, SysTime, Timestamp, string); // non-date-time types is OK 1| assert(cast(A) Timestamp(1023) == Timestamp(1023)); // Year isn't represented in the algebraic, use fallback type 1| assert(cast(A) Timestamp.onlyTime(7, 40, 30) == TimeOfDay(7, 40, 30)); 1| assert(cast(A) Timestamp(1982, 4, 1, 20, 59, 22) == DateTime(1982, 4, 1, 20, 59, 22)); | 1| auto dt = DateTime(1982, 4, 1, 20, 59, 22); 1| auto tz = new immutable SimpleTimeZone(-330.minutes); 1| auto st = SysTime(dt, 1234567.hnsecs, tz); 1| assert(cast(A) Timestamp(st) == st); | } | | /++ | Casts timestamp to a date-time type. | | Supported types up to T.stringof equivalence: | | $(UL | $(LI `Year`) | $(LI `YearMonth`) | $(LI `YearMonthDay`) | $(LI `Date`) | $(LI `date`) | $(LI `TimeOfDay`) | $(LI `DateTime`) | $(LI `SysTime`) | ) | +/ | T opCast(T)() const | if ( | T.stringof == "Year" | || T.stringof == "YearMonth" | || T.stringof == "YearMonthDay" | || T.stringof == "Date" | || T.stringof == "date" | || T.stringof == "TimeOfDay" | || T.stringof == "Duration" | || T.stringof == "DateTime" | || T.stringof == "SysTime") | { | static if (T.stringof == "YearMonth") | { | return T(year, month); | } | else | static if (T.stringof == "Date" || T.stringof == "date" || T.stringof == "YearMonthDay") | { 2| return T(year, month, day); | } | else | static if (T.stringof == "DateTime") | { 2| return T(year, month, day, hour, minute, second); | } | else | static if (T.stringof == "TimeOfDay") | { 2| return T(hour, minute, second); | } | else | static if (T.stringof == "SysTime") | { | import core.time : hnsecs, minutes; | import std.datetime.date: DateTime; | import std.datetime.systime: SysTime; | import std.datetime.timezone: UTC, SimpleTimeZone; 2| auto ret = SysTime.fromUnixTime(toUnixTime, UTC()) + getFraction!7.hnsecs; 2| if (offset) | { 2| ret = ret.toOtherTZ(new immutable SimpleTimeZone(offset.minutes)); | } 2| return ret; | } | else | static if (T.stringof == "Duration") | { 2| if (!isDuration) 0000000| throw ExpectedDuration; 2| auto coeff = ((((long(year) * 7 + month) * 24 + hour) * 60 + minute) * 60 + second) * 10_000_000 + getFraction!7; 2| if (isNegativeDuration) 1| coeff = -coeff; | | import mir.conv: to; | import core.time: hnsecs; 2| return coeff.hnsecs.to!T; | } | } | | /++ | +/ | long getFraction(int digits)() @property const @safe pure nothrow @nogc | if (digits >= 1 && digits <= 12) | { 4| long coeff; 4| if (fractionCoefficient) | { 4| coeff = fractionCoefficient; 4| int exp = fractionExponent; 4| while (exp > -digits) | { 0000000| exp--; 0000000| coeff *= 10; | } 4| while (exp < -digits) | { 0000000| exp++; 0000000| coeff /= 10; | } | } 4| return coeff; | } | | /++ | Returns: true if timestamp represent a time only value. | +/ | bool isOnlyTime() @property const @safe pure nothrow @nogc | { 126| return precision > Precision.day && day == 0; | } | | /// | int opCmp(Timestamp rhs) const @safe pure nothrow @nogc | { | import std.meta: AliasSeq; | static foreach (member; [ | "year", | "month", | "day", | "hour", | "minute", | "second", | ]) 6| if (auto d = int(__traits(getMember, this, member)) - int(__traits(getMember, rhs, member))) 0000000| return d; 1| int frel = this.fractionExponent; 1| int frer = rhs.fractionExponent; 1| ulong frcl = this.fractionCoefficient; 1| ulong frcr = rhs.fractionCoefficient; 1| while(frel > frer) | { 0000000| frel--; 0000000| frcl *= 10; | } 1| while(frer > frel) | { 0000000| frer--; 0000000| frcr *= 10; | } 1| if (frcl < frcr) return -1; 1| if (frcl > frcr) return +1; 1| if (auto d = int(this.fractionExponent) - int(rhs.fractionExponent)) 0000000| return d; 1| return int(this.offset) - int(rhs.offset); | } | | /++ | Attaches local offset, doesn't adjust other fields. | Local-time offsets may be represented as either `hour*60+minute` offsets from UTC, | or as the zero to denote a local time of UTC. They are required on timestamps with time and are not allowed on date values. | +/ | @safe pure nothrow @nogc const | Timestamp withOffset(short minutes) | { 20| assert(-24 * 60 <= minutes && minutes <= 24 * 60, "Offset absolute value should be less or equal to 24 * 60"); 10| assert(precision >= Precision.minute, "Offsets are not allowed on date values."); 10| Timestamp ret = this; 10| ret.offset = minutes; 10| return ret; | } | | version(D_BetterC){} else | private string toStringImpl(alias fun)() const @safe pure nothrow | { | import mir.appender: UnsafeArrayBuffer; 52| char[64] buffer = void; 52| auto w = UnsafeArrayBuffer!char(buffer); 52| fun(w); 52| return w.data.idup; | } | | /++ | Converts this $(LREF Timestamp) to a string with the format `yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm`. | | If `w` writer is set, the resulting string will be written directly | to it. | | Returns: | A `string` when not using an output range; `void` otherwise. | +/ | alias toString = toISOExtString; | | /// | version (mir_test) | @safe pure nothrow unittest | { 1| assert(Timestamp.init.toString == "0000T"); 1| assert(Timestamp(2010, 7, 4).toString == "2010-07-04"); 1| assert(Timestamp(1998, 12, 25).toString == "1998-12-25"); 1| assert(Timestamp(0, 1, 5).toString == "0000-01-05"); 1| assert(Timestamp(-4, 1, 5).toString == "-0004-01-05"); | | // yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm 1| assert(Timestamp(2021).toString == "2021T"); 1| assert(Timestamp(2021, 01).toString == "2021-01T", Timestamp(2021, 01).toString); 1| assert(Timestamp(2021, 01, 29).toString == "2021-01-29"); 1| assert(Timestamp(2021, 01, 29, 19, 42).toString == "2021-01-29T19:42Z"); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toString == "2021-01-29T19:42:44+07", Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toString); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toString == "2021-01-29T20:12:44+07:30"); | 1| assert(Timestamp.onlyTime(7, 40).toString == "07:40Z"); 1| assert(Timestamp.onlyTime(7, 40, 30).toString == "07:40:30Z"); 1| assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toString == "07:40:30.056Z"); | } | | /// | version (mir_test) | @safe unittest | { | // Test A.D. 1| assert(Timestamp(9, 12, 4).toISOExtString == "0009-12-04"); 1| assert(Timestamp(99, 12, 4).toISOExtString == "0099-12-04"); 1| assert(Timestamp(999, 12, 4).toISOExtString == "0999-12-04"); 1| assert(Timestamp(9999, 7, 4).toISOExtString == "9999-07-04"); 1| assert(Timestamp(10000, 10, 20).toISOExtString == "+10000-10-20"); | | // Test B.C. 1| assert(Timestamp(0, 12, 4).toISOExtString == "0000-12-04"); 1| assert(Timestamp(-9, 12, 4).toISOExtString == "-0009-12-04"); 1| assert(Timestamp(-99, 12, 4).toISOExtString == "-0099-12-04"); 1| assert(Timestamp(-999, 12, 4).toISOExtString == "-0999-12-04"); 1| assert(Timestamp(-9999, 7, 4).toISOExtString == "-9999-07-04"); 1| assert(Timestamp(-10000, 10, 20).toISOExtString == "-10000-10-20"); | 1| assert(Timestamp.onlyTime(7, 40).toISOExtString == "07:40Z"); 1| assert(Timestamp.onlyTime(7, 40, 30).toISOExtString == "07:40:30Z"); 1| assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOExtString == "07:40:30.056Z"); | 1| const cdate = Timestamp(1999, 7, 6); 1| immutable idate = Timestamp(1999, 7, 6); 1| assert(cdate.toISOExtString == "1999-07-06"); 1| assert(idate.toISOExtString == "1999-07-06"); | } | | /// ditto | alias toISOExtString = toISOStringImp!true; | | /++ | Converts this $(LREF Timestamp) to a string with the format `YYYYMMDDThhmmss±hhmm`. | | If `w` writer is set, the resulting string will be written directly | to it. | | Returns: | A `string` when not using an output range; `void` otherwise. | +/ | alias toISOString = toISOStringImp!false; | | /// | version (mir_test) | @safe pure nothrow unittest | { 1| assert(Timestamp.init.toISOString == "0000T"); 1| assert(Timestamp(2010, 7, 4).toISOString == "20100704"); 1| assert(Timestamp(1998, 12, 25).toISOString == "19981225"); 1| assert(Timestamp(0, 1, 5).toISOString == "00000105"); 1| assert(Timestamp(-4, 1, 5).toISOString == "-00040105"); | | // YYYYMMDDThhmmss±hhmm 1| assert(Timestamp(2021).toISOString == "2021T"); 1| assert(Timestamp(2021, 01).toISOString == "2021-01T"); // always extended 1| assert(Timestamp(2021, 01, 29).toISOString == "20210129"); 1| assert(Timestamp(2021, 01, 29, 19, 42).toISOString == "20210129T1942Z"); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toISOString == "20210129T194244+07"); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toISOString == "20210129T201244+0730"); | static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toISOString == "20210129T201244+0730"); | 1| assert(Timestamp.onlyTime(7, 40).toISOString == "T0740Z"); 1| assert(Timestamp.onlyTime(7, 40, 30).toISOString == "T074030Z"); 1| assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOString == "T074030.056Z"); | } | | /// | long toUnixTime() const @safe pure nothrow @nogc | { | import mir.date: Date; 6| long result; 12| if (!isDuration && !isOnlyTime) | { | enum fistDay = Date.trustedCreate(1970, 1, 1); 6| result = Date.trustedCreate(year, month ? month : 1, day ? day : 1) - fistDay; 6| result *= 24; | } 6| result += hour; 6| result *= 60; 6| result += minute; 6| result *= 60; 6| result += second; 6| return result; | } | | /// | version(mir_test) | @safe pure nothrow @nogc unittest | { 1| assert(Timestamp(1970, 1, 1).toUnixTime == 0); 1| assert(Timestamp(2007, 12, 22, 8, 14, 45).toUnixTime == 1_198_311_285); 1| assert(Timestamp(2007, 12, 22, 8, 14, 45).withOffset(90).toUnixTime == 1_198_311_285); 1| assert(Timestamp(1969, 12, 31, 23, 59, 59).toUnixTime == -1); | } | | /// | static Timestamp fromUnixTime(long time) @safe pure nothrow @nogc | { | import mir.date: Date; 6| auto days = time / (24 * 60 * 60); 6| time -= days * (24 * 60 * 60); 6| if (time < 0) | { 1| days--; 1| time += 24 * 60 * 60; | } 6| assert(time >= 0); 6| auto second = time % 60; 6| time /= 60; 6| auto minute = time % 60; 6| time /= 60; 6| auto hour = cast(ubyte) time; | enum fistDay = Date.trustedCreate(1970, 1, 1); 6| with((fistDay + cast(int)days).yearMonthDay) 6| return Timestamp(year, cast(ubyte)month, day, hour, cast(ubyte)minute, cast(ubyte)second); | } | | /// | version(mir_test) | @safe pure nothrow @nogc unittest | { | import mir.format; 1| assert(Timestamp.fromUnixTime(0) == Timestamp(1970, 1, 1, 0, 0, 0)); 1| assert(Timestamp.fromUnixTime(1_198_311_285) == Timestamp(2007, 12, 22, 8, 14, 45)); 1| assert(Timestamp.fromUnixTime(-1) == Timestamp(1969, 12, 31, 23, 59, 59)); | } | | /// Helpfer for time zone offsets | void addMinutes(short minutes) @safe pure nothrow @nogc | { 14| int totalMinutes = minutes + (this.minute + this.hour * 60u); 14| auto h = totalMinutes / 60; | 14| int dayShift; | 15| while (totalMinutes < 0) | { 1| totalMinutes += 24 * 60; 1| dayShift--; | } | 18| while (totalMinutes >= 24 * 60) | { 4| totalMinutes -= 24 * 60; 4| dayShift++; | } | 14| if (dayShift) | { | import mir.date: Date; 5| auto ymd = (Date.trustedCreate(year, month, day) + dayShift).yearMonthDay; 5| year = ymd.year; 5| month = cast(ubyte)ymd.month; 5| day = ymd.day; | } | 14| hour = cast(ubyte) (totalMinutes / 60); 14| minute = cast(ubyte) (totalMinutes % 60); | } | | template toISOStringImp(bool ext) | { | version(D_BetterC){} else | string toISOStringImp() const @safe pure nothrow | { 52| return toStringImpl!toISOStringImp; | } | | /// ditto | void toISOStringImp(W)(ref scope W w) const scope | // if (isOutputRange!(W, char)) | { | import mir.format: printZeroPad; | // yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm 52| Timestamp t = this; | 52| if (t.offset) | { 10| assert(-24 * 60 <= t.offset && t.offset <= 24 * 60, "Offset absolute value should be less or equal to 24 * 60"); 5| assert(precision >= Precision.minute, "Offsets are not allowed on date values."); 5| t.addMinutes(t.offset); | } | 52| if (!t.isOnlyTime) | { 42| if (t.year >= 10_000) 1| w.put('+'); 42| printZeroPad(w, t.year, t.year >= 0 ? t.year < 10_000 ? 4 : 5 : t.year > -10_000 ? 5 : 6); 42| if (precision == Precision.year) | { 4| w.put('T'); 4| return; | } 68| if (ext || precision == Precision.month) w.put('-'); | 38| printZeroPad(w, cast(uint)t.month, 2); 38| if (precision == Precision.month) | { 2| w.put('T'); 2| return; | } 28| static if (ext) w.put('-'); | 36| printZeroPad(w, t.day, 2); 36| if (precision == Precision.day) 25| return; | } | 21| if (!ext || !t.isOnlyTime) 14| w.put('T'); | 21| printZeroPad(w, t.hour, 2); 15| static if (ext) w.put(':'); 21| printZeroPad(w, t.minute, 2); | 21| if (precision >= Precision.second) | { 12| static if (ext) w.put(':'); 16| printZeroPad(w, t.second, 2); | 23| if (precision > Precision.second && (t.fractionExponent < 0 || t.fractionCoefficient)) | { 7| w.put('.'); 7| printZeroPad(w, t.fractionCoefficient, -int(t.fractionExponent)); | } | } | 21| if (t.offset == 0) | { 16| w.put('Z'); 16| return; | } | 5| bool sign = t.offset < 0; 5| uint absoluteOffset = !sign ? t.offset : -int(t.offset); 5| uint offsetHour = absoluteOffset / 60u; 5| uint offsetMinute = absoluteOffset % 60u; | 5| w.put(sign ? '-' : '+'); 5| printZeroPad(w, offsetHour, 2); 5| if (offsetMinute) | { 2| static if (ext) w.put(':'); 3| printZeroPad(w, offsetMinute, 2); | } | } | } | | /++ | Creates a $(LREF Timestamp) from a string with the format `YYYYMMDDThhmmss±hhmm | or its leading part allowed by the standard. | | or its leading part allowed by the standard. | | Params: | str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) formats dates. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. | +/ | alias fromISOString = fromISOStringImpl!false; | | /// | version (mir_test) | @safe unittest | { 1| assert(Timestamp.fromISOString("20100704") == Timestamp(2010, 7, 4)); 1| assert(Timestamp.fromISOString("19981225") == Timestamp(1998, 12, 25)); 1| assert(Timestamp.fromISOString("00000105") == Timestamp(0, 1, 5)); | // assert(Timestamp.fromISOString("-00040105") == Timestamp(-4, 1, 5)); | 1| assert(Timestamp(2021) == Timestamp.fromISOString("2021")); 1| assert(Timestamp(2021) == Timestamp.fromISOString("2021T")); | // assert(Timestamp(2021, 01) == Timestamp.fromISOString("2021-01")); | // assert(Timestamp(2021, 01) == Timestamp.fromISOString("2021-01T")); 1| assert(Timestamp(2021, 01, 29) == Timestamp.fromISOString("20210129")); 1| assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOString("20210129T1942")); 1| assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOString("20210129T1942Z")); 1| assert(Timestamp(2021, 01, 29, 19, 42, 12) == Timestamp.fromISOString("20210129T194212")); 1| assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67) == Timestamp.fromISOString("20210129T194212.067Z")); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60) == Timestamp.fromISOString("20210129T194244+07")); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730")); | static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730")); | static assert(Timestamp(2021, 01, 29, 4, 42, 44).withOffset(- (7 * 60 + 30)) == Timestamp.fromISOString("20210128T211244-0730")); | } | | version (mir_test) | @safe unittest | { | import std.exception: assertThrown; 2| assertThrown!DateTimeException(Timestamp.fromISOString("")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("990704")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("0100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010070")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("120100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("-0100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("+0100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010070a")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("20100a04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010a704")); | 2| assertThrown!DateTimeException(Timestamp.fromISOString("99-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-0")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("12010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("-010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("+010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-0a")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-0a-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-a7-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010/07/04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010/7/04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010/7/4")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010/07/4")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-7-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-7-4")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-4")); | 2| assertThrown!DateTimeException(Timestamp.fromISOString("99Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010Jul0")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("12010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("-010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("+010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010Jul0a")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010Jua04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010aul04")); | 2| assertThrown!DateTimeException(Timestamp.fromISOString("99-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jul-0")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("12010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("-010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("+010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jul-0a")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jua-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jal-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-aul-04")); | | // assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jul-04")); | 1| assert(Timestamp.fromISOString("19990706") == Timestamp(1999, 7, 6)); | // assert(Timestamp.fromISOString("-19990706") == Timestamp(-1999, 7, 6)); | // assert(Timestamp.fromISOString("+019990706") == Timestamp(1999, 7, 6)); 1| assert(Timestamp.fromISOString("19990706") == Timestamp(1999, 7, 6)); | } | | // bug# 17801 | version (mir_test) | @safe unittest | { | import std.conv : to; | import std.meta : AliasSeq; | static foreach (C; AliasSeq!(char, wchar, dchar)) | { | static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) 9| assert(Timestamp.fromISOString(to!S("20121221")) == Timestamp(2012, 12, 21)); | } | } | | /++ | Creates a $(LREF Timestamp) from a string with the format `yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm` | or its leading part allowed by the standard. | | Params: | str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) formats dates. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. | +/ | alias fromISOExtString = fromISOStringImpl!true; | | | /// | version (mir_test) | @safe unittest | { 1| assert(Timestamp.fromISOExtString("2010-07-04") == Timestamp(2010, 7, 4)); 1| assert(Timestamp.fromISOExtString("1998-12-25") == Timestamp(1998, 12, 25)); 1| assert(Timestamp.fromISOExtString("0000-01-05") == Timestamp(0, 1, 5)); 1| assert(Timestamp.fromISOExtString("-0004-01-05") == Timestamp(-4, 1, 5)); | 1| assert(Timestamp(2021) == Timestamp.fromISOExtString("2021")); 1| assert(Timestamp(2021) == Timestamp.fromISOExtString("2021T")); 1| assert(Timestamp(2021, 01) == Timestamp.fromISOExtString("2021-01")); 1| assert(Timestamp(2021, 01) == Timestamp.fromISOExtString("2021-01T")); 1| assert(Timestamp(2021, 01, 29) == Timestamp.fromISOExtString("2021-01-29")); 1| assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOExtString("2021-01-29T19:42")); 1| assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOExtString("2021-01-29T19:42Z")); 1| assert(Timestamp(2021, 01, 29, 19, 42, 12) == Timestamp.fromISOExtString("2021-01-29T19:42:12")); 1| assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67) == Timestamp.fromISOExtString("2021-01-29T19:42:12.067Z")); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60) == Timestamp.fromISOExtString("2021-01-29T19:42:44+07")); 1| assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOExtString("2021-01-29T20:12:44+07:30")); | static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOExtString("2021-01-29T20:12:44+07:30")); | static assert(Timestamp(2021, 01, 29, 4, 42, 44).withOffset(- (7 * 60 + 30)) == Timestamp.fromISOExtString("2021-01-28T21:12:44-07:30")); | } | | /++ | Creates a $(LREF Timestamp) from a YAML string format | or its leading part allowed by the standard. | | Params: | str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) formats dates. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. | +/ | alias fromYamlString = fromISOStringImpl!(true, true); | | /// | version (mir_test) | @safe unittest | { | // canonical 1| assert(Timestamp.fromYamlString("2001-12-15T02:59:43.1Z") == Timestamp("2001-12-15T02:59:43.1Z")); | // with lower 't' separator 1| assert(Timestamp.fromYamlString("2001-12-14t21:59:43.1-05:30") == Timestamp("2001-12-14T21:59:43.1-05:30")); | // yaml space separated 1| assert(Timestamp.fromYamlString("2001-12-14 21:59:43.1 -5") == Timestamp("2001-12-14T21:59:43.1-05")); | // no time zone (Z) 1| assert(Timestamp.fromYamlString("2001-12-15 2:59:43.10") == Timestamp("2001-12-15T02:59:43.10")); | // date 00:00:00Z 1| assert(Timestamp.fromYamlString("2002-12-14") == Timestamp("2002-12-14")); | } | | version (mir_test) | @safe unittest | { | import std.exception: assertThrown; | 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("990704")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("0100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("120100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("-0100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("+0100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010070a")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("20100a04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010a704")); | 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("99-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-07-0")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("12010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("-010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("+010-07-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-07-0a")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-0a-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-a7-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/07/04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/7/04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/7/4")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/07/4")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-7-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-7-4")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-07-4")); | 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("99Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010Jul0")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("12010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("-010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("+010Jul04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010Jul0a")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010Jua04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010aul04")); | 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("99-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jul-0")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("12010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("-010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("+010-Jul-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jul-0a")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jua-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jal-04")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-aul-04")); | 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("20100704")); 2| assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jul-04")); | 1| assert(Timestamp.fromISOExtString("1999-07-06") == Timestamp(1999, 7, 6)); 1| assert(Timestamp.fromISOExtString("-1999-07-06") == Timestamp(-1999, 7, 6)); 1| assert(Timestamp.fromISOExtString("+01999-07-06") == Timestamp(1999, 7, 6)); | } | | // bug# 17801 | version (mir_test) | @safe unittest | { | import std.conv : to; | import std.meta : AliasSeq; | static foreach (C; AliasSeq!(char, wchar, dchar)) | { | static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) 9| assert(Timestamp.fromISOExtString(to!S("2012-12-21")) == Timestamp(2012, 12, 21)); | } | } | | /++ | Creates a $(LREF Timestamp) from a string. | | Params: | str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) and $(LREF .Timestamp.toISOString) format dates, also YAML like spaces seprated strings are accepted. The function is case sensetive. | value = (optional) result value. | | Throws: | $(LREF DateTimeException) if the given string is | not in the correct format. Two arguments overload is `nothrow`. | Returns: | `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. | +/ | static bool fromString(C)(scope const(C)[] str, out Timestamp value) @safe pure nothrow @nogc | { 17| return fromYamlString(str, value) 5| || fromISOString(str, value); | } | | /// | version (mir_test) | @safe pure @nogc unittest | { 1| assert(Timestamp.fromString("2010-07-04") == Timestamp(2010, 7, 4)); 1| assert(Timestamp.fromString("20100704") == Timestamp(2010, 7, 4)); | } | | /// ditto | static Timestamp fromString(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 17| Timestamp ret; 17| if (fromString(str, ret)) 17| return ret; 0000000| throw InvalidString; | } | | template fromISOStringImpl(bool ext, bool yaml = false) | { | static Timestamp fromISOStringImpl(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 148| Timestamp ret; 148| if (fromISOStringImpl(str, ret)) 56| return ret; | static if (yaml) 0000000| throw InvalidYamlString; | else | static if (ext) 46| throw InvalidISOExtendedString; | else 46| throw InvalidISOString; | } | | static bool fromISOStringImpl(C)(scope const(C)[] str, out Timestamp value) @safe pure nothrow @nogc | if (isSomeChar!C) | { | import mir.parse: fromString, parse; | 170| if (str.length < 4) 2| return false; | | static if (ext) 202| auto isOnlyTime = (str[0] == 'T' || yaml && (str[0] == 't')) || str[2] == ':'; | else 74| auto isOnlyTime = str[0] == 'T' || yaml && (str[0] == 't'); | 168| if (!isOnlyTime) | { | // YYYY | static if (ext) | {{ 176| auto startIsDigit = str.length && str[0].isDigit; 88| auto strOldLength = str.length; 88| if (!parse(str, value.year)) 9| return false; 79| auto l = strOldLength - str.length; 79| if ((l == 4) != startIsDigit) 16| return false; | }} | else | { 205| if (str.length < 4 || !str[0].isDigit || !fromString(str[0 .. 4], value.year)) 14| return false; 57| str = str[4 .. $]; | } | 120| value.precision = Precision.year; 238| if (str.length == 0 || str == "T") 4| return true; | | static if (ext) | { 61| if (str[0] != '-') 9| return false; 52| str = str[1 .. $]; | } | | // MM 292| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.month)) 36| return false; 71| str = str[2 .. $]; 71| value.precision = Precision.month; 143| if (str.length == 0 || str.length == 1 && (str[0] == 'T' || (yaml && (str[0] == 't' || str[0] == ' ' || str[0] == '\t')))) 3| return ext; | | static if (ext) | { 40| if (str[0] != '-') 0000000| return false; 40| str = str[1 .. $]; | } | | // DD 196| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.day)) 6| return false; 62| str = str[2 .. $]; 62| value.precision = Precision.day; 62| if (str.length == 0) 39| return true; | } | | // str isn't empty here | // T 43| if ((str[0] == 'T' || (yaml && (str[0] == 't' || str[0] == ' ' || str[0] == '\t')))) | { 29| str = str[1 .. $]; | // OK, onlyTime requires length >= 3 29| if (str.length == 0) 0000000| return true; | } | else | { 3| if (!(ext && isOnlyTime)) 1| return false; | } | 31| value.precision = Precision.minute; // we don't have hour precision | | // hh 93| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.hour)) | { | static if (yaml) | { 3| if (str.length < 1 || !str[0].isDigit || !fromString(str[0 .. 1], value.hour)) 0000000| return false; | else 1| str = str[1 .. $]; | } | else 0000000| return false; | } | else 30| str = str[2 .. $]; | 31| if (str.length == 0) 0000000| return true; | | static if (ext) | { 21| if (str[0] != ':') 3| return false; 18| str = str[1 .. $]; | } | | // mm | { 28| uint minute; 84| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], minute)) 0000000| return false; 28| value.minute = cast(ubyte) minute; 28| str = str[2 .. $]; 28| if (str.length == 0) 2| return true; | } | | static if (ext) | { 17| if (str[0] != ':') 2| goto TZ; 15| str = str[1 .. $]; | } | | // ss | { 24| uint second; 46| if (str.length < 2 || !str[0].isDigit) 2| goto TZ; 22| if (!fromString(str[0 .. 2], second)) 0000000| return false; 22| value.second = cast(ubyte) second; 22| str = str[2 .. $]; 22| value.precision = Precision.second; 22| if (str.length == 0) 2| return true; | } | | // . 20| if (str[0] != '.') 7| goto TZ; 13| str = str[1 .. $]; 13| value.precision = Precision.fraction; | | // fraction | { 13| const strOldLength = str.length; 13| ulong fractionCoefficient; 39| if (str.length < 1 || !str[0].isDigit || !parse!ulong(str, fractionCoefficient)) 0000000| return false; 13| sizediff_t fractionExponent = str.length - strOldLength; 13| if (fractionExponent < -12) 0000000| return false; 13| value.fractionExponent = cast(byte)fractionExponent; 13| value.fractionCoefficient = fractionCoefficient; 13| if (str.length == 0) 2| return true; | } | | TZ: | | static if (yaml) | { 29| if (str.length && (str[0] == ' ' || str[0] == '\t')) 1| str = str[1 .. $]; | } | 22| if (str == "Z") 13| return true; | 9| int hour; 9| int minute; 25| if (str.length < 3 || str[0].isDigit || !fromString(str[0 .. 3], hour)) | { | static if (yaml) | { 3| if (str.length < 2 || str[0].isDigit || !fromString(str[0 .. 2], hour)) 0000000| return false; 1| str = str[2 .. $]; | } | else 0000000| return false; | } | else | { 8| str = str[3 .. $]; | } | 9| if (str.length) | { | static if (ext) | { 3| if (str[0] != ':') 0000000| return false; 3| str = str[1 .. $]; | } 15| if (str.length != 2 || !str[0].isDigit || !fromString(str[0 .. 2], minute)) 0000000| return false; | } | 9| value.offset = cast(short)(hour * 60 + (hour < 0 ? -minute : minute)); 9| value.addMinutes(cast(short)-int(value.offset)); 9| return true; | } | } | | /// | bool isDuration() const @safe pure nothrow @nogc @property | { 23| return day == 88 || day == 99; | } | | /// | bool isNegativeDuration() const @safe pure nothrow @nogc @property | { 2| return day == 99; | } |} | |version(mir_test) |unittest |{ 1| long sec = -2208988800; 1| uint nanosec = 0; 1| auto ts = Timestamp.fromUnixTime(sec); 1| if (nanosec >= 0) | { 1| ts.precision = Timestamp.Precision.fraction; 1| ts.fractionCoefficient = nanosec; 1| ts.fractionExponent = -9; | } 1| auto ts2 = "1900-01-01T00:00:00.000000000Z".Timestamp; 1| assert(ts == ts2); |} source/mir/timestamp.d is 94% covered <<<<<< EOF # path=source-mir-type_info.lst |/++ |$(H1 Type Information) | |Type Information implementation compatible with BetterC mode. | |Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilia Ki | |Macros: |NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.type_info; | |/++ |+/ |struct mir_type_info |{ | /// | extern(C) | void function(void*) @safe pure nothrow @nogc destructor; | /++ | Note: Negative values are used for classes to indicate that | +/ | int size; |} | |/++ |+/ |ref immutable(mir_type_info) mir_get_type_info(T)() @trusted |{ | import std.traits: Unqual, hasElaborateDestructor; | | static if (is(T == class)) | enum sizeof = __traits(classInstanceSize, T); | else | enum sizeof = T.sizeof; | | static if (!is(T == Unqual!T)) | { 32| return mir_get_type_info!(Unqual!T); | } | else | static if (hasElaborateDestructor!T) | { | import std.traits: SetFunctionAttributes, functionAttributes; | alias fun = void function(void*) @safe pure nothrow @nogc; | extern(C) | static void destroy_impl(void* ptr) nothrow | { | static if (is(T == class)) | T inst() return @trusted | { | return cast(T)ptr; | } | else | ref T inst() return @trusted | { 19| return *cast(T*)ptr; | } | version(assert) 19| destroy!true(inst()); | else | destroy!false(inst()); | } | | static immutable ti = mir_type_info(cast(SetFunctionAttributes!(fun, "C", functionAttributes!fun))&destroy_impl, sizeof); 9| return ti; | } | else | { 278| return .mir_get_type_info!sizeof; | } |} | |/++ |+/ |ref immutable(mir_type_info) mir_get_type_info(uint sizeof)() |{ | static immutable ti = mir_type_info(null, sizeof); 278| return ti; |} | |package template hasDestructor(T) |{ | import std.traits: Unqual; | | static if (is(T == struct)) | { | static if (__traits(hasMember, Unqual!T, "__xdtor")) | enum hasDestructor = __traits(isSame, Unqual!T, __traits(parent, T.init.__xdtor)); | else | enum hasDestructor = false; | } | else | static if (is(T == class)) | { | enum hasDestructor = __traits(hasMember, Unqual!T, "__xdtor"); | } | else | { | enum hasDestructor = false; | } |} | |package const(void)* mir_get_payload_ptr(T)() |{ | import std.traits: Unqual; | | static if (!is(T == Unqual!T)) | { 32| return mir_get_payload_ptr!(Unqual!T); | } | else | static if (is(T == class)) | { 4| return typeid(T).initializer.ptr; | } | else | static if (__traits(isZeroInit, T) || __traits(isFloating, T)) | { 237| return null; | } | else | { | static immutable payload = T.init; 46| return &payload; | } |} source/mir/type_info.d is 100% covered <<<<<< EOF