.gitmodules Dockerfile.aarch64 LICENSE NOTICE cpp_example/eye.d cpp_example/init_rcarray.d cpp_example/main.cpp cpp_example/meson.build dub.sdl 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/json.d source/mir/algorithm/iteration.d source/mir/algorithm/setops.d source/mir/appender.d source/mir/array/allocation.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/dec2flt_table.d source/mir/bignum/internal/ryu/generic_128.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/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/generic.d source/mir/interpolate/linear.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/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/string_map.d source/mir/timestamp.d source/mir/type_info.d subprojects/mir-core.wrap test_travis.sh <<<<<< network # path=./..-..-..-.dub-packages-mir-core-1.1.82-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 Algebraic first argument) $(TH Fuses Algebraic types on return)) |$(LEADINGROWN 8, Classic handlers) |$(T8 visit, Yes, N/A, No, No, 1+, Yes, No) |$(T8 optionalVisit, No, No, Yes, No, 1+, Yes, No) |$(T8 autoVisit, No, No, auto, No, 1+, Yes, No) |$(T8 tryVisit, No, Yes, No, No, 1+, Yes, No) |$(LEADINGROWN 8, Multiple dispatch and algebraic fusion on return) |$(T8 match, Yes, N/A, No, Yes, 0+, auto, Yes) |$(T8 optionalMatch, No, No, Yes, Yes, 0+, auto, Yes) |$(T8 autoMatch, No, No, auto, Yes, 0+, auto, Yes) |$(T8 tryMatch, No, Yes, No, Yes, 0+, auto, Yes) |$(LEADINGROWN 8, Member access) |$(T8 getMember, Yes, N/A, No, No, 1+, Yes, No) |$(T8 optionalGetMember, No, No, Yes, No, 1+, Yes, No) |$(T8 autoGetMember, No, No, auto, No, 1+, Yes, No) |$(T8 tryGetMember, No, Yes, No, No, 1+, Yes, No) |$(LEADINGROWN 8, Member access with algebraic fusion on return) |$(T8 matchMember, Yes, N/A, No, No, 1+, Yes, Yes) |$(T8 optionalMatchMember, No, No, Yes, No, 1+, Yes, Yes) |$(T8 autoMatchMember, No, No, auto, No, 1+, Yes, Yes) |$(T8 tryMatchMember, No, Yes, No, No, 1+, Yes, 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 TaggedType, Dummy type used to associate tags with 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 isTaggedVariant, Checks if the type is instance of tagged $(LREF Algebraic).) |$(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. ) | |) | | |$(H3 Type Set) |$(UL |$(LI $(LREF TaggedTypeSet) is supported. Example:`TargetTypeSet!(["integer", "floating"], int, double)`). |$(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. ) |) | |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: Ilya Yaroshenko | |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; | |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(T == 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); |} | |/++ |Checks if the type is instance of tagged $(LREF Algebraic). | |Tagged algebraics can be defined with $(LREF TaggedVariant). |+/ |enum bool isTaggedVariant(T) = isVariant!T && is(T.Kind == enum); | |/// |@safe pure version(mir_core_test) unittest |{ | static assert(!isTaggedVariant!int); | static assert(!isTaggedVariant!(Variant!(int, string))); | static assert(isTaggedVariant!(TaggedVariant!(["integer", "string"], int, string))); |} | |/++ |Checks if the type is instance of $(LREF Algebraic) with a self $(LREF TypeSet) that contains `typeof(null)`. |+/ |enum bool isNullable(T) = is(T == 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); |} | |/++ |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 const: 0000000| int opCmp(typeof(this)) { return 0; } 0000000| string toString() { return typeof(this).stringof; } |} | |/++ |Dummy type used to associate tags with a type. |+/ |struct TaggedType(T, string name) | if (name.length) |{ | private enum tag = name; | private alias Type = T; | static if (!is(T == void) && !is(T == typeof(null))) | private T payload; |@safe pure nothrow @nogc const: | int opCmp(typeof(this)) { return 0; } | string toString() { return typeof(this).stringof; } |} | |/++ |Checks if `T` is $(LREF TaggedType) instance. |+/ |enum isTaggedType(T) = is(T == TaggedType!(I, name), I, string name); | |/++ |Gets $(LREF TaggedType) underlying type. |+/ |alias getTaggedTypeUnderlying(T : TaggedType!(I, name), I, string name) = I; | |/++ |Gets $(LREF TaggedType) tag name. |+/ |enum getTaggedTypeName(T : TaggedType!(I, name), I, string name) = name; | |// 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 = RefTuple; | | // 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)) | { | static if (anySatisfy!(isTaggedType, T)) | { | import std.meta: Filter, templateNot; | static assert(Filter!(templateNot!isTaggedType, T).length == 0, | "Either all or none types must be tagged. Types that doesn't have tags: " ~ | Filter!(templateNot!isTaggedType, T).stringof); | } | alias TypeSet = T; | } | else | alias TypeSet = .TypeSet!(staticSort!(TypeCmp, T)); | else | alias TypeSet = TypeSet!(NoDuplicates!T); | else | alias TypeSet = TypeSet!(staticMap!(TryRemoveConst, T)); |} | |private template TypeCmp(A, B) |{ | enum bool TypeCmp = is(A == B) ? false: | is(A == typeof(null)) || is(A == TaggedType!(typeof(null), aname), string aname) ? true: | is(B == typeof(null)) || is(B == TaggedType!(typeof(null), bname), string bname) ? false: | is(A == void) || is(A == TaggedType!(void, aname), string aname) ? true: | is(B == void) || is(A == TaggedType!(void, bname), string bname) ? false: | A.sizeof < B.sizeof ? true: | A.sizeof > B.sizeof ? false: | A.mangleof < B.mangleof; |} | |/// |version(mir_core_test) unittest |{ | 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!(TaggedType!(T[0], tagNames[0]), .applyTags!(tagNames[1 .. $], T[1 .. $])); |} | |/++ |Type set for tagged $(LREF Variants) self-referencing. |+/ |alias TaggedTypeSet(string[] tagNames, T...) = TypeSet!(applyTags!(tagNames, T)); | |/++ |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 |{ | struct S { ubyte d; } | static assert(Nullable!(byte, char, S).sizeof == 2); |} | |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | 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: |---- |alias TaggedVariant(string[] tags, T...) = Variant!(applyTags!(tags, T)); |// and |template TaggedVariant(T) | if (is(T == union)) |{ | ... |} |---- | |See_also: $(LREF Variant), $(LREF isTaggedVariant). |+/ |alias TaggedVariant(string[] tags, T...) = Variant!(applyTags!(tags, T)); | |/// ditto |template TaggedVariant(T) | if (is(T == union)) |{ | import std.meta: staticMap; | enum names = __traits(allMembers, T); | alias TypeOf(string member) = typeof(__traits(getMember, T, member)); | alias Types = staticMap!(TypeOf, names); | alias TaggedVariant = .TaggedVariant!([names], Types); |} | |/// Json Value |@safe pure |version(mir_core_test) unittest |{ | static union JsonUnion | { | long integer; | double floating; | bool boolean; | typeof(null) null_; | string string_; | This[] array; | This[string] object; | } | | alias JsonValue = TaggedVariant!JsonUnion; | | // typeof(null) has priority | static assert(JsonValue.Kind.init == JsonValue.Kind.null_); | static assert(JsonValue.Kind.null_ == 0); | | // Kind and AllowedTypes has the same order | static assert (is(JsonValue.AllowedTypes[JsonValue.Kind.array] == JsonValue[])); | static assert (is(JsonValue.AllowedTypes[JsonValue.Kind.boolean] == bool)); | static assert (is(JsonValue.AllowedTypes[JsonValue.Kind.floating] == double)); | static assert (is(JsonValue.AllowedTypes[JsonValue.Kind.integer] == long)); | static assert (is(JsonValue.AllowedTypes[JsonValue.Kind.null_] == typeof(null))); | static assert (is(JsonValue.AllowedTypes[JsonValue.Kind.object] == JsonValue[string])); | | JsonValue v; | assert(v.kind == JsonValue.Kind.null_); | | v = 1; | assert(v.kind == JsonValue.Kind.integer); | assert(v == 1); | v = JsonValue(1); | assert(v == 1); | v = v.get!(long, double); | | v = "Tagged!"; | assert(v.get !string == "Tagged!"); | assert(v.trustedGet!string == "Tagged!"); | | assert(v.kind == JsonValue.Kind.string_); | | assert(v.get !(JsonValue.Kind.string_) == "Tagged!"); // Kind-based get | assert(v.trustedGet!(JsonValue.Kind.string_) == "Tagged!"); // Kind-based trustedGet | | v = [JsonValue("str"), JsonValue(4.3)]; | | assert(v.kind == JsonValue.Kind.array); | assert(v.trustedGet!(JsonValue[])[1].kind == JsonValue.Kind.floating); | | v = null; | assert(v.kind == JsonValue.Kind.null_); |} | |/// Wrapped algebraic with propogated primitives |@safe pure |version(mir_core_test) unittest |{ | static struct Response | { | alias Union = TaggedVariant!( | ["double_", "string", "array", "table"], | double, | string, | Response[], | Response[string], | ); | | Union data; | alias Tag = Union.Kind; | // propogates opEquals, opAssign, and other primitives | alias data this; | | static foreach (T; Union.AllowedTypes) | this(T v) @safe pure nothrow @nogc { data = v; } | } | | Response v = 3.0; | assert(v.kind == Response.Tag.double_); | 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); |} | |/++ |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(_Types...) |{ | 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; | import std.traits: | hasElaborateAssign, | hasElaborateCopyConstructor, | hasElaborateDestructor, | hasMember, | isEqualityComparable, | isOrderingComparable, | Largest, | Unqual | ; | | private enum bool _variant_test_ = is(_Types == AliasSeq!(typeof(null), double)); | | static if (anySatisfy!(isTaggedType, _Types)) | { | private alias _UntaggedThisTypeSetList = staticMap!(getTaggedTypeUnderlying, _Types); | } | else | { | private alias _UntaggedThisTypeSetList = _Types; | } | | /++ | Allowed types list | See_also: $(LREF TypeSet) | +/ | alias AllowedTypes = AliasSeq!(ReplaceTypeUnless!(isVariant, This, Algebraic!_Types, _UntaggedThisTypeSetList)); | | 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*))); | } | | 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)))) | 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 | { | 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 (anySatisfy!(isTaggedType, _Types)) | { | version (D_Ddoc){} | else | { | mixin(enumKindText([staticMap!(getTaggedTypeName, _Types)])); | | } | } | 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(); | } | } | | /// 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 | { | 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)) | { | // 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, AllowedTypes)) | this(return ref scope inout Algebraic rhs) inout | { | 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, AllowedTypes)) | this(return ref scope Algebraic rhs) | { | 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, AllowedTypes)) | this(return ref scope const Algebraic rhs) const | { | 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, AllowedTypes)) | this(return ref scope immutable Algebraic rhs) immutable | { | 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, AllowedTypes)) | this(return ref scope const Algebraic rhs) immutable | { | 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, AllowedTypes)) | this(return ref scope const Algebraic rhs) | { | 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() @trusted nothrow const | { | static if (AllowedTypes.length == 0 || is(AllowedTypes == AliasSeq!(typeof(null)))) | { | return 0; | } | else | switch (_identifier_) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (is(T == void)) | return i; | else | static if (is(T == typeof(null))) | return i; | else | static if (__traits(compiles, hashOf(trustedGet!T, cast(size_t)i))) | return hashOf(trustedGet!T, cast(size_t)i); | else | { | debug pragma(msg, "Mir warning: can't compute hash of " ~ (const T).stringof); | return i; | } | } | default: assert(0); | } | } | | /++ | +/ | bool opEquals()(auto ref const typeof(this) rhs) const @trusted | { | static if (AllowedTypes.length == 0) | { | return true; | } | else | { | if (this._identifier_ != rhs._identifier_) | return false; | switch (_identifier_) | { | static foreach (i, T; AllowedTypes) | { | case i: | return this.trustedGet!T == rhs.trustedGet!T; | } | default: assert(0); | } | } | } | | /++ | +/ | static if (is(AllowedTypes == _Types)) | auto opCmp()(auto ref const typeof(this) rhs) const @trusted | { | static if (AllowedTypes.length == 0) | { | return 0; | } | else | { | import mir.internal.utility: isFloatingPoint; | if (auto d = int(this._identifier_) - int(rhs._identifier_)) | return d; | switch (_identifier_) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (__traits(compiles, __cmp(trustedGet!T, rhs.trustedGet!T))) | return __cmp(trustedGet!T, rhs.trustedGet!T); | else | static if (__traits(hasMember, T, "opCmp") && !is(T == U*, U)) | return this.trustedGet!T.opCmp(rhs.trustedGet!T); | else | // static if (isFloatingPoint!T) | // return trustedGet!T == rhs ? 0 : trustedGet!T - rhs.trustedGet!T; | // else | return this.trustedGet!T < rhs.trustedGet!T ? -1 : | this.trustedGet!T > rhs.trustedGet!T ? +1 : 0; | } | default: assert(0); | } | } | } | | /// Requires mir-algorithm package | string toString()() const | { | static if (AllowedTypes.length == 0) | { | return "Algebraic"; | } | else | { | import mir.conv: to; | switch (_identifier_) | { | static foreach (i, T; AllowedTypes) | { | case i: | static if (is(T == void)) | return "void"; | else | static if (is(T == typeof(null))) | return "null"; | else | static if (__traits(compiles, { auto s = to!string(trustedGet!T);})) | return to!string(trustedGet!T); | else | return AllowedTypes[i].stringof; | } | default: assert(0); | } | } | } | | ///ditto | void toString(W)(scope ref W w) const | { | 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); })) | { import mir.format: print; print(w, trustedGet!T); } | else | w.put(AllowedTypes[i].stringof); | return; | } | default: assert(0); | } | } | } | | 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) | { | import mir.utility: _expect; | if (_expect(!_identifier_, false)) | { | throw variantNullException; | } | static if (AllowedTypes.length != 2) | { | Algebraic!(_Types[1 .. $]) ret; | | S: switch (_identifier_) | { | static foreach (i, T; AllowedTypes[1 .. $]) | { | { | case i + 1: | if (!hasElaborateCopyConstructor!T && !__ctfe) | goto default; | 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 (anySatisfy!(isTaggedType, _Types)) | /// `trustedGet` overload that accept $(LREF .Algebraic.Kind). | alias trustedGet(Kind kind) = trustedGet!(AllowedTypes[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 (anySatisfy!(isTaggedType, _Types)) | /// `get` overload that accept $(LREF .Algebraic.Kind). | alias get(Kind kind) = get!(AllowedTypes[kind]); | | 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 || !isVariant!(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 ( | member != "_ID_" && | member != "_identifier_" && | member != "_is" && | member != "_storage_" && | member != "_Storage_" && | member != "_variant_test_" && | member != "_void" && | member != "AllowedTypes" && | member != "get" && | member != "isNull" && | member != "kind" && | member != "Kind" && | member != "nullify" && | member != "opAssign" && | member != "opCast" && | member != "opCmp" && | member != "opEquals" && | member != "opPostMove" && | member != "toHash" && | member != "toString" && | member != "trustedGet" && | !(member.length >= 2 && member[0 .. 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(`auto ref ` ~ member ~q{(this This, Args...)(auto ref Args args) { static if (args.length) { import core.lifetime: forward; return this.getMember!member(forward!args); } else return this.getMember!member; }}); | } | } | } | | /// | ref opAssign(RhsTypes...)(Algebraic!RhsTypes rhs) return @trusted | if (RhsTypes.length < AllowedTypes.length && allSatisfy!(Contains!AllowedTypes, Algebraic!RhsTypes.AllowedTypes)) | { | import core.lifetime: forward; | static if (anySatisfy!(hasElaborateDestructor, AllowedTypes)) | this.__dtor(); | __ctor(forward!rhs); | return this; | } | | 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 | { | import core.lifetime: forward; | static if (anySatisfy!(hasElaborateDestructor, AllowedTypes)) | this.__dtor(); | __ctor(forward!rhs); | return this; | } | | /++ | +/ | auto opEquals()(auto ref const T rhs) const | { | static if (AllowedTypes.length > 1) | if (_identifier_ != i) | return false; | return trustedGet!T == rhs; | } | | /++ | +/ | auto opCmp()(auto ref const T rhs) const | { | import mir.internal.utility: isFloatingPoint; | 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 | static if (isFloatingPoint!T) | return trustedGet!T == rhs ? 0 : trustedGet!T - 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)); | } | } | } | } | } |} | |/++ |Constructor and methods propagation. |+/ |version(mir_core_test) |unittest |{ | static struct Base | { | double d; | } | | static class C | { | // alias this members are supported | Base base; | alias base this; | | int a; | private string _b; | | @safe pure nothrow @nogc: | | string b() const @property { return _b; } | void b(string b) @property { _b = b; } | | int retArg(int v) { return v; } | | this(int a, string b) | { | this.a = a; | this._b = b; | } | } | | static struct S | { | string b; | int a; | | double retArg(double v) { return v; } | | // alias this members are supported | Base base; | alias base this; | } | | static void inc(ref int a) { a++; } | | alias V = Nullable!(C, S); // or Variant! | | auto v = V(2, "str"); | assert(v._is!C); | 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); | | 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 |{ | 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() { return hashOf(_payload); } | | static if (hasOpEquals) | auto opEquals(ref const typeof(this) rhs) @trusted { return memcmp(_payload.ptr, rhs._payload.ptr, _payload.length); } | auto opCmp(ref const typeof(this) rhs) { return _payload == rhs._payload; } | } | | 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 C { const(uint)* value; } | | S s; | S r = s; | r = s; | r = S.init; | | alias V = Variant!(S, C); | 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); |} | |/++ |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.to!string == "null"); | | variant = V._void; | assert(variant._is!void); | assert(is(typeof(variant.get!void()) == void)); | | assert(secondOrderVisitorHandler(visitorHandler(variant)) == "VOID"); | assert(variant.to!string == "void"); | | variant = 5; | | assert(secondOrderVisitorHandler(visitorHandler(variant)) == "SO VOID"); | assert(variant == 6); | assert(variant.to!string == (MIR_ALGORITHM ? "6" : "int")); |} | |/++ |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 |{ | struct Asteroid { uint size; } | 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; | struct Asteroid { uint size; } | 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)); | | // not enough information to deduce the type from (ea, es) pair | static assert(is(typeof(collide(ea, es)) == void)); | // 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)); | | // 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 |{ | struct Asteroid { uint size; } | 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 |{ | struct Asteroid { uint size; } | 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) = visitImpl!(getMemberHandler!member, 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 C { alias bar = (double a) => a * 2; enum boolean = false; } | | alias V = Variant!(S, C); | | V x = S(); | V y = C(); | | 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) = visitImpl!(getMemberHandler!member, Exhaustive.compileTime, true); | |/// |@safe pure @nogc nothrow |version(mir_core_test) unittest |{ | static struct S | { | Nullable!int m; | } | | static struct C | { | Variant!(float, double) m; | } | | alias V = Variant!(S, C); | | V x = S(2.nullable); | V y = C(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 C { alias Bar = (double a) => a * 2; } | | alias V = Variant!(S, C); | | V x = S(); | V y = C(); | | 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) = visitImpl!(getMemberHandler!member, 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) = visitImpl!(getMemberHandler!member, 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) = visitImpl!(getMemberHandler!member, 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) = visitImpl!(getMemberHandler!member, 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) = visitImpl!(getMemberHandler!member, Exhaustive.auto_, true); | |private template getMemberHandler(string member) |{ | /// | auto ref getMemberHandler(V, Args...)(ref V value, auto ref Args args) | { | static if (Args.length == 0) | { | return __traits(getMember, value, member); | } | else | { | import core.lifetime: forward; | import mir.reflection: hasField; | 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; | 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, args...) |{ | auto ref visitThis(T, 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, args...) |{ | auto ref visitLast(T, Args...)(auto ref Args args) | { | import core.lifetime: forward; | static if (is(T == void)) | return visitor(forward!(args[1 .. $])); | else | return visitor(args[0].trustedGet!T, forward!(args[1 .. $])); | } |} | |private template visitImpl(alias visitor, Exhaustive exhaustive, bool fused) |{ | import std.meta: anySatisfy, staticMap, AliasSeq; | | /// | auto ref visitImpl(Args...)(auto ref Args args) | { | import core.lifetime: forward; | | static if (!anySatisfy!(isVariant, 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 | throw 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 (!isVariant!(Args[0])) | { | return .visitImpl!(nextVisitor!(visitor, args[0]), exhaustive, fused)(forward!(args[1 .. $])); | } | else | { | static if (fused && anySatisfy!(isVariant, Args[1 .. $])) | { | alias fun = visitThis!(visitor, exhaustive); | } | else | { | static assert (isVariant!(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))) | static if (fused && is(typeof(fun!T(forward!args)) : Algebraic!Types, Types...)) | alias VariantReturnTypesImpl = TryRemoveConst!(typeof(fun!T(forward!args))).AllowedTypes; | else | alias VariantReturnTypesImpl = AliasSeq!(TryRemoveConst!(typeof(fun!T(forward!args)))); | 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 | { | throw variantMemberException; | } | } | default: assert(0); | } | } | } |} | |private string enumKindText()(string[] 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; |} ../../../.dub/packages/mir-core-1.1.82/mir-core/source/mir/algebraic.d is 0% covered <<<<<< EOF # path=./source-mir-bignum-internal-dec2flt_table.lst |/++ |Tables of approximations of powers of ten. |DO NOT MODIFY: Generated by `etc/dec2flt_table.py` |+/ |module mir.bignum.internal.dec2flt_table; | |enum min_p10_e = -512; |enum max_p10_e = 512; | |// |static immutable align(16) ulong[2][1025] p10_coefficients = [ | [0x9049EE32DB23D21C, 0x7132D332E3F204D5], | [0xB45C69BF91ECC6A4, 0x8D7F87FF9CEE860A], | [0xE173842F7667F84C, 0x70DF69FF842A278D], | [0x8CE8329DAA00FB30, 0xC68BA23FB29A58B8], | [0xB0223F45148139FC, 0xB82E8ACF9F40EEE6], | [0xDC2ACF1659A1887B, 0xA63A2D8387112A9F], | [0x899AC16DF804F54D, 0xA7E45C72346ABAA4], | [0xAC0171C9760632A0, 0xD1DD738EC185694D], | [0xD701CE3BD387BF48, 0xC654D07271E6C3A0], | [0x866120E56434D78D, 0xDBF5024787303A44], | [0xA7F9691EBD420D70, 0x12F242D968FC48D5], | [0xD1F7C3666C9290CC, 0x17AED38FC33B5B0A], | [0x833ADA2003DB9A80, 0x8ECD4439DA0518E6], | [0xA40990A804D2811F, 0x7280954850865F20], | [0xCD0BF4D206072167, 0x4F20BA9A64A7F6E8], | [0x8027790343C474E1, 0x917474A07EE8FA51], | [0xA031574414B59219, 0xB5D191C89EA338E5], | [0xC83DAD1519E2F69F, 0xE345F63AC64C071E], | [0xFA4D185A605BB447, 0x9C1773C977DF08E6], | [0x9C702F387C3950AC, 0x218EA85DEAEB6590], | [0xC38C3B069B47A4D7, 0x29F2527565A63EF4], | [0xF46F49C842198E0D, 0xF46EE712BF0FCEB0], | [0x98C58E1D294FF8C8, 0x18C5506BB769E12E], | [0xBEF6F1A473A3F6FA, 0x1EF6A486A544597A], | [0xEEB4AE0D908CF4B9, 0xA6B44DA84E956FD8], | [0x9530ECC87A5818F3, 0x6830B089311D65E7], | [0xBA7D27FA98EE1F30, 0x423CDCAB7D64BF61], | [0xE91C71F93F29A6FC, 0x52CC13D65CBDEF39], | [0x91B1C73BC77A085E, 0xB3BF8C65F9F6B584], | [0xB61E390AB9588A75, 0x20AF6F7F787462E5], | [0xE3A5C74D67AEAD12, 0x68DB4B5F56917B9E], | [0x8E479C9060CD2C2C, 0x81890F1B961AED43], | [0xB1D983B479007736, 0x61EB52E27BA1A893], | [0xDE4FE4A197409504, 0xFA66279B1A8A12B8], | [0x8AF1EEE4FE885D22, 0x7C7FD8C0F0964BB3], | [0xADAE6A9E3E2A746B, 0x1B9FCEF12CBBDEA0], | [0xD91A0545CDB51186, 0xE287C2AD77EAD648], | [0x87B0434BA0912AF4, 0xAD94D9AC6AF2C5ED], | [0xA99C541E88B575B1, 0x98FA101785AF7768], | [0xD40369262AE2D31D, 0xBF38941D671B5542], | [0x848221B7DACDC3F2, 0xF7835C9260711549], | [0xA5A2AA25D18134EE, 0x756433B6F88D5A9C], | [0xCF0B54AF45E1822A, 0x12BD40A4B6B0B143], | [0x816714ED8BACF15A, 0x4BB64866F22E6ECA], | [0xA1C0DA28EE982DB1, 0xDEA3DA80AEBA0A7C], | [0xCA3110B32A3E391D, 0x164CD120DA688D1B], | [0xFCBD54DFF4CDC764, 0x5BE005691102B062], | [0x9DF6550BF9009C9F, 0xB96C0361AAA1AE3D], | [0xC573EA4EF740C3C6, 0x67C7043A154A19CC], | [0xF6D0E4E2B510F4B8, 0x01B8C5489A9CA040], | [0x9A428F0DB12A98F3, 0x01137B4D60A1E428], | [0xC0D332D11D753F30, 0xC1585A20B8CA5D32], | [0xF107FF8564D28EFC, 0xB1AE70A8E6FCF47E], | [0x96A4FFB35F03995D, 0x4F0D0669905E18CF], | [0xBC4E3FA036C47FB5, 0xA2D04803F4759F02], | [0xEB61CF8844759FA2, 0xCB845A04F19306C3], | [0x931D21B52AC983C5, 0x1F32B84316FBE43A], | [0xB7E46A22757BE4B6, 0x66FF6653DCBADD48], | [0xE5DD84AB12DADDE4, 0x00BF3FE8D3E9949A], | [0x8FAA72EAEBC8CAAF, 0x807787F18471FCE1], | [0xB3950FA5A6BAFD5A, 0x209569EDE58E7C19], | [0xE07A538F1069BCB1, 0xA8BAC4695EF21B1F], | [0x8C4C74396A4215EE, 0x6974BAC1DB5750F3], | [0xAF5F9147C4D29B6A, 0x03D1E972522D2530], | [0xDB377599B6074245, 0x84C663CEE6B86E7C], | [0x8902A98011C4896B, 0xD2FBFE615033450E], | [0xAB4353E01635ABC6, 0x87BAFDF9A4401651], | [0xD61428D81BC316B7, 0xE9A9BD780D501BE5], | [0x85CC99871159EE32, 0x520A166B0852116F], | [0xA73FBFE8D5B069BF, 0xE68C9C05CA6695CB], | [0xD10FAFE30B1C842F, 0xA02FC3073D003B3E], | [0x82A9CDEDE6F1D29D, 0x241DD9E486202507], | [0xA354416960AE4744, 0x6D25505DA7A82E48], | [0xCC2951C3B8D9D916, 0x886EA475119239DA], | [0xFF33A634A7104F5B, 0xEA8A4D9255F6C851], | [0x9F8047E0E86A3199, 0xD296707B75BA3D33], | [0xC76059D92284BDFF, 0x073C0C9A5328CC7F], | [0xF938704F6B25ED7F, 0xC90B0FC0E7F2FF9F], | [0x9BC34631A2F7B46F, 0x3DA6E9D890F7DFC3], | [0xC2B417BE0BB5A18B, 0x0D10A44EB535D7B4], | [0xF3611DAD8EA309EE, 0xD054CD6262834DA1], | [0x981CB28C7925E635, 0xA235005D7D921085], | [0xBE23DF2F976F5FC2, 0xCAC24074DCF694A6], | [0xEDACD6FB7D4B37B2, 0x3D72D092143439D0], | [0x948C065D2E4F02CF, 0x6667C25B4CA0A422], | [0xB9AF07F479E2C383, 0x4001B2F21FC8CD2A], | [0xE81AC9F1985B7464, 0x10021FAEA7BB0075], | [0x9110BE36FF3928BF, 0x8A0153CD28D4E049], | [0xB554EDC4BF0772EE, 0x2C81A8C0730A185B], | [0xE2AA2935EEC94FAA, 0xB7A212F08FCC9E72], | [0x8DAA59C1B53DD1CA, 0x12C54BD659DFE307], | [0xB114F032228D463D, 0x97769ECBF057DBC9], | [0xDD5A2C3EAB3097CC, 0xBD54467EEC6DD2BB], | [0x8A585BA72AFE5EDF, 0x5654AC0F53C4A3B5], | [0xACEE7290F5BDF697, 0x2BE9D71328B5CCA2], | [0xD82A0F35332D743D, 0xF6E44CD7F2E33FCB], | [0x871A49813FFC68A6, 0x1A4EB006F7CE07DF], | [0xA8E0DBE18FFB82D0, 0xA0E25C08B5C189D7], | [0xD31912D9F3FA6384, 0x891AF30AE331EC4C], | [0x83EFABC8387C7E32, 0x35B0D7E6CDFF33B0], | [0xA4EB96BA469B9DBF, 0xC31D0DE0817F009C], | [0xCE267C68D842852E, 0x73E45158A1DEC0C2], | [0x80D80DC18729933D, 0x086EB2D7652B387A], | [0xA10E1131E8F3F80C, 0x4A8A5F8D3E760698], | [0xC951957E6330F60F, 0x5D2CF7708E13883E], | [0xFBA5FADDFBFD3393, 0x3478354CB1986A4D], | [0x9D47BCCABD7E403C, 0x00CB214FEEFF4270], | [0xC499ABFD6CDDD04B, 0x00FDE9A3EABF130C], | [0xF5C016FCC815445E, 0xC13D640CE56ED7D0], | [0x99980E5DFD0D4ABB, 0x98C65E880F6546E2], | [0xBFFE11F57C509D69, 0x3EF7F62A133E989A], | [0xEFFD9672DB64C4C4, 0x8EB5F3B4980E3EC1], | [0x95FE7E07C91EFAFA, 0x3931B850DF08E738], | [0xBB7E1D89BB66B9B9, 0xC77E266516CB2106], | [0xEA5DA4EC2A406827, 0xF95DAFFE5C7DE948], | [0x927A87139A684118, 0x5BDA8DFEF9CEB1CD], | [0xB71928D88102515E, 0x72D1317EB8425E40], | [0xE4DF730EA142E5B6, 0x0F857DDE6652F5D0], | [0x8F0BA7E924C9CF92, 0xC9B36EAAFFF3D9A2], | [0xB2CE91E36DFC4376, 0x3C204A55BFF0D00B], | [0xDF82365C497B5454, 0xCB285CEB2FED040E], | [0x8BB161F9ADED14B4, 0x5EF93A12FDF42288], | [0xAE9DBA78196859E1, 0x76B78897BD712B2B], | [0xDA4529161FC2705A, 0xD4656ABDACCD75F5], | [0x886B39ADD3D98638, 0x24BF62B68C0069B9], | [0xAA86081948CFE7C6, 0x2DEF3B642F008428], | [0xD5278A1F9B03E1B8, 0xB96B0A3D3AC0A531], | [0x8538B653C0E26D13, 0xD3E2E66644B8673F], | [0xA686E3E8B11B0858, 0x88DB9FFFD5E6810F], | [0xD0289CE2DD61CA6D, 0x6B1287FFCB602152], | [0x8219620DCA5D1E84, 0x62EB94FFDF1C14D3], | [0xA29FBA913CF46625, 0x7BA67A3FD6E31A08], | [0xCB47A9358C317FAF, 0xDA9018CFCC9BE08A], | [0xFE199382EF3DDF9B, 0x91341F03BFC2D8AD], | [0x9ECFFC31D586ABC1, 0x9AC0936257D9C76C], | [0xC683FB3E4AE856B1, 0xC170B83AEDD03947], | [0xF824FA0DDDA26C5D, 0xF1CCE649A9444799], | [0x9B171C48AA8583BA, 0x17200FEE09CAACC0], | [0xC1DCE35AD526E4A9, 0x9CE813E98C3D57EF], | [0xF2541C318A709DD3, 0xC42218E3EF4CADEB], | [0x9774919EF68662A4, 0xBA954F8E758FECB3], | [0xBD51B606B427FB4D, 0xA93AA37212F3E7E0], | [0xECA623886131FA20, 0xD3894C4E97B0E1D8], | [0x93E7D6353CBF3C54, 0xE435CFB11ECE8D27], | [0xB8E1CBC28BEF0B69, 0xDD43439D66823071], | [0xE71A3EB32EEACE43, 0x14941484C022BC8D], | [0x9070672FFD52C0EA, 0xECDC8CD2F815B5D8], | [0xB48C80FBFCA77124, 0x6813B007B61B234E], | [0xE1AFA13AFBD14D6E, 0x82189C09A3A1EC21], | [0x8D0DC4C4DD62D064, 0x714F618606453395], | [0xB05135F614BB847E, 0x8DA339E787D6807A], | [0xDC65837399EA659D, 0xF10C086169CC2099], | [0x89BF722840327F82, 0x16A7853CE21F945F], | [0xAC2F4EB2503F1F63, 0x9C51668C1AA77977], | [0xD73B225EE44EE73B, 0x4365C02F215157D5], | [0x8684F57B4EB15085, 0x0A1F981D74D2D6E5], | [0xA82632DA225DA4A6, 0x4CA77E24D2078C9E], | [0xD22FBF90AAF50DD0, 0xDFD15DAE06896FC6], | [0x835DD7BA6AD928A2, 0xEBE2DA8CC415E5DC], | [0xA4354DA9058F72CA, 0x66DB912FF51B5F53], | [0xCD42A11346F34F7D, 0x0092757BF2623727], | [0x8049A4AC0C5811AE, 0x205B896D777D6279], | [0xA05C0DD70F6E161A, 0xA8726BC8D55CBB17], | [0xC873114CD3499BA0, 0x128F06BB0AB3E9DD], | [0xFA8FD5A0081C0288, 0x1732C869CD60E454], | [0x9C99E58405118195, 0x0E7FBD42205C8EB4], | [0xC3C05EE50655E1FA, 0x521FAC92A873B261], | [0xF4B0769E47EB5A79, 0xE6A797B752909EFA], | [0x98EE4A22ECF3188C, 0x9028BED2939A635C], | [0xBF29DCABA82FDEAE, 0x7432EE873880FC33], | [0xEEF453D6923BD65A, 0x113FAA2906A13B40], | [0x9558B4661B6565F8, 0x4AC7CA59A424C508], | [0xBAAEE17FA23EBF76, 0x5D79BCF00D2DF64A], | [0xE95A99DF8ACE6F54, 0xF4D82C2C107973DC], | [0x91D8A02BB6C10594, 0x79071B9B8A4BE86A], | [0xB64EC836A47146FA, 0x9748E2826CDEE284], | [0xE3E27A444D8D98B8, 0xFD1B1B2308169B25], | [0x8E6D8C6AB0787F73, 0xFE30F0F5E50E20F7], | [0xB208EF855C969F50, 0xBDBD2D335E51A935], | [0xDE8B2B66B3BC4724, 0xAD2C788035E61382], | [0x8B16FB203055AC76, 0x4C3BCB5021AFCC31], | [0xADDCB9E83C6B1794, 0xDF4ABE242A1BBF3E], | [0xD953E8624B85DD79, 0xD71D6DAD34A2AF0D], | [0x87D4713D6F33AA6C, 0x8672648C40E5AD68], | [0xA9C98D8CCB009506, 0x680EFDAF511F18C2], | [0xD43BF0EFFDC0BA48, 0x0212BD1B2566DEF3], | [0x84A57695FE98746D, 0x014BB630F7604B58], | [0xA5CED43B7E3E9188, 0x419EA3BD35385E2E], | [0xCF42894A5DCE35EA, 0x52064CAC828675B9], | [0x818995CE7AA0E1B2, 0x7343EFEBD1940994], | [0xA1EBFB4219491A1F, 0x1014EBE6C5F90BF9], | [0xCA66FA129F9B60A7, 0xD41A26E077774EF7], | [0xFD00B897478238D1, 0x8920B098955522B5], | [0x9E20735E8CB16382, 0x55B46E5F5D5535B1], | [0xC5A890362FDDBC63, 0xEB2189F734AA831D], | [0xF712B443BBD52B7C, 0xA5E9EC7501D523E4], | [0x9A6BB0AA55653B2D, 0x47B233C92125366F], | [0xC1069CD4EABE89F9, 0x999EC0BB696E840A], | [0xF148440A256E2C77, 0xC00670EA43CA250D], | [0x96CD2A865764DBCA, 0x380406926A5E5728], | [0xBC807527ED3E12BD, 0xC605083704F5ECF2], | [0xEBA09271E88D976C, 0xF7864A44C633682F], | [0x93445B8731587EA3, 0x7AB3EE6AFBE0211D], | [0xB8157268FDAE9E4C, 0x5960EA05BAD82965], | [0xE61ACF033D1A45DF, 0x6FB92487298E33BE], | [0x8FD0C16206306BAC, 0xA5D3B6D479F8E057], | [0xB3C4F1BA87BC8697, 0x8F48A4899877186C], | [0xE0B62E2929ABA83C, 0x331ACDABFE94DE87], | [0x8C71DCD9BA0B4926, 0x9FF0C08B7F1D0B15], | [0xAF8E5410288E1B6F, 0x07ECF0AE5EE44DDA], | [0xDB71E91432B1A24B, 0xC9E82CD9F69D6150], | [0x892731AC9FAF056F, 0xBE311C083A225CD2], | [0xAB70FE17C79AC6CA, 0x6DBD630A48AAF407], | [0xD64D3D9DB981787D, 0x092CBBCCDAD5B108], | [0x85F0468293F0EB4E, 0x25BBF56008C58EA5], | [0xA76C582338ED2622, 0xAF2AF2B80AF6F24E], | [0xD1476E2C07286FAA, 0x1AF5AF660DB4AEE2], | [0x82CCA4DB847945CA, 0x50D98D9FC890ED4D], | [0xA37FCE126597973D, 0xE50FF107BAB528A1], | [0xCC5FC196FEFD7D0C, 0x1E53ED49A96272C9], | [0xFF77B1FCBEBCDC4F, 0x25E8E89C13BB0F7B], | [0x9FAACF3DF73609B1, 0x77B191618C54E9AD], | [0xC795830D75038C1E, 0xD59DF5B9EF6A2418], | [0xF97AE3D0D2446F25, 0x4B0573286B44AD1E], | [0x9BECCE62836AC577, 0x4EE367F9430AEC33], | [0xC2E801FB244576D5, 0x229C41F793CDA73F], | [0xF3A20279ED56D48A, 0x6B43527578C1110F], | [0x9845418C345644D7, 0x830A13896B78AAAA], | [0xBE5691EF416BD60C, 0x23CC986BC656D554], | [0xEDEC366B11C6CB8F, 0x2CBFBE86B7EC8AA9], | [0x94B3A202EB1C3F39, 0x7BF7D71432F3D6AA], | [0xB9E08A83A5E34F08, 0xDAF5CCD93FB0CC54], | [0xE858AD248F5C22CA, 0xD1B3400F8F9CFF69], | [0x91376C36D99995BE, 0x23100809B9C21FA2], | [0xB58547448FFFFB2E, 0xABD40A0C2832A78A], | [0xE2E69915B3FFF9F9, 0x16C90C8F323F516D], | [0x8DD01FAD907FFC3C, 0xAE3DA7D97F6792E4], | [0xB1442798F49FFB4B, 0x99CD11CFDF41779D], | [0xDD95317F31C7FA1D, 0x40405643D711D584], | [0x8A7D3EEF7F1CFC52, 0x482835EA666B2572], | [0xAD1C8EAB5EE43B67, 0xDA3243650005EECF], | [0xD863B256369D4A41, 0x90BED43E40076A83], | [0x873E4F75E2224E68, 0x5A7744A6E804A292], | [0xA90DE3535AAAE202, 0x711515D0A205CB36], | [0xD3515C2831559A83, 0x0D5A5B44CA873E04], | [0x8412D9991ED58092, 0xE858790AFE9486C2], | [0xA5178FFF668AE0B6, 0x626E974DBE39A873], | [0xCE5D73FF402D98E4, 0xFB0A3D212DC81290], | [0x80FA687F881C7F8E, 0x7CE66634BC9D0B9A], | [0xA139029F6A239F72, 0x1C1FFFC1EBC44E80], | [0xC987434744AC874F, 0xA327FFB266B56220], | [0xFBE9141915D7A922, 0x4BF1FF9F0062BAA8], | [0x9D71AC8FADA6C9B5, 0x6F773FC3603DB4A9], | [0xC4CE17B399107C23, 0xCB550FB4384D21D4], | [0xF6019DA07F549B2B, 0x7E2A53A146606A48], | [0x99C102844F94E0FB, 0x2EDA7444CBFC426D], | [0xC0314325637A193A, 0xFA911155FEFB5309], | [0xF03D93EEBC589F88, 0x793555AB7EBA27CB], | [0x96267C7535B763B5, 0x4BC1558B2F3458DF], | [0xBBB01B9283253CA3, 0x9EB1AAEDFB016F16], | [0xEA9C227723EE8BCB, 0x465E15A979C1CADC], | [0x92A1958A7675175F, 0x0BFACD89EC191ECA], | [0xB749FAED14125D37, 0xCEF980EC671F667C], | [0xE51C79A85916F485, 0x82B7E12780E7401B], | [0x8F31CC0937AE58D3, 0xD1B2ECB8B0908811], | [0xB2FE3F0B8599EF08, 0x861FA7E6DCB4AA15], | [0xDFBDCECE67006AC9, 0x67A791E093E1D49A], | [0x8BD6A141006042BE, 0xE0C8BB2C5C6D24E0], | [0xAECC49914078536D, 0x58FAE9F773886E19], | [0xDA7F5BF590966849, 0xAF39A475506A899F], | [0x888F99797A5E012D, 0x6D8406C952429603], | [0xAAB37FD7D8F58179, 0xC8E5087BA6D33B84], | [0xD5605FCDCF32E1D7, 0xFB1E4A9A90880A65], | [0x855C3BE0A17FCD26, 0x5CF2EEA09A55067F], | [0xA6B34AD8C9DFC070, 0xF42FAA48C0EA481F], | [0xD0601D8EFC57B08C, 0xF13B94DAF124DA27], | [0x823C12795DB6CE57, 0x76C53D08D6B70858], | [0xA2CB1717B52481ED, 0x54768C4B0C64CA6E], | [0xCB7DDCDDA26DA269, 0xA9942F5DCF7DFD0A], | [0xFE5D54150B090B03, 0xD3F93B35435D7C4C], | [0x9EFA548D26E5A6E2, 0xC47BC5014A1A6DB0], | [0xC6B8E9B0709F109A, 0x359AB6419CA1091B], | [0xF867241C8CC6D4C1, 0xC30163D203C94B62], | [0x9B407691D7FC44F8, 0x79E0DE63425DCF1D], | [0xC21094364DFB5637, 0x985915FC12F542E5], | [0xF294B943E17A2BC4, 0x3E6F5B7B17B2939E], | [0x979CF3CA6CEC5B5B, 0xA705992CEECF9C43], | [0xBD8430BD08277231, 0x50C6FF782A838353], | [0xECE53CEC4A314EBE, 0xA4F8BF5635246428], | [0x940F4613AE5ED137, 0x871B7795E136BE99], | [0xB913179899F68584, 0x28E2557B59846E3F], | [0xE757DD7EC07426E5, 0x331AEADA2FE589CF], | [0x9096EA6F3848984F, 0x3FF0D2C85DEF7622], | [0xB4BCA50B065ABE63, 0x0FED077A756B53AA], | [0xE1EBCE4DC7F16DFC, 0xD3E8495912C62894], | [0x8D3360F09CF6E4BD, 0x64712DD7ABBBD95D], | [0xB080392CC4349DED, 0xBD8D794D96AACFB4], | [0xDCA04777F541C568, 0xECF0D7A0FC5583A1], | [0x89E42CAAF9491B61, 0xF41686C49DB57245], | [0xAC5D37D5B79B6239, 0x311C2875C522CED6], | [0xD77485CB25823AC7, 0x7D633293366B828B], | [0x86A8D39EF77164BD, 0xAE5DFF9C02033197], | [0xA8530886B54DBDEC, 0xD9F57F830283FDFD], | [0xD267CAA862A12D67, 0xD072DF63C324FD7C], | [0x8380DEA93DA4BC60, 0x4247CB9E59F71E6D], | [0xA46116538D0DEB78, 0x52D9BE85F074E609], | [0xCD795BE870516656, 0x67902E276C921F8B], | [0x806BD9714632DFF6, 0x00BA1CD8A3DB53B7], | [0xA086CFCD97BF97F4, 0x80E8A40ECCD228A5], | [0xC8A883C0FDAF7DF0, 0x6122CD128006B2CE], | [0xFAD2A4B13D1B5D6C, 0x796B805720085F81], | [0x9CC3A6EEC6311A64, 0xCBE3303674053BB1], | [0xC3F490AA77BD60FD, 0xBEDBFC4411068A9D], | [0xF4F1B4D515ACB93C, 0xEE92FB5515482D44], | [0x991711052D8BF3C5, 0x751BDD152D4D1C4B], | [0xBF5CD54678EEF0B7, 0xD262D45A78A0635D], | [0xEF340A98172AACE5, 0x86FB897116C87C35], | [0x9580869F0E7AAC0F, 0xD45D35E6AE3D4DA1], | [0xBAE0A846D2195713, 0x8974836059CCA109], | [0xE998D258869FACD7, 0x2BD1A438703FC94B], | [0x91FF83775423CC06, 0x7B6306A34627DDCF], | [0xB67F6455292CBF08, 0x1A3BC84C17B1D543], | [0xE41F3D6A7377EECA, 0x20CABA5F1D9E4A94], | [0x8E938662882AF53E, 0x547EB47B7282EE9C], | [0xB23867FB2A35B28E, 0xE99E619A4F23AA43], | [0xDEC681F9F4C31F31, 0x6405FA00E2EC94D4], | [0x8B3C113C38F9F37F, 0xDE83BC408DD3DD05], | [0xAE0B158B4738705F, 0x9624AB50B148D446], | [0xD98DDAEE19068C76, 0x3BADD624DD9B0957], | [0x87F8A8D4CFA417CA, 0xE54CA5D70A80E5D6], | [0xA9F6D30A038D1DBC, 0x5E9FCF4CCD211F4C], | [0xD47487CC8470652B, 0x7647C3200069671F], | [0x84C8D4DFD2C63F3B, 0x29ECD9F40041E073], | [0xA5FB0A17C777CF0A, 0xF468107100525890], | [0xCF79CC9DB955C2CC, 0x7182148D4066EEB4], | [0x81AC1FE293D599C0, 0xC6F14CD848405531], | [0xA21727DB38CB0030, 0xB8ADA00E5A506A7D], | [0xCA9CF1D206FDC03C, 0xA6D90811F0E4851C], | [0xFD442E4688BD304B, 0x908F4A166D1DA663], | [0x9E4A9CEC15763E2F, 0x9A598E4E043287FE], | [0xC5DD44271AD3CDBA, 0x40EFF1E1853F29FE], | [0xF7549530E188C129, 0xD12BEE59E68EF47D], | [0x9A94DD3E8CF578BA, 0x82BB74F8301958CE], | [0xC13A148E3032D6E8, 0xE36A52363C1FAF02], | [0xF18899B1BC3F8CA2, 0xDC44E6C3CB279AC2], | [0x96F5600F15A7B7E5, 0x29AB103A5EF8C0B9], | [0xBCB2B812DB11A5DE, 0x7415D448F6B6F0E8], | [0xEBDF661791D60F56, 0x111B495B3464AD21], | [0x936B9FCEBB25C996, 0xCAB10DD900BEEC35], | [0xB84687C269EF3BFB, 0x3D5D514F40EEA742], | [0xE65829B3046B0AFA, 0x0CB4A5A3112A5113], | [0x8FF71A0FE2C2E6DC, 0x47F0E785EABA72AC], | [0xB3F4E093DB73A093, 0x59ED216765690F57], | [0xE0F218B8D25088B8, 0x306869C13EC3532C], | [0x8C974F7383725573, 0x1E414218C73A13FC], | [0xAFBD2350644EEAD0, 0xE5D1929EF90898FB], | [0xDBAC6C247D62A584, 0xDF45F746B74ABF39], | [0x894BC396CE5DA772, 0x6B8BBA8C328EB784], | [0xAB9EB47C81F5114F, 0x066EA92F3F326565], | [0xD686619BA27255A3, 0xC80A537B0EFEFEBE], | [0x8613FD0145877586, 0xBD06742CE95F5F37], | [0xA798FC4196E952E7, 0x2C48113823B73704], | [0xD17F3B51FCA3A7A1, 0xF75A15862CA504C5], | [0x82EF85133DE648C5, 0x9A984D73DBE722FB], | [0xA3AB66580D5FDAF6, 0xC13E60D0D2E0EBBA], | [0xCC963FEE10B7D1B3, 0x318DF905079926A9], | [0xFFBBCFE994E5C620, 0xFDF17746497F7053], | [0x9FD561F1FD0F9BD4, 0xFEB6EA8BEDEFA634], | [0xC7CABA6E7C5382C9, 0xFE64A52EE96B8FC1], | [0xF9BD690A1B68637B, 0x3DFDCE7AA3C673B1], | [0x9C1661A651213E2D, 0x06BEA10CA65C084F], | [0xC31BFA0FE5698DB8, 0x486E494FCFF30A62], | [0xF3E2F893DEC3F126, 0x5A89DBA3C3EFCCFB], | [0x986DDB5C6B3A76B8, 0xF89629465A75E01D], | [0xBE89523386091466, 0xF6BBB397F1135824], | [0xEE2BA6C0678B597F, 0x746AA07DED582E2D], | [0x94DB483840B717F0, 0xA8C2A44EB4571CDC], | [0xBA121A4650E4DDEC, 0x92F34D62616CE413], | [0xE896A0D7E51E1566, 0x77B020BAF9C81D18], | [0x915E2486EF32CD60, 0x0ACE1474DC1D122F], | [0xB5B5ADA8AAFF80B8, 0x0D819992132456BB], | [0xE3231912D5BF60E6, 0x10E1FFF697ED6C69], | [0x8DF5EFABC5979C90, 0xCA8D3FFA1EF463C2], | [0xB1736B96B6FD83B4, 0xBD308FF8A6B17CB2], | [0xDDD0467C64BCE4A1, 0xAC7CB3F6D05DDBDF], | [0x8AA22C0DBEF60EE4, 0x6BCDF07A423AA96B], | [0xAD4AB7112EB3929E, 0x86C16C98D2C953C6], | [0xD89D64D57A607745, 0xE871C7BF077BA8B8], | [0x87625F056C7C4A8B, 0x11471CD764AD4973], | [0xA93AF6C6C79B5D2E, 0xD598E40D3DD89BCF], | [0xD389B47879823479, 0x4AFF1D108D4EC2C3], | [0x843610CB4BF160CC, 0xCEDF722A585139BA], | [0xA54394FE1EEDB8FF, 0xC2974EB4EE658829], | [0xCE947A3DA6A9273E, 0x733D226229FEEA33], | [0x811CCC668829B887, 0x0806357D5A3F5260], | [0xA163FF802A3426A9, 0xCA07C2DCB0CF26F8], | [0xC9BCFF6034C13053, 0xFC89B393DD02F0B6], | [0xFC2C3F3841F17C68, 0xBBAC2078D443ACE3], | [0x9D9BA7832936EDC1, 0xD54B944B84AA4C0E], | [0xC5029163F384A931, 0x0A9E795E65D4DF11], | [0xF64335BCF065D37D, 0x4D4617B5FF4A16D6], | [0x99EA0196163FA42E, 0x504BCED1BF8E4E46], | [0xC06481FB9BCF8D3A, 0xE45EC2862F71E1D7], | [0xF07DA27A82C37088, 0x5D767327BB4E5A4D], | [0x964E858C91BA2655, 0x3A6A07F8D510F870], | [0xBBE226EFB628AFEB, 0x890489F70A55368C], | [0xEADAB0ABA3B2DBE5, 0x2B45AC74CCEA842F], | [0x92C8AE6B464FC96F, 0x3B0B8BC90012929D], | [0xB77ADA0617E3BBCB, 0x09CE6EBB40173745], | [0xE55990879DDCAABE, 0xCC420A6A101D0516], | [0x8F57FA54C2A9EAB7, 0x9FA946824A12232E], | [0xB32DF8E9F3546564, 0x47939822DC96ABF9], | [0xDFF9772470297EBD, 0x59787E2B93BC56F7], | [0x8BFBEA76C619EF36, 0x57EB4EDB3C55B65B], | [0xAEFAE51477A06B04, 0xEDE622920B6B23F1], | [0xDAB99E59958885C5, 0xE95FAB368E45ECED], | [0x88B402F7FD75539B, 0x11DBCB0218EBB414], | [0xAAE103B5FCD2A882, 0xD652BDC29F26A11A], | [0xD59944A37C0752A2, 0x4BE76D3346F04960], | [0x857FCAE62D8493A5, 0x6F70A4400C562DDC], | [0xA6DFBD9FB8E5B88F, 0xCB4CCD500F6BB953], | [0xD097AD07A71F26B2, 0x7E2000A41346A7A8], | [0x825ECC24C8737830, 0x8ED400668C0C28C9], | [0xA2F67F2DFA90563B, 0x728900802F0F32FB], | [0xCBB41EF979346BCA, 0x4F2B40A03AD2FFBA], | [0xFEA126B7D78186BD, 0xE2F610C84987BFA8], | [0x9F24B832E6B0F436, 0x0DD9CA7D2DF4D7C9], | [0xC6EDE63FA05D3144, 0x91503D1C79720DBB], | [0xF8A95FCF88747D94, 0x75A44C6397CE912A], | [0x9B69DBE1B548CE7D, 0xC986AFBE3EE11ABA], | [0xC24452DA229B021C, 0xFBE85BADCE996169], | [0xF2D56790AB41C2A3, 0xFAE27299423FB9C3], | [0x97C560BA6B0919A6, 0xDCCD879FC967D41A], | [0xBDB6B8E905CB600F, 0x5400E987BBC1C921], | [0xED246723473E3813, 0x290123E9AAB23B69], | [0x9436C0760C86E30C, 0xF9A0B6720AAF6521], | [0xB94470938FA89BCF, 0xF808E40E8D5B3E6A], | [0xE7958CB87392C2C3, 0xB60B1D1230B20E04], | [0x90BD77F3483BB9BA, 0xB1C6F22B5E6F48C3], | [0xB4ECD5F01A4AA828, 0x1E38AEB6360B1AF3], | [0xE2280B6C20DD5232, 0x25C6DA63C38DE1B0], | [0x8D590723948A535F, 0x579C487E5A38AD0E], | [0xB0AF48EC79ACE837, 0x2D835A9DF0C6D852], | [0xDCDB1B2798182245, 0xF8E431456CF88E66], | [0x8A08F0F8BF0F156B, 0x1B8E9ECB641B5900], | [0xAC8B2D36EED2DAC6, 0xE272467E3D222F40], | [0xD7ADF884AA879177, 0x5B0ED81DCC6ABB10], | [0x86CCBB52EA94BAEB, 0x98E947129FC2B4EA], | [0xA87FEA27A539E9A5, 0x3F2398D747B36224], | [0xD29FE4B18E88640F, 0x8EEC7F0D19A03AAD], | [0x83A3EEEEF9153E89, 0x1953CF68300424AC], | [0xA48CEAAAB75A8E2B, 0x5FA8C3423C052DD7], | [0xCDB02555653131B6, 0x3792F412CB06794D], | [0x808E17555F3EBF12, 0xE2BBD88BBEE40BD0], | [0xA0B19D2AB70E6ED6, 0x5B6ACEAEAE9D0EC4], | [0xC8DE047564D20A8C, 0xF245825A5A445275], | [0xFB158592BE068D2F, 0xEED6E2F0F0D56713], | [0x9CED737BB6C4183D, 0x55464DD69685606C], | [0xC428D05AA4751E4D, 0xAA97E14C3C26B887], | [0xF53304714D9265E0, 0xD53DD99F4B3066A8], | [0x993FE2C6D07B7FAC, 0xE546A8038EFE4029], | [0xBF8FDB78849A5F97, 0xDE98520472BDD033], | [0xEF73D256A5C0F77D, 0x963E66858F6D4440], | [0x95A8637627989AAE, 0xDDE7001379A44AA8], | [0xBB127C53B17EC159, 0x5560C018580D5D52], | [0xE9D71B689DDE71B0, 0xAAB8F01E6E10B4A7], | [0x9226712162AB070E, 0xCAB3961304CA70E8], | [0xB6B00D69BB55C8D1, 0x3D607B97C5FD0D22], | [0xE45C10C42A2B3B06, 0x8CB89A7DB77C506B], | [0x8EB98A7A9A5B04E3, 0x77F3608E92ADB243], | [0xB267ED1940F1C61C, 0x55F038B237591ED3], | [0xDF01E85F912E37A3, 0x6B6C46DEC52F6688], | [0x8B61313BBABCE2C6, 0x2323AC4B3B3DA015], | [0xAE397D8AA96C1B78, 0xABEC975E0A0D081B], | [0xD9C7DCED53C72256, 0x96E7BD358C904A21], | [0x881CEA14545C7575, 0x7E50D64177DA2E55], | [0xAA242499697392D3, 0xDDE50BD1D5D0B9EA], | [0xD4AD2DBFC3D07788, 0x955E4EC64B44E864], | [0x84EC3C97DA624AB5, 0xBD5AF13BEF0B113F], | [0xA6274BBDD0FADD62, 0xECB1AD8AEACDD58E], | [0xCFB11EAD453994BA, 0x67DE18EDA5814AF2], | [0x81CEB32C4B43FCF5, 0x80EACF948770CED7], | [0xA2425FF75E14FC32, 0xA1258379A94D028D], | [0xCAD2F7F5359A3B3E, 0x096EE45813A04330], | [0xFD87B5F28300CA0E, 0x8BCA9D6E188853FC], | [0x9E74D1B791E07E48, 0x775EA264CF55347E], | [0xC612062576589DDB, 0x95364AFE032A819D], | [0xF79687AED3EEC551, 0x3A83DDBD83F52205], | [0x9ABE14CD44753B53, 0xC4926A9672793543], | [0xC16D9A0095928A27, 0x75B7053C0F178294], | [0xF1C90080BAF72CB1, 0x5324C68B12DD6338], | [0x971DA05074DA7BEF, 0xD3F6FC16EBCA5E03], | [0xBCE5086492111AEB, 0x88F4BB1CA6BCF584], | [0xEC1E4A7DB69561A5, 0x2B31E9E3D06C32E5], | [0x9392EE8E921D5D07, 0x3AFF322E62439FCF], | [0xB877AA3236A4B449, 0x09BEFEB9FAD487C3], | [0xE69594BEC44DE15B, 0x4C2EBE687989A9B4], | [0x901D7CF73AB0ACD9, 0x0F9D37014BF60A10], | [0xB424DC35095CD80F, 0x538484C19EF38C94], | [0xE12E13424BB40E13, 0x2865A5F206B06FBA], | [0x8CBCCC096F5088CC, 0xF93F87B7442E45D4], | [0xAFEBFF0BCB24AAFF, 0xF78F69A51539D749], | [0xDBE6FECEBDEDD5BF, 0xB573440E5A884D1B], | [0x89705F4136B4A597, 0x31680A88F8953031], | [0xABCC77118461CEFD, 0xFDC20D2B36BA7C3D], | [0xD6BF94D5E57A42BC, 0x3D32907604691B4D], | [0x8637BD05AF6C69B6, 0xA63F9A49C2C1B110], | [0xA7C5AC471B478423, 0x0FCF80DC33721D54], | [0xD1B71758E219652C, 0xD3C36113404EA4A9], | [0x83126E978D4FDF3B, 0x645A1CAC083126E9], | [0xA3D70A3D70A3D70A, 0x3D70A3D70A3D70A4], | [0xCCCCCCCCCCCCCCCD, 0xCCCCCCCCCCCCCCCD], | [0x8000000000000000, 0x0000000000000000], | [0xA000000000000000, 0x0000000000000000], | [0xC800000000000000, 0x0000000000000000], | [0xFA00000000000000, 0x0000000000000000], | [0x9C40000000000000, 0x0000000000000000], | [0xC350000000000000, 0x0000000000000000], | [0xF424000000000000, 0x0000000000000000], | [0x9896800000000000, 0x0000000000000000], | [0xBEBC200000000000, 0x0000000000000000], | [0xEE6B280000000000, 0x0000000000000000], | [0x9502F90000000000, 0x0000000000000000], | [0xBA43B74000000000, 0x0000000000000000], | [0xE8D4A51000000000, 0x0000000000000000], | [0x9184E72A00000000, 0x0000000000000000], | [0xB5E620F480000000, 0x0000000000000000], | [0xE35FA931A0000000, 0x0000000000000000], | [0x8E1BC9BF04000000, 0x0000000000000000], | [0xB1A2BC2EC5000000, 0x0000000000000000], | [0xDE0B6B3A76400000, 0x0000000000000000], | [0x8AC7230489E80000, 0x0000000000000000], | [0xAD78EBC5AC620000, 0x0000000000000000], | [0xD8D726B7177A8000, 0x0000000000000000], | [0x878678326EAC9000, 0x0000000000000000], | [0xA968163F0A57B400, 0x0000000000000000], | [0xD3C21BCECCEDA100, 0x0000000000000000], | [0x84595161401484A0, 0x0000000000000000], | [0xA56FA5B99019A5C8, 0x0000000000000000], | [0xCECB8F27F4200F3A, 0x0000000000000000], | [0x813F3978F8940984, 0x4000000000000000], | [0xA18F07D736B90BE5, 0x5000000000000000], | [0xC9F2C9CD04674EDF, 0xA400000000000000], | [0xFC6F7C4045812296, 0x4D00000000000000], | [0x9DC5ADA82B70B59E, 0xF020000000000000], | [0xC5371912364CE305, 0x6C28000000000000], | [0xF684DF56C3E01BC7, 0xC732000000000000], | [0x9A130B963A6C115C, 0x3C7F400000000000], | [0xC097CE7BC90715B3, 0x4B9F100000000000], | [0xF0BDC21ABB48DB20, 0x1E86D40000000000], | [0x96769950B50D88F4, 0x1314448000000000], | [0xBC143FA4E250EB31, 0x17D955A000000000], | [0xEB194F8E1AE525FD, 0x5DCFAB0800000000], | [0x92EFD1B8D0CF37BE, 0x5AA1CAE500000000], | [0xB7ABC627050305AE, 0xF14A3D9E40000000], | [0xE596B7B0C643C719, 0x6D9CCD05D0000000], | [0x8F7E32CE7BEA5C70, 0xE4820023A2000000], | [0xB35DBF821AE4F38C, 0xDDA2802C8A800000], | [0xE0352F62A19E306F, 0xD50B2037AD200000], | [0x8C213D9DA502DE45, 0x4526F422CC340000], | [0xAF298D050E4395D7, 0x9670B12B7F410000], | [0xDAF3F04651D47B4C, 0x3C0CDD765F114000], | [0x88D8762BF324CD10, 0xA5880A69FB6AC800], | [0xAB0E93B6EFEE0054, 0x8EEA0D047A457A00], | [0xD5D238A4ABE98068, 0x72A4904598D6D880], | [0x85A36366EB71F041, 0x47A6DA2B7F864750], | [0xA70C3C40A64E6C52, 0x999090B65F67D924], | [0xD0CF4B50CFE20766, 0xFFF4B4E3F741CF6D], | [0x82818F1281ED44A0, 0xBFF8F10E7A8921A4], | [0xA321F2D7226895C8, 0xAFF72D52192B6A0D], | [0xCBEA6F8CEB02BB3A, 0x9BF4F8A69F764490], | [0xFEE50B7025C36A08, 0x02F236D04753D5B5], | [0x9F4F2726179A2245, 0x01D762422C946591], | [0xC722F0EF9D80AAD6, 0x424D3AD2B7B97EF5], | [0xF8EBAD2B84E0D58C, 0xD2E0898765A7DEB2], | [0x9B934C3B330C8577, 0x63CC55F49F88EB2F], | [0xC2781F49FFCFA6D5, 0x3CBF6B71C76B25FB], | [0xF316271C7FC3908B, 0x8BEF464E3945EF7A], | [0x97EDD871CFDA3A57, 0x97758BF0E3CBB5AC], | [0xBDE94E8E43D0C8EC, 0x3D52EEED1CBEA317], | [0xED63A231D4C4FB27, 0x4CA7AAA863EE4BDD], | [0x945E455F24FB1CF9, 0x8FE8CAA93E74EF6A], | [0xB975D6B6EE39E437, 0xB3E2FD538E122B45], | [0xE7D34C64A9C85D44, 0x60DBBCA87196B616], | [0x90E40FBEEA1D3A4B, 0xBC8955E946FE31CE], | [0xB51D13AEA4A488DD, 0x6BABAB6398BDBE41], | [0xE264589A4DCDAB15, 0xC696963C7EED2DD2], | [0x8D7EB76070A08AED, 0xFC1E1DE5CF543CA3], | [0xB0DE65388CC8ADA8, 0x3B25A55F43294BCC], | [0xDD15FE86AFFAD912, 0x49EF0EB713F39EBF], | [0x8A2DBF142DFCC7AB, 0x6E3569326C784337], | [0xACB92ED9397BF996, 0x49C2C37F07965405], | [0xD7E77A8F87DAF7FC, 0xDC33745EC97BE906], | [0x86F0AC99B4E8DAFD, 0x69A028BB3DED71A4], | [0xA8ACD7C0222311BD, 0xC40832EA0D68CE0D], | [0xD2D80DB02AABD62C, 0xF50A3FA490C30190], | [0x83C7088E1AAB65DB, 0x792667C6DA79E0FA], | [0xA4B8CAB1A1563F52, 0x577001B891185939], | [0xCDE6FD5E09ABCF27, 0xED4C0226B55E6F87], | [0x80B05E5AC60B6178, 0x544F8158315B05B4], | [0xA0DC75F1778E39D6, 0x696361AE3DB1C721], | [0xC913936DD571C84C, 0x03BC3A19CD1E38EA], | [0xFB5878494ACE3A5F, 0x04AB48A04065C724], | [0x9D174B2DCEC0E47B, 0x62EB0D64283F9C76], | [0xC45D1DF942711D9A, 0x3BA5D0BD324F8394], | [0xF5746577930D6501, 0xCA8F44EC7EE36479], | [0x9968BF6ABBE85F20, 0x7E998B13CF4E1ECC], | [0xBFC2EF456AE276E9, 0x9E3FEDD8C321A67F], | [0xEFB3AB16C59B14A3, 0xC5CFE94EF3EA101E], | [0x95D04AEE3B80ECE6, 0xBBA1F1D158724A13], | [0xBB445DA9CA61281F, 0x2A8A6E45AE8EDC98], | [0xEA1575143CF97227, 0xF52D09D71A3293BE], | [0x924D692CA61BE758, 0x593C2626705F9C56], | [0xB6E0C377CFA2E12E, 0x6F8B2FB00C77836C], | [0xE498F455C38B997A, 0x0B6DFB9C0F956447], | [0x8EDF98B59A373FEC, 0x4724BD4189BD5EAC], | [0xB2977EE300C50FE7, 0x58EDEC91EC2CB658], | [0xDF3D5E9BC0F653E1, 0x2F2967B66737E3ED], | [0x8B865B215899F46D, 0xBD79E0D20082EE74], | [0xAE67F1E9AEC07188, 0xECD8590680A3AA11], | [0xDA01EE641A708DEA, 0xE80E6F4820CC9496], | [0x884134FE908658B2, 0x3109058D147FDCDE], | [0xAA51823E34A7EEDF, 0xBD4B46F0599FD415], | [0xD4E5E2CDC1D1EA96, 0x6C9E18AC7007C91A], | [0x850FADC09923329E, 0x03E2CF6BC604DDB0], | [0xA6539930BF6BFF46, 0x84DB8346B786151D], | [0xCFE87F7CEF46FF17, 0xE612641865679A64], | [0x81F14FAE158C5F6E, 0x4FCB7E8F3F60C07E], | [0xA26DA3999AEF774A, 0xE3BE5E330F38F09E], | [0xCB090C8001AB551C, 0x5CADF5BFD3072CC5], | [0xFDCB4FA002162A63, 0x73D9732FC7C8F7F7], | [0x9E9F11C4014DDA7E, 0x2867E7FDDCDD9AFA], | [0xC646D63501A1511E, 0xB281E1FD541501B9], | [0xF7D88BC24209A565, 0x1F225A7CA91A4227], | [0x9AE757596946075F, 0x3375788DE9B06958], | [0xC1A12D2FC3978937, 0x0052D6B1641C83AE], | [0xF209787BB47D6B85, 0xC0678C5DBD23A49A], | [0x9745EB4D50CE6333, 0xF840B7BA963646E0], | [0xBD176620A501FC00, 0xB650E5A93BC3D898], | [0xEC5D3FA8CE427B00, 0xA3E51F138AB4CEBE], | [0x93BA47C980E98CE0, 0xC66F336C36B10137], | [0xB8A8D9BBE123F018, 0xB80B0047445D4185], | [0xE6D3102AD96CEC1E, 0xA60DC059157491E6], | [0x9043EA1AC7E41393, 0x87C89837AD68DB30], | [0xB454E4A179DD1877, 0x29BABE4598C311FC], | [0xE16A1DC9D8545E95, 0xF4296DD6FEF3D67B], | [0x8CE2529E2734BB1D, 0x1899E4A65F58660D], | [0xB01AE745B101E9E4, 0x5EC05DCFF72E7F90], | [0xDC21A1171D42645D, 0x76707543F4FA1F74], | [0x899504AE72497EBA, 0x6A06494A791C53A8], | [0xABFA45DA0EDBDE69, 0x0487DB9D17636892], | [0xD6F8D7509292D603, 0x45A9D2845D3C42B7], | [0x865B86925B9BC5C2, 0x0B8A2392BA45A9B2], | [0xA7F26836F282B733, 0x8E6CAC7768D7141F], | [0xD1EF0244AF2364FF, 0x3207D795430CD927], | [0x8335616AED761F1F, 0x7F44E6BD49E807B8], | [0xA402B9C5A8D3A6E7, 0x5F16206C9C6209A6], | [0xCD036837130890A1, 0x36DBA887C37A8C10], | [0x802221226BE55A65, 0xC2494954DA2C978A], | [0xA02AA96B06DEB0FE, 0xF2DB9BAA10B7BD6C], | [0xC83553C5C8965D3D, 0x6F92829494E5ACC7], | [0xFA42A8B73ABBF48D, 0xCB772339BA1F17F9], | [0x9C69A97284B578D8, 0xFF2A760414536EFC], | [0xC38413CF25E2D70E, 0xFEF5138519684ABB], | [0xF46518C2EF5B8CD1, 0x7EB258665FC25D69], | [0x98BF2F79D5993803, 0xEF2F773FFBD97A62], | [0xBEEEFB584AFF8604, 0xAAFB550FFACFD8FA], | [0xEEAABA2E5DBF6785, 0x95BA2A53F983CF39], | [0x952AB45CFA97A0B3, 0xDD945A747BF26184], | [0xBA756174393D88E0, 0x94F971119AEEF9E4], | [0xE912B9D1478CEB17, 0x7A37CD5601AAB85E], | [0x91ABB422CCB812EF, 0xAC62E055C10AB33B], | [0xB616A12B7FE617AA, 0x577B986B314D6009], | [0xE39C49765FDF9D95, 0xED5A7E85FDA0B80B], | [0x8E41ADE9FBEBC27D, 0x14588F13BE847307], | [0xB1D219647AE6B31C, 0x596EB2D8AE258FC9], | [0xDE469FBD99A05FE3, 0x6FCA5F8ED9AEF3BB], | [0x8AEC23D680043BEE, 0x25DE7BB9480D5855], | [0xADA72CCC20054AEA, 0xAF561AA79A10AE6A], | [0xD910F7FF28069DA4, 0x1B2BA1518094DA05], | [0x87AA9AFF79042287, 0x90FB44D2F05D0843], | [0xA99541BF57452B28, 0x353A1607AC744A54], | [0xD3FA922F2D1675F2, 0x42889B8997915CE9], | [0x847C9B5D7C2E09B7, 0x69956135FEBADA11], | [0xA59BC234DB398C25, 0x43FAB9837E699096], | [0xCF02B2C21207EF2F, 0x94F967E45E03F4BB], | [0x8161AFB94B44F57D, 0x1D1BE0EEBAC278F5], | [0xA1BA1BA79E1632DC, 0x6462D92A69731732], | [0xCA28A291859BBF93, 0x7D7B8F7503CFDCFF], | [0xFCB2CB35E702AF78, 0x5CDA735244C3D43F], | [0x9DEFBF01B061ADAB, 0x3A0888136AFA64A7], | [0xC56BAEC21C7A1916, 0x088AAA1845B8FDD1], | [0xF6C69A72A3989F5C, 0x8AAD549E57273D45], | [0x9A3C2087A63F6399, 0x36AC54E2F678864B], | [0xC0CB28A98FCF3C80, 0x84576A1BB416A7DE], | [0xF0FDF2D3F3C30B9F, 0x656D44A2A11C51D5], | [0x969EB7C47859E744, 0x9F644AE5A4B1B325], | [0xBC4665B596706115, 0x873D5D9F0DDE1FEF], | [0xEB57FF22FC0C795A, 0xA90CB506D155A7EA], | [0x9316FF75DD87CBD8, 0x09A7F12442D588F3], | [0xB7DCBF5354E9BECE, 0x0C11ED6D538AEB2F], | [0xE5D3EF282A242E82, 0x8F1668C8A86DA5FB], | [0x8FA475791A569D11, 0xF96E017D694487BD], | [0xB38D92D760EC4455, 0x37C981DCC395A9AC], | [0xE070F78D3927556B, 0x85BBE253F47B1417], | [0x8C469AB843B89563, 0x93956D7478CCEC8E], | [0xAF58416654A6BABB, 0x387AC8D1970027B2], | [0xDB2E51BFE9D0696A, 0x06997B05FCC0319F], | [0x88FCF317F22241E2, 0x441FECE3BDF81F03], | [0xAB3C2FDDEEAAD25B, 0xD527E81CAD7626C4], | [0xD60B3BD56A5586F2, 0x8A71E223D8D3B075], | [0x85C7056562757457, 0xF6872D5667844E49], | [0xA738C6BEBB12D16D, 0xB428F8AC016561DB], | [0xD106F86E69D785C8, 0xE13336D701BEBA52], | [0x82A45B450226B39D, 0xECC0024661173473], | [0xA34D721642B06084, 0x27F002D7F95D0190], | [0xCC20CE9BD35C78A5, 0x31EC038DF7B441F4], | [0xFF290242C83396CE, 0x7E67047175A15271], | [0x9F79A169BD203E41, 0x0F0062C6E984D387], | [0xC75809C42C684DD1, 0x52C07B78A3E60868], | [0xF92E0C3537826146, 0xA7709A56CCDF8A83], | [0x9BBCC7A142B17CCC, 0x88A66076400BB692], | [0xC2ABF989935DDBFE, 0x6ACFF893D00EA436], | [0xF356F7EBF83552FE, 0x0583F6B8C4124D43], | [0x98165AF37B2153DF, 0xC3727A337A8B704A], | [0xBE1BF1B059E9A8D6, 0x744F18C0592E4C5D], | [0xEDA2EE1C7064130C, 0x1162DEF06F79DF74], | [0x9485D4D1C63E8BE8, 0x8ADDCB5645AC2BA8], | [0xB9A74A0637CE2EE1, 0x6D953E2BD7173693], | [0xE8111C87C5C1BA9A, 0xC8FA8DB6CCDD0437], | [0x910AB1D4DB9914A0, 0x1D9C9892400A22A2], | [0xB54D5E4A127F59C8, 0x2503BEB6D00CAB4B], | [0xE2A0B5DC971F303A, 0x2E44AE64840FD61E], | [0x8DA471A9DE737E24, 0x5CEAECFED289E5D3], | [0xB10D8E1456105DAD, 0x7425A83E872C5F47], | [0xDD50F1996B947519, 0xD12F124E28F77719], | [0x8A5296FFE33CC930, 0x82BD6B70D99AAA70], | [0xACE73CBFDC0BFB7B, 0x636CC64D1001550C], | [0xD8210BEFD30EFA5A, 0x3C47F7E05401AA4F], | [0x8714A775E3E95C78, 0x65ACFAEC34810A71], | [0xA8D9D1535CE3B396, 0x7F1839A741A14D0D], | [0xD31045A8341CA07C, 0x1EDE48111209A051], | [0x83EA2B892091E44E, 0x934AED0AAB460432], | [0xA4E4B66B68B65D61, 0xF81DA84D5617853F], | [0xCE1DE40642E3F4B9, 0x36251260AB9D668F], | [0x80D2AE83E9CE78F4, 0xC1D72B7C6B426019], | [0xA1075A24E4421731, 0xB24CF65B8612F820], | [0xC94930AE1D529CFD, 0xDEE033F26797B628], | [0xFB9B7CD9A4A7443C, 0x169840EF017DA3B1], | [0x9D412E0806E88AA6, 0x8E1F289560EE864F], | [0xC491798A08A2AD4F, 0xF1A6F2BAB92A27E3], | [0xF5B5D7EC8ACB58A3, 0xAE10AF696774B1DB], | [0x9991A6F3D6BF1766, 0xACCA6DA1E0A8EF29], | [0xBFF610B0CC6EDD3F, 0x17FD090A58D32AF3], | [0xEFF394DCFF8A948F, 0xDDFC4B4CEF07F5B0], | [0x95F83D0A1FB69CD9, 0x4ABDAF101564F98E], | [0xBB764C4CA7A44410, 0x9D6D1AD41ABE37F2], | [0xEA53DF5FD18D5514, 0x84C86189216DC5EE], | [0x92746B9BE2F8552C, 0x32FD3CF5B4E49BB5], | [0xB7118682DBB66A77, 0x3FBC8C33221DC2A2], | [0xE4D5E82392A40515, 0x0FABAF3FEAA5334A], | [0x8F05B1163BA6832D, 0x29CB4D87F2A7400E], | [0xB2C71D5BCA9023F8, 0x743E20E9EF511012], | [0xDF78E4B2BD342CF7, 0x914DA9246B255417], | [0x8BAB8EEFB6409C1A, 0x1AD089B6C2F7548E], | [0xAE9672ABA3D0C321, 0xA184AC2473B529B2], | [0xDA3C0F568CC4F3E9, 0xC9E5D72D90A2741E], | [0x8865899617FB1871, 0x7E2FA67C7A658893], | [0xAA7EEBFB9DF9DE8E, 0xDDBB901B98FEEAB8], | [0xD51EA6FA85785631, 0x552A74227F3EA565], | [0x8533285C936B35DF, 0xD53A88958F87275F], | [0xA67FF273B8460357, 0x8A892ABAF368F137], | [0xD01FEF10A657842C, 0x2D2B7569B0432D85], | [0x8213F56A67F6B29C, 0x9C3B29620E29FC73], | [0xA298F2C501F45F43, 0x8349F3BA91B47B90], | [0xCB3F2F7642717713, 0x241C70A936219A74], | [0xFE0EFB53D30DD4D8, 0xED238CD383AA0111], | [0x9EC95D1463E8A507, 0xF4363804324A40AB], | [0xC67BB4597CE2CE49, 0xB143C6053EDCD0D5], | [0xF81AA16FDC1B81DB, 0xDD94B7868E94050A], | [0x9B10A4E5E9913129, 0xCA7CF2B4191C8327], | [0xC1D4CE1F63F57D73, 0xFD1C2F611F63A3F0], | [0xF24A01A73CF2DCD0, 0xBC633B39673C8CEC], | [0x976E41088617CA02, 0xD5BE0503E085D814], | [0xBD49D14AA79DBC82, 0x4B2D8644D8A74E19], | [0xEC9C459D51852BA3, 0xDDF8E7D60ED1219F], | [0x93E1AB8252F33B46, 0xCABB90E5C942B503], | [0xB8DA1662E7B00A17, 0x3D6A751F3B936244], | [0xE7109BFBA19C0C9D, 0x0CC512670A783AD5], | [0x906A617D450187E2, 0x27FB2B80668B24C5], | [0xB484F9DC9641E9DB, 0xB1F9F660802DEDF6], | [0xE1A63853BBD26451, 0x5E7873F8A0396974], | [0x8D07E33455637EB3, 0xDB0B487B6423E1E8], | [0xB049DC016ABC5E60, 0x91CE1A9A3D2CDA63], | [0xDC5C5301C56B75F7, 0x7641A140CC7810FB], | [0x89B9B3E11B6329BB, 0xA9E904C87FCB0A9D], | [0xAC2820D9623BF429, 0x546345FA9FBDCD44], | [0xD732290FBACAF134, 0xA97C177947AD4095], | [0x867F59A9D4BED6C0, 0x49ED8EABCCCC485D], | [0xA81F301449EE8C70, 0x5C68F256BFFF5A75], | [0xD226FC195C6A2F8C, 0x73832EEC6FFF3112], | [0x83585D8FD9C25DB8, 0xC831FD53C5FF7EAB], | [0xA42E74F3D032F526, 0xBA3E7CA8B77F5E56], | [0xCD3A1230C43FB26F, 0x28CE1BD2E55F35EB], | [0x80444B5E7AA7CF85, 0x7980D163CF5B81B3], | [0xA0555E361951C367, 0xD7E105BCC3326220], | [0xC86AB5C39FA63441, 0x8DD9472BF3FEFAA8], | [0xFA856334878FC151, 0xB14F98F6F0FEB952], | [0x9C935E00D4B9D8D2, 0x6ED1BF9A569F33D3], | [0xC3B8358109E84F07, 0x0A862F80EC4700C8], | [0xF4A642E14C6262C9, 0xCD27BB612758C0FA], | [0x98E7E9CCCFBD7DBE, 0x8038D51CB897789C], | [0xBF21E44003ACDD2D, 0xE0470A63E6BD56C3], | [0xEEEA5D5004981478, 0x1858CCFCE06CAC74], | [0x95527A5202DF0CCB, 0x0F37801E0C43EBC9], | [0xBAA718E68396CFFE, 0xD30560258F54E6BB], | [0xE950DF20247C83FD, 0x47C6B82EF32A2069], | [0x91D28B7416CDD27E, 0x4CDC331D57FA5442], | [0xB6472E511C81471E, 0xE0133FE4ADF8E952], | [0xE3D8F9E563A198E5, 0x58180FDDD97723A7], | [0x8E679C2F5E44FF8F, 0x570F09EAA7EA7648], | [0xB201833B35D63F73, 0x2CD2CC6551E513DA], | [0xDE81E40A034BCF50, 0xF8077F7EA65E58D1], | [0x8B112E86420F6192, 0xFB04AFAF27FAF783], | [0xADD57A27D29339F6, 0x79C5DB9AF1F9B563], | [0xD94AD8B1C7380874, 0x18375281AE7822BC], | [0x87CEC76F1C830549, 0x8F2293910D0B15B6], | [0xA9C2794AE3A3C69B, 0xB2EB3875504DDB23], | [0xD433179D9C8CB841, 0x5FA60692A46151EC], | [0x849FEEC281D7F329, 0xDBC7C41BA6BCD333], | [0xA5C7EA73224DEFF3, 0x12B9B522906C0800], | [0xCF39E50FEAE16BF0, 0xD768226B34870A00], | [0x81842F29F2CCE376, 0xE6A1158300D46640], | [0xA1E53AF46F801C53, 0x60495AE3C1097FD0], | [0xCA5E89B18B602368, 0x385BB19CB14BDFC4], | [0xFCF62C1DEE382C42, 0x46729E03DD9ED7B5], | [0x9E19DB92B4E31BA9, 0x6C07A2C26A8346D1], | [0xC5A05277621BE294, 0xC7098B7305241886], | [0xF70867153AA2DB39, 0xB8CBEE4FC66D1EA7], | [0x9A65406D44A5C903, 0x737F74F1DC043328], | [0xC0FE908895CF3B44, 0x505F522E53053FF2], | [0xF13E34AABB430A15, 0x647726B9E7C68FEF], | [0x96C6E0EAB509E64D, 0x5ECA783430DC19F5], | [0xBC789925624C5FE1, 0xB67D16413D132073], | [0xEB96BF6EBADF77D9, 0xE41C5BD18C57E88F], | [0x933E37A534CBAAE8, 0x8E91B962F7B6F15A], | [0xB80DC58E81FE95A1, 0x723627BBB5A4ADB0], | [0xE61136F2227E3B0A, 0xCEC3B1AAA30DD91C], | [0x8FCAC257558EE4E6, 0x213A4F0AA5E8A7B2], | [0xB3BD72ED2AF29E20, 0xA988E2CD4F62D19E], | [0xE0ACCFA875AF45A8, 0x93EB1B80A33B8605], | [0x8C6C01C9498D8B89, 0xBC72F130660533C3], | [0xAF87023B9BF0EE6B, 0xEB8FAD7C7F8680B4], | [0xDB68C2CA82ED2A06, 0xA67398DB9F6820E1], | [0x892179BE91D43A44, 0x88083F8943A1148D], | [0xAB69D82E364948D4, 0x6A0A4F6B948959B0], | [0xD6444E39C3DB9B0A, 0x848CE34679ABB01C], | [0x85EAB0E41A6940E6, 0xF2D80E0C0C0B4E12], | [0xA7655D1D2103911F, 0x6F8E118F0F0E2196], | [0xD13EB46469447567, 0x4B7195F2D2D1A9FB], | [0x82C730BEC1CAC961, 0x8F26FDB7C3C30A3D], | [0xA378FCEE723D7BB9, 0xB2F0BD25B4B3CCCC], | [0xCC573C2A0ECCDAA7, 0xDFACEC6F21E0C000], | [0xFF6D0B3492801151, 0x9798278AEA58EFFF], | [0x9FA42700DB900AD2, 0x5EBF18B6D2779600], | [0xC78D30C112740D87, 0xF66EDEE487157B80], | [0xF9707CF1571110E9, 0xB40A969DA8DADA5F], | [0x9BE64E16D66AAA91, 0x70869E228988C87C], | [0xC2DFE19C8C055536, 0xCCA845AB2BEAFA9B], | [0xF397DA03AF06AA83, 0x3FD25715F6E5B941], | [0x983EE8424D642A92, 0x07E3766DBA4F93C9], | [0xBE4EA252E0BD3537, 0x89DC540928E378BB], | [0xEDE24AE798EC8284, 0x2C53690B731C56EA], | [0x94AD6ED0BF93D193, 0x9BB421A727F1B652], | [0xB9D8CA84EF78C5F7, 0x42A12A10F1EE23E7], | [0xE84EFD262B56F775, 0x134974952E69ACE0], | [0x91315E37DB165AA9, 0x2C0DE8DD3D020C0C], | [0xB57DB5C5D1DBF153, 0x771163148C428F0F], | [0xE2DD23374652EDA8, 0x54D5BBD9AF5332D3], | [0x8DCA36028BF3D489, 0x350595680D93FFC4], | [0xB13CC3832EF0C9AC, 0x8246FAC210F8FFB5], | [0xDD8BF463FAACFC16, 0x62D8B97295373FA2], | [0x8A7778BE7CAC1D8E, 0xFDC773E79D4287C5], | [0xAD1556EE1BD724F1, 0x7D3950E1849329B7], | [0xD85AACA9A2CCEE2E, 0xDC87A519E5B7F424], | [0x8738ABEA05C014DD, 0xA9D4C7302F92F897], | [0xA906D6E487301A14, 0xD449F8FC3B77B6BC], | [0xD3488C9DA8FC2099, 0xC95C773B4A55A46B], | [0x840D57E2899D945F, 0x7DD9CA850E7586C3], | [0xA510ADDB2C04F977, 0x5D503D265212E874], | [0xCE54D951F70637D5, 0x34A44C6FE697A291], | [0x80F507D33A63E2E5, 0x40E6AFC5F01EC59B], | [0xA13249C808FCDB9F, 0x91205BB76C267701], | [0xC97EDC3A0B3C1286, 0x356872A5473014C1], | [0xFBDE93488E0B1728, 0xC2C28F4E98FC19F2], | [0x9D6B1C0D58C6EE79, 0xD9B999911F9D9037], | [0xC4C5E310AEF8AA17, 0x1027FFF56784F445], | [0xF5F75BD4DAB6D49D, 0xD431FFF2C1663156], | [0x99BA996508B244E2, 0x049F3FF7B8DFDED6], | [0xC0293FBE4ADED61B, 0x85C70FF5A717D68B], | [0xF0338FADDD968BA1, 0x2738D3F310DDCC2E], | [0x962039CCAA7E1745, 0xB8838477EA8A9F9D], | [0xBBA8483FD51D9D16, 0xE6A46595E52D4784], | [0xEA925A4FCA65045B, 0x604D7EFB5E789965], | [0x929B7871DE7F22B9, 0x1C306F5D1B0B5FDF], | [0xB742568E561EEB67, 0x633C8B3461CE37D7], | [0xE512EC31EBA6A641, 0x3C0BAE017A41C5CD], | [0x8F2BD39F334827E9, 0xC5874CC0EC691BA0], | [0xB2F6C887001A31E3, 0xF6E91FF127836288], | [0xDFB47AA8C020BE5C, 0xB4A367ED71643B2A], | [0x8BD0CCA9781476F9, 0x50E620F466DEA4FA], | [0xAEC4FFD3D61994B8, 0xA51FA93180964E39], | [0xDA763FC8CB9FF9E6, 0x8E67937DE0BBE1C7], | [0x8889E7DD7F43FC2F, 0x7900BC2EAC756D1C], | [0xAAAC61D4DF14FB3B, 0x5740EB3A5792C863], | [0xD5577A4A16DA3A0A, 0x2D112608ED777A7C], | [0x8556AC6E4E486446, 0x5C2AB7C5946AAC8E], | [0xA6AC5789E1DA7D58, 0xF33565B6F98557B1], | [0xD0576D6C5A511CAE, 0xF002BF24B7E6AD9D], | [0x8236A463B872B1ED, 0xB601B776F2F02C82], | [0xA2C44D7CA68F5E68, 0xE3822554AFAC37A3], | [0xCB7560DBD0333602, 0xDC62AEA9DB97458C], | [0xFE52B912C4400382, 0x537B5A54527D16EE], | [0x9EF3B3ABBAA80231, 0x742D1874B38E2E55], | [0xC6B0A096A95202BE, 0xD1385E91E071B9EA], | [0xF85CC8BC53A6836D, 0x45867636588E2865], | [0x9B39FD75B4481224, 0x4B7409E1F758D93F], | [0xC2087CD3215A16AD, 0x5E510C5A752F0F8F], | [0xF28A9C07E9B09C59, 0xB5E54F71127AD373], | [0x9796A184F20E61B7, 0x71AF51A6AB8CC428], | [0xBD7C49E62E91FA25, 0x4E1B2610566FF531], | [0xECDB5C5FBA3678AF, 0xA1A1EF946C0BF27E], | [0x940919BBD4620B6D, 0x250535BCC387778F], | [0xB90B602AC97A8E48, 0x6E46832BF4695572], | [0xE74E38357BD931DB, 0x89D823F6F183AACF], | [0x9090E3216D67BF29, 0x9627167A56F24AC1], | [0xB4B51BE9C8C1AEF3, 0xBBB0DC18ECAEDD72], | [0xE1E262E43AF21AAF, 0x6A9D131F27DA94CE], | [0x8D2D7DCEA4D750AE, 0xA2A22BF378E89D01], | [0xB078DD424E0D24D9, 0x0B4AB6F05722C441], | [0xDC971492E1906E0F, 0x4E1D64AC6CEB7551], | [0x89DE6CDBCCFA44CA, 0x90D25EEBC4132953], | [0xAC560812C038D5FC, 0xF506F6A6B517F3A7], | [0xD76B8A1770470B7B, 0xF248B450625DF091], | [0x86A3364EA62C672D, 0xD76D70B23D7AB65B], | [0xA84C03E24FB780F8, 0x0D48CCDECCD963F2], | [0xD25F04DAE3A56136, 0x109B0016800FBCEE], | [0x837B6308CE475CC2, 0xCA60E00E1009D615], | [0xA45A3BCB01D933F2, 0x3CF91811940C4B9A], | [0xCD70CABDC24F80EF, 0xCC375E15F90F5E80], | [0x80667EB69971B095, 0x3FA29ACDBBA99B10], | [0xA0801E643FCE1CBB, 0x8F8B41812A9401D4], | [0xC8A025FD4FC1A3E9, 0x336E11E175390249], | [0xFAC82F7CA3B20CE4, 0x80499659D28742DC], | [0x9CBD1DADE64F480E, 0x302DFDF8239489C9], | [0xC3EC65195FE31A12, 0xBC397D762C79AC3C], | [0xF4E77E5FB7DBE096, 0x2B47DCD3B798174B], | [0x9910AEFBD2E96C5E, 0xDB0CEA0452BF0E8F], | [0xBF54DABAC7A3C775, 0x51D02485676ED232], | [0xEF2A1169798CB953, 0xA6442DA6C14A86BF], | [0x957A4AE1EBF7F3D4, 0xA7EA9C8838CE9437], | [0xBAD8DD9A66F5F0C9, 0x91E543AA47023945], | [0xE98F150100B36CFB, 0xB65E9494D8C2C796], | [0x91F96D20A070241D, 0xB1FB1CDD0779BCBE], | [0xB677C868C88C2D24, 0xDE79E41449582BED], | [0xE415BA82FAAF386D, 0xD6185D195BAE36E9], | [0x8E8D9491DCAD8344, 0x05CF3A2FD94CE251], | [0xB230F9B653D8E415, 0x074308BBCFA01AE6], | [0xDEBD3823E8CF1D1A, 0x4913CAEAC388219F], | [0x8B36431671817230, 0x6DAC5ED2BA351504], | [0xAE03D3DC0DE1CEBD, 0x8917768768C25A44], | [0xD984C8D3115A426C, 0xAB5D542942F2F0D6], | [0x87F2FD83EAD86983, 0x4B1A5499C9D7D685], | [0xA9EFBCE4E58E83E4, 0x1DE0E9C03C4DCC27], | [0xD46BAC1E1EF224DD, 0x255924304B613F31], | [0x84C34B92D357570A, 0x3757B69E2F1CC77E], | [0xA5F41E77882D2CCD, 0xC52DA445BAE3F95E], | [0xCF7126156A387800, 0xF6790D57299CF7B5], | [0x81A6B7CD62634B00, 0xFA0BA8567A021AD1], | [0xA21065C0BAFC1DC0, 0xF88E926C1882A186], | [0xCA947F30E9BB2530, 0xF6B237071EA349E7], | [0xFD399EFD2429EE7C, 0xF45EC4C8E64C1C61], | [0x9E44035E369A350D, 0x78BB3AFD8FEF91BD], | [0xC5D50435C440C251, 0xD6EA09BCF3EB762C], | [0xF74A45433550F2E5, 0x0CA48C2C30E653B7], | [0x9A8E6B4A015297CF, 0x27E6D79B9E8FF452], | [0xC132061C81A73DC3, 0xF1E08D828633F167], | [0xF17E87A3A2110D34, 0xAE58B0E327C0EDC0], | [0x96EF14C6454AA840, 0x4CF76E8DF8D89498], | [0xBCAAD9F7D69D5250, 0x60354A31770EB9BE], | [0xEBD59075CC44A6E4, 0x78429CBDD4D2682E], | [0x93657A499FAAE84F, 0xCB29A1F6A503811D], | [0xB83ED8DC0795A262, 0x7DF40A744E446164], | [0xE64E8F13097B0AFB, 0x1D710D1161D579BD], | [0x8FF1196BE5ECE6DD, 0xF266A82ADD256C16], | [0xB3ED5FC6DF682094, 0x2F005235946EC71C], | [0xE0E8B7B8974228B9, 0x3AC066C2F98A78E2], | [0x8C9172D35E895974, 0xC4B84039DBF68B8E], | [0xAFB5CF88362BAFD1, 0xB5E6504852F42E71], | [0xDBA3436A43B69BC5, 0xE35FE45A67B13A0D], | [0x89460A226A52215B, 0x0E1BEEB880CEC448], | [0xAB978CAB04E6A9B2, 0xD1A2EA66A102755A], | [0xD67D6FD5C620541E, 0x460BA500494312B1], | [0x860E65E59BD43493, 0xEBC747202DC9EBAF], | [0xA791FF5F02C941B8, 0xA6B918E8393C669A], | [0xD1767F36C37B9226, 0x90675F22478B8041], | [0x82EA0F823A2D3B57, 0x7A409B756CB73028], | [0xA3A49362C8B88A2D, 0x58D0C252C7E4FC33], | [0xCC8DB83B7AE6ACB9, 0xAF04F2E779DE3B3F], | [0xFFB1264A59A057E7, 0xDAC62FA15855CA0F], | [0x9FCEB7EE780436F0, 0x48BBDDC4D7359E49], | [0xC7C265EA160544AC, 0x5AEAD5360D0305DC], | [0xF9B2FF649B8695D7, 0x71A58A839043C753], | [0x9C0FDF9EE1341DA7, 0xA70776923A2A5C94], | [0xC313D78699812510, 0x50C95436C8B4F3B9], | [0xF3D8CD683FE16E54, 0x64FBA9447AE230A7], | [0x9867806127ECE4F5, 0xBF1D49CACCCD5E68], | [0xBE81607971E81E32, 0xEEE49C3D8000B602], | [0xEE21B897CE6225BE, 0x6A9DC34CE000E383], | [0x94D5135EE0FD5797, 0x02A29A100C008E32], | [0xBA0A5836993CAD7D, 0xC34B40940F00B1BE], | [0xE88CEE443F8BD8DC, 0xF41E10B912C0DE2E], | [0x915814EAA7B76789, 0x7892CA73ABB88ADD], | [0xB5AE1A2551A5416C, 0xD6B77D1096A6AD94], | [0xE319A0AEA60E91C7, 0xCC655C54BC5058F9], |]; | |// |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, |]; source/mir/bignum/internal/dec2flt_table.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.82-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: Ilya Yaroshenko, $(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.1.82/mir-core/source/mir/primitives.d has no code <<<<<< 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Andrei Alexandrescu (Phobos), Ilya Yaroshenko (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_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_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_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_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_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) | { 77| 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; 77| if (slice.anyEmpty) 0000000| return slice; 77| .quickSortImpl!less(slice.flattened); 77| return slice; | } | | /++ | Sort for arrays | +/ | T[] sort(T)(T[] ar) | { | 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) 108| index.zip(data).sort!((a, b) => less(a.a, b.a)); 36| 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_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_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_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(;;) | { 77| auto l = slice._iterator; 77| auto r = l; 77| r += slice.length; | | static if (naive > 1) | { 77| if (slice.length <= naive || __ctfe) | { 77| auto p = r; 77| --p; 401| while(p != l) | { 324| --p; | //static if (is(typeof(() nothrow | // { | // auto t = slice[0]; if (less(t, slice[0])) slice[0] = slice[0]; | // }))) | //{ 324| auto d = p; | import mir.functional: unref; 324| auto temp = unref(*d); 324| auto c = d; 324| ++c; 324| if (less(*c, temp)) | { | do | { 844| d[0] = *c; 844| ++d; 844| ++c; | } 1597| while (c != r && less(*c, temp)); 249| 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; | // } | //} | } 77| return; | } | } | else | { | if(slice.length <= 1) | return; | } | | // partition 0000000| auto lessI = l; 0000000| --r; 0000000| auto pivotIdx = l + slice.length / 2; 0000000| setPivot!less(slice.length, l, pivotIdx, r); | import mir.functional: unref; 0000000| auto pivot = unref(*pivotIdx); 0000000| --lessI; 0000000| auto greaterI = r; 0000000| swapStars(pivotIdx, greaterI); | | outer: for (;;) | { 0000000| do ++lessI; 0000000| while (less(*lessI, pivot)); 0000000| assert(lessI <= greaterI, "sort: invalid comparison function."); | for (;;) | { 0000000| if (greaterI == lessI) 0000000| break outer; 0000000| --greaterI; 0000000| if (!less(pivot, *greaterI)) 0000000| break; | } 0000000| assert(lessI <= greaterI, "sort: invalid comparison function."); 0000000| if (lessI == greaterI) 0000000| break; 0000000| swapStars(lessI, greaterI); | } | 0000000| swapStars(r, lessI); | 0000000| ptrdiff_t len = lessI - l; 0000000| auto tail = slice[len + 1 .. $]; 0000000| slice = slice[0 .. len]; 0000000| if (tail.length > slice.length) 0000000| swap(slice, tail); 0000000| quickSortImpl!less(tail); | } |} | |void setPivot(alias less, Iterator)(size_t length, ref Iterator l, ref Iterator mid, ref Iterator r) @trusted |{ 0000000| if (length < 512) | { 0000000| if (length >= 32) 0000000| medianOf!less(l, mid, r); 0000000| return; | } 0000000| auto quarter = length >> 2; 0000000| auto b = mid - quarter; 0000000| auto e = mid + quarter; 0000000| 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; | 243| if (less(*c, *a)) // c < a | { 117| if (less(*a, *b)) // c < a < b | { 35| swapStars(a, b); 35| swapStars(a, c); | } | else // c < a, b <= a | { 82| swapStars(a, c); 119| if (less(*b, *a)) swapStars(a, b); | } | } | else // a <= c | { 126| if (less(*b, *a)) // b < a <= c | { 27| swapStars(a, b); | } | else // a <= c, a <= b | { 140| if (less(*c, *b)) swapStars(b, c); | } | } 243| assert(!less(*b, *a)); 243| 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 | 4| version(unittest) scope(success) | { 4| assert(!less(*c, *a)); 4| assert(!less(*c, *b)); 4| assert(!less(*d, *c)); 4| assert(!less(*e, *c)); | } | 7| if (less(*c, *a)) swapStars(a, c); 4| if (less(*d, *b)) swapStars(b, d); 4| if (less(*d, *c)) | { 3| swapStars(c, d); 3| swapStars(a, b); | } 4| if (less(*e, *b)) swapStars(b, e); 4| if (less(*e, *c)) | { 0000000| swapStars(c, e); 0000000| if (less(*c, *a)) swapStars(a, c); | } | else | { 4| 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_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) | { 3242| size_t first = 0, count = slice.length; 4949| while (count > 0) | { 6656| immutable step = count / 2, it = first + step; 3328| if (test(slice[it], v)) | { 1471| first = it + 1; 1471| count -= step + 1; | } | else | { 1857| count = step; | } | } 1621| return first; | } | | /// ditto | size_t transitionIndex(T, V)(scope T[] ar, auto ref scope const V v) | { 1406| return .transitionIndex!test(ar.sliced, v); | } | | } | else | alias transitionIndex = .transitionIndex!(naryFun!test); |} | |/// |version(mir_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; 12| return r | .length | .iota!I | .slice 26| .sort!((a, b) => naryFun!less(r[a], r[b])); |} | |/// |I[] makeIndex(I = size_t, alias less = "a < b", T)(scope T[] r) |{ 12| return .makeIndex!(I, less)(r.sliced).field; |} | |/// |version(mir_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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)); |} source/mir/ndslice/sorting.d is 93% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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 | { 30| typeof(return) ret; | foreach(f, NdField; NdFields) | { | static if (hasShape!NdField) | { 70| auto s = _fields[f].shape; | foreach(j; Iota!(s.length)) 72| ret[M!f + j] = s[j]; | } | else | { | ret[M!f] = _fields[f].length; | } | } 30| 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 : refTuple; 592| return mixin("refTuple(" ~ _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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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_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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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; | |@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) |{ 5| 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)); |} | |/// 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. |+/ 13|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): | | /++ | +/ 5| this(Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) data) @safe @nogc | { | 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); | } 8| foreach(i, ref x; grid) | { 8| if (x.length < 2) | { 0000000| version(D_Exceptions) throw exc_min; | else assert(0, msg_min); | } 8| if (x.length != data._lengths[i]) | { 0000000| version(D_Exceptions) throw exc_eq; | else assert(0, msg_eq); | } 8| _grid[i] = x._iterator.move; | } 5| _data = data.move; | } | |@trusted: | | /// 0000000| Linear lightConst()() const @property { return *cast(Linear*)&this; } | | /// | Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() scope return const @property | if (dimension < N) | { | return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() scope return const @property @trusted | if (dimension < N) | { 291| return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | { 291| assert(_data._lengths[dimension] > 1); 291| return _data._lengths[dimension] - 1; | } | | /// | size_t[N] gridShape()() scope const @property | { | return _data.shape; | } | | /// | enum uint derivativeOrder = 1; | | /// | 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) | { | import mir.functional: AliasCall; | import mir.ndslice.topology: iota; | alias Kernel = AliasCall!(LinearKernel!F, "opCall", derivative); | 144| size_t[N] indices; 144| 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]; 260| indices[i] = this.findInterval!i(x); | } 290| kernels[i] = LinearKernel!F(_grid[i][indices[i]], _grid[i][indices[i] + 1], x); | } | | align(64) F[2 ^^ N][derivative + 1] local; 144| immutable strides = _data._lengths.iota.strides; | | void load(sizediff_t i)(F* from, F* to) | { | version(LDC) pragma(inline, true); | static if (i == -1) | { 704| *to = *from; | } | else | { 560| from += strides[i] * indices[i]; 560| load!(i - 1)(from, to); 560| from += strides[i]; | enum s = 2 ^^ (N - 1 - i); 560| to += s; 560| load!(i - 1)(from, to); | } | } | 144| 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; 290| 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) 5| 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) 144| 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; | | /// | auto lightConst()() const @property | { | return LinearKernel!X(step, w0, w1); | } | | /// | auto lightImmutable()() immutable @property | { | return LinearKernel!X(step, w0, w1); | } | | /// 290| this()(X x0, X x1, X x) | { 290| step = x1 - x0; 290| auto c0 = x - x0; 290| auto c1 = x1 - x; 290| w0 = c0 / step; 290| w1 = c1 / step; | } | | /// | template opCall(uint derivative = 0) | if (derivative <= 1) | { | /// | auto opCall(Y)(in Y y0, in Y y1) | { 566| auto r0 = y0 * w1; 566| auto r1 = y1 * w0; 566| auto y = r0 + r1; | static if (derivative) | { 16| auto diff = y1 - y0; 16| Y[derivative + 1] ret = 0; 16| ret[0] = y; 16| ret[1] = diff / step; 16| return ret; | } | else | { 550| return y; | } | } | } | | /// | alias withDerivative = opCall!1; |} source/mir/interpolate/linear.d is 96% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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.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 should be applied to a string-like member that should be de/serialized as an annotation / attribute. | |This feature is used in $(MIR_PACKAGE mir-ion). |+/ |enum serdeAnnotation; | | |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!(DeserializableMembers!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; | } | | static assert(serdeGetAnnotationMembersOut!int == []); | static assert(serdeGetAnnotationMembersOut!S == ["a", "b"]); |} | |/++ |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 | static if (__traits(getAliasThis, T).length) | { | T* aggregate; | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | enum serdeHasAlgebraicAnnotation = .serdeHasAlgebraicAnnotation!A; | } | 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; | } |} | |/++ |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 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 only to fields that can be constructed from strings. |Does not allocate new data when deserializeing. Raw data is used for strings instead of new memory allocation. |Use this attributes only for strings that would not be used after the input data deallocation. |+/ |deprecated("use @serdeScoped @serdeProxy!(const(char)[]) instead") enum serdeScopeStringProxy; | |/++ |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; | |/++ |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)(const char[] str, ref E res) | @safe pure nothrow @nogc |{ | static if (__VERSION__ < 2093) | { | static if (hasUDA!(E, serdeIgnoreCase)) | { | import mir.format: stringBuf; | stringBuf buf; | buf << str; | auto ustr = buf.data.fastToUpperInPlace; | } | else | { | alias ustr = str; | } | switch(ustr) | { | foreach(i, member; EnumMembers!E) | {{ | enum initKeys = serdeGetKeysIn(EnumMembers!E[i]); | static if (hasUDA!(E, serdeIgnoreCase)) | { | import mir.ndslice.topology: map; | import mir.array.allocation: array; | enum keys = initKeys.map!fastLazyToUpper.map!array.array; | } | else | { | enum keys = initKeys; | } | static assert (keys.length, "At least one input enum key is required"); | static foreach (key; keys) | { | case key: | } | res = member; | return true; | }} | default: | return false; | } | } | else | { | 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(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(serdeParseEnum("B", e)); 1| assert(e == E.b); 1| assert(serdeParseEnum("c", e)); 1| assert(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)))) | { | 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 = Unqual!V; | static if (isAggregateType!E || is(E == enum)) | alias serdeGetFinalDeepProxy = .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 == string), serdeGetFinalDeepProxy!C.stringof); | 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 = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | enum isAlgebraicAliasThis = isVariant!A; | } | 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 ~ ";"); |} | |template deserializeValueMemberImpl(alias deserializeValue, alias deserializeScoped) |{ | /// | SerdeException deserializeValueMemberImpl(string member, Data, T, Context...)(Data data, ref T value, ref SerdeFlags!T requiredFlags, 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()(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()(scope return 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 76% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.82-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 refTuple)) | $(TD Removes $(LREF Ref) shell. | )) | $(TR $(TD $(LREF unref)) | $(TD Creates a $(LREF RefTuple) structure. | )) | $(TR $(TD $(LREF __ref)) | $(TD Creates a $(LREF Ref) structure. | )) |) |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko, $(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(scope Ref!T rhs) const scope | { | return __value == rhs.__value; | } | | 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 RefTuple(T...) |{ | @optmath: | T expand; | alias expand this; | mixin _RefTupleMixin!T; |} | |/// Removes $(LREF Ref) shell. |alias Unref(V : Ref!T, T) = T; |/// ditto |template Unref(V : RefTuple!T, T...) |{ | import std.meta: staticMap; | alias Unref = RefTuple!(staticMap!(.Unref, T)); |} | |/// ditto |alias Unref(V) = V; | |/++ |Returns: a $(LREF RefTuple) structure. |+/ |RefTuple!Args refTuple(Args...)(auto ref Args args) |{ | return RefTuple!Args(args); |} | |/// Removes $(LREF Ref) shell. |ref T unref(V : Ref!T, T)(scope return V value) |{ | return *value.__ptr; |} | |/// ditto |Unref!(RefTuple!T) unref(V : RefTuple!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 string joinStrings()(string[] strs) |{ | if (strs.length) | { | auto ret = strs[0]; | foreach(s; strs[1 .. $]) | ret ~= s; | return ret; | } | return null; |} | |/++ |Takes multiple functions and adjoins them together. The result is a |$(LREF RefTuple) 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 ~ "](forward!args)), "; | else | enum _adjoin = "fun[" ~ i.stringof ~ "](forward!args), "; | } | | import mir.internal.utility; | mixin("return refTuple(" ~ [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) == RefTuple!(bool, int))); | assert(x.a == true && x.b == 2); |} | |@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) == RefTuple!(bool, int))); | assert(x2.a && x2.b == 2); | auto x3 = adjoin!(F1, F2, F2)(5); | assert(is(typeof(x3) == RefTuple!(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) == refTuple(Ref!int(a), 10, 15, 25, -5)); | assert(afun(a) == refTuple(Ref!int(b), 10, 15, 25, -5)); | | static class C{} | alias IC = immutable(C); | IC foo(){return typeof(return).init;} | RefTuple!(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 RefTuple!(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"; | } |} | |/++ |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)) | { | import mir.math.common; | /// Specialization for string lambdas | @optmath auto ref naryFun(Args...)(auto ref Args args) | if (args.length <= 26) | { | mixin(_naryAliases!(Args.length)); | return mixin(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 = "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.1.82/mir-core/source/mir/functional.d has no code <<<<<< 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=./..-..-..-.dub-packages-mir-core-1.1.82-mir-core-source-mir-reflection.lst |/++ |Base reflection utilities. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko |Macros: |+/ |module mir.reflection; | |import std.meta; |import std.traits: hasUDA, getUDAs, Parameters, isSomeFunction, FunctionAttribute, functionAttributes, EnumMembers, isAggregateType; | |deprecated |package alias isSomeStruct = isAggregateType; | |/++ |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 info; |} | |/// 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")); |} | |/++ |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), FieldsAndProperties!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; | | 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", "gm", "gc"]); | static assert(SerializableMembers!(const S) == ["y", "f", "d", "gc"]); |} | |/++ |Returns: list of the deserializable (public setters) members. |+/ |enum string[] DeserializableMembers(T) = [Filter!(ApplyLeft!(Deserializable, T), FieldsAndProperties!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 FieldsAndProperties(T) = Reverse!(NoDuplicates!(Reverse!(FieldsAndPropertiesImpl!T))); | |private template allMembers(T) |{ | static if (isAggregateType!T) | alias allMembers = __traits(allMembers, T); | else | alias allMembers = AliasSeq!(); |} | |private template FieldsAndPropertiesImpl(T) |{ | alias isProperty = ApplyLeft!(.isProperty, T); | alias hasField = ApplyLeft!(.hasField, T); | alias isOriginalMember = ApplyLeft!(.isOriginalMember, T); | alias isMember = templateAnd!(templateOr!(hasField, isProperty), isOriginalMember); | static if (__traits(getAliasThis, T).length) | { | T* aggregate; | alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); | static if (isAggregateType!T) | alias baseMembers = FieldsAndPropertiesImpl!A; | else | alias baseMembers = AliasSeq!(); | alias members = Erase!(__traits(getAliasThis, T)[0], __traits(allMembers, T)); | alias FieldsAndPropertiesImpl = 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 FieldsAndPropertiesImpl = 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.1.82/mir-core/source/mir/reflection.d has no code <<<<<< EOF # path=./source-mir-bignum-internal-ryu-generic_128.lst |// Converted and then optimised from generic_128.h and generic_128.c |// Copyright 2018 Ulf Adams (original code https://github.com/ulfjack/ryu) |// Copyright 2020 Ilya Yaroshenko (2020 D conversion and optimisation) |// License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |// This is a generic 128-bit implementation of float to shortest conversion |// using the Ryu algorithm. It can handle any IEEE-compatible floating-point |// type up to 128 bits. In order to use this correctly, you must use the |// appropriate *_to_fd128 function for the underlying type - DO NOT CAST your |// input to another floating-point type, doing so will result in incorrect |// output! |// |// For any floating-point type that is not natively defined by the compiler, |// you can use genericBinaryToDecimal to work directly on the underlying bit |// representation. | |module mir.bignum.internal.ryu.generic_128; | |version(BigEndian) | static assert (0, "Let us know if you are using Mir on BigEndian target and we will add support for this module."); | |debug(ryu) import core.stdc.stdio; | |import mir.bignum.decimal: Decimal; |import mir.bignum.fixed: UInt, extendedMulHigh, extendedMul; | |@safe pure nothrow @nogc: | |// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 32768. |uint pow5bits(const int e) |{ | version(LDC) pragma(inline, true); 253| assert(e >= 0); 253| assert(e <= 1 << 15); 253| 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); 90| assert(shift > 0); 90| assert(shift < 256); 90| 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) |{ | version(LDC) pragma(inline, true); 81| const uint base = i / POW5_TABLE_SIZE; 81| const uint base2 = base * POW5_TABLE_SIZE; 81| const mul = UInt!256(GENERIC_POW5_SPLIT[base]); 81| if (i == base2) | { 1| result = mul; | } | else | { 80| const uint offset = i - base2; 80| const m = UInt!128(GENERIC_POW5_TABLE[offset]); 80| const uint delta = pow5bits(i) - pow5bits(base2); 80| const uint corr = cast(uint) ((POW5_ERRORS[i / 32] >> (2 * (i % 32))) & 3); 80| 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) |{ | 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(size_t 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(size_t size)(ref UInt!size value) |{ 874| auto q = div10(value); 874| auto r = cast(uint)(value - q * 10); 874| value = q; 874| return r; |} | |uint rem5(size_t size)(UInt!size value) |{ 0000000| return divRem5(value); |} | |uint rem10(size_t size)(UInt!size value) |{ | return divRem10(value); |} | |UInt!size div5(size_t size)(UInt!size value) |{ 0000000| return extendedMulHigh(value, fiveReciprocal.toSize!size) >> 2; |} | |UInt!size div10(size_t size)(UInt!size value) |{ 2768| return extendedMulHigh(value, fiveReciprocal.toSize!size) >> 3; |} | |// Returns true if value is divisible by 5^p. |bool multipleOfPowerOf5(size_t 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(size_t size)(const UInt!size value, const uint p) |{ | version(LDC) pragma(inline, true); 77| 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(size_t size)(const UInt!size m, const UInt!256 mul, const uint j) |{ | version(LDC) pragma(inline, true); 222| assert(j > 128); 222| 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. 75| assert(e >= 0); 75| assert(e <= 1 << 15); 75| 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; | 88| Decimal!wordCount fd; 88| if (_expect(x != x, false)) | { 4| fd.coefficient = 1u; 4| fd.exponent = fd.exponent.max; | } | else 84| if (_expect(x.fabs == T.infinity, false)) | { 4| fd.exponent = fd.exponent.max; | } | else 80| if (x) | { | import mir.bignum.fp: Fp; 73| const fp = Fp!coefficientSize(x, false); 73| int e2 = cast(int) fp.exponent - 2; 73| UInt!workSize m2 = fp.coefficient; | 73| const bool even = (fp.coefficient & 1) == 0; 73| 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. 73| const UInt!workSize mv = m2 << 2; | // Implicit bool -> int conversion. True is 1, false is 0. 73| const bool mmShift = fp.coefficient != (UInt!coefficientSize(1) << (T.mant_dig - 1)); | | // Step 3: Convert to a decimal power base using 128-bit arithmetic. 219| UInt!workSize vr, vp, vm; 73| int e10; 73| bool vmIsTrailingZeros = false; 73| bool vrIsTrailingZeros = false; 73| 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). 71| const uint q = log10Pow5(-e2) - (-e2 > 1); 71| e10 = q + e2; 71| const int i = -e2 - q; 71| const int k = pow5bits(i) - FLOAT_128_POW5_BITCOUNT; 71| const int j = q - k; 71| UInt!256 pow5; 71| generic_computePow5(i, pow5); 71| vr = mulShift(mv, pow5, j); 71| vp = mulShift(mv + 2, pow5, j); 71| 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)); | } 71| 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 71| 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. 71| 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. 73| uint removed = 0; 73| uint lastRemovedDigit = 0; 73| UInt!workSize output; | | for (;;) | { 947| auto div10vp = div10(vp); 947| auto div10vm = div10(vm); 947| if (div10vp == div10vm) 73| break; 874| vmIsTrailingZeros &= vm - div10vm * 10 == 0; 874| vrIsTrailingZeros &= lastRemovedDigit == 0; 874| lastRemovedDigit = vr.divRem10; 874| vp = div10vp; 874| vm = div10vm; 874| ++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); | } 73| 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); | } 99| 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. 159| output = vr + ((vr == vm && (!acceptBounds || !vmIsTrailingZeros)) || (lastRemovedDigit >= 5)); | 73| 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; 73| fd.coefficient.__ctor(output); 73| fd.exponent = exp; | } 88| fd.coefficient.sign = x.signbit; 88| return fd; |} | |private enum FLOAT_128_POW5_INV_BITCOUNT = 249; |private enum FLOAT_128_POW5_BITCOUNT = 249; |private 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. |private 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], |]; | |private 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. |private 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, |]; | |private 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 ], |]; | |private 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/generic_128.d is 86% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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: RefTuple, unref; | static if (is(typeof(_field[index]) : RefTuple!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: RefTuple, unref; | static if (is(typeof(_field[index]) : RefTuple!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: RefTuple, 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("RefTuple!(_zip_types!Fields)(" ~ _zip_index!Fields ~ ")"); | } | | auto opIndexAssign(Types...)(RefTuple!(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; 4202| 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_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); | } |} | |/// |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); |} | |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 | { 777| size_t[N] indices; | foreach_reverse (i; Iota!(N - 1)) | { 1151| indices[i + 1] = index % _lengths[i]; 1151| index /= _lengths[i]; | } 777| indices[0] = index; 777| 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 | { 1148| sizediff_t d = _length - 1; 1148| auto v = typeof(T.init.re)(d - index); 1148| auto w = typeof(T.init.re)(index); 1148| v /= d; 1148| w /= d; 1148| auto a = v * _start; 1148| auto b = w * _stop; 1148| return a + b; | } | |@optmath: | | /// | size_t length(size_t dimension = 0)() scope const @property | if (dimension == 0) | { 26| 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-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: Ilya Yaroshenko |Macros: |+/ |module mir.algebraic_alias.json; | |import mir.algebraic: TaggedVariant, This; |public import mir.string_map: StringMap; | |/++ |Definition union for $(LREF JsonAlgebraic). |+/ |union JsonAlgebraicUnion |{ | /// | 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 = TaggedVariant!JsonAlgebraicUnion; | |/// |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); 1| assert(value.get!bool == true); 1| assert(value.get!(JsonAlgebraic.Kind.boolean) == true); | | // Null 1| value = object["null"] = null; 1| assert(value.isNull); 1| assert(value == null); 1| assert(value.kind == JsonAlgebraic.Kind.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"); 1| assert(value.get!string == "s"); 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.get!long == 4); 1| assert(value.get!(JsonAlgebraic.Kind.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.get!double == 3.0); 1| assert(value.get!(JsonAlgebraic.Kind.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.get!(JsonAlgebraic[])[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.get!(StringMap!JsonAlgebraic).keys is object.keys); 1| assert(value.get!(StringMap!JsonAlgebraic).values is object.values); | 1| JsonAlgebraic[string] aa = object.toAA; 1| object = StringMap!JsonAlgebraic(aa); | 1| JsonAlgebraic fromAA = ["a" : JsonAlgebraic(3), "b" : JsonAlgebraic("b")]; 1| assert(fromAA.get!(StringMap!JsonAlgebraic)["a"] == 3); 1| assert(fromAA.get!(StringMap!JsonAlgebraic)["b"] == "b"); |} source/mir/algebraic_alias/json.d is 100% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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 : "*")() 63416| { return _index; } | | void opUnary(string op)() | if (op == "--" || op == "++") | { mixin(op ~ `_index;`); } | | I opIndex()(ptrdiff_t index) const 5702| { 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_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_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_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). |+/ 4|struct RetroIterator(Iterator) |{ |@optmath: | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 81| 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 : "*")() 4504| { return *_iterator; } | | void opUnary(string op : "--")() 1| { ++_iterator; } | | void opUnary(string op : "++")() pure 4163| { --_iterator; } | | auto ref opIndex()(ptrdiff_t index) 483| { return _iterator[-index]; } | | void opOpAssign(string op : "-")(ptrdiff_t index) scope 3| { _iterator += index; } | | void opOpAssign(string op : "+")(ptrdiff_t index) scope 83| { _iterator -= index; } | | auto opBinary(string op)(ptrdiff_t index) | if (op == "+" || op == "-") | { 37| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 37| 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_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_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 | { 42| 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 : "*")() 267| { return *_iterator; } | | void opUnary(string op)() scope | if (op == "--" || op == "++") | { mixin("_iterator " ~ op[0] ~ "= _stride;"); } | | auto ref opIndex()(ptrdiff_t index) 2675| { 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_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_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). |+/ 0000000|struct ZipIterator(Iterators...) | if (Iterators.length > 1) |{ |@optmath: | import std.traits: ConstOf, ImmutableOf; | import std.meta: staticMap; | import mir.functional: RefTuple, 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); 28| 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 : "*")() 756| { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } | | | auto opUnary(string op : "*")() const | { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } | | auto opUnary(string op : "*")() immutable | { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } | | void opUnary(string op)() scope | if (op == "++" || op == "--") | { 1612| foreach (ref _iterator; _iterators) | mixin(op ~ `_iterator;`); | } | | auto opIndex()(ptrdiff_t index) 199| { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_index!Iterators ~ ")"); } | | auto opIndexAssign(Types...)(RefTuple!(Types) value, ptrdiff_t index) | if (Types.length == Iterators.length) | { 246| foreach(i, ref val; value.expand) | { | import mir.functional: unref; 246| _iterators[i][index] = unref(val); | } 123| return opIndex(index); | } | | void opOpAssign(string op)(ptrdiff_t index) scope | if (op == "+" || op == "-") | { 198| 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 167| { 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_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()() scope @property | { 2| return CachedIterator!(LightScopeOf!Iterator, LightScopeOf!CacheIterator, LightScopeOf!FlagIterator)( | .lightScope(_iterator), | .lightScope(_caches), | .lightScope(_flags), | ); | } | | /// | auto lightScope()() scope const @property | { 0000000| return lightConst.lightScope; | } | | /// | auto lightScope()() 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: RefTuple, unref; | | auto ref opUnary(string op : "*")() | { | static if (is(typeof(*_iterator) : RefTuple!T, T...)) | { | auto t = *_iterator; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | else | return _fun(*_iterator); | } | | auto ref opIndex(ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | 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]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ") = value"); | } | else | return _fun(_iterator[index]) = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin(op ~ "_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | else | return mixin(op ~ "_fun(_iterator[index])"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")" ~ op ~ "= value"); | } | else | return mixin("_fun(_iterator[index])" ~ op ~ "= value"); | } | } |}; | |/++ |`VmapIterator` is used by $(SUBREF topology, map). |+/ 65|struct VmapIterator(Iterator, Fun) |{ |@optmath: | | /// | Iterator _iterator; | /// | Fun _fun; | | /// | auto lightConst()() const @property | { 35| 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: RefTuple, unref; | | auto ref opUnary(string op : "*")() | { | static if (is(typeof(*_iterator) : RefTuple!T, T...)) | { 166| auto t = *_iterator; 166| return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | else 967| return _fun(*_iterator); | } | | auto ref opIndex(ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { 0000000| auto t = _iterator[index]; 0000000| return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | else 880| 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]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ") = value"); | } | else | return _fun(_iterator[index]) = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin(op ~ "_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | else | return mixin(op ~ "_fun(_iterator[index])"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")" ~ 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). |+/ 7|struct MapIterator(Iterator, alias _fun) |{ |@optmath: | /// | Iterator _iterator; | | /// | auto lightConst()() const @property | { 121| return MapIterator!(LightConstOf!Iterator, _fun)(.lightConst(_iterator)); | } | | /// | auto lightImmutable()() immutable @property | { | return MapIterator!(LightImmutableOf!Iterator, _fun)(.lightImmutable(_iterator)); | } | | import mir.functional: pipe; | /// | static alias __map(alias fun1) = MapIterator__map!(Iterator, _fun, pipe!(_fun, fun1)); | | import mir.functional: RefTuple, unref; | | auto ref opUnary(string op : "*")() | { | static if (is(typeof(*_iterator) : RefTuple!T, T...)) | { 468| auto t = *_iterator; 468| return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | else 4215| return _fun(*_iterator); | } | | auto ref opIndex(ptrdiff_t index) scope | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { 49| auto t = _iterator[index]; 49| return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | 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]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ") = value"); | } | else | return _fun(_iterator[index]) = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin(op ~ "_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); | } | else | return mixin(op ~ "_fun(_iterator[index])"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")" ~ 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 520| 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_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: RefTuple, _ref; | | private alias RA = Unqual!(typeof(_fun(_iterator[-1], _iterator[+1]))); | private alias Result = RefTuple!(_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 : "*")() | { 4| U ret = { value: DestinationType.init }; | foreach (i; Iota!count) 12| ret.bytes[i] = _iterator[i]; 4| 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). |+/ 399|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 : "*")() | { 125| return mixin("fun(" ~ _iotaArgs!(params, "_iterator[", "], ") ~ ")"); | } | | auto ref opIndex()(ptrdiff_t index) | { 3016| return mixin("fun(" ~ _iotaArgs!(params, "_iterator[index + ", "], ") ~ ")"); | } | | mixin(std_ops); |} | |/// |version(mir_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_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: RefTuple, unref; | |@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]) : RefTuple!T, T...)) | { 18| auto t = *_iterator; 18| return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]"); | } | else 1809| return _field[*_iterator]; | } | | auto ref opIndex()(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { 0000000| auto t = _iterator[index]; 0000000| return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]"); | } | else 63| 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]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "] = value"); | } | else | return _field[_iterator[index]] = value; | } | | auto ref opIndexUnary(string op)(ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin(op ~ "_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]"); | } | else | return mixin(op ~ "_field[_iterator[index]]"); | } | | auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) | { | static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) | { | auto t = _iterator[index]; | return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]" ~ 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 | { 124| 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 : "*")() | { 1446| 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_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 : "*")() 16395| { return _field[_index]; } | | void opUnary(string op)() scope | if (op == "++" || op == "--") | { mixin(op ~ `_index;`); } | | auto ref opIndex()(ptrdiff_t index) 1532| { 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 == "-") | { 63| auto ret = this; | mixin(`ret ` ~ op ~ `= index;`); 63| 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_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) scope return | { | 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_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_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_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 92% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.82-mir-core-source-mir-enums.lst |/++ |Enum utilities. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko |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; | } | else | { | return enumMembers!T[index]; | } |} | |/++ |+/ |template getEnumIndexFromKey(T, bool caseInsesetive = 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, caseInsesetive); | static immutable indices = () | { | minimalSignedIndexType!indexLength[indexLength] indices; | | foreach (i, member; EnumMembers!T) | foreach (key; keysOf!(EnumMembers!T[i])) | { | static if (caseInsesetive) | { | key = key.dup.fastToUpperInPlace; | } | indices[table[key]] = i; | } | | return indices; | } (); | | static if (!caseInsesetive && keys.length <= 6 && keys[$ - 1].length <= 32) | { | | } | else | { | | } | | uint stringId = void; | if (_expect(table.get(key, stringId), true)) | { | index = indices[stringId]; | return true; | } | return false; | } | } |} ../../../.dub/packages/mir-core-1.1.82/mir-core/source/mir/enums.d has no code <<<<<< 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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: RefTuple; |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!(RefTuple, T); |package enum bool isInterval(alias T) = isInstanceOf!(RefTuple, 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 | { 1108| immutable sizediff_t len = interpolant.intervalCount - 1; 1108| auto grid = interpolant.gridScopeView[1 .. $][0 .. len]; | } 1402| assert(len >= 0); 1402| 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 1|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() | { 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. |+/ |RefTuple!(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 1034| *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) | { | copyvec(a, c); | 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) | { 514| copyvec(a, c); 514| 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) | { 1615| auto r = kernel(a0[i], b0[i], a1[i], b1[i]); | static if (R == 1) 1551| c[0][i] = r; | else | foreach(j; Iota!R) 168| 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 | // { 290| F[N][R] _c;//Temporary array in case "c" overlaps "a" and/or "b". | foreach(i; Iota!N) | { 566| auto r = kernel(a[i], b[i]); | static if (R == 1) 550| _c[0][i] = r; | else | foreach(j; Iota!R) 32| _c[j][i] = r[j]; | } 290| 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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: RefTuple; |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) | 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; | | /++ | 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() scope return 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(RefTuple!(size_t, T)(this.findInterval(x), x)); | } | | /// | pragma(inline, false) | auto opCall(RefTuple!(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-utility.lst |module mir.interpolate.utility; | |import mir.ndslice.slice; |import std.traits; |import mir.math.common: optmath; | |/++ |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, in Y y0, in Y y1, in 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, in Y y0, in Y y1, in Y y2) |{ 21| auto d0 = (y2 - y1) / (x2 - x1); 21| auto d1 = (y0 - y2) / (x0 - x2); 21| auto d2 = (y1 - y0) / (x1 - x0); 21| return [d1 + d2 - d0, d0 + d2 - d1, d0 + d1 - d2]; |} | |/// |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))); |} | | |/// |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; |} | |/// |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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments | |Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Yaroshenko (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 322| return less(b.front, a.front); | } | | /// Heap | BinaryHeap!(compFront, RangeOfRanges) _heap; | | /// 23| this(RangeOfRanges ror) | { | // Preemptively get rid of all empty ranges in the input | // No need for stability either 23| auto temp = ror.lightScope; 87| for (;!temp.empty;) | { 64| if (!temp.front.empty) | { 64| temp.popFront; 64| continue; | } | import mir.utility: swap; 0000000| swap(temp.back, temp.front); 0000000| temp.popBack; 0000000| ror.popBack; | } | //Build the heap across the range 23| _heap = typeof(_heap)(ror.move); | } | | /// 716| @property bool empty() scope const { return _heap.empty; } | | /// | @property auto ref front() | { 292| assert(!empty); 292| return _heap.front.front; | } | | /// | void popFront() scope @safe | { 173| _heap._store.front.popFront; 173| if (!_heap._store.front.empty) 109| _heap.siftDown(_heap._store[], 0, _heap._length); | else 64| _heap.removeFront; | } |} | |/// Ditto |MultiwayMerge!(naryFun!less, RangeOfRanges) multiwayMerge |(alias less = "a < b", RangeOfRanges) |(RangeOfRanges ror) |{ 23| 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; | 21| 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) |{ 15| size_t length; 15| auto u = move(ror).multiwayUnion!less; 15| if (!u.empty) do { 78| length++; 78| u.popFront; 78| } while(!u.empty); 15| return length; |} source/mir/algorithm/setops.d is 91% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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_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.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))); |} | |/// Transposed |@safe pure version(mir_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_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_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_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 (hasShape!NDRange) | { | import mir.conv: emplaceRef; | import mir.algorithm.iteration: each; | import mir.ndslice.allocation; 108| 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); 106| 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); | } | else | { 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 | { 96| if (__ctfe) | { | ret = shape.slice!UT; | ret.each!"a = b"(r); | } | else | { 96| ret = shape.uninitSlice!UT; 96| ret.each!(emplaceRef!T)(r); | } | } | } | static if (RC) | { | import core.lifetime: move; 4| return move(*(() @trusted => cast(R*)&ret)()); | } | else | { 212| return *(() @trusted => cast(R*)&ret)(); | } | } | else | alias fuseImpl = .fuseImpl!(RC, T_, staticMap!(toSize_t, Dimensions)); |} | |private template fuseDimensionCount(R) |{ | static if (is(typeof(R.init.shape) : size_t[N], size_t N) && (isDynamicArray!R || __traits(hasMember, R, "front"))) | { | import mir.ndslice.topology: repeat; | enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); | } | else | enum size_t fuseDimensionCount = 0; |} | |private static immutable shapeExceptionMsg = "fuseShape Exception: elements have different shapes/lengths"; | |version(D_Exceptions) | static immutable shapeException = new Exception(shapeExceptionMsg); | |/+ |TODO docs |+/ |size_t[fuseDimensionCount!Range] fuseShape(Range)(Range r) | if (hasShape!Range) |{ | // auto outerShape = r.shape; | enum N = r.shape.length; | enum RN = typeof(return).length; | enum M = RN - N; | static if (M == 0) | { 466| return r.shape; | } | else | { | import mir.ndslice.topology: repeat; 137| typeof(return) ret; 137| ret[0 .. N] = r.shape; 137| if (!ret[0 .. N].anyEmptyShape) | { 137| ret[N .. $] = fuseShape(mixin("r" ~ ".front".repeat(N).fuseCells.field)); | import mir.algorithm.iteration: all; 441| if (!all!((a) => cast(size_t[M]) ret[N .. $] == .fuseShape(a))(r)) | { | version (D_Exceptions) 0000000| throw shapeException; | else | assert(0, shapeExceptionMsg); | } | } 137| return ret; | } |} | |private template FuseElementType(NDRange) |{ | import mir.ndslice.topology: repeat; | alias FuseElementType = typeof(mixin("NDRange.init" ~ ".front".repeat(fuseDimensionCount!NDRange).fuseCells.field)); |} | |/++ |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; | } | else | { | 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); 4| return R(ret._structure, (() @trusted => cast(T*)ret._iterator)()); | } |} | |/// 1D |@safe pure version(mir_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_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 95% 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 Ilya Yaroshenko, 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), Ilya Yaroshenko (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 | { 32| if (_length < 2) return; 276| for (size_t n = _length - 1; n >= 1; --n) | { 106| auto parentIdx = (n - 1) / 2; 106| 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). | +/ 33| this(Store s, size_t initialSize = size_t.max) | { 33| 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) | { 33| _store = move(s); 33| _length = min(_store.length, initialSize); 34| if (_length < 2) return; 32| buildHeap(_store[]); 32| 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 | { 1204| return _length; | } | | /++ | Returns $(D true) if the heap is _empty, $(D false) otherwise. | +/ | @property bool empty() scope const | { 1190| 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() scope return | { 336| assert(!empty, "Cannot call front on an empty heap."); 336| 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)))) | { 0000000| if (length == _store.length) | { | // reallocate 0000000| _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 29| for (size_t n = _length; n; ) | { 17| auto parentIdx = (n - 1) / 2; 25| if (!comp(_store[parentIdx], _store[n])) break; // done! | // must swap and continue 9| _store.swapAt(parentIdx, n); 9| n = parentIdx; | } 10| ++_length; | debug(BinaryHeap) assertValid(); 10| return 1; | } | | /++ | Removes the largest element from the heap. | +/ | void removeFront() scope | { 105| assert(!empty, "Cannot call removeFront on an empty heap."); 105| if (--_length) 79| _store.swapAt(0, _length); 105| 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) |{ | 9| 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); |} | |@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) | { 32| immutable n = r.length; 127| for (size_t i = n / 2; i-- > 0; ) | { 63| siftDown(r, i, n); | } 32| assert(isHeap(r)); | } | | /// | bool isHeap()(Range r) | { 32| size_t parent = 0; 414| foreach (child; 1 .. r.length) | { 106| if (lessFun(r[parent], r[child])) return false; | // Increment parent every other pass 106| parent += !(child & 1); | } 32| 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 (;;) | { 250| auto child = (parent + 1) * 2; 250| if (child >= end) | { | // Leftover left child? 223| if (child == end && lessFun(r[parent], r[--child])) 43| r.swapAt(parent, child); 138| 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) | { 110| immutable root = parent; | | // Sift down | for (;;) | { 182| auto child = (parent + 1) * 2; | 182| if (child >= end) | { 110| if (child == end) | { | // Leftover left node. 21| --child; 21| r.swapAt(parent, child); 21| parent = child; | } 110| 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 246| 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 84% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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...) |{ 23| immutable len = lengths.lengthsProduct; 23| auto _lengths = lengths; 23| 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; | 219| auto result = (() @trusted => slice.shape.mininitRcslice!(Unqual!E))(); | | import mir.algorithm.iteration: each; 73| each!(emplaceRef!E)(result.lightScope, slice.lightScope); | 146| return *(() @trusted => cast(Slice!(RCI!E, N)*) &result)(); |} | |/// ditto |auto rcslice(T)(T[] array) |{ 24| return rcslice(array.sliced); |} | |/// ditto |auto rcslice(T, I)(I[] array) | if (!isImplicitlyConvertible!(I[], T[])) |{ | import mir.ndslice.topology: as; 1| return rcslice(array.sliced.as!T); |} | |/// |version(mir_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_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_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_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_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_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...) |{ 78| immutable len = lengths.lengthsProduct; 78| auto _lengths = lengths; 78| return Slice!(RCI!T, N)(_lengths, RCI!T(mininitRcarray!T(len))); |} | |/// |version(mir_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) | { 79| auto shape = lengths; // DMD variadic bug workaround 79| immutable len = shape.lengthsProduct; 157| 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)(); 79| 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_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_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) |{ 108| 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; | 216| auto result = (() @trusted => slice.shape.uninitSlice!(Unqual!E))(); | | import mir.algorithm.iteration: each; 108| each!(emplaceRef!E)(result, slice); | 216| return (() @trusted => cast(Slice!(E*, N)) result)(); | } |} | |/// |version(mir_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_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_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_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...) |{ 485| immutable len = lengths.lengthsProduct; | import std.array : uninitializedArray; 485| auto arr = uninitializedArray!(T[])(len); | version (mir_secure_memory) | {()@trusted{ | (cast(ubyte[])arr)[] = 0; | }();} 485| return arr.sliced(lengths); |} | |/// |version(mir_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_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_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_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_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_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_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_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_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_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_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_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_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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |Macros: |NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.series; | |public import mir.ndslice.slice; |public import mir.ndslice.sorting: sort; |import mir.ndslice.iterator: IotaIterator; |import mir.ndslice.sorting: transitionIndex; |import mir.qualifier; |import std.traits; | |/++ |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]]); |} | |/// |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; | /// An alias for time-series index. | alias time = index; | /// An alias for key-value representation. | alias key = index; | /// Value or ndslice. | Data data; | /// An alias for key-value representation. | alias value = 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. |+/ 15|struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous) |{ | private enum doUnittest = is(typeof(this) == Series!(int*, double*)); | | /// | alias IndexIterator = IndexIterator_; | | /// | alias Iterator = Iterator_; | | /// | enum size_t N = N_; | | /// | enum SliceKind kind = kind_; | | /// | Slice!(Iterator, N, kind) _data; | | /// | IndexIterator _index; | | /// Index / Key / Time type aliases | alias Index = typeof(typeof(this).init.index.front); | /// ditto | alias Key = Index; | /// ditto | alias Time = Index; | /// Data / Value type aliases | alias Data = typeof(typeof(this).init.data.front); | /// ditto | alias Value = Data; | | /// An alias for time-series index. | alias time = index; | /// An alias for key-value representation. | alias key = index; | /// An alias for key-value representation. | alias value = data; | | private enum defaultMsg() = "Series " ~ Unqual!(this.Data).stringof ~ "[" ~ Unqual!(this.Index).stringof ~ "]: Missing"; | private static immutable defaultExc() = new Exception(defaultMsg!() ~ " required key"); | |@optmath: | | /// 171| this()(Slice!IndexIterator index, Slice!(Iterator, N, kind) data) | { 171| assert(index.length == data.length, "Series constructor: index and data lengths must be equal."); 171| _data = data; 171| _index = index._iterator; | } | | | /// Construct from null 2| this(typeof(null)) | { 2| _data = _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; | } | | /++ | 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 | { 691| return _index.sliced(_data._lengths[0]); | } | | /// ditto | auto index()() @property @trusted const | { 204| return _index.lightConst.sliced(_data._lengths[0]); | } | | /// ditto | auto index()() @property @trusted immutable | { | return _index.lightImmutable.sliced(_data._lengths[0]); | } | | private auto lightScopeIndex()() @property @trusted | { 97| return .lightScope(_index).sliced(_data._lengths[0]); | } | | private auto lightScopeIndex()() @property @trusted const | { 31| return .lightScope(_index).sliced(_data._lengths[0]); | } | | private auto lightScopeIndex()() @property @trusted immutable | { | return .lightScope(_index).sliced(_data._lengths[0]); | } | | /++ | Data is any ndslice with only one constraints, | `data` and `index` lengths should be equal. | +/ | auto data()() @property @trusted | { 458| return _data; | } | | /// ditto | auto data()() @property @trusted const | { 204| return _data[]; | } | | /// ditto | auto data()() @property @trusted immutable | { | return _data[]; | } | | /// | typeof(this) opBinary(string op : "~")(typeof(this) rhs) | { 2| return unionSeries(this.lightScope, rhs.lightScope); | } | | /// ditto | auto opBinary(string op : "~")(const typeof(this) rhs) const | { 2| return unionSeries(this.lightScope, rhs.lightScope); | } | | 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| auto l = this.lightScope; 4| auto 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 < _data._lengths[0] && _index[idx] == key ? _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 < _data._lengths[0] && _index[idx] == key ? _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 < _data._lengths[0] && _index[idx] == key) | { 4| return _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 < _data._lengths[0] && _index[idx] == key) | { 3| return _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 < _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 < _data._lengths[0] && _index[idx] == key; | static if (__traits(compiles, &_data[size_t.init])) | { 2| if (cond) 1| return &_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 < _data._lengths[0] && _index[idx] == key; 4| if (cond) 2| val = _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 < _data._lengths[0]; 2| if (cond) 2| val = _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 < _data._lengths[0]; 2| if (cond) | { 2| key = _index[idx]; 2| val = _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 = _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 = _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 < _data._lengths[0] && _index[idx] <= upperBound; 2| if (cond) 1| val = _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 < _data._lengths[0] && _index[idx] <= upperBound; 1| if (cond) | { 1| lowerBound = _index[idx]; 1| val = _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 = _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 = _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) | { 973| return _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(_data.back!dimension); | } | else | { | return index.back.observation(_data.back); | } | } | | /// ditto | void popFront(size_t dimension = 0)() @trusted | if (dimension < N) | { 264| assert(!empty!dimension); | static if (dimension == 0) 263| _index++; 264| _data.popFront!dimension; | } | | /// ditto | void popBack(size_t dimension = 0)() | if (dimension < N) | { | assert(!empty!dimension); | _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; | _data.popFrontExactly!dimension(n); | } | | /// ditto | void popBackExactly(size_t dimension = 0)(size_t n) | if (dimension < N) | { 1| assert(length!dimension >= n); 1| _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 <= _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 _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()() @trusted @property | { 40| return typeof(return)(lightScopeIndex, _data.lightScope); | } | | /// ditto | Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() @trusted const @property | { 21| return typeof(return)(lightScopeIndex, _data.lightScope); | } | | /// ditto | Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() @trusted immutable @property | { | return typeof(return)(lightScopeIndex, _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, Spec)(auto ref Writer w, const ref Spec f) const | { | import std.format: formatValue, formatElement; | import std.range: put; | 150| if (f.spec != 's' && f.spec != '(') 0000000| throw new Exception("incompatible format character for Mir Series argument: %" ~ f.spec); | | enum defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; 100| auto fmtSpec = f.spec == '(' ? f.nested : defSpec; | 100| if (f.spec == 's') 50| put(w, f.seqBefore); 200| if (length) for (size_t i = 0;;) | { 200| auto fmt = Spec(fmtSpec); 200| fmt.writeUpToNextSpec(w); 200| if (f.flDash) | { 50| formatValue(w, index[i], fmt); 50| fmt.writeUpToNextSpec(w); 50| formatValue(w, data[i], fmt); | } | else | { 150| formatElement(w, index[i], fmt); 150| fmt.writeUpToNextSpec(w); 150| formatElement(w, data[i], fmt); | } 200| if (f.sep !is null) | { 0000000| fmt.writeUpToNextSpec(w); 0000000| if (++i != length) 0000000| put(w, f.sep); | else 0000000| break; | } | else | { 200| if (++i != length) 100| fmt.writeUpToNextSpec(w); | else 100| break; | } | } 100| if (f.spec == 's') 50| put(w, f.seqAfter); | } | | version(mir_test) | /// | unittest | { | import mir.series: series, sort; 25| auto s = ["b", "a"].series([9, 8]).sort; | | import std.conv : to; 25| assert(s.to!string == `["a":8, "b":9]`); | | import std.format : format; 25| assert("%s".format(s) == `["a":8, "b":9]`); 25| assert("%(%s %s | %)".format(s) == `"a" 8 | "b" 9`); 25| assert("%-(%s,%s\n%)\n".format(s) == "a,8\nb,9\n"); | } |} | |/// 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) | { | | } |} | |/++ |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, | ) |{ 110| assert(index.length == data.length); 110| return Series!(IndexIterator, Iterator, N, kind)(index, data); |} | |/// ditto |auto series(Index, Data)(Index[] index, Data[] data) |{ 52| assert(index.length == data.length); 52| 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.Key; | alias SV = series.Value; | 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. | |Params: | seriesTuple = variadic static array of composed of series, each series must be sorted. |Returns: sorted GC-allocated series. |See_also $(LREF Series.opBinary) $(LREF makeUnionSeries) |*/ |auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)(Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...) | if (C > 1) |{ 11| return unionSeriesImplPrivate!false(seriesTuple); |} | |/// |@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. | |Params: | seriesTuple = variadic static array of composed of series. |Returns: sorted manually allocated series. |See_also $(LREF unionSeries) |*/ |auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)(Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...) | if (C > 1) |{ 2| return unionSeriesImplPrivate!true(seriesTuple); |} | |/// |@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)( | Series!(IndexIterator, Iterator, N, kind)[] seriesTuple, | Series!(UI*, UE*, N) uninitSeries, | ) |{ | 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...)(ref Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple, ref Allocator allocator) | 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; | 15| immutable len = 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 | { 3| unionSeriesImpl!(I, E)(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-lob.lst |/++ |+/ |module mir.lob; | |/++ |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). |+/ |struct Clob |{ | /// | const(char)[] data; |} | |/++ |This is a sequence of octets with no interpretation (and thus opaque to the application). |+/ |struct Blob |{ | /// | const(ubyte)[] data; |} source/mir/lob.d has no code <<<<<< 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; |} | 1| 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); } 0000000|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 | { 1| immutable hexString = upper ? hexStringUpper : hexStringLower; 7| foreach_reverse(ref e; buf) | { 2| e = hexStringUpper[c & 0xF]; 2| c >>= 4; | } | } 1| 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); } | | 112|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) |{ 129| if (c < 0) | { 36| sign = '-'; 36| c = -c; | } | 129| auto ret = printUnsignedToTail(c, buf[1 .. N]); | 129| if (sign != '\0') | { 40| buf[$ - ++ret] = sign; | } 129| 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); } | 360|size_t printUnsignedToTail(uint c, scope ref char[10] buf) { return printUnsignedToTailGen(c, buf); } 16|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) | { 362| if (c < 10) | { 172| buf[$ - 1] = cast(char)('0' + c); 172| return 1; | } | static assert(N == 10); | } | else | static if (T.sizeof == 8) | { 17| if (c <= uint.max) | { 17| 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); 190| size_t refLen = buf.length; | do { 547| T nc = c / 10; 547| buf[].ptr[--refLen] = cast(C)('0' + c - nc * 10); 547| c = nc; | } 547| while(c); 190| 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"); |} | |ref W printIntegralZeroImpl(C, size_t N, W, I)(scope return ref 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 25% covered <<<<<< EOF # path=./source-mir-timestamp.lst |/++ |Timestamp |+/ |module mir.timestamp; | 360|private alias isDigit = (dchar c) => uint(c - '0') < 10; |import mir.serde: serdeIgnore; | |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("Invalid Month"); | private static immutable InvalidDay = new DateTimeException("Invalid Day"); | private static immutable InvalidISOString = new DateTimeException("Invalid ISO String"); | private static immutable InvalidISOExtendedString = new DateTimeException("Invalid ISO Extended String"); | private static immutable InvalidString = new DateTimeException("Invalid String"); |} | |/++ |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). |+/ |struct Timestamp |{ | import std.traits: isSomeChar; | | /// | enum Precision : ubyte | { | /// | year, | /// | month, | /// | day, | /// | minute, | /// | second, | /// | fraction, | } | | /// 8| this(scope const(char)[] str) @safe pure @nogc | { 8| 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; | } | } | |@serdeIgnore: | | /++ | 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 `fraction_exponent` and `fraction_coefficient` 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 | long fractionCoefficient; | } | else | { | import mir.bitmanip: bitfields; | version (LittleEndian) | { | | mixin(bitfields!( | ubyte, "minute", 8, | ubyte, "second", 8, | byte, "fractionExponent", 8, | long, "fractionCoefficient", 40, | )); | } | else | { | mixin(bitfields!( | long, "fractionCoefficient", 40, | byte, "fractionExponent", 8, | ubyte, "second", 8, | ubyte, "minute", 8, | )); | } | } | | /// | @safe pure nothrow @nogc 6| this(short year) | { 6| this.year = year; 6| this.precision = Precision.year; | } | | /// | @safe pure nothrow @nogc 4| this(short year, ubyte month) | { 4| this.year = year; 4| this.month = month; 4| this.precision = Precision.month; | } | | /// | @safe pure nothrow @nogc 62| this(short year, ubyte month, ubyte day) | { 62| this.year = year; 62| this.month = month; 62| this.day = day; 62| 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 18| this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute, ubyte second) | { 18| this.year = year; 18| this.month = month; 18| this.day = day; 18| this.hour = hour; 18| this.day = day; 18| this.minute = minute; 18| this.second = second; 18| this.precision = Precision.second; | } | | /// | @safe pure nothrow @nogc 8| this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute, ubyte second, byte fractionExponent, ulong fractionCoefficient) | { 8| this.year = year; 8| this.month = month; 8| this.day = day; 8| this.hour = hour; 8| this.day = day; 8| this.minute = minute; 8| this.second = second; 8| assert(fractionExponent < 0); 8| this.fractionExponent = fractionExponent; 8| this.fractionCoefficient = fractionCoefficient; 8| 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) | { 6| 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); | } | | /// 1| this(SysTime)(const SysTime systime) | if (SysTime.stringof == "SysTime") | { 2| with(systime.toUTC) this(year, month, day, hour, minute, second, -7, fracSecs.total!"hnsecs"); 1| 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); | } | | /// | T opCast(T)() const | if (T.stringof == "YearMonth" | || T.stringof == "YearMonthDay" | || T.stringof == "Date" | || T.stringof == "TimeOfDay" | || T.stringof == "date" | || T.stringof == "DateTime" | || T.stringof == "SysTime") | { | static if (T.stringof == "YearMonth") | { | return T(year, month, day); | } | else | static if (T.stringof == "Date" || T.stringof == "date" || T.stringof == "YearMonthDay") | { 2| return T(year, month, day); | } | else | static if (T.stringof == "DateTime") | { 1| return T(year, month, day, hour, minute, second); | } | else | static if (T.stringof == "TimeOfDay") | { 1| 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; 1| auto ret = SysTime(DateTime(year, month, day, hour, minute, second), UTC()); 1| if (fractionCoefficient) | { 1| long coeff = fractionCoefficient; 1| int exp = fractionExponent; 1| while (exp > -7) | { 0000000| exp--; 0000000| coeff *= 10; | } 1| while (exp < -7) | { 0000000| exp++; 0000000| coeff /= 10; | } 1| ret.fracSecs = coeff.hnsecs; | } 1| if (offset) | { 1| ret = ret.toOtherTZ(new immutable SimpleTimeZone(offset.minutes)); | } 1| return ret; | } | } | | /++ | Returns: true if timestamp represent a time only value. | +/ | bool isOnlyTime() @property const @safe pure nothrow @nogc | { 91| 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) | { 18| assert(-24 * 60 <= minutes && minutes <= 24 * 60, "Offset absolute value should be less or equal to 24 * 60"); 9| assert(precision >= Precision.minute, "Offsets are not allowed on date values."); 9| Timestamp ret = this; 9| ret.offset = minutes; 9| return ret; | } | | version(D_BetterC){} else | private string toStringImpl(alias fun)() const @safe pure nothrow | { | import mir.appender: UnsafeArrayBuffer; 49| char[64] buffer = void; 49| auto w = UnsafeArrayBuffer!char(buffer); 49| fun(w); 49| return w.data.idup; | } | | /++ | Converts this $(LREF Timestamp) to a string with the format `YYYY-MM-DDThh:mm:ss±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±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"); | } | | /// Helpfer for time zone offsets | void addMinutes(short minutes) @safe pure nothrow @nogc | { 10| int totalMinutes = minutes + (this.minute + this.hour * 60u); 10| auto h = totalMinutes / 60; | 10| int dayShift; | 11| while (totalMinutes < 0) | { 1| totalMinutes += 24 * 60; 1| dayShift--; | } | 10| while (totalMinutes >= 24 * 60) | { 0000000| totalMinutes -= 24 * 60; 0000000| dayShift++; | } | 10| if (dayShift) | { | import mir.date: Date; 1| auto ymd = (Date.trustedCreate(year, month, day) + dayShift).yearMonthDay; 1| year = ymd.year; 1| month = cast(ubyte)ymd.month; 1| day = ymd.day; | } | 10| hour = cast(ubyte) (totalMinutes / 60); 10| minute = cast(ubyte) (totalMinutes % 60); | } | | template toISOStringImp(bool ext) | { | version(D_BetterC){} else | string toISOStringImp() const @safe pure nothrow | { 49| return toStringImpl!toISOStringImp; | } | | /// ditto | void toISOStringImp(W)(scope ref W w) const scope | // if (isOutputRange!(W, char)) | { | import mir.format: printZeroPad; | // YYYY-MM-DDThh:mm:ss±hh:mm 49| Timestamp t = this; | 49| 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); | } | 49| if (!t.isOnlyTime) | { 39| if (t.year >= 10_000) 1| w.put('+'); 39| printZeroPad(w, t.year, t.year >= 0 ? t.year < 10_000 ? 4 : 5 : t.year > -10_000 ? 5 : 6); 39| if (precision == Precision.year) | { 4| w.put('T'); 4| return; | } 62| if (ext || precision == Precision.month) w.put('-'); | 35| printZeroPad(w, cast(uint)t.month, 2); 35| if (precision == Precision.month) | { 2| w.put('T'); 2| return; | } 25| static if (ext) w.put('-'); | 33| printZeroPad(w, t.day, 2); 33| if (precision == Precision.day) 25| return; | } | 18| if (!ext || !t.isOnlyTime) 11| w.put('T'); | 18| printZeroPad(w, t.hour, 2); 12| static if (ext) w.put(':'); 18| printZeroPad(w, t.minute, 2); | 18| if (precision >= Precision.second) | { 9| static if (ext) w.put(':'); 13| printZeroPad(w, t.second, 2); | 17| if (precision > Precision.second && (t.fractionExponent < 0 || t.fractionCoefficient)) | { 4| w.put('.'); 4| printZeroPad(w, t.fractionCoefficient, -int(t.fractionExponent)); | } | } | 18| if (t.offset == 0) | { 13| w.put('Z'); 13| 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±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")); | } | | 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 with the format YYYY-MM-DD, YYYYMMDD, or YYYY-Mon-DD. | | Params: | str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) and $(LREF .Timestamp.toISOString) format dates. 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 | { 10| return fromISOExtString(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) | { 10| Timestamp ret; 10| if (fromString(str, ret)) 10| return ret; 0000000| throw InvalidString; | } | | template fromISOStringImpl(bool ext) | { | static Timestamp fromISOStringImpl(C)(scope const(C)[] str) @safe pure | if (isSomeChar!C) | { 143| Timestamp ret; 143| if (fromISOStringImpl(str, ret)) 51| return ret; 92| throw InvalidISOExtendedString; | } | | static bool fromISOStringImpl(C)(scope const(C)[] str, out Timestamp value) @safe pure nothrow @nogc | if (isSomeChar!C) | { | import mir.parse: fromString, parse; | | static if (ext) 243| auto isOnlyTime = str.length >= 3 && (str[0] == 'T' || str[2] == ':'); | else 149| auto isOnlyTime = str.length >= 3 && str[0] == 'T'; | 158| if (!isOnlyTime) | { | // YYYY | static if (ext) | {{ 153| auto startIsDigit = str.length && str[0].isDigit; 77| auto strOldLength = str.length; 77| if (!parse(str, value.year)) 10| return false; 67| auto l = strOldLength - str.length; 67| if ((l == 4) != startIsDigit) 16| return false; | }} | else | { 206| if (str.length < 4 || !str[0].isDigit || !fromString(str[0 .. 4], value.year)) 15| return false; 57| str = str[4 .. $]; | } | 108| value.precision = Precision.year; 214| if (str.length == 0 || str == "T") 4| return true; | | static if (ext) | { 49| if (str[0] != '-') 9| return false; 40| str = str[1 .. $]; | } | | // MM 256| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.month)) 36| return false; 59| str = str[2 .. $]; 59| value.precision = Precision.month; 116| if (str.length == 0 || str == "T") 3| return ext; | | static if (ext) | { 28| if (str[0] != '-') 0000000| return false; 28| str = str[1 .. $]; | } | | // DD 160| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.day)) 6| return false; 50| str = str[2 .. $]; 50| value.precision = Precision.day; 50| if (str.length == 0) 36| return true; | } | | // str isn't empty here | // T 23| if (str[0] == 'T') | { 20| str = str[1 .. $]; | // OK, onlyTime requires length >= 3 20| if (str.length == 0) 0000000| return true; | } | else | { 3| if (!(ext && isOnlyTime)) 1| return false; | } | 22| value.precision = Precision.minute; // we don't have hour precision | | // hh 66| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.hour)) 0000000| return false; 22| str = str[2 .. $]; 22| if (str.length == 0) 0000000| return true; | | static if (ext) | { 12| if (str[0] != ':') 3| return false; 9| str = str[1 .. $]; | } | | // mm | { 19| uint minute; 57| if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], minute)) 0000000| return false; 19| value.minute = cast(ubyte) minute; 19| str = str[2 .. $]; 19| if (str.length == 0) 2| return true; | } | | static if (ext) | { 8| if (str[0] != ':') 2| goto TZ; 6| str = str[1 .. $]; | } | | // ss | { 15| uint second; 28| if (str.length < 2 || !str[0].isDigit) 2| goto TZ; 13| if (!fromString(str[0 .. 2], second)) 0000000| return false; 13| value.second = cast(ubyte) second; 13| str = str[2 .. $]; 13| value.precision = Precision.second; 13| if (str.length == 0) 2| return true; | } | | // . 11| if (str[0] != '.') 7| goto TZ; 4| str = str[1 .. $]; 4| value.precision = Precision.fraction; | | // fraction | { 4| const strOldLength = str.length; 4| ulong fractionCoefficient; 12| if (str.length < 1 || !str[0].isDigit || !parse!ulong(str, fractionCoefficient)) 0000000| return false; 4| sizediff_t fractionExponent = str.length - strOldLength; 4| if (fractionExponent < -12) 0000000| return false; 4| value.fractionExponent = cast(byte)fractionExponent; 4| value.fractionCoefficient = fractionCoefficient; 4| if (str.length == 0) 0000000| return true; | } | | TZ: | 15| if (str == "Z") 10| return true; | 5| int hour; 5| int minute; 15| if (str.length < 3 || str[0].isDigit || !fromString(str[0 .. 3], hour)) 0000000| return false; 5| str = str[3 .. $]; | 5| if (str.length) | { | static if (ext) | { 1| if (str[0] != ':') 0000000| return false; 1| str = str[1 .. $]; | } 9| if (str.length != 2 || !str[0].isDigit || !fromString(str[0 .. 2], minute)) 0000000| return false; | } | 5| value.offset = cast(short)(hour * 60 + (hour < 0 ? -minute : minute)); 5| value.addMinutes(cast(short)-int(value.offset)); 5| return true; | } | } |} source/mir/timestamp.d is 93% 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_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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko |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_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_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_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_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_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_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_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_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]); |} source/mir/ndslice/package.d is 92% 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 scope return pure nothrow @nogc @trusted @property | { 17686| assert(_payload); 17686| return (cast(mir_rc_context*)_payload)[-1]; | } 0000000| package void _reset() { _payload = null; } | | package alias ThisTemplate = .mir_rcarray; | package alias _thisPtr = _payload; | | /// | alias serdeKeysProxy = T; | | /// | void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe | { 192| auto t = this._payload; 192| this._payload = rhs._payload; 192| 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 0000000| 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) | { 19| if (false) // break @safe and pure attributes | { | Unqual!T* object; | (*object).__xdtor; | } | } 3224| if (this) | { 3698| (() @trusted { mir_rc_decrease_counter(context); })(); | } | } | | /// | size_t length() @trusted scope pure nothrow @nogc const @property | { 10701| return _payload !is null ? context.length : 0; | } | | /// | inout(T)* ptr() @system scope inout | { 150| return _payload; | } | | /// | ref opIndex(size_t i) @trusted scope inout | { 1252| assert(_payload); 1252| assert(i < context.length); 1252| return _payload[i]; | } | | /// | inout(T)[] opIndex() @trusted scope inout | { 2205| 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; 22| 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; 10| 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`. | +/ 175| this(size_t length, bool initialize = true, bool deallocate = true) @trusted @nogc | { 175| if (length == 0) 0000000| return; 175| Unqual!T[] ar; 175| () @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 175| auto ctx = mir_rc_create(mir_get_type_info!T, length, mir_get_payload_ptr!T, initialize, deallocate); 175| if (!ctx) | { | version(D_Exceptions) 0000000| throw allocationError; | else | assert(0, allocationExcMsg); | } 175| _payload = cast(T*)(ctx + 1); 175| ar = cast(Unqual!T[])_payload[0 .. length]; | } (); 284| if (initialize || hasElaborateAssign!(Unqual!T)) | { | import mir.conv: uninitializedFillDefault; 69| 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); | } | } | 1686| this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc | { 1686| if (rhs) | { 1675| this._payload = rhs._payload; 1675| mir_rc_increase_counter(context); | } | } | | /// | ref opAssign(typeof(null)) return @trusted // pure nothrow @nogc | { 1| this = typeof(this).init; | } | | /// | ref opAssign(return typeof(this) rhs) return @trusted // pure nothrow @nogc | { 2| this.proxySwap(rhs); 2| return this; | } | | /// | ref opAssign(Q)(return ThisTemplate!Q rhs) 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; | enum LikeArray = is(Range : Slice!(T*, N, kind), T, size_t N, SliceKind kind); | } | 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)(V[] values...) | if (is(T == void) && hasIndirections!V) |{ 1| return .rcarray(values, true); |} | |/// ditto |RCArray!V rcarray(T = void, V)(scope V[] values...) | if (is(T == void) && !hasIndirections!V) |{ 2| return .rcarray(values, true); |} | |/// ditto |RCArray!V rcarray(T = void, V)(V[] values, bool deallocate) | if (is(T == void) && hasIndirections!V) |{ 1| return .rcarray!V(values, deallocate); |} | |/// ditto |RCArray!V rcarray(T = void, V)(scope V[] values, bool deallocate) | if (is(T == void) && !hasIndirections!V) |{ 2| 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)) | { | 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)(V[] values...) | if (hasIndirections!V) | { | return .rcarray!T(values, true); | } | | /// ditto | RCArray!T rcarray(V)(scope V[] values...) | if (!hasIndirections!V) | { 9| return .rcarray!T(values, true); | } | | /// ditto | RCArray!T rcarray(V)(V[] values, bool deallocate) | if (hasIndirections!V) | { 1| auto ret = mir_rcarray!T(values.length, false, deallocate); | static if (!hasElaborateAssign!(Unqual!T) && is(Unqual!V == Unqual!T)) | { | ()@trusted { | import core.stdc.string: memcpy; | memcpy(cast(void*)ret.ptr, cast(const void*)values.ptr, values.length * T.sizeof); | }(); | } | else | { | import mir.conv: emplaceRef; 1| auto lhs = ret[]; 7| foreach (i, ref e; values) 1| lhs[i].emplaceRef!T(e); | } 1| return ret; | } | | /// ditto | RCArray!T rcarray(V)(scope V[] values, bool deallocate) | if (!hasIndirections!V) | { 11| auto ret = mir_rcarray!T(values.length, false); | static if (!hasElaborateAssign!(Unqual!T) && is(Unqual!V == Unqual!T)) | { 9| ()@trusted { | import core.stdc.string: memcpy; 9| memcpy(cast(void*)ret.ptr, cast(const void*)values.ptr, values.length * T.sizeof); | }(); | } | else | { | import mir.conv: emplaceRef; 2| auto lhs = ret[]; 14| foreach (i, ref e; values) 2| lhs[i].emplaceRef!T(e); | } 11| 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) |{ 89| return RCArray!T(length, false, deallocate); |} | |/// |@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. |+/ 1616|struct mir_rci(T) |{ | import mir.ndslice.slice: Slice; | import mir.ndslice.iterator: IotaIterator; | | /// | T* _iterator; | | /// | RCArray!T _array; | | /// 137| this(RCArray!T array) | { 274| this._iterator = (()@trusted => array.ptr)(); 137| this._array.proxySwap(array); | } | | /// 42| this(T* _iterator, RCArray!T array) | { 42| this._iterator = _iterator; 42| this._array.proxySwap(array); | } | | /// | inout(T)* lightScope()() scope return inout @property @trusted | { | debug | { 955| assert(_array._payload <= _iterator); 1910| assert(_iterator is null || _iterator <= _array._payload + _array.length); | } 955| 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 | { 7| _iterator = rhs._iterator; 7| _array.proxySwap(rhs._array); 7| 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()() scope return const nothrow @property 37| { return typeof(return)(_iterator, _array.lightConst); } | | /// | mir_rci!(immutable T) lightImmutable()() scope return immutable nothrow @property | { return typeof(return)(_iterator, _array.lightImmutable); } | | /// | ref inout(T) opUnary(string op : "*")() inout scope return | { | debug | { 221| assert(_iterator); 221| assert(_array._payload); 221| assert(_array._payload <= _iterator); 221| assert(_iterator <= _array._payload + _array.length); | } 221| return *_iterator; | } | | /// | ref inout(T) opIndex(ptrdiff_t index) inout scope return @trusted | { | debug | { 9454| assert(_iterator); 9454| assert(_array._payload); 9454| assert(_array._payload <= _iterator + index); 9454| assert(_iterator + index <= _array._payload + _array.length); | } 9454| 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 == "-") 5| { 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 |{ | import core.stdc.stdio; | | 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-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.bignum.low_level_view: DecimalExponentKey; |import mir.bignum.low_level_view: ceilLog10Exp2; | |private enum expBufferLength = 2 + ceilLog10Exp2(size_t.sizeof * 8); |private static immutable C[9] zerosImpl(C) = "0.00000.0"; | |/++ |Stack-allocated decimal type. |Params: | maxSize64 = count of 64bit words in coefficient |+/ |@serdeScoped @serdeProxy!(const(char)[]) |struct Decimal(size_t maxSize64) | if (maxSize64 && maxSize64 <= ushort.max) |{ | import mir.format: NumericSpec; | import mir.bignum.integer; | import mir.bignum.low_level_view; | import std.traits: isMutable, isFloatingPoint; | | /// | sizediff_t exponent; | /// | BigInt!maxSize64 coefficient; | | /// | DecimalView!size_t view() | { 1| return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); | } | | /// ditto | DecimalView!(const size_t) view() const | { 43| 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!" ~ maxSize64.stringof ~ " from string `", str , "`"); | } | else | { | static immutable exception = new Exception("Can't parse Decimal!" ~ maxSize64.stringof ~ "."); 0000000| throw exception; | } | } | | static if (maxSize64 == 3) | /// | version(mir_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); | } | | /++ | 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. | +/ 72| this(T)(const T x) | if (isFloatingPoint!T && maxSize64 >= 1 + (T.mant_dig >= 64)) | { | import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; 72| this = genericBinaryToDecimal(x); | } | | static if (maxSize64 == 3) | /// | 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); | } | | /// | ref opAssign(size_t rhsMaxSize64)(auto ref scope const Decimal!rhsMaxSize64 rhs) return | if (rhsMaxSize64 < maxSize64) | { 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, | ) | 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| stringBuf buffer; 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); | } | | static if (maxSize64 == 3) | /// | 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); | } | | /++ | 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) | { | enum optimize = size_t.sizeof == 8 && maxSize64 == 1; | version(LDC) | { | static if (optimize || (allowSpecialValues && allowDExponent && allowStartingPlus && checkEmpty) == false) | pragma(inline, true); | } | static if (optimize) | { | import mir.utility: _expect; | static if (checkEmpty) | { 29| if (_expect(str.length == 0, false)) 0000000| return false; | } | 29| coefficient.sign = str[0] == '-'; 29| if (coefficient.sign) | { 7| str = str[1 .. $]; 7| if (_expect(str.length == 0, false)) 0000000| return false; | } | else | static if (allowStartingPlus) | { 22| if (_expect(str[0] == '+', false)) | { 1| str = str[1 .. $]; 1| if (_expect(str.length == 0, false)) 0000000| return false; | } | } | 29| uint d = str[0] - '0'; 29| str = str[1 .. $]; 29| exponent = 0; | 29| ulong v; 29| bool dot; | static if (allowUnderscores) | { 29| bool recentUnderscore; | } | static if (!allowLeadingZeros) | { | if (d == 0) | { | if (str.length == 0) | goto R; | if (str[0] >= '0' && str[0] <= '9') | return false; | goto S; | } | } | 29| if (d < 10) | { 20| goto S; | } | | static if (allowDotOnBounds) | { 9| if (d == '.' - '0') | { 2| if (str.length == 0) 1| return false; 1| key = DecimalExponentKey.dot; 1| dot = true; 1| goto F; | } | } | | static if (allowSpecialValues) | { 7| goto NI; | } | else | { | return false; | } | | F: for(;;) | { 82| d = str[0] - '0'; 82| str = str[1 .. $]; | 82| if (_expect(d <= 10, true)) | { | static if (allowUnderscores) | { 64| recentUnderscore = false; | } | { | import mir.checkedint: mulu; 64| bool overflow; 64| v = mulu(v, cast(uint)10, overflow); 64| if (overflow) 1| break; | } | S: 83| v += d; 83| exponentShift -= dot; 83| if (str.length) 72| continue; | E: 15| exponent += exponentShift; | R: 24| coefficient.data[0] = v; 24| coefficient.length = v != 0; | static if (allowUnderscores) | { 24| return !recentUnderscore; | } | else | { | return true; | } | } | static if (allowUnderscores) | { 18| if (recentUnderscore) 0000000| return false; | } 18| switch (d) | { 13| case DecimalExponentKey.dot: 13| key = DecimalExponentKey.dot; 13| if (_expect(dot, false)) 2| break; 11| dot = true; 11| if (str.length) | { | static if (allowUnderscores) | { 9| recentUnderscore = true; | } 9| continue; | } | static if (allowDotOnBounds) | { 2| goto R; | } | else | { | break; | } | static if (allowExponent) | { | static if (allowDExponent) | { 1| case DecimalExponentKey.d: 2| case DecimalExponentKey.D: 2| goto case DecimalExponentKey.e; | } 4| case DecimalExponentKey.e: 5| case DecimalExponentKey.E: | import mir.parse: parse; 5| key = cast(DecimalExponentKey)d; 9| if (parse(str, exponent) && str.length == 0) 4| goto E; 1| break; | } | static if (allowUnderscores) | { 0000000| case '_' - '0': 0000000| recentUnderscore = true; 0000000| if (str.length) 0000000| continue; 0000000| break; | } 0000000| default: | } 3| break; | } 4| return false; | static if (allowSpecialValues) | { | NI: 7| exponent = exponent.max; 7| if (str.length == 2) | { 7| auto stail = cast(C[2])str[0 .. 2]; 17| if (d == 'i' - '0' && stail == cast(C[2])"nf" || d == 'I' - '0' && (stail == cast(C[2])"nf" || stail == cast(C[2])"NF")) | { 4| key = DecimalExponentKey.infinity; 4| goto R; | } 9| if (d == 'n' - '0' && stail == cast(C[2])"an" || d == 'N' - '0' && (stail == cast(C[2])"aN" || stail == cast(C[2])"AN")) | { 3| v = 1; 3| key = DecimalExponentKey.nan; 3| goto R; | } | } 0000000| return false; | } | } | else | { | import mir.bignum.low_level_view: DecimalView, BigUIntView, MaxWordPow10; 57| auto work = DecimalView!size_t(false, 0, BigUIntView!size_t(coefficient.data)); 57| auto ret = work.fromStringImpl!(C, | allowSpecialValues, | allowDotOnBounds, | allowDExponent, | allowStartingPlus, | allowUnderscores, | allowLeadingZeros, | allowExponent, | checkEmpty, | )(str, key, exponentShift); 57| coefficient.length = cast(uint) work.coefficient.coefficients.length; 57| coefficient.sign = work.sign; 57| exponent = work.exponent; 57| return ret; | } | } | | static if (maxSize64 == 3) | /// | version(mir_bignum_test) | @safe pure nothrow @nogc | unittest | { | import mir.conv: to; 1| Decimal!3 decimal; 1| DecimalExponentKey key; | | // Check precise percentate parsing 1| assert(decimal.fromStringImpl("71.7", key, -2)); 1| assert(key == DecimalExponentKey.dot); | // The result is exact value instead of 0.7170000000000001 = 71.7 / 100 1| assert(cast(double) decimal == 0.717); | 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("100_000_000"w, key)); 1| assert(key == DecimalExponentKey.none); 1| assert(cast(double) decimal == 1e8); | 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(key == DecimalExponentKey.none); 1| assert(cast(double) decimal == 2482734692817364218734682973648217364981273648923423.0); | 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); | 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)); | } | | static if (maxSize64 == 3) | 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); | 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)); | } | | 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 @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; | } | | static if (maxSize64 == 3) | /// | 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"); | } | | /// | void toString(C = char, W)(scope ref W w, NumericSpec spec = NumericSpec.init) const | if(isSomeChar!C && isMutable!C) | { 146| assert(spec.format == NumericSpec.Format.exponent || spec.format == NumericSpec.Format.human); | import mir.utility: _expect; | // handle special values 73| 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; | } | 65| C[coefficientBufferLength + 16] buffer0 = void; 65| auto buffer = buffer0[0 .. $ - 16]; | 65| size_t coefficientLength; | static if (size_t.sizeof == 8) | { 65| 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); | version (LittleEndian) | { | data[i * 2 + 0] = l; | data[i * 2 + 1] = h; | } | else | { | data[$ - 1 - (i * 2 + 0)] = l; | data[$ - 1 - (i * 2 + 1)] = h; | } | } | auto work = BigUIntView!uint(data); | work = work.topLeastSignificantPart(coefficient.length * 2).normalized; | coefficientLength = work.toStringImpl(buffer); | } | else | { 65| BigInt!maxSize64 work = coefficient; 65| coefficientLength = work.view.unsigned.toStringImpl(buffer); | } | } | else | { | BigInt!maxSize64 work = coefficient; | coefficientLength = work.view.unsigned.toStringImpl(buffer); | } | 65| C[1] sign = coefficient.sign ? "-" : "+"; 116| bool addSign = coefficient.sign || spec.plus; 65| sizediff_t s = this.exponent + coefficientLength; | | alias zeros = zerosImpl!C; | 65| if (spec.format == NumericSpec.Format.human) | { 65| 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 65| 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 .. -s + 2]); 30| w.put(buffer[$ - coefficientLength .. $]); 30| return; | } | } | else 28| 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[$ - (this.exponent + 2) .. $]); 8| return; | } | } | else | { 8| if (s <= 12) | { 7| buffer0[$ - 16 .. $] = '0'; 7| putL(buffer0[$ - coefficientLength - 16 .. $ - 16 + this.exponent]); 7| w.put(zeros[$ - 2 .. $]); 7| return; | } | } | } | else | { | ///dddddd.0 10| if (!spec.separatorChar) | { | ///dddddd.d.... 7| if (s <= 6 || coefficientLength <= 6) | { 5| buffer[$ - coefficientLength - 1] = sign[0]; 5| w.put(buffer[$ - coefficientLength - addSign .. $ - coefficientLength + s]); | T2: 9| buffer[$ - coefficientLength + s - 1] = '.'; 9| w.put(buffer[$ - coefficientLength + s - 1 .. $]); 9| return; | } | } | else | { 4| if (s <= 12 || coefficientLength <= 12) | { 4| putL(buffer[$ - coefficientLength .. $ - coefficientLength + s]); 4| goto T2; | } | } | } | } | 11| assert(coefficientLength); | 11| sizediff_t 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 (sizediff_t.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 .. $]); | } | | static if (maxSize64 == 3) | /// 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| stringBuf buffer; 1| buffer << decimal; 1| assert(buffer.data == str, buffer.data); | } | | /++ | 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, bool nonZero = false)() const | if (isFloatingPoint!T && isMutable!T) | { | | enum optimize = maxSize64 == 1 && size_t.sizeof == 8 && T.mant_dig < 64; | | version(LDC) | { | static if (optimize || wordNormalized) | pragma(inline, true); | } | | static if (optimize) | { | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp, extendedMul; | import mir.bignum.internal.dec2flt_table; | import mir.bignum.low_level_view: MaxWordPow5, MaxFpPow5; | import mir.math.common: floor; | import mir.utility: _expect; | 17| T ret = 0; 17| size_t length = coefficient.length; | | | static if (!wordNormalized) | { 17| if (coefficient.data[0] == 0) 5| length = 0; | } | 17| if (_expect(exponent == exponent.max, false)) | { 10| ret = length ? T.nan : T.infinity; 10| goto R; | } | | static if (!nonZero) 7| if (length == 0) 1| goto R; | enum S = 9; | | Fp!64 load(typeof(exponent) e) | { 6| auto p10coeff = p10_coefficients[cast(sizediff_t)e - min_p10_e][0]; 6| auto p10exp = p10_exponents[cast(sizediff_t)e - min_p10_e]; 6| return Fp!64(false, p10exp, UInt!64(p10coeff)); | } | { 6| auto expSign = exponent < 0; 6| if (_expect((expSign ? -exponent : exponent) >>> S == 0, true)) | { | enum ulong mask = (1UL << (64 - T.mant_dig)) - 1; | enum ulong half = (1UL << (64 - T.mant_dig - 1)); | enum ulong bound = ulong(1) << T.mant_dig; | 6| auto c = Fp!64(UInt!64(coefficient.data[0])); 6| auto z = c.extendedMul(load(exponent)); 6| ret = cast(T) z; 6| long bitsDiff = (cast(ulong) z.opCast!(Fp!64).coefficient & mask) - half; 6| if (_expect((bitsDiff < 0 ? -bitsDiff : bitsDiff) > 3 * expSign, true)) 6| goto R; 0000000| if (!expSign && exponent <= MaxWordPow5!ulong || exponent == 0) 0000000| goto R; 0000000| if (expSign && MaxFpPow5!T >= -exponent && cast(ulong)c.coefficient < bound) | { 0000000| auto e = load(-exponent); 0000000| ret = c.opCast!(T, true) / cast(T) (cast(ulong)e.coefficient >> e.exponent); 0000000| goto R; | } 0000000| ret = algoR!T(ret, view.coefficient, cast(int) exponent); 0000000| goto R; | } 0000000| ret = expSign ? 0 : T.infinity; | } | R: 17| if (coefficient.sign) 8| ret = -ret; 17| return ret; | } | else | { 43| return view.opCast!(T, wordNormalized, nonZero); | } | } | | /// | bool isNaN() const @property | { 0000000| return exponent == exponent.max && coefficient.length; | } | | /// | bool isInfinity() const @property | { 0000000| return exponent == exponent.max && !coefficient.length; | } | | /// | ref opOpAssign(string op, size_t rhsMaxSize64)(ref const Decimal!rhsMaxSize64 rhs) @safe pure return | if (op == "+" || op == "-") | { 20| BigInt!rhsMaxSize64 rhsCopy; 20| BigIntView!(const size_t) rhsView; 20| auto expDiff = exponent - rhs.exponent; 20| if (expDiff >= 0) | { 11| exponent = rhs.exponent; 11| coefficient.mulPow5(expDiff); 11| coefficient.opOpAssign!"<<"(expDiff); 11| rhsView = rhs.coefficient.view; | } | else | { 9| rhsCopy.copyFrom(rhs.coefficient.view); 9| rhsCopy.mulPow5(-expDiff); 9| rhsCopy.opOpAssign!"<<"(-expDiff); 9| rhsView = rhsCopy.view; | } 20| coefficient.opOpAssign!op(rhsView); 20| return this; | } | | static if (maxSize64 == 3) | /// | version(mir_bignum_test) @safe pure @nogc unittest | { | import std.stdio; 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"); | } |} | |/// |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ | import mir.conv: to; 1| Decimal!3 decimal; 1| DecimalExponentKey key; | | import mir.math.constant: PI; 1| assert(decimal.fromStringImpl("3.141592653589793378e-10", key)); 1| assert(cast(double) decimal == double(PI) / 1e10); 1| assert(key == 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); |} | |deprecated("use decimal.fromStringImpl insteade") |@trusted @nogc pure nothrow |bool parseDecimal(size_t maxSize64, C)(scope const(C)[] str, ref Decimal!maxSize64 decimal, out DecimalExponentKey key) | if (isSomeChar!C) |{ | return decimal.fromStringImpl(str, key); |} | | |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); |} source/mir/bignum/decimal.d is 92% 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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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_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_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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko |+/ |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); | |extern(C++, "mir"): | |/++ |Self-contained generic Small String implementaton. |+/ |@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 11| this(scope const(char)[] str) | { 11| 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 | { 11| if (str.length > _data.sizeof) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 11| if (__ctfe) | _data[0 .. str.length] = str; | else 11| memcpy(_data.ptr, str.ptr, str.length); 11| _data[str.length .. $] = '\0'; 11| 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 | { 1| auto length = opIndex.length; 1| if (length == maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 1| _data[length] = c; 1| return this; | } | | /// | ref typeof(this) append(scope const(char)[] str) @trusted | { 6| auto length = opIndex.length; 6| if (length + str.length > maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 6| if (__ctfe) | _data[length .. str.length + length] = str; | else 6| memcpy(_data.ptr + length, str.ptr, str.length); 6| 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 property is used as common string representation self alias. | | The alias helps with `[]`, `[i]`, `[i .. j]`, `==`, and `!=` operations and implicit conversion to strings. | +/ | inout(char)[] opIndex() inout @trusted scope return | { 30| size_t i; 30| if (__ctfe) | while (i < maxLength && _data[i]) i++; | else 30| i = _data[$ - 1] ? _data.length : strlen(_data.ptr); 30| return _data[0 .. i]; | } | | /// | ref inout(char) opIndex(size_t index) inout scope return | { 1| return opIndex[index]; | } | |const: | | /// | bool empty() @property | { 2| return _data[0] == 0; | } | | /// | size_t length() @property | { 0000000| return opIndex.length; | } | | /// | alias toString = opIndex; | | /// Hash implementation | size_t toHash() | { 0000000| return hashOf(opIndex); | } | | /// Comparisons operator overloads | bool opEquals(ref scope const SmallString rhs) scope | { 1| return _data == rhs._data; | } | | /// ditto | bool opEquals(SmallString rhs) scope | { 0000000| return _data == rhs._data; | } | | /// ditto | bool opEquals(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) scope | if (rhsMaxLength != maxLength) | { | return opIndex == rhs.opIndex; | } | | /// ditto | bool opEquals()(scope const(char)[] str) scope | { 9| return opIndex == str; | } | | /// ditto | int opCmp(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) scope | { 1| return __cmp(opIndex, rhs.opIndex); | } | | /// ditto | int opCmp()(scope const(char)[] str) scope | { 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!!", s8[]); | 1| s16 = s8; 1| assert(s16 == "Hellow!!", s16[]); 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 85% 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: | maxSize64 = count of 64bit words in coefficient |+/ |@serdeScoped @serdeProxy!(const(char)[]) |struct BigInt(size_t maxSize64) | if (maxSize64 && maxSize64 <= ushort.max) |{ | import mir.bignum.low_level_view; | import mir.bignum.fixed; | | /// | bool sign; | /// | uint length; | /// | size_t[ulong.sizeof / size_t.sizeof * maxSize64] data = void; | | /// 115| this(size_t size)(UInt!size fixedInt) | { 115| this(fixedInt.data); | } | | /// 115| this(size_t N)(size_t[N] data) | if (N <= this.data.length) | { 115| sign = false; | version(LittleEndian) 115| this.data[0 .. N] = data; | else | this.data[$ - N .. $] = data; 115| length = data.length; 115| normalize; | } | | /// 0000000| this(ulong data) | { 0000000| sign = false; | static if (size_t.sizeof == ulong.sizeof) | { 0000000| length = 1; 0000000| view.leastSignificantFirst[0] = data; | } | else | { | length = 2; | auto d = view.leastSignificantFirst; | d[0] = cast(uint) data; | d[1] = cast(uint) (data >> 32); | } 0000000| normalize; | } | | /// | this()(scope const(char)[] str) @safe pure @nogc | // if (isSomeChar!C) | { 6| if (fromStringImpl(str)) 6| return; | static if (__traits(compiles, () @nogc { throw new Exception("Can't parse BigInt."); })) | { | import mir.exception: MirException; | throw new MirException("Can't parse BigInt!" ~ maxSize64.stringof ~ " from string `", str , "`."); | } | else | { | static immutable exception = new Exception("Can't parse BigInt!" ~ maxSize64.stringof ~ "."); 0000000| throw exception; | } | } | | /// | ref opAssign(ulong data) return | { | static if (size_t.sizeof == ulong.sizeof) | { 4| length = 1; 4| view.leastSignificantFirst[0] = data; | } | else | { | length = 2; | auto d = view.leastSignificantFirst; | d[0] = cast(uint) data; | d[1] = cast(uint) (data >> 32); | } 4| normalize; 4| return this; | } | | static if (maxSize64 == 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(size_t rhsMaxSize64)(auto ref scope const BigInt!rhsMaxSize64 rhs) return | if (rhsMaxSize64 < maxSize64) | { 4| this.sign = rhs.sign; 4| this.length = rhs.length; | version(LittleEndian) | { 4| data[0 .. length] = rhs.data[0 .. length]; | } | else | { | data[$ - length .. $] = rhs.data[$ - length .. $]; | } 4| return this; | } | | /++ | 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) | { 6| auto work = BigIntView!size_t(data[]); 6| if (work.fromStringImpl(str)) | { 6| length = cast(uint) work.coefficients.length; 6| sign = work.sign; 6| 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 | { 12| return view == rhs.view; | } | | /// | bool opEquals()(size_t rhs, bool rhsSign = false) | const @safe pure nothrow @nogc | { 12| return rhs == 0 && length == 0 || length == 1 && sign == rhsSign && view.unsigned.leastSignificant == rhs; | } | | /// | bool opEquals()(sizediff_t rhs) | const @safe pure nothrow @nogc | { 3| auto sign = rhs < 0; 3| return opEquals(sign ? ulong(-rhs) : ulong(rhs), sign); | } | | /++ | +/ | auto opCmp()(auto ref const BigInt rhs) | const @safe pure nothrow @nogc | { 1| return view.opCmp(rhs.view); | } | | /// | BigIntView!size_t view()() @property | { | version (LittleEndian) 1096| return typeof(return)(data[0 .. length], sign); | else | return typeof(return)(data[$ - length .. $], sign); | } | | /// | BigIntView!(const size_t) view()() const @property | { | version (LittleEndian) 141| return typeof(return)(data[0 .. length], sign); | else | return typeof(return)(data[$ - length .. $], sign); | } | | /// | void normalize()() | { 181| auto norm = view.normalized; 181| this.length = cast(uint) norm.unsigned.coefficients.length; 181| this.sign = norm.sign; | } | | /++ | +/ | void putCoefficient(size_t value) | { 331| assert(length < data.length); | version (LittleEndian) 331| data[length++] = value; | else | 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 | { 1| if (length == 0) 0000000| goto L; 1| overflow = view.unsigned.opOpAssign!op(rhs, overflow); 2| if (overflow && length < data.length) | { | L: 0000000| putCoefficient(overflow); 0000000| overflow = 0; | } 1| 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 | { 36| if (length == 0) 0000000| goto L; 36| overflow = view.unsigned.opOpAssign!op(rhs, overflow); 72| if (overflow && length < data.length) | { | L: | static if (size <= 64) | { 35| auto o = cast(ulong)overflow; | static if (size_t.sizeof == ulong.sizeof) | { 35| putCoefficient(o); 35| 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); | } | } 36| return overflow; | } | | /++ | Performs `size_t overflow = big *= fixed` operatrion. | Params: | rhs = unsigned value to multiply by | Returns: | overflow | +/ | bool opOpAssign(string op, size_t rhsMaxSize64)(ref const BigInt!rhsMaxSize64 rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 44| return opOpAssign!op(rhs.view); | } | | /// ditto | bool opOpAssign(string op)(BigIntView!(const size_t) rhs) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 64| sizediff_t diff = length - rhs.coefficients.length; 64| if (diff < 0) | { 5| auto oldLength = length; 5| length = cast(int)rhs.coefficients.length; 5| view.unsigned.leastSignificantFirst[oldLength .. $] = 0; | } | else 59| if (rhs.coefficients.length == 0) 2| return false; 62| auto thisView = view; 62| auto overflow = thisView.opOpAssign!op(rhs); 62| this.sign = thisView.sign; 62| if (overflow) | { 0000000| if (length < data.length) | { 0000000| putCoefficient(overflow); 0000000| overflow = false; | } | } | else | { 62| normalize; | } 62| return overflow; | } | | /++ | +/ | static BigInt fromHexString(bool allowUnderscores = false)(scope const(char)[] str) | @trusted pure | { 11| BigInt ret; 11| if (ret.fromHexStringImpl!(char, allowUnderscores)(str)) 11| 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) | { 11| auto work = BigIntView!size_t(data); 11| auto ret = work.fromHexStringImpl!(C, allowUnderscores)(str); 11| if (ret) | { 11| length = cast(uint)work.unsigned.coefficients.length; 11| sign = work.sign; | } 11| return ret; | } | | /// | bool mulPow5(size_t degree) | { | // assert(approxCanMulPow5(degree)); 63| if (length == 0) 4| return false; | enum n = MaxWordPow5!size_t; | enum wordInit = size_t(5) ^^ n; 59| size_t word = wordInit; 59| bool of; 379| while(degree) | { 320| if (degree >= n) | { 267| degree -= n; | } | else | { 53| word = 1; 556| do word *= 5; 556| while(--degree); | } 320| if (auto overflow = view *= word) | { 302| of = length >= data.length; 302| if (!of) 296| putCoefficient(overflow); | } | } 59| return of; | } | | /// | ref BigInt opOpAssign(string op)(size_t shift) | @safe pure nothrow @nogc return | if (op == "<<" || op == ">>") | { 103| auto index = shift / (size_t.sizeof * 8); 103| auto bs = shift % (size_t.sizeof * 8); 103| auto ss = size_t.sizeof * 8 - bs; | static if (op == ">>") | { 3| if (index >= length) | { 0000000| length = 0; 0000000| return this; | } 3| auto d = view.leastSignificantFirst; 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.back >>> bs; 3| length -= index + (most == 0); | } | else | { 198| if (index >= data.length || length == 0) | { 6| length = 0; 6| return this; | } | 94| if (bs) | { 81| auto most = view.unsigned.mostSignificant >> ss; 81| length += index; 81| if (length < data.length) | { 70| if (most) | { 14| length++; 14| view.unsigned.mostSignificant = most; 14| length--; | } | } | else | { 11| length = data.length; | } | 81| auto d = view.leastSignificantFirst; 775| foreach_reverse (j; index + 1 .. length) | { 266| d[j] = (d[j - index] << bs) | (d[j - (index + 1)] >> ss); | } 81| d[index] = d.front << bs; 81| if (length < data.length) 70| length += most != 0; | } | else | { 13| length = cast(uint) min(length + index, cast(uint)data.length); 13| auto d = view.leastSignificantFirst; 79| foreach_reverse (j; index .. length) | { 20| d[j] = d[j - index]; | } | } 94| view.leastSignificantFirst[0 .. index] = 0; | } 97| return this; | } | | /// | T opCast(T, bool wordNormalized = false, bool nonZero = false)() const | if (isFloatingPoint!T && isMutable!T) | { 3| return view.opCast!(T, wordNormalized, nonZero); | } | | /// | 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, WordEndian endian)(BigIntView!(const W, endian) view) | { | static if (W.sizeof > size_t.sizeof && endian == TargetEndian) | { | return this.copyFrom(cast(BigIntView!(const size_t))view); | } | else | { 53| this.sign = view.sign; 53| auto lhs = BigUIntView!W(cast(W[])data); 53| auto rhs = view; 53| auto overflow = lhs.coefficients.length < rhs.coefficients.length; 53| auto n = overflow ? lhs.coefficients.length : rhs.coefficients.length; 53| lhs.leastSignificantFirst[0 .. n] = rhs.leastSignificantFirst[0 .. n]; 53| this.length = cast(uint)(n / (size_t.sizeof / W.sizeof)); 53| if (auto tail = n % (size_t.sizeof / W.sizeof)) | { 23| this.length++; 23| auto shift = ((size_t.sizeof / W.sizeof) - tail) * (W.sizeof * 8); 23| auto value = this.view.unsigned.mostSignificant; 23| value <<= shift; 23| value >>= shift; 23| this.view.unsigned.mostSignificant = value; | } 53| return overflow; | } | } | | /// ditto | bool copyFrom(W, WordEndian endian)(BigUIntView!(const W, endian) view) | { 42| return this.copyFrom(BigIntView!(const W, endian)(view)); | } | | /// | immutable(C)[] toString(C = char)() 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 (maxSize64 == 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)(scope ref W w) 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 .. $]); | } | | static if (maxSize64 == 3) | /// Check @nogc toString impl | version(mir_bignum_test) @safe pure @nogc unittest | { | import mir.format: stringBuf; 1| auto str = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; 1| auto integer = BigInt!4(str); 2| stringBuf buffer; 1| buffer << integer; 1| assert(buffer.data == str, buffer.data); | } |} | | |/// |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.view)); 1| assert(c == b); 1| b >>= 18; 1| auto bView = cast(BigIntView!ushort)b.view; 1| assert(!c.copyFrom(bView.topLeastSignificantPart(bView.unsigned.coefficients.length - 1))); 1| assert(c == b); |} | |version(mir_bignum_test) |@safe pure @nogc unittest |{ 1| BigInt!4 i = "-0"; 1| assert(i.view.coefficients.length == 0); 1| assert(cast(long) i == 0); |} source/mir/bignum/integer.d is 88% covered <<<<<< EOF # path=./source-mir-type_info.lst |/++ |$(H1 Type Information) | |Type Information implementation compatible with BetterC mode. | |Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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)) | { 6| 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 | { 5| return *cast(T*)ptr; | } | version(assert) 5| destroy!true(inst()); | else | destroy!false(inst()); | } | | static immutable ti = mir_type_info(cast(SetFunctionAttributes!(fun, "C", functionAttributes!fun))&destroy_impl, sizeof); 5| return ti; | } | else | { 182| 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); 182| 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)) | { 6| 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)) | { 157| return null; | } | else | { | static immutable payload = T.init; 26| return &payload; | } |} source/mir/type_info.d is 100% 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 Ilya Yaroshenko, 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; | 43| 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) | { 33| auto length = r.length; 33| if (length == 0) 0000000| return null; | | import mir.conv : emplaceRef; | import std.array: uninitializedArray; | 66| auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))(); | | static if (isInputRange!Range) | { 15663| foreach(ref e; result) | { 5189| emplaceRef!E(e, r.front); 5189| 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 .. $]; | } | } | 66| return (() @trusted => cast(E[]) result)(); | } | else | { | import mir.appender: scopedBuffer; | import std.array: uninitializedArray; | 20| auto a = scopedBuffer!(Unqual!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 () @trusted { 10| auto ret = uninitializedArray!(Unqual!E[])(a.length); 10| a.moveDataAndEmplaceTo(ret); 10| return ret; | } (); | } |} | |/// |@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() @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-graph-package.lst |/++ |Basic routines to work with graphs. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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-bignum-fp.lst |/++ |Note: | The module doesn't provide full arithmetic API for now. |+/ |module mir.bignum.fp; | |import std.traits; |import mir.bitop; |import mir.utility; | |package enum half(size_t hs) = (){ | import mir.bignum.fixed: UInt; | UInt!hs ret; ret.signBit = true; return ret; |}(); | |/++ |Software floating point number. | |Params: | coefficientSize = coefficient size in bits | |Note: the implementation doesn't support NaN and Infinity values. |+/ |struct Fp(size_t coefficientSize, Exp = sizediff_t) | if ((is(Exp == int) || is(Exp == long)) && coefficientSize % (size_t.sizeof * 8) == 0 && coefficientSize >= (size_t.sizeof * 8)) |{ | import mir.bignum.fixed: UInt; | | bool sign; | Exp exponent; | UInt!coefficientSize coefficient; | | /++ | +/ | nothrow 1902| this(bool sign, Exp exponent, UInt!coefficientSize normalizedCoefficient) | { 1902| this.coefficient = normalizedCoefficient; 1902| this.exponent = exponent; 1902| 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. | +/ 75| this(T)(const T value, bool normalize = true) | @safe pure nothrow @nogc | if (isFloatingPoint!T && T.mant_dig <= coefficientSize) | { | import mir.math.common : fabs; | import mir.math.ieee : frexp, signbit, ldexp; 76| assert(value == value); 76| assert(value.fabs < T.infinity); 76| this.sign = value.signbit != 0; 76| if (value == 0) 1| return; 75| T x = value.fabs; 75| int exp; | { | enum scale = T(2) ^^ T.mant_dig; 75| x = frexp(x, exp) * scale; 75| exp -= T.mant_dig; | } | static if (T.mant_dig < 64) | { 74| this.coefficient = UInt!coefficientSize(cast(ulong)cast(long)x); | } | else | static if (T.mant_dig == 64) | { 1| this.coefficient = UInt!coefficientSize(cast(ulong)x); | } | 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; | this.coefficient = (UInt!coefficientSize(ulong(high)) << 64) | cast(ulong)x; | } 75| if (normalize) | { 1| auto shift = cast(int)this.coefficient.ctlz; 1| exp -= shift; 1| this.coefficient <<= shift; | } | else | { 74| int shift = T.min_exp - T.mant_dig - exp; 74| if (shift > 0) | { 0000000| this.coefficient >>= shift; 0000000| exp = T.min_exp - T.mant_dig; | } | } 75| this.exponent = exp; | } | | static if (coefficientSize == 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 assert(cast(real) Fp!64(real.min_normal / 2) == real.min_normal / 2); | static assert(cast(real) Fp!64(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 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); | } | | static if (coefficientSize == 128) | /// Without normalization | version(mir_bignum_test) | @safe pure @nogc nothrow | unittest | { 1| auto f = Fp!64(-33.0 * 2.0 ^^ -10, false); 1| assert(f.sign); 1| assert(f.exponent == -10 - (double.mant_dig - 6)); 1| assert(f.coefficient == 33UL << (double.mant_dig - 6)); | } | | /++ | +/ 976| this(size_t size)(UInt!size integer, bool normalizedInteger = false) | nothrow | { | import mir.bignum.fixed: UInt; | static if (size < coefficientSize) | { 3| if (normalizedInteger) | { 1| this(false, Exp(size) - coefficientSize, integer.rightExtend!(coefficientSize - size)); | } | else | { 2| this(integer.toSize!coefficientSize, false); | } | } | else | { 973| this.exponent = size - coefficientSize; 973| if (!normalizedInteger) | { 16| auto c = integer.ctlz; 16| integer <<= c; 16| this.exponent -= c; | } | static if (size == coefficientSize) | { 13| coefficient = integer; | } | else | { | enum N = coefficient.data.length; | version (LittleEndian) 960| coefficient.data = integer.data[$ - N .. $]; | else | coefficient.data = integer.data[0 .. N]; | enum tailSize = size - coefficientSize; 960| auto cr = integer.toSize!tailSize.opCmp(half!tailSize); 1697| if (cr > 0 || cr == 0 && coefficient.bt(0)) | { 283| if (auto overflow = coefficient += 1) | { 1| coefficient = half!coefficientSize; 1| exponent++; | } | } | } | } | } | | static if (coefficientSize == 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")); | } | | /// | ref Fp opOpAssign(string op : "*")(Fp rhs) nothrow return | { 77| this = this.opBinary!op(rhs); 77| return this; | } | | /// | Fp opBinary(string op : "*")(Fp rhs) nothrow const | { 78| return cast(Fp) .extendedMul(this, rhs); | } | | static if (coefficientSize == 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 * b; 1| assert(fp.sign); 1| assert(fp.exponent == 100 - 13 + 128); 1| assert(fp.coefficient == UInt!128.fromHexString("c6841dd302415d785373ab6d93712988")); | } | | /// | T opCast(T)() nothrow const | if (is(Unqual!T == bool)) | { | return coefficient != 0; | } | | /// | T opCast(T, bool noHalf = false)() nothrow const | if (isFloatingPoint!T) | { | import mir.math.ieee: ldexp; 916| auto exp = cast()exponent; | static if (coefficientSize == 32) | { | Unqual!T c = cast(uint) coefficient; | } | else | static if (coefficientSize == 64) | { 16| Unqual!T c = cast(ulong) coefficient; | } | else | { | enum shift = coefficientSize - T.mant_dig; | enum rMask = (UInt!coefficientSize(1) << shift) - UInt!coefficientSize(1); | enum rHalf = UInt!coefficientSize(1) << (shift - 1); | enum rInc = UInt!coefficientSize(1) << shift; 900| UInt!coefficientSize adC = coefficient; | static if (!noHalf) | { 900| auto cr = (coefficient & rMask).opCmp(rHalf); 900| if ((cr > 0) | (cr == 0) & coefficient.bt(shift)) | { 348| if (auto overflow = adC += rInc) | { 29| adC = half!coefficientSize; 29| exp++; | } | } | } 900| adC >>= shift; 900| exp += shift; 900| Unqual!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); | } | } 916| if (sign) 5| c = -c; | static if (exp.sizeof > int.sizeof) | { | import mir.utility: min, max; 916| exp = exp.max(int.min).min(int.max); | } 916| return ldexp(c, cast(int)exp); | } | | static if (coefficientSize == 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); | } | | static if (coefficientSize == 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 (coefficientSize == 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 (coefficientSize == 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!newCoefficientSize, size_t newCoefficientSize)() nothrow const | { 956| auto ret = Fp!newCoefficientSize(coefficient, true); 956| ret.exponent += exponent; 956| ret.sign = sign; 956| return ret; | } | | static if (coefficientSize == 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")); | } |} | |/// |Fp!(coefficientizeA + coefficientizeB) extendedMul(size_t coefficientizeA, size_t coefficientizeB)(Fp!coefficientizeA a, Fp!coefficientizeB b) | @safe pure nothrow @nogc |{ | import mir.bignum.fixed: extendedMul; 976| auto coefficient = extendedMul(a.coefficient, b.coefficient); 976| auto exponent = a.exponent + b.exponent; 976| auto sign = a.sign ^ b.sign; 976| if (!coefficient.signBit) | { 701| --exponent; 701| coefficient = coefficient.smallLeftShift(1); | } 976| return typeof(return)(sign, exponent, coefficient); |} source/mir/bignum/fp.d is 98% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.82-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 | 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(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.1.82/mir-core/source/mir/internal/utility.d has no code <<<<<< EOF # path=./source-mir-string_map.lst |/++ |$(H1 Ordered string-value associtaive array) |Macros: |AlgebraicREF = $(GREF_ALTTEXT mir-core, $(TT $1), $1, mir, algebraic)$(NBSP) |+/ | |module mir.string_map; | |import std.traits; | |/++ |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); |} | |/++ |Ordered string-value associtaive 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, U = uint) | if (isMutable!T && !__traits(hasMember, T, "opPostMove") && __traits(isUnsigned, U)) |{ | import mir.utility: _expect; | import core.lifetime: move; | import mir.conv: emplaceRef; | | private alias Impl = StructImpl!(T, U); | private Impl* implementation; | | /// | // current implementation is workaround for linking bugs when used in self referencing algebraic types | bool opEquals(const StringMap rhs) const | { 1| if (keys != rhs.keys) 0000000| return false; 1| if (implementation) 9| foreach (const i; 0 .. implementation._length) 2| if (implementation._values[i] != rhs.implementation._values[i]) 0000000| return false; 1| 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 associtave 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 associtave 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))` | +/ 9| this()(T[string] aa) @trusted pure nothrow | { 9| 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 | { | import mir.format: stringBuf; | stringBuf buffer; | toString(buffer); | return buffer.data.idup; | } | | ///ditto | void toString(W)(scope ref W w) const | { | 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))` | +/ 10| this()(string[] keys, T[] values) @trusted pure nothrow | { 10| assert(keys.length == values.length); 10| 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 | { 121| 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. | | Complexity: `O(1)` | +/ | const(string)[] keys()() @safe pure nothrow @nogc const @property | { 16| return implementation ? implementation.keys : null; | } | | 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. | | Complexity: `O(1)` | +/ | inout(T)[] values()() @safe pure nothrow @nogc inout @property | { 16| return implementation ? implementation.values : null; | } | | 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()() @system 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 | { 16| size_t index; 32| if (implementation && implementation.findIndex(key, index)) | { 14| implementation.removeAt(index); 14| 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) @system 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)) | /// | @system 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 | { 14| size_t index; 28| if (implementation && implementation.findIndex(key, index)) | { 14| assert (index < length); 14| index = implementation._indices[index]; 14| assert (index < length); 14| 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; 10| T v; 10| v = forward!value; 10| return opIndexAssign(move(v), key); | } | | /// ditto | ref T opIndexAssign()(T value, string key) @trusted pure nothrow | { 39| size_t index; 39| if (_expect(!implementation, false)) | { 14| implementation = new Impl; | } | else | { 25| if (key.length + 1 < implementation.lengthTable.length) | { 17| if (implementation.findIndex(key, index)) | { 1| assert (index < length); 1| index = implementation._indices[index]; 1| assert (index < length); 1| implementation._values[index] = move(value); 1| return implementation._values[index]; | } 16| assert (index <= length); | } | else | { 8| index = length; | } | } 38| assert (index <= length); 38| implementation.insertAt(key, move(value), index); 38| index = implementation._indices[index]; 38| 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) | { | import std.stdio; 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 the associtave array to a common Dlang associative array. | | Complexity: `O(n)`. | +/ | template toAA() | { | static if (__traits(compiles, (ref const T a) { T b; b = a;})) | { | /// | T[string] toAA()() const | { 5| T[string] ret; 42| foreach (i; 0 .. length) | { 9| ret[implementation.keys[i]] = implementation.values[i]; | } 5| 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 | { 4| StringMap!int map = ["k": 1]; 4| int[string] aa = map.toAA; 4| assert(aa["k"] == 1); | } |} | |version(mir_test) |/// |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); |} | |private struct StructImpl(T, U = uint) | if (!__traits(hasMember, T, "opPostMove") && __traits(isUnsigned, U)) |{ | import core.lifetime: move; | import mir.conv: emplaceRef; | | size_t _length; | string* _keys; | T* _values; | U* _indices; | U[] _lengthTable; | | /++ | +/ 10| this()(string[] keys, T[] values) @trusted pure nothrow | { | import mir.array.allocation: array; | import mir.ndslice.sorting: makeIndex; | import mir.ndslice.topology: iota, indexed; | import mir.string_table: smallerStringFirst; | 10| assert(keys.length == values.length); 10| if (keys.length == 0) 0000000| return; 10| _length = keys.length; 10| _keys = keys.ptr; 10| _values = values.ptr; 10| _indices = keys.makeIndex!(U, smallerStringFirst).ptr; 10| auto sortedKeys = _keys.indexed(indices); 10| size_t maxKeyLength = sortedKeys[$ - 1].length; 10| _lengthTable = new U[maxKeyLength + 2]; | 10| size_t ski; 126| foreach (length; 0 .. maxKeyLength + 1) | { 86| while(ski < sortedKeys.length && sortedKeys[ski].length == length) 16| ski++; 32| _lengthTable[length + 1] = cast(U)ski; | } | } | | void insertAt()(string key, T value, size_t i) @trusted | { | pragma(inline, false); | 40| assert(i <= length); | { 40| auto a = keys; 40| a ~= key; 40| _keys = a.ptr; | } | { 40| auto a = values; 40| a ~= move(value); 40| _values = a.ptr; | } | { 40| auto a = indices; 40| a ~= 0; 40| _indices = a.ptr; | 40| if (__ctfe) | { | foreach_reverse (idx; i .. length) | { | _indices[idx + 1] = _indices[idx]; | } | } | else | { | import core.stdc.string: memmove; 40| memmove(_indices + i + 1, _indices + i, (length - i) * U.sizeof); | } 40| assert(length <= U.max); 40| _indices[i] = cast(U)length; 40| _length++; | } | { 40| if (key.length + 2 <= lengthTable.length) | { 18| ++lengthTable[key.length + 1 .. $]; | } | else | { 22| auto oldLen = _lengthTable.length; 22| _lengthTable.length = key.length + 2; 22| auto oldVal = oldLen ? _lengthTable[oldLen - 1] : 0; 22| _lengthTable[oldLen .. key.length + 1] = oldVal; 22| _lengthTable[key.length + 1] = oldVal + 1; | } | } | } | | void removeAt()(size_t i) | { 14| assert(i < length); 14| auto j = _indices[i]; 14| assert(j < length); | { 14| --_lengthTable[_keys[j].length + 1 .. $]; | } | { 14| if (__ctfe) | { | foreach (idx; i .. length) | { | _indices[idx] = _indices[idx + 1]; | _indices[idx] = _indices[idx + 1]; | } | } | else | { | import core.stdc.string: memmove; 14| memmove(_indices + i, _indices + i + 1, (length - 1 - i) * U.sizeof); | } 90| foreach (ref elem; indices[0 .. $ - 1]) 16| if (elem > j) 13| elem--; | } | { 14| 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; 14| destroy!false(_values[j]); 14| memmove(_keys + j, _keys + j + 1, (length - 1 - j) * string.sizeof); 14| memmove(_values + j, _values + j + 1, (length - 1 - j) * T.sizeof); 14| emplaceRef(_values[length - 1]); | } | } 14| _length--; 14| _lengthTable = _lengthTable[0 .. length ? _keys[_indices[length - 1]].length + 2 : 0]; | } | | size_t length()() @safe pure nothrow @nogc const @property | { 388| return _length; | } | | inout(string)[] keys()() @trusted inout @property | { 84| return _keys[0 .. _length]; | } | | inout(T)[] values()() @trusted inout @property | { 90| return _values[0 .. _length]; | } | | inout(U)[] indices()() @trusted inout @property | { 92| return _indices[0 .. _length]; | } | | inout(U)[] lengthTable()() @trusted inout @property | { 88| 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; 64| 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 | 64| auto low = _lengthTable[key.length] + 0u; 64| auto high = _lengthTable[key.length + 1] + 0u; 95| while (low < high) | { 72| auto mid = (low + high) / 2; | | import core.stdc.string: memcmp; 72| int r = void; | 72| if (__ctfe) | r = __cmp(key, _keys[_indices[mid]]); | else 72| r = memcmp(key.ptr, _keys[_indices[mid]].ptr, key.length); | 72| if (r == 0) | { 41| index = mid; 41| return true; | } 31| if (r > 0) 8| low = mid + 1; | else 23| high = mid; | } 23| index = low; | } 23| return false; | } |} | |version(mir_test) |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 97% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko, John Michael Hall, Andrei Alexandrescu (original Phobos code) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments | |Authors: , Ilya Yaroshenko (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); |} | |/// |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...) | (scope ref const 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) 1452| continue; | else | static if (slices[i].shape.length == N) 1400| assert(slices[i].shape == slices[0].shape, msgShape); | else | { | import mir.ndslice.fuse: fuseShape; | static assert(slices[i].fuseShape.length >= N); 54| 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 ls()() | { | import mir.ndslice.topology: flattened; 292| return flattened(arg); | } | alias allFlattened = AliasSeq!(ls, 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, scope Slices slices) |{ | do | { | static if (DimensionCount!(Slices[0]) == 1) 10701| seed = fun(seed, frontOf!slices); | else | seed = .reduceImpl!fun(seed, frontOf!slices); 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, scope 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, scope 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...)(scope Slices slices) |{ 2327| foreach(ref slice; slices) 2327| assert(!slice.empty); | do | { | static if (DimensionCount!(Slices[0]) == 1) 7705| fun(frontOf!slices); | else 589| .eachImpl!fun(frontOf!slices); | foreach_reverse(i; Iota!(Slices.length)) 16294| slices[i].popFront; | } 8294| while(!slices[0].empty); |} | |void chequerEachImpl(alias fun, Slices...)(Chequer color, scope 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 | { 10| .chequerEachImpl!fun(color, frontOf!slices); 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) | { 1| fun(frontOf!slices); 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...)(scope Slices slices) | if (Slices.length && !is(Slices[0] : Chequer)) | { | static if (Slices.length > 1) 599| slices.checkShapesMatch; | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; 91| .each!fun(allFlattened!(allLightScope!slices)); | } | else | { 516| if (slices[0].anyEmpty) 3| return; 513| 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, scope 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 (fun(frontOf!slices)) | { 7| backwardIndex[0] = slices[0].length; 7| return true; | } | } | else | { 14| if (findImpl!fun(backwardIndex[1 .. $], frontOf!slices)) | { 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...)(auto ref 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); | | // sl was changed 1| assert(sl == [[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...)(scope 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 (fun(frontOf!slices)) 5| return true; | } | else | { | if (anyImpl!fun(frontOf!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...)(scope 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...)(scope 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) | { 5436| if (!fun(frontOf!slices)) 13| return false; | } | else | { 132| if (!allImpl!fun(frontOf!slices)) 0000000| return false; | } 10790| foreach_reverse(ref slice; slices) 10790| slice.popFront; | } 5555| while(!slices[0].empty); 943| 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...)(scope Slices slices) | if ((Slices.length == 1 || !__traits(isSame, pred, "a")) && Slices.length) | { | static if (Slices.length > 1) 834| slices.checkShapesMatch; | static if (areAllContiguousSlices!Slices) | { | import mir.ndslice.topology: flattened; 46| return .all!pred(allFlattened!(allLightScope!slices)); | } | else | { 1649| 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...)(scope 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); |} | |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...)(scope Slices slices) | if (Slices.length >= 2) | { | import mir.internal.utility; | static if (allSatisfy!(hasShape, Slices)) | { 250| auto shape0 = slices[0].shape; | enum N = DimensionCount!(Slices[0]); 251| foreach (ref slice; slices[1 .. $]) | { 251| if (slice.shape != shape0) 2| goto False; | } 248| return all!pred(allLightScope!slices); | } | else | { | for(;;) | { 109| auto empty = slices[0].empty; 109| foreach (ref slice; slices[1 .. $]) | { 109| if (slice.empty != empty) 0000000| goto False; | } 109| if (empty) 24| return true; 85| if (!pred(frontOf!slices)) 0000000| goto False; 170| foreach (ref slice; slices) 170| 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; 57| if (naryFun!pred(sl1.front, sl2.front)) 38| return -1; 19| if (naryFun!pred(sl2.front, sl1.front)) 2| return 1; | } | else | { 6| if (auto res = .cmpImpl!pred(sl1.front, sl2.front)) 3| return res; | } 20| sl1.popFront; 20| if (sl1.empty) 6| 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) | { 49| auto b = sl2.anyEmpty; 49| 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; | } 45| if (b) 1| return 1; 44| 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...)(scope 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(fun(frontOf!slices)) 23| ret++; | } | else | ret += .countImpl!fun(frontOf!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) |{ 34| auto shape = s.shape; 34| size_t length = 0; | foreach(i; Iota!(shape.length)) 40| if (shape[i] > length) 38| length = shape[i]; 34| 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), Ilya Yaroshenko (betterC rework) |+/ |struct Uniq(alias pred, Range) |{ | Range _input; | | ref opSlice() inout | { 1| return this; | } | | void popFront() scope | { 136| assert(!empty, "Attempting to popFront an empty uniq."); 136| auto last = _input.front; | do | { 200| _input.popFront(); | } 371| while (!_input.empty && pred(last, _input.front)); | } | | auto ref front() @property | { 58| assert(!empty, "Attempting to fetch the front of an empty uniq."); 58| 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() scope return @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 | { 338| @property bool empty() const { return _input.empty; } | } | | import std.range.primitives: isForwardRange; | | static if (isForwardRange!Range) | { | @property typeof(this) save() scope return | { 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; 10| auto r = slice.move.flattened; 10| 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; | | ref opSlice() inout | { 0000000| return this; | } | | void popFront() scope | { 43| assert(!_input.empty, "Attempting to popFront an empty Filter."); 43| version(assert) assert(_freshEmpty, "Attempting to pop the front of a Filter without calling '.empty' method ahead."); 43| version(assert) _freshEmpty = false; 43| _input.popFront; | } | | auto ref front() @property | { 43| assert(!_input.empty, "Attempting to fetch the front of an empty Filter."); 43| version(assert) assert(_freshEmpty, "Attempting to fetch the front of a Filter without calling '.empty' method ahead."); 43| return _input.front; | } | | bool empty() @property | { 84| version(assert) _freshEmpty = true; | for (;;) | { 124| if (auto r = _input.empty) 17| return true; 107| if (pred(_input.front)) 67| return false; 40| _input.popFront; | } | } | | import std.range.primitives: isForwardRange; | static if (isForwardRange!Range) | { | @property typeof(this) save() scope return | { 0000000| return typeof(this)(_input.save); | } | } |} | |/// |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] ])); |} | |/++ |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); |} | |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-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), Ilya Yaroshenko, John Michael Hall | |Copyright: 2020 Ilya Yaroshenko, 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) { | import std.traits: Unqual; | static if (is(T : cdouble)) { | deprecated("Built-in complex types deprecated in D language version 2.097") alias statType = Unqual!T; | } else { | alias statType = Unqual!T; | } | } else static if (is(T : cdouble)) { | deprecated("Built-in complex types deprecated in D language version 2.097") alias statType = cdouble; | } 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_builtincomplex_test) |@safe pure nothrow @nogc |unittest |{ | static assert(is(statType!cfloat == cfloat)); | static assert(is(statType!cdouble == cdouble)); | static assert(is(statType!creal == creal)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | import std.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_builtincomplex_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | cfloat x; | alias x this; | } | | static assert(is(statType!Foo == cdouble)); // note: this is not 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_builtincomplex_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | cdouble x; | alias x this; | } | | static assert(is(statType!Foo == cdouble)); |} | |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_builtincomplex_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | creal x; | alias x this; | } | | static assert(is(statType!Foo == cdouble)); // note: this is not 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_builtincomplex_test) |@safe pure nothrow @nogc |unittest |{ | static assert(is(meanType!(cfloat[]) == cfloat)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | float x; | alias x this; | } | | static assert(is(meanType!(Foo[]) == float)); |} | |version(mir_builtincomplex_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | cfloat x; | alias x this; | } | | static assert(is(meanType!(Foo[]) == cfloat)); |} | |/++ |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; | 1| assert(mean([1.0, 2, 3]) == 2); 1| assert(mean([1.0 + 3i, 2, 3]) == 2 + 1i); | 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.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [1.0 + 2i, 2 + 3i, 3 + 4i, 4 + 5i].sliced; 1| assert(x.mean.approxEqual(2.5 + 3.5i)); |} | |/// 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 |{ | static assert(is(hmeanType!(int[]) == double)); | static assert(is(hmeanType!(double[]) == double)); | static assert(is(hmeanType!(float[]) == float)); | static assert(is(hmeanType!(cfloat[]) == cfloat)); |} | |version(mir_test) |@safe pure nothrow @nogc |unittest |{ | static struct Foo { | float x; | alias x this; | } | | static struct Bar { | cfloat x; | alias x this; | } | | static assert(is(hmeanType!(Foo[]) == float)); | static assert(is(hmeanType!(Bar[]) == cfloat)); |} | |/++ |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.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [1.0 + 2i, 2 + 3i, 3 + 4i, 4 + 5i].sliced; 1| assert(x.hmean.approxEqual(1.97110904 + 3.14849332i)); |} | |/// 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)() @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)() @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; | | /// | size_t count() @property | { 22| return meanAccumulator.count; | } | | /// | F mean(F = T)() @property | { | return meanAccumulator.mean; | } | | /// | 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); | } | | /// | F variance(F = T)(bool isPopulation) @property | { 10| if (isPopulation == false) 6| return cast(F) sumOfSquares.sum / cast(F) (count - 1) - | (cast(F) meanAccumulator.mean) ^^ 2 * (cast(F) count / cast(F) (count - 1)); | else 4| return cast(F) sumOfSquares.sum / cast(F) count - | (cast(F) meanAccumulator.mean) ^^ 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; | | 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; | | /// | size_t count() @property | { 982| return meanAccumulator.count; | } | | /// | F mean(F = T)() @property | { 2| return meanAccumulator.mean; | } | | /// | 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); | } | | /// | F variance(F = T)(bool isPopulation) @property | { 243| if (isPopulation == false) 222| return cast(F) centeredSumOfSquares.sum / cast(F) (count - 1); | else 21| return cast(F) centeredSumOfSquares.sum / cast(F) count; | } |} | |/// |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_builtincomplex_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | | auto x = [1.0 + 3i, 2, 3].sliced; | | VarianceAccumulator!(cdouble, VarianceAlgo.online, Summation.naive) v; | v.put(x); | assert(v.variance(true).approxEqual((-4.0 - 6i) / 3)); | assert(v.variance(false).approxEqual((-4.0 - 6i) / 2)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | import std.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; | | /// | size_t count() @property | { 6| return meanAccumulator.count; | } | | /// | F mean(F = T)() @property | { | return meanAccumulator.mean; | } | | /// | 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); | } | | /// | F variance(F = T)(bool isPopulation) @property | { 6| if (isPopulation == false) 4| return cast(F) centeredSumOfSquares.sum / cast(F) (count - 1); | else 2| return cast(F) centeredSumOfSquares.sum / cast(F) count; | } |} | |/// |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; | | /// | size_t count() @property | { 35| return _count; | } | | /// | F mean(F = T)() @property | { | return cast(F) 0; | } | | /// | 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); | } | | /// | F variance(F = T)(bool isPopulation) @property | { 33| if (isPopulation == false) 20| return cast(F) centeredSumOfSquares.sum / cast(F) (count - 1); | else 13| return cast(F) centeredSumOfSquares.sum / cast(F) count; | } |} | |/// |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_builtincomplex_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | | auto a = [1.0 + 3i, 2, 3].sliced; | auto x = a.center; | | VarianceAccumulator!(cdouble, VarianceAlgo.assumeZeroMean, Summation.naive) v; | v.put(x); | assert(v.variance(true).approxEqual((-4.0 - 6i) / 3)); | assert(v.variance(false).approxEqual((-4.0 - 6i) / 2)); |} | |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.math.common: approxEqual; | import mir.ndslice.slice: sliced; | import std.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.ndslice.slice: sliced; | 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([1.0 + 3i, 2, 3]).approxEqual((-4.0 - 6i) / 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.math.common: approxEqual; | import mir.ndslice.slice: sliced; | 1| auto x = [1.0 + 2i, 2 + 3i, 3 + 4i, 4 + 5i].sliced; 1| assert(x.variance.approxEqual((0.0+10.0i)/ 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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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_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_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-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; | | /// 1275| this(size_t N)(auto ref const size_t[N] data) | if (N <= this.data.length) | { | version(LittleEndian) 1275| this.data[0 .. N] = data; | else | this.data[$ - N .. $] = data; | } | | /// 1| this(size_t argSize)(auto ref const UInt!argSize arg) | if (argSize <= size) | { 1| 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); | } | } | } | } | | /// 1958| this(ulong data) | { 1958| auto d = view.leastSignificantFirst; | static if (size_t.sizeof == ulong.sizeof) | { 1958| d.front = data; | } | else | { | d.front = cast(uint) data; | d[1] = cast(uint) (data >> 32); | } | } | | static if (size < 64) | /// | this(uint data) | { | view.leastSignificant = 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)() 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)(scope ref W w) 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| stringBuf buffer; 1| buffer << integer; 1| assert(buffer.data == str, buffer.data); | } | | /// | 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 @safe | { 29151| return BigUIntView!size_t(data); | } | | /// | BigUIntView!(const size_t) view()() const @property pure nothrow @nogc scope @safe | { 4027| return BigUIntView!(const size_t)(data); | } | | /// | static UInt!size fromHexString(bool allowUnderscores = false)(scope const(char)[] str) | { 102| typeof(return) ret; 102| if (ret.fromHexStringImpl!(char, allowUnderscores)(str)) 102| 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) | { 102| return view.fromHexStringImpl!(C, allowUnderscores)(str); | } | | /++ | +/ | auto opEquals(size_t rhsSize)(auto ref const UInt!rhsSize rhs) const | { | static if (rhsSize == size) 2215| return this.data == rhs.data; | else | static if (rhsSize > size) | return this.toSize!rhsSize.data == rhs.data; | else | return this.data == rhs.toSize!size.data; | } | | /// ditto | auto opEquals(ulong rhs) const | { 1029| return opEquals(UInt!size(rhs)); | } | | /++ | +/ | auto opCmp(UInt!size rhs) const | { | version (LittleEndian) // workaround for CTFE bug | { 8314| foreach_reverse(i; 0 .. data.length) | { 2238| if (this.data[i] < rhs.data[i]) 1194| return -1; 1044| if (this.data[i] > rhs.data[i]) 626| return +1; | } 66| return 0; | } | else | { | import mir.algorithm.iteration: cmp; | return cmp(this.view.mostSignificantFirst, rhs.view.mostSignificantFirst); | } | } | | /// ditto | auto opCmp(ulong rhs) const scope | { 0000000| return opCmp(UInt!size(rhs)); | } | | /++ | +/ | ref UInt!size opAssign(ulong rhs) scope return | @safe pure nothrow @nogc | { 0000000| this.data = UInt!size(rhs).data; 0000000| 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 == "-") | { 2409| return view.opOpAssign!op(rhs.view, overflow); | } | | /// ditto | bool opOpAssign(string op)(size_t rhs) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { 775| 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, size_t 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 | { 1748| 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; | } | | /// | 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];`); 978| 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.leastSignificantFirst[0] ` ~ op ~ `= rhs;`); 74| 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 == ">>") | { 7261| auto d = view.leastSignificantFirst; 7261| assert(shift < size); 7261| auto index = shift / (size_t.sizeof * 8); 7261| auto bs = shift % (size_t.sizeof * 8); 7261| auto ss = size_t.sizeof * 8 - bs; | static if (op == ">>") | { 7016| if (bs) | { 14595| foreach (j; 0 .. data.length - (index + 1)) | { 1171| d[j] = (d[j + index] >>> bs) | (d[j + (index + 1)] << ss); | } | } | else | { 11487| foreach (j; 0 .. data.length - (index + 1)) | { 507| d[j] = d[j + index]; | } | } 7016| d[$ - (index + 1)] = d.back >>> bs; 36060| foreach (j; data.length - index .. data.length) | { 5004| d[j] = 0; | } | } | else | { 245| if (bs) | { 731| foreach_reverse (j; index + 1 .. data.length) | { 10| d[j] = (d[j - index] << bs) | (d[j - (index + 1)] >> ss); | } | } | else | { 46| foreach_reverse (j; index + 1 .. data.length) | { 11| d[j] = d[j - index]; | } | } 245| d[index] = d.front << bs; 747| foreach_reverse (j; 0 .. index) | { 6| d[j] = 0; | } | } 7261| return this; | } | | /++ | `auto c = a << b` operation. | +/ | UInt!size opBinary(string op)(size_t rhs) | const @safe pure nothrow @nogc | if (op == "<<" || op == ">>>" || op == ">>") | { 6302| UInt!size ret = this; 6302| ret.opOpAssign!op(rhs); 6302| 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) | { 2732| UInt!size ret = this; 2732| ret.opOpAssign!op(rhs); 2732| return ret; | } | | /// ditto | UInt!size opBinary(ulong rhs) | const @safe pure nothrow @nogc | { 2282| UInt!size ret = this; 2282| ret.opOpAssign!op(rhs); 2282| return ret; | } | } | | /// 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 | { 1622| assert(shift < size_t.sizeof * 8); 1622| UInt!size ret = this; 1622| if (shift) | { 1578| auto csh = size_t.sizeof * 8 - shift; | version (LittleEndian) | { | static foreach_reverse (i; 1 .. data.length) | { 1575| ret.data[i] = (ret.data[i] << shift) | (ret.data[i - 1] >>> csh); | } 1578| ret.data[0] = ret.data[0] << shift; | } | else | { | static foreach (i; 0 .. data.length - 1) | { | ret.data[i] = (ret.data[i] << shift) | (ret.data[i + 1] >>> csh); | } | ret.data[$ - 1] = ret.data[$ - 1] << shift; | } | } 1622| 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; | version (LittleEndian) | { | 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; | } | else | { | static foreach_reverse (i; 1 .. data.length) | { | ret.data[i] = (ret.data[i] >>> shift) | (ret.data[i - 1] << csh); | } | ret.data[0] = ret.data[0] >>> 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) | { 46| if (data[i]) 46| return true; | } 0000000| return false; | } | | /++ | +/ | T opCast(T)() const | if (is(Unqual!T == ulong)) | { 2135| auto d = view.leastSignificantFirst; | static if (size_t.sizeof == ulong.sizeof) | { 2135| return d.front; | } | else | { | return d.front | (ulong(d[1]) << 32); | } | } | | /++ | +/ | T opCast(T)() const | if (is(Unqual!T == uint)) | { 874| auto d = view.leastSignificantFirst; 874| return cast(uint) d.front; | } | | /++ | Returns: | the number with shrinked or extended size. | +/ | UInt!newSize toSize(size_t newSize, bool lowerBits = true)() | const @safe pure @nogc nothrow | { 11765| typeof(return) ret; | import mir.utility: min; | enum N = min(ret.data.length, data.length); | static if (lowerBits) | { | version (LittleEndian) 11681| ret.data[0 .. N] = data[0 .. N]; | else | ret.data[$ - N .. $] = data[$ - N .. $]; | } | else | { | version (LittleEndian) 84| ret.data[0 .. N] = data[$ - N .. $]; | else | ret.data[$ - N .. $] = data[0 .. N]; | } 11765| return ret; | } | | /// | UInt!(size + additionalRightBits) rightExtend(size_t additionalRightBits)() | const @safe pure @nogc nothrow | if (additionalRightBits) | { 1| typeof(return) ret; | version (BigEndian) | ret.data[0 .. data.length] = data; | else 1| ret.data[$ - data.length .. $] = data; 1| return ret; | } | | /++ | +/ | bool bt()(size_t position) const | @safe pure nothrow @nogc | { 953| assert(position < data.sizeof * 8); 953| 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 @property | @safe pure nothrow @nogc | { 20| 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 | { 45| 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 | { | version (LittleEndian) 988| return data[$ - 1] >> (size_t.sizeof * 8 - 1); | else | return data[0] >> (size_t.sizeof * 8 - 1); | } | | /// ditto | void signBit()(bool value) @property | { | enum signMask = ptrdiff_t.max; | version (LittleEndian) 12| data[$ - 1] = (data[$ - 1] & ptrdiff_t.max) | (size_t(value) << (size_t.sizeof * 8 - 1)); | else | data[ 0] = (data[ 0] & ptrdiff_t.max) | (size_t(value) << (size_t.sizeof * 8 - 1)); | } | | /// | version(mir_bignum_test) | unittest | { 6| auto a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); 6| assert(a.signBit); 6| a.signBit = false; 6| assert(a == UInt!128.fromHexString("5fbbfae3cd0aff2714a1de7022b0029d")); 6| assert(!a.signBit); 6| a.signBit = true; 6| assert(a == UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d")); | } |} | |/++ |+/ |UInt!sizeB extendedMulHigh(size_t sizeA, size_t sizeB)(UInt!sizeA a, UInt!sizeB b) |{ 2768| return (extendedMul(a, b) >> sizeA).toSize!sizeB; |} | |/++ |+/ |UInt!(sizeA + sizeB) extendedMul(size_t sizeA, size_t sizeB)(UInt!sizeA a, UInt!sizeB b) @safe |{ 4082| UInt!(sizeA + sizeB) ret; | enum al = a.data.length; | enum alp1 = a.data.length + 1; | version (LittleEndian) | { 4082| ret.data[0 .. alp1] = extendedMul(a, b.data[0]).data; | static foreach ( i; 1 .. b.data.length) 744| ret.data[i .. i + alp1] = extendedMulAdd(a, b.data[i], UInt!sizeA(ret.data[i .. i + al])).data; | } | else | { | ret.data[$ - alp1 .. $] = extendedMul(a, b.data[$ - 1]).data; | static foreach_reverse ( i; 0 .. b.data.length - 1) | ret.data[i .. i + alp1] = extendedMulAdd(a, b.data[i], UInt!sizeA(ret.data[i .. i + al])).data; | } 4082| return ret; |} | |/// ditto |UInt!(size + size_t.sizeof * 8) | extendedMul(size_t size)(UInt!size a, size_t b) @safe |{ 4826| size_t overflow = a.view *= b; 4826| auto ret = a.toSize!(size + size_t.sizeof * 8); 4826| ret.view.mostSignificant = overflow; 4826| return ret; |} | |/// ditto |UInt!128 extendedMul()(ulong a, ulong b) |{ | static if (size_t.sizeof == ulong.sizeof) | { | import mir.utility: extMul; 2| auto e = extMul(a, b); | version(LittleEndian) 2| return typeof(return)([e.low, e.high]); | else | return typeof(return)([e.high, e.low]); | } | else | { | return extendedMul(UInt!64(a), UInt!64(b)); | } |} | |/// ditto |UInt!64 extendedMul()(uint a, uint b) |{ | static if (size_t.sizeof == uint.sizeof) | { | import mir.utility: extMul; | auto e = extMul(a, b); | version(LittleEndian) | return typeof(return)([e.low, e.high]); | else | return typeof(return)([e.high, e.low]); | } | else | { 2| return typeof(return)([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) |{ 744| auto ret = extendedMul(a, b); 744| auto view = ret.view; 744| view.mostSignificant += view.topLeastSignificantPart(a.data.length) += c.view; 744| return ret; |} source/mir/bignum/fixed.d is 95% covered <<<<<< EOF # path=./source-mir-format.lst |/++ |$(H1 @nogc Formatting Utilities) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko |+/ |module mir.format; | |import std.traits; | |/// `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 ref Args args) | if (Args.length > 0) |{ | static if (Args.length == 1) | { | import mir.functional: forward; | import mir.conv: to; | return to!string(forward!args); | } | else | { | import mir.appender: scopedBuffer; 4| auto buffer = scopedBuffer!char; 10| foreach (i, ref arg; args) | { 10| buffer.print(arg); | static if (separator.length && i + 1 < args.length) | { 3| buffer.printStaticString!char(separator); | } | } 2| return buffer.data.idup; | } |} | |/// |@safe pure nothrow unittest |{ 1| assert(text("str ", true, " ", 100, " ", 124.1) == "str true 100 124.1", text("str ", true, " ", 100, " ", 124.1)); 1| assert(text!" "("str", true, 100, 124.1) == "str true 100 124.1"); |} | |import mir.format_impl; | |/// |struct GetData {} | |/// |enum getData = GetData(); | |/++ |+/ |struct _stringBuf(C) |{ | import mir.appender: ScopedBuffer; | | /// | ScopedBuffer!(C, 256) buffer; | | /// | alias buffer this; | | /// | mixin StreamFormatOp!C; |} | |///ditto |alias stringBuf = _stringBuf!char; |///ditto |alias wstringBuf = _stringBuf!wchar; |///ditto |alias dstringBuf = _stringBuf!dchar; | |/++ |+/ |mixin template StreamFormatOp(C) |{ | /// | ref typeof(this) opBinary(string op : "<<", T)(ref const T c) scope | { 13| return print!C(this, c); | } | | /// | ref typeof(this) opBinary(string op : "<<", T)(const T c) scope | { 19| return print!C(this, c); | } | | /// ditto | const(C)[] opBinary(string op : "<<", T : GetData)(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(wstringBuf() << "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(dstringBuf() << "Hi "d << name << ver << "!\n"d << getData == "Hi D2!\n"); |} | |@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 | { | 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 | { | enum N = T.sizeof * 2; | static if(isFastBuffer!W) | { | w.advance(printHexAddress(value, w.getStaticBuf!N, cast(bool) switchLU)); | } | else | { 1| C[N] buf = void; 1| printHexAddress(value, buf, cast(bool) switchLU); 1| w.put(buf[]); | } | } |} | |/++ |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 ? '\'' : '\"'; | |/++ |+/ |ref W printEscaped(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope return ref W w, scope const(C)[] str) |{ | 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 w; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | | import mir.format: stringBuf; 2| stringBuf w; 1| assert(w.printEscaped("Hi \a\v\0\f\t\b \\\r\n" ~ `"@nogc"`).data == `Hi \a\v\0\f\t\b \\\r\n\"@nogc\"`); 1| w.reset; 1| assert(w.printEscaped("\x03").data == `\x03`, w.data); |} | |/++ |Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters. |+/ |ref W put_xXX(C = char, W)(scope return ref W w, char 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'); 1| return w.printStaticString(buffer); |} | |/++ |Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters. |+/ |ref W put_uXXXX(C = char, W)(scope return ref W w, char 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. |+/ |ref W put_uXXXX(C = char, W)(scope return ref W w, ushort c) |{ | ubyte[4] spl; | spl[0] = (c >> 12) & 0xF; | spl[1] = (c >> 8) & 0xF; | spl[2] = (c >> 4) & 0xF; | spl[3] = c & 0xF; | C[6] buffer; | buffer[0] = '\\'; | buffer[1] = 'u'; | buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); | buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); | buffer[4] = cast(ubyte)(spl[2] < 10 ? spl[2] + '0' : spl[2] - 10 + 'A'); | buffer[5] = cast(ubyte)(spl[3] < 10 ? spl[3] + '0' : spl[3] - 10 + 'A'); | return w.printStaticString(buffer); |} | |/// |ref W printElement(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope return ref W w, scope const(C)[] c) | if (isSomeChar!C) |{ | static immutable C[1] quote = '\"'; 4| return w | .printStaticString!C(quote) | .printEscaped!(C, escapeFormat)(c) | .printStaticString!C(quote); |} | |/// |ref W printElement(C = char, EscapeFormat escapeFormat = EscapeFormat.ion, W, T)(scope return ref W w, scope auto ref const T c) | if (!isSomeString!T) |{ 2| return w.print!C(c); |} | |/++ |Multiargument overload. |+/ |ref W print(C = char, W, Args...)(scope return ref W w, scope auto ref const Args args) | if (Args.length > 1) |{ 0000000| foreach(i, ref c; args) | static if (i < Args.length - 1) 0000000| w.print!C(c); | else 0000000| return w.print!C(c); |} | |/// Prints enums |ref W print(C = char, W, T)(scope return ref W w, const T c) | if (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 w; | } | static immutable C[] str = T.stringof ~ "("; 0000000| w.put(str[]); 0000000| print!C(w, cast(OriginalType!T) c); 0000000| w.put(')'); 0000000| return w; |} | |/// |@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", w.data); |} | |/// Prints boolean |ref W print(C = char, W)(scope return ref W w, bool 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 w; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| assert(w.print(true).data == `true`, w.data); 1| w.reset; 1| assert(w.print(false).data == `false`, w.data); |} | |/// Prints associative array |pragma(inline, false) |ref W print(C = char, W, V, K)(scope return ref W w, scope const V[K] 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) | .printStaticString!C(mid) | .printElement!C(value); | } 1| w.put(right); 1| return w; |} | |/// |@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]`, w.data); |} | |/// Prints array |pragma(inline, false) |ref W print(C = char, W, T)(scope return ref W w, scope const(T)[] c) | if (!isSomeChar!T) |{ | enum C left = '['; | enum C right = ']'; | enum C[2] sep = ", "; 1| w.put(left); 1| bool first = true; 9| foreach (ref e; c) | { 2| if (!first) 1| w.printStaticString!C(sep); 2| first = false; 2| printElement!C(w, e); | } 1| w.put(right); 1| return w; |} | |/// |@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| assert(w.print(array[]).data == `["a\na", "b"]`, w.data); |} | |/// Prints escaped character in the form `'c'`. |pragma(inline, false) |ref W print(C = char, W)(scope return ref W w, char c) |{ 4| w.put('\''); 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 w; |} | |/// |@safe pure nothrow @nogc |version (mir_test) unittest |{ | import mir.appender: scopedBuffer; 2| auto w = scopedBuffer!char; 1| assert(w | .print('\n') | .print('\'') | .print('a') | .print('\xF4') | .data == `'\n''\'''a''\xF4'`); |} | |/// Prints some string |ref W print(C = char, W)(scope return ref W w, scope const(C)[] c) | if (isSomeChar!C) |{ 15| w.put(c); 15| return w; |} | |/// Prints integers |ref W print(C = char, W, I)(scope return ref W w, const I c) | if (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; 11| C[N + !__traits(isUnsigned, I)] buf = void; | static if (__traits(isUnsigned, I)) 1| auto n = printUnsignedToTail(c, buf); | else 10| auto n = printSignedToTail(c, buf); 11| w.put(buf[$ - n .. $]); 11| return w; |} | |/// Prints floating point numbers |ref W print(C = char, W, T)(scope return ref W w, const T c, NumericSpec spec = NumericSpec.init) | if(is(T == float) || is(T == double) || is(T == real)) |{ | import mir.bignum.decimal; 66| auto decimal = Decimal!(T.mant_dig < 64 ? 1 : 2)(c); 66| decimal.toString(w, spec); 66| return w; |} | |/// Human friendly precise output (default) |version(mir_bignum_test) |@safe pure nothrow @nogc |unittest |{ 1| auto spec = NumericSpec.human; 2| stringBuf buffer; | | void check(double num, string value) | { 118| assert(buffer.print(num, spec).data == value, buffer.data); 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) |ref W print(C = char, W, T)(scope return ref W w, ref const T c) | if (is(T == struct) || is(T == union) && !is(T : NumericSpec)) |{ | static if (__traits(hasMember, T, "toString")) | { | static if (is(typeof(c.toString!C(w)))) 9| c.toString!C(w); | else | static if (is(typeof(c.toString(w)))) 1| 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 static assert(0, T.stringof ~ ".toString definition is wrong: 'const' qualifier may be missing."); 14| return w; | } | else | static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) | { 1| scope const(C)[] string_of_c = c; 1| return w.print!C(string_of_c); | } | 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) | printStaticString!C(w, sep); | first = false; | print!C(w, e); | } | w.put(right); | return w; | } | else | { | enum C left = '('; | enum C right = ')'; | enum C[2] sep = ", "; | w.put(left); | foreach (i, ref e; c.tupleof) | { | static if (i) | w.printStaticString!C(sep); | print!C(w, e); | } | w.put(right); | return w; | } |} | |/// ditto |// FUTURE: remove it |pragma(inline, false) |ref W print(C = char, W, T)(scope return ref W w, scope const T c) | if (is(T == struct) || is(T == union)) |{ 1| 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) |ref W print(C = char, W, T)(scope return ref W w, scope const T c) | if (is(T == class) || is(T == interface)) |{ | enum C[4] Null = "null"; | static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; })) | { 5| if (c is null) 0000000| w.printStaticString!C(Null); | else | static if (is(typeof(c.toString!C(w)))) 1| c.toString!C(w); | else | static if (is(typeof(c.toString(w)))) 1| 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)))) 1| w.put(c.toString); | else | static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) | { 1| scope const(C)[] string_of_c = c; 1| 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); | } | else | { | w.put(T.stringof); | } 4| return w; |} | |/// |@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"); |} | |/// |ref W printStaticString(C, size_t N, W)(scope return ref W w, ref scope const C[N] c) | if (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 | { 29| w.put(c[]); | } 29| return w; |} | |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 return 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)(scope return 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"); |} | |/// |ref W printZeroPad(C = char, W, I)(scope return ref W w, const I c, size_t minimalLength) | if (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; 340| C[N + !__traits(isUnsigned, I)] buf = void; | static if (__traits(isUnsigned, I)) 232| auto n = printUnsignedToTail(c, buf); | else 108| auto n = printSignedToTail(c, buf); 340| sizediff_t zeros = minimalLength - n; | 340| if (zeros > 0) | { | static if (!__traits(isUnsigned, I)) | { 47| if (c < 0) | { 19| n--; 19| w.put(C('-')); | } | } 243| do w.put(C('0')); 243| while(--zeros); | } 340| w.put(buf[$ - n .. $]); 340| return w; |} | |/// |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", w.data); |} | |/// |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; | } |} source/mir/format.d is 94% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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): | | /++ | +/ 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)() scope return const @property | if (dimension < N) | { | return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() scope return 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; | | /// | 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; | } 6| return _data[indices]; | } | } |} source/mir/interpolate/constant.d is 86% covered <<<<<< EOF # path=./source-mir-ndslice-topology.lst |/++ |This is a submodule of $(MREF mir,ndslice). | |Selectors create new views and iteration patterns over the same data, without copying. | |$(BOOKTABLE $(H2 Sequence Selectors), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 cycle, Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice) |$(T2 iota, Contiguous Slice with initial flattened (contiguous) index.) |$(T2 linspace, Evenly spaced numbers over a specified interval.) |$(T2 magic, Magic square.) |$(T2 ndiota, Contiguous Slice with initial multidimensional index.) |$(T2 repeat, Slice with identical values) |) | |$(BOOKTABLE $(H2 Shape Selectors), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 blocks, n-dimensional slice composed of n-dimensional non-overlapping blocks. If the slice has two dimensions, it is a block matrix.) |$(T2 diagonal, 1-dimensional slice composed of diagonal elements) |$(T2 dropBorders, Drops borders for all dimensions.) |$(T2 reshape, New slice view with changed dimensions) |$(T2 squeeze, New slice view of an n-dimensional slice with dimension removed) |$(T2 unsqueeze, New slice view of an n-dimensional slice with a dimension added) |$(T2 windows, n-dimensional slice of n-dimensional overlapping windows. If the slice has two dimensions, it is a sliding window.) | |) | | |$(BOOKTABLE $(H2 Subspace Selectors), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 alongDim , Returns a slice that can be iterated along dimension.) |$(T2 byDim , Returns a slice that can be iterated by dimension.) |$(T2 pack , Returns slice of slices.) |$(T2 ipack , Returns slice of slices.) |$(T2 unpack , Merges two hight dimension packs. See also $(SUBREF fuse, fuse).) |$(T2 evertPack, Reverses dimension packs.) | |) | |$(BOOKTABLE $(H2 SliceKind Selectors), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 asKindOf, Converts a slice to a user provied kind $(SUBREF slice, SliceKind).) |$(T2 universal, Converts a slice to universal $(SUBREF slice, SliceKind).) |$(T2 canonical, Converts a slice to canonical $(SUBREF slice, SliceKind).) |$(T2 assumeCanonical, Converts a slice to canonical $(SUBREF slice, SliceKind). Does only `assert` checks.) |$(T2 assumeContiguous, Converts a slice to contiguous $(SUBREF slice, SliceKind). Does only `assert` checks.) |$(T2 assumeHypercube, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.) |$(T2 assumeSameShape, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.) | |) | |$(BOOKTABLE $(H2 Products), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 cartesian, Cartesian product.) |$(T2 kronecker, Kronecker product.) | |) | |$(BOOKTABLE $(H2 Representation Selectors), |$(TR $(TH Function Name) $(TH Description)) | |$(T2 as, Convenience function that creates a lazy view, |where each element of the original slice is converted to a type `T`.) |$(T2 bitpack, Bitpack slice over an unsigned integral slice.) |$(T2 bitwise, Bitwise slice over an unsigned integral slice.) |$(T2 bytegroup, Groups existing slice into fixed length chunks and uses them as data store for destination type.) |$(T2 cached, Random access cache. It is usefull in combiation with $(LREF map) and $(LREF vmap).) |$(T2 cachedGC, Random access cache auto-allocated in GC heap. It is usefull in combiation with $(LREF map) and $(LREF vmap).) |$(T2 diff, Differences between vector elements.) |$(T2 flattened, Contiguous 1-dimensional slice of all elements of a slice.) |$(T2 map, Multidimensional functional map.) |$(T2 member, Field (element's member) projection.) |$(T2 orthogonalReduceField, Functional deep-element wise reduce of a slice composed of fields or iterators.) |$(T2 pairwise, Pairwise map for vectors.) |$(T2 pairwiseMapSubSlices, Maps pairwise index pairs to subslices.) |$(T2 retro, Reverses order of iteration for all dimensions.) |$(T2 slide, Lazy convolution for tensors.) |$(T2 slideAlong, Lazy convolution for tensors.) |$(T2 stairs, Two functions to pack, unpack, and iterate triangular and symmetric matrix storage.) |$(T2 stride, Strides 1-dimensional slice.) |$(T2 subSlices, Maps index pairs to subslices.) |$(T2 triplets, Constructs a lazy view of triplets with `left`, `center`, and `right` members. The topology is usefull for Math and Physics.) |$(T2 unzip, Selects a slice from a zipped slice.) |$(T2 withNeighboursSum, Zip view of elements packed with sum of their neighbours.) |$(T2 zip, Zips slices into a slice of refTuples.) |) | |Subspace selectors serve to generalize and combine other selectors easily. |For a slice of `Slice!(Iterator, N, kind)` type `slice.pack!K` creates a slice of |slices of `Slice!(kind, [N - K, K], Iterator)` type by packing |the last `K` dimensions of the top dimension pack, |and the type of element of $(LREF flattened) is `Slice!(Iterator, K)`. |Another way to use $(LREF pack) is transposition of dimension packs using |$(LREF evertPack). |Examples of use of subspace selectors are available for selectors, |$(SUBREF slice, Slice.shape), and $(SUBREF slice, Slice.elementCount). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko, John Michael Hall, Shigeki Karita (original numir code) | |Sponsors: Part of this work has been sponsored by $(LINK2 http://symmetryinvestments.com, Symmetry Investments) and Kaleidic Associates. | |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.topology; | |import mir.internal.utility; |import mir.math.common: optmath; |import mir.ndslice.field; |import mir.ndslice.internal; |import mir.ndslice.iterator; |import mir.ndslice.ndfield; |import mir.ndslice.slice; |import mir.primitives; |import mir.qualifier; |import mir.utility: min; |import std.meta: AliasSeq, allSatisfy, staticMap, templateOr, Repeat; | |private immutable choppedExceptionMsg = "bounds passed to chopped are out of sliceable bounds."; |version (D_Exceptions) private immutable choppedException = new Exception(choppedExceptionMsg); | |@optmath: | |/++ |Converts a slice to user provided kind. | |Contiguous slices can be converted to any kind. |Canonical slices can't be converted to contiguous slices. |Universal slices can be converted only to the same kind. | |See_also: | $(LREF canonical), | $(LREF universal), | $(LREF assumeCanonical), | $(LREF assumeContiguous). |+/ |template asKindOf(SliceKind kind) |{ | static if (kind == Contiguous) | { | auto asKindOf(Iterator, size_t N, Labels...)(Slice!(Iterator, N, Contiguous, Labels) slice) | { 1| return slice; | } | } | else | static if (kind == Canonical) | { | alias asKindOf = canonical; | } | else | { | alias asKindOf = universal; | } |} | |/// Universal |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice: Universal; 1| auto slice = iota(2, 3).asKindOf!Universal; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); 1| assert(slice._strides == [3, 1]); |} | |/// Canonical |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice: Canonical; 1| auto slice = iota(2, 3).asKindOf!Canonical; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); 1| assert(slice._strides == [3]); |} | |/// Contiguous |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice: Contiguous; 1| auto slice = iota(2, 3).asKindOf!Contiguous; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); 1| assert(slice._strides == []); |} | |/++ |Converts a slice to universal kind. | |Params: | slice = a slice |Returns: | universal slice |See_also: | $(LREF canonical), | $(LREF assumeCanonical), | $(LREF assumeContiguous). |+/ |auto universal(Iterator, size_t N, SliceKind kind, Labels...)(Slice!(Iterator, N, kind, Labels) slice) |{ | import core.lifetime: move; | | static if (kind == Universal) | { 97| return slice; | } | else | static if (is(Iterator : RetroIterator!It, It)) | { | return slice.move.retro.universal.retro; | } | else | { | alias Ret = Slice!(Iterator, N, Universal, Labels); 351| size_t[Ret.N] lengths; 351| auto strides = sizediff_t[Ret.S].init; | foreach (i; Iota!(slice.N)) 828| lengths[i] = slice._lengths[i]; | static if (kind == Canonical) | { | foreach (i; Iota!(slice.S)) 38| strides[i] = slice._strides[i]; 36| strides[$-1] = 1; | } | else | { 315| ptrdiff_t ball = 1; | foreach_reverse (i; Iota!(Ret.S)) | { 754| strides[i] = ball; | static if (i) 439| ball *= slice._lengths[i]; | } | } 351| return Ret(lengths, strides, slice._iterator.move, slice._labels); | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ 1| auto slice = iota(2, 3).universal; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); 1| assert(slice._strides == [3, 1]); |} | |@safe pure nothrow |version(mir_test) unittest |{ 1| auto slice = iota(2, 3).canonical.universal; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); 1| assert(slice._strides == [3, 1]); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | 1| auto dataframe = slice!(double, int, string)(2, 3); 1| dataframe.label[] = [1, 2]; 1| dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | 1| auto universaldf = dataframe.universal; 1| assert(universaldf._lengths == [2, 3]); 1| assert(universaldf._strides == [3, 1]); | 1| assert(is(typeof(universaldf) == | Slice!(double*, 2, Universal, int*, string*))); 1| assert(universaldf.label!0[0] == 1); 1| assert(universaldf.label!1[1] == "Label2"); |} | |/++ |Converts a slice to canonical kind. | |Params: | slice = contiguous or canonical slice |Returns: | canonical slice |See_also: | $(LREF universal), | $(LREF assumeCanonical), | $(LREF assumeContiguous). |+/ |Slice!(Iterator, N, N == 1 ? Contiguous : Canonical, Labels) | canonical | (Iterator, size_t N, SliceKind kind, Labels...) | (Slice!(Iterator, N, kind, Labels) slice) | if (kind == Contiguous || kind == Canonical) |{ | import core.lifetime: move; | | static if (kind == Canonical || N == 1) 7| return slice; | else | { | alias Ret = typeof(return); 111| size_t[Ret.N] lengths; 111| auto strides = sizediff_t[Ret.S].init; | foreach (i; Iota!(slice.N)) 245| lengths[i] = slice._lengths[i]; 111| ptrdiff_t ball = 1; | foreach_reverse (i; Iota!(Ret.S)) | { 134| ball *= slice._lengths[i + 1]; 134| strides[i] = ball; | } 111| return Ret(lengths, strides, slice._iterator.move, slice._labels); | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ 1| auto slice = iota(2, 3).canonical; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); 1| assert(slice._strides == [3]); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | 1| auto dataframe = slice!(double, int, string)(2, 3); 1| dataframe.label[] = [1, 2]; 1| dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | 1| auto canonicaldf = dataframe.canonical; 1| assert(canonicaldf._lengths == [2, 3]); 1| assert(canonicaldf._strides == [3]); | 1| assert(is(typeof(canonicaldf) == | Slice!(double*, 2, Canonical, int*, string*))); 1| assert(canonicaldf.label!0[0] == 1); 1| assert(canonicaldf.label!1[1] == "Label2"); |} | |/++ |Converts a slice to canonical kind (unsafe). | |Params: | slice = a slice |Returns: | canonical slice |See_also: | $(LREF universal), | $(LREF canonical), | $(LREF assumeContiguous). |+/ |Slice!(Iterator, N, Canonical, Labels) | assumeCanonical | (Iterator, size_t N, SliceKind kind, Labels...) | (Slice!(Iterator, N, kind, Labels) slice) |{ | static if (kind == Contiguous) 1| return slice.canonical; | else | static if (kind == Canonical) | return slice; | else | { | import mir.utility: swap; 4| assert(slice._lengths[N - 1] <= 1 || slice._strides[N - 1] == 1); 2| typeof(return) ret; 2| ret._lengths = slice._lengths; 2| ret._strides = slice._strides[0 .. $ - 1]; 2| swap(ret._iterator, slice._iterator); | foreach(i, _; Labels) | swap(ret._labels[i], slice._labels[i]); 2| return ret; | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ 1| auto slice = iota(2, 3).universal.assumeCanonical; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); 1| assert(slice._strides == [3]); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | 1| auto dataframe = slice!(double, int, string)(2, 3); 1| dataframe.label[] = [1, 2]; 1| dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | 1| auto assmcanonicaldf = dataframe.assumeCanonical; 1| assert(assmcanonicaldf._lengths == [2, 3]); 1| assert(assmcanonicaldf._strides == [3]); | 1| assert(is(typeof(assmcanonicaldf) == | Slice!(double*, 2, Canonical, int*, string*))); 1| assert(assmcanonicaldf.label!0[0] == 1); 1| assert(assmcanonicaldf.label!1[1] == "Label2"); |} | |/++ |Converts a slice to contiguous kind (unsafe). | |Params: | slice = a slice |Returns: | canonical slice |See_also: | $(LREF universal), | $(LREF canonical), | $(LREF assumeCanonical). |+/ |Slice!(Iterator, N, Contiguous, Labels) | assumeContiguous | (Iterator, size_t N, SliceKind kind, Labels...) | (Slice!(Iterator, N, kind, Labels) slice) |{ | static if (kind == Contiguous) 70| return slice; | else | { | import mir.utility: swap; 67| typeof(return) ret; 67| ret._lengths = slice._lengths; 67| swap(ret._iterator, slice._iterator); | foreach(i, _; Labels) 2| swap(ret._labels[i], slice._labels[i]); 67| return ret; | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ 1| auto slice = iota(2, 3).universal.assumeContiguous; 1| assert(slice == [[0, 1, 2], [3, 4, 5]]); 1| assert(slice._lengths == [2, 3]); | static assert(slice._strides.length == 0); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | 1| auto dataframe = slice!(double, int, string)(2, 3); 1| dataframe.label[] = [1, 2]; 1| dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | 1| auto assmcontdf = dataframe.canonical.assumeContiguous; 1| assert(assmcontdf._lengths == [2, 3]); | static assert(assmcontdf._strides.length == 0); | 1| assert(is(typeof(assmcontdf) == | Slice!(double*, 2, Contiguous, int*, string*))); 1| assert(assmcontdf.label!0[0] == 1); 1| assert(assmcontdf.label!1[1] == "Label2"); |} | |/++ |Helps the compiler to use optimisations related to the shape form |+/ |void assumeHypercube | (Iterator, size_t N, SliceKind kind, Labels...) | (ref scope Slice!(Iterator, N, kind, Labels) slice) |{ | foreach (i; Iota!(1, N)) | { 2| assert(slice._lengths[i] == slice._lengths[0]); 2| slice._lengths[i] = slice._lengths[0]; | } |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ 1| auto b = iota(5, 5); | 1| assumeHypercube(b); | 1| assert(b == iota(5, 5)); |} | |/++ |Helps the compiler to use optimisations related to the shape form |+/ |void assumeSameShape(T...) | (ref scope T slices) | if (allSatisfy!(isSlice, T)) |{ | foreach (i; Iota!(1, T.length)) | { 1| assert(slices[i]._lengths == slices[0]._lengths); 1| slices[i]._lengths = slices[0]._lengths; | } |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ 1| auto a = iota(5, 5); 1| auto b = iota(5, 5); | 1| assumeHypercube(a); // first use this one, if applicable 1| assumeSameShape(a, b); // | 1| assert(a == iota(5, 5)); 1| assert(b == iota(5, 5)); |} | |/++ |+/ |auto assumeFieldsHaveZeroShift(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (__traits(hasMember, Iterator, "assumeFieldsHaveZeroShift")) |{ 1| return slice._iterator.assumeFieldsHaveZeroShift.slicedField(slice._lengths); |} | |/++ |Creates a packed slice, i.e. slice of slices. |Packs the last `P` dimensions. |The function does not allocate any data. | |Params: | P = size of dimension pack | slice = a slice to pack |Returns: | `slice.pack!p` returns `Slice!(kind, [N - p, p], Iterator)` |See_also: $(LREF ipack) |+/ |Slice!(SliceIterator!(Iterator, P, P == 1 && kind == Canonical ? Contiguous : kind), N - P, Universal) |pack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | if (P && P < N) |{ | import core.lifetime: move; 36| return slice.move.ipack!(N - P); |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice: sliced, Slice; | 1| auto a = iota(3, 4, 5, 6); 1| auto b = a.pack!2; | | static immutable res1 = [3, 4]; | static immutable res2 = [5, 6]; 1| assert(b.shape == res1); 1| assert(b[0, 0].shape == res2); 1| assert(a == b.unpack); 1| assert(a.pack!2 == b); | static assert(is(typeof(b) == typeof(a.pack!2))); |} | |/++ |Creates a packed slice, i.e. slice of slices. |Packs the last `N - P` dimensions. |The function does not allocate any data. | |Params: | + = size of dimension pack | slice = a slice to pack |See_also: $(LREF pack) |+/ |Slice!(SliceIterator!(Iterator, N - P, N - P == 1 && kind == Canonical ? Contiguous : kind), P, Universal) |ipack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) | if (P && P < N) |{ | import core.lifetime: move; | alias Ret = typeof(return); | alias It = Ret.Iterator; | alias EN = It.Element.N; | alias ES = It.Element.S; 183| auto sl = slice.move.universal; | static if (It.Element.kind == Contiguous) 108| return Ret( | cast( size_t[P]) sl._lengths[0 .. P], | cast(ptrdiff_t[P]) sl._strides[0 .. P], | It( | cast(size_t[EN]) sl._lengths[P .. $], | sl._iterator.move)); | else 75| return Ret( | cast( size_t[P]) sl._lengths[0 .. P], | cast(ptrdiff_t[P]) sl._strides[0 .. P], | It( | cast( size_t[EN]) sl._lengths[P .. $], | cast(ptrdiff_t[ES]) sl._strides[P .. $ - (It.Element.kind == Canonical)], | sl._iterator.move)); |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice: sliced, Slice; | 1| auto a = iota(3, 4, 5, 6); 1| auto b = a.ipack!2; | | static immutable res1 = [3, 4]; | static immutable res2 = [5, 6]; 1| assert(b.shape == res1); 1| assert(b[0, 0].shape == res2); 1| assert(a.ipack!2 == b); | static assert(is(typeof(b) == typeof(a.ipack!2))); |} | |/++ |Unpacks a packed slice. | |The functions does not allocate any data. | |Params: | slice = packed slice |Returns: | unpacked slice, that is a view on the same data. | |See_also: $(LREF pack), $(LREF evertPack) |+/ |Slice!(Iterator, N + M, min(innerKind, Canonical)) | unpack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind) | (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice) |{ | alias Ret = typeof(return); 49| size_t[N + M] lengths; 49| auto strides = sizediff_t[Ret.S].init; 49| auto outerStrides = slice.strides; 49| auto innerStrides = Slice!(Iterator, M, innerKind)( | slice._iterator._structure, | slice._iterator._iterator, | ).strides; | foreach(i; Iota!N) 78| lengths[i] = slice._lengths[i]; | foreach(i; Iota!N) 78| strides[i] = outerStrides[i]; | foreach(i; Iota!M) 76| lengths[N + i] = slice._iterator._structure[0][i]; | foreach(i; Iota!(Ret.S - N)) 43| strides[N + i] = innerStrides[i]; 49| return Ret(lengths, strides, slice._iterator._iterator); |} | |/++ |Reverses the order of dimension packs. |This function is used in a functional pipeline with other selectors. | |Params: | slice = packed slice |Returns: | packed slice | |See_also: $(LREF pack), $(LREF unpack) |+/ |Slice!(SliceIterator!(Iterator, N, outerKind), M, innerKind) |evertPack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind) | (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice) |{ | import core.lifetime: move; 204| return typeof(return)( | slice._iterator._structure, | typeof(return).Iterator( | slice._structure, | slice._iterator._iterator.move)); |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.dynamic : transposed; 1| auto slice = iota(3, 4, 5, 6, 7, 8, 9, 10, 11).universal; 1| assert(slice | .pack!2 | .evertPack | .unpack | == slice.transposed!( | slice.shape.length-2, | slice.shape.length-1)); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.iterator: SliceIterator; | import mir.ndslice.slice: sliced, Slice, Universal; | import mir.ndslice.allocation: slice; | static assert(is(typeof( | slice!int(6) | .sliced(1,2,3) | .pack!1 | .evertPack | ) | == Slice!(SliceIterator!(int*, 2, Universal), 1))); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ 1| auto a = iota(3, 4, 5, 6, 7, 8, 9, 10, 11); 1| auto b = a.pack!2.unpack; | static assert(is(typeof(a.canonical) == typeof(b))); 1| assert(a == b); |} | |/++ |Returns a slice, the elements of which are equal to the initial flattened index value. | |Params: | N = dimension count | lengths = list of dimension lengths | start = value of the first element in a slice (optional for integer `I`) | stride = value of the stride between elements (optional) |Returns: | n-dimensional slice composed of indices |See_also: $(LREF ndiota) |+/ |Slice!(IotaIterator!I, N) |iota | (I = sizediff_t, size_t N)(size_t[N] lengths...) | if (__traits(isIntegral, I)) |{ | import mir.ndslice.slice: sliced; 2286| return IotaIterator!I(I.init).sliced(lengths); |} | |///ditto |Slice!(IotaIterator!sizediff_t, N) |iota | (size_t N)(size_t[N] lengths, sizediff_t start) |{ | import mir.ndslice.slice: sliced; 173| return IotaIterator!sizediff_t(start).sliced(lengths); |} | |///ditto |Slice!(StrideIterator!(IotaIterator!sizediff_t), N) |iota | (size_t N)(size_t[N] lengths, sizediff_t start, size_t stride) |{ | import mir.ndslice.slice: sliced; 27| return StrideIterator!(IotaIterator!sizediff_t)(stride, IotaIterator!sizediff_t(start)).sliced(lengths); |} | |///ditto |template iota(I) | if (__traits(isIntegral, I)) |{ | /// | Slice!(IotaIterator!I, N) | iota | (size_t N)(size_t[N] lengths, I start) | if (__traits(isIntegral, I)) | { | import mir.ndslice.slice: sliced; 12| return IotaIterator!I(start).sliced(lengths); | } | | ///ditto | Slice!(StrideIterator!(IotaIterator!I), N) | iota | (size_t N)(size_t[N] lengths, I start, size_t stride) | if (__traits(isIntegral, I)) | { | import mir.ndslice.slice: sliced; | return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths); | } |} | |///ditto |Slice!(IotaIterator!I, N) |iota | (I, size_t N)(size_t[N] lengths, I start) | if (is(I P : P*)) |{ | import mir.ndslice.slice: sliced; 3| return IotaIterator!I(start).sliced(lengths); |} | |///ditto |Slice!(StrideIterator!(IotaIterator!I), N) |iota | (I, size_t N)(size_t[N] lengths, I start, size_t stride) | if (is(I P : P*)) |{ | import mir.ndslice.slice: sliced; | return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths); |} | |/// |@safe pure nothrow @nogc version(mir_test) unittest |{ | import mir.primitives: DeepElementType; 1| auto slice = iota(2, 3); | static immutable array = | [[0, 1, 2], | [3, 4, 5]]; | 1| assert(slice == array); | | static assert(is(DeepElementType!(typeof(slice)) == sizediff_t)); |} | |/// |pure nothrow @nogc |version(mir_test) unittest |{ 1| int[6] data; 1| auto slice = iota([2, 3], data.ptr); 1| assert(slice[0, 0] == data.ptr); 1| assert(slice[0, 1] == data.ptr + 1); 1| assert(slice[1, 0] == data.ptr + 3); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ 1| auto im = iota([10, 5], 100); 1| assert(im[2, 1] == 111); // 100 + 2 * 5 + 1 | | //slicing works correctly 1| auto cm = im[1 .. $, 3 .. $]; 1| assert(cm[2, 1] == 119); // 119 = 100 + (1 + 2) * 5 + (3 + 1) |} | |/// `iota` with step |@safe pure nothrow version(mir_test) unittest |{ 1| auto sl = iota([2, 3], 10, 10); | 1| assert(sl == [[10, 20, 30], | [40, 50, 60]]); |} | |/++ |Returns a 1-dimensional slice over the main diagonal of an n-dimensional slice. |`diagonal` can be generalized with other selectors such as |$(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice). | |Params: | slice = input slice |Returns: | 1-dimensional slice composed of diagonal elements |See_also: $(LREF antidiagonal) |+/ |Slice!(Iterator, 1, N == 1 ? kind : Universal) | diagonal | (Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) |{ | static if (N == 1) | { | return slice; | } | else | { | alias Ret = typeof(return); 61| size_t[Ret.N] lengths; 61| auto strides = sizediff_t[Ret.S].init; 61| lengths[0] = slice._lengths[0]; | foreach (i; Iota!(1, N)) 63| if (lengths[0] > slice._lengths[i]) 1| lengths[0] = slice._lengths[i]; | foreach (i; Iota!(1, Ret.N)) | lengths[i] = slice._lengths[i + N - 1]; 61| auto rstrides = slice.strides; 61| strides[0] = rstrides[0]; | foreach (i; Iota!(1, N)) 63| strides[0] += rstrides[i]; | foreach (i; Iota!(1, Ret.S)) | strides[i] = rstrides[i + N - 1]; 61| return Ret(lengths, strides, slice._iterator); | } |} | |/// Matrix, main diagonal |@safe @nogc pure nothrow version(mir_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_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_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_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_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_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_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_test) unittest |{ | // ----- | // | 0 1 | | // | 2 3 | | // ----- | //-> | // | 1 2 | | static immutable c = [1, 2]; | import std.stdio; 1| assert(iota(2, 2).antidiagonal == c); |} | |/// |@safe @nogc pure nothrow version(mir_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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) | { 422| return slice; | } | else | { | import core.lifetime: move; 1663| size_t[typeof(return).N] lengths; 1663| lengths[0] = slice.elementCount; 1663| return typeof(return)(lengths, slice._iterator.move); | } |} | |/// ditto |Slice!(StrideIterator!Iterator) | flattened | (Iterator) | (Slice!(Iterator, 1, Universal) slice) |{ | import core.lifetime: move; 142| return slice.move.hideStride; |} | |version(mir_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_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_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_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_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_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_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_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_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_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_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_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_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_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) |{ 25| return FieldIterator!(ndIotaField!N)(0, ndIotaField!N(lengths[1 .. $])).sliced(lengths); |} | |/// |@safe pure nothrow @nogc version(mir_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_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_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)) |{ 15| Repeat!(N, LinspaceField!T) fields; | foreach(i; Iota!N) | { 17| assert(lengths[i] > 1, "linspace: all lengths must be greater then 1."); 17| fields[i] = LinspaceField!T(lengths[i], intervals[i][0], intervals[i][1]); | } | static if (N == 1) 13| return slicedField(fields); | else 2| return cartesian(fields); |} | |// example from readme |version(mir_test) unittest |{ | import mir.ndslice; | // import std.stdio: writefln; | | 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_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_test) unittest |{ | import mir.functional: refTuple; | 1| auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]); | 1| assert(s == [ | [refTuple(1.00, 0.00), refTuple(1.00, 0.5), refTuple(1.00, 1.0)], | [refTuple(1.25, 0.00), refTuple(1.25, 0.5), refTuple(1.25, 1.0)], | [refTuple(1.50, 0.00), refTuple(1.50, 0.5), refTuple(1.50, 1.0)], | [refTuple(1.75, 0.00), refTuple(1.75, 0.5), refTuple(1.75, 1.0)], | [refTuple(2.00, 0.00), refTuple(2.00, 0.5), refTuple(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_test) unittest |{ 1| auto s = linspace!cdouble([3], [1.0 + 0i, 2.0 + 4i]); 1| assert(s == [1.0 + 0i, 1.5 + 2i, 2.0 + 4i]); |} | |/++ |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_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_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_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_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_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_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_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) | { 729| size_t[slice.N] lengths; | foreach (i; Iota!(slice.N)) 736| 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); 720| slice._iterator += slice.lastIndex; 720| 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_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_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); 973| auto structure_ = Ret._Structure.init; | foreach(i; Iota!(Ret.N)) 974| structure_[0][i] = slice._lengths[i]; 973| 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 972| return Ret(structure_, It(0, BitField!Iterator(slice._iterator.move))); | } |} | |/// ditto |auto bitwise(T)(T[] array) |{ 1| return bitwise(array.sliced); |} | |/// ditto |auto bitwise(T)(T withAsSlice) | if (hasAsSlice!T) |{ | return bitwise(withAsSlice.asSlice); |} | |/// |@safe pure nothrow @nogc |version(mir_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_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_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; 2| auto structure = slice._structure; 2| structure[0][$ - 1] /= group; 2| return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator.move)); |} | |/// ditto |auto bytegroup(size_t pack, DestinationType, T)(T[] array) |{ 1| 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_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_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."); 551| return Slice!(MIterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator.move)); | } | | /// ditto | auto map(T)(T[] array) | { 2| 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_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_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_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_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_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 refTuple containing |one element for each function. |+/ |@safe pure nothrow |version(mir_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_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_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_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_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; | 9| auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 12| auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; 1| assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); 9| auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 12| 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_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); 134| 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_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_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_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 refTuple containing |one element for each function. |+/ |@safe pure nothrow |version(none) version(mir_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_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_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; 20| this(T f) { factor = f; } 38| auto opCall(U)(U x) {return x * factor; } 8| 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) 357| return Slice!(StrideIterator!Iterator)( | slice._lengths, | StrideIterator!Iterator(slice._strides[0], move(slice._iterator))); | else 202| 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_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_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_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_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_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_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_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_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)) 35| return slice; | else | static if (is(Iterator : T*)) 3| return slice.toConst; | else | { | import core.lifetime: move; | import mir.conv: to; 57| return map!(to!T)(slice.move); | } | } | | /// ditto | auto as(S)(S[] array) | { 2| 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_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_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_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; 437| return typeof(return)( | indices._structure, | IndexIterator!(Iterator, Field)( | indices._iterator.move, | source)); |} | |/// ditto |auto indexed(Field, S)(Field source, S[] indices) |{ 13| 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_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_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_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 refTuple |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 refTuple | 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"); 692| 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); 487| auto structure = Ret._Structure.init; 487| structure[0] = slices[0]._lengths; | foreach (i; Iota!(Ret.S)) | structure[1][i] = slices[0]._strides[i]; 487| return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")")); | } | } | else | { 1| return .zip(toSlices!slices); | } | } |} | |/// |@safe pure nothrow version(mir_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_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_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) | { 134| return slideAlong(slice.move.flattened); | } | else | { | alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); | enum dimension = Dimensions[$ - 1]; 433| size_t len = slice._lengths[dimension] - (params - 1); 433| if (sizediff_t(len) <= 0) // overfow 4| len = 0; 433| slice._lengths[dimension] = len; | static if (dimension + 1 == N || kind == Universal) | { | alias I = SlideIterator!(Iterator, params, fun); 429| 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) | { 430| 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_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; 428| return slice.move.slideAlong!(params, fun, Iota!N); | } | | /// ditto | auto slide(S)(S[] slice) | { | 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_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_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_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_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_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_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_test) unittest |{ | // 0 1 2 3 | // 4 5 6 7 | // 8 9 10 11 1| auto s = iota(3, 4); | import std.stdio; 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_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_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_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)) |{ 15| return Cartesian!NdFields(fields).slicedNdField; |} | |/// 1D x 1D |version(mir_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_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_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_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_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_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_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_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_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_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_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_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_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; | 9| auto s1 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 12| 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; | 141| auto trans = slice | .move | .transposed!Dimensions; | static if (Dimensions.length == N) | { | return trans; | } | else | { 141| auto ret = trans.move.ipack!(Dimensions.length); 0000000| static if ((kind == Contiguous || kind == Canonical && N - Dimensions.length == 1) && [Dimensions].all!(a => a < Dimensions.length)) | { 70| 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)]) | { 65| 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_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_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_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_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; | 9| auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; 1| assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); 12| 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_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_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_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; | } |} | |/// |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_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_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_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_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-combinatorics-package.lst |/** |This module contains various combinatorics algorithms. | |Authors: Sebastian Wilzbach, Ilya Yaroshenko | |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; | 1| assert(['a', 'b'].permutations.fuse == [['a', 'b'], ['b', 'a']]); 1| assert(['a', 'b'].cartesianPower(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'a'], ['b', 'b']]); 1| assert(['a', 'b'].combinations(2).fuse == [['a', 'b']]); 1| assert(['a', 'b'].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)(T n, T k) | if (isArithmetic!(R, T) && | ((is(typeof(T.min < 0)) && is(typeof(T.init & 1))) || !is(typeof(T.min < 0))) ) |{ 102| R result = 1; | | enum hasMinProperty = is(typeof(T.min < 0)); | // only add negative support if possible | static if ((hasMinProperty && T.min < 0) || !hasMinProperty) | { 24| if (n < 0) | { 3| if (k >= 0) | { 2| return (k & 1 ? -1 : 1) * binomial!(R, T)(-n + k-1, k); | } 1| else if (k <= n) | { 1| return ((n-k) & 1 ? -1 : 1) * binomial!(R, T)(-k-1, n-k); | } | } 21| if (k < 0) | { 2| result = 0; 2| return result; | } | } | 97| if (k > n) | { 9| result = 0; 9| return result; | } 88| if (k > n - k) | { 38| k = n - k; | } | // make a copy of n (could be a custom type) 714| for (T i = 1, m = n; i <= k; 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))) | { 203| if (result / i > R.max / m) return 0; 201| result = result / i * m + result % i * m / i; | } | else | { 24| result = result * m / i; | } | } 87| 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); |} | |/** |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 IndexedRoR!(LightScopeOf!Collection, LightScopeOf!Range)(.lightScope(c), .lightScope(r)); | } | | /// | auto lightScope()() const | { | return IndexedRoR!(LightConstOf!(LightScopeOf!Collection), LightConstOf!(LightScopeOf!Range))(.lightScope(c), .lightScope(r)); | } | | /// | auto lightConst()() 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 mir.ndslice.topology: only; | 1| auto projectionD = 2.permutations.indexedRoR("ab"d); 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); | 1| assert("AB"d.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; | 1| assert(iota(0).cartesianPower.length == 0); 1| assert("AB"d.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.cartesianPower(2).fuse == expected); | // verify with array too 1| assert("ABCD"d.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; 1| assert(iota(3).combinations(2).fuse == [[0, 1], [0, 2], [1, 2]]); 1| assert("AB"d.combinations(2).fuse == ["AB"d]); 1| assert("ABC"d.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; | 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.combinations(2).fuse == expected); | // verify with array too 1| assert("ABCD"d.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; | 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.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; | 1| assert(iota(0).combinationsRepeat.length == 0); 1| assert("AB"d.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.combinationsRepeat(2).fuse == expected); | // verify with array too 1| assert("ABCD"d.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-numeric.lst |/++ |Base numeric algorithms. | |Reworked part of `std.numeric`. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko (API, findLocalMin, findRoot extension), Don Clugston (findRoot) |+/ |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/findLocalMin: 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() | { 80| with(FindRootStatus) return 80| ax != ax || bx != bx ? nanX : 80| ay != ay || by != by ? nanY : 40| 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() | { 22| 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));} | ) | )) |{ 84| if (false) // break attributes | T y = f(T(1)); 84| scope funInst = delegate(T x) { 1129| return T(f(x)); | }; 84| 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; | } 84| 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 |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)`. |+/ |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); 23| 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: Ilya Yaroshenko (Bounds extension logic, | // API improvements, infinity and huge numbers handing, compiled code size reduction) | 84| T d; // [a .. b] is our current bracket. d is the third best guess. 84| T fd; // Value of f at d. 84| bool done = false; // Has a root been found? 84| uint iterations; | | static void swap(ref T a, ref T b) | { 12| T t = a; a = b; b = t; | } | | bool exit() | { | pragma(inline, false); 1066| return done 1016| || iterations >= maxIterations 1016| || b == nextUp(a) 1021| || tolerance !is null && tolerance(a, b); | } | | // Test the function at point c; update brackets accordingly | void bracket(T c) | { | pragma(inline, false); 928| T fc = f(c); 928| iterations++; 1811| if (fc == 0 || fc != fc) // Exact solution, or NaN | { 45| a = c; 45| fa = fc; 45| d = c; 45| fd = fc; 45| done = true; 45| return; | } | 883| fc = fc.fabs.fmin(T.max / 2).copysign(fc); | | // Determine new enclosing interval 883| if (signbit(fa) != signbit(fc)) | { 441| d = b; 441| fd = fb; 441| b = c; 441| fb = fc; | } | else | { 442| d = a; 442| fd = fa; 442| a = c; 442| 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); 185| if (a - b == a && b != 0 252| || b - a == b && a != 0) | { | // Catastrophic cancellation 13| return ieeeMean(a, b); | } | // avoid overflow 167| T m = fa - fb; 167| T wa = fa / m; 167| T wb = fb / m; 167| T c = b * wa - a * wb; 548| if (c == a || c == b || c != c || c.fabs == T.infinity) 58| c = a.half + b.half; 167| 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. 218| const T a0 = fa; 218| const T a1 = (fb - fa)/(b - a); 218| const T a2 = ((fd - fb)/(d - b) - a1)/(d - a); | | // Determine the starting point of newton steps. 218| T c = a2.signbit != fa.signbit ? a : b; | | // start the safeguarded newton steps. 2233| foreach (int i; 0 .. numsteps) | { 527| const T pc = a0 + (a1 + a2 * (c - b))*(c - a); 527| const T pdc = a1 + a2*((2 * c) - (a + b)); 527| if (pdc == 0) 1| return a - a0 / a1; | else 526| c = c - pc / pdc; | } 217| return c; | } | | // Starting with the second iteration, higher-order interpolation can | // be used. 84| int itnum = 1; // Iteration number 84| int baditer = 1; // Num bisections to take if an iteration is bad. 168| T c, e; // e is our fourth best guess 84| T fe; 84| bool left; | | // Allow a and b to be provided in reverse order 84| if (a > b) | { 1| swap(a, b); 1| swap(fa, fb); | } | 168| if (a != a || b != b) | { 0000000| done = true; 0000000| goto whileloop; | } | 84| if (lb != lb) | { 65| lb = a; | } | 84| if (ub != ub) | { 65| ub = b; | } | 84| if (lb > ub) | { 2| swap(lb, ub); 2| left = true; | } | 84| if (lb == -T.infinity) | { 2| lb = -T.max; | } | 84| if (ub == +T.infinity) | { 6| ub = +T.max; | } | 84| if (!(lb <= a)) | { 2| a = lb; 2| fa = T.nan; | } | 84| if (!(b <= ub)) | { 3| a = lb; 3| fa = T.nan; | } | 84| if (fa != fa) | { 83| fa = f(a); 83| iterations++; | } | | // On the first iteration we take a secant step: 165| if (fa == 0 || fa != fa) | { 3| done = true; 3| b = a; 3| fb = fa; 3| goto whileloop; | } | 81| if (fb != fb) | { 80| if (a == b) | { 5| fb = fa; | } | else | { 75| fb = f(b); 75| iterations++; | } | } | 161| if (fb == 0 || fb != fb) | { 1| done = true; 1| a = b; 1| fa = fb; 1| goto whileloop; | } | 80| if (fa.fabs < fb.fabs) | { 33| left = true; | } | else 47| if (fa.fabs > fb.fabs) | { 20| left = false; | } | | // extend inner boundaries 80| if (fa.signbit == fb.signbit) | { 14| T lx = a; 14| T ux = b; 14| T ly = fa; 14| T uy = fb; 14| const sb = fa.signbit; | | import mir.ndslice.topology: linspace; | 28| typeof(linspace!T([2], [lx, lb])) lgrid, ugrid; 14| if (steps) | { 4| lgrid = linspace!T([steps + 1], [lx, lb]); 4| lgrid.popFront; 4| ugrid = linspace!T([steps + 1], [ux, ub]); 4| ugrid.popFront; | } | 14| for(T mx;;) | { 43| bool lw = lb < lx; 43| bool uw = ux < ub; | 86| if (!lw && !uw || iterations >= maxIterations) | { 0000000| done = true; 0000000| 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; | } | } | } | 79| fa = fa.fabs.fmin(T.max / 2).copysign(fa); 79| fb = fb.fabs.fmin(T.max / 2).copysign(fb); 79| bracket(secantInterpolate(a, b, fa, fb)); | |whileloop: 333| while (!exit) | { 652| T a0 = a, b0 = b; // record the brackets | | // Do two higher-order (cubic or parabolic) interpolation steps. 2337| 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. 1607| bool distinct = (fa != fb) && (fa != fd) && (fa != fe) 1496| && (fb != fd) && (fb != fe) && (fd != fe); | // The first time, cubic interpolation is impossible. 622| if (itnum<2) distinct = false; 545| bool ok = distinct; 545| if (distinct) | { | // Cubic inverse interpolation of f(x) at a, b, d, and e 420| const q11 = (d - e) * fd / (fe - fd); 420| const q21 = (b - d) * fb / (fd - fb); 420| const q31 = (a - b) * fa / (fb - fa); 420| const d21 = (b - d) * fd / (fd - fb); 420| const d31 = (a - b) * fb / (fb - fa); | 420| const q22 = (d21 - q11) * fb / (fe - fb); 420| const q32 = (d31 - q21) * fa / (fd - fa); 420| const d32 = (d31 - q21) * fd / (fd - fa); 420| const q33 = (d32 - q22) * fa / (fe - fa); 420| c = a + (q31 + q32 + q33); 1202| 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. 113| if (c == a && (a - b != a || a - b != -b)) | { 10| auto down = !(a - b != a); 10| if (down) 0000000| c = -c; 10| c = c.nextUp; 10| if (down) 0000000| c = -c; | } | else | { 93| ok = false; | } | | } | } 545| if (!ok) | { | // DAC: Alefeld doesn't explain why the number of newton steps | // should vary. 218| c = newtonQuadratic(2 + distinct); 602| if (c != c || (c <= a) || (c >= b)) | { | // Failure, try a secant step: 101| c = secantInterpolate(a, b, fa, fb); | } | } 545| ++itnum; 545| e = d; 545| fe = fd; 545| bracket(c); 545| if (exit) 61| break whileloop; 484| if (itnum == 2) 77| continue whileloop; | } | | // Now we take a double-length secant step: 188| T u; 188| T fu; 188| if (fabs(fa) < fabs(fb)) | { 105| u = a; 105| fu = fa; | } | else | { 83| u = b; 83| fu = fb; | } 188| c = u - 2 * (fu / (fb - fa)) * (b - a); | | // DAC: If the secant predicts a value equal to an endpoint, it's | // probably false. 704| if (c == a || c == b || c != c || fabs(c - u) > (b - a) * 0.5f) | { 64| if ((a - b) == a || (b - a) == b) | { 10| c = ieeeMean(a, b); | } | else | { 22| c = a.half + b.half; | } | } 188| e = d; 188| fe = fd; 188| bracket(c); 188| if (exit) 16| 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. | 172| if ((a == 0 170| || b == 0 170| || fabs(a) >= 0.5f * fabs(b) 136| && fabs(b) >= 0.5f * fabs(a)) 134| && b - a < 0.25f * (b0 - a0)) | { 119| baditer = 1; 119| 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; | } 84| 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) | { 32| 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) | { 199| ++numCalls; 199| ++alefeldSums[2]; 199| 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) | { 73| ++numCalls; 73| ++alefeldSums[4]; 73| return x*x - pow(1-x, n); | } | real alefeld5(real x) | { 80| ++numCalls; 80| ++alefeldSums[5]; 80| return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4); | } | real alefeld6(real x) | { 87| ++numCalls; 87| ++alefeldSums[6]; 87| 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 | { 16| with(FindRootStatus) final switch(status) | { 32| 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() | { 19| 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. |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)) | if (isFloatingPoint!T && __traits(compiles, T(f(T.init)))) |{ 19| if (false) // break attributes | { | T y = f(T(123)); | } 19| scope funInst = delegate(T x) { 98361| return T(f(x)); | }; 19| scope fun = funInst.trustedAllAttr; | 19| return findLocalMinImpl(ax, bx, relTolerance, absTolerance, fun); |} | |@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, | ) @safe pure nothrow @nogc |{ | pragma(inline, false); 6| return findLocalMinImplGen!float(ax, bx, relTolerance, absTolerance, f); |} | |@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, | ) @safe pure nothrow @nogc |{ | pragma(inline, false); 7| return findLocalMinImplGen!double(ax, bx, relTolerance, absTolerance, f); |} | |@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, | ) @safe pure nothrow @nogc |{ | pragma(inline, false); 6| return findLocalMinImplGen!real(ax, bx, relTolerance, absTolerance, f); |} | |@fmamath |private FindLocalMinResult!T findLocalMinImplGen(T)( | const T ax, | const T bx, | const T relTolerance, | const T absTolerance, | scope const T delegate(T) @safe pure nothrow @nogc f, | ) | if (isFloatingPoint!T && __traits(compiles, {T _ = f(T.init);})) |in |{ 38| assert(relTolerance.fabs >= T.min_normal && relTolerance.fabs < T.infinity, "relTolerance is not normal floating point number"); 38| assert(absTolerance.fabs >= T.min_normal && absTolerance.fabs < T.infinity, "absTolerance is not normal floating point number"); 19| assert(relTolerance >= 0, "absTolerance is not positive"); 19| assert(absTolerance >= T.epsilon*2, "absTolerance is not greater then `2*T.epsilon`"); |} |do |{ | 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; 19| T tolerance; 19| T a = ax > bx ? bx : ax; 19| T b = ax > bx ? ax : bx; 19| if (a < -T.max) 0000000| a = -T.max; 19| if (b > +T.max) 0000000| b = +T.max; | // sequence of declarations suitable for SIMD instructions 19| T v = a * cm1 + b * c; 19| assert(v.fabs < T.infinity); 19| T fv = v == v ? f(v) : v; 35| if (fv != fv || fv == -T.infinity) | { 3| return typeof(return)(v, fv, T.init); | } 16| T w = v; 16| T fw = fv; 16| T x = v; 16| T fx = fv; 16| size_t i; 32| for (T d = 0, e = 0;;) | { 98358| i++; 98358| T m = (a + b) * 0.5f; | // This fix is not part of the original algorithm 98358| if (!((m.fabs < T.infinity))) // fix infinity loop. Issue can be reproduced in R. | { 0000000| m = a.half + b.half; | } 98358| tolerance = absTolerance * fabs(x) + relTolerance; 98358| const t2 = tolerance * 2; | // check stopping criterion 98358| if (!(fabs(x - m) > t2 - (b - a) * 0.5f)) | { 16| break; | } 98342| T p = 0; 98342| T q = 0; 98342| T r = 0; | // fit parabola 98342| if (fabs(e) > tolerance) | { 98326| const xw = x - w; 98326| const fxw = fx - fw; 98326| const xv = x - v; 98326| const fxv = fx - fv; 98326| const xwfxv = xw * fxv; 98326| const xvfxw = xv * fxw; 98326| p = xv * xvfxw - xw * xwfxv; 98326| q = (xvfxw - xwfxv) * 2; 98326| if (q > 0) 86943| p = -p; | else 11383| q = -q; 98326| r = e; 98326| e = d; | } 98342| T u; | // a parabolic-interpolation step 107048| if (fabs(p) < fabs(q * r * 0.5f) && p > q * (a - x) && p < q * (b - x)) | { 4353| d = p / q; 4353| u = x + d; | // f must not be evaluated too close to a or b 8701| if (u - a < t2 || b - u < t2) 7| d = x < m ? tolerance: -tolerance; | } | // a golden-section step | else | { 93989| e = (x < m ? b : a) - x; 93989| d = c * e; | } | // f must not be evaluated too close to x 98342| u = x + (fabs(d) >= tolerance ? d: d > 0 ? tolerance: -tolerance); 98342| const fu = f(u); 196684| if (fu != fu || fu == -T.infinity) | { 0000000| return typeof(return)(u, fu, T.init); | } | // update a, b, v, w, and x 98342| if (fu <= fx) | { 83337| (u < x ? b : a) = x; 166674| v = w; fv = fw; 166674| w = x; fw = fx; 166674| x = u; fx = fu; | } | else | { 15005| (u < x ? a : b) = u; 21016| if (fu <= fw || w == x) | { 18010| v = w; fv = fw; 18010| w = u; fw = fu; | } 6347| else if (fu <= fv || v == x || v == w) | { // do not remove this braces 11660| v = u; fv = fu; | } | } | } 16| 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))); | } | } |} | |// force disabled FMA math |private static auto half(T)(const T x) |{ | pragma(inline, false); 160| 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_; 105| return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; |} source/mir/numeric.d is 93% covered <<<<<< EOF # path=./source-mir-appender.lst |/++ |$(H1 Scoped Buffer) | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko |+/ |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); | | |/// |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; | private 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 | { 104605| return *cast(inout(T[_bufferLength])*)&_scopeBufferPayload; | } | | private T[] prepare(size_t n) @trusted scope | { | import mir.internal.memory: realloc, malloc; 54622| _currentLength += n; 54622| if (_buffer.length == 0) | { 34412| if (_currentLength <= _bufferLength) | { 34368| return _scopeBuffer[0 .. _currentLength]; | } | else | { 44| auto 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 20210| if (_currentLength > _buffer.length) | { 43| auto newLen = _currentLength << 1; 43| _buffer = (cast(T*)realloc(cast(void*)_buffer.ptr, T.sizeof * newLen))[0 .. newLen]; | version (mir_secure_memory) | { | (cast(ubyte[])_buffer[_currentLength .. $])[] = 0; | } | } 20254| 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; 172| 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; 917| data._mir_destroy; | version(mir_secure_memory) | _currentLength = 0; 1878| (() @trusted { if (_buffer.ptr) free(cast(void*)_buffer.ptr); })(); | } | | /// | void shrinkTo(size_t length) | { 54268| assert(length <= _currentLength); 54268| data[length .. _currentLength]._mir_destroy; 54268| _currentLength = length; | } | | /// | size_t length() scope const @property | { 80| return _currentLength; | } | | /// | void popBackN(size_t n) | { 3| sizediff_t t = _currentLength - n; 3| if (t < 0) | assert(0, "ScopedBffer.popBackN: n is too large."); 3| data[t .. _currentLength]._mir_destroy; 3| _currentLength = t; | } | | /// | void put(T e) @safe scope | { 54375| auto cl = _currentLength; 54375| auto d = prepare(1); | static if (isMutable!T) | { | import core.lifetime: moveEmplace; 108738| ()@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 | { 247| auto cl = _currentLength; 247| auto d = prepare(e.length); 247| if (!__ctfe) 494| (()@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); | } | } | | /// | void reset() scope nothrow | { 233| this.__dtor; 233| _currentLength = 0; 233| _buffer = null; | } | | /// | void initialize() @system scope nothrow @nogc | { 29| _currentLength = 0; 29| _buffer = null; | } | | /// | inout(T)[] data() inout @property @safe scope | { 110703| 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 { 17| assert(array.length == _currentLength); | } | do { 17| memcpy(cast(void*)array.ptr, data.ptr, _currentLength * T.sizeof); 17| _currentLength = 0; | } |} | |/// ditto |auto scopedBuffer(T, size_t bytes = 4096)() @trusted |{ 29| ScopedBuffer!(T, bytes) buffer = void; 29| buffer.initialize; 29| 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"); |} | |/// |struct UnsafeArrayBuffer(T) |{ | import std.traits: isImplicitlyConvertible; | | /// | T[] buffer; | /// | size_t length; | | /// | void put(T a) | { | import core.lifetime: move; 443| assert(length < buffer.length); 443| 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; 353| assert(buffer.length >= a.length + length); 353| buffer[length .. length + a.length] = a; 353| length += a.length; | } | | /// | inout(T)[] data() inout @property @safe scope | { 117| 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() const | { 1| return "_"; | } | } 1| Algebraic!(int, string, double) x; 1| x = 42; 1| auto s = x.to!string; 1| assert(s == "42", s); 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 91% 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) | { 1685| if (counter) | { 1685| 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) | { 1872| if (counter) | { 1870| if (counter.atomicOp!"-="(1) == 0) | { 185| mir_rc_delete(context); | } | } | // else | // { | // assert(0); | // } | } |} | |/++ |+/ |export extern(C) |void mir_rc_delete(ref mir_rc_context context) | @system nothrow @nogc pure |{ 185| assert(context.deallocator); | with(context) | { | with(typeInfo) | { 185| if (destructor) | { 5| auto ptr = cast(void*)(&context + 1); 5| auto i = length; 5| assert(i); | do | { 5| destructor(ptr); 5| ptr += size; | } 5| while(--i); | } | } | } 185| if (context.counter) | assert(0); | version (mir_secure_memory) | { | (cast(ubyte*)(&context + 1))[0 .. context.length * context.typeInfo.size] = 0; | } 185| 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; | 187| assert(length); 187| auto size = length * typeInfo.size; 187| auto fullSize = mir_rc_context.sizeof + size; 187| if (auto p = malloc(fullSize)) | { | version (mir_secure_memory) | { | (cast(ubyte*)p)[0 .. fullSize] = 0; | } 187| auto context = cast(mir_rc_context*)p; 187| context.deallocator = &free; 187| context.typeInfo = &typeInfo; 187| context.counter = deallocate; 187| context.length = length; | 187| if (initialize) | { 78| auto ptr = cast(void*)(context + 1); 78| if (payload) | { 28| 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; | } 25| default: 774| foreach(i; 0 .. length) | { 233| memcpy(ptr, payload, typeInfo.size); 233| ptr += typeInfo.size; | } | } | } | else | { 50| memset(ptr, 0, size); | } | } 187| return context; | } 0000000| return null; |} | |/// |package mixin template CommonRCImpl() |{ | /// | ThisTemplate!(const T) lightConst()() scope return const @nogc nothrow @trusted @property | { return *cast(typeof(return)*) &this; } | | /// ditto | ThisTemplate!(immutable T) lightImmutable()() scope return immutable @nogc nothrow @trusted @property | { return *cast(typeof(return)*) &this; } | | /// | ThisTemplate!(const Unqual!T) moveToConst()() scope return @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-range.lst |/++ |Ranges. | |See_also: $(MREF mir,_primitives). | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko, 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-bignum-low_level_view.lst |/++ |Low-level betterC utilities for big integer arithmetic libraries. | |The module provides $(REF BigUIntAccumulator), $(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; | |version(LDC) import ldc.attributes: optStrategy; |else struct optStrategy { string opt; } | |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"; |version (D_Exceptions) |{ | package immutable hexStringException = new Exception(hexStringErrorMsg); |} | |/++ |+/ |enum WordEndian |{ | /// | little, | /// | big, |} | |version(LittleEndian) |{ | /++ | +/ | enum TargetEndian = WordEndian.little; |} |else |{ | /++ | +/ | enum TargetEndian = WordEndian.big; |} | |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); |} | |package 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); |} | |package 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"); |} | |/++ |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; 76| auto result = extMul(0x9a209a84fbcff799UL, e); 76| return cast(T) ((result.high >> 1) + ((result.low != 0) | (result.high & 1))); |} | |/// |version(mir_bignum_test) |@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, WordEndian endian = TargetEndian) | 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; | | /++ | Retrurns: signed integer view using the same data payload | +/ | BigIntView!(W, endian) signed()() @safe pure nothrow @nogc scope @property | { 8| return typeof(return)(this); | } | | /// | T opCast(T, bool wordNormalized = false, bool nonZero = false)() scope const | if (isFloatingPoint!T && isMutable!T) | { | import mir.bignum.fp; | enum md = T.mant_dig; | enum b = size_t.sizeof * 8; | enum n = md / b + (md % b != 0); | enum s = n * b; 13| return this.opCast!(Fp!s, s - md, wordNormalized, nonZero).opCast!(T, true); | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | unittest | { 2| auto a = cast(double) BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 2| assert(a == 0xa.fbbfae3cd0bp+124); 2| assert(cast(double) BigUIntView!size_t.init == 0); 2| assert(cast(double) BigUIntView!size_t([0]) == 0); | } | | /// | @safe | T opCast(T : Fp!coefficientSize, size_t internalRoundLastBits = 0, bool wordNormalized = false, bool nonZero = false, size_t coefficientSize)() scope const | if (internalRoundLastBits < size_t.sizeof * 8 && (size_t.sizeof >= W.sizeof || endian == TargetEndian)) | { | static if (isMutable!W) | { 32| return lightConst.opCast!(T, internalRoundLastBits, wordNormalized, nonZero); | } | else | static if (W.sizeof > size_t.sizeof) | { | return lightConst.opCast!(BigUIntView!size_t).opCast!(T, internalRoundLastBits, false, nonZero); | } | else | { | import mir.utility: _expect; | import mir.bitop: ctlz; 927| Fp!coefficientSize ret; 927| auto integer = lightConst; | static if (!wordNormalized) 35| integer = integer.normalized; | static if (!nonZero) 35| if (integer.coefficients.length == 0) 7| goto R; | { 920| assert(integer.coefficients.length); | enum N = ret.coefficient.data.length; 920| sizediff_t size = integer.coefficients.length * (W.sizeof * 8); 920| sizediff_t expShift = size - coefficientSize; 920| ret.exponent = expShift; 920| if (_expect(expShift <= 0, true)) | { | static if (N == 1 && W.sizeof == size_t.sizeof) | { 177| ret.coefficient.data[0] = integer.mostSignificant; | } | else | { 679| BigUIntView!size_t(ret.coefficient.data) | .opCast!(BigUIntView!(Unqual!W)) | .leastSignificantFirst | [$ - integer.coefficients.length .. $] = integer.leastSignificantFirst; | } 856| auto c = cast(uint) ctlz(ret.coefficient.view.mostSignificant); 856| ret.exponent -= c; 856| ret.coefficient = ret.coefficient.smallLeftShift(c); | } | else | { 64| UInt!(coefficientSize + size_t.sizeof * 8) holder; | | | static if (N == 1 && W.sizeof == size_t.sizeof) | { | version (BigEndian) | { | holder.data[0] = integer.mostSignificantFirst[0]; | holder.data[1] = integer.mostSignificantFirst[1]; | } | else | { 19| holder.data[0] = integer.mostSignificantFirst[1]; 19| holder.data[1] = integer.mostSignificantFirst[0]; | } | } | else | { 45| auto holderView = holder | .view | .opCast!(BigUIntView!(Unqual!W)) | .leastSignificantFirst; | import mir.utility: min; 45| auto minLength = min(integer.coefficients.length, holderView.length); 45| holderView[$ - minLength .. $] = integer.leastSignificantFirst[$ - minLength .. $]; | } | 64| auto c = cast(uint) ctlz(holder.view.mostSignificant); 64| ret.exponent -= c; 64| holder = holder.smallLeftShift(c); 64| ret.coefficient = holder.toSize!(coefficientSize, false); 64| auto tail = BigUIntView!size_t(holder.data).leastSignificant; | | bool nonZeroTail() | { 16| while(_expect(integer.leastSignificant == 0, false)) | { 0000000| integer.popLeastSignificant; 0000000| assert(integer.coefficients.length); | } 16| return integer.coefficients.length > (N + 1) * (size_t.sizeof / W.sizeof); | } | | static if (internalRoundLastBits) | { | enum half = size_t(1) << (internalRoundLastBits - 1); | enum mask0 = (size_t(1) << internalRoundLastBits) - 1; 6| auto tail0 = BigUIntView!size_t(ret.coefficient.data).leastSignificant & mask0; 6| BigUIntView!size_t(ret.coefficient.data).leastSignificant &= ~mask0; 6| auto condInc = tail0 >= half 6| && ( tail0 > half 0000000| || tail 0000000| || (BigUIntView!size_t(ret.coefficient.data).leastSignificant & 1) 0000000| || nonZeroTail); | } | else | { | enum half = cast(size_t)Signed!size_t.min; 58| auto condInc = tail >= half 42| && ( tail > half 18| || (BigUIntView!size_t(ret.coefficient.data).leastSignificant & 1) 16| || nonZeroTail); | } | 64| if (condInc) | { | enum inc = size_t(1) << internalRoundLastBits; 32| if (auto overflow = ret.coefficient += inc) | { | import mir.bignum.fp: half; 2| ret.coefficient = half!coefficientSize; 2| ret.exponent++; | } | } | } | } | R: 927| return ret; | } | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { | import mir.bignum.fp: Fp; | import mir.bignum.fixed: UInt; | 2| auto fp = cast(Fp!128) BigUIntView!ulong.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 2| assert(fp.exponent == 0); 2| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); | 2| fp = cast(Fp!128) BigUIntView!uint.fromHexString("ae3cd0aff2714a1de7022b0029d"); 2| assert(fp.exponent == -20); 2| assert(fp.coefficient == UInt!128.fromHexString("ae3cd0aff2714a1de7022b0029d00000")); | 2| fp = cast(Fp!128) BigUIntView!ushort.fromHexString("e7022b0029d"); 2| assert(fp.exponent == -84); 2| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 2| fp = cast(Fp!128) BigUIntView!ubyte.fromHexString("e7022b0029d"); 2| assert(fp.exponent == -84); 2| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 2| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("e7022b0029d"); 2| assert(fp.exponent == -84); 2| assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); | 2| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ffffffffffffffffffffffffffffffff1000000000000000"); 2| assert(fp.exponent == 64); 2| assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); | 2| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ffffffffffffffffffffffffffffffff8000000000000000"); 2| assert(fp.exponent == 65); 2| assert(fp.coefficient == UInt!128.fromHexString("80000000000000000000000000000000")); | 2| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("fffffffffffffffffffffffffffffffe8000000000000000"); 2| assert(fp.exponent == 64); 2| assert(fp.coefficient == UInt!128.fromHexString("fffffffffffffffffffffffffffffffe")); | 2| fp = cast(Fp!128) BigUIntView!size_t.fromHexString("fffffffffffffffffffffffffffffffe8000000000000001"); 2| assert(fp.exponent == 64); 2| assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); | } | | | /// | T opCast(T, bool nonZero = false)() const scope | if (isIntegral!T && isUnsigned!T && isMutable!T) | { 50| auto work = lightConst; | static if (!nonZero) | { 50| if (coefficients.length == 0) | { 2| return 0; | } | } | static if (T.sizeof <= W.sizeof) | { 22| return cast(T) work.leastSignificant; | } | else | { 26| T ret; | do | { 242| ret <<= W.sizeof * 8; 242| ret |= work.mostSignificant; 242| work.popMostSignificant; | } 242| while(work.coefficients.length); 26| return ret; | } | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigUIntView!ulong.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 2| assert(cast(ulong) view == 0x14a1de7022b0029d); 2| assert(cast(uint) view == 0x22b0029d); 2| assert(cast(ubyte) view == 0x9d); | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigUIntView!ushort.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 2| assert(cast(ulong) view == 0x14a1de7022b0029d); 2| assert(cast(uint) view == 0x22b0029d); 2| assert(cast(ubyte) view == 0x9d); | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigUIntView!uint.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 2| assert(cast(ulong) view == 0x14a1de7022b0029d); 2| assert(cast(uint) view == 0x22b0029d); 2| assert(cast(ubyte) view == 0x9d); | } | | static if (endian == TargetEndian) | /// | @trusted pure nothrow @nogc | BigUIntView!V opCast(T : BigUIntView!V, V)() scope return | if (V.sizeof <= W.sizeof) | { 766| return typeof(return)(cast(V[])this.coefficients); | } | | /// | BigUIntView!(const W, endian) lightConst()() | const @safe pure nothrow @nogc @property scope return | { 5338| return typeof(return)(coefficients); | } | ///ditto | alias lightConst this; | | /++ | +/ | sizediff_t opCmp(scope BigUIntView!(const W, endian) rhs) | const @safe pure nothrow @nogc scope | { | import mir.algorithm.iteration: cmp; 36| auto l = this.lightConst.normalized; 36| auto r = rhs.lightConst.normalized; 36| if (sizediff_t d = l.coefficients.length - r.coefficients.length) 0000000| return d; 36| return cmp(l.mostSignificantFirst, r.mostSignificantFirst); | } | | /// | bool opEquals(scope BigUIntView!(const W, endian) rhs) | const @safe pure nothrow @nogc scope | { 25| return this.coefficients == rhs.coefficients; | } | | /++ | +/ | ref inout(W) mostSignificant() inout @property scope return | { | static if (endian == WordEndian.big) 300| return coefficients[0]; | else 8325| return coefficients[$ - 1]; | } | | /++ | +/ | ref inout(W) leastSignificant() inout @property scope return | { | static if (endian == WordEndian.little) 577| return coefficients[0]; | else 143| return coefficients[$ - 1]; | } | | /++ | +/ | void popMostSignificant() scope | { | static if (endian == WordEndian.big) 10| coefficients = coefficients[1 .. $]; | else 283| coefficients = coefficients[0 .. $ - 1]; | } | | /++ | +/ | void popLeastSignificant() scope | { | static if (endian == WordEndian.little) 0000000| coefficients = coefficients[1 .. $]; | else 0000000| coefficients = coefficients[0 .. $ - 1]; | } | | /++ | +/ | BigUIntView topMostSignificantPart(size_t length) scope return | { | static if (endian == WordEndian.big) 16| return BigUIntView(coefficients[0 .. length]); | else 17| return BigUIntView(coefficients[$ - length .. $]); | } | | /++ | +/ | BigUIntView topLeastSignificantPart(size_t length) scope return | { | static if (endian == WordEndian.little) 1790| return BigUIntView(coefficients[0 .. length]); | else 219| return BigUIntView(coefficients[$ - length .. $]); | } | | /++ | Shifts left using at most `size_t.sizeof * 8 - 1` bits | +/ | void smallLeftShiftInPlace()(uint shift) scope | { 5| assert(shift < W.sizeof * 8); 5| if (shift == 0) 2| return; 3| auto csh = W.sizeof * 8 - shift; 3| auto d = leastSignificantFirst; 3| assert(d.length); 23| foreach_reverse (i; 1 .. d.length) 7| d[i] = (d[i] << shift) | (d[i - 1] >>> csh); 3| d.front <<= shift; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto a = BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 2| a.smallLeftShiftInPlace(4); 2| assert(a == BigUIntView!size_t.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); 2| a.smallLeftShiftInPlace(0); 2| assert(a == BigUIntView!size_t.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); | } | | /++ | Shifts right using at most `size_t.sizeof * 8 - 1` bits | +/ | void smallRightShiftInPlace()(uint shift) | { 2| assert(shift < W.sizeof * 8); 2| if (shift == 0) 0000000| return; 2| auto csh = W.sizeof * 8 - shift; 2| auto d = leastSignificantFirst; 2| assert(d.length); 12| foreach (i; 0 .. d.length - 1) 2| d[i] = (d[i] >>> shift) | (d[i + 1] << csh); 2| d.back >>>= shift; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto a = BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); 2| a.smallRightShiftInPlace(4); 2| assert(a == BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029")); | } | | /++ | +/ | static BigUIntView fromHexString(C, bool allowUnderscores = false)(scope const(C)[] str) | @trusted pure | if (isSomeChar!C) | { 383| auto length = str.length / (W.sizeof * 2) + (str.length % (W.sizeof * 2) != 0); 383| auto data = new Unqual!W[length]; 383| auto view = BigUIntView!(Unqual!W, endian)(data); 383| if (view.fromHexStringImpl!(C, allowUnderscores)(str)) 363| return BigUIntView(cast(W[])view.coefficients); | version(D_Exceptions) 20| 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) { 34| 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 { 964| if (_expect(str.length == 0 || str.length > coefficients.length * W.sizeof * 2, false)) 0000000| return false; | } | 516| leastSignificant = 0; 516| auto work = topLeastSignificantPart(1); 516| W current; 1032| size_t i, j; 34| static if (allowUnderscores) bool recentUnderscore; | | do | { 8914| ubyte c; 8914| switch(str[$ - ++i]) | { 4074| case '0': c = 0x0; break; 1614| case '1': c = 0x1; break; 2088| case '2': c = 0x2; break; 1059| case '3': c = 0x3; break; 882| case '4': c = 0x4; break; 807| case '5': c = 0x5; break; 648| case '6': c = 0x6; break; 1377| case '7': c = 0x7; break; 681| case '8': c = 0x8; break; 1221| case '9': c = 0x9; break; 84| case 'A': 1854| case 'a': c = 0xA; break; 140| case 'B': 1716| case 'b': c = 0xB; break; 105| case 'C': 1017| case 'c': c = 0xC; break; 100| case 'D': 1680| case 'd': c = 0xD; break; 70| case 'E': 1284| case 'e': c = 0xE; break; 229| case 'F': 4206| case 'f': c = 0xF; break; | static if (allowUnderscores) | { 178| case '_': 192| if (recentUnderscore) return false; 164| recentUnderscore = true; 164| continue; | } 0000000| default: return false; | } 8736| ++j; 660| 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 8736| W cc = cast(W)(W(c) << s); | // shift unsigned right 4 bits 8736| current >>>= 4; | // add number to top most 4 bits of current var 8736| current |= cc; 8736| if (j % (W.sizeof * 2) == 0) // is this packed var full? | { 813| work.mostSignificant = current; 813| current = 0; 813| if (_expect(work.coefficients.length < coefficients.length, true)) | { 655| work = topLeastSignificantPart(work.coefficients.length + 1); | } 158| else if (i < str.length) // if we've run out of coefficients before reaching the end of the string, error | { 0000000| return false; | } | } | } 8900| while(i < str.length); | | static if (allowUnderscores) | { | // check for a underscore at the beginning or the end 42| if (recentUnderscore || str[$ - 1] == '_') return false; | } | 496| if (current) | { 309| current >>>= 4 * (W.sizeof * 2 - j % (W.sizeof * 2)); 309| work.mostSignificant = current; | } | 496| coefficients = coefficients[0 .. (j / (W.sizeof * 2) + (j % (W.sizeof * 2) != 0))]; | 496| return true; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view = BigUIntView!size_t.fromHexString!(char, true)("abcd_efab_cdef"); 2| assert(cast(ulong)view == 0xabcd_efab_cdef); | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { | // Check that invalid underscores in hex literals throw an error. | void expectThrow(const(char)[] input) { 20| bool caught = false; | try { 20| auto view = BigUIntView!size_t.fromHexString!(char, true)(input); | } catch (Exception e) { 20| caught = true; | } | 20| assert(caught); | } | 2| expectThrow("abcd_efab_cef_"); 2| expectThrow("abcd__efab__cef"); 2| expectThrow("_abcd_efab_cdef"); 2| expectThrow("_abcd_efab_cdef_"); 2| expectThrow("_abcd_efab_cdef__"); 2| expectThrow("__abcd_efab_cdef"); 2| expectThrow("__abcd_efab_cdef_"); 2| expectThrow("__abcd_efab_cdef__"); 2| expectThrow("__abcd__efab_cdef__"); 2| expectThrow("__abcd__efab__cdef__"); | } | | 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; | 9| assert(coefficients.length); | 9| if (_expect(str.length == 0, false)) 0000000| return false; | 9| leastSignificant = 0; 9| uint d = str[0] - '0'; 9| str = str[1 .. $]; | 9| W v; 9| W t = 1; | 9| if (d == 0) | { 1| if (str.length == 0) | { 1| coefficients = null; 1| return true; | } 0000000| return false; | } | else 8| if (d >= 10) 0000000| return false; | 8| auto work = topLeastSignificantPart(1); 8| goto S; | | for(;;) | { | enum mp10 = W(10) ^^ MaxWordPow10!W; 532| d = str[0] - '0'; 532| str = str[1 .. $]; 532| if (_expect(d > 10, false)) 0000000| break; 532| v *= 10; | S: 540| t *= 10; 540| v += d; | 1052| if (_expect(t == mp10 || str.length == 0, false)) | { | L: 36| if (auto overflow = work.opOpAssign!"*"(t, v)) | { 20| if (_expect(work.coefficients.length < coefficients.length, true)) | { 20| work = topLeastSignificantPart(work.coefficients.length + 1); 20| work.mostSignificant = overflow; | } | else | { 0000000| return false; | } | } 36| v = 0; 36| t = 1; 36| if (str.length == 0) | { 8| this = work; 8| 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, endian) rhs, bool overflow = false) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { 3247| assert(this.coefficients.length > 0); 3247| assert(rhs.coefficients.length <= this.coefficients.length); 3247| auto ls = this.leastSignificantFirst; 3247| auto rs = rhs.leastSignificantFirst; | do | { 10058| bool overflowM, overflowG; 5029| ls.front = ls.front.cop!op(rs.front, overflowM).cop!op(overflow, overflowG); 5029| overflow = overflowG | overflowM; 5029| ls.popFront; 5029| rs.popFront; | } 5029| while(rs.length); 3479| if (overflow && ls.length) 32| return topMostSignificantPart(ls.length).opOpAssign!op(W(overflow)); 3215| return overflow; | } | | static if (isMutable!W && W.sizeof >= 4) | /// ditto | bool opOpAssign(string op)(scope BigIntView!(const W, endian) rhs, bool overflow = false) | @safe pure nothrow @nogc scope | if (op == "+" || op == "-") | { 8| 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)) | { 899| assert(this.coefficients.length > 0); 899| auto ns = this.leastSignificantFirst; 899| W additive = rhs; | do | { 945| bool overflow; 945| ns.front = ns.front.cop!op(additive, overflow); 945| if (!overflow) 856| return overflow; 89| additive = overflow; 89| ns.popFront; | } 89| while (ns.length); 43| 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)) | { 8| 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 | { 7413| assert(coefficients.length); 7413| auto ns = this.leastSignificantFirst; | do | { | import mir.utility: extMul; 14553| auto ext = ns.front.extMul(rhs); 14553| bool overflowM; 14553| ns.front = ext.low.cop!"+"(overflow, overflowM); 14553| overflow = ext.high + overflowM; 14553| ns.popFront; | } 14553| while (ns.length); 7413| return overflow; | } | | static if (isMutable!W && W.sizeof == 4 || W.sizeof == 8 && endian == TargetEndian) | /++ | 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 | { 82| assert(overflow < rhs); 82| assert(coefficients.length); | static if (W.sizeof == 4) | { 41| auto ns = this.mostSignificantFirst; 41| size_t i; | do | { 227| auto ext = (ulong(overflow) << 32) ^ ns[i]; 227| ns[i] = cast(uint)(ext / rhs); 227| overflow = ext % rhs; | } 227| while (++i < ns.length); 41| if (mostSignificant == 0) 34| popMostSignificant; 41| return overflow; | } | else | { 41| auto work = opCast!(BigUIntView!uint); 41| if (work.mostSignificant == 0) 17| work.popMostSignificant; 41| auto remainder = work.opOpAssign!op(rhs, overflow); 41| coefficients = coefficients[0 .. work.coefficients.length / 2 + work.coefficients.length % 2]; 41| 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 | { 38| assert(coefficients.length); 38| auto ns = this.leastSignificantFirst; | do | { 307| auto t = rhs; 307| auto overflowW = t.view *= ns.front; 307| auto overflowM = t += overflow; 307| overflowW += overflowM; 307| ns.front = cast(size_t) t; | static if (size > size_t.sizeof * 8) 20| overflow = t.toSize!(size - size_t.sizeof * 8, false).toSize!size; 307| BigUIntView!size_t(overflow.data).mostSignificant = overflowW; 307| ns.popFront; | } 307| while (ns.length); 38| return overflow; | } | | /++ | Returns: the same intger view with inversed sign | +/ | BigIntView!(W, endian) opUnary(string op : "-")() scope return | { 16| return typeof(return)(this, true); | } | | static if (isMutable!W && W.sizeof >= 4) | /++ | +/ | void bitwiseNotInPlace() scope | { 315| foreach (ref coefficient; this.coefficients) 73| 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 | { 32| assert(coefficients.length); 32| bitwiseNotInPlace(); 32| return this.opOpAssign!"+"(W(1)); | } | | /++ | Returns: a slice of coefficients starting from the least significant. | +/ | auto leastSignificantFirst() | @safe pure nothrow @nogc @property scope return | { | import mir.ndslice.slice: sliced; | static if (endian == WordEndian.little) | { 29466| return coefficients.sliced; | } | else | { | import mir.ndslice.topology: retro; 525| return coefficients.sliced.retro; | } | } | | /// | auto leastSignificantFirst() | const @safe pure nothrow @nogc @property scope return | { | import mir.ndslice.slice: sliced; | static if (endian == WordEndian.little) | { 45| return coefficients.sliced; | } | else | { | import mir.ndslice.topology: retro; 0000000| return coefficients.sliced.retro; | } | } | | /++ | Returns: a slice of coefficients starting from the most significant. | +/ | auto mostSignificantFirst() | @safe pure nothrow @nogc @property scope return | { | import mir.ndslice.slice: sliced; | static if (endian == WordEndian.big) | { 20| return coefficients.sliced; | } | else | { | import mir.ndslice.topology: retro; 147| return coefficients.sliced.retro; | } | } | | /// | auto mostSignificantFirst() | const @safe pure nothrow @nogc @property scope return | { | import mir.ndslice.slice: sliced; | static if (endian == WordEndian.big) | { 0000000| return coefficients.sliced; | } | else | { | import mir.ndslice.topology: retro; 20| return coefficients.sliced.retro; | } | } | | /++ | Strips most significant zero coefficients. | +/ | BigUIntView normalized() scope return | { 1328| auto number = this; 1328| if (number.coefficients.length) do | { | static if (endian == WordEndian.big) | { 433| if (number.coefficients[0]) 410| break; 23| number.coefficients = number.coefficients[1 .. $]; | } | else | { 940| if (number.coefficients[$ - 1]) 854| break; 86| number.coefficients = number.coefficients[0 .. $ - 1]; | } | } 109| while (number.coefficients.length); 1328| return number; | } | | ///ditto | BigUIntView!(const W, endian) normalized() scope const | { 0000000| return lightConst.normalized; | } | | /++ | +/ | bool bt()(size_t position) scope | { | import mir.ndslice.topology: bitwise; 953| assert(position < coefficients.length * W.sizeof * 8); 953| return leastSignificantFirst.bitwise[position]; | } | | /++ | +/ | size_t ctlz()() scope const @property | @safe pure nothrow @nogc | { | import mir.bitop: ctlz; 20| assert(coefficients.length); 20| auto d = mostSignificantFirst; 20| size_t ret; | do | { 25| if (auto c = d.front) | { 19| ret += ctlz(c); 19| break; | } 6| ret += W.sizeof * 8; 6| d.popFront; | } 6| while(d.length); 20| return ret; | } | | /++ | +/ | size_t cttz()() scope const @property | @safe pure nothrow @nogc | { | import mir.bitop: cttz; 45| assert(coefficients.length); 45| auto d = leastSignificantFirst; 45| size_t ret; | do | { 47| if (auto c = d.front) | { 44| ret += cttz(c); 44| break; | } 3| ret += W.sizeof * 8; 3| d.popFront; | } 3| while(d.length); 45| return ret; | } | | /// | BigIntView!(W, endian) withSign()(bool sign) | { | 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 scope 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 | { 38| foreach (d; lightConst.leastSignificantFirst) | { | static if (W.sizeof >= ulong.sizeof) | { 0000000| if (d != rhs) 0000000| return false; 0000000| rhs = 0; | } | else | { 10| if (d != (rhs & W.max)) 0000000| return false; 10| rhs >>>= W.sizeof * 8; | } | } 4| return rhs == 0; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto view2 = BigUIntView!(const(ubyte), WordEndian.big)([1, 0]); 2| assert(view2 == 256); // false 2| assert(cast(ulong)view2 == 256); // true 2| auto view = BigUIntView!(const(ubyte), WordEndian.big)([15, 255, 255]); 2| assert(view == 1048575); // false 2| assert(cast(ulong)view == 1048575); // true | } | | 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) | { 73| assert(str.length); 73| assert(str.length >= ceilLog10Exp2(coefficients.length * (W.sizeof * 8))); | 73| size_t i = str.length; 114| while(coefficients.length > 1) | { 41| uint rem = this /= 1_000_000_000; 1230| foreach (_; 0 .. 9) | { 369| str[--i] = cast(char)(rem % 10 + '0'); 369| rem /= 10; | } | } | 73| W rem = coefficients.length == 1 ? coefficients[0] : W(0); | do | { 560| str[--i] = cast(char)(rem % 10 + '0'); 560| rem /= 10; | } 560| while(rem); | 73| return str.length - i; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure @nogc | unittest | { | import mir.bignum.integer; | 2| auto a = BigInt!2("123456789098765432123456789098765432100"); 2| char[ceilLog10Exp2(a.data.length * (size_t.sizeof * 8))] buffer; 2| auto len = a.view.unsigned.toStringImpl(buffer); 2| assert(buffer[$ - len .. $] == "123456789098765432123456789098765432100"); | } |} | |/// |version(mir_bignum_test) |@safe pure nothrow |unittest |{ | import std.traits; | alias AliasSeq(T...) = T; | | foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) | foreach (endian; AliasSeq!(WordEndian.little, WordEndian.big)) | { | static if (endian == WordEndian.little) | { 4| T[3] lhsData = [1, T.max-1, 0]; 4| T[3] rhsData = [T.max, T.max, 0]; | } | else | { 4| T[3] lhsData = [0, T.max-1, 1]; 4| T[3] rhsData = [0, T.max, T.max]; | } | 8| auto lhs = BigUIntView!(T, endian)(lhsData).normalized; | | /// bool overflow = bigUInt op= scalar 8| assert(lhs.leastSignificantFirst == [1, T.max-1]); 8| assert(lhs.mostSignificantFirst == [T.max-1, 1]); | static if (T.sizeof >= 4) | { 4| assert((lhs += T.max) == false); 4| assert(lhs.leastSignificantFirst == [0, T.max]); 4| assert((lhs += T.max) == false); 4| assert((lhs += T.max) == true); // overflow bit 4| assert(lhs.leastSignificantFirst == [T.max-1, 0]); 4| assert((lhs -= T(1)) == false); 4| assert(lhs.leastSignificantFirst == [T.max-2, 0]); 4| assert((lhs -= T.max) == true); // underflow bit 4| assert(lhs.leastSignificantFirst == [T.max-1, T.max]); 4| assert((lhs -= Signed!T(-4)) == true); // overflow bit 4| assert(lhs.leastSignificantFirst == [2, 0]); 4| assert((lhs += Signed!T.max) == false); // overflow bit 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0]); | | /// bool overflow = bigUInt op= bigUInt/bigInt 4| lhs = BigUIntView!(T, endian)(lhsData); 4| auto rhs = BigUIntView!(T, endian)(rhsData).normalized; 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0, 0]); 4| assert(rhs.leastSignificantFirst == [T.max, T.max]); 4| assert((lhs += rhs) == false); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 1, 0, 1]); 4| assert((lhs -= rhs) == false); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0, 0]); 4| assert((lhs += -rhs) == true); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 3, 0, T.max]); 4| assert((lhs += -(-rhs)) == true); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0, 0]); | | /// W overflow = bigUInt *= scalar 4| assert((lhs *= T.max) == 0); 4| assert((lhs += T(Signed!T.max + 2)) == false); 4| assert(lhs.leastSignificantFirst == [0, Signed!T.max + 2, 0]); 4| lhs = lhs.normalized; 4| lhs.leastSignificantFirst[1] = T.max / 2 + 3; 4| assert(lhs.leastSignificantFirst == [0, T.max / 2 + 3]); 4| assert((lhs *= 8u) == 4); 4| assert(lhs.leastSignificantFirst == [0, 16]); | } | } |} | |/++ |Arbitrary length signed integer view. |+/ |struct BigIntView(W, WordEndian endian = TargetEndian) | 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, endian) unsigned; | | /++ | Sign bit | +/ | bool sign; | | /// | inout(W)[] coefficients() inout @property scope return | { 692| return unsigned.coefficients; | } | | /// 1286| this(W[] coefficients, bool sign = false) | { 1286| this(BigUIntView!(W, endian)(coefficients), sign); | } | | /// 1385| this(BigUIntView!(W, endian) unsigned, bool sign = false) | { 1385| this.unsigned = unsigned; 1385| 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; | 6| if (_expect(str.length == 0, false)) 0000000| return false; | 6| if (str[0] == '-') | { 4| sign = true; 4| str = str[1 .. $]; | } | else 2| if (_expect(str[0] == '+', false)) | { 0000000| str = str[1 .. $]; | } | 6| return unsigned.fromStringImpl(str); | } | | /++ | +/ | static BigIntView fromHexString(C, bool allowUnderscores = false)(scope const(C)[] str) | @trusted pure | if (isSomeChar!C) | { 20| auto length = str.length / (W.sizeof * 2) + (str.length % (W.sizeof * 2) != 0); 20| auto ret = BigIntView!(Unqual!W, endian)(new Unqual!W[length]); 20| if (ret.fromHexStringImpl!(C, allowUnderscores)(str)) 20| 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; | 31| assert(unsigned.coefficients.length); | 31| if (_expect(str.length == 0, false)) 0000000| return false; | 31| sign = false; | 31| if (str[0] == '-') | { 20| sign = true; 20| str = str[1 .. $]; | } | else 11| if (_expect(str[0] == '+', false)) | { 0000000| str = str[1 .. $]; | } | 31| return unsigned.fromHexStringImpl!(C, allowUnderscores)(str); | } | | /// | T opCast(T, bool wordNormalized = false, bool nonZero = false)() scope const | if (isFloatingPoint!T && isMutable!T) | { 7| auto ret = this.unsigned.opCast!(T, wordNormalized, nonZero); 7| if (sign) 5| ret = -ret; 7| return ret; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto a = cast(double) BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0029d"); 2| assert(a == -0xa.fbbfae3cd0bp+124); | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { 2| auto a = cast(double) BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_029d"); 2| assert(a == -0xa.fbbfae3cd0bp+124); | } | | /// | T opCast(T, bool nonZero = false)() scope const | if (is(T == long) || is(T == int)) | { 26| auto ret = this.unsigned.opCast!(Unsigned!T, nonZero); 26| if (sign) 26| ret = -ret; 26| return ret; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | 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 && endian == TargetEndian) | /// | 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 && endian == TargetEndian) | 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 && endian == TargetEndian) | version(mir_bignum_test) | @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 && endian == TargetEndian) | 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 && endian == TargetEndian) | 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); | } | | /++ | +/ | T opCast(T : Fp!coefficientSize, size_t internalRoundLastBits = 0, bool wordNormalized = false, bool nonZero = false, size_t coefficientSize)() scope const | if (internalRoundLastBits < size_t.sizeof * 8 && (size_t.sizeof >= W.sizeof || endian == TargetEndian)) | { 4| auto ret = unsigned.opCast!(Fp!coefficientSize, internalRoundLastBits, wordNormalized, nonZero); 4| ret.sign = sign; 4| return ret; | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | /// | version(mir_bignum_test) | @safe pure | unittest | { | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp; | 2| auto fp = cast(Fp!128) BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0029d"); 2| assert(fp.sign); 2| assert(fp.exponent == 0); 2| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); | } | | static if (W.sizeof == size_t.sizeof && endian == TargetEndian) | version(mir_bignum_test) | @safe pure | unittest | { | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp; | 2| auto fp = cast(Fp!128) BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_029d"); 2| assert(fp.sign); 2| assert(fp.exponent == 0); 2| assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); | } | | static if (endian == TargetEndian) | /// | BigIntView!V opCast(T : BigIntView!V, V)() scope return | if (V.sizeof <= W.sizeof) | { 1| return typeof(return)(this.unsigned.opCast!(BigUIntView!V), sign); | } | | /// | BigIntView!(const W, endian) lightConst()() scope return | const @safe pure nothrow @nogc @property | { 27| return typeof(return)(unsigned.lightConst, sign); | } | | ///ditto | alias lightConst this; | | /++ | +/ | sizediff_t opCmp(BigIntView!(const W, endian) rhs) | const @safe pure nothrow @nogc scope | { | import mir.algorithm.iteration: cmp; 1| if (auto s = rhs.sign - this.sign) | { 0000000| if (this.unsigned.coefficients.length && rhs.unsigned.coefficients.length) 0000000| return s; | } 1| auto d = this.unsigned.opCmp(rhs.unsigned); 1| return sign ? -d : d; | } | | /// | bool opEquals(BigIntView!(const W, endian) rhs) | const @safe pure nothrow @nogc scope | { 24| 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) | { 1| 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, endian) rhs, bool overflow = false) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 78| assert(rhs.coefficients.length > 0); | import mir.conv; 78| 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 78| if ((sign == rhs.sign) == sum) 16| return unsigned.opOpAssign!"+"(rhs.unsigned, overflow); | // pos -= pos | // pos += neg | // neg += pos | // neg -= neg 62| if (unsigned.opOpAssign!"-"(rhs.unsigned, overflow)) | { 24| sign = !sign; 24| unsigned.twoComplementInPlace; | } 62| return false; | } | | static if (isMutable!W && W.sizeof >= 4) | /// ditto | bool opOpAssign(string op)(scope BigUIntView!(const W, endian) rhs, bool overflow = false) | @safe pure nothrow @nogc | if (op == "+" || op == "-") | { 8| 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)) | { 8| assert(this.coefficients.length > 0); | enum sum = op == "+"; | // pos += pos | // neg += neg | // neg -= pos | // pos -= neg 8| auto urhs = cast(W) (rhs < 0 ? -rhs : rhs); 8| if ((sign == (rhs < 0)) == sum) 4| return unsigned.opOpAssign!"+"(urhs); | // pos -= pos | // pos += neg | // neg += pos | // neg -= neg 4| if (unsigned.opOpAssign!"-"(urhs)) | { 4| sign = !sign; 4| unsigned.twoComplementInPlace; | } 4| 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)) | { 20| assert(this.coefficients.length > 0); | enum sum = op == "+"; | // pos += pos | // neg -= pos 20| if ((sign == false) == sum) 12| return unsigned.opOpAssign!"+"(rhs); | // pos -= pos | // neg += pos 8| if (unsigned.opOpAssign!"-"(rhs)) | { 4| sign = !sign; 4| unsigned.twoComplementInPlace; | } 8| 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 | { 320| return unsigned.opOpAssign!op(rhs, overflow); | } | | /++ | Returns: the same intger view with inversed sign | +/ | BigIntView opUnary(string op : "-")() | { 4| return BigIntView(unsigned, !sign); | } | | /++ | Returns: a slice of coefficients starting from the least significant. | +/ | auto leastSignificantFirst() | @safe pure nothrow @nogc @property | { 300| return unsigned.leastSignificantFirst; | } | | /++ | Returns: a slice of coefficients starting from the most significant. | +/ | auto mostSignificantFirst() | @safe pure nothrow @nogc @property | { 8| return unsigned.mostSignificantFirst; | } | | /++ | Strips zero most significant coefficients. | Strips most significant zero coefficients. | Sets sign to zero if no coefficients were left. | +/ | BigIntView normalized() | { 189| auto number = this; 189| number.unsigned = number.unsigned.normalized; 189| number.sign = number.coefficients.length == 0 ? false : number.sign; 189| return number; | } | | ///ditto | BigIntView!(const W, endian) normalized() const | { 0000000| return lightConst.normalized; | } |} | |/// |version(mir_bignum_test) |@safe pure nothrow |unittest |{ | import std.traits; | alias AliasSeq(T...) = T; | | foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) | foreach (endian; AliasSeq!(WordEndian.little, WordEndian.big)) | { | static if (endian == WordEndian.little) | { 4| T[3] lhsData = [1, T.max-1, 0]; 4| T[3] rhsData = [T.max, T.max, 0]; | } | else | { 4| T[3] lhsData = [0, T.max-1, 1]; 4| T[3] rhsData = [0, T.max, T.max]; | } | 8| auto lhs = BigIntView!(T, endian)(lhsData).normalized; | | /// bool overflow = bigUInt op= scalar 8| assert(lhs.leastSignificantFirst == [1, T.max-1]); 8| assert(lhs.mostSignificantFirst == [T.max-1, 1]); | | static if (T.sizeof >= 4) | { | 4| assert((lhs += T.max) == false); 4| assert(lhs.leastSignificantFirst == [0, T.max]); 4| assert((lhs += T.max) == false); 4| assert((lhs += T.max) == true); // overflow bit 4| assert(lhs.leastSignificantFirst == [T.max-1, 0]); 4| assert((lhs -= T(1)) == false); 4| assert(lhs.leastSignificantFirst == [T.max-2, 0]); 4| assert((lhs -= T.max) == false); 4| assert(lhs.leastSignificantFirst == [2, 0]); 4| assert(lhs.sign); 4| assert((lhs -= Signed!T(-4)) == false); 4| assert(lhs.leastSignificantFirst == [2, 0]); 4| assert(lhs.sign == false); 4| assert((lhs += Signed!T.max) == false); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0]); | | /// bool overflow = bigUInt op= bigUInt/bigInt 4| lhs = BigIntView!(T, endian)(lhsData); 4| auto rhs = BigUIntView!(T, endian)(rhsData).normalized; 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0, 0]); 4| assert(rhs.leastSignificantFirst == [T.max, T.max]); 4| assert((lhs += rhs) == false); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 1, 0, 1]); 4| assert((lhs -= rhs) == false); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0, 0]); 4| assert((lhs += -rhs) == false); 4| assert(lhs.sign); 4| assert(lhs.leastSignificantFirst == [T.max - (Signed!T.max + 2), T.max, 0]); 4| assert(lhs.sign); 4| assert((lhs -= -rhs) == false); 4| assert(lhs.leastSignificantFirst == [Signed!T.max + 2, 0, 0]); 4| assert(lhs.sign == false); | } | } |} | |/// |version(mir_bignum_test) |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) |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")); |} | |/++ |An utility type to wrap a local buffer to accumulate unsigned numbers. |+/ |struct BigUIntAccumulator(W, WordEndian endian = TargetEndian) | if (is(Unqual!W == uint) || is(Unqual!W == ulong)) |{ | /++ | A group of coefficients for a $(MREF DecimalRadix)`!W`. | | The order corresponds to endianness. | | The unused part can be uninitialized. | +/ | W[] coefficients; | | /++ | Current length of initialized coefficients. | | The initialization order corresponds to endianness. | | The `view` method may return a view with empty coefficients, which isn't usable. | Put `0` or another number first to make the accumulator maintain a non-empty view. | +/ | size_t length; | | /++ | Returns: | Current unsigned integer view. | Note: | The method may return a view with empty coefficients, which isn't usable. | Put `0` or another number first to make the accumulator maintain a non-empty view. | +/ | BigUIntView!(W, endian) view() @safe pure nothrow @nogc @property | { | static if (endian == WordEndian.little) 75| return typeof(return)(coefficients[0 .. length]); | else 64| return typeof(return)(coefficients[$ - length .. $]); | } | | /++ | Returns: | True if the accumulator can accept next most significant coefficient | +/ | bool canPut() @property | { 8| return length < coefficients.length; | } | | /++ | Places coefficient to the next most significant position. | +/ | void put(W coeffecient) | in { 18| assert(length < coefficients.length); | } | do { | static if (endian == WordEndian.little) 12| coefficients[length++] = coeffecient; | else 6| coefficients[$ - ++length] = coeffecient; | } | | /++ | Strips most significant zero coefficients from the current `view`. | Note: | The `view` method may return a view with empty coefficients, which isn't usable. | Put `0` or another number first to make the accumulator maintain a non-empty view. | +/ | void normalize() | { 0000000| length = view.normalized.coefficients.length; | } | | /// | bool canPutN(size_t n) | { 3| return length + n <= coefficients.length; | } | | /// | bool approxCanMulPow5(size_t degree) | { | // TODO: more precise result | enum n = MaxWordPow5!W; 1| return canPutN(degree / n + (degree % n != 0)); | } | | /// | bool canMulPow2(size_t degree) | { | import mir.bitop: ctlz; | enum n = W.sizeof * 8; 2| return canPutN(degree / n + (degree % n > ctlz(view.mostSignificant))); | } | | /// | void mulPow5(size_t degree) | { | // assert(approxCanMulPow5(degree)); | enum n = MaxWordPow5!W; | enum wordInit = W(5) ^^ n; 1| W word = wordInit; 6| while(degree) | { 5| if (degree >= n) | { 4| degree -= n; | } | else | { 1| word = 1; 8| do word *= 5; 8| while(--degree); | } 5| if (auto overflow = view *= word) | { 4| put(overflow); | } | } | } | | /// | void mulPow2(size_t degree) scope @safe | { | import mir.bitop: ctlz; 1| assert(canMulPow2(degree)); | enum n = W.sizeof * 8; 1| auto ws = degree / n; 1| auto oldLength = length; 1| length += ws; 1| if (ws) | { 1| auto v = view.leastSignificantFirst; 13| foreach_reverse (i; 0 .. oldLength) | { 5| v[i + ws] = v[i]; | } 1| do v[--ws] = 0; 1| while(ws); | } | 1| if (auto tail = cast(uint)(degree % n)) | { 1| if (tail > ctlz(view.mostSignificant)) | { 1| put(0); 1| oldLength++; | } 1| view.topMostSignificantPart(oldLength).smallLeftShiftInPlace(tail); | } | } |} | |/// |version(mir_bignum_test) |@safe pure |unittest |{ | import std.traits; | alias AliasSeq(T...) = T; | | foreach (T; AliasSeq!(uint, ulong)) | foreach (endian; AliasSeq!(WordEndian.little, WordEndian.big)) | { 4| T[16 / T.sizeof] buffer; 4| auto accumulator = BigUIntAccumulator!(T, endian)(buffer); 4| assert(accumulator.length == 0); 4| assert(accumulator.coefficients.length == buffer.length); 4| assert(accumulator.view.coefficients.length == 0); | // needs to put a number before any operations on `.view` 4| accumulator.put(1); | // compute N factorial 4| auto N = 30; 372| foreach(i; 1 .. N + 1) | { 120| if (auto overflow = accumulator.view *= i) | { 8| if (!accumulator.canPut) 0000000| throw new Exception("Factorial buffer overflow"); 8| accumulator.put(overflow); | } | } 4| assert(accumulator.view == BigUIntView!(T, endian).fromHexString("D13F6370F96865DF5DD54000000")); | } |} | |/// Computes `13 * 10^^60` |version(mir_bignum_test) |@safe pure |unittest |{ 1| uint[7] buffer; 1| auto accumulator = BigUIntAccumulator!uint(buffer); 1| accumulator.put(13); // initial value 1| assert(accumulator.approxCanMulPow5(60)); 1| accumulator.mulPow5(60); 1| assert(accumulator.canMulPow2(60)); 1| accumulator.mulPow2(60); 1| assert(accumulator.view == BigUIntView!uint.fromHexString("81704fcef32d3bd8117effd5c4389285b05d000000000000000")); |} | |/// |enum DecimalExponentKey |{ | /// | none = 0, | /// | infinity = 1, | /// | nan = 2, | /// | dot = '.' - '0', | /// | d = 'd' - '0', | /// | e = 'e' - '0', | /// | D = 'D' - '0', | /// | E = 'E' - '0', |} | |/++ |+/ |struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t) | if (isUnsigned!W) |{ | /// | bool sign; | /// | Exp exponent; | /// | BigUIntView!(W, endian) coefficient; | | static if (isMutable!W && W.sizeof >= 4) | /++ | 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) | { | import mir.utility: _expect; | | version(LDC) | { | static if ((allowSpecialValues && allowDExponent && allowStartingPlus && allowDotOnBounds && checkEmpty) == false) | pragma(inline, true); | } | 57| assert(coefficient.coefficients.length); | 57| coefficient.leastSignificant = 0; 57| auto work = coefficient.topLeastSignificantPart(1); | | static if (checkEmpty) | { 54| if (_expect(str.length == 0, false)) 0000000| return false; | } | 57| if (str[0] == '-') | { 10| sign = true; 10| str = str[1 .. $]; 10| if (_expect(str.length == 0, false)) 0000000| return false; | } | else | static if (allowStartingPlus) | { 47| if (_expect(str[0] == '+', false)) | { 1| str = str[1 .. $]; 1| if (_expect(str.length == 0, false)) 0000000| return false; | } | } | 57| uint d = str[0] - '0'; 57| str = str[1 .. $]; | 57| W v; 57| W t = 1; 57| bool dot; | | static if (allowUnderscores) | { 54| bool recentUnderscore; | } | | static if (!allowLeadingZeros) | { | if (d == 0) | { | if (str.length == 0) | { | coefficient = coefficient.init; | return true; | } | if (str[0] >= '0' && str[0] <= '9') | return false; | goto S; | } | } | 57| if (d < 10) | { 43| goto S; | } | | static if (allowDotOnBounds) | { 14| if (d == '.' - '0') | { 4| if (str.length == 0) 1| return false; 3| key = DecimalExponentKey.dot; 3| dot = true; 3| goto F; | } | } | | static if (allowSpecialValues) | { 10| goto NI; | } | else | { | return false; | } | | F: for(;;) | { | enum mp10 = W(10) ^^ MaxWordPow10!W; 350| d = str[0] - '0'; 350| str = str[1 .. $]; | 350| if (_expect(d <= 10, true)) | { | static if (allowUnderscores) | { 270| recentUnderscore = false; | } 292| v *= 10; | S: 335| t *= 10; 335| v += d; 335| exponentShift -= dot; | 662| if (_expect(t == mp10 || str.length == 0, false)) | { | L: 42| if (auto overflow = work.opOpAssign!"*"(t, v)) | { 5| if (_expect(work.coefficients.length < coefficient.coefficients.length, true)) | { 5| work = coefficient.topLeastSignificantPart(work.coefficients.length + 1); 5| work.mostSignificant = overflow; | } | else | { 0000000| return false; | } | } | 42| v = 0; 42| t = 1; 42| if (str.length == 0) | { | M: 36| exponent += exponentShift; 36| coefficient = work.mostSignificant == 0 ? coefficient.init : work; | static if (allowUnderscores) | { 33| return !recentUnderscore; | } | else | { 3| return true; | } | } | } | 313| continue; | } | static if (allowUnderscores) | { 56| if (recentUnderscore) 5| return false; | } 53| switch (d) | { 26| case DecimalExponentKey.dot: 26| key = DecimalExponentKey.dot; 26| if (_expect(dot, false)) 2| break; 24| dot = true; 24| if (str.length) | { | static if (allowUnderscores) | { 19| recentUnderscore = true; | } 21| continue; | } | static if (allowDotOnBounds) | { 3| goto L; | } | else | { 0000000| return false; | } | static if (allowExponent) | { | static if (allowDExponent) | { 1| case DecimalExponentKey.d: 2| case DecimalExponentKey.D: 2| goto case DecimalExponentKey.e; | } 10| case DecimalExponentKey.e: 12| case DecimalExponentKey.E: | import mir.parse: parse; 12| key = cast(DecimalExponentKey)d; 23| if (parse(str, exponent) && str.length == 0) | { 11| if (t != 1) 9| goto L; 2| goto M; | } 1| break; | } | static if (allowUnderscores) | { 15| case '_' - '0': 15| recentUnderscore = true; 15| if (str.length) 13| continue; 2| break; | } 0000000| default: | } 5| break; | } 5| return false; | | static if (allowSpecialValues) | { | NI: 10| exponent = exponent.max; 10| if (str.length == 2) | { 7| auto stail = cast(C[2])str[0 .. 2]; 17| if (d == 'i' - '0' && stail == cast(C[2])"nf" || d == 'I' - '0' && (stail == cast(C[2])"nf" || stail == cast(C[2])"NF")) | { 4| coefficient = coefficient.init; 4| key = DecimalExponentKey.infinity; 4| return true; | } 9| if (d == 'n' - '0' && stail == cast(C[2])"an" || d == 'N' - '0' && (stail == cast(C[2])"aN" || stail == cast(C[2])"AN")) | { 3| coefficient.leastSignificant = 1; 3| coefficient = coefficient.topLeastSignificantPart(1); 3| key = DecimalExponentKey.nan; 3| return true; | } | } 3| return false; | } | } | | /// | DecimalView!(const W, endian, Exp) lightConst()() | const @safe pure nothrow @nogc @property | { | return typeof(return)(sign, exponent, coefficient.lightConst); | } | ///ditto | alias lightConst this; | | /++ | +/ | BigIntView!(W, endian) signedCoefficient() | { 0000000| return typeof(return)(coefficient, sign); | } | | /++ | 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, bool nonZero = false)() scope const | if (isFloatingPoint!T && isMutable!T) | { | version(LDC) | { | static if (wordNormalized) | pragma(inline, true); | } | | import mir.bignum.fixed: UInt; | import mir.bignum.fp: Fp, extendedMul; | import mir.bignum.internal.dec2flt_table; | import mir.math.common: floor; | import mir.utility: _expect; | 1009| auto coeff = coefficient.lightConst; 1009| T ret = 0; | | static if (!wordNormalized) 1009| coeff = coeff.normalized; | 1009| if (_expect(exponent == exponent.max, false)) | { 10| ret = coeff.coefficients.length ? T.nan : T.infinity; 10| goto R; | } | | static if (!nonZero) 999| if (coeff.coefficients.length == 0) 44| goto R; | | enum S = 9; | | static if (T.mant_dig < 64) | { | Fp!64 load(Exp e) | { 590| auto p10coeff = p10_coefficients[cast(sizediff_t)e - min_p10_e][0]; 590| auto p10exp = p10_exponents[cast(sizediff_t)e - min_p10_e]; 590| return Fp!64(false, p10exp, UInt!64(p10coeff)); | } | { 646| auto expSign = exponent < 0; 646| if (_expect((expSign ? -exponent : exponent) >>> S == 0, true)) | { | enum ulong mask = (1UL << (64 - T.mant_dig)) - 1; | enum ulong half = (1UL << (64 - T.mant_dig - 1)); | enum ulong bound = ulong(1) << T.mant_dig; | 590| auto c = coeff.opCast!(Fp!64, 0, true, true); 590| auto z = c.extendedMul(load(exponent)); 590| ret = cast(T) z; 590| auto slop = (coeff.coefficients.length > (ulong.sizeof / W.sizeof)) + 3 * expSign; 590| long bitsDiff = (cast(ulong) z.opCast!(Fp!64).coefficient & mask) - half; 590| if (_expect((bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) 548| goto R; 84| if (slop == 0 && exponent <= MaxWordPow5!ulong || exponent == 0) 14| goto R; 49| if (slop == 3 && MaxFpPow5!T >= -exponent && cast(ulong)c.coefficient < bound) | { 0000000| auto e = load(-exponent); 0000000| ret = c.opCast!(T, true) / cast(T) (cast(ulong)e.coefficient >> e.exponent); 0000000| goto R; | } 28| ret = algoR!T(ret, coeff, cast(int) exponent); 28| goto R; | } 56| ret = expSign ? 0 : T.infinity; 56| goto R; | } | } | else | { | enum P = 1 << S; | static assert(min_p10_e <= -P); | static assert(max_p10_e >= P); | Fp!128 load(Exp e) | { 323| auto idx = cast(sizediff_t)e - min_p10_e; 323| ulong h = p10_coefficients[idx][0]; 323| ulong l = p10_coefficients[idx][1]; 323| if (l >= cast(ulong)long.min) 99| h--; | version(BigEndian) | auto p10coeff = UInt!128(cast(ulong[2])[h, l]); | else 323| auto p10coeff = UInt!128(cast(ulong[2])[l, h]); 323| auto p10exp = p10_exponents[idx] - 64; 323| return Fp!128(false, p10exp, p10coeff); | } | | { 309| auto expSign = exponent < 0; 309| Unsigned!Exp exp = exponent; 309| exp = expSign ? -exp : exp; 309| if (exp >= 5000) | { 7| ret = expSign ? 0 : T.infinity; 7| goto R; | } 302| Exp index = exp & 0x1FF; 302| bool gotoAlgoR; 302| auto c = load(expSign ? -index : index); | { 302| exp >>= S; 302| gotoAlgoR = exp != 0; 302| if (_expect(gotoAlgoR, false)) | { 21| auto v = load(expSign ? -P : P); | do | { 63| if (exp & 1) 35| c *= v; 63| exp >>>= 1; 63| if (exp == 0) 21| break; 42| v *= v; | } 42| while(true); | } | } 302| auto z = coeff.opCast!(Fp!128, 0, true, true).extendedMul(c); 302| ret = cast(T) z; 302| if (!gotoAlgoR) | { | static if (T.mant_dig == 64) | enum ulong mask = ulong.max; | else | enum ulong mask = (1UL << (128 - T.mant_dig)) - 1; | enum ulong half = (1UL << (128 - T.mant_dig - 1)); | enum UInt!128 bound = UInt!128(1) << T.mant_dig; | 281| auto slop = (coeff.coefficients.length > (ulong.sizeof * 2 / W.sizeof)) + 3 * expSign; 281| long bitsDiff = (cast(ulong) z.opCast!(Fp!128).coefficient & mask) - half; 281| if (_expect((bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) 232| goto R; 98| if (slop == 0 && exponent <= 55 || exponent == 0) 42| goto R; 21| if (slop == 3 && MaxFpPow5!T >= -exponent && c.coefficient < bound) | { 0000000| auto e = load(-exponent); 0000000| ret = c.opCast!(T, true) / cast(T) e; 0000000| goto R; | } | } 28| ret = algoR!T(ret, coeff, cast(int) exponent); 28| goto R; | } | } | R: 1009| if (sign) 51| ret = -ret; 1009| return ret; | } |} | |@optStrategy("minsize") |package T algoR(T, W, WordEndian endian)(T ret, scope BigUIntView!(const W, endian) coeff, int exponent) |{ | pragma(inline, false); | | import mir.bignum.fixed: UInt; | import mir.bignum.integer: BigInt; | import mir.math.common: floor; | import mir.math.ieee: ldexp, frexp, nextDown, nextUp; | import mir.utility: _expect; | 112| BigInt!256 x = void, y = void; // max value is 2^(2^14)-1 56| if (exponent >= 0) | { 0000000| if (!x.copyFrom(coeff) && !x.mulPow5(exponent)) // if no overflow 0000000| ret = ldexp(cast(T) x, exponent); | } | else do | { 56| if (ret < ret.min_normal) 14| break; 42| int k; 42| auto m0 = frexp(ret, k); 42| k -= T.mant_dig; | static if (T.mant_dig <= 64) | { | enum p2 = T(2) ^^ T.mant_dig; 42| auto m = UInt!64(cast(ulong) (m0 * p2)); | } | else | { | enum p2h = T(2) ^^ (T.mant_dig - 64); | enum p2l = T(2) ^^ 64; | m0 *= p2h; | auto mhf = floor(m0); | auto mh = cast(ulong) mhf; | m0 -= mhf; | m0 *= p2l; | auto ml = cast(ulong) m0; | auto m = UInt!128(mh); | m <<= 64; | m |= UInt!128(ml); | } 42| auto mtz = m.cttz; 42| if (mtz != m.sizeof * 8) | { 42| m >>= mtz; 42| k += mtz; | } | 42| if (x.copyFrom(coeff)) // if overflow 0000000| break; 42| y.__ctor(m); 42| y.mulPow5(-exponent); 42| auto shift = k - exponent; 42| (shift >= 0 ? y : x) <<= shift >= 0 ? shift : -shift; 42| x -= y; 42| if (x.length == 0) 7| break; 35| x <<= 1; 35| x *= m; 35| auto cond = mtz == T.mant_dig - 1 && x.sign; 35| auto cmp = x.view.unsigned.opCmp(y.view.unsigned); 35| if (cmp < 0) | { 35| if (!cond) 35| break; 0000000| x <<= 1; 0000000| if (x.view.unsigned <= y.view.unsigned) 0000000| break; | } | else 0000000| if (!cmp && !cond && !mtz) 0000000| break; 0000000| ret = x.sign ? nextDown(ret) : nextUp(ret); 0000000| if (ret == 0) 0000000| break; | } 0000000| while (T.mant_dig >= 64 && exponent < -512); 56| return ret; |} | |/// |version(mir_bignum_test) |unittest |{ | alias AliasSeq(T...) = T; | | foreach (T; AliasSeq!(float, double, real)) | {{ 3| T value = 3.518437208883201171875E+013; | | }} | | foreach(E; AliasSeq!(WordEndian.little, WordEndian.big)) | foreach(W; AliasSeq!(ulong, uint, ushort, ubyte)) | static if (!(E != TargetEndian && (W.sizeof > size_t.sizeof || W.sizeof == 1))) | {{ | alias Args = AliasSeq!(W, E); | 7| auto view = DecimalView!Args(false, -8, BigUIntView!Args.fromHexString("BEBC2000000011E1A3")); | 7| assert (cast(float)view == 3.518437208883201171875E+013f); 7| assert (cast(double)view == 3.518437208883201171875E+013); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 3.518437208883201171875E+013L); | 7| view = DecimalView!Args(true, -169, BigUIntView!Args.fromHexString("5A174AEDA65CC")); 7| assert (cast(float)view == -0); 7| assert (cast(double)view == -0x1.1p-511); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == -0x8.80000000000019fp-514L); | 7| view = DecimalView!Args(true, 293, BigUIntView!Args.fromHexString("36496F6C4ED38")); 7| assert (cast(float)view == -float.infinity); 7| assert (cast(double)view == -9.55024478104888e+307); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == -9.55024478104888e+307L); | 7| view = DecimalView!Args(false, 0, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 1); 7| assert (cast(double)view == 1); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 1L); | 7| view = DecimalView!Args(false, -5, BigUIntView!Args.fromHexString("3")); 7| assert (cast(float)view == 3e-5f); 7| assert (cast(double)view == 3e-5); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 3e-5L); | 7| view = DecimalView!Args(false, -1, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0.1f); 7| assert (cast(double)view == 0.1); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0.1L); | 7| view = DecimalView!Args(false, 0, BigUIntView!Args.fromHexString("3039")); 7| assert (cast(float)view == 12345.0f); 7| assert (cast(double)view == 12345.0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 12345.0L); | 7| view = DecimalView!Args(false, -7, BigUIntView!Args.fromHexString("98967F")); 7| assert (cast(float)view == 0.9999999f); 7| assert (cast(double)view == 0.9999999); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0.9999999L); | 7| view = DecimalView!Args(false, -324, BigUIntView!Args.fromHexString("4F0CEDC95A718E")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 2.2250738585072014e-308); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 2.2250738585072014e-308L); | 7| view = DecimalView!Args(false, 0, BigUIntView!Args.fromHexString("1FFFFFFFFFFFFFFFD")); 7| assert (cast(float)view == 36893488147419103229f); 7| assert (cast(double)view == 36893488147419103229.0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x1FFFFFFFFFFFFFFFDp0L); | 7| view = DecimalView!Args(false, -33, BigUIntView!Args.fromHexString("65")); 7| assert (cast(float)view == 101e-33f); 7| assert (cast(double)view == 101e-33); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 101e-33L); | 7| view = DecimalView!Args(false, 23, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 1e23f); 7| assert (cast(double)view == 1e23); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 1e23L); | 7| view = DecimalView!Args(false, 23, BigUIntView!Args.fromHexString("81B")); 7| assert (cast(float)view == 2075e23f); 7| assert (cast(double)view == 0xaba3d58a1f1a98p+32); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xaba3d58a1f1a9cp+32L); | 7| view = DecimalView!Args(false, -23, BigUIntView!Args.fromHexString("2209")); 7| assert (cast(float)view == 8713e-23f); 7| assert (cast(double)view == 0x1.9b75b4e7de2b9p-64); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xc.dbada73ef15c401p-67L); | 7| view = DecimalView!Args(false, 300, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == 0x1.7e43c8800759cp+996); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xb.f21e44003acdd2dp+993L); | 7| view = DecimalView!Args(false, 245, BigUIntView!Args.fromHexString("B3A73CEB227")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == 0x1.48e3735333cb6p+857); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xa.471b9a999e5b01ep+854L); | 7| view = DecimalView!Args(false, 0, BigUIntView!Args.fromHexString("88BF4748507FB9900ADB624CCFF8D78897DC900FB0460327D4D86D327219")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == 0x1.117e8e90a0ff7p+239); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.8bf4748507fb99p+236L); | 7| view = DecimalView!Args(false, -324, BigUIntView!Args.fromHexString("5")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0x0.0000000000001p-1022); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.18995ce7aa0e1b2p-1077L); | 7| view = DecimalView!Args(false, -324, BigUIntView!Args.fromHexString("5B")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0x0.0000000000012p-1022); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x9.3594d9adeb09a55p-1073L); | 7| view = DecimalView!Args(false, -322, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0x0.0000000000014p-1022); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xa.1ebfb4219491a1fp-1073L); | 7| view = DecimalView!Args(false, -320, BigUIntView!Args.fromHexString("CA1CCB")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0x0.000063df832d9p-1022); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xc.7bf065b215888c7p-1043L); | 7| view = DecimalView!Args(false, -319, BigUIntView!Args.fromHexString("33CE7943FB")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0x1.000000000162p-1022); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.000000000b103b6p-1025L); | 7| view = DecimalView!Args(false, -309, BigUIntView!Args.fromHexString("15")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0x0.f19c2629ccf53p-1022); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xf.19c2629ccf52fc4p-1026L); | 7| view = DecimalView!Args(false, -340, BigUIntView!Args.fromHexString("AF87023B9BF0EE")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0x0.0000000000001p-1022); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xf.fffffffffffff64p-1078L); | 7| view = DecimalView!Args(false, 400, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == double.infinity); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xd.a763fc8cb9ff9e6p+1325L); | 7| view = DecimalView!Args(false, 309, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == double.infinity); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xb.201833b35d63f73p+1023L); | 7| view = DecimalView!Args(false, 308, BigUIntView!Args.fromHexString("2")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == double.infinity); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.e679c2f5e44ff8fp+1021L); | 7| view = DecimalView!Args(false, 308, BigUIntView!Args.fromHexString("2")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == double.infinity); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.e679c2f5e44ff8fp+1021L); | 7| view = DecimalView!Args(false, 295, BigUIntView!Args.fromHexString("1059949B7090")); 7| assert (cast(float)view == float.infinity); 7| assert (cast(double)view == double.infinity); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.00000000006955ap+1021L); | 7| view = DecimalView!Args(false, 0, BigUIntView!Args.fromHexString("0")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0L); | 7| view = DecimalView!Args(false, 0, BigUIntView!Args.init); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0L); | 7| view = DecimalView!Args(false, -325, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xa.5ced43b7e3e9188p-1083L); | 7| view = DecimalView!Args(false, -326, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.4a57695fe98746dp-1086L); | 7| view = DecimalView!Args(false, -500, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.33ada2003db9a8p-1664L); | 7| view = DecimalView!Args(false, -1000, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.68a9188a89e1467p-3325L); | 7| view = DecimalView!Args(false, -4999, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0L); | 7| view = DecimalView!Args(false, -10000, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0L); | 7| view = DecimalView!Args(false, -4969, BigUIntView!Args.fromHexString("329659A941466C6B")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == real.min_normal * real.epsilon); | 7| view = DecimalView!Args(false, -15, BigUIntView!Args.fromHexString("525DB0200FFAB")); 7| assert (cast(float)view == 1.448997445238699f); 7| assert (cast(double)view == 0x1.72f17f1f49aadp+0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xb.978bf8fa4d56cp-3L); | 7| view = DecimalView!Args(false, -15, BigUIntView!Args.fromHexString("525DB0200FFAB")); 7| assert (cast(float)view == 1.448997445238699f); 7| assert (cast(double)view == 0x1.72f17f1f49aadp+0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xb.978bf8fa4d56cp-3L); | 7| view = DecimalView!Args(false, -325, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xa.5ced43b7e3e9188p-1083L); | 7| view = DecimalView!Args(false, -326, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0); 7| assert (cast(double)view == 0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8.4a57695fe98746dp-1086L); | 7| view = DecimalView!Args(false, 0, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 1); 7| assert (cast(double)view == 0x1p+0); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0x8p-3L); | 7| view = DecimalView!Args(false, -5, BigUIntView!Args.fromHexString("3")); 7| assert (cast(float)view == 3e-5f); 7| assert (cast(double)view == 0x1.f75104d551d69p-16); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xf.ba8826aa8eb4635p-19L); | 7| view = DecimalView!Args(false, -1, BigUIntView!Args.fromHexString("1")); 7| assert (cast(float)view == 0.1f); 7| assert (cast(double)view == 0x1.999999999999ap-4); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xc.ccccccccccccccdp-7L); | 7| view = DecimalView!Args(false, -7, BigUIntView!Args.fromHexString("98967F")); 7| assert (cast(float)view == 0.9999999f); 7| assert (cast(double)view == 0x1.fffffca501acbp-1); | static if (real.mant_dig >= 64) 7| assert (cast(real)view == 0xf.ffffe5280d65435p-4L); | }} |} | |/++ |+/ |struct BinaryView(W, WordEndian endian = TargetEndian, Exp = int) |{ | /// | bool sign; | /// | Exp exponent; | /// | BigUIntView!(W, endian) coefficient; | | /// | DecimalView!(const W, endian, Exp) lightConst()() | const @safe pure nothrow @nogc @property | { | return typeof(return)(sign, exponent, coefficient.lightConst); | } | ///ditto | alias lightConst this; | | /++ | +/ | BigIntView!(W, endian) signedCoefficient() | { | return typeof(return)(sign, coefficients); | } |} source/mir/bignum/low_level_view.d is 93% covered <<<<<< EOF # path=./source-mir-math-func-expdigamma.lst |/** |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) | |Authors: Ilya Yaroshenko |*/ |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-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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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; | |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)() scope return const @property | if (dimension == 0) | { | return _grid.lightConst.sliced(_data._lengths[0]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() scope return 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)(RefTuple!(size_t, X)(this.findInterval(x), x)); | } | | /// | auto opCall(X)(RefTuple!(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 83% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko |+/ |module mir.small_array; | |import mir.serde: serdeProxy; | |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 std.traits: Unqual, isIterable, isImplicitlyConvertible; | | static if (isImplicitlyConvertible!(const T, T)) | alias V = const T; | else | alias V = T; | | /// | @serdeProxy!(V[]) | struct SmallArray | { | uint _length; | T[maxLength] _data; | | /// | alias serdeKeysProxy = T; | | @safe pure @nogc: | | /// Constructor 0000000| this(typeof(null)) | { | } | | /// ditto 2| this(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; 1| if (_length == maxLength) | { 0000000| version(D_Exceptions) throw exception; | else assert(0, errorMsg); | } 1| _data[_length++] = move(elem); 1| 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 return scope | { 15| return _data[0 .. _length]; | } | | /// | ref inout(T) opIndex(size_t index) inout scope return | { 1| return opIndex[index]; | } | | const: | | /// | bool empty() @property | { 3| return _length == 0; | } | | /// | size_t length() @property | { 0000000| return _length; | } | | /// Hash implementation | size_t toHash() | { 0000000| return hashOf(opIndex); | } | | /// Comparisons operator overloads | bool opEquals(ref const SmallArray rhs) | { 0000000| return opIndex == rhs.opIndex; | } | | /// ditto | bool opEquals(SmallArray rhs) | { 0000000| return opIndex == rhs.opIndex; | } | | /// ditto | bool opEquals()(V[] array) | { 7| return opIndex == array; | } | | /// ditto | bool opEquals(uint rhsMaxLength)(auto ref SmallArray!(T, rhsMaxLength) array) | { 1| return opIndex == array.opIndex; | } | | /// ditto | auto opCmp()(V[] array) | { 3| return __cmp(opIndex, array); | } | | /// ditto | auto opCmp(uint rhsMaxLength)(auto ref 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!!", s8[]); | 1| s16 = s8; 1| assert(s16 == "Hellow!!", s16[]); 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 73% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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_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_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_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_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_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_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-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; 2| return RightOp!(op, LightConstOf!T)(value.lightConst); | } | | auto lightImmutable()() immutable @property | { | import mir.qualifier; | return RightOp!(op, LightImmutableOf!T)(value.lightImmutable); | } | 8| this()(ref T v) { value = v; } 68| 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 | { 62| 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); | } | 124| 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 | { 1518| 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_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) |{ 1313| size_t length = lengths[0]; | foreach (i; Iota!(1, N)) 663| length *= lengths[i]; 1313| return length; |} | |pure nothrow version(mir_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(args...) |{ | static if (args.length == 0) | enum frontOf = args; | else | { | @optmath @property auto ref ls()() | { | return args[0].front; | } | alias frontOf = AliasSeq!(ls, frontOf!(args[1..$])); | } |} | |package(mir) template backOf(args...) |{ | static if (args.length == 0) | enum backOf = args; | else | { | @optmath @property auto ref ls()() | { | return args[0].back; | } | alias backOf = AliasSeq!(ls, backOf!(args[1..$])); | } |} | |package(mir) template frontOfD(size_t dimension, args...) |{ | static if (args.length == 0) | enum frontOfD = args; | else | { | @optmath @property auto ref ls()() | { | return args[0].front!dimension; | } | alias frontOfD = AliasSeq!(ls, 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 ls()() | { | return args[0].back!dimension; | } | alias backOfD = AliasSeq!(ls, 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 ls() | { | return arg.front!dim; | } | alias frontOfDim = AliasSeq!(ls, 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 ls()() | { | return arg.lightScope.selectFront!0(input); | } | alias selectFrontOf = AliasSeq!(ls, 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 ls()() | { | return arg.selectBack!0(input); | } | alias selectBackOf = AliasSeq!(ls, 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 ls()() | { | return arg.lightScope.front.selectFront!0(input); | } | alias frontSelectFrontOf = AliasSeq!(ls, 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 ls()() | { | return arg.lightScope.front.selectBack!0(input); | } | alias frontSelectBackOf = AliasSeq!(ls, frontSelectBackOf!(input, args[1..$])); | } |} source/mir/ndslice/internal.d is 71% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |$(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_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_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) |{ 6| 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_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_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)(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)); 4344| size_t[N] _lengths; | foreach (i; Iota!N) 8174| _lengths[i] = lengths[i]; 4344| ptrdiff_t[1] _strides = 0; | static if (isDynamicArray!Iterator) | { 552| assert(lengthsProduct(_lengths) <= iterator.length, | "array length should be greater or equal to the product of constructed ndslice lengths"); 552| auto ptr = iterator.length ? &iterator[0] : null; 552| return Slice!(typeof(C.init[0])*, N)(_lengths, ptr); | } | else | { | // break safety 3792| if (false) | { | ++iterator; | --iterator; | iterator += 34; | iterator -= 34; | } | import core.lifetime: move; 3792| return Slice!(C, N)(_lengths, iterator.move); | } |} | |/// Random access range primitives for slices over user defined types |@safe pure nothrow @nogc version(mir_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); 34642| return Slice!(T*)([array.length], array.ptr); |} | |/// Creates a slice from an array. |@safe pure nothrow version(mir_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_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) 59| assert(lengths.lengthsProduct <= field.length, "Length product should be less or equal to the field length."); 65| return FieldIterator!Field(0, field).sliced(lengths); |} | |///ditto |auto slicedField(Field)(Field field) | if(hasLength!Field) |{ 18| return .slicedField(field, field.length); |} | |/// Creates an 1-dimensional slice over a field, array, or random access range. |@safe @nogc pure nothrow version(mir_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) | { 20| auto shape = field.shape; 192| foreach (i; 0 .. N) 44| assert(lengths[i] <= shape[i], "Lengths should fit into ndfield's shape."); | } | import mir.ndslice.topology: indexed, ndiota; 20| return indexed(field, ndiota(lengths)); |} | |///ditto |auto slicedNdField(ndField)(ndField field) | if(hasShape!ndField) |{ 20| 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 = args[0]; | alias Arg = typeof(arg); | static if(!isDynamicArray!Arg) | { | static if(!is(LightScopeOf!Arg == Arg)) | @optmath @property ls()() | { | import mir.qualifier: lightScope; | return lightScope(arg); | } | else alias ls = arg; | } | else alias ls = arg; | alias allLightScope = AliasSeq!(ls, 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 |------- |+/ 1121|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 = 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) | { 7712| 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; 46306| assert(_indices[E] < _lengths[E], indexError!(E, N)); 46306| ptrdiff_t ball = this._stride!E; 46306| ptrdiff_t stride = _indices[E] * ball; | foreach_reverse (i; Iota!E) //static | { 608| ball *= _lengths[i + 1]; 608| assert(_indices[i] < _lengths[i], indexError!(i, N)); 608| 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; 3522| assert(_indices[E] < _lengths[E], indexError!(E, N)); 3522| 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]; | } | } 49838| 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_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_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()() scope return @property | { 2016| auto ret = Slice!(LightScopeOf!Iterator, N, kind, staticMap!(LightScopeOf, Labels)) | (_structure, .lightScope(_iterator)); | foreach(i; Iota!L) 4| ret._labels[i] = .lightScope(_labels[i]); 2016| return ret; | } | | /// ditto | auto lightScope()() scope const return @property | { 1121| auto ret = Slice!(LightConstOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightConstOfLightScopeOf, Labels)) | (_structure, .lightScope(_iterator)); | foreach(i; Iota!L) | ret._labels[i] = .lightScope(_labels[i]); 1121| return ret; | } | | /// ditto | auto lightScope()() scope immutable return @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()() scope return 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()() scope return const @property @trusted | { 2149| auto ret = typeof(return)(_structure, .lightConst(_iterator)); | foreach(i; Iota!L) 2| ret._labels[i] = .lightConst(_labels[i]); 2149| return ret; | } | | /// ditto | Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightConst()() scope return 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) | { 206| 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()() scope return immutable @trusted pure nothrow @nogc | { 1| return Slice!(ImmutableOfUnqualOfPointerTarget!Iterator, N, kind, staticMap!(ImmutableOfUnqualOfPointerTarget, Labels)) | (_structure, _iterator, _labels); | } | | /// ditto | auto toConst()() scope return 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_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_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 scope return @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() scope return inout @property | { 403| return _iterator._iterator; | } | } | | /++ | Field (array) data. | Returns: | Raw data slice. | Constraints: | Field is defined only for contiguous slices. | +/ | auto field()() scope return @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]))) | { 893| return _iterator[size_t(0) .. elementCount]; | } | else | { | import mir.ndslice.topology: flattened; | return this.flattened; | } | } | | /// ditto | auto field()() scope const return @trusted @property | { 2| return this.lightConst.field; | } | | /// ditto | auto field()() scope immutable return @trusted @property | { 2| return this.lightImmutable.field; | } | | static if (doUnittest) | /// | @safe version(mir_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 | { 3326| return _lengths[0 .. N]; | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_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_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 | { 865| typeof(return) ret; | static if (kind == Canonical) | { | foreach (i; Iota!S) 123| ret[i] = _strides[i]; 110| ret[$-1] = 1; | } | else | { 755| ret[$ - 1] = _stride!(N - 1); | foreach_reverse (i; Iota!(N - 1)) 470| ret[i] = ret[i + 1] * _lengths[i + 1]; | } 865| return ret; | } | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow | version(mir_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_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_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_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_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_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()() scope return inout @property | { 6| return this; | } | | static if (doUnittest) | /// Save range | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; 2| auto slice = iota(2, 3).save; | } | | static if (doUnittest) | /// Pointer type. | @safe pure nothrow version(mir_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) | { 41455| return _lengths[dimension]; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_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) | { 402| return _strides[dimension]; | } | else | static if (dimension + 1 == N) | { 56248| return 1; | } | else | { 2121| size_t ball = _lengths[$ - 1]; | foreach_reverse(i; Iota!(dimension + 1, N - 1)) 48| ball *= _lengths[i]; 2121| return ball; | } | | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_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_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) | { 257139| return _lengths[dimension] == 0; | } | | static if (N == 1) | { | ///ditto | auto ref front(size_t dimension = 0)() scope return @trusted @property | if (dimension == 0) | { 162447| assert(!empty!dimension); 162447| return *_iterator; | } | | ///ditto | auto ref front(size_t dimension = 0)() scope return @trusted @property const | if (dimension == 0) | { 12| assert(!empty!dimension); 12| return *_iterator.lightScope; | } | | ///ditto | auto ref front(size_t dimension = 0)() scope return @trusted @property immutable | if (dimension == 0) | { | assert(!empty!dimension); | return *_iterator.lightScope; | } | } | else | { | /// ditto | Element!dimension front(size_t dimension = 0)() scope return @property | if (dimension < N) | { 3475| typeof(return)._Structure structure_ = typeof(return)._Structure.init; | | foreach (i; Iota!(typeof(return).N)) | { | enum j = i >= dimension ? i + 1 : i; 3719| 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]; | } | 3475| return typeof(return)(structure_, _iterator); | } | | ///ditto | auto front(size_t dimension = 0)() scope return @trusted @property const | if (dimension < N) | { | assert(!empty!dimension); | return this.lightConst.front!dimension; | } | | ///ditto | auto front(size_t dimension = 0)() scope return @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) scope return @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)() scope return @trusted @property | if (dimension < N) | { 7691| assert(!empty!dimension); 7691| return _iterator[backIndex]; | } | else | auto ref Element!dimension | back(size_t dimension = 0)() scope return @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) scope return @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 | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { 138715| assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0."); 138715| _lengths[dimension]--; | static if ((kind == Contiguous || kind == Canonical) && dimension + 1 == N) 125779| ++_iterator; | else | static if (kind == Canonical || kind == Universal) 10960| _iterator += _strides[dimension]; | else 1976| _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_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) | { 726| 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()() scope return @trusted @property | { 5| assert(!anyEmpty); 5| return *_iterator; | } | | static if (isMutable!DeepElement && !hasAccessByRef) | ///ditto | auto ref first(T)(T value) scope return @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_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 scope return @property | { 1| assert(!anyEmpty); 1| return _iterator[lastIndex]; | } | | static if (isMutable!DeepElement && !hasAccessByRef) | ///ditto | auto ref last(T)(T value) @trusted scope return @property | { | assert(!anyEmpty); | return _iterator[lastIndex] = value; | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_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_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_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 @property scope const | { | static if (isInstanceOf!(SliceIterator, Iterator)) | { | import mir.ndslice.topology: unpack; 22| return this.lightScope.unpack.anyRUEmpty; | } | else 1231| 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 | { 2121| return _lengths[0 .. N].anyEmptyShape; | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_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) scope return | { | foreach (i; Iota!N) 24| index[i] = _lengths[i] - index[i]; 12| return this[index]; | } | | /// ditto | auto ref backward()(size_t[N] index) scope return const | { | return this.lightConst.backward(index); | } | | /// ditto | auto ref backward()(size_t[N] index) scope return const | { | return this.lightConst.backward(index); | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_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 | { 5702| size_t len = 1; | foreach (i; Iota!N) 11763| len *= _lengths[i]; 5702| return len; | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_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_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_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) scope return | { | 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_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) scope return | { | 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_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; 170| return equal(this.lightScope, rslice.lightScope); | } | } | | /// ditto | bool opEquals(T)(scope const(T)[] arr) @trusted scope const | { 1857| auto slice = this.lightConst; 1839| if (slice.length != arr.length) 2| return false; 1837| if (arr.length) do | { 5818| if (slice.front != arr[0]) 6| return false; 5797| slice.popFront; 5797| arr = arr[1 .. $]; | } 5797| while (arr.length); 1831| return true; | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_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_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 | { 1793| 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."; 1793| assert(j <= _lengths[dimension], | "Slice.opSlice!" ~ dimension.stringof ~ errorMsg); | } | do | { 1793| return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /++ | $(BOLD Fully defined index) | +/ | auto ref opIndex()(size_t[N] _indices...) scope return @trusted | { 25727| return _iterator[indexStride(_indices)]; | } | | /// ditto | auto ref opIndex()(size_t[N] _indices...) scope return const @trusted | { | static if (is(typeof(_iterator[indexStride(_indices)]))) 6544| return _iterator[indexStride(_indices)]; | else | return .lightConst(.lightScope(_iterator))[indexStride(_indices)]; | } | | /// ditto | auto ref opIndex()(size_t[N] _indices...) scope return 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...) scope return @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 75| return Ret(_lengths[I .. N], _iterator + indexStride(_indices)); | } | | /// ditto | auto opIndex(size_t I)(size_t[I] _indices...) scope return const | if (I && I < N) | { | return this.lightConst.opIndex(_indices); | } | | /// ditto | auto opIndex(size_t I)(size_t[I] _indices...) scope return immutable | if (I && I < N) | { | return this.lightImmutable.opIndex(_indices); | } | | /++ | $(BOLD Partially or fully defined slice.) | +/ | auto opIndex(Slices...)(Slices slices) scope return @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); 1806| 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); 1806| 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 | { 1802| ptrdiff_t ball = this._stride!(slices.length - 1); 1932| 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 | { 1881| stride += ball * slice._iterator._index; 1881| 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]; | 1806| return Ret(structure_, _iterator + stride); | } | else | { 1484| return this; | } | } | | static if (doUnittest) | /// | pure nothrow version(mir_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...)(scope return Slices slices) scope return | 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_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_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)() scope return | 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_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) scope return | if(!isSlice!T) | { | import mir.ndslice.topology: vmap; 37| return this.vmap(LeftOp!(op, ImplicitlyUnqual!T)(value)); | } | | /// ditto | auto opBinaryRight(string op, T)(scope return T value) scope return | if(!isSlice!T) | { | import mir.ndslice.topology: vmap; 36| return this.vmap(RightOp!(op, ImplicitlyUnqual!T)(value)); | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_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_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) scope return | if(N == RN && (kind == Contiguous && rkind == Contiguous || N == 1) && op != "~") | { | import mir.ndslice.topology: zip, map; 296| return zip(this, rhs).map!("a " ~ op ~ " b"); | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_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 | { 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_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_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 | { 1391| assert(n < elementCount, "indexStrideValue: n must be less than elementCount"); 1391| assert(n >= 0, "indexStrideValue: n must be greater than or equal to zero"); | | static if (kind == Contiguous) { 1322| 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) scope return @trusted | { 1391| return _iterator[indexStrideValue(index)]; | } | | /// | version(mir_test) | @safe pure @nogc nothrow | unittest | { | import mir.ndslice.topology: iota, flattened; | 1294| auto x = iota(2, 3, 4); 1294| 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; 14| this.flattened.opIndexOpAssignImplSlice!op(value.flattened); | } | else | { 1232| 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); 2988| value.popFront; | } 3086| ls.popFront; | } 3086| 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) scope return | if (isFullPureSlice!Slices || isIndexedSlice!Slices) | { 985| auto sl = this.lightScope.opIndex(slices); 985| assert(_checkAssignLengths(sl, value)); 985| if(!sl.anyRUEmpty) 985| sl.opIndexOpAssignImplSlice!""(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_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_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_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) scope return | 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_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_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) scope return | 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); | } | | /++ | Assignment of a value (e.g. a number) to a $(B fully defined slice). | +/ | void opIndexAssign(T, Slices...)(T value, Slices slices) scope return | if ((isFullPureSlice!Slices || isIndexedSlice!Slices) | && (!isDynamicArray!T || isDynamicArray!DeepElement) | && DynamicArrayDimensionsCount!T == DynamicArrayDimensionsCount!DeepElement | && !isSlice!T | && !isConcatenation!T) | { 151| auto sl = this.lightScope.opIndex(slices); 151| if(!sl.anyRUEmpty) 73| sl.opIndexOpAssignImplValue!""(value); | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_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_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...) scope return @trusted | { | // check assign safety | static auto ref fun(ref DeepElement t, ref T v) @safe | { 0000000| return t = v; | } 16478| return _iterator[indexStride(_indices)] = value; | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_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_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...) scope return @trusted | { | // check op safety | static auto ref fun(ref DeepElement t, ref T v) @safe | { 0000000| return mixin(`t` ~ op ~ `= v`); | } 992| auto str = indexStride(_indices); | static if (op == "^^" && isFloatingPoint!DeepElement && isFloatingPoint!(typeof(value))) | { | import mir.math.common: pow; | _iterator[str] = pow(_iterator[str], value); | } | else 1004| return mixin (`_iterator[str] ` ~ op ~ `= value`); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_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_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) scope return | if (isFullPureSlice!Slices || isIndexedSlice!Slices) | { 24| auto sl = this.lightScope.opIndex(slices); 24| assert(_checkAssignLengths(sl, value)); 24| if(!sl.anyRUEmpty) 24| sl.opIndexOpAssignImplSlice!op(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_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_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_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) scope return | 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_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_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) scope return | { | static if (N > 1 && kind == Contiguous) | { | import mir.ndslice.topology : flattened; 11| this.flattened.opIndexOpAssignImplValue!op(value); | } | else | { 305| 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); 2767| ls.popFront; | } 2767| 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) scope return | 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_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) scope return | 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_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...) scope return | @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_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_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_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) scope return | 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_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_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_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_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_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_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_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_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_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_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_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)) 1061| if (ls._lengths[i + LN - RN] != rs._lengths[i]) 3| return false; 1010| return true; | } |} | |@safe pure nothrow @nogc version(mir_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_test) unittest |{ | import mir.ndslice.topology: iota, universal; | 1| auto sl = iota(3, 4).universal; 1| assert(sl[0 .. $] == sl); |} | |version(mir_test) unittest |{ | import mir.ndslice.topology: canonical, iota; | static assert(kindOf!(typeof(iota([1, 2]).canonical[1])) == Contiguous); |} | |version(mir_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_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_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_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_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_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_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_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_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_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_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_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_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_test) |@safe pure @nogc nothrow |unittest // check it can be compiled |{ | import mir.algebraic; | alias S = Slice!(double*, 2); | alias D = Variant!S; |} source/mir/ndslice/slice.d is 98% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.82-mir-core-source-mir-conv.lst |/++ |Conversion utilities. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko |+/ |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, () @safe {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; | ScopedBuffer!(Unqual!(ForeachType!T)) buffer; | } | 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); | } | else | static assert(0); | } |} | |/// |@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.1.82/mir-core/source/mir/conv.d has no code <<<<<< 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: Ilya Yaroshenko | |Copyright: 2020 Ilya Yaroshenko, 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); |} | |/// |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_builtincomplex_test) |unittest |{ | cdouble[] ar = [1.0 + 2i, 2 + 3i, 3 + 4i, 4 + 5i]; | cdouble r = 10 + 14i; | assert(r == ar.sum!"fast"); | assert(r == ar.sum!"naive"); | assert(r == ar.sum!"pairwise"); | assert(r == ar.sum!"kahan"); | version(LDC) // DMD Internal error: backend/cgxmm.c 628 | { | assert(r == ar.sum!"kbn"); | assert(r == ar.sum!"kb2"); | } | assert(r == ar.sum!"precise"); | assert(r == ar.sum!"decimal"); |} | |/// |version(mir_test) |unittest |{ | import std.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 |{ 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(1.0 + 2i, 2 + 3i, 3 + 4i, 4 + 5i) == (10 + 14i)); |} | |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; 54283|private alias isFinite = x => x.fabs < x.infinity; 513703|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) || | is(F == cfloat) || | is(F == cdouble) || | (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 | { 793| bool _break; 2467| 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)); | } 793| 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!256 s; | T ss = 0; | } | else | static assert(0, "Unsupported summation type for std.numeric.Summator."); | | |public: | | /// 1656| this()(T n) | { | static if (summation == Summation.precise) | { 350| s = 0.0; 350| o = 0; 609| if (n) put(n); | } | else | static if (summation == Summation.kb2) | { 258| s = n; | static if (isComplex!T) | { | static if (is(T : cfloat)) { | cs = 0 + 0fi; | ccs = 0 + 0fi; | } else { | cs = Complex!float(0, 0); | ccs = Complex!float(0, 0); | } | } | else | { 258| cs = 0.0; 258| ccs = 0.0; | } | } | else | static if (summation == Summation.kbn) | { 260| s = n; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { | c = Complex!float(0, 0); | } | } else 260| c = 0.0; | } | else | static if (summation == Summation.kahan) | { 259| s = n; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { | c = Complex!float(0, 0); | } | } else 259| c = 0.0; | } | else | static if (summation == Summation.pairwise) | { 261| counter = index = 1; 261| partials[0] = n; | } | else | static if (summation == Summation.naive) | { 133| s = n; | } | else | static if (summation == Summation.fast) | { 132| 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) 56423| F x = n; | static if (summation == Summation.precise) | { 54283| if (.isFinite(x)) | { 54268| size_t i; 54268| auto partials_data = partials.data; 1703631| foreach (y; partials_data[]) | { 513609| F h = x + y; 513609| 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); 513609| F l; 513609| if (fabs(x) < fabs(y)) | { 51933| F t = h - y; 51933| l = x - t; | } | else | { 461676| F t = h - x; 461676| l = y - t; | } | debug(numeric) assert(l.isFinite); 513609| if (l) | { 460999| partials_data[i++] = l; | } 513609| x = h; | } 54268| partials.shrinkTo(i); 54268| if (x) | { 54132| partials.put(x); | } | } | else | { 15| s += x; | } | } | else | static if (summation == Summation.kb2) | { | static if (isFloatingPoint!F) | { 930| F t = s + x; 930| F c = 0; 930| if (fabs(s) >= fabs(x)) | { 516| F d = s - t; 516| c = d + x; | } | else | { 414| F d = x - t; 414| c = d + s; | } 930| s = t; 930| t = cs + c; 930| if (fabs(cs) >= fabs(c)) | { 920| F d = cs - t; 920| d += c; 920| ccs += d; | } | else | { 10| F d = c - t; 10| d += cs; 10| ccs += d; | } 930| 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; | static if (is(F : cfloat)) { | s = x_re + s.im * 1fi; | x = s_re + x.im * 1fi; | } else { 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; | static if (is(F : cfloat)) { | s = s.re + x_im * 1fi; | x = x.re + s_im * 1fi; | } else { 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; | static if (is(F : cfloat)) { | c = cs_re + c.im * 1fi; | cs = c_re + cs.im * 1fi; | } else { 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; | static if (is(F : cfloat)) { | c = c.re + cs_im * 1fi; | cs = cs.re + c_im * 1fi; | } else { 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) | { 805| F t = s + x; 805| if (fabs(s) >= fabs(x)) | { 387| F d = s - t; 387| d += x; 387| c += d; | } | else | { 418| F d = x - t; 418| d += s; 418| c += d; | } 805| 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; | static if (is(F : cfloat)) { | s = x_re + s.im * 1fi; | x = s_re + x.im * 1fi; | } else { 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; | static if (is(F : cfloat)) { | s = s.re + x_im * 1fi; | x = x.re + s_im * 1fi; | } else { 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) | { 397| y = x - c; 397| t = s + y; 397| c = t - s; 397| c -= y; 397| s = t; | } | else | static if (summation == Summation.pairwise) | { | import mir.bitop: cttz; 7621| ++counter; 7621| partials[index] = n; 42834| foreach (_; 0 .. cttz(counter)) | { 6657| immutable newIndex = index - 1; 6657| partials[newIndex] += partials[index]; 6657| index = newIndex; | } 7621| ++index; | } | else | static if (summation == Summation.naive) | { 581| s += n; | } | else | static if (summation == Summation.fast) | { 147| s += n; | } | else | static if (summation == summation.decimal) | { | import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; 32| if (-n.infinity < n && n < n.infinity) | { 16| auto decimal = genericBinaryToDecimal(n); 16| 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) | { 94| F[registersCount] v; | foreach (i, n; chainSeq!registersCount) | { 470| if (r.length >= n * 2) do | { | foreach (j; Iota!n) 403| v[j] = cast(F) r[j]; | foreach (j; Iota!n) 403| v[j] += cast(F) r[n + j]; | foreach (m; chainSeq!(n / 2)) | foreach (j; Iota!m) 268| v[j] += v[m + j]; 135| put(v[0]); 135| r = r[n * 2 .. $]; | } 9| while (!i && r.length >= n * 2); | } 94| if (r.length) | { 43| put(cast(F) r[0]); 43| r = r[1 .. $]; | } 94| assert(r.length == 0); | } | else | static if (summation == Summation.fast) | { | static if (isComplex!F) { | static if (is(F : cfloat)) { 3| F s0 = 0 + 0fi; | } else { 1| F s0 = F(0, 0f); | } | } else 46| F s0 = 0; 1803| foreach (ref elem; r) 551| s0 += elem; 50| s += s0; | } | else | { 3523| foreach (ref elem; r) 1138| 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) { | static if (is(F : cfloat)) { | F s0 = 0 + 0fi; | } else { | 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); | } | 799| if (s) 6| return s; 793| auto parts = partials.data[]; 793| F y = 0.0; | //pick last 793| if (parts.length) | { 786| y = parts[$-1]; 786| parts = parts[0..$-1]; | } 793| 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; | } | } 793| return partialsReduce(y, parts); | } | else | static if (summation == Summation.kb2) | { 672| return s + (cs + ccs); | } | else | static if (summation == Summation.kbn) | { 673| return s + c; | } | else | static if (summation == Summation.kahan) | { 656| return s; | } | else | static if (summation == Summation.pairwise) | { 2149| F s = summationInitValue!T; 2149| assert((counter == 0) == (index == 0)); 12481| 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 3014| s += e; | } 2149| return s; | } | else | static if (summation == Summation.naive) | { 615| return s; | } | else | static if (summation == Summation.fast) | { 1081| return s; | } | else | static if (summation == Summation.decimal) | { 5| 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) | { 87| auto ret = typeof(return).init; 87| ret.s = s; 87| ret.o = o; 519| foreach (p; partials.data[]) | { 86| ret.partials.put(p); | } | enum exp_diff = P.max_exp / T.max_exp; | static if (exp_diff) | { 87| 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); | } | } 87| return ret; | } | else | static if (summation == Summation.kb2) | { 86| auto ret = typeof(return).init; 86| ret.s = s; 86| ret.cs = cs; 86| ret.ccs = ccs; 86| return ret; | } | else | static if (summation == Summation.kbn) | { 86| auto ret = typeof(return).init; 86| ret.s = s; 86| ret.c = c; 86| return ret; | } | else | static if (summation == Summation.kahan) | { 86| auto ret = typeof(return).init; 86| ret.s = s; 86| ret.c = c; 86| return ret; | } | else | static if (summation == Summation.pairwise) | { 86| auto ret = typeof(return).init; 86| ret.counter = counter; 86| ret.index = index; 774| foreach (i; 0 .. index) 172| ret.partials[i] = partials[i]; 86| 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) | { 172| partials.reset; 172| s = 0.0; 172| o = 0; 301| if (rhs) put(rhs); | } | else | static if (summation == Summation.kb2) | { 129| s = rhs; | static if (isComplex!T) | { | static if (is(T : cfloat)) { | cs = 0 + 0fi; | ccs = 0 + 0fi; | } else { 0000000| cs = T(0, 0f); 0000000| ccs = T(0.0, 0f); | } | } | else | { 129| cs = 0.0; 129| ccs = 0.0; | } | } | else | static if (summation == Summation.kbn) | { 129| s = rhs; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { 0000000| c = T(0, 0f); | } | } else 129| c = 0.0; | } | else | static if (summation == Summation.kahan) | { 129| s = rhs; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { 0000000| c = T(0, 0f); | } | } else 129| c = 0.0; | } | else | static if (summation == Summation.pairwise) | { 129| counter = 1; 129| index = 1; 129| partials[0] = rhs; | } | else | static if (summation == Summation.naive) | { 129| s = rhs; | } | else | static if (summation == Summation.fast) | { 129| s = rhs; | } | else | static if (summation == summation.decimal) | { 3| __ctor(rhs); | } | else | static assert(0); | } | | ///ditto | void opOpAssign(string op : "+")(T rhs) | { 23060| 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) | { 21683| put(-rhs); | } | else | static if (summation == Summation.kb2) | { 129| put(-rhs); | } | else | static if (summation == Summation.kbn) | { 129| put(-rhs); | } | else | static if (summation == Summation.kahan) | { 258| y = 0.0; 258| y -= rhs; 258| y -= c; 258| t = s + y; 258| c = t - s; 258| c -= y; 258| s = t; | } | else | static if (summation == Summation.pairwise) | { 129| put(-rhs); | } | else | static if (summation == Summation.naive) | { 129| s -= rhs; | } | else | static if (summation == Summation.fast) | { 129| s -= rhs; | } | else | static assert(0); | } | | ///ditto | void opOpAssign(string op : "-")(ref const Summator rhs) | { | static if (summation == Summation.precise) | { 172| s -= rhs.s; 172| o -= rhs.o; 3870| foreach (f; rhs.partials.data[]) 1118| put(-f); | } | else | static if (summation == Summation.kb2) | { 129| put(-rhs.ccs); 129| put(-rhs.cs); 129| put(-rhs.s); | } | else | static if (summation == Summation.kbn) | { 129| put(-rhs.c); 129| put(-rhs.s); | } | else | static if (summation == Summation.kahan) | { 129| this -= rhs.s; | } | else | static if (summation == Summation.pairwise) | { 645| foreach_reverse (e; rhs.partials[0 .. rhs.index]) 129| put(-e); 129| counter -= rhs.index; 129| 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; 21543| auto r1 = iota(500).map!(a => 1.7L.pow(a+1) - 1.7L.pow(a)); 21543| auto r2 = iota([500], 500).map!(a => 1.7L.pow(a+1) - 1.7L.pow(a)); 172| Summator!(real, Summation.precise) s1 = 0, s2 = 0.0; 86086| foreach (e; r1) s1 += e; 86086| foreach (e; r2) s2 -= e; 43| s1 -= s2; 43| s1 -= 1.7L.pow(1000); 43| 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)) | { 774| Summator!(T, summation) sum = 1; 645| sum += 3; 645| assert(sum.sum == 4); 645| sum -= 10; 645| assert(sum.sum == -6); 774| Summator!(T, summation) sum2 = 3; 645| sum -= sum2; 645| assert(sum.sum == -9); 645| sum2 = 100; 645| sum += 100; 645| assert(sum.sum == 91); 774| auto sum3 = cast(Summator!(real, summation))sum; 645| assert(sum3.sum == 91); 645| 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)) | { 258| Summator!(T, summation) sum = 1; 258| sum += 3.5; 258| assert(sum.sum.approxEqual(4.5)); 258| sum = 2; 258| assert(sum.sum == 2); 258| sum -= 4; 258| 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: RefTuple, refTuple; | 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| RefTuple!(double[], double)[] tests = [ | refTuple(new double[0], 0.0), | refTuple([0.0], 0.0), | refTuple([1e100, 1.0, -1e100, 1e-100, 1e50, -1, -1e50], 1e-100), | refTuple([1e308, 1e308, -1e308], 1e308), | refTuple([-1e308, 1e308, 1e308], 1e308), | refTuple([1e308, -1e308, 1e308], 1e308), | refTuple([M, M, -2.0^^1000], 1.7976930277114552e+308), | refTuple([M, M, M, M, -M, -M, -M], 8.9884656743115795e+307), | refTuple([2.0^^53, -0.5, -2.0^^-54], 2.0^^53-1.0), | refTuple([2.0^^53, 1.0, 2.0^^-100], 2.0^^53+2.0), | refTuple([2.0^^53+10.0, 1.0, 2.0^^-100], 2.0^^53+12.0), | refTuple([2.0^^53-4.0, 0.5, 2.0^^-54], 2.0^^53-3.0), | refTuple([M-2.0^^970, -1, M], 1.7976931348623157e+308), | refTuple([double.max, double.max*2.^^-54], double.max), | refTuple([double.max, double.max*2.^^-53], double.infinity), 1000| refTuple(iota([1000], 1).map!(a => 1.0/a).array , 7.4854708605503451), 1000| refTuple(iota([1000], 1).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... 1000| refTuple(iota([1000], 1).map!(a => 1.0/a).retro.array , 7.4854708605503451), 1000| refTuple(iota([1000], 1).map!(a => (-1.0)^^a/a).retro.array, -0.69264743055982025), | refTuple([double.infinity, -double.infinity, double.nan], double.nan), | refTuple([double.nan, double.infinity, -double.infinity], double.nan), | refTuple([double.infinity, double.nan, double.infinity], double.nan), | refTuple([double.infinity, double.infinity], double.infinity), | refTuple([double.infinity, -double.infinity], double.nan), | refTuple([-double.infinity, 1e308, 1e308, -double.infinity], -double.infinity), | refTuple([M-2.0^^970, 0.0, M], double.infinity), | refTuple([M-2.0^^970, 1.0, M], double.infinity), | refTuple([M, M], double.infinity), | refTuple([M, M, -1], double.infinity), | refTuple([M, M, M, M, -M, -M], double.infinity), | refTuple([M, M, M, M, -M, M], double.infinity), | refTuple([-M, -M, -M, -M], -double.infinity), | refTuple([M, M, -2.^^971], double.max), | refTuple([M, M, -2.^^970], double.infinity), | refTuple([-2.^^970, M, M, -0X0.0000000000001P-0 * 2.^^-1022], double.max), | refTuple([M, M, -2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], double.infinity), | refTuple([-M, 2.^^971, -M], -double.max), | refTuple([-M, -M, 2.^^970], -double.infinity), | refTuple([-M, -M, 2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], -double.max), | refTuple([-0X0.0000000000001P-0 * 2.^^-1022, -M, -M, 2.^^970], -double.infinity), | refTuple([2.^^930, -2.^^980, M, M, M, -M], 1.7976931348622137e+308), | refTuple([M, M, -1e307], 1.6976931348623159e+308), | refTuple([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); | } | } | static if (is(F : cfloat)) { | return sumRe.sum + sumIm.sum * 1fi; | } else { 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 | { 66| Summator!(F, ResolveSummationType!(summation, const(F)[], F)) sum; 65| sum.put(r); 65| 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); 753| 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 ~ ");"); |} | | | |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_builtincomplex_test) |unittest |{ | assert(sum(1.0 + 1i, 2.0 + 2i, 3.0 + 3i) == (6 + 6i)); | assert(sum!cfloat(1.0 + 1i, 2.0 + 2i, 3.0 + 3i) == (6f + 6i)); |} | |version(mir_test) |unittest |{ | import std.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;})) | { 2151| T a = 0.0; 2151| 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;})) | { 0000000| T a = 0 + 0fi; 0000000| return a; | } | else | { | 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-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 Ilya Yaroshenko, 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_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_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_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_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_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_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_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_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-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) |)) |$(TR $(TD Other date types) $(TD | $(LREF Month) | $(LREF DayOfWeek) |)) |$(TR $(TD Date checking) $(TD | $(LREF valid) | $(LREF yearIsLeapYear) |)) |$(TR $(TD Date conversion) $(TD | $(LREF daysToDayOfWeek) |)) |$(TR $(TD Other) $(TD | $(LREF AllowDayOverflow) | $(LREF DateTimeException) |)) |)) |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: $(HTTP jmdavisprog.com, Jonathan M Davis), Ilya Yaroshenko (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") 2481| 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") |{ 1155| 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 |{ 871| if (year % 400 == 0) 162| return true; 709| if (year % 100 == 0) 28| return false; 681| 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("Invalid Month"); | private static immutable InvalidDay = new DateTimeException("Invalid Day"); | private static immutable InvalidISOString = new DateTimeException("Invalid ISO String"); | private static immutable InvalidISOExtendedString = new DateTimeException("Invalid ISO Extended String"); | private static immutable InvalidSimpleString = new DateTimeException("Invalid Simple String"); | private static immutable InvalidString = new DateTimeException("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; | | /// | Timestamp timestamp() @safe pure nothrow @nogc @property | { 1| return Timestamp(year, cast(ubyte)month, day); | } | | /// | alias opCast(T : Timestamp) = timestamp; | | /// | version(mir_test) | unittest | { | import mir.timestamp; 1| auto timestamp = cast(Timestamp) YearMonthDay(2020, Month.may, 12); | } | | /// 4| this(short year, Month month, ubyte day) @safe pure nothrow @nogc | { 4| this.year = year; 4| this.month = month; 4| this.day = day; | } | | /// 0000000| this(Date date) @safe pure nothrow @nogc | { 0000000| this = date.yearMonthDay; | } | | version(D_Exceptions) | /// 0000000| this(Timestamp timestamp) @safe pure nothrow @nogc | { 0000000| if (timestamp.precision != Timestamp.Precision.day) | { | static immutable exc = new Exception("YearMonthDay: invalid timestamp precision"); | } 0000000| with(timestamp) this(year, cast(Month)month, day); | } | | // Shares documentation with "years" version. | @safe pure nothrow @nogc | ref YearMonthDay add(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) | if (units == "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; | | 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; | } | | // Shares documentation with "years" version. | @safe pure nothrow @nogc | ref YearMonthDay add(string units)(long years, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) | if (units == "years") | { | 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; | } |} | |/++ | 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 | { 558| Date ret; 558| immutable int[] lastDay = yearIsLeapYear(_year) ? lastDayLeap : lastDayNonLeap; 558| auto monthIndex = _month - Month.jan; | 558| const dayOfYear = lastDay[monthIndex] + _day; | 1116| if (_month >= Month.jan && _month <= Month.dec) {} else | assert(0, "Invalid month."); 558| if (_year > 0) | { 357| if (_year == 1) | { 21| ret._julianDay = dayOfYear; 21| goto R; | } | 336| int years = _year - 1; 336| auto days = (years / 400) * daysIn400Years; 336| years %= 400; | 336| days += (years / 100) * daysIn100Years; 336| years %= 100; | 336| days += (years / 4) * daysIn4Years; 336| years %= 4; | 336| days += years * daysInYear; | 336| days += dayOfYear; | 336| 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: 558| ret._julianDay += _julianShift; 558| return ret; | } | | /// | Timestamp timestamp() @safe pure nothrow @nogc const @property | { 0000000| return yearMonthDay.timestamp; | } | | version(D_Exceptions) | /// 0000000| this(Timestamp timestamp) @safe pure @nogc | { 0000000| if (timestamp.precision != Timestamp.Precision.day) | { | static immutable exc = new Exception("Date: invalid timestamp precision"); | } | } | | version(D_Exceptions) | /// 0000000| this(scope const(char)[] str) @safe pure @nogc | { 0000000| this = fromString(str); | } | | version(D_Exceptions) | /// 0000000| this(YearMonthDay ymd) @safe pure @nogc | { 0000000| with(ymd) this(year, month, day); | } | | version(D_Exceptions) | /// 505| this(int _year, int _month, int _day) @safe pure @nogc | { 524| if (!valid!"months"(_month)) 2| throw InvalidMonth; 522| if (!valid!"days"(_year, cast(Month) _month, _day)) 17| throw InvalidDay; 505| this = trustedCreate(_year, _month, _day); | } | | /// | static bool fromYMD(int _year, int _month, int _day, out Date value) @safe pure nothrow @nogc | { 104| if (valid!"months"(_month) && valid!"days"(_year, cast(Month) _month, _day)) | { 52| value = trustedCreate(_year, _month, _day); 52| 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. | +/ 44| this(int day) @safe pure nothrow @nogc | { 44| _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 | { 2| 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 | { 217| 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 _dict = () | { | 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 | { 102| uint day = _julianDay; 102| if (day < _endDict) | { | import mir.checkedint: subu; 89| bool overflow; 89| auto index = subu(day, _startDict, overflow); 89| if (!overflow) 47| return _dict[index]; | } 55| return yearMonthDayImpl; | } | | /// | short year() const @safe pure nothrow @nogc @property | { 0000000| return yearMonthDay.year; | } | | /// | Month month() const @safe pure nothrow @nogc @property | { 0000000| return yearMonthDay.month; | } | | /// | ubyte day() const @safe pure nothrow @nogc @property | { 0000000| return yearMonthDay.day; | } | | pragma(inline, false) | YearMonthDay yearMonthDayImpl() const @safe pure nothrow @nogc @property | { 55| YearMonthDay ymd; 55| int days = dayOfGregorianCal; | with(ymd) 55| if (days > 0) | { 16| int years = (days / daysIn400Years) * 400 + 1; 16| days %= daysIn400Years; | | { 16| immutable tempYears = days / daysIn100Years; | 16| if (tempYears == 4) | { 0000000| years += 300; 0000000| days -= daysIn100Years * 3; | } | else | { 16| years += tempYears * 100; 16| days %= daysIn100Years; | } | } | 16| years += (days / daysIn4Years) * 4; 16| days %= daysIn4Years; | | { 16| immutable tempYears = days / daysInYear; | 16| if (tempYears == 4) | { 0000000| years += 3; 0000000| days -= daysInYear * 3; | } | else | { 16| years += tempYears; 16| days %= daysInYear; | } | } | 16| if (days == 0) | { 0000000| year = cast(short)(years - 1); 0000000| month = Month.dec; 0000000| 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); | } | } 55| return ymd; | } | | /++ | $(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 lhs) const | { | return _julianDay - lhs._julianDay; | } | | /// | Date opBinary(string op : "+")(int lhs) const | { 1| return Date(_julianDay + lhs); | } | | /// | Date opBinaryRight(string op : "+")(int lhs) const | { | return Date(_julianDay + lhs); | } | | /// | Date opBinary(string op : "-")(int lhs) const | { | return Date(_julianDay - lhs); | } | | 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; | 134| if (str.length < 10 || str[$-3] != '-' || str[$-6] != '-') 42| return false; | 23| auto yearStr = str[0 .. $ - 6]; | 44| if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) 3| return false; | 40| uint day, month; 20| int year; | 20| return | fromString(str[$ - 2 .. $], day) 19| && fromString(str[$ - 5 .. $ - 3], month) 17| && fromString(yearStr, year) 17| && 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 | { 3| 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) | { 3| Date ret; 3| if (fromString(str, ret)) 3| 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 | { 44| _julianDay = cast(int)(_julianDay + days); 44| 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 |{ 52| if (currDoW == dow) 8| return 0; 44| if (currDoW < dow) 23| return dow - currDoW; 21| 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 |{ 663| assert(valid!"months"(month)); |} |do |{ 663| switch (month) | { 2016| case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec: 537| return 31; 58| case Month.feb: 58| return yearIsLeapYear(year) ? 29 : 28; 179| case Month.apr, Month.jun, Month.sep, Month.nov: 68| 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 2| if (day >= 0) 2| 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]; | | |/+ | 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 93% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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.interpolate: Repeat; |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; | |@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); // 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)); | | /// 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.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); | | ///// 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.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| 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)); |} | |/++ |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, |} | |/++ |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, | ) | { 9| return spline(grid, 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, | ) | { 15| return spline(grid, 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, | ) | { 15| return spline(grid, values, boundaries, boundaries, kind, param); | } | | /++ | Params: | grid = immutable `x` values for interpolant | values = `f(x)` values for interpolant | rBoundary = $(LREF SplineBoundaryCondition) for left tail. | lBoundary = $(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 rBoundary, | SplineBoundaryCondition!T lBoundary, | SplineType kind = SplineType.c2, | in T param = 0, | ) | { 17| auto ret = typeof(return)(forward!grid); 17| ret._values = values; 17| ret._computeDerivatives(kind, param, rBoundary, lBoundary); 17| return ret; | } |} | |/++ |Cubic Spline Boundary Condition Type. | |See_also: $(LREF SplineBoundaryCondition) $(LREF SplineType) |+/ |extern(C++, "mir", "interpolate") |enum SplineBoundaryType |{ | /++ | Not implemented. | +/ | periodic = -1, | /++ | 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, |} | |/++ |Cubic Spline Boundary Condition | |See_also: $(LREF SplineBoundaryType) |+/ |extern(C++, "mir", "interpolate") |struct SplineBoundaryCondition(T) |{ | /// type (default is $(LREF SplineBoundaryType.notAKnot)) | SplineBoundaryType type = SplineBoundaryType.notAKnot; | /// value (default is 0) | T value = 0; |} | |/++ |Multivariate cubic spline with nodes on rectilinear grid. |+/ 49|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; | |@fmamath extern(D): | | /++ | +/ 17| this(Repeat!(N, Slice!(RCI!(immutable X))) grid) @safe @nogc | { 17| size_t length = 1; 17| size_t[N] shape; | enum msg = "spline interpolant: minimal allowed length for the grid equals 2."; | version(D_Exceptions) | static immutable exc = new Exception(msg); 20| foreach(i, ref x; grid) | { 20| if (x.length < 2) | { 0000000| version(D_Exceptions) throw exc; | else assert(0, msg); | } 20| length *= shape[i] = x.length; 20| this._grid[i] = x._iterator.move; | } | import mir.ndslice.allocation: rcslice; 17| this._data = shape.rcslice!(F[2 ^^ N]); | } | | package static auto pickDataSubslice(D)(auto scope ref D data, size_t index) @trusted | { 283| auto strides = data.strides; | foreach (i; Iota!(strides.length)) 286| strides[i] *= DeepElementType!D.length; 283| return Slice!(F*, strides.length, Universal)(data.shape, strides, data._iterator.ptr + index); | } | | /++ | Assigns function values to the internal memory. | $(RED For internal use.) | +/ | void _values(SliceKind kind, Iterator)(Slice!(Iterator, N, kind) values) scope @property @trusted | { 17| assert(values.shape == _data.shape, "'values' should have the same shape as the .gridShape"); 17| 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: | lbc = left boundary condition | rbc = right boundary condition | temp = temporal buffer length points count (optional) | | $(RED For internal use.) | +/ | void _computeDerivatives(SplineType kind, F param, SplineBoundaryCondition!F lbc, SplineBoundaryCondition!F rbc) scope @trusted nothrow @nogc | { | import mir.algorithm.iteration: maxLength; 17| auto ml = this._data.maxLength; 34| auto temp = RCArray!F(ml); 17| auto tempSlice = temp[].sliced; 17| _computeDerivativesTemp(kind, param, lbc, rbc, tempSlice); | } | | /// ditto | pragma(inline, false) | void _computeDerivativesTemp(SplineType kind, F param, SplineBoundaryCondition!F lbc, SplineBoundaryCondition!F rbc, Slice!(F*) temp) scope @system nothrow @nogc | { | import mir.algorithm.iteration: maxLength, each; | import mir.ndslice.topology: map, byDim, evertPack; | 17| assert(temp.length >= _data.maxLength); | | static if (N == 1) | { 15| splineSlopes!(F, F)(_grid[0].lightConst.sliced(_data._lengths[0]), pickDataSubslice(_data.lightScope, 0), pickDataSubslice(_data.lightScope, 1), temp[0 .. _data._lengths[0]], kind, param, lbc, rbc); | } | 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| splineSlopes!(F, F)(_grid[i].sliced(_data._lengths[i]), y, s, temp[0 .. _data._lengths[i]], kind, param, lbc, rbc); | // 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)() scope return const @property | if (dimension < N) | { | return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); | } | | /// | immutable(X)[] gridScopeView(size_t dimension = 0)() scope return const @property @trusted | if (dimension < N) | { 405| return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; | } | | /++ | Returns: intervals count. | +/ | size_t intervalCount(size_t dimension = 0)() scope const @property | { 405| assert(_data._lengths[dimension] > 1); 405| 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 | { 10| auto d4 = this.opCall!3(xs); 10| 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) 30| fun!(d - 1)(a[i], b[i]); | else 30| b = a; | } 10| fun!N(d4, d3); 10| 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; | 259| size_t[N] indices; 259| 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]; 405| indices[i] = this.findInterval!i(x); | } 407| 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: 940| *to = *from; | } | else | { 681| from += strides[i] * indices[i]; 681| load!(i - 1)(from, to); 681| from += strides[i]; | enum s = 2 ^^ (N - 1 - i); 681| to += s; 681| load!(i - 1)(from, to); | } | } | 259| immutable strides = _data._lengths.iota.strides; 259| 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; 407| 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]); 407| 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"); | // } 407| local[0][] = F.init; 407| 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) | { 259| 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. | lbc = left boundary condition | rbc = 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. |+/ |void splineSlopes(F, T, IP, IV, IS, SliceKind gkind, SliceKind vkind, SliceKind skind)( | Slice!(IP, 1, gkind) points, | Slice!(IV, 1, vkind) values, | Slice!(IS, 1, skind) slopes, | Slice!(T*) temp, | SplineType kind, | F param, | SplineBoundaryCondition!F lbc, | SplineBoundaryCondition!F rbc, | ) @trusted |{ | import mir.ndslice.topology: diff, zip, slide; | 133| assert (points.length >= 2); 133| assert (points.length == values.length); 133| assert (points.length == slopes.length); 133| assert (temp.length == points.length); | 133| auto n = points.length; | 266| typeof(slopes[0]) first, last; | 266| auto xd = points.diff; 133| auto yd = values.diff; 266| auto dd = yd / xd; 266| auto dd2 = points.zip(values).slide!(3, "(c[1] - a[1]) / (c[0] - a[0])"); | 133| with(SplineType) final switch(kind) | { 127| case c2: 127| break; 0000000| case cardinal: 0000000| if (lbc.type == SplineBoundaryType.notAKnot) 0000000| lbc.type = SplineBoundaryType.parabolic; 0000000| if (rbc.type == SplineBoundaryType.notAKnot) 0000000| rbc.type = SplineBoundaryType.parabolic; 0000000| break; 4| case monotone: 4| if (lbc.type == SplineBoundaryType.notAKnot) 4| lbc.type = SplineBoundaryType.monotone; 4| if (rbc.type == SplineBoundaryType.notAKnot) 4| rbc.type = SplineBoundaryType.monotone; 4| break; 1| case doubleQuadratic: 1| if (lbc.type == SplineBoundaryType.notAKnot) 1| lbc.type = SplineBoundaryType.parabolic; 1| if (rbc.type == SplineBoundaryType.notAKnot) 1| rbc.type = SplineBoundaryType.parabolic; 1| break; 1| case akima: 1| if (lbc.type == SplineBoundaryType.notAKnot) 1| lbc.type = SplineBoundaryType.akima; 1| if (rbc.type == SplineBoundaryType.notAKnot) 1| rbc.type = SplineBoundaryType.akima; 1| break; | } | 133| if (n <= 3) | { 21| if (lbc.type == SplineBoundaryType.notAKnot) 21| lbc.type = SplineBoundaryType.parabolic; 21| if (rbc.type == SplineBoundaryType.notAKnot) 21| rbc.type = SplineBoundaryType.parabolic; | 21| if (n == 2) | { 0000000| if (lbc.type == SplineBoundaryType.monotone 0000000| || lbc.type == SplineBoundaryType.akima) 0000000| lbc.type = SplineBoundaryType.parabolic; 0000000| if (rbc.type == SplineBoundaryType.monotone 0000000| || rbc.type == SplineBoundaryType.akima) 0000000| rbc.type = SplineBoundaryType.parabolic; | } | /// special case 21| if (rbc.type == SplineBoundaryType.parabolic 21| && lbc.type == SplineBoundaryType.parabolic) | { | import mir.interpolate.utility; 21| if (n == 3) | { 21| auto derivatives = parabolaDerivatives(points[0], points[1], points[2], values[0], values[1], values[2]); 21| slopes[0] = derivatives[0]; 21| slopes[1] = derivatives[1]; 21| slopes[2] = derivatives[2]; | } | else | { 0000000| assert(slopes.length == 2); 0000000| slopes.back = slopes.front = yd.front / xd.front; | } 21| return; | } | } | 112| with(SplineBoundaryType) final switch(lbc.type) | { | case periodic: | | assert(0); | 101| case notAKnot: | 101| auto dx0 = xd[0]; 101| auto dx1 = xd[1]; 101| auto dy0 = yd[0]; 101| auto dy1 = yd[1]; 101| auto dd0 = dy0 / dx0; 101| auto dd1 = dy1 / dx1; | 101| slopes.front = dx1; 101| first = dx0 + dx1; 101| temp.front = ((dx0 + 2 * first) * dx1 * dd0 + dx0 ^^ 2 * dd1) / first; 101| break; | 2| case firstDerivative: | 2| slopes.front = 1; 2| first = 0; 2| temp.front = lbc.value; 2| break; | 3| case secondDerivative: | 3| slopes.front = 2; 3| first = 1; 3| temp.front = 3 * dd.front - 0.5 * lbc.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; | | } | 112| with(SplineBoundaryType) final switch(rbc.type) | { | case periodic: | assert(0); | 101| case notAKnot: | 101| auto dx0 = xd[$ - 1]; 101| auto dx1 = xd[$ - 2]; 101| auto dy0 = yd[$ - 1]; 101| auto dy1 = yd[$ - 2]; 101| auto dd0 = dy0 / dx0; 101| auto dd1 = dy1 / dx1; 101| slopes.back = dx1; 101| last = dx0 + dx1; 101| temp.back = ((dx0 + 2 * last) * dx1 * dd0 + dx0 ^^ 2 * dd1) / last; 101| break; | 2| case firstDerivative: | 2| slopes.back = 1; 2| last = 0; 2| temp.back = rbc.value; 2| break; | 3| case secondDerivative: | 3| slopes.back = 2; 3| last = 1; 3| temp.back = 3 * dd.back + 0.5 * rbc.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; | | } | 112| with(SplineType) final switch(kind) | { 106| case c2: | 1179| foreach (i; 1 .. n - 1) | { 287| auto dx0 = xd[i - 1]; 287| auto dx1 = xd[i - 0]; 287| auto dy0 = yd[i - 1]; 287| auto dy1 = yd[i - 0]; 287| slopes[i] = 2 * (dx0 + dx1); 287| temp[i] = 3 * (dy0 / dx0 * dx1 + dy1 / dx1 * dx0); | } 106| 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; | } | } | 1677| foreach (i; 0 .. n - 1) | { 447| auto c = i == 0 ? first : kind == SplineType.c2 ? xd[i - 1] : 0; 447| auto a = i == n - 2 ? last : kind == SplineType.c2 ? xd[i + 1] : 0; 447| auto w = slopes[i] == 1 ? a : a / slopes[i]; 447| slopes[i + 1] -= w * c; 447| temp[i + 1] -= w * temp[i]; | } | 112| slopes.back = temp.back / slopes.back; | 1230| foreach_reverse (i; 0 .. n - 1) | { 447| auto c = i == 0 ? first : kind == SplineType.c2 ? xd[i - 1] : 0; 447| auto v = temp[i] - c * slopes[i + 1]; 447| slopes[i] = slopes[i] == 1 ? v : v / slopes[i]; | } |} | |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; |} | |/// |struct SplineKernel(X) |{ | X step = 0; | X w0 = 0; | X w1 = 0; | X wq = 0; | | /// 407| this(X x0, X x1, X x) | { 407| step = x1 - x0; 407| auto c0 = x - x0; 407| auto c1 = x1 - x; 407| w0 = c0 / step; 407| w1 = c1 / step; 407| wq = w0 * w1; | } | | /// | template opCall(uint derivative = 0) | if (derivative <= 3) | { | /// | auto opCall(Y)(in Y y0, in Y y1, in Y s0, in Y s1) const | { 1615| auto diff = y1 - y0; 1615| auto z0 = s0 * step - diff; 1615| auto z1 = s1 * step - diff; 1615| auto a0 = z0 * w1; 1615| auto a1 = z1 * w0; 1615| auto pr = a0 - a1; 1615| auto b0 = y0 * w1; 1615| auto b1 = y1 * w0; 1615| auto pl = b0 + b1; 1615| auto y = pl + wq * pr; | static if (derivative) | { 64| Y[derivative + 1] ret = 0; 64| ret[0] = y; 64| auto wd = w1 - w0; 64| auto zd = z1 + z0; 64| ret[1] = (diff + (wd * pr - wq * zd)) / step; | static if (derivative > 1) | { 20| auto astep = zd / (step * step); 20| ret[2] = -3 * wd * astep + (s1 - s0) / step; | static if (derivative > 2) 20| ret[3] = 6 * astep / step; | } 64| return ret; | } | else | { 1551| return y; | } | } | } | | /// | 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; |} source/mir/interpolate/spline.d is 92% 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: Ilya Yaroshenko |Copyright: 2020 Ilya Yaroshenko, 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)) | { 352| int lexp; | import mir.math.ieee: frexp; 352| x *= frexp(e, lexp); 352| exp += lexp; 352| if (x.fabs < 0.5f) | { 305| x += x; 305| exp--; | } | } else { 282| 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) | { 951| foreach (ref elem; r) 261| 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 | { 296| foreach(elem; r) 74| this.put(elem); | } | } | | /// | @safe pure @nogc nothrow | T prod() const scope @property | { | import mir.math.ieee: ldexp; 38| int e = | exp > int.max ? int.max : | exp < int.min ? int.min : | cast(int) exp; 38| 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; | 19| ProdAccumulator!F prod; 19| prod.put(r.move); 19| 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; | 15| ProdAccumulator!F prod; 15| prod.put(r.move); 15| exp = prod.exp; 15| 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); 14| 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)) |{ 10| long exp = 0; 10| auto x = .prod(r, exp); 10| 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); |} source/mir/math/numeric.d is 91% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.82-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; | stringBuf buf; | 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. | +/ | @nogc @safe pure nothrow this(Args...)(scope auto ref Args args, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) | if (Args.length > 1) | { | static assert (__traits(compiles, {import mir.format;}), "MirThrowableImpl needs mir-algorithm for mir.format and exception formatting."); | import mir.format; | stringBuf buf; | this(buf.print(args).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.1.82/mir-core/source/mir/exception.d is 0% 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: Ilya Yaroshenko |Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |+/ |module mir.parse; | |/// `mir.conv: to` extension. |version(mir_test) |@safe pure @nogc |unittest |{ | import mir.conv: to; | 1| assert("123.0".to!double == 123); 1| assert("123".to!int == 123); 1| assert("123".to!byte == 123); | | import mir.small_string; | alias S = SmallString!32; 1| assert(S("123.0").to!double == 123); 1| assert(S("123.").to!double == 123.); 1| assert(S(".123").to!double == .123); 1| assert(S("123").to!(immutable int) == 123); |} | |import mir.primitives; |import std.traits: isMutable, isFloatingPoint, isSomeChar; | |/++ |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.+/ |T fromString(T, C)(scope const(C)[] str) | if (isMutable!T) |{ | import mir.utility: _expect; | static immutable excfp = new Exception("fromString failed to parse " ~ T.stringof); | | static if (isFloatingPoint!T) | { 26| T value; 26| if (_expect(fromString(str, value), true)) 16| 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); | 10| T value; 10| if (_expect(parse!T(str, value), true)) | { 10| if (_expect(str.empty, true)) 10| return value; | version (D_Exceptions) 0000000| throw excne; | else | assert(0); | } | else | { | version (D_Exceptions) 0000000| throw excfp; | else | assert(0); | } | } |} | |/// |version(mir_bignum_test) |@safe pure @nogc unittest |{ 1| assert("123".fromString!int == 123); | static assert("-123".fromString!int == -123); | 1| assert(".5".fromString!float == .5); 1| assert("12.3".fromString!double == 12.3); 1| assert("12.3".fromString!float == 12.3f); 1| assert("12.3".fromString!real == 12.3L); 1| assert("-12.3e-30".fromString!double == -12.3e-30); 1| assert("2.9802322387695312E-8".fromString!double == 2.9802322387695312E-8); | | // default support of underscores 1| assert("123_456.789_012".fromString!double == 123_456.789_012); 1| assert("12_34_56_78_90_12e-6".fromString!double == 123_456.789_012); | | // default support of leading zeros 1| assert("010".fromString!double == 10.0); 1| assert("000010".fromString!double == 10.0); 1| assert("0000.10".fromString!double == 0.1); 1| assert("0000e10".fromString!double == 0); | | /// Test CTFE support | 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); | |// 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. | |Returns: 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; | 26| Decimal!256 decimal = void; 26| DecimalExponentKey key; 26| auto ret = decimal.fromStringImpl(str, key); 26| if (_expect(ret, true)) | { 16| switch(key) with(DecimalExponentKey) | { 0000000| case nan: value = decimal.coefficient.sign ? -T.nan : T.nan; break; 0000000| case infinity: value = decimal.coefficient.sign ? -T.infinity : T.infinity; break; 48| default: value = cast(T) decimal; break; | } | } 26| return ret; | } | else | { 816| return parse!T(str, value) && str.empty; | } |} | |/// |version(mir_test) |@safe pure nothrow @nogc unittest |{ 1| int value; 2| assert("123".fromString(value) && value == 123); |} | |/++ |Single character parsing utilities. | |Returns: true if success and false otherwise. |+/ |bool parse(T, Range)(scope ref Range r, scope ref T value) | if (isInputRange!Range && isSomeChar!T) |{ 1| if (r.empty) 0000000| return false; 1| value = r.front; 1| r.popFront; 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, Range)(scope ref Range r, scope ref T value) | if ((is(T == byte) || is(T == short)) && isInputRange!Range && !__traits(isUnsigned, T)) |{ 159| int lvalue; 159| auto ret = parse!(int, Range)(r, lvalue); 159| value = cast(T) lvalue; 313| return ret && value == lvalue; |} | |/// ditto |bool parse(T, Range)(scope ref Range r, scope ref T value) | if (is(T == int) && isInputRange!Range && !__traits(isUnsigned, T)) |{ | version(LDC) pragma(inline, true); 238| return parseSignedImpl!(int, Range)(r, value); |} | |/// ditto |bool parse(T, Range)(scope ref Range r, scope ref T value) | if (is(T == long) && isInputRange!Range && !__traits(isUnsigned, T)) |{ | version(LDC) pragma(inline, true); 26| return parseSignedImpl!(long, Range)(r, value); |} | |/// ditto |bool parse(T, Range)(scope ref Range r, scope ref T value) | if ((is(T == ubyte) || is(T == ushort)) && isInputRange!Range && __traits(isUnsigned, T)) |{ 150| uint lvalue; 150| auto ret = parse!(uint, Range)(r, lvalue); 150| value = cast(T) lvalue; 296| return ret && value == lvalue; |} | |/// ditto |bool parse(T, Range)(scope ref Range r, scope ref T value) | if (is(T == uint) && isInputRange!Range && __traits(isUnsigned, T)) |{ | version(LDC) pragma(inline, true); 538| return parseUnsignedImpl!(uint, Range)(r, value); |} | |/// ditto |bool parse(T, Range)(scope ref Range r, scope ref T value) | if (is(T == ulong) && isInputRange!Range && __traits(isUnsigned, T)) |{ | version(LDC) pragma(inline, true); 32| return parseUnsignedImpl!(ulong, Range)(r, value); |} | | |/// |version (mir_test) unittest |{ | 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| assert(val == 123); 8| str = "0"; 8| assert(parse(str, val)); 8| assert(val == 0); 8| str = "9"; 8| assert(parse(str, val)); 8| assert(val == 9); 8| str = ""; 8| assert(!parse(str, val)); 8| assert(val == 0); 8| str = "text"; 8| assert(!parse(str, val)); 8| assert(val == 0); | } |} | |/// |version (mir_test) unittest |{ | import std.meta: AliasSeq; | foreach (T; AliasSeq!(byte, short, int, long)) | { 4| auto str = "-123"; 4| T val; 4| assert(parse(str, val)); 4| assert(val == -123); 4| str = "-0"; 4| assert(parse(str, val)); 4| assert(val == 0); 4| str = "-9text"; 4| assert(parse(str, val)); 4| assert(val == -9); 4| assert(str == "text"); | enum m = T.min + 0; 4| str = m.stringof; 4| assert(parse(str, val)); 4| assert(val == T.min); | } |} | |private bool parseUnsignedImpl(T, Range)(scope ref Range r, scope ref T value) | if(__traits(isUnsigned, T)) |{ | version(LDC) pragma(inline, true); | import mir.checkedint: addu, mulu; | 570| bool sign; |B: 583| if (!r.empty) | { 579| auto f = r.front + 0u; 1145| if (!sign && f == '+') | { 13| r.popFront; 13| sign = true; 13| goto B; | } 566| uint c = f - '0'; 566| if (c >= 10) 18| goto F; 548| T x = c; | for(;;) | { 1501| r.popFront; 1501| if (r.empty) 449| break; 1052| c = r.front - '0'; 1052| if (c >= 10) 99| break; 953| bool overflow; 953| T y = mulu(x, cast(uint)10, overflow); 953| if (overflow) 0000000| goto R; 953| x = y; 953| T z = addu(x, cast(uint)c, overflow); 953| if (overflow) 0000000| goto R; 953| x = z; | } 548| value = x; 548| return true; | } 22|F: value = 0; 22|R: return false; |} | |private bool parseSignedImpl(T, Range)(scope ref Range r, scope ref T value) | if(!__traits(isUnsigned, T)) |{ | version(LDC) pragma(inline, true); | import core.checkedint: negs; | import std.traits: Unsigned; | 264| bool sign; |B: 303| if (!r.empty) | { 296| auto f = r.front + 0u; 553| if (!sign && f == '-') | { 39| r.popFront; 39| sign = true; 39| goto B; | } 514| auto retu = (()@trusted=>parse(r, *cast(Unsigned!T*) &value))(); | // auto retu = false; 257| if (!retu) 4| goto R; 253| if (!sign) | { 214| if (value < 0) 0000000| goto R; | } | else | { 41| if (value < 0 && value != T.min) 0000000| goto R; 39| value = -value; | } 253| return true; | } 7|F: value = 0; 11|R: return false; |} source/mir/parse.d is 94% covered <<<<<< EOF # path=./source-mir-polynomial.lst |/++ |Polynomial ref-counted structure. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko |+/ |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` | +/ 1| this(RCArray!(const F) coefficients) | { | import core.lifetime: move; 1| 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; 6| auto ret = cast(typeof(return))0; 6| if (coefficients) | { 6| ptrdiff_t i = coefficients.length - 1; 6| assert(i >= 0); 6| auto c = cast()coefficients[i]; | static foreach (d; Iota!derivative) 6| c *= i - d; 6| ret = cast(typeof(return)) c; 18| while (--i >= cast(ptrdiff_t)derivative) | { 12| assert(i < coefficients.length); 12| c = cast()coefficients[i]; | static foreach (d; Iota!derivative) 8| c *= i - d; 12| ret *= x; 12| ret += c; | } | } 6| return ret; | } | } |} | |/// ditto |Polynomial!F polynomial(F)(RCArray!(const F) coefficients) |{ | import core.lifetime: move; 1| 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-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 scope return 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 scope return @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() scope inout @property | { 4| assert(this, getExcMsg); 4| return _value; | } | else | /// | pragma(inline, true) | ref inout(T) _get_value() 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| return _thisPtr == rhs._thisPtr; | } | | /// | sizediff_t opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc | { 0000000| return cast(void*)_thisPtr - cast(void*)rhs._thisPtr; | } | | /// | size_t toHash() @trusted scope const pure nothrow @nogc | { 0000000| return cast(size_t) _thisPtr; | } | | /// | ~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; } | } 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; | } 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 79% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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-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 scope return @trusted @property | { 32| return *cast(mir_rc_context*)_context; | } | | package void _reset() | { 15| _value = null; 15| _context = null; | } | | inout(void)* _thisPtr() inout scope return @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() scope inout @property | { 11| assert(this, getExcMsg); 11| return _value; | } | else | /// | pragma(inline, true) | ref inout(T) _get_value() 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| return _thisPtr == rhs._thisPtr; | } | | /// | sizediff_t opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc | { 0000000| return cast(void*)_thisPtr - cast(void*)rhs._thisPtr; | } | | /// | size_t toHash() @trusted scope const pure nothrow @nogc | { 0000000| return cast(size_t) _thisPtr; | } | | /// | ~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) |{ | 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, context._get_value, 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, context._get_value, 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; | } 1| assert(createRC!C.index == 34); |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | static interface I { ref double bar() @safe pure nothrow @nogc; } | static abstract class D { int index; } | static class C : D, I | { | double value; 2| ref double bar() @safe pure nothrow @nogc { return value; } 2| this(double d) { value = d; } | } 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 85% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.82-mir-core-source-mir-complex.lst |/++ |Complex numbers | |Authors: Ilya Yaroshenko |+/ |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; | } | |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) ; | } | |scope: | | /// | 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) | 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)); |} ../../../.dub/packages/mir-core-1.1.82/mir-core/source/mir/complex.d is 0% 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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments |Authors: Ilya Yaroshenko | |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_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_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_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)() 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()() const @property | { | size_t count = 1; | foreach(i; Iota!N) | count *= length!i; | return count; | } | | /// Shape of the concatenation. | size_t[N] shape()() 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)() 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)() | { | 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 front(size_t d = 0)() | { | 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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=./.dub-code-mir-algorithm-test-default-unittest-cov-posix.osx.darwin-x86_64-ldc_v1.27.1-CBCE2EF5AB377C2231318462D391AFCD_dub_test_root.lst |module dub_test_root; |import std.typetuple; |static import mir.algebraic_alias.json; |static import mir.algorithm.iteration; |static import mir.algorithm.setops; |static import mir.appender; |static import mir.array.allocation; |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.dec2flt_table; |static import mir.bignum.internal.ryu.generic_128; |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.format; |static import mir.format_impl; |static import mir.graph.tarjan; |static import mir.interpolate.constant; |static import mir.interpolate.generic; |static import mir.interpolate.linear; |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.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.string_map; |static import mir.timestamp; |static import mir.type_info; |alias allModules = TypeTuple!(mir.algebraic_alias.json, mir.algorithm.iteration, mir.algorithm.setops, mir.appender, mir.array.allocation, mir.bignum.decimal, mir.bignum.fixed, mir.bignum.fp, mir.bignum.integer, mir.bignum.internal.dec2flt_table, mir.bignum.internal.ryu.generic_128, mir.bignum.low_level_view, mir.container.binaryheap, mir.cpp_export.numeric, mir.date, mir.format, mir.format_impl, mir.graph.tarjan, mir.interpolate.constant, mir.interpolate.generic, mir.interpolate.linear, mir.interpolate.polynomial, mir.interpolate.spline, mir.interpolate.utility, mir.lob, mir.math.func.expdigamma, 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.string_map, 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-posix.osx.darwin-x86_64-ldc_v1.27.1-CBCE2EF5AB377C2231318462D391AFCD_dub_test_root.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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments | |Authors: Ilya Yaroshenko | |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_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_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_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_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_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_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)]) | { 71| 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; 88| 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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=fixes ./include/mir/small_string.h:9,11,14,16,18,23,24,40,41,43,46,48,49,52,54,56,58 ./include/mir/rcarray.h:2,4,17,20,23,25,28,31,34,36,43,44,46,52,54,56,57,59,61,62,64,68,69,76,78,82,84,85,88,90,92,95,96,97,98,101,102,103,106,111,116,118,119,121,124,126,130,132,133,136,138,139,142,144,145,148,150,151,166,173,181,184,187,193,196,199,201,202,205,207,209,212,213,215,218,219,221,224,225,227,230,231,233,236,237,239,242,243,245,248,249,251,254,255,257,261,262,264,268,269,271,273,274,276,280,281,283,287,288,290,292,293,295,297,298,306,308,313,320,326,330,333,335,336,341,343,344,351,353,354,359,361,362,367,369,370,371 ./include/mir/numeric.h:2,4,7,9,19,22,33,40,42,48,49,50,55,58,62,65,70,73,75,76,79,81,82,97,112,127,141,156,157,171,184,185 ./include/mir/ndslice.h:2,4,7,10,19,34,37,42,45,51,56,57,64,68,71,73,74,77,79,81,87,91,94,96,97,100,102,104,109,113,115,117,118,121,123,124,126,128,129,132,134,135,137,139,140,142,146,147,149,153,154,156,162,163,165,171,172,174,176,177,179,181,182,184,186,187,189,191,192,194,196,197,199,201,202,204,206,207,209,211,213,218,222,227,230,232,233,236,238,239,241,242,246,247,249,253,254,256,258,259,261,263,264,266,268,269,271,273,274,276,278,279,281,283,284,286,288,289,291,293,294,296,298,299,301,303,304,312,317,321,324,326,327,330,332,333,335,336,340,341,343,347,348,350,352,353,355,357,359,361,368,369 ./include/mir/slim_rcptr.h:2,4,6,10,12,15,17,19,23,25,28,29,34,36,40,42,43,46,56,57,59,62,65,69,71,72,75,77,78,81,83,84,86,104,106,109,111,112,115,118,119,122,125,126,127,129,132,135,137,139,141,142,143 ./include/mir/interpolate.h:2,5,7,14,16,25,28,32,33 ./include/mir/rcptr.h:2,4,11,13,17,19,25,27,29,31,39,41,44,48,51,53,56,58,59,61,64,66,68,70,72,74,77,81,82,83,87,89,93,95,97,105,107,112,114,115,118,128,129,131,134,137,142,144,145,148,150,151,154,156,157,159,162,169,170,173,180,181,199,203,205,211,213,215,223,225,230,232,233,236,246,247,249,252,255,260,262,263,266,268,269,272,274,275,277,280,287,288,291,298,299,317,320,322,328,330,332,340,342,347,349,350,353,363,364,366,369,372,377,379,380,383,385,386,389,391,392,394,397,404,405,408,415,416,434,436,439,441,442,445,448,449,452,457,458,459,462,465,466,469,472,473,474,476,479,482,484,486,488,489,490 ./include/mir/series.h:2,4,10,16,20,28,31,36,39,42,44,46,47,49,51,52,54,56,57,59,61,62,64,66,67,69,71,72,74,76,77,79,81,82,84,86,87,89,97,98,100,103,106,109,111,113,114,116,117,119,122,125,128,130,132,133,135,136,138,141,142,144,150,151,153,159,160,162,168,169,171,177,178,180,186,187,189,193,196,198,199,201,207,208,210,214,217,219,220,222,228,229,231,235,238,240,241,243,249,250,252,256,259,261,262,264,270,281,283,286,288,291,294,298,300,310,313,314,322,327,331,333,334,342,350,351 ./cpp_example/main.cpp:13,15,20,21,29,31,38,39,47,51,55,59,65,70,74,76,78,81,83,84,87,89,90,92,94,95,97,99,100,103,110,112,113,115,122,124,127,130,132,133,135,138,141,145,152,161,169,170,173,175,184,185,187,196,197,199,205,206,208,215,218,219,221,232,233,235,237,240,242,244,246,251 <<<<<< EOF