TRAVIS_OS_NAME=linux <<<<<< ENV benchmarks/ndslice/binarization.d benchmarks/ndslice/convolution.d benchmarks/ndslice/dot_product.d benchmarks/ndslice/euclidean_distance.d dub.json examples/data/stop_words examples/data/trndocs.dat examples/data/words examples/lda_hoffman_sparse.d examples/means_of_columns.d examples/median_filter.d index.d meson.build source/mir/glas/l1.d source/mir/glas/l2.d source/mir/glas/package.d source/mir/model/lda/hoffman.d source/mir/sparse/blas/axpy.d source/mir/sparse/blas/dot.d source/mir/sparse/blas/gemm.d source/mir/sparse/blas/gemv.d source/mir/sparse/blas/package.d source/mir/sparse/package.d subprojects/mir-algorithm.wrap subprojects/mir-core.wrap subprojects/mir-linux-kernel.wrap subprojects/mir-random.wrap <<<<<< network # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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. | ////////////////////////////////////// | auto index0 = [ | Date(2017, 01, 01), | Date(2017, 03, 01), | Date(2017, 04, 01)]; | | auto data0 = [1.0, 3, 4]; | auto series0 = index0.series(data0); | | auto index1 = [ | Date(2017, 01, 01), | Date(2017, 02, 01), | Date(2017, 05, 01)]; | | auto data1 = [10.0, 20, 50]; | auto series1 = index1.series(data1); | | ////////////////////////////////////// | // asSlice method | ////////////////////////////////////// | assert(series0 | .asSlice | // ref qualifier is optional | .map!((ref key, ref value) => key.yearMonthDay.month == value) | .all); | | ////////////////////////////////////// | // get* methods | ////////////////////////////////////// | | auto refDate = Date(2017, 03, 01); | auto missingDate = Date(2016, 03, 01); | | // default value | double defaultValue = 100; | assert(series0.get(refDate, defaultValue) == 3); | assert(series0.get(missingDate, defaultValue) == defaultValue); | | // Exceptions handlers | assert(series0.get(refDate) == 3); | assert(series0.get(refDate, new Exception("My exception msg")) == 3); | assert(series0.getVerbose(refDate) == 3); | assert(series0.getExtraVerbose(refDate, "My exception msg") == 3); | | assert(collectExceptionMsg!Exception( | series0.get(missingDate) | ) == "Series double[date]: Missing required key"); | | assert(collectExceptionMsg!Exception( | series0.get(missingDate, new Exception("My exception msg")) | ) == "My exception msg"); | | assert(collectExceptionMsg!Exception( | series0.getVerbose(missingDate) | ) == "Series double[date]: Missing 2016-03-01 key"); | | assert(collectExceptionMsg!Exception( | series0.getExtraVerbose(missingDate, "My exception msg") | ) == "My exception msg. Series double[date]: Missing 2016-03-01 key"); | | // assign with get* | series0.get(refDate) = 100; | assert(series0.get(refDate) == 100); | series0.get(refDate) = 3; | | // tryGet | double val; | assert(series0.tryGet(refDate, val)); | assert(val == 3); | assert(!series0.tryGet(missingDate, val)); | 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. | ////////////////////////////////////// | auto m0 = unionSeries(series0, series1); | auto m1 = unionSeries(series1, series0); // order is matter | | assert(m0.index == [ | Date(2017, 01, 01), | Date(2017, 02, 01), | Date(2017, 03, 01), | Date(2017, 04, 01), | Date(2017, 05, 01)]); | | assert(m0.index == m1.index); | assert(m0.data == [ 1, 20, 3, 4, 50]); | assert(m1.data == [10, 20, 3, 4, 50]); | | ////////////////////////////////////// | // Joins two time-series into a one with two columns. | ////////////////////////////////////// | auto u = [index0, index1].multiwayUnion; | auto index = u.move.array; | auto data = slice!double([index.length, 2], 0); // initialized to 0 value | auto series = index.series(data); | | series[0 .. $, 0][] = series0; // fill first column | series[0 .. $, 1][] = series1; // fill second column | | assert(data == [ | [1, 10], | [0, 20], | [3, 0], | [4, 0], | [0, 50]]); |} | |/// |unittest{ | | import mir.series; | | double[int] map; | map[1] = 4.0; | map[2] = 5.0; | map[4] = 6.0; | map[5] = 10.0; | map[10] = 11.0; | | const s = series(map); | | double value; | int key; | assert(s.tryGet(2, value) && value == 5.0); | assert(!s.tryGet(8, value)); | | assert(s.tryGetNext(2, value) && value == 5.0); | assert(s.tryGetPrev(2, value) && value == 5.0); | assert(s.tryGetNext(8, value) && value == 11.0); | assert(s.tryGetPrev(8, value) && value == 10.0); | assert(!s.tryGetFirst(8, 9, value)); | assert(s.tryGetFirst(2, 10, value) && value == 5.0); | assert(s.tryGetLast(2, 10, value) && value == 11.0); | assert(s.tryGetLast(2, 8, value) && value == 10.0); | | key = 2; assert(s.tryGetNextUpdateKey(key, value) && key == 2 && value == 5.0); | key = 2; assert(s.tryGetPrevUpdateKey(key, value) && key == 2 && value == 5.0); | key = 8; assert(s.tryGetNextUpdateKey(key, value) && key == 10 && value == 11.0); | key = 8; assert(s.tryGetPrevUpdateKey(key, value) && key == 5 && value == 10.0); | key = 2; assert(s.tryGetFirstUpdateLower(key, 10, value) && key == 2 && value == 5.0); | key = 10; assert(s.tryGetLastUpdateKey(2, key, value) && key == 10 && value == 11.0); | 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); | 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. |+/ |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: | | /// | this()(Slice!IndexIterator index, Slice!(Iterator, N, kind) data) | { | assert(index.length == data.length, "Series constructor: index and data lengths must be equal."); | _data = data; | _index = index._iterator; | } | | | /// Construct from null | this(typeof(null)) | { | _data = _data.init; | _index = _index.init; | } | | /// | bool opEquals(RIndexIterator, RIterator, size_t RN, SliceKind rkind, )(Series!(RIndexIterator, RIterator, RN, rkind) rhs) const | { | 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 | { | return _index.sliced(_data._lengths[0]); | } | | /// ditto | auto index()() @property @trusted const | { | 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 | { | return .lightScope(_index).sliced(_data._lengths[0]); | } | | private auto lightScopeIndex()() @property @trusted const | { | 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 | { | return _data; | } | | /// ditto | auto data()() @property @trusted const | { | return _data[]; | } | | /// ditto | auto data()() @property @trusted immutable | { | return _data[]; | } | | /// | typeof(this) opBinary(string op : "~")(typeof(this) rhs) | { | return unionSeries(this.lightScope, rhs.lightScope); | } | | /// ditto | auto opBinary(string op : "~")(const typeof(this) rhs) const | { | return unionSeries(this.lightScope, rhs.lightScope); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.date: Date; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// | auto index0 = [1,3,4]; | auto data0 = [1.0, 3, 4]; | auto series0 = index0.series(data0); | | auto index1 = [1,2,5]; | auto data1 = [10.0, 20, 50]; | auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. | auto m0 = series0 ~ series1; | auto m1 = series1 ~ series0; | | assert(m0.index == m1.index); | assert(m0.data == [ 1, 20, 3, 4, 50]); | 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. | ////////////////////////////////////// | auto index0 = [1,3,4]; | auto data0 = [1.0, 3, 4]; | auto series0 = index0.series(data0); | | auto index1 = [1,2,5]; | auto data1 = [10.0, 20, 50]; | const series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. | auto m0 = series0 ~ series1; | auto m1 = series1 ~ series0; | | assert(m0.index == m1.index); | assert(m0.data == [ 1, 20, 3, 4, 50]); | 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) | { | opIndexOpAssign!("", IndexIterator_, Iterator_, N_, kind_)(r); | } | | static if (doUnittest) | /// | version(mir_test) unittest | { | auto index = [1, 2, 3, 4]; | auto data = [10.0, 10, 10, 10]; | auto series = index.series(data); | | auto rindex = [0, 2, 4, 5]; | auto rdata = [1.0, 2, 3, 4]; | auto rseries = rindex.series(rdata); | | // series[] = rseries; | series[] = rseries; | 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) | { | auto l = this.lightScope; | auto r = rSeries.lightScope; | if (r.empty) | return; | if (l.empty) | return; | Unqual!(typeof(*r._index)) rf = *r._index; | Unqual!(typeof(*l._index)) lf = *l._index; | goto Begin; | R: | r.popFront; | if (r.empty) | goto End; | rf = *r._index; | Begin: | if (lf > rf) | goto R; | if (lf < rf) | goto L; | E: | static if (N != 1) | mixin("l.data.front[] " ~ op ~ "= r.data.front;"); | else | mixin("l.data.front " ~ op ~ "= r.data.front;"); | | r.popFront; | if (r.empty) | goto End; | rf = *r._index; | L: | l.popFront; | if (l.empty) | goto End; | lf = *l._index; | | if (lf < rf) | goto L; | if (lf == rf) | goto E; | goto R; | End: | } | | static if (doUnittest) | /// | version(mir_test) unittest | { | auto index = [1, 2, 3, 4]; | auto data = [10.0, 10, 10, 10]; | auto series = index.series(data); | | auto rindex = [0, 2, 4, 5]; | auto rdata = [1.0, 2, 3, 4]; | auto rseries = rindex.series(rdata); | | series[] += rseries; | 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) | { | return opIndex(opSlice(0, lightScopeIndex.transitionIndex(key))); | } | | /// ditto | auto lowerBound(Index)(auto ref scope const Index key) const | { | 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) | { | return opIndex(opSlice(lightScopeIndex.transitionIndex!"a <= b"(key), length)); | } | | /// ditto | auto upperBound(Index)(auto ref scope const Index key) const | { | 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))) | { | size_t idx = lightScopeIndex.transitionIndex(key); | 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 | { | size_t idx = lightScopeIndex.transitionIndex(key); | if (idx < _data._lengths[0] && _index[idx] == key) | { | return _data[idx]; | } | throw defaultExc!(); | } | | /// ditto | auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) @trusted | { | size_t idx = lightScopeIndex.transitionIndex(key); | if (idx < _data._lengths[0] && _index[idx] == key) | { | return _data[idx]; | } | 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; | 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; | 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 | { | size_t idx = lightScopeIndex.transitionIndex(key); | return idx < _data._lengths[0] && _index[idx] == key; | } | | /// | auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) @trusted | { | size_t idx = lightScopeIndex.transitionIndex(key); | bool cond = idx < _data._lengths[0] && _index[idx] == key; | static if (__traits(compiles, &_data[size_t.init])) | { | if (cond) | return &_data[idx]; | 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 | { | size_t idx = lightScopeIndex.transitionIndex(key); | auto cond = idx < _data._lengths[0] && _index[idx] == key; | if (cond) | val = _data[idx]; | return cond; | } | | /// ditto | bool tryGet(Index, Value)(Index key, scope ref Value val) const | { | 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) | { | size_t idx = lightScopeIndex.transitionIndex(key); | auto cond = idx < _data._lengths[0]; | if (cond) | val = _data[idx]; | return cond; | } | | /// ditto | bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) const | { | 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 | { | size_t idx = lightScopeIndex.transitionIndex(key); | auto cond = idx < _data._lengths[0]; | if (cond) | { | key = _index[idx]; | val = _data[idx]; | } | return cond; | } | | /// ditto | bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) const | { | 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) | { | size_t idx = lightScopeIndex.transitionIndex!"a <= b"(key) - 1; | auto cond = 0 <= sizediff_t(idx); | if (cond) | val = _data[idx]; | return cond; | } | | /// ditto | bool tryGetPrev(Index, Value)(auto ref scope const Index key, scope ref Value val) const | { | 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 | { | size_t idx = lightScopeIndex.transitionIndex!"a <= b"(key) - 1; | auto cond = 0 <= sizediff_t(idx); | if (cond) | { | key = _index[idx]; | val = _data[idx]; | } | return cond; | } | | /// ditto | bool tryGetPrevUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) const | { | 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 | { | size_t idx = lightScopeIndex.transitionIndex(lowerBound); | auto cond = idx < _data._lengths[0] && _index[idx] <= upperBound; | if (cond) | val = _data[idx]; | return cond; | } | | /// ditto | bool tryGetFirst(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const | { | 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 | { | size_t idx = lightScopeIndex.transitionIndex(lowerBound); | auto cond = idx < _data._lengths[0] && _index[idx] <= upperBound; | if (cond) | { | lowerBound = _index[idx]; | val = _data[idx]; | } | return cond; | } | | /// ditto | bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const | { | 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 | { | size_t idx = lightScopeIndex.transitionIndex!"a <= b"(upperBound) - 1; | auto cond = 0 <= sizediff_t(idx) && _index[idx] >= lowerBound; | if (cond) | val = _data[idx]; | return cond; | } | | /// ditto | bool tryGetLast(Index, Value)(Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) const | { | 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 | { | size_t idx = lightScopeIndex.transitionIndex!"a <= b"(upperBound) - 1; | auto cond = 0 <= sizediff_t(idx) && _index[idx] >= lowerBound; | if (cond) | { | upperBound = _index[idx]; | val = _data[idx]; | } | return cond; | } | | /// ditto | bool tryGetLastUpdateKey(Index, Value)(Index lowerBound, ref Index upperBound, scope ref Value val) const | { | 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) | 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) | { | return !length!dimension; | } | | /// ditto | size_t length(size_t dimension = 0)() const @property | if (dimension < N) | { | return _data.length!dimension; | } | | /// ditto | auto front(size_t dimension = 0)() @property | if (dimension < N) | { | assert(!empty!dimension); | static if (dimension) | { | return index.series(data.front!dimension); | } | else | { | 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) | { | assert(!empty!dimension); | static if (dimension == 0) | _index++; | _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) | { | assert(length!dimension >= n); | _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) | { | auto len = length!dimension; | n = n <= len ? n : len; | popBackExactly!dimension(n); | } | | /// ditto | Slice!(IotaIterator!size_t) opSlice(size_t dimension = 0)(size_t i, size_t j) const | if (dimension < N) | in | { | 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."; | assert(j - i <= _data._lengths[dimension], | "Series.opSlice!" ~ dimension.stringof ~ errorMsg); | } | do | { | return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /// ditto | size_t opDollar(size_t dimension = 0)() const | { | return _data.opDollar!dimension; | } | | /// ditto | auto opIndex(Slices...)(Slices slices) | if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) | { | static if (Slices.length == 0) | { | return this; | } | else | static if (is_Slice!(Slices[0])) | { | return index[slices[0]].series(data[slices]); | } | else | { | return index[slices[0]].observation(data[slices]); | } | } | | /// ditto | auto opIndex(Slices...)(Slices slices) const | if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices)) | { | 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; | this._data._structure = rvalue._data._structure; | swap(this._data._iterator, rvalue._data._iterator); | swap(this._index, rvalue._index); | 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; | this._data._structure = rvalue._data._structure; | this._data._iterator = rvalue._data._iterator.move; | this._index = rvalue._index.move; | 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 | { | return this = this.init; | } | | /// ditto | auto save()() @property | { | return this; | } | | /// | Series!(LightScopeOf!IndexIterator, LightScopeOf!Iterator, N, kind) lightScope()() @trusted @property | { | return typeof(return)(lightScopeIndex, _data.lightScope); | } | | /// ditto | Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() @trusted const @property | { | 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 | { | 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; | | if (f.spec != 's' && f.spec != '(') | throw new Exception("incompatible format character for Mir Series argument: %" ~ f.spec); | | enum defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; | auto fmtSpec = f.spec == '(' ? f.nested : defSpec; | | if (f.spec == 's') | put(w, f.seqBefore); | if (length) for (size_t i = 0;;) | { | auto fmt = Spec(fmtSpec); | fmt.writeUpToNextSpec(w); | if (f.flDash) | { | formatValue(w, index[i], fmt); | fmt.writeUpToNextSpec(w); | formatValue(w, data[i], fmt); | } | else | { | formatElement(w, index[i], fmt); | fmt.writeUpToNextSpec(w); | formatElement(w, data[i], fmt); | } | if (f.sep !is null) | { | fmt.writeUpToNextSpec(w); | if (++i != length) | put(w, f.sep); | else | break; | } | else | { | if (++i != length) | fmt.writeUpToNextSpec(w); | else | break; | } | } | if (f.spec == 's') | put(w, f.seqAfter); | } | | version(mir_test) | /// | unittest | { | import mir.series: series, sort; | auto s = ["b", "a"].series([9, 8]).sort; | | import std.conv : to; | assert(s.to!string == `["a":8, "b":9]`); | | import std.format : format; | assert("%s".format(s) == `["a":8, "b":9]`); | assert("%(%s %s | %)".format(s) == `"a" 8 | "b" 9`); | 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 |{ | auto index = [1, 2, 3, 4]; | auto data = [2.1, 3.4, 5.6, 7.8]; | auto series = index.series(data); | const cseries = series; | | assert(series.contains(2)); | assert( ()@trusted{ return (2 in series) is &data[1]; }() ); | | assert(!series.contains(5)); | assert( ()@trusted{ return (5 in series) is null; }() ); | | assert(series.lowerBound(2) == series[0 .. 1]); | assert(series.upperBound(2) == series[2 .. $]); | | assert(cseries.lowerBound(2) == cseries[0 .. 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 | auto seriesSlice = series[1 .. $ - 1]; | assert(seriesSlice.index == index[1 .. $ - 1]); | assert(seriesSlice.data == data[1 .. $ - 1]); | static assert(is(typeof(series) == typeof(seriesSlice))); | | /// indexing | assert(series[1] == observation(2, 3.4)); | | /// range primitives | assert(series.length == 4); | assert(series.front == observation(1, 2.1)); | | series.popFront; | assert(series.front == observation(2, 3.4)); | | series.popBackN(10); | assert(series.empty); |} | |/// 2-dimensional data |@safe pure version(mir_test) unittest |{ | import mir.date: Date; | import mir.ndslice.topology: canonical, iota; | | size_t row_length = 5; | | 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 | auto data = iota!int([index.length, row_length], 1); | | // canonical and universal ndslices are more flexible then contiguous | auto series = index.series(data.canonical); | | /// slicing | auto seriesSlice = series[1 .. $ - 1, 2 .. 4]; | assert(seriesSlice.index == index[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 | assert(series[1, 4] == observation(Date(2017, 02, 01), 10)); | assert(series[2] == observation(Date(2017, 03, 01), iota!int([row_length], 11))); | | /// range primitives | assert(series.length == 4); | assert(series.length!1 == 5); | | series.popFront!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*); | Map a = null; | auto b = Map(null); | assert(a.empty); | 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, | ) |{ | assert(index.length == data.length); | return Series!(IndexIterator, Iterator, N, kind)(index, data); |} | |/// ditto |auto series(Index, Data)(Index[] index, Data[] data) |{ | assert(index.length == data.length); | 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) |{ | assert(index.length == data.length); | 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; | const size_t length = aa.length; | alias R = typeof(return); | 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; | Series!(Unqual!K*, Unqual!V*) ret = series(length.uninitSlice!(Unqual!K), length.uninitSlice!(Unqual!V)); | auto it = ret; | foreach(ref kv; aa.byKeyValue) | { | import mir.conv: emplaceRef; | emplaceRef!K(it.index.front, kv.key.to!K); | emplaceRef!V(it._data.front, kv.value.to!V); | it.popFront; | } | .sort(ret); | static if (is(typeof(ret) == typeof(return))) | return ret; | else | 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))) |{ | 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 |{ | auto s = [1: 1.5, 3: 3.3, 2: 20.9].series; | assert(s.index == [1, 2, 3]); | assert(s.data == [1.5, 20.9, 3.3]); | assert(s.data[s.findIndex(2)] == 20.9); |} | |pure nothrow version(mir_test) unittest |{ | immutable aa = [1: 1.5, 3: 3.3, 2: 2.9]; | auto s = aa.series; | s = cast() s; | s = cast(const) s; | s = cast(immutable) s; | s = s; | assert(s.index == [1, 2, 3]); | assert(s.data == [1.5, 2.9, 3.3]); | 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); | const size_t length = aa.length; | auto ret = series(length.mininitRcarray!(Unqual!K).asSlice, length.mininitRcarray!(Unqual!V).asSlice); | auto it = ret.lightScope; | foreach(ref kv; aa.byKeyValue) | { | import mir.conv: emplaceRef; | emplaceRef!K(it.lightScopeIndex.front, kv.key.to!K); | emplaceRef!V(it._data.front, kv.value.to!V); | it.popFront; | } | import core.lifetime: move; | .sort(ret.lightScope); | static if (is(typeof(ret) == R)) | return ret; | else | 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))) |{ | 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 |{ | auto s = [1: 1.5, 3: 3.3, 2: 20.9].rcseries; | assert(s.index == [1, 2, 3]); | assert(s.data == [1.5, 20.9, 3.3]); | assert(s.data[s.findIndex(2)] == 20.9); |} | |// pure nothrow |version(mir_test) unittest |{ | import mir.rc.array; | immutable aa = [1: 1.5, 3: 3.3, 2: 2.9]; | auto s = aa.rcseries; | Series!(RCI!(const int), RCI!(const double)) c; | s = cast() s; | c = s; | s = cast(const) s; | s = cast(immutable) s; | s = s; | assert(s.index == [1, 2, 3]); | assert(s.data == [1.5, 2.9, 3.3]); | 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; | | immutable size_t length = aa.length; | | auto ret = series( | allocator.makeUninitSlice!(Unqual!K)(length), | allocator.makeUninitSlice!(Unqual!V)(length)); | | auto it = ret; | foreach(ref kv; aa.byKeyValue) | { | it.index.front.emplaceRef!K(kv.key); | it.data.front.emplaceRef!V(kv.value); | it.popFront; | } | | ret.sort; | static if (is(typeof(ret) == typeof(return))) | 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; | | InSituRegion!(1024) allocator; | auto aa = [1: 1.5, 3: 3.3, 2: 2.9]; | | auto s = (double[int] aa) @nogc @trusted pure nothrow { | return allocator.makeSeries(aa); | }(aa); | | auto indexArray = s.index.field; | auto dataArray = s.data.field; | | assert(s.index == [1, 2, 3]); | assert(s.data == [1.5, 2.9, 3.3]); | assert(s.data[s.findIndex(2)] == 2.9); | | allocator.dispose(indexArray); | 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"); | | V[K] aa; | aa.insertOrAssign = series; | return aa; |} | |/// |@safe pure version(mir_test) unittest |{ | import mir.ndslice; //iota and etc | import mir.series; | | auto s = ["c", "a", "b"].series(3.iota!int); | 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) |{ | auto idx = series.lightScopeIndex.transitionIndex(key); | if (idx < series._data._lengths[0] && series.index[idx] == key) | { | return idx; | } | return size_t.max; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | auto index = [1, 2, 3, 4].sliced; | auto data = [2.1, 3.4, 5.6, 7.8].sliced; | auto series = index.series(data); | | assert(series.data[series.findIndex(3)] == 5.6); | 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) |{ | auto idx = series.lightScopeIndex.transitionIndex(key); | auto bidx = series._data._lengths[0] - idx; | if (bidx && series.index[idx] == key) | { | return bidx; | } | return 0; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | auto index = [1, 2, 3, 4].sliced; | auto data = [2.1, 3.4, 5.6, 7.8].sliced; | auto series = index.series(data); | | if (auto bi = series.find(3)) | { | assert(series.data[$ - bi] == 5.6); | } | else | { | assert(0); | } | | 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 std.range.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, | ) | { | if (lhs.empty) | goto R0; | if (rhs.empty) | goto L1; | for(;;) | { | if (lhs.index.front < rhs.index.front) | { | lfun(lhs.index.front, lhs.data.front); | lhs.popFront; | if (lhs.empty) | goto R1; | continue; | } | else | if (lhs.index.front > rhs.index.front) | { | rfun(rhs.index.front, rhs.data.front); | rhs.popFront; | if (rhs.empty) | goto L1; | continue; | } | else | { | cfun(lhs.index.front, lhs.data.front, rhs.data.front); | lhs.popFront; | rhs.popFront; | if (rhs.empty) | goto L0; | if (lhs.empty) | goto R1; | continue; | } | } | | L0: | if (lhs.empty) | return; | L1: | do | { | lfun(lhs.index.front, lhs.data.front); | lhs.popFront; | } while(!lhs.empty); | return; | | R0: | if (rhs.empty) | return; | R1: | do | { | rfun(rhs.index.front, rhs.data.front); | rhs.popFront; | } while(!rhs.empty); | 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) | { | if (lhs.empty) | goto R0; | if (rhs.empty) | goto L1; | for(;;) | { | if (lhs.front < rhs.front) | { | lfun(lhs.front); | lhs.popFront; | if (lhs.empty) | goto R1; | continue; | } | else | if (lhs.front > rhs.front) | { | rfun(rhs.front); | rhs.popFront; | if (rhs.empty) | goto L1; | continue; | } | else | { | cfun(lhs.front, rhs.front); | lhs.popFront; | rhs.popFront; | if (rhs.empty) | goto L0; | if (lhs.empty) | goto R1; | continue; | } | } | | L0: | if (lhs.empty) | return; | L1: | do | { | lfun(lhs.front); | lhs.popFront; | } while(!lhs.empty); | return; | | R0: | if (rhs.empty) | return; | R1: | do | { | rfun(rhs.front); | rhs.popFront; | } while(!rhs.empty); | 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; | const length = troykaLength(lhs.index, rhs.index); | import mir.ndslice.allocation: uninitSlice; | auto index = length.uninitSlice!UI; | auto data = length.uninitSlice!UE; | auto ret = index.series(data); | alias algo = troykaSeriesImpl!(lfun, cfun, rfun); | algo!(I, E)(lhs.lightScope, rhs.lightScope, ret); | return (()@trusted => cast(R) ret)(); | } |} | |/// |version(mir_test) unittest |{ | import mir.ndslice; | auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1)); | auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0); | alias unionAlgorithm = troykaSeries!( | (key, left) => left, | (key, left, right) => left + right, | (key, right) => -right, | ); | auto c = unionAlgorithm(a, b); | assert(c.index == [0, 1, 2, 3, 4, 9]); | 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; | const length = troykaLength(lhs.index, rhs.index); | import mir.ndslice.allocation: uninitSlice; | auto ret = length.mininitRcarray!UI.asSlice.series(length.mininitRcarray!UE.asSlice); | alias algo = troykaSeriesImpl!(lfun, cfun, rfun); | algo!(I, E)(lhs.lightScope, rhs.lightScope, ret.lightScope); | return (()@trusted => *cast(R*) &ret)(); | } |} | |/// |version(mir_test) unittest |{ | import mir.ndslice; | auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1)); | auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0); | alias unionAlgorithm = rcTroykaSeries!( | (key, left) => left, | (key, left, right) => left + right, | (key, right) => -right, | ); | auto c = unionAlgorithm(a, b); | assert(c.index == [0, 1, 2, 3, 4, 9]); | 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) |{ | size_t length; | alias counter = (scope auto ref _) => ++length; | alias ccounter = (scope auto ref _l, scope auto ref _r) => ++length; | troykaGalop!(counter, ccounter, counter)(lhs, rhs); | 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; | troykaGalop!( | (auto ref key, auto ref value) { | uninitSlice.index.front.emplaceRef!I(key); | uninitSlice.data.front.emplaceRef!E(lfun(key, value)); | uninitSlice.popFront; | }, | (auto ref key, auto ref lvalue, auto ref rvalue) { | uninitSlice.index.front.emplaceRef!I(key); | uninitSlice.data.front.emplaceRef!E(cfun(key, lvalue, rvalue)); | uninitSlice.popFront; | }, | (auto ref key, auto ref value) { | uninitSlice.index.front.emplaceRef!I(key); | uninitSlice.data.front.emplaceRef!E(rfun(key, value)); | uninitSlice.popFront; | }, | )(lhs, rhs); | 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) |{ | return unionSeriesImplPrivate!false(seriesTuple); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.date: Date; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// | auto index0 = [1,3,4]; | auto data0 = [1.0, 3, 4]; | auto series0 = index0.series(data0); | | auto index1 = [1,2,5]; | auto data1 = [10.0, 20, 50]; | auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. | auto m0 = unionSeries(series0, series1); | auto m1 = unionSeries(series1, series0); | | assert(m0.index == m1.index); | assert(m0.data == [ 1, 20, 3, 4, 50]); | assert(m1.data == [10, 20, 3, 4, 50]); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.date: Date; | | ////////////////////////////////////// | // Constructs three time-series. | ////////////////////////////////////// | auto index0 = [1,3,4]; | auto data0 = [1.0, 3, 4]; | auto series0 = index0.series(data0); | | auto index1 = [1,2,5]; | auto data1 = [10.0, 20, 50]; | auto series1 = index1.series(data1); | | auto index2 = [1, 6]; | auto data2 = [100.0, 600]; | auto series2 = index2.series(data2); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | // Order is matter. | // The first slice has higher priority. | auto m0 = unionSeries(series0, series1, series2); | auto m1 = unionSeries(series1, series0, series2); | auto m2 = unionSeries(series2, series0, series1); | | assert(m0.index == m1.index); | assert(m0.index == m2.index); | assert(m0.data == [ 1, 20, 3, 4, 50, 600]); | assert(m1.data == [ 10, 20, 3, 4, 50, 600]); | 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) |{ | 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. | ////////////////////////////////////// | auto index0 = [1,3,4]; | | auto data0 = [1.0, 3, 4]; | auto series0 = index0.series(data0); | | auto index1 = [1,2,5]; | | auto data1 = [10.0, 20, 50]; | auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | | InSituRegion!(1024) allocator; | | auto m0 = allocator.makeUnionSeries(series0, series1); | auto m1 = allocator.makeUnionSeries(series1, series0); // order is matter | | assert(m0.index == m1.index); | assert(m0.data == [ 1, 20, 3, 4, 50]); | assert(m1.data == [10, 20, 3, 4, 50]); | | /// series should have the same sizes as after allocation | allocator.dispose(m0.index.field); | allocator.dispose(m0.data.field); | allocator.dispose(m1.index.field); | 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) |{ | return unionSeriesImplPrivate!true(seriesTuple); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.rc.array; | | ////////////////////////////////////// | // Constructs two time-series. | ////////////////////////////////////// | auto index0 = [1,3,4]; | | auto data0 = [1.0, 3, 4]; | auto series0 = index0.series(data0); | | auto index1 = [1,2,5]; | | auto data1 = [10.0, 20, 50]; | auto series1 = index1.series(data1); | | ////////////////////////////////////// | // Merges multiple series into one. | ////////////////////////////////////// | | Series!(RCI!int, RCI!double) m0 = rcUnionSeries(series0, series1); | Series!(RCI!int, RCI!double) m1 = rcUnionSeries(series1, series0); // order is matter | | assert(m0.index == m1.index); | assert(m0.data == [ 1, 20, 3, 4, 50]); | 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)); | | if(uninitSeries.length) | { | auto u = seriesTuple.multiwayUnion!"a.index < b.index"; | do | { | auto obs = u.front; | emplaceRef!I(uninitSeries.index.front, obs.index); | static if (N == 1) | emplaceRef!E(uninitSeries._data.front, obs.data); | else | each!(emplaceRef!E)(uninitSeries._data.front, obs.data); | u.popFront; | uninitSeries.popFront; | } | 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; | | Slice!IndexIterator[C] indeces; | foreach (i; Iota!C) | indeces[i] = seriesTuple[i].index; | | 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) | auto ret = (()@trusted => allocator[0].makeUninitSlice!UI(len).series(allocator[0].makeUninitSlice!UE(shape)))(); | else | 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 | auto ret = (()@trusted => | len | .mininitRcarray!UI | .asSlice | .series( | shape | .iota | .elementCount | .mininitRcarray!UE | .asSlice | .sliced(shape)))(); | } | | static if (C == 2) // fast path | { | alias algo = troykaSeriesImpl!( | ref (scope ref key, scope return ref left) => left, | ref (scope ref key, scope return ref left, scope return ref right) => left, | ref (scope ref key, scope return ref right) => right, | ); | algo!(I, E)(seriesTuple[0], seriesTuple[1], ret.lightScope); | } | else | { | unionSeriesImpl!(I, E)(seriesTuple, ret.lightScope); | } | | 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 |{ | auto s = series.lightScope; | foreach (i; 0 .. s.length) | { | aa[s.index[i]] = s.data[i]; | } | return aa; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | auto a = [1: 3.0, 4: 2.0]; | auto s = series([1, 2, 3], [10, 20, 30]); | a.insertOrAssign = s; | 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 |{ | auto s = series.lightScope; | foreach (i; 0 .. s.length) | { | if (s.index[i] in aa) | continue; | aa[s.index[i]] = s.data[i]; | } | return aa; |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | auto a = [1: 3.0, 4: 2.0]; | auto s = series([1, 2, 3], [10, 20, 30]); | a.insert = s; | 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; } ()); |} | |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/series.d has no code <<<<<< EOF # path=./source-mir-sparse-blas-axpy.lst |/** |License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). | |Authors: Ilya Yaroshenko |*/ |module mir.sparse.blas.axpy; | |import std.traits; |import mir.ndslice.slice; |import mir.sparse; |import mir.series; | |/++ |Constant times a vector plus a vector. | |Params: | x = sparse vector | y = dense vector | alpha = scalar |Returns: | `y = alpha * x + y` |+/ |void axpy( | CR, | V1 : Series!(I1, T1), | I1, T1, V2) |(in CR alpha, V1 x, V2 y) | if (isDynamicArray!V2 || isSlice!V2) |in |{ 28| if (x.index.length) 27| assert(x.index[$-1] < y.length); |} |body |{ | import mir.internal.utility; | 321| foreach (size_t i; 0 .. x.index.length) | { 79| auto j = x.index[i]; 79| y[j] = alpha * x.value[i] + y[j]; | } |} | |/// |unittest |{ | import mir.series; 1| auto x = series([0, 3, 5, 9, 10], [1.0, 3, 4, 9, 13]); 1| auto y = [0.0, 1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; 1| axpy(2.0, x, y); 1| assert(y == [2.0, 1.0, 2, 9, 4, 13, 6, 7, 8, 27, 36, 11, 12]); |} | |unittest |{ | import mir.series; 1| auto x = series([0, 3, 5, 9, 10], [1.0, 3, 4, 9, 13]); 1| auto y = [0.0, 1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; 1| axpy(2.0, x, y.sliced); 1| assert(y == [2.0, 1.0, 2, 9, 4, 13, 6, 7, 8, 27, 36, 11, 12]); |} | |unittest |{ 1| auto x = series([0, 3, 5, 9, 10], [1.0, 3, 4, 9, 13]); 1| auto y = [0.0, 1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; 1| axpy(2.0, x, y.slicedField); 1| assert(y == [2.0, 1.0, 2, 9, 4, 13, 6, 7, 8, 27, 36, 11, 12]); |} source/mir/sparse/blas/axpy.d is 100% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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) | assert(indices[i].length == to.length!i); |} |do { | static if (N == 1) | to[] = from[indices[0]]; | else | foreach (i; 0 .. indices[0].length) | { | 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 | auto a = iota!int(3, 4); | auto b = slice!int(2, 2); | copyMinor(a, b, [2, 1].sliced, [0, 3].sliced); | assert(b == [[8, 11], [4, 7]]); |} | |/++ |Reverses data in the 1D slice. |+/ |void reverseInPlace(Iterator)(Slice!Iterator slice) |{ | import mir.utility : swap; | foreach (i; 0 .. slice.length / 2) | swap(slice[i], slice[$ - (i + 1)]); |} | |/// |version(mir_test) |@safe pure nothrow |unittest |{ | import mir.ndslice; | auto s = 5.iota.slice; | s.reverseInPlace; | assert([4, 3, 2, 1, 0]); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/mutation.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.69-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 |+/ |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); |} ../../../.dub/packages/mir-core-1.1.69/mir-core/source/mir/primitives.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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; | return RightOp!(op, LightConstOf!T)(value.lightConst); | } | | auto lightImmutable()() immutable @property | { | import mir.qualifier; | return RightOp!(op, LightImmutableOf!T)(value.lightImmutable); | } | | this()(ref T v) { value = v; } | 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 | { | return mixin("value " ~ op ~ " right"); | } | } |} | |struct LeftOp(string op, T) |{ | T value; | | auto lightConst()() const @property | { | import mir.qualifier; | return LeftOp!(op, LightConstOf!T)(value.lightConst); | } | | auto lightImmutable()() immutable @property | { | import mir.qualifier; | return LeftOp!(op, LightImmutableOf!T)(value.lightImmutable); | } | | this()(ref T v) { value = v; } | 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; | return pow(left, value); | } | else | { | 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) |{ | 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...) |{ | auto ret = Unqual!E.min; | foreach(e; arr) | if (e > ret) | ret = e; | return ret; |} | |E minElem(E)(E[] arr...) |{ | auto ret = Unqual!E.max; | foreach(e; arr) | if (e < ret) | ret = e; | return ret; |} | |size_t sum()(size_t[] packs) |{ | size_t s; | foreach(pack; packs) | s += pack; | 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) |{ | int[N] mask; | return isValidPartialPermutationImpl(perm, mask); |} | |version(mir_test) unittest |{ | assert(isPermutation([0, 1])); | // all numbers 0..N-1 need to be part of the permutation | assert(!isPermutation([1, 2])); | assert(!isPermutation([0, 2])); | // duplicates are not allowed | assert(!isPermutation([0, 1, 1])); | | size_t[0] emptyArr; | // empty permutations are not allowed either | assert(!isPermutation(emptyArr)); |} | |bool isValidPartialPermutation(size_t N)(in size_t[] perm) |{ | int[N] mask; | return isValidPartialPermutationImpl(perm, mask); |} | |private bool isValidPartialPermutationImpl(size_t N)(in size_t[] perm, ref int[N] mask) |{ | if (perm.length == 0) | return false; | foreach (j; perm) | { | if (j >= N) | return false; | if (mask[j]) //duplicate | return false; | mask[j] = true; | } | 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) |{ 0000000| size_t length = lengths[0]; | foreach (i; Iota!(1, N)) 0000000| length *= lengths[i]; 0000000| return length; |} | |pure nothrow version(mir_test) unittest |{ | const size_t[3] lengths = [3, 4, 5]; | assert(lengthsProduct(lengths) == 60); | 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..$])); | } |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/internal.d is 0% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.69-mir-core-source-mir-math-common.lst |/++ |Common floating point math functions. | |This module has generic LLVM-oriented API compatible with all D compilers. | |License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) |Authors: Ilya Yaroshenko, Phobos Team |+/ |module mir.math.common; | |import mir.internal.utility: isComplex, isComplexOf, isFloatingPoint; | |version(LDC) |{ | static import ldc.attributes; | | private alias AliasSeq(T...) = T; | | /++ | Functions attribute, an alias for `AliasSeq!(llvmFastMathFlag("contract"));`. | | $(UL | $(LI 1. Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). ) | ) | | Note: Can be used with all compilers. | +/ | alias fmamath = AliasSeq!(ldc.attributes.llvmFastMathFlag("contract")); | | /++ | Functions attribute, an alias for `AliasSeq!(llvmFastMathFlag("fast"))`. | | It is similar to $(LREF fastmath), but does not allow unsafe-fp-math. | This flag does NOT force LDC to use the reciprocal of an argument rather than perform division. | | This flag is default for string lambdas. | | Note: Can be used with all compilers. | +/ | alias optmath = AliasSeq!(ldc.attributes.llvmFastMathFlag("fast")); | | /++ | Functions attribute, an alias for `ldc.attributes.fastmath` . | | $(UL | | $(LI 1. Enable optimizations that make unsafe assumptions about IEEE math (e.g. that addition is associative) or may not work for all input ranges. | These optimizations allow the code generator to make use of some instructions which would otherwise not be usable (such as fsin on X86). ) | | $(LI 2. Allow optimizations to assume the arguments and result are not NaN. | Such optimizations are required to retain defined behavior over NaNs, | but the value of the result is undefined. ) | | $(LI 3. Allow optimizations to assume the arguments and result are not +$(BACKTICK)-inf. | Such optimizations are required to retain defined behavior over +$(BACKTICK)-Inf, | but the value of the result is undefined. ) | | $(LI 4. Allow optimizations to treat the sign of a zero argument or result as insignificant. ) | | $(LI 5. Allow optimizations to use the reciprocal of an argument rather than perform division. ) | | $(LI 6. Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). ) | | $(LI 7. Allow algebraically equivalent transformations that may dramatically change results in floating point (e.g. reassociate). ) | ) | | Note: Can be used with all compilers. | +/ | alias fastmath = ldc.attributes.fastmath; |} |else |enum |{ | /++ | Functions attribute, an alias for `AliasSeq!(llvmFastMathFlag("contract"));`. | | $(UL | $(LI Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). ) | ) | | Note: Can be used with all compilers. | +/ | fmamath, | | /++ | Functions attribute, an alias for `AliasSeq!(llvmAttr("unsafe-fp-math", "false"), llvmFastMathFlag("fast"))`. | | It is similar to $(LREF fastmath), but does not allow unsafe-fp-math. | This flag does NOT force LDC to use the reciprocal of an argument rather than perform division. | | This flag is default for string lambdas. | | Note: Can be used with all compilers. | +/ | optmath, | | /++ | Functions attribute, an alias for `ldc.attributes.fastmath = AliasSeq!(llvmAttr("unsafe-fp-math", "true"), llvmFastMathFlag("fast"))` . | | $(UL | | $(LI Enable optimizations that make unsafe assumptions about IEEE math (e.g. that addition is associative) or may not work for all input ranges. | These optimizations allow the code generator to make use of some instructions which would otherwise not be usable (such as fsin on X86). ) | | $(LI Allow optimizations to assume the arguments and result are not NaN. | Such optimizations are required to retain defined behavior over NaNs, | but the value of the result is undefined. ) | | $(LI Allow optimizations to assume the arguments and result are not +$(BACKTICK)-inf. | Such optimizations are required to retain defined behavior over +$(BACKTICK)-Inf, | but the value of the result is undefined. ) | | $(LI Allow optimizations to treat the sign of a zero argument or result as insignificant. ) | | $(LI Allow optimizations to use the reciprocal of an argument rather than perform division. ) | | $(LI Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). ) | | $(LI Allow algebraically equivalent transformations that may dramatically change results in floating point (e.g. reassociate). ) | ) | | Note: Can be used with all compilers. | +/ | fastmath |} | |version(LDC) |{ | nothrow @nogc pure @safe: | | pragma(LDC_intrinsic, "llvm.sqrt.f#") | /// | T sqrt(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.sin.f#") | /// | T sin(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.cos.f#") | /// | T cos(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.powi.f#") | /// | T powi(T)(in T val, int power) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.pow.f#") | /// | T pow(T)(in T val, in T power) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.exp.f#") | /// | T exp(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.log.f#") | /// | T log(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.fma.f#") | /// | T fma(T)(T vala, T valb, T valc) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.fabs.f#") | /// | T fabs(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.floor.f#") | /// | T floor(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.exp2.f#") | /// | T exp2(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.log10.f#") | /// | T log10(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.log2.f#") | /// | T log2(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.ceil.f#") | /// | T ceil(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.trunc.f#") | /// | T trunc(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.rint.f#") | /// | T rint(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.nearbyint.f#") | /// | T nearbyint(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.copysign.f#") | /// | T copysign(T)(in T mag, in T sgn) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.round.f#") | /// | T round(T)(in T val) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.fmuladd.f#") | /// | T fmuladd(T)(in T vala, in T valb, in T valc) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.minnum.f#") | /// | T fmin(T)(in T vala, in T valb) if (isFloatingPoint!T); | | pragma(LDC_intrinsic, "llvm.maxnum.f#") | /// | T fmax(T)(in T vala, in T valb) if (isFloatingPoint!T); |} |else version(GNU) |{ | static import gcc.builtins; | | // Calls GCC builtin for either float (suffix "f"), double (no suffix), or real (suffix "l"). | private enum mixinGCCBuiltin(string fun) = | `static if (T.mant_dig == float.mant_dig) return gcc.builtins.__builtin_`~fun~`f(x);`~ | ` else static if (T.mant_dig == double.mant_dig) return gcc.builtins.__builtin_`~fun~`(x);`~ | ` else static if (T.mant_dig == real.mant_dig) return gcc.builtins.__builtin_`~fun~`l(x);`~ | ` else static assert(0);`; | | // As above but for two-argument function. | private enum mixinGCCBuiltin2(string fun) = | `static if (T.mant_dig == float.mant_dig) return gcc.builtins.__builtin_`~fun~`f(x, y);`~ | ` else static if (T.mant_dig == double.mant_dig) return gcc.builtins.__builtin_`~fun~`(x, y);`~ | ` else static if (T.mant_dig == real.mant_dig) return gcc.builtins.__builtin_`~fun~`l(x, y);`~ | ` else static assert(0);`; | | /// | T sqrt(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`sqrt`); } | /// | T sin(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`sin`); } | /// | T cos(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`cos`); } | /// | T pow(T)(in T x, in T power) if (isFloatingPoint!T) { alias y = power; mixin(mixinGCCBuiltin2!`pow`); } | /// | T powi(T)(in T x, int power) if (isFloatingPoint!T) { alias y = power; mixin(mixinGCCBuiltin2!`powi`); } | /// | T exp(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`exp`); } | /// | T log(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`log`); } | /// | T fabs(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`fabs`); } | /// | T floor(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`floor`); } | /// | T exp2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`exp2`); } | /// | T log10(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`log10`); } | /// | T log2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`log2`); } | /// | T ceil(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`ceil`); } | /// | T trunc(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`trunc`); } | /// | T rint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`rint`); } | /// | T nearbyint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`nearbyint`); } | /// | T copysign(T)(in T mag, in T sgn) if (isFloatingPoint!T) { alias y = sgn; mixin(mixinGCCBuiltin2!`copysign`); } | /// | T round(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`round`); } | /// | T fmuladd(T)(in T a, in T b, in T c) if (isFloatingPoint!T) | { | static if (T.mant_dig == float.mant_dig) | return gcc.builtins.__builtin_fmaf(a, b, c); | else static if (T.mant_dig == double.mant_dig) | return gcc.builtins.__builtin_fma(a, b, c); | else static if (T.mant_dig == real.mant_dig) | return gcc.builtins.__builtin_fmal(a, b, c); | else | static assert(0); | } | version(mir_core_test) | unittest { assert(fmuladd!double(2, 3, 4) == 2 * 3 + 4); } | /// | T fmin(T)(in T x, in T y) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin2!`fmin`); } | /// | T fmax(T)(in T x, in T y) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin2!`fmax`); } |} |else static if (__VERSION__ >= 2082) // DMD 2.082 onward. |{ | static import std.math; | static import core.stdc.math; | | // Calls either std.math or cmath function for either float (suffix "f") | // or double (no suffix). std.math will always be used during CTFE or for | // arguments with greater than double precision or if the cmath function | // is impure. | private enum mixinCMath(string fun) = | `pragma(inline, true); | static if (!is(typeof(std.math.`~fun~`(0.5f)) == float) | && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f)))) | if (!__ctfe) | { | static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x); | else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x); | } | return std.math.`~fun~`(x);`; | | // As above but for two-argument function (both arguments must be floating point). | private enum mixinCMath2(string fun) = | `pragma(inline, true); | static if (!is(typeof(std.math.`~fun~`(0.5f, 0.5f)) == float) | && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f, 0.5f)))) | if (!__ctfe) | { | static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x, y); | else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x, y); | } | return std.math.`~fun~`(x, y);`; | | // Some std.math functions have appropriate return types (float, | // double, real) without need for a wrapper. We can alias them | // directly but we leave the templates afterwards for documentation | // purposes and so explicit template instantiation still works. | // The aliases will always match before the templates. | // Note that you cannot put any "static if" around the aliases or | // compilation will fail due to conflict with the templates! | alias sqrt = std.math.sqrt; | alias sin = std.math.sin; | alias cos = std.math.cos; | alias exp = std.math.exp; | //alias fabs = std.math.fabs; | alias floor = std.math.floor; | alias exp2 = std.math.exp2; | alias ceil = std.math.ceil; | alias rint = std.math.rint; | | /// | T sqrt(T)(in T x) if (isFloatingPoint!T) { return std.math.sqrt(x); } | /// | T sin(T)(in T x) if (isFloatingPoint!T) { return std.math.sin(x); } | /// | T cos(T)(in T x) if (isFloatingPoint!T) { return std.math.cos(x); } | /// | T pow(T)(in T x, in T power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); } | /// | T powi(T)(in T x, int power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); } | /// | T exp(T)(in T x) if (isFloatingPoint!T) { return std.math.exp(x); } | /// | T log(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log`); } | /// | T fabs(T)(in T x) if (isFloatingPoint!T) { return std.math.fabs(x); } | /// | T floor(T)(in T x) if (isFloatingPoint!T) { return std.math.floor(x); } | /// | T exp2(T)(in T x) if (isFloatingPoint!T) { return std.math.exp2(x); } | /// | T log10(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log10`); } | /// | T log2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log2`); } | /// | T ceil(T)(in T x) if (isFloatingPoint!T) { return std.math.ceil(x); } | /// | T trunc(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`trunc`); } | /// | T rint(T)(in T x) if (isFloatingPoint!T) { return std.math.rint(x); } | /// | T nearbyint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`nearbyint`); } | /// | T copysign(T)(in T mag, in T sgn) if (isFloatingPoint!T) | { | alias x = mag; | alias y = sgn; | mixin(mixinCMath2!`copysign`); | } | /// | T round(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`round`); } | /// | T fmuladd(T)(in T a, in T b, in T c) if (isFloatingPoint!T) { return a * b + c; } | version(mir_core_test) | unittest { assert(fmuladd!double(2, 3, 4) == 2 * 3 + 4); } | /// | T fmin(T)(in T x, in T y) if (isFloatingPoint!T) | { | version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798 | { | version (CRuntime_Microsoft) | mixin(mixinCMath2!`fmin`); | else | return std.math.fmin(x, y); | } | else | mixin(mixinCMath2!`fmin`); | } | /// | T fmax(T)(in T x, in T y) if (isFloatingPoint!T) | { | version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798 | { | version (CRuntime_Microsoft) | mixin(mixinCMath2!`fmax`); | else | return std.math.fmax(x, y); | } | else | mixin(mixinCMath2!`fmax`); | } | | version (mir_core_test) @nogc nothrow pure @safe unittest | { | // Check the aliases are correct. | static assert(is(typeof(sqrt(1.0f)) == float)); | static assert(is(typeof(sin(1.0f)) == float)); | static assert(is(typeof(cos(1.0f)) == float)); | static assert(is(typeof(exp(1.0f)) == float)); | static assert(is(typeof(fabs(1.0f)) == float)); | static assert(is(typeof(floor(1.0f)) == float)); | static assert(is(typeof(exp2(1.0f)) == float)); | static assert(is(typeof(ceil(1.0f)) == float)); | static assert(is(typeof(rint(1.0f)) == float)); | | auto x = sqrt!float(2.0f); // Explicit template instantiation still works. | auto fp = &sqrt!float; // Can still take function address. | | // Test for DMD linker problem with fmin on Windows. | static assert(is(typeof(fmin!float(1.0f, 1.0f)))); | static assert(is(typeof(fmax!float(1.0f, 1.0f)))); | } |} |else // DMD version prior to 2.082 |{ | static import std.math; | static import core.stdc.math; | | // Calls either std.math or cmath function for either float (suffix "f") | // or double (no suffix). std.math will always be used during CTFE or for | // arguments with greater than double precision or if the cmath function | // is impure. | private enum mixinCMath(string fun) = | `pragma(inline, true); | static if (!is(typeof(std.math.`~fun~`(0.5f)) == float) | && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f)))) | if (!__ctfe) | { | static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x); | else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x); | } | return std.math.`~fun~`(x);`; | | // As above but for two-argument function (both arguments must be floating point). | private enum mixinCMath2(string fun) = | `pragma(inline, true); | static if (!is(typeof(std.math.`~fun~`(0.5f, 0.5f)) == float) | && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f, 0.5f)))) | if (!__ctfe) | { | static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x, y); | else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x, y); | } | return std.math.`~fun~`(x, y);`; | | // Some std.math functions have appropriate return types (float, | // double, real) without need for a wrapper. | alias sqrt = std.math.sqrt; | | /// | T sqrt(T)(in T x) if (isFloatingPoint!T) { return std.math.sqrt(x); } | /// | T sin(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`sin`); } | /// | T cos(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`cos`); } | /// | T pow(T)(in T x, in T power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); } | /// | T powi(T)(in T x, int power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); } | /// | T exp(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`exp`); } | /// | T log(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log`); } | /// | T fabs(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`fabs`); } | /// | T floor(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`floor`); } | /// | T exp2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`exp2`); } | /// | T log10(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log10`); } | /// | T log2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log2`); } | /// | T ceil(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`ceil`); } | /// | T trunc(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`trunc`); } | /// | T rint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`rint`); } | /// | T nearbyint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`nearbyint`); } | /// | T copysign(T)(in T mag, in T sgn) if (isFloatingPoint!T) | { | alias x = mag; | alias y = sgn; | mixin(mixinCMath2!`copysign`); | } | /// | T round(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`round`); } | /// | T fmuladd(T)(in T a, in T b, in T c) if (isFloatingPoint!T) { return a * b + c; } | version(mir_core_test) | unittest { assert(fmuladd!double(2, 3, 4) == 2 * 3 + 4); } | /// | T fmin(T)(in T x, in T y) if (isFloatingPoint!T) | { | version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798 | { | version (CRuntime_Microsoft) | mixin(mixinCMath2!`fmin`); | else | return std.math.fmin(x, y); | } | else | mixin(mixinCMath2!`fmin`); | } | /// | T fmax(T)(in T x, in T y) if (isFloatingPoint!T) | { | version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798 | { | version (CRuntime_Microsoft) | mixin(mixinCMath2!`fmax`); | else | return std.math.fmax(x, y); | } | else | mixin(mixinCMath2!`fmax`); | } | | version (mir_core_test) @nogc nothrow pure @safe unittest | { | // Check the aliases are correct. | static assert(is(typeof(sqrt(1.0f)) == float)); | auto x = sqrt!float(2.0f); // Explicit template instantiation still works. | auto fp = &sqrt!float; // Can still take function address. | | // Test for DMD linker problem with fmin on Windows. | static assert(is(typeof(fmin!float(1.0f, 1.0f)))); | static assert(is(typeof(fmax!float(1.0f, 1.0f)))); | } |} | |version (mir_core_test) |@nogc nothrow pure @safe unittest |{ | import mir.math: PI, feqrel; | assert(feqrel(pow(2.0L, -0.5L), cos(PI / 4)) >= real.mant_dig - 1); |} | |/// Overload for cdouble, cfloat and creal |@optmath auto fabs(T)(in T x) | if (isComplex!T) |{ | return x.re * x.re + x.im * x.im; |} | |/// |version(mir_core_test) unittest |{ | static struct UserComplex(T) { T re, im; } | alias _cdouble = UserComplex!double; | assert(fabs(_cdouble(3, 4)) == 25); |} | |/++ |Computes whether two values are approximately equal, admitting a maximum |relative difference, and a maximum absolute difference. |Params: | lhs = First item to compare. | rhs = Second item to compare. | maxRelDiff = Maximum allowable difference relative to `rhs`. Defaults to `0.5 ^^ 20`. | maxAbsDiff = Maximum absolute difference. Defaults to `0.5 ^^ 20`. | |Returns: | `true` if the two items are equal or approximately equal under either criterium. |+/ |bool approxEqual(T)(const T lhs, const T rhs, const T maxRelDiff = T(0x1p-20f), const T maxAbsDiff = T(0x1p-20f)) | if (isFloatingPoint!T) |{ | if (rhs == lhs) // infs | return true; | auto diff = fabs(lhs - rhs); | if (diff <= maxAbsDiff) | return true; | diff /= fabs(rhs); | return diff <= maxRelDiff; |} | |/// |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | assert(approxEqual(1.0, 1.0000001)); | assert(approxEqual(1.0f, 1.0000001f)); | assert(approxEqual(1.0L, 1.0000001L)); | | assert(approxEqual(10000000.0, 10000001)); | assert(approxEqual(10000000f, 10000001f)); | assert(!approxEqual(100000.0L, 100001L)); |} | |/// ditto |bool approxEqual(T)(const T lhs, const T rhs, float maxRelDiff = 0x1p-20f, float maxAbsDiff = 0x1p-20f) | if (isComplexOf!(T, float)) |{ | return approxEqual(lhs.re, rhs.re, maxRelDiff, maxAbsDiff) | && approxEqual(lhs.im, rhs.im, maxRelDiff, maxAbsDiff); |} | |/// ditto |bool approxEqual(T)(const T lhs, const T rhs, double maxRelDiff = 0x1p-20f, double maxAbsDiff = 0x1p-20f) | if (isComplexOf!(T, double)) |{ | return approxEqual(lhs.re, rhs.re, maxRelDiff, maxAbsDiff) | && approxEqual(lhs.im, rhs.im, maxRelDiff, maxAbsDiff); |} | |/// ditto |bool approxEqual(T)(const T lhs, const T rhs, real maxRelDiff = 0x1p-20f, real maxAbsDiff = 0x1p-20f) | if (isComplexOf!(T, real)) |{ | return approxEqual(lhs.re, rhs.re, maxRelDiff, maxAbsDiff) | && approxEqual(lhs.im, rhs.im, maxRelDiff, maxAbsDiff); |} | |/// Complex types works as `approxEqual(l.re, r.re) && approxEqual(l.im, r.im)` |@safe pure nothrow @nogc version(mir_core_test) unittest |{ | import mir.internal.utility: isComplexOf; | static struct UserComplex(T) { T re, im; } | alias _cdouble = UserComplex!double; | | static assert(isComplexOf!(_cdouble, double)); | | assert(approxEqual(_cdouble(1.0, 1), _cdouble(1.0000001, 1), 1.0000001)); | assert(!approxEqual(_cdouble(100000.0L, 0), _cdouble(100001L, 0))); |} ../../../.dub/packages/mir-core-1.1.69/mir-core/source/mir/math/common.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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; | string str; | foreach(i, T; Types) | static if (hasZeroShiftFieldMember!T) | str ~= "_fields[" ~ i.stringof ~ "].assumeFieldsHaveZeroShift, "; | else | str ~= "_fields[" ~ i.stringof ~ "], "; | return str; |} | |auto MapField__map(Field, alias fun, alias fun1)(ref MapField!(Field, fun) f) |{ | import core.lifetime: move; | import mir.functional: pipe; | 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 | { | 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...)) | { | 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 _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; | auto f = ZipField!(LeftField, RightField)(field._fields[0]._field, field._fields[1]._field)._mapField!fun; | return f.bitwiseField!(typeof(f), I); | } | else | static if (__traits(hasMember, Field, "__map")) | return Field.__map!fun(field); | else | 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; | 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; | 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 | { | 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 | { return cast(T) _value; } |} | |/++ |`BitField` is used by $(SUBREF topology, bitwise). |+/ |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 | { | 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; | return bt!(Field, I)(_field, index) != 0; | } | | bool opIndexAssign()(bool value, size_t index) | { | import mir.bitop: bta; | bta!(Field, I)(_field, index, value); | 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; | ushort[10] data; | auto f = FieldIterator!(BitField!(ushort*))(0, BitField!(ushort*)(data.ptr)); | f[123] = true; | f++; | 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; | auto f = _mapField!(naryFun!"~a")(move(field._field)); | return f.bitwiseField!(typeof(f), I); | } | else | { | 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 | { | return BitpackField!(LightConstOf!Field, pack)(.lightConst(_field)); | } | | /// | auto lightImmutable()() immutable @property | { | return BitpackField!(LightImmutableOf!Field, pack)(.lightImmutable(_field)); | } | | I opIndex()(size_t index) | { | index *= pack; | size_t start = index % bits; | index /= bits; | auto ret = (_field[index] >>> start) & mask; | static if (bits % pack) | { | sizediff_t end = start - (bits - pack); | if (end > 0) | ret ^= cast(I)(_field[index + 1] << (bits - end)) >>> (bits - pack); | } | return cast(I) ret; | } | | I opIndexAssign()(I value, size_t index) | { | import std.traits: Unsigned; | assert(cast(Unsigned!I)value <= mask); | index *= pack; | size_t start = index % bits; | index /= bits; | _field[index] = cast(I)((_field[index] & ~(mask << start)) ^ (value << start)); | static if (bits % pack) | { | sizediff_t end = start - (bits - pack); | if (end > 0) | _field[index + 1] = cast(I)((_field[index + 1] & ~((I(1) << end) - 1)) ^ (value >>> (pack - end))); | } | 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; | ushort[10] data; | auto f = FieldIterator!(BitpackField!(ushort*, 6))(0, BitpackField!(ushort*, 6)(data.ptr)); | f[0] = cast(ushort) 31; | f[1] = cast(ushort) 13; | f[2] = cast(ushort) 8; | f[3] = cast(ushort) 43; | f[4] = cast(ushort) 28; | f[5] = cast(ushort) 63; | f[6] = cast(ushort) 39; | f[7] = cast(ushort) 23; | f[8] = cast(ushort) 44; | | assert(f[0] == 31); | assert(f[1] == 13); | assert(f[2] == 8); | assert(f[3] == 43); | assert(f[4] == 28); | assert(f[5] == 63); | assert(f[6] == 39); | assert(f[7] == 23); | assert(f[8] == 44); | assert(f[9] == 0); | assert(f[10] == 0); | assert(f[11] == 0); |} | |unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology; | import mir.ndslice.sorting; | uint[2] data; | auto packed = data[].sliced.bitpack!18; | assert(packed.length == 3); | packed[0] = 5; | packed[1] = 3; | packed[2] = 2; | packed.sort; | assert(packed[0] == 2); | assert(packed[1] == 3); | 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 | { | auto fields = _fields.lightConst; | 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; | auto fields = _fields; | T r = _initialValue; | if (!fields.empty) do | { | r = cast(T) fun(r, fields.front[index]); | fields.popFront; | } | while(!fields.empty); | return r; | } |} | |/// |struct CycleField(Field) |{ | import mir.ndslice.slice: Slice; | |@optmath: | /// Cycle length | size_t _length; | /// | Field _field; | | /// | auto lightConst()() const @property | { | auto field = .lightConst(_field); | 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) | { | 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 | { | auto field = .lightConst(_field); | 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) | { | 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 | { | return ndIotaField!N(_lengths); | } | | /// | auto lightImmutable()() const @property | { | return ndIotaField!N(_lengths); | } | | /// | size_t[N] opIndex()(size_t index) const | { | size_t[N] indices; | foreach_reverse (i; Iota!(N - 1)) | { | indices[i + 1] = index % _lengths[i]; | index /= _lengths[i]; | } | indices[0] = index; | 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 | { 0000000| 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 | { 0000000| sizediff_t d = _length - 1; 0000000| auto v = typeof(T.init.re)(d - index); 0000000| auto w = typeof(T.init.re)(index); 0000000| v /= d; 0000000| w /= d; 0000000| auto a = v * _start; 0000000| auto b = w * _stop; 0000000| return a + b; | } | |@optmath: | | /// | size_t length(size_t dimension = 0)() scope const @property | if (dimension == 0) | { 0000000| return _length; | } | | /// | size_t[1] shape()() scope const @property @nogc | { | return [_length]; | } |} | |/++ |Magic square field. |+/ |struct MagicField |{ |@optmath: |@safe pure nothrow @nogc: | | /++ | Magic Square size. | +/ | size_t _n; | |scope const: | | /// | MagicField lightConst()() @property | { 0000000| return this; | } | | /// | MagicField lightImmutable()() @property | { | return this; | } | | /// | size_t length(size_t dimension = 0)() @property | if(dimension <= 2) | { 0000000| return _n * _n; | } | | /// | size_t[1] shape() @property | { 0000000| return [_n * _n]; | } | | /// | size_t opIndex(size_t index) | { | pragma(inline, false); 0000000| auto d = index / _n; 0000000| auto m = index % _n; 0000000| if (_n & 1) | { | //d = _n - 1 - d; // MATLAB synchronization | //index = d * _n + m; // ditto 0000000| auto r = (index + 1 - d + (_n - 3) / 2) % _n; 0000000| auto c = (_n * _n - index + 2 * d) % _n; 0000000| return r * _n + c + 1; | } | else 0000000| if ((_n & 2) == 0) | { 0000000| auto a = (d + 1) & 2; 0000000| auto b = (m + 1) & 2; 0000000| return a != b ? index + 1: _n * _n - index; | } | else | { 0000000| auto n = _n / 2 ; 0000000| size_t shift; 0000000| ptrdiff_t q; 0000000| ptrdiff_t p = m - n; 0000000| if (p >= 0) | { 0000000| m = p; 0000000| shift = n * n; 0000000| auto mul = m <= n / 2 + 1; 0000000| q = d - n; 0000000| if (q >= 0) | { 0000000| d = q; 0000000| mul = !mul; | } 0000000| if (mul) | { 0000000| shift *= 2; | } | } | else | { 0000000| auto mul = m < n / 2; 0000000| q = d - n; 0000000| if (q >= 0) | { 0000000| d = q; 0000000| mul = !mul; | } 0000000| if (d == n / 2 && (m == 0 || m == n / 2)) | { 0000000| mul = !mul; | } 0000000| if (mul) | { 0000000| shift = n * n * 3; | } | } 0000000| index = d * n + m; 0000000| auto r = (index + 1 - d + (n - 3) / 2) % n; 0000000| auto c = (n * n - index + 2 * d) % n; 0000000| 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; | } |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/field.d is 0% covered <<<<<< EOF # path=./.dub-code-mir-test-unittest-unittest-cov-linux.posix-x86-dmd_v2.096.1-1790227D545C37DC9FF324808847527B_dub_test_root.lst |module dub_test_root; |import std.typetuple; |static import mir.glas.l1; |static import mir.glas.l2; |static import mir.model.lda.hoffman; |static import mir.sparse.blas.axpy; |static import mir.sparse.blas.dot; |static import mir.sparse.blas.gemm; |static import mir.sparse.blas.gemv; |alias allModules = TypeTuple!(mir.glas.l1, mir.glas.l2, mir.model.lda.hoffman, mir.sparse.blas.axpy, mir.sparse.blas.dot, mir.sparse.blas.gemm, mir.sparse.blas.gemv); | | 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-test-unittest-unittest-cov-linux.posix-x86-dmd_v2.096.1-1790227D545C37DC9FF324808847527B_dub_test_root.d is 0% covered <<<<<< EOF # path=./source-mir-sparse-blas-package.lst |/** |License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). | |Authors: Ilya Yaroshenko |*/ |module mir.sparse.blas; | |public import mir.sparse.blas.dot; |public import mir.sparse.blas.axpy; |public import mir.sparse.blas.gemv; |public import mir.sparse.blas.gemm; source/mir/sparse/blas/package.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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 { | assert(matrix.length == vec.length); |} |do { | import mir.conv: to; | | foreach (v; matrix) | { | F a = vec.front; | vec.popFront; | F x = to!F(1); | foreach (ref e; v) | { | e = x; | x *= a; | } | } |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice: sliced; | import mir.ndslice.allocation: uninitSlice; | auto x = [1.0, 2, 3, 4, 5].sliced; | auto v = uninitSlice!double(x.length, x.length); | v.fillVandermonde(x); | 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]]); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/filling.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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; | | // // 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 | { | implementation = null; | return this; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!int map = ["key" : 1]; | map = null; | } | | /++ | Initialize the associtave array with default value. | +/ | this()(typeof(null) aa) @safe pure nothrow @nogc | { | implementation = null; | } | | version(mir_test) static if (is(T == int)) | /// Usefull for default funcion argument. | @safe pure unittest | { | StringMap!int map = null; // | } | | /++ | Constructs an associative array using keys and values from the builtin associative array | Complexity: `O(n log(n))` | +/ | this()(T[string] aa) @trusted pure nothrow | { | this(aa.keys, aa.values); | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!int map = ["key" : 1]; | assert(map.findPos("key") == 0); | } | | /++ | 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))` | +/ | this()(string[] keys, T[] values) @trusted pure nothrow | { | assert(keys.length == values.length); | implementation = new Impl(keys, values); | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | auto keys = ["ba", "a"]; | auto values = [1.0, 3.0]; | auto map = StringMap!double(keys, values); | assert(map.keys is keys); | assert(map.values is values); | } | | /++ | Returns: number of elements in the table. | +/ | size_t length()() @safe pure nothrow @nogc const @property | { | return implementation ? implementation.length : 0; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!double map; | assert(map.length == 0); | map["a"] = 3.0; | assert(map.length == 1); | map["c"] = 4.0; | assert(map.length == 2); | assert(map.remove("c")); | assert(map.length == 1); | assert(!map.remove("c")); | assert(map.length == 1); | assert(map.remove("a")); | 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 | { | return implementation ? implementation.keys : null; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!double map; | assert(map.keys == []); | map["c"] = 4.0; | assert(map.keys == ["c"]); | map["a"] = 3.0; | assert(map.keys == ["c", "a"]); | map.remove("c"); | assert(map.keys == ["a"]); | map.remove("a"); | assert(map.keys == []); | map["c"] = 4.0; | 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 | { | return implementation ? implementation.values : null; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!double map; | assert(map.values == []); | map["c"] = 4.0; | assert(map.values == [4.0]); | map["a"] = 3.0; | assert(map.values == [4.0, 3.0]); | map.values[0]++; | assert(map.values == [5.0, 3.0]); | map.remove("c"); | assert(map.values == [3.0]); | map.remove("a"); | assert(map.values == []); | map["c"] = 4.0; | 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; | | return !implementation ? 0 : min( | implementation.keys.capacity, | implementation.values.capacity, | implementation.indices.capacity, | ); | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { | StringMap!double map; | assert(map.capacity == 0); | map["c"] = 4.0; | assert(map.capacity >= 1); | map["a"] = 3.0; | assert(map.capacity >= 2); | map.remove("c"); | map.assumeSafeAppend; | 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; | | if (_expect(!implementation, false)) | { | implementation = new Impl; | } | | auto keysV = implementation.keys; | auto keysVCaacity = keysV.reserve(newcapacity); | implementation._keys = keysV.ptr; | | auto valuesV = implementation.values; | auto valuesCapacity = valuesV.reserve(newcapacity); | implementation._values = valuesV.ptr; | | auto indicesV = implementation.indices; | auto indicesCapacity = indicesV.reserve(newcapacity); | implementation._indices = indicesV.ptr; | | return min( | keysVCaacity, | valuesCapacity, | indicesCapacity, | ); | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { | StringMap!double map; | auto capacity = map.reserve(10); | assert(capacity >= 10); | assert(map.capacity == capacity); | map["c"] = 4.0; | assert(map.capacity == capacity); | map["a"] = 3.0; | assert(map.capacity >= 2); | assert(map.remove("c")); | capacity = map.reserve(20); | assert(capacity >= 20); | 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 | { | if (implementation) | { | implementation.keys.assumeSafeAppend; | implementation.values.assumeSafeAppend; | implementation.indices.assumeSafeAppend; | } | return this; | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { | StringMap!double map; | map["c"] = 4.0; | map["a"] = 3.0; | assert(map.capacity >= 2); | map.remove("c"); | assert(map.capacity == 0); | map.assumeSafeAppend; | assert(map.capacity >= 2); | } | | /++ | Removes all remaining keys and values from an associative array. | | Complexity: `O(1)` | +/ | void clear()() @safe pure nothrow @nogc | { | if (implementation) | { | implementation._length = 0; | implementation._lengthTable = implementation._lengthTable[0 .. 0]; | } | | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { | StringMap!double map; | map["c"] = 4.0; | map["a"] = 3.0; | map.clear; | assert(map.length == 0); | assert(map.capacity == 0); | map.assumeSafeAppend; | 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 | { | size_t index; | if (implementation && implementation.findIndex(key, index)) | { | implementation.removeAt(index); | return true; | } | return false; | } | | version(mir_test) static if (is(T == int)) | /// | unittest | { | StringMap!double map; | map["a"] = 3.0; | map["c"] = 4.0; | assert(map.remove("c")); | assert(!map.remove("c")); | assert(map.remove("a")); | assert(map.length == 0); | assert(map.capacity == 0); | 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 | { | if (!implementation) | return -1; | size_t index; | if (!implementation.findIndex(key, index)) | return -1; | return implementation._indices[index]; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!double map; | map["c"] = 3.0; | map["La"] = 4.0; | map["a"] = 5.0; | | assert(map.findPos("C") == -1); | assert(map.findPos("c") == 0); | assert(map.findPos("La") == 1); | assert(map.findPos("a") == 2); | | map.remove("c"); | | assert(map.findPos("c") == -1); | assert(map.findPos("La") == 0); | 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 | { | if (!implementation) | return null; | size_t index; | if (!implementation.findIndex(key, index)) | return null; | return implementation._values + index; | } | | version(mir_test) static if (is(T == int)) | /// | @system nothrow pure unittest | { | StringMap!double map; | assert(("c" in map) is null); | map["c"] = 3.0; | 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 | { | size_t index; | if (implementation && implementation.findIndex(key, index)) | { | assert (index < length); | index = implementation._indices[index]; | assert (index < length); | return implementation._values[index]; | } | import mir.exception: MirException; | throw new MirException("No member: ", key); | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!double map; | map["c"] = 3.0; | map["La"] = 4.0; | map["a"] = 5.0; | | map["La"] += 10; | 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()(T value, string key) @trusted pure nothrow | { | size_t index; | if (_expect(!implementation, false)) | { | implementation = new Impl; | } | else | { | if (key.length + 1 < implementation.lengthTable.length) | { | if (implementation.findIndex(key, index)) | { | assert (index < length); | index = implementation._indices[index]; | assert (index < length); | implementation._values[index] = move(value); | return implementation._values[index]; | } | assert (index <= length); | index = index < length ? implementation.indices[index] : length; | assert (index <= length); | } | else | { | index = length; | } | } | assert (index <= length); | implementation.insertAt(key, move(value), index); | index = implementation._indices[index]; | 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) | { | size_t index; | if (implementation && implementation.findIndex(key, index)) | { | assert (index < length); | index = implementation.indices[index]; | assert (index < length); | return implementation.values[index]; | } | return defaultValue; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!int map; | map["c"] = 3; | assert(map.get("c", 1) == 3); | 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; | size_t index; | if (_expect(!implementation, false)) | { | implementation = new Impl; | } | else | { | if (key.length + 1 < implementation.lengthTable.length) | { | if (implementation.findIndex(key, index)) | { | assert (index < length); | index = implementation.indices[index]; | assert (index < length); | return implementation.values[index]; | } | assert (index <= length); | index = index < length ? implementation.indices[index] : length; | assert (index <= length); | } | else | { | index = length; | } | } | assert (index <= length); | implementation.insertAt(key, value, index); | index = implementation.indices[index]; | return implementation.values[index]; | } | | version(mir_test) static if (is(T == int)) | /// | @safe pure unittest | { | StringMap!int map = ["aa": 1]; | int add3(ref int x) { x += 3; return x; } | assert(add3(map.require("aa", 10)) == 4); | assert(add3(map.require("bb", 10)) == 13); | assert(map.require("a", 100)); | assert(map.require("aa") == 4); | assert(map.require("bb") == 13); | 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 | { | T[string] ret; | foreach (i; 0 .. length) | { | ret[implementation.keys[i]] = implementation.values[i]; | } | 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 | { | StringMap!int map = ["k": 1]; | int[string] aa = map.toAA; | assert(aa["k"] == 1); | } |} | |version(mir_test) |/// |unittest |{ | StringMap!int table; | table["L"] = 3; | table["A"] = 2; | table["val"] = 1; | assert(table.keys == ["L", "A", "val"]); | assert(table.values == [3, 2, 1]); | assert(table["A"] == 2); | table.values[2] += 10; | assert(table["A"] == 2); | assert(table["L"] == 3); | assert(table["val"] == 11); | assert(table.keys == ["L", "A", "val"]); | assert(table.values == [3, 2, 11]); | table.remove("A"); | assert(table.keys == ["L", "val"]); | assert(table.values == [3, 11]); | assert(table["L"] == 3); | assert(table["val"] == 11); |} | |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; | | /++ | +/ | this()(string[] keys, T[] values) @trusted pure nothrow | { | import mir.array.allocation: array; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: iota, indexed; | import mir.string_table: smallerStringFirst; | | assert(keys.length == values.length); | if (keys.length == 0) | return; | _length = keys.length; | _keys = keys.ptr; | _values = values.ptr; | _indices = keys.length.iota!U.array.ptr; | auto sortedKeys = _keys.indexed(indices); | sortedKeys.sort!smallerStringFirst; | size_t maxKeyLength; | foreach (ref key; keys) | if (key.length > maxKeyLength) | maxKeyLength = key.length; | _lengthTable = new U[maxKeyLength + 2]; | | size_t ski; | foreach (length; 0 .. maxKeyLength + 1) | { | while(ski < sortedKeys.length && sortedKeys[ski].length == length) | ski++; | _lengthTable[length + 1] = cast(U)ski; | } | } | | void insertAt()(string key, T value, size_t i) @trusted | { | pragma(inline, false); | | assert(i <= length); | { | auto a = keys; | a ~= key; | _keys = a.ptr; | } | { | auto a = values; | a ~= move(value); | _values = a.ptr; | } | { | auto a = indices; | a ~= 0; | _indices = a.ptr; | | if (__ctfe) | { | foreach_reverse (idx; i .. length) | { | _indices[idx + 1] = _indices[idx]; | } | } | else | { | import core.stdc.string: memmove; | memmove(_indices + i + 1, _indices + i, (length - i) * U.sizeof); | } | assert(length <= U.max); | _indices[i] = cast(U)length; | _length++; | } | { | if (key.length + 2 <= lengthTable.length) | { | ++lengthTable[key.length + 1 .. $]; | } | else | { | auto oldLen = _lengthTable.length; | _lengthTable.length = key.length + 2; | auto oldVal = oldLen ? _lengthTable[oldLen - 1] : 0; | _lengthTable[oldLen .. key.length + 1] = oldVal; | _lengthTable[key.length + 1] = oldVal + 1; | } | } | } | | void removeAt()(size_t i) | { | assert(i < length); | auto j = _indices[i]; | assert(j < length); | { | --_lengthTable[_keys[j].length + 1 .. $]; | } | { | if (__ctfe) | { | foreach (idx; i .. length) | { | _indices[idx] = _indices[idx + 1]; | _indices[idx] = _indices[idx + 1]; | } | } | else | { | import core.stdc.string: memmove; | memmove(_indices + i, _indices + i + 1, (length - 1 - i) * U.sizeof); | } | foreach (ref elem; indices[0 .. $ - 1]) | if (elem > j) | elem--; | } | { | 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; | destroy!false(_values[j]); | memmove(_keys + j, _keys + j + 1, (length - 1 - j) * string.sizeof); | memmove(_values + j, _values + j + 1, (length - 1 - j) * T.sizeof); | emplaceRef(_values[length - 1]); | } | } | _length--; | _lengthTable = _lengthTable[0 .. length ? _keys[_indices[length - 1]].length + 2 : 0]; | } | | size_t length()() @safe pure nothrow @nogc const @property | { | return _length; | } | | inout(string)[] keys()() @trusted inout @property | { | return _keys[0 .. _length]; | } | | inout(T)[] values()() @trusted inout @property | { | return _values[0 .. _length]; | } | | inout(U)[] indices()() @trusted inout @property | { | return _indices[0 .. _length]; | } | | inout(U)[] lengthTable()() @trusted inout @property | { | 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; | 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 | | auto low = _lengthTable[key.length] + 0u; | auto high = _lengthTable[key.length + 1] + 0u; | while (low < high) | { | auto mid = (low + high) / 2; | | import core.stdc.string: memcmp; | int r = void; | | if (__ctfe) | r = __cmp(key, _keys[_indices[mid]]); | else | r = memcmp(key.ptr, _keys[_indices[mid]].ptr, key.length); | | if (r == 0) | { | index = mid; | return true; | } | if (r > 0) | low = mid + 1; | else | high = mid; | } | index = low; | } | return false; | } |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/string_map.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.69-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.69/mir-core/source/mir/functional.d has no code <<<<<< EOF # path=./source-mir-sparse-package.lst |/++ |$(H2 Sparse Tensors) | |License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |Authors: Ilya Yaroshenko |+/ |module mir.sparse; | |import std.traits; |import std.meta; | |import mir.ndslice.slice; |public import mir.ndslice.field: SparseField; |public import mir.ndslice.iterator: ChopIterator, FieldIterator; |public import mir.series: isSeries, Series, mir_series, series; |public import mir.ndslice.slice: CoordinateValue, Slice, mir_slice; |public import mir.ndslice.topology: chopped; | |//TODO: replace with `static foreach` |private 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)); |} | |/++ |Sparse tensors represented in Dictionary of Keys (DOK) format. | |Params: | N = dimension count | lengths = list of dimension lengths |Returns: | `N`-dimensional slice composed of indeces |See_also: $(LREF Sparse) |+/ |Sparse!(T, N) sparse(T, size_t N)(size_t[N] lengths...) |{ 12| T[size_t] table; 12| table[0] = 0; 12| table.remove(0); 12| assert(table !is null); 12| with (typeof(return)) return FieldIterator!(SparseField!T)(0, SparseField!T(table)).sliced(lengths); |} | |/// |pure unittest |{ 1| auto slice = sparse!double(2, 3); 1| slice[0][] = 1; 1| slice[0, 1] = 2; 1| --slice[0, 0]; 1| slice[1, 2] += 4; | 1| assert(slice == [[0, 2, 1], [0, 0, 4]]); | | import std.range.primitives: isRandomAccessRange; | static assert(isRandomAccessRange!(Sparse!(double, 2))); | | import mir.ndslice.slice: Slice, DeepElementType; | static assert(is(Sparse!(double, 2) : Slice!(FieldIterator!(SparseField!double), 2))); | static assert(is(DeepElementType!(Sparse!(double, 2)) == double)); |} | |/++ |Returns unsorted forward range of (coordinate, value) pairs. | |Params: | slice = sparse slice with pure structure. Any operations on structure of a slice are not allowed. |+/ |auto byCoordinateValue(size_t N, T)(Slice!(FieldIterator!(SparseField!T), N) slice) |{ | struct ByCoordinateValue | { | private sizediff_t[N-1] _strides; | mixin _sparse_range_methods!(typeof(slice._iterator._field._table.byKeyValue())); | | auto front() @property | {S: 5| assert(!_range.empty); 5| auto iv = _range.front; 5| size_t index = iv.key; 10| if (!(_l <= index && index < _r)) | { 0000000| _range.popFront; 0000000| goto S; | } 5| CoordinateValue!(T, N) ret; | foreach (i; Iota!(0, N - 1)) | { 5| ret.index[i] = index / _strides[i]; 5| index %= _strides[i]; | } 5| ret.index[N - 1] = index; 5| ret.value = iv.value; 5| return ret; | } | } 1| size_t l = slice._iterator._index; 1| size_t r = l + slice.elementCount; 1| size_t length = slice._iterator._field._table.byKey.countInInterval(l, r); 1| return ByCoordinateValue(slice.strides[0..N-1], length, l, r, slice._iterator._field._table.byKeyValue); |} | |/// |pure unittest |{ | import mir.array.allocation: array; | import mir.ndslice.sorting: sort; | alias CV = CoordinateValue!(double, 2); | 1| auto slice = sparse!double(3, 3); 1| slice[] = [[0, 2, 1], [0, 0, 4], [6, 7, 0]]; 1| assert(slice.byCoordinateValue.array.sort() == [ | CV([0, 1], 2), | CV([0, 2], 1), | CV([1, 2], 4), | CV([2, 0], 6), | CV([2, 1], 7)]); |} | |/++ |Returns unsorted forward range of coordinates. |Params: | slice = sparse slice with pure structure. Any operations on structure of a slice are not allowed. |+/ |auto byCoordinate(T, size_t N)(Slice!(FieldIterator!(SparseField!T), N) slice) |{ | struct ByCoordinate | { | private sizediff_t[N-1] _strides; | mixin _sparse_range_methods!(typeof(slice._iterator._field._table.byKey())); | | auto front() @property | {S: 5| assert(!_range.empty); 5| size_t index = _range.front; 10| if (!(_l <= index && index < _r)) | { 0000000| _range.popFront; 0000000| goto S; | } 5| size_t[N] ret; | foreach (i; Iota!(0, N - 1)) | { 5| ret[i] = index / _strides[i]; 5| index %= _strides[i]; | } 5| ret[N - 1] = index; 5| return ret; | } | } 1| size_t l = slice._iterator._index; 1| size_t r = l + slice.elementCount; 1| size_t length = slice._iterator._field._table.byKey.countInInterval(l, r); 1| return ByCoordinate(slice.strides[0 .. N - 1], length, l, r, slice._iterator._field._table.byKey); |} | |/// |pure unittest |{ | import mir.array.allocation: array; | import mir.ndslice.sorting: sort; | 1| auto slice = sparse!double(3, 3); 1| slice[] = [[0, 2, 1], [0, 0, 4], [6, 7, 0]]; 1| assert(slice.byCoordinate.array.sort() == [ | [0, 1], | [0, 2], | [1, 2], | [2, 0], | [2, 1]]); |} | |/++ |Returns unsorted forward range of values. |Params: | slice = sparse slice with pure structure. Any operations on structure of a slice are not allowed. |+/ |auto onlyByValue(T, size_t N)(Slice!(FieldIterator!(SparseField!T), N) slice) |{ | struct ByValue | { | mixin _sparse_range_methods!(typeof(slice._iterator._field._table.byKeyValue())); | | auto front() @property | {S: 5| assert(!_range.empty); 5| auto iv = _range.front; 5| size_t index = iv.key; 10| if (!(_l <= index && index < _r)) | { 0000000| _range.popFront; 0000000| goto S; | } 5| return iv.value; | } | } 1| size_t l = slice._iterator._index; 1| size_t r = l + slice.elementCount; 1| size_t length = slice._iterator._field._table.byKey.countInInterval(l, r); 1| return ByValue(length, l, r, slice._iterator._field._table.byKeyValue); |} | |/// |pure unittest |{ | import mir.array.allocation: array; | import mir.ndslice.sorting: sort; | 1| auto slice = sparse!double(3, 3); 1| slice[] = [[0, 2, 1], [0, 0, 4], [6, 7, 0]]; 1| assert(slice.onlyByValue.array.sort() == [1, 2, 4, 6, 7]); |} | |pragma(inline, false) |private size_t countInInterval(Range)(Range range, size_t l, size_t r) |{ 3| size_t count; 51| foreach(ref i; range) 30| if (l <= i && i < r) 15| count++; 3| return count; |} | |private mixin template _sparse_range_methods(Range) |{ | private size_t _length, _l, _r; | private Range _range; | | void popFront() | { 15| assert(!_range.empty); 15| _range.popFront; 15| _length--; | } | | bool empty() const @property | { 0000000| return _length == 0; | } | | auto save() @property | { 0000000| auto ret = this; 0000000| ret._range = ret._range.save; 0000000| return ret; | } | | size_t length() const @property | { 3| return _length; | } |} | |/++ |Returns compressed tensor. |Note: allocates using GC. |+/ |auto compress(I = uint, J = size_t, SliceKind kind, size_t N, Iterator)(Slice!(Iterator, N, kind) slice) | if (N > 1) |{ 8| return compressWithType!(DeepElementType!(Slice!(Iterator, N, kind)), I, J)(slice); |} | |/// Sparse tensor compression |unittest |{ 1| auto sparse = sparse!double(5, 3); 1| sparse[] = | [[0, 2, 1], | [0, 0, 4], | [0, 0, 0], | [6, 0, 9], | [0, 0, 5]]; | 1| auto crs = sparse.compressWithType!double; | // assert(crs.iterator._field == CompressedField!(double, uint, uint)( | // 3, | // [2, 1, 4, 6, 9, 5], | // [1, 2, 2, 0, 2, 2], | // [0, 2, 3, 3, 5, 6])); |} | |/// Sparse tensor compression |unittest |{ 1| auto sparse = sparse!double(5, 8); 1| sparse[] = | [[0, 2, 0, 0, 0, 0, 0, 1], | [0, 0, 0, 0, 0, 0, 0, 4], | [0, 0, 0, 0, 0, 0, 0, 0], | [6, 0, 0, 0, 0, 0, 0, 9], | [0, 0, 0, 0, 0, 0, 0, 5]]; | 1| auto crs = sparse.compressWithType!double; | // assert(crs.iterator._field == CompressedField!(double, uint, uint)( | // 8, | // [2, 1, 4, 6, 9, 5], | // [1, 7, 7, 0, 7, 7], | // [0, 2, 3, 3, 5, 6])); |} | |/// Dense tensor compression |unittest |{ | import mir.ndslice.allocation: slice; | 1| auto sl = slice!double(5, 3); 1| sl[] = | [[0, 2, 1], | [0, 0, 4], | [0, 0, 0], | [6, 0, 9], | [0, 0, 5]]; | 1| auto crs = sl.compressWithType!double; | | // assert(crs.iterator._field == CompressedField!(double, uint, uint)( | // 3, | // [2, 1, 4, 6, 9, 5], | // [1, 2, 2, 0, 2, 2], | // [0, 2, 3, 3, 5, 6])); |} | |/// Dense tensor compression |unittest |{ | import mir.ndslice.allocation: slice; | 1| auto sl = slice!double(5, 8); 1| sl[] = | [[0, 2, 0, 0, 0, 0, 0, 1], | [0, 0, 0, 0, 0, 0, 0, 4], | [0, 0, 0, 0, 0, 0, 0, 0], | [6, 0, 0, 0, 0, 0, 0, 9], | [0, 0, 0, 0, 0, 0, 0, 5]]; | 1| auto crs = sl.compress; | // assert(crs.iterator._field == CompressedField!(double, uint, uint)( | // 8, | // [2, 1, 4, 6, 9, 5], | // [1, 7, 7, 0, 7, 7], | // [0, 2, 3, 3, 5, 6])); |} | |/++ |Returns compressed tensor with different element type. |Note: allocates using GC. |+/ |Slice!(ChopIterator!(J*, Series!(I*, V*)), N - 1) | compressWithType(V, I = uint, J = size_t, T, size_t N) | (Slice!(FieldIterator!(SparseField!T), N) slice) | if (is(T : V) && N > 1 && isUnsigned!I) |{ | import mir.array.allocation: array; | import mir.ndslice.sorting: sort; | import mir.ndslice.topology: iota; 8| auto compressedData = slice | .iterator | ._field | ._table | .series!(size_t, T, I, V); 8| auto pointers = new J[slice.shape[0 .. N - 1].iota.elementCount + 1]; 16| size_t k = 1, shift; 8| pointers[0] = 0; 8| pointers[1] = 0; 8| const rowLength = slice.length!(N - 1); 233| if(rowLength) foreach (ref index; compressedData.index.field) | { | for(;;) | { 90| sizediff_t newIndex = index - shift; 90| if (newIndex >= rowLength) | { 23| pointers[k + 1] = pointers[k]; 23| shift += rowLength; 23| k++; 23| continue; | } 67| index = cast(I)newIndex; 67| pointers[k] = cast(J) (pointers[k] + 1); 67| break; | } | | } 8| pointers[k + 1 .. $] = pointers[k]; 8| return compressedData.chopped(pointers); |} | | |/// ditto |Slice!(ChopIterator!(J*, Series!(I*, V*)), N - 1) | compressWithType(V, I = uint, J = size_t, Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (!is(Iterator : FieldIterator!(SparseField!ST), ST) && is(DeepElementType!(Slice!(Iterator, N, kind)) : V) && N > 1 && isUnsigned!I) |{ | import std.array: appender; | import mir.ndslice.topology: pack, flattened; 4| auto vapp = appender!(V[]); 4| auto iapp = appender!(I[]); 4| auto psl = slice.pack!1; 4| auto count = psl.elementCount; 4| auto pointers = new J[count + 1]; | 4| pointers[0] = 0; 4| auto elems = psl.flattened; 4| size_t j = 0; 72| foreach (ref pointer; pointers[1 .. $]) | { 20| auto row = elems.front; 20| elems.popFront; 20| size_t i; 445| foreach (e; row) | { 135| if (e) | { 24| vapp.put(e); 24| iapp.put(cast(I)i); 24| j++; | } 135| i++; | } 20| pointer = cast(J)j; | } 4| return iapp.data.series(vapp.data).chopped(pointers); |} | | |/++ |Re-compresses a compressed tensor. Makes all values, indeces and pointers consequent in memory. | |Sparse slice is iterated twice. The first tine it is iterated to get length of each sparse row, the second time - to copy the data. | |Note: allocates using GC. |+/ |Slice!(ChopIterator!(J*, Series!(I*, V*)), N) | recompress | (V, I = uint, J = size_t, Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) sparseSlice) | if (isSeries!(DeepElementType!(Slice!(Iterator, N, kind)))) |{ | import mir.algorithm.iteration: each; | import mir.conv: to, emplaceRef; | import mir.ndslice.allocation: uninitSlice; | import mir.ndslice.topology: pack, flattened, as, member, zip; | | size_t count = sparseSlice.elementCount; | size_t length; | auto pointers = uninitSlice!J(count + 1); | pointers.front = 0; | sparseSlice | .member!"data" | .member!"elementCount" | .each!((len, ref ptr) {ptr = length += len;})(pointers[1 .. $]); | | auto i = uninitSlice!I(length); | auto v = uninitSlice!V(length); | | auto ret = i.series(v).chopped(pointers); | | sparseSlice | .each!((a, b) { | b.index[] = a.index.as!I; | b.value.each!(emplaceRef!V)(a.value.as!V); | })(ret); | | return ret; |} | |/// |unittest |{ | import mir.ndslice.topology: universal; | import mir.ndslice.allocation: slice; | 1| auto sl = slice!double(5, 8); 1| sl[] = | [[0, 2, 0, 0, 0, 0, 0, 1], | [0, 0, 0, 0, 0, 0, 0, 4], | [0, 0, 0, 0, 0, 0, 0, 0], | [6, 0, 0, 0, 0, 0, 0, 9], | [0, 0, 0, 0, 0, 0, 0, 5]]; | 1| auto crs = sl.compress; | // assert(crs.iterator._field == CompressedField!(double, uint, uint)( | // 8, | // [2, 1, 4, 6, 9, 5], | // [1, 7, 7, 0, 7, 7], | // [0, 2, 3, 3, 5, 6])); | | import mir.ndslice.dynamic: reversed; 1| auto rec = crs.reversed.recompress!real; 1| auto rev = sl.universal.reversed.compressWithType!real; 1| assert(rev.structure == rec.structure); | // assert(rev.iterator._field.values == rec.iterator._field.values); | // assert(rev.iterator._field.indeces == rec.iterator._field.indeces); | // assert(rev.iterator._field.pointers == rec.iterator._field.pointers); |} | |/++ |Sparse Slice in Dictionary of Keys (DOK) format. |+/ |alias Sparse(T, size_t N = 1) = Slice!(FieldIterator!(SparseField!T), N); | |/// |alias CompressedVector(T, I = uint) = Series!(T*, I*); | |/// |alias CompressedMatrix(T, I = uint) = Slice!(ChopIterator!(J*, Series!(T*, I*))); | |/// |alias CompressedTensor(T, size_t N, I = uint, J = size_t) = Slice!(ChopIterator!(J*, Series!(T*, I*)), N - 1); | |///ditto |alias CompressedTensor(T, size_t N : 1, I = uint) = Series!(I*, T*); source/mir/sparse/package.d is 92% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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); | }); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/traits.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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 | auto matrix = ror.fuse; | | auto rcmatrix = ror.rcfuse; // nogc version | | assert(matrix == [3, 4].iota); | 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). | auto matrix = ror.fuse!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; | | auto ror = | [[[ 0, 1, 2, 3], | [ 4, 5, 6, 7]], | [[ 8, 9,10,11], | [12,13,14,15]]]; | | auto nd = [2, 2, 4].iota; | | assert(ror.fuse == nd); | assert(ror.fuse!2 == nd.transposed!2); | assert(ror.fuse!(1, 2) == nd.transposed!(1, 2)); | 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) | { | 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 | auto matrix = ror.fuseAs!double; | | auto rcmatrix = ror.rcfuseAs!double; // nogc version | | assert(matrix == [3, 4].iota); | 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; | 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); | Slice!(RCI!UT, fuseDimensionCount!NDRange) ret; | } | else | { | alias R = Slice!(T*, fuseDimensionCount!NDRange); | 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]); | size_t[shape.length] shapep; | foreach(i; Iota!(shape.length)) | 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 | { | if (__ctfe) | { | ret = shapep.slice!UT; | ret.transposed!InverseDimensions.each!"a = b"(r); | } | else | { | ret = shapep.uninitSlice!UT; | ret.transposed!InverseDimensions.each!(emplaceRef!T)(r); | } | | } | } | else | { | static if (RC) | { | ret = shape.uninitRCslice!UT; | ret.lightScope.each!(emplaceRef!T)(r); | } | else | { | if (__ctfe) | { | ret = shape.slice!UT; | ret.each!"a = b"(r); | } | else | { | ret = shape.uninitSlice!UT; | ret.each!(emplaceRef!T)(r); | } | } | } | static if (RC) | { | import core.lifetime: move; | return move(*(() @trusted => cast(R*)&ret)()); | } | else | { | 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) | { | return r.shape; | } | else | { | import mir.ndslice.topology: repeat; | typeof(return) ret; | ret[0 .. N] = r.shape; | if (!ret[0 .. N].anyEmptyShape) | { | ret[N .. $] = fuseShape(mixin("r" ~ ".front".repeat(N).fuseCells.field)); | import mir.algorithm.iteration: all; | if (!all!((a) => cast(size_t[M]) ret[N .. $] == .fuseShape(a))(r)) | { | version (D_Exceptions) | throw shapeException; | else | assert(0, shapeExceptionMsg); | } | } | 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; | 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; | auto ret = cells.fuseCellsShape.uninitSlice!UT; | ret.fuseCellsAssign!(emplaceRef!T) = cells; | alias R = Slice!(T*, ret.N); | 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); | assert (ar.fuseCells == 10.iota); |} | |/// 2D |@safe pure version(mir_test) unittest |{ | import mir.ndslice.topology: iota; | import mir.ndslice.chunks; | | auto sl = iota(11, 17); | 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) |{ | assert(to.shape == cells.fuseCellsShape, "'cells.fuseCellsShape' should be equal to 'to.shape'"); | | if (cells.anyEmpty) | goto R; | | import mir.functional: naryFun; | import mir.ndslice.topology: canonical; | static if (kind == Contiguous) | fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to.canonical, cells); | else | fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to, cells); | R: return to; |} | |/+ |TODO docs |+/ |size_t[S.init.shape.length] fuseCellsShape(S)(S cells) @property |{ | typeof(return) ret; | enum N = ret.length; | static if (N == 1) | { | foreach (ref e; cells) | ret[0] += e.length; | } | else | { | import mir.ndslice.topology: repeat; | enum expr = "e" ~ ".front".repeat(N).fuseCells.field; | foreach (i; Iota!N) | for (auto e = cells.save; !e.empty!i; e.popFront!i) | ret[i] += mixin(expr).length!i; | } | 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 | { | auto from = cells.front; | static if (M == 1) | { | auto n = from.length!i; | } | else | { | import mir.ndslice.topology: repeat; | enum expr = "from" ~ ".front".repeat(N - 1 - i).fuseCells.field; | auto n = mixin(expr).length!i; | } | assert (to.length!i >= n); | static if (i + 1 == M) | { | import mir.algorithm.iteration: each; | each!fun(to.selectFront!i(n), from); | } | else | { | .fuseCellsEmplaceImpl!(fun, i + 1, N)(to.selectFront!i(n), from); | } | to.popFrontExactly!i(n); | cells.popFront; | } | while(!cells.empty); | return to; |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/fuse.d has no code <<<<<< EOF # path=./source-mir-model-lda-hoffman.lst |/** | |$(H3 Online variational Bayes for latent Dirichlet allocation) | |References: | Hoffman, Matthew D., Blei, David M. and Bach, Francis R.. | "Online Learning for Latent Dirichlet Allocation.." | Paper presented at the meeting of the NIPS, 2010. | |License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |Copyright: 2016-, Ilya Yaroshenko |Authors: Ilya Yaroshenko |*/ |module mir.model.lda.hoffman; | |import std.traits; | |/++ |Batch variational Bayes for LDA with mini-batches. |+/ |struct LdaHoffman(F) | if (isFloatingPoint!F) |{ | import std.parallelism; | import mir.ndslice.iterator: FieldIterator; | import mir.ndslice.topology: iota; | | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | | import mir.math.common; | import mir.sparse; | | private alias Vector = Slice!(F*); | private alias Matrix = Slice!(F*, 2); | | private size_t D; | private F alpha; | private F eta; | private F kappa; | private F _tau; | private F eps; | | private Matrix _lambda; // [k, w] | private Matrix _beta; // [k, w] | | private TaskPool tp; | | private F[][] _lambdaTemp; | | @disable this(); | @disable this(this); | | /++ | Params: | K = theme count | W = dictionary size | D = approximate total number of documents in a collection. | alpha = Dirichlet document-topic prior (0.1) | eta = Dirichlet word-topic prior (0.1) | tau0 = tau0 ≧ 0 slows down the early iterations of the algorithm. | kappa = `kappa belongs to $(LPAREN)0.5, 1]`, controls the rate at which old values of lambda are forgotten. | `lambda = (1 - rho(tau)) lambda + rho lambda', rho(tau) = (tau0 + tau)^(-kappa)`. Use `kappa = 0` for Batch variational Bayes LDA. | eps = Stop iterations if `||lambda - lambda'||_l1 < s * eps`, where `s` is a documents count in a batch. | tp = task pool | +/ 0000000| this(size_t K, size_t W, size_t D, F alpha, F eta, F tau0, F kappa, F eps = 1e-5, TaskPool tp = taskPool()) | { | import mir.random; | 0000000| this.D = D; 0000000| this.alpha = alpha; 0000000| this.eta = eta; 0000000| this._tau = tau0; 0000000| this.kappa = kappa; 0000000| this.eps = eps; 0000000| this.tp = tp; | 0000000| _lambda = slice!F(K, W); 0000000| _beta = slice!F(K, W); 0000000| _lambdaTemp = new F[][](tp.size + 1, W); | | import std.math: fabs; 0000000| auto gen = Random(unpredictableSeed); 0000000| foreach (r; _lambda) 0000000| foreach (ref e; r) 0000000| e = (gen.rand!F.fabs + 0.9) / 1.901; | 0000000| updateBeta(); | } | | /// | void updateBeta() | { 0000000| foreach (i; tp.parallel(lambda.length.iota)) 0000000| unparameterize(lambda[i], beta[i]); | } | | /++ | Posterior over the topics | +/ | Slice!(F*, 2) beta() @property | { 0000000| return _beta; | } | | /++ | Parameterized posterior over the topics. | +/ | Slice!(F*, 2) lambda() @property | { 0000000| return _lambda; | } | | /++ | Count of already seen documents. | Slows down the iterations of the algorithm. | +/ | F tau() const @property | { 0000000| return _tau; | } | | /// ditto | void tau(F v) @property | { 0000000| _tau = v; | } | | /++ | Accepts mini-batch and performs multiple E-step iterations for each document and single M-step. | | This implementation is optimized for sparse documents, | which contain much less unique words than a dictionary. | | Params: | n = mini-batch, a collection of compressed documents. | maxIterations = maximal number of iterations for s This implementation is optimized for sparse documents, |ingle document in a batch for E-step. | +/ | size_t putBatch(SliceKind kind, C, I, J)(Slice!(ChopIterator!(J*, Series!(I*, C*)), 1, kind) n, size_t maxIterations) | { | return putBatchImpl(n.recompress!F, maxIterations); | } | | private size_t putBatchImpl(Slice!(ChopIterator!(size_t*, Series!(uint*, F*))) n, size_t maxIterations) | { | import std.math: isFinite; | import mir.sparse.blas.dot; | import mir.sparse.blas.gemv; | import mir.ndslice.dynamic: transposed; | import mir.ndslice.topology: universal; | import mir.internal.utility; | | immutable S = n.length; | immutable K = _lambda.length!0; | immutable W = _lambda.length!1; | _tau += S; | auto theta = slice!F(S, K); | auto nsave = saveN(n); | | immutable rho = pow!F(F(tau), -kappa); | auto thetat = theta.universal.transposed; | auto _gamma = slice!F(tp.size + 1, K); | shared size_t ret; | // E step | foreach (d; tp.parallel(S.iota)) | { | auto gamma = _gamma[tp.workerIndex]; | gamma[] = 1; | auto nd = n[d]; | auto thetad = theta[d]; | for (size_t c; ;c++) | { | unparameterize(gamma, thetad); | | selectiveGemv!"/"(_beta.universal.transposed, thetad, nd); | F sum = 0; | { | auto beta = _beta; | auto th = thetad; | foreach (ref g; gamma) | { | if (!th.front.isFinite) | th.front = F.max; | auto value = dot(nd, beta.front) * th.front + alpha; | sum += fabs(value - g); | g = value; | beta.popFront; | th.popFront; | } | } | if (c < maxIterations && sum > eps * K) | { | nd.value[] = nsave[d].value; | continue; | } | import core.atomic; | ret.atomicOp!"+="(c); | break; | } | } | // M step | foreach (k; tp.parallel(K.iota)) | { | auto lambdaTemp = _lambdaTemp[tp.workerIndex]; | gemtv!F(F(1), n, thetat[k], F(0), lambdaTemp.sliced); | import mir.algorithm.iteration: each; | each!((ref l, bk, lt) {l = (1 - rho) * l + | rho * (eta + (F(D) / F(S)) * bk * lt);})(_lambda[k], _beta[k],lambdaTemp.sliced); | unparameterize(_lambda[k], _beta[k]); | } | return ret; | } | | private auto saveN(Slice!(ChopIterator!(size_t*, Series!(uint*, F*))) n) | { | import mir.series: series; | import mir.ndslice.topology: chopped, universal; 0000000| return n.iterator._sliceable.index | .series(n.iterator._sliceable.value.dup) | .chopped(n.iterator._iterator.sliced(n.length + 1)); | } | | private static void unparameterize(Vector param, Vector posterior) | { 0000000| assert(param.structure == posterior.structure); | import mir.ndslice.topology: zip; | import mir.math.func.expdigamma; | import mir.math.sum: sum; 0000000| immutable c = 1 / expDigamma(sum(param)); 0000000| foreach (e; zip(param, posterior)) 0000000| e.b = c * expDigamma(e.a); | } |} | |unittest |{ | alias ff = LdaHoffman!double; |} source/mir/model/lda/hoffman.d is 0% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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; | auto ar = [1, 1e100, 1, -1e100].sliced.map!"a * 10_000"; | const r = 20_000; | assert(r == ar.sum!"kbn"); | assert(r == ar.sum!"kb2"); | assert(r == ar.sum!"precise"); | assert(r == ar.sum!"decimal"); |} | |/// Decimal precise summation |version(mir_test) |unittest |{ | auto ar = [777.7, -777]; | assert(ar.sum!"decimal" == 0.7); | assert(sum!"decimal"(777.7, -777) == 0.7); | | // The exact binary reuslt is 0.7000000000000455 | assert(ar[0] + ar[1] == 0.7000000000000455); | assert(ar.sum!"fast" == 0.7000000000000455); | assert(ar.sum!"kahan" == 0.7000000000000455); | assert(ar.sum!"kbn" == 0.7000000000000455); | assert(ar.sum!"kb2" == 0.7000000000000455); | 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; | auto ar = 1000 | .iota | .map!(n => 1.7L.pow(n+1) - 1.7L.pow(n)) | ; | real d = 1.7L.pow(1000); | assert(sum!"precise"(concatenation(ar, [-d].sliced).slicedField) == -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 == "-") | { | Quaternion ret ; | foreach (i, ref e; ret.rijk) | mixin("e = rijk[i] "~op~" rhs.rijk[i];"); | return ret; | } | | /// += and -= operator overloading | Quaternion opOpAssign(string op)(auto ref const Quaternion rhs) | if (op == "+" || op == "-") | { | foreach (i, ref e; rijk) | mixin("e "~op~"= rhs.rijk[i];"); | return this; | } | | ///constructor with single FP argument | this(F f) | { | rijk[] = f; | } | | ///assigment with single FP argument | void opAssign(F f) | { | rijk[] = f; | } | } | | Quaternion!double q, p, r; | q.rijk = [0, 1, 2, 4]; | p.rijk = [3, 4, 5, 9]; | r.rijk = [3, 5, 7, 13]; | | assert(r == [p, q].sum!"naive"); | assert(r == [p, q].sum!"pairwise"); | 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; | | auto ar = [Complex!double(1.0, 2), Complex!double(2.0, 3), Complex!double(3.0, 4), Complex!double(4.0, 5)]; | Complex!double r = Complex!double(10.0, 14); | 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) |@safe pure nothrow unittest |{ | import mir.ndslice.topology: repeat, iota; | | //simple integral summation | assert(sum([ 1, 2, 3, 4]) == 10); | | //with initial value | assert(sum([ 1, 2, 3, 4], 5) == 15); | | //with integral promotion | assert(sum([false, true, true, false, true]) == 3); | assert(sum(ubyte.max.repeat(100)) == 25_500); | | //The result may overflow | assert(uint.max.repeat(3).sum == 4_294_967_293U ); | //But a seed can be used to change the summation primitive | assert(uint.max.repeat(3).sum(ulong.init) == 12_884_901_885UL); | | //Floating point summation | 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)); | assert(sum([1F, 2, 3, 4]) == 10); | assert(sum([1F, 2, 3, 4], 5F) == 15); | | //Force pair-wise floating point summation on large integers | import mir.math : approxEqual; | 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; | 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; | auto r = iota(1000).map!(n => 1.7L.pow(n+1) - 1.7L.pow(n)); | Summator!(real, Summation.precise) s = 0.0; | s.put(r); | s -= 1.7L.pow(1000); | assert(s.sum == -1); |} | |/// Precise summation with output range |version(mir_test) |nothrow @nogc unittest |{ | import mir.math.common; | float M = 2.0f ^^ (float.max_exp-1); | double N = 2.0 ^^ (float.max_exp-1); | auto s = Summator!(float, Summation.precise)(0); | s += M; | s += M; | assert(float.infinity == s.sum); //infinity | auto e = cast(Summator!(double, Summation.precise)) s; | assert(e.sum < double.infinity); | 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 | { | return meanAccumulator.mean; | } | | this(double[] buffer) | { | assert(buffer.length); | circularBuffer = buffer; | meanAccumulator.put(buffer); | } | | ///operation without rounding | void put(T x) | { | import mir.utility: swap; | meanAccumulator.summator += x; | swap(circularBuffer[frontIndex++], x); | frontIndex = frontIndex == circularBuffer.length ? 0 : frontIndex; | meanAccumulator.summator -= x; | } | } | | /// ma always keeps precise average of last 1000 elements | auto x = linspace!double([1000], [0.0, 999]).rcarray; | auto ma = MovingAverage!double(x[]); | assert(ma.avg == (1000 * 999 / 2) / 1000.0); | /// move by 10 elements | foreach(e; linspace!double([10], [1000.0, 1009.0])) | ma.put(e); | assert(ma.avg == (1010 * 1009 / 2 - 10 * 9 / 2) / 1000.0); |} | |/// Arbitrary sum |version(mir_test) |@safe pure nothrow |unittest |{ | assert(sum(1, 2, 3, 4) == 10); | assert(sum!float(1, 2, 3, 4) == 10f); | assert(sum(1f, 2, 3, 4) == 10f); | 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; | double2 a = 1, b = 2, c = 3, d = 6; | with(Summation) | { | foreach (algo; AliasSeq!(naive, fast, pairwise, kahan)) | { | assert([a, b, c].sum!algo.array == d.array); | 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; | |private alias isNaN = x => x != x; |private alias isFinite = x => x.fabs < x.infinity; |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); | ScopedBuffer!(F, 8) partials; | //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 | { | bool _break; | foreach_reverse (i, y; partials) | { | s = partialsReducePred(s, y, i ? partials[i-1] : 0, _break); | if (_break) | break; | debug(numeric) assert(.isFinite(s)); | } | return s; | } | | static F partialsReducePred(F s, F y, F z, out bool _break) | out(result) | { | debug(numeric) assert(.isFinite(result)); | } | do | { | F x = s; | s = x + y; | F d = s - x; | F l = y - d; | debug(numeric) | { | assert(.isFinite(x)); | assert(.isFinite(y)); | assert(.isFinite(s)); | assert(fabs(y) < fabs(x)); | } | 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. | if (z && !signbit(l * z)) | { | l *= 2; | x = s + l; | F t = x - s; | if (l == t) | s = x; | } | _break = true; | } | return s; | } | | //Returns corresponding infinity if is overflow and 0 otherwise. | F overflow()() const | { | if (o == 0) | return 0; | if (partials.length && (o == -1 || o == 1) && signbit(o * partials.data[$-1])) | { | // problem case: decide whether result is representable | F x = o * M; | F y = partials.data[$-1] / 2; | F h = x + y; | F d = h - x; | F l = (y - d) * 2; | y = h * 2; | d = h + l; | F t = d - h; | version(X86) | { | if (!.isInfinity(cast(T)y) || !.isInfinity(sum())) | return 0; | } | else | { | if (!.isInfinity(cast(T)y) || | ((partials.length > 1 && !signbit(l * partials.data[$-2])) && t == l)) | return 0; | } | } | 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: | | /// | this()(T n) | { | static if (summation == Summation.precise) | { | s = 0.0; | o = 0; | if (n) put(n); | } | else | static if (summation == Summation.kb2) | { | 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 | { | cs = 0.0; | ccs = 0.0; | } | } | else | static if (summation == Summation.kbn) | { | s = n; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { | c = Complex!float(0, 0); | } | } else | c = 0.0; | } | else | static if (summation == Summation.kahan) | { | s = n; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { | c = Complex!float(0, 0); | } | } else | c = 0.0; | } | else | static if (summation == Summation.pairwise) | { | counter = index = 1; | partials[0] = n; | } | else | static if (summation == Summation.naive) | { | s = n; | } | else | static if (summation == Summation.fast) | { | s = n; | } | else | static if (summation == Summation.decimal) | { | ss = 0; | if (!(-n.infinity < n && n < n.infinity)) | { | ss = n; | n = 0; | } | 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) | F x = n; | static if (summation == Summation.precise) | { | if (.isFinite(x)) | { | size_t i; | auto partials_data = partials.data; | foreach (y; partials_data[]) | { | F h = x + y; | if (.isInfinity(cast(T)h)) | { | if (fabs(x) < fabs(y)) | { | F t = x; x = y; y = t; | } | //h == -F.infinity | if (signbit(h)) | { | x += M; | x += M; | o--; | } | //h == +F.infinity | else | { | x -= M; | x -= M; | o++; | } | debug(numeric) assert(x.isFinite); | h = x + y; | } | debug(numeric) assert(h.isFinite); | 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(l.isFinite); | if (l) | { | partials_data[i++] = l; | } | x = h; | } | partials.shrinkTo(i); | if (x) | { | partials.put(x); | } | } | else | { | s += x; | } | } | else | static if (summation == Summation.kb2) | { | static if (isFloatingPoint!F) | { | F t = s + x; | F c = 0; | if (fabs(s) >= fabs(x)) | { | F d = s - t; | c = d + x; | } | else | { | F d = x - t; | c = d + s; | } | s = t; | t = cs + c; | if (fabs(cs) >= fabs(c)) | { | F d = cs - t; | d += c; | ccs += d; | } | else | { | F d = c - t; | d += cs; | ccs += d; | } | cs = t; | } | else | { | F t = s + x; | if (fabs(s.re) < fabs(x.re)) | { | auto s_re = s.re; | auto x_re = x.re; | static if (is(F : cfloat)) { | s = x_re + s.im * 1fi; | x = s_re + x.im * 1fi; | } else { | s = F(x_re, s.im); | x = F(s_re, x.im); | } | } | if (fabs(s.im) < fabs(x.im)) | { | auto s_im = s.im; | auto x_im = x.im; | static if (is(F : cfloat)) { | s = s.re + x_im * 1fi; | x = x.re + s_im * 1fi; | } else { | s = F(s.re, x_im); | x = F(x.re, s_im); | } | } | F c = (s-t)+x; | s = t; | if (fabs(cs.re) < fabs(c.re)) | { | auto c_re = c.re; | auto cs_re = cs.re; | static if (is(F : cfloat)) { | c = cs_re + c.im * 1fi; | cs = c_re + cs.im * 1fi; | } else { | c = F(cs_re, c.im); | cs = F(c_re, cs.im); | } | } | if (fabs(cs.im) < fabs(c.im)) | { | auto c_im = c.im; | auto cs_im = cs.im; | static if (is(F : cfloat)) { | c = c.re + cs_im * 1fi; | cs = cs.re + c_im * 1fi; | } else { | c = F(c.re, cs_im); | cs = F(cs.re, c_im); | } | } | F d = cs - t; | d += c; | ccs += d; | cs = t; | } | } | else | static if (summation == Summation.kbn) | { | static if (isFloatingPoint!F) | { | F t = s + x; | if (fabs(s) >= fabs(x)) | { | F d = s - t; | d += x; | c += d; | } | else | { | F d = x - t; | d += s; | c += d; | } | s = t; | } | else | { | F t = s + x; | if (fabs(s.re) < fabs(x.re)) | { | auto s_re = s.re; | auto x_re = x.re; | static if (is(F : cfloat)) { | s = x_re + s.im * 1fi; | x = s_re + x.im * 1fi; | } else { | s = F(x_re, s.im); | x = F(s_re, x.im); | } | } | if (fabs(s.im) < fabs(x.im)) | { | auto s_im = s.im; | auto x_im = x.im; | static if (is(F : cfloat)) { | s = s.re + x_im * 1fi; | x = x.re + s_im * 1fi; | } else { | s = F(s.re, x_im); | x = F(x.re, s_im); | } | } | F d = s - t; | d += x; | c += d; | s = t; | } | } | else | static if (summation == Summation.kahan) | { | y = x - c; | t = s + y; | c = t - s; | c -= y; | s = t; | } | else | static if (summation == Summation.pairwise) | { | import mir.bitop: cttz; | ++counter; | partials[index] = n; | foreach (_; 0 .. cttz(counter)) | { | immutable newIndex = index - 1; | partials[newIndex] += partials[index]; | index = newIndex; | } | ++index; | } | else | static if (summation == Summation.naive) | { | s += n; | } | else | static if (summation == Summation.fast) | { | s += n; | } | else | static if (summation == summation.decimal) | { | import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; | if (-n.infinity < n && n < n.infinity) | { | auto decimal = genericBinaryToDecimal(n); | s += decimal; | } | else | { | 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) | { | F[registersCount] v; | foreach (i, n; chainSeq!registersCount) | { | if (r.length >= n * 2) do | { | foreach (j; Iota!n) | v[j] = cast(F) r[j]; | foreach (j; Iota!n) | v[j] += cast(F) r[n + j]; | foreach (m; chainSeq!(n / 2)) | foreach (j; Iota!m) | v[j] += v[m + j]; | put(v[0]); | r = r[n * 2 .. $]; | } | while (!i && r.length >= n * 2); | } | if (r.length) | { | put(cast(F) r[0]); | r = r[1 .. $]; | } | assert(r.length == 0); | } | else | static if (summation == Summation.fast) | { | static if (isComplex!F) { | static if (is(F : cfloat)) { | F s0 = 0 + 0fi; | } else { | F s0 = F(0, 0f); | } | } else | F s0 = 0; | foreach (ref elem; r) | s0 += elem; | s += s0; | } | else | { | foreach (ref elem; r) | 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; | this.put(r.flattened); | } | else | static if (isPointer!Iterator && kind == Contiguous) | { | 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 | F s0 = 0; | import mir.algorithm.iteration: reduce; | s0 = s0.reduce!"a + b"(r); | s += s0; | } | else | { | foreach(elem; r) | 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); | } | | if (s) | return s; | auto parts = partials.data[]; | F y = 0.0; | //pick last | if (parts.length) | { | y = parts[$-1]; | parts = parts[0..$-1]; | } | if (o) | { | immutable F of = o; | if (y && (o == -1 || o == 1) && signbit(of * y)) | { | // problem case: decide whether result is representable | y /= 2; | F x = of * M; | immutable F h = x + y; | F t = h - x; | F l = (y - t) * 2; | y = h * 2; | if (.isInfinity(cast(T)y)) | { | // overflow, except in edge case... | x = h + l; | t = x - h; | y = parts.length && t == l && !signbit(l*parts[$-1]) ? | x * 2 : | F.infinity * of; | parts = null; | } | else if (l) | { | bool _break; | y = partialsReducePred(y, l, parts.length ? parts[$-1] : 0, _break); | if (_break) | parts = null; | } | } | else | { | y = F.infinity * of; | parts = null; | } | } | return partialsReduce(y, parts); | } | else | static if (summation == Summation.kb2) | { | return s + (cs + ccs); | } | else | static if (summation == Summation.kbn) | { | return s + c; | } | else | static if (summation == Summation.kahan) | { | return s; | } | else | static if (summation == Summation.pairwise) | { | F s = summationInitValue!T; | assert((counter == 0) == (index == 0)); | foreach_reverse (ref e; partials[0 .. index]) | { | static if (is(F : __vector(W[N]), W, size_t N)) | s += cast(Unqual!F) e; //DMD bug workaround | else | s += e; | } | return s; | } | else | static if (summation == Summation.naive) | { | return s; | } | else | static if (summation == Summation.fast) | { | return s; | } | else | static if (summation == Summation.decimal) | { | 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) | { | auto ret = typeof(return).init; | ret.s = s; | ret.o = o; | foreach (p; partials.data[]) | { | ret.partials.put(p); | } | enum exp_diff = P.max_exp / T.max_exp; | static if (exp_diff) | { | if (ret.o) | { | immutable f = ret.o / exp_diff; | immutable t = cast(int)(ret.o % exp_diff); | ret.o = f; | ret.put((P(2) ^^ T.max_exp) * t); | } | } | return ret; | } | else | static if (summation == Summation.kb2) | { | auto ret = typeof(return).init; | ret.s = s; | ret.cs = cs; | ret.ccs = ccs; | return ret; | } | else | static if (summation == Summation.kbn) | { | auto ret = typeof(return).init; | ret.s = s; | ret.c = c; | return ret; | } | else | static if (summation == Summation.kahan) | { | auto ret = typeof(return).init; | ret.s = s; | ret.c = c; | return ret; | } | else | static if (summation == Summation.pairwise) | { | auto ret = typeof(return).init; | ret.counter = counter; | ret.index = index; | foreach (i; 0 .. index) | ret.partials[i] = partials[i]; | 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)) | { | return cast(C)sum(); | } | | ///Operator overloading. | // opAssign should initialize partials. | void opAssign(T rhs) | { | static if (summation == Summation.precise) | { | partials.reset; | s = 0.0; | o = 0; | if (rhs) put(rhs); | } | else | static if (summation == Summation.kb2) | { | s = rhs; | static if (isComplex!T) | { | static if (is(T : cfloat)) { | cs = 0 + 0fi; | ccs = 0 + 0fi; | } else { | cs = T(0, 0f); | ccs = T(0.0, 0f); | } | } | else | { | cs = 0.0; | ccs = 0.0; | } | } | else | static if (summation == Summation.kbn) | { | s = rhs; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { | c = T(0, 0f); | } | } else | c = 0.0; | } | else | static if (summation == Summation.kahan) | { | s = rhs; | static if (isComplex!T) { | static if (is(T : cfloat)) { | c = 0 + 0fi; | } else { | c = T(0, 0f); | } | } else | c = 0.0; | } | else | static if (summation == Summation.pairwise) | { | counter = 1; | index = 1; | partials[0] = rhs; | } | else | static if (summation == Summation.naive) | { | s = rhs; | } | else | static if (summation == Summation.fast) | { | s = rhs; | } | else | static if (summation == summation.decimal) | { | __ctor(rhs); | } | else | static assert(0); | } | | ///ditto | void opOpAssign(string op : "+")(T rhs) | { | 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) | { | put(-rhs); | } | else | static if (summation == Summation.kb2) | { | put(-rhs); | } | else | static if (summation == Summation.kbn) | { | put(-rhs); | } | else | static if (summation == Summation.kahan) | { | y = 0.0; | y -= rhs; | y -= c; | t = s + y; | c = t - s; | c -= y; | s = t; | } | else | static if (summation == Summation.pairwise) | { | put(-rhs); | } | else | static if (summation == Summation.naive) | { | s -= rhs; | } | else | static if (summation == Summation.fast) | { | s -= rhs; | } | else | static assert(0); | } | | ///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) | { | this -= 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) | { | 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; | auto r1 = iota(500).map!(a => 1.7L.pow(a+1) - 1.7L.pow(a)); | auto r2 = iota([500], 500).map!(a => 1.7L.pow(a+1) - 1.7L.pow(a)); | Summator!(real, Summation.precise) s1 = 0, s2 = 0.0; | foreach (e; r1) s1 += e; | foreach (e; r2) s2 -= e; | s1 -= s2; | s1 -= 1.7L.pow(1000); | 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)) | { | Summator!(T, summation) sum = 1; | sum += 3; | assert(sum.sum == 4); | sum -= 10; | assert(sum.sum == -6); | Summator!(T, summation) sum2 = 3; | sum -= sum2; | assert(sum.sum == -9); | sum2 = 100; | sum += 100; | assert(sum.sum == 91); | auto sum3 = cast(Summator!(real, summation))sum; | assert(sum3.sum == 91); | 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)) | { | Summator!(T, summation) sum = 1; | sum += 3.5; | assert(sum.sum.approxEqual(4.5)); | sum = 2; | assert(sum.sum == 2); | sum -= 4; | assert(sum.sum.approxEqual(-2)); | } | } | | static if (summation == Summation.precise) | { | ///Returns `true` if current sum is a NaN. | bool isNaN()() const | { | return .isNaN(s); | } | | ///Returns `true` if current sum is finite (not infinite or NaN). | bool isFinite()() const | { | if (s) | return false; | return !overflow; | } | | ///Returns `true` if current sum is ±∞. | bool isInfinity()() const | { | 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; | | Summator!(double, Summation.precise) summator = 0.0; | | enum double M = (cast(double)2) ^^ (double.max_exp - 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), | refTuple(iota([1000], 1).map!(a => 1.0/a).array , 7.4854708605503451), | refTuple(iota([1000], 1).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... | refTuple(iota([1000], 1).map!(a => 1.0/a).retro.array , 7.4854708605503451), | 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), | ]; | foreach (i, test; tests) | { | summator = 0.0; | foreach (t; test.a) summator.put(t); | auto r = test.b; | auto s = summator.sum; | assert(summator.isNaN() == r.isNaN()); | assert(summator.isFinite() == r.isFinite()); | assert(summator.isInfinity() == r.isInfinity()); | 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)) | { | return sum(r, summationInitValue!F); | } | else | { | static if (summation == Summation.decimal) | { | Summator!(F, summation) sum = void; | sum = 0; | } | else | { | Summator!(F, ResolveSummationType!(summation, Range, sumType!Range)) sum; | } | sum.put(r.move); | 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) | { | Summator!(T, summation) sumRe = void; | sumRe = seed.re; | | Summator!(T, summation) sumIm = void; | sumIm = seed.im; | } | else | { | auto sumRe = Summator!(T, Summation.precise)(seed.re); | 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 | { | foreach (ref elem; r) | { | sumRe.put(elem.re); | sumIm.put(elem.im); | } | } | static if (is(F : cfloat)) { | return sumRe.sum + sumIm.sum * 1fi; | } else { | return F(sumRe.sum, sumIm.sum); | } | } | else | { | static if (summation == Summation.decimal) | { | Summator!(F, summation) sum = void; | sum = seed; | } | else | { | auto sum = Summator!(F, ResolveSummationType!(summation, Range, F))(seed); | } | sum.put(r.move); | return sum.sum; | } | } | } | | /// | F sum(scope const F[] r...) | { | static if (isComplex!F && (summation == Summation.precise || summation == Summation.decimal)) | { | return sum(r, summationInitValue!F); | } | else | { | Summator!(F, ResolveSummationType!(summation, const(F)[], F)) sum; | sum.put(r); | 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); | return .sum!(F, ResolveSummationType!(summation, Range, F))(r.move); | } | | /// | F sum(Range, F)(Range r, F seed) | if (isIterable!Range) | { | import core.lifetime: move; | return .sum!(F, ResolveSummationType!(summation, Range, F))(r.move, seed); | } | | /// | sumType!T sum(T)(scope const T[] ar...) | { | alias F = typeof(return); | 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)); | | int[] empty; | assert(sum(empty) == 0); | assert(sum([42]) == 42); | assert(sum([42, 43]) == 42 + 43); | assert(sum([42, 43, 44]) == 42 + 43 + 44); | 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)); | const(float[]) a = [1F, 2F, 3F, 4F]; | static assert(is(typeof(sum!double(a)) == double)); | const(float)[] b = [1F, 2F, 3F, 4F]; | static assert(is(typeof(sum!double(a)) == double)); | | double[] empty; | assert(sum(empty) == 0); | assert(sum([42.]) == 42); | assert(sum([42., 43.]) == 42 + 43); | assert(sum([42., 43., 44.]) == 42 + 43 + 44); | assert(sum([42., 43., 44., 45.5]) == 42 + 43 + 44 + 45.5); |} | |version(mir_test) |@safe pure nothrow unittest |{ | import mir.ndslice.topology: iota; | 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)); | | assert(sum(SList!double()[]) == 0); | assert(sum(SList!double(1)[]) == 1); | assert(sum(SList!double(1, 2)[]) == 1 + 2); | assert(sum(SList!double(1, 2, 3)[]) == 1 + 2 + 3); | 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; | immutable a = [10, 20]; | auto s = a.sliced; | auto s1 = sum(a); // Error | auto s2 = s.map!(x => x).sum; // Error |} | |version(mir_test) |unittest |{ | import std.bigint; | import mir.ndslice.topology: repeat; | | auto a = BigInt("1_000_000_000_000_000_000").repeat(10); | auto b = (ulong.max/2).repeat(10); | auto sa = a.sum(); | auto sb = b.sum(BigInt(0)); //reduce ulongs into bigint | assert(sa == BigInt("10_000_000_000_000_000_000")); | assert(sb == (BigInt(ulong.max/2) * 10)); |} | |version(mir_test) |unittest |{ | with(Summation) | foreach (F; AliasSeq!(float, double, real)) | { | F[] ar = [1, 2, 3, 4]; | F r = 10; | assert(r == ar.sum!fast()); | assert(r == ar.sum!pairwise()); | assert(r == ar.sum!kahan()); | assert(r == ar.sum!kbn()); | assert(r == ar.sum!kb2()); | } |} | |version(mir_test) |unittest |{ | assert(sum(1) == 1); | assert(sum(1, 2, 3) == 6); | assert(sum(1.0, 2.0, 3.0) == 6); |} | |version(mir_test) |unittest |{ | assert(sum!float(1) == 1f); | assert(sum!float(1, 2, 3) == 6f); | 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; | | 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)); | 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); | | double2[] ar = [double2([1.0, 2]), double2([2, 3]), double2([3, 4]), double2([4, 6])]; | double2 c = double2([10, 15]); | | foreach (sumType; sums) | { | double2 s = ar.sum!(sumType); | 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); | | int[2] ns = [9, 101]; | | foreach (n; ns) | { | foreach (sumType; sums) | { | auto ar = iota(n).as!double; | double c = n * (n - 1) / 2; // gauss for n=100 | double s = ar.sum!(sumType); | 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;})) | { | T a = 0.0; | return a; | } | else | static if (__traits(compiles, {T a = 0;})) | { | T a = 0; | return a; | } | else | static if (__traits(compiles, {T a = 0 + 0fi;})) | { | T a = 0 + 0fi; | 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; | | auto p = iota([2, 3, 4, 5]); | auto a = p.as!double; | auto b = a.flattened; | auto c = a.slice; | auto d = c.flattened; | auto s = p.flattened.sum; | | assert(a.sum == s); | assert(b.sum == s); | assert(c.sum == s); | assert(d.sum == s); | | assert(a.canonical.sum == s); | assert(b.canonical.sum == s); | assert(c.canonical.sum == s); | assert(d.canonical.sum == s); | | assert(a.universal.transposed!3.sum == s); | assert(b.universal.sum == s); | assert(c.universal.transposed!3.sum == s); | assert(d.universal.sum == s); | | assert(a.sum!"fast" == s); | assert(b.sum!"fast" == s); | assert(c.sum!(float, "fast") == s); | assert(d.sum!"fast" == s); | | assert(a.canonical.sum!"fast" == s); | assert(b.canonical.sum!"fast" == s); | assert(c.canonical.sum!"fast" == s); | assert(d.canonical.sum!"fast" == s); | | assert(a.universal.transposed!3.sum!"fast" == s); | assert(b.universal.sum!"fast" == s); | assert(c.universal.transposed!3.sum!"fast" == s); | assert(d.universal.sum!"fast" == s); | |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/math/sum.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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; | | JsonAlgebraic value; | | StringMap!JsonAlgebraic object; | | // Default | assert(value.isNull); | assert(value.kind == JsonAlgebraic.Kind.null_); | | // Boolean | value = true; | object["bool"] = value; | assert(!value.isNull); | assert(value == true); | assert(value.kind == JsonAlgebraic.Kind.boolean); | assert(value.get!bool == true); | assert(value.get!(JsonAlgebraic.Kind.boolean) == true); | | // Null | value = null; | object["null"] = value; | assert(value.isNull); | assert(value == null); | assert(value.kind == JsonAlgebraic.Kind.null_); | assert(value.get!(typeof(null)) == null); | assert(value.get!(JsonAlgebraic.Kind.null_) == null); | | // String | value = "s"; | object["string"] = value; | assert(value.kind == JsonAlgebraic.Kind.string); | assert(value == "s"); | assert(value.get!string == "s"); | assert(value.get!(JsonAlgebraic.Kind.string) == "s"); | | // Integer | value = 4; | object["integer"] = value; | assert(value.kind == JsonAlgebraic.Kind.integer); | assert(value == 4); | assert(value != 4.0); | assert(value.get!long == 4); | assert(value.get!(JsonAlgebraic.Kind.integer) == 4); | | // Float | value = 3.0; | object["float"] = value; | assert(value.kind == JsonAlgebraic.Kind.float_); | assert(value != 3); | assert(value == 3.0); | assert(value.get!double == 3.0); | assert(value.get!(JsonAlgebraic.Kind.float_) == 3.0); | | // Array | JsonAlgebraic[] arr = [0, 1, 2, 3, 4].map!JsonAlgebraic.array; | | value = arr; | object["array"] = value; | assert(value.kind == JsonAlgebraic.Kind.array); | assert(value == arr); | assert(value.get!(JsonAlgebraic[])[3] == 3); | | // Object | assert(object.keys == ["bool", "null", "string", "integer", "float", "array"]); | object.values[0] = "false"; | assert(object["bool"] == "false"); // it is a string now | object.remove("bool"); // remove the member | | value = object; | object["array"] = value; | assert(value.kind == JsonAlgebraic.Kind.object); | assert(value.get!(StringMap!JsonAlgebraic).keys is object.keys); | assert(value.get!(StringMap!JsonAlgebraic).values is object.values); | | JsonAlgebraic[string] aa = object.toAA; | object = StringMap!JsonAlgebraic(aa); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/algebraic_alias/json.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-random-2.2.16-mir-random-source-mir-random-engine-package.lst |/++ |$(SCRIPT inhibitQuickIndex = 1;) |Uniform random engines. | |$(B Sections:) | $(LINK2 #Convenience, Convenience) |• $(LINK2 #Entropy, Entropy) |• $(LINK2 #ThreadLocal, Thread-Local) |• $(LINK2 #Traits, Traits) |• $(LINK2 #CInterface, C Interface) | |$(BOOKTABLE | |$(LEADINGROW Convenience) |$(TR | $(RROW Random, Default random number _engine)) | $(RROW rne, Per-thread uniquely-seeded instance of default `Random`. Requires $(LINK2 https://en.wikipedia.org/wiki/Thread-local_storage, TLS).) | |$(LEADINGROW Entropy) |$(TR | $(RROW unpredictableSeed, Seed of `size_t` using system entropy. May use `unpredictableSeed!UIntType` for unsigned integers of different sizes.) | $(RROW genRandomNonBlocking, Fills a buffer with system entropy, returning number of bytes copied or negative number on error) | $(RROW genRandomBlocking, Fills a buffer with system entropy, possibly waiting if the system believes it has insufficient entropy. Returns 0 on success.)) | |$(LEADINGROW Thread-Local (when $(LINK2 https://en.wikipedia.org/wiki/Thread-local_storage, TLS) enabled)) |$(TR | $(TR $(TDNW $(LREF threadLocal)`!(Engine)`) $(TD Per-thread uniquely-seeded instance of any specified `Engine`. Requires $(LINK2 https://en.wikipedia.org/wiki/Thread-local_storage, TLS).)) | $(TR $(TDNW $(LREF threadLocalPtr)`!(Engine)`) $(TD `@safe` pointer to `threadLocal!Engine`. Always initializes before return. $(I Warning: do not share between threads!))) | $(TR $(TDNW $(LREF threadLocalInitialized)`!(Engine)`) $(TD Explicitly manipulate "is seeded" flag for thread-local instance. Not needed by most library users.)) | $(TR $(TDNW $(LREF setThreadLocalSeed)`!(Engine, A...)`) $(TD Initialize thread-local `Engine` with a known seed rather than a random seed.)) | ) | |$(LEADINGROW Traits) |$(TR | $(RROW EngineReturnType, Get return type of random number _engine's `opCall()`) | $(RROW isRandomEngine, Check if is random number _engine) | $(RROW isSaturatedRandomEngine, Check if random number _engine `G` such that `G.max == EngineReturnType!(G).max`) | $(RROW preferHighBits, Are the high bits of the _engine's output known to have better statistical properties than the low bits?)) | |$(LEADINGROW C Interface) | $(RROW mir_random_engine_ctor, Perform any necessary setup. Automatically called by DRuntime.) | $(RROW mir_random_engine_dtor, Release any resources. Automatically called by DRuntime.) | $(RROW mir_random_genRandomNonBlocking, External name for $(LREF genRandomNonBlocking)) | $(RROW mir_random_genRandomBlocking, External name for $(LREF genRandomBlocking)) |) | |Copyright: Ilya Yaroshenko 2016-. |License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). |Authors: Ilya Yaroshenko | |Macros: | T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) | RROW = $(TR $(TDNW $(LREF $1)) $(TD $+)) |+/ |module mir.random.engine; | |version (OSX) | version = Darwin; |else version (iOS) | version = Darwin; |else version (TVOS) | version = Darwin; |else version (WatchOS) | version = Darwin; | |// A secure arc4random implementation that uses some modern algorithm rather |// than ARC4 may be used synonymously with non-blocking system entropy. |version (CRuntime_Bionic) | version = SecureARC4Random; // ChaCha20 |version (Darwin) | version = SecureARC4Random; // AES |version (OpenBSD) | version = SecureARC4Random; // ChaCha20 |version (NetBSD) | version = SecureARC4Random; // ChaCha20 | |// A legacy arc4random should not be used when cryptographic security |// is required but may used for `unpredictableSeed`. |version (CRuntime_UClibc) | version = LegacyARC4Random; // ARC4 |version (FreeBSD) | version = LegacyARC4Random; // ARC4 |version (DragonFlyBSD) | version = LegacyARC4Random; // ARC4 |version (BSD) | version = LegacyARC4Random; // Unknown implementation | |version (SecureARC4Random) | version = AnyARC4Random; |version (LegacyARC4Random) | version = AnyARC4Random; | |version (D_betterC) | private enum bool THREAD_LOCAL_STORAGE_AVAILABLE = false; |else | private enum bool THREAD_LOCAL_STORAGE_AVAILABLE = __traits(compiles, { static size_t x = 0; }); | |import std.traits; | |import mir.random.engine.mersenne_twister; | |/++ |Like `std.traits.ReturnType!T` but it works even if |T.opCall is a function template. |+/ |template EngineReturnType(T) |{ | import std.traits : ReturnType; | static if (is(ReturnType!T)) | alias EngineReturnType = ReturnType!T; | else | alias EngineReturnType = typeof(T.init()); |} | |/++ |Test if T is a random engine. |A type should define `enum isRandomEngine = true;` to be a random engine. |+/ |template isRandomEngine(T) |{ | static if (is(typeof(T.isRandomEngine) : bool) && is(typeof(T.init()))) | { | private alias R = typeof(T.init()); | static if (T.isRandomEngine && isUnsigned!R) | enum isRandomEngine = is(typeof({ | enum max = T.max; | static assert(is(typeof(T.max) == R)); | })); | else enum isRandomEngine = false; | } | else enum isRandomEngine = false; |} | |/++ |Test if T is a saturated random-bit generator. |A random number generator is saturated if `T.max == ReturnType!T.max`. |A type should define `enum isRandomEngine = true;` to be a random engine. |+/ |template isSaturatedRandomEngine(T) |{ | static if (isRandomEngine!T) | enum isSaturatedRandomEngine = T.max == EngineReturnType!T.max; | else | enum isSaturatedRandomEngine = false; |} | |/++ |Are the high bits of the engine's output known to have |better statistical properties than the low bits of the |output? This property is set by checking the value of |an optional enum named `preferHighBits`. If the property |is missing it is treated as false. | |This should be specified as true for: | |+/ |template preferHighBits(G) | if (isSaturatedRandomEngine!G) |{ | static if (__traits(compiles, { enum bool e = G.preferHighBits; })) | private enum bool preferHighBits = G.preferHighBits; | else | private enum bool preferHighBits = false; |} | |/* | * Marker indicating it's safe to construct from void | * (i.e. the constructor doesn't depend on the struct | * being in an initially valid state). | * Either checks an explicit flag `_isVoidInitOkay` | * or tests to make sure that the structure contains | * nothing that looks like a pointer or an index into | * an array. Also ensures that there is not an elaborate | * destructor since it could be called when the struct | * is in an invalid state. | * Non-public because we don't want to commit to this | * design. | */ |package template _isVoidInitOkay(G) if (isRandomEngine!G && is(G == struct)) |{ | static if (is(typeof(G._isVoidInitOkay) : bool)) | enum bool _isVoidInitOkay = G._isVoidInitOkay; | else static if (!hasNested!G && !hasElaborateDestructor!G) | { | import std.meta : allSatisfy; | static if (allSatisfy!(isScalarType, FieldTypeTuple!G)) | //All members are scalars. | enum bool _isVoidInitOkay = true; | else static if (FieldTypeTuple!(G).length == 1 && isStaticArray!(FieldTypeTuple!(G)[0])) | //Only has one member which is a static array of scalars. | enum bool _isVoidInitOkay = isScalarType!(typeof(FieldTypeTuple!(G)[0].init[0])); | else | enum bool _isVoidInitOkay = false; | } | else | enum bool _isVoidInitOkay = false; |} |@nogc nothrow pure @safe version(mir_random_test) |{ | import mir.random.engine.mersenne_twister: Mt19937, Mt19937_64; | //Ensure that this property is set for the Mersenne Twister, | //whose internal state is huge enough for this to potentially | //matter: | static assert(_isVoidInitOkay!Mt19937); | static assert(_isVoidInitOkay!Mt19937_64); | //Check that the property is set for a moderately-sized PRNG. | import mir.random.engine.xorshift: Xorshift1024StarPhi; | static assert(_isVoidInitOkay!Xorshift1024StarPhi); | //Check that PRNGs not explicitly marked as void-init safe | //can be inferred as such if they only have scalar fields. | import mir.random.engine.pcg: pcg32, pcg32_oneseq; | import mir.random.engine.splitmix: SplitMix64; | static assert(_isVoidInitOkay!pcg32); | static assert(_isVoidInitOkay!pcg32_oneseq); | static assert(_isVoidInitOkay!SplitMix64); | //Check that PRNGs not explicitly marked as void-init safe | //can be inferred as such if their only field is a static | //array of scalars. | import mir.random.engine.xorshift: Xorshift128, Xoroshiro128Plus; | static assert(_isVoidInitOkay!Xorshift128); | static assert(_isVoidInitOkay!Xoroshiro128Plus); |} | |version (D_Ddoc) |{ | /++ | A "good" seed for initializing random number engines. Initializing | with $(D_PARAM unpredictableSeed) makes engines generate different | random number sequences every run. | | Returns: | A single unsigned integer seed value, different on each successive call | +/ | pragma(inline, true) | @property size_t unpredictableSeed() @trusted nothrow @nogc | { | return unpredictableSeed!size_t; | } |} | |/// ditto |pragma(inline, true) |@property T unpredictableSeed(T = size_t)() @trusted nothrow @nogc | if (isUnsigned!T) |{ | import mir.utility: _expect; 0000000| T seed = void; | version (AnyARC4Random) | { | // If we just need 32 bits it's faster to call arc4random() | // than arc4random_buf(&seed, seed.sizeof). | static if (T.sizeof <= uint.sizeof) | seed = cast(T) arc4random(); | else | arc4random_buf(&seed, seed.sizeof); | } 0000000| else if (_expect(genRandomNonBlocking(&seed, seed.sizeof) != T.sizeof, false)) | { | // fallback to old time/thread-based implementation in case of errors 0000000| seed = cast(T) fallbackSeed(); | } 0000000| return seed; |} | |// Old name of `unpredictableSeedOf!T`. Undocumented but |// defined so existing code using mir.random won't break. |deprecated("Use unpredictableSeed!T instead of unpredictableSeedOf!T") |public alias unpredictableSeedOf(T) = unpredictableSeed!T; | |version (mir_random_test) @nogc nothrow @safe unittest |{ | // Check unpredictableSeed syntax works with or without parentheses. | auto a = unpredictableSeed; | auto b = unpredictableSeed!uint; | auto c = unpredictableSeed!ulong; | static assert(is(typeof(a) == size_t)); | static assert(is(typeof(b) == uint)); | static assert(is(typeof(c) == ulong)); | | auto d = unpredictableSeed(); | auto f = unpredictableSeed!uint(); | auto g = unpredictableSeed!ulong(); | static assert(is(typeof(d) == size_t)); | static assert(is(typeof(f) == uint)); | static assert(is(typeof(g) == ulong)); |} | |// Is llvm_readcyclecounter supported on this platform? |// We need to whitelist platforms where it is known to work because if it |// isn't supported it will compile but always return 0. |// https://llvm.org/docs/LangRef.html#llvm-readcyclecounter-intrinsic |version(LDC) |{ | // The only architectures the documentation says are supported are | // x86 and Alpha. x86 uses RDTSC and Alpha uses RPCC. | version(X86_64) version = LLVMReadCycleCounter; | // Do *not* support 32-bit x86 because some x86 processors don't | // support `rdtsc` and because on x86 (but not x86-64) Linux | // `prctl` can disable a process's ability to use `rdtsc`. | else version(Alpha) version = LLVMReadCycleCounter; |} | | |pragma(inline, false) |private ulong fallbackSeed()() |{ | // fallback to old time/thread-based implementation in case of errors | version(LLVMReadCycleCounter) | { | import ldc.intrinsics : llvm_readcyclecounter; | ulong ticks = llvm_readcyclecounter(); | } | else version(D_InlineAsm_X86_64) | { | // RDTSC takes around 22 clock cycles. | ulong ticks = void; | asm @nogc nothrow | { | rdtsc; | shl RDX, 32; | xor RDX, RAX; | mov ticks, RDX; | } | } | //else version(D_InlineAsm_X86) | //{ | // // We don't use `rdtsc` with version(D_InlineAsm_X86) because | // // some x86 processors don't support `rdtsc` and because on | // // x86 (but not x86-64) Linux `prctl` can disable a process's | // // ability to use `rdtsc`. | // static assert(0); | //} | else version(Windows) | { | import core.sys.windows.winbase : QueryPerformanceCounter; | ulong ticks = void; | QueryPerformanceCounter(cast(long*)&ticks); | } | else version(Darwin) | { | import core.time : mach_absolute_time; | ulong ticks = mach_absolute_time(); | } | else version(Posix) | { | import core.sys.posix.time : clock_gettime, CLOCK_MONOTONIC, timespec; 0000000| timespec ts = void; 0000000| const tserr = clock_gettime(CLOCK_MONOTONIC, &ts); | // Should never fail. Only allowed arror codes are | // EINVAL if the 1st argument is an invalid clock ID and | // EFAULT if the 2nd argument is an invalid address. 0000000| assert(tserr == 0, "Call to clock_gettime failed."); 0000000| ulong ticks = (cast(ulong) ts.tv_sec << 32) ^ ts.tv_nsec; | } | version(Posix) | { | import core.sys.posix.unistd : getpid; | import core.sys.posix.pthread : pthread_self; 0000000| auto pid = cast(uint) getpid; 0000000| auto tid = cast(uint) pthread_self(); | } | else | version(Windows) | { | import core.sys.windows.winbase : GetCurrentProcessId, GetCurrentThreadId; | auto pid = cast(uint) GetCurrentProcessId; | auto tid = cast(uint) GetCurrentThreadId; | } 0000000| ulong k = ((cast(ulong)pid << 32) ^ tid) + ticks; 0000000| k ^= k >> 33; 0000000| k *= 0xff51afd7ed558ccd; 0000000| k ^= k >> 33; 0000000| k *= 0xc4ceb9fe1a85ec53; 0000000| k ^= k >> 33; 0000000| return k; |} | |/// |@safe version(mir_random_test) unittest |{ | auto rnd = Random(unpredictableSeed); | auto n = rnd(); | static assert(is(typeof(n) == size_t)); |} | |/++ |The "default", "favorite", "suggested" random number generator type on |the current platform. It is an alias for one of the |generators. You may want to use it if (1) you need to generate some |nice random numbers, and (2) you don't care for the minutiae of the |method being used. |+/ |static if (is(size_t == uint)) | alias Random = Mt19937; |else | alias Random = Mt19937_64; | |/// |version(mir_random_test) unittest |{ | import std.traits; | static assert(isSaturatedRandomEngine!Random); | static assert(is(EngineReturnType!Random == size_t)); |} | |static if (THREAD_LOCAL_STORAGE_AVAILABLE) |{ | /++ | Thread-local instance of the default $(LREF Random) allocated and seeded independently | for each thread. Requires $(LINK2 https://en.wikipedia.org/wiki/Thread-local_storage, TLS). | +/ | alias rne = threadLocal!Random; | /// | @nogc nothrow @safe version(mir_random_test) unittest | { | import mir.random; | import std.complex; | | auto c = complex(rne.rand!real, rne.rand!real); | | int[10] array; | foreach (ref e; array) | e = rne.rand!int; | auto picked = array[rne.randIndex(array.length)]; | } | | private static struct TL(Engine) | if (isSaturatedRandomEngine!Engine && is(Engine == struct)) | { | static bool initialized; | static if (_isVoidInitOkay!Engine) | static Engine engine = void; | else static if (__traits(compiles, { Engine defaultConstructed; })) | static Engine engine; | else | static Engine engine = Engine.init; | | static if (is(ucent) && is(typeof((ucent t) => Engine(t)))) | alias seed_t = ucent; | else static if (is(typeof((ulong t) => Engine(t)))) | alias seed_t = ulong; | else static if (is(typeof((uint t) => Engine(t)))) | alias seed_t = uint; | else | alias seed_t = EngineReturnType!Engine; | | pragma(inline, false) // Usually called only once per thread. | private static void reseed() | { 0000000| engine.__ctor(unpredictableSeed!(seed_t)); 0000000| initialized = true; | } | } | /++ | `threadLocal!Engine` returns a reference to a thread-local instance of | the specified random number generator allocated and seeded uniquely | for each thread. Requires $(LINK2 https://en.wikipedia.org/wiki/Thread-local_storage, TLS). | | `threadLocalPtr!Engine` is a pointer to the area of thread-local | storage used by `threadLocal!Engine`. This function is provided because | the compiler can infer it is `@safe`, unlike `&(threadLocal!Engine)`. | Like `threadLocal!Engine` this function will auto-initialize the engine. | $(I Do not share pointers returned by threadLocalPtr between | threads!) | | `threadLocalInitialized!Engine` is a low-level way to explicitly change | the "initialized" flag used by `threadLocal!Engine` to determine whether | the Engine needs to be seeded. Setting this to `false` gives a way of | forcing the next call to `threadLocal!Engine` to reseed. In general this | is unnecessary but there are some specialized use cases where users have | requested this ability. | +/ | @property ref Engine threadLocal(Engine)() | if (isSaturatedRandomEngine!Engine && is(Engine == struct)) | { | version (DigitalMars) | pragma(inline);//DMD may fail to inline this. | else | pragma(inline, true); | import mir.utility: _expect; 0000000| if (_expect(!TL!Engine.initialized, false)) | { 0000000| TL!Engine.reseed(); | } 0000000| return TL!Engine.engine; | } | /// ditto | @property Engine* threadLocalPtr(Engine)() | if (isSaturatedRandomEngine!Engine && is(Engine == struct)) | { | version (DigitalMars) | pragma(inline);//DMD may fail to inline this. | else | pragma(inline, true); | import mir.utility: _expect; | if (_expect(!TL!Engine.initialized, false)) | { | TL!Engine.reseed(); | } | return &TL!Engine.engine; | } | /// ditto | @property ref bool threadLocalInitialized(Engine)() | if (isSaturatedRandomEngine!Engine && is(Engine == struct)) | { | version (DigitalMars) | pragma(inline);//DMD may fail to inline this. | else | pragma(inline, true); | return TL!Engine.initialized; | } | /// | @nogc nothrow @safe version(mir_random_test) unittest | { | import mir.random; | import mir.random.engine.xorshift; | | alias gen = threadLocal!Xorshift1024StarPhi; | double x = gen.rand!double; | size_t i = gen.randIndex(100u); | ulong a = gen.rand!ulong; | } | /// | @nogc nothrow @safe version(mir_random_test) unittest | { | import mir.random; | //If you need a pointer to the engine, getting it like this is @safe: | Random* ptr = threadLocalPtr!Random; | } | /// | @nogc nothrow @safe version(mir_random_test) unittest | { | import mir.random; | import mir.random.engine.xorshift; | //If you need to mark the engine as uninitialized to force a reseed, | //you can do it like this: | threadLocalInitialized!Xorshift1024StarPhi = false; | } | /// | @nogc nothrow @safe version(mir_random_test) unittest | { | import mir.random; | import mir.random.engine.mersenne_twister; | //You can mark the engine as already initialized to skip | //automatic seeding then initialize it yourself, for instance | //if you want to use a known seed rather than a random one. | threadLocalInitialized!Mt19937 = true; | immutable uint[4] customSeed = [0x123, 0x234, 0x345, 0x456]; | threadLocal!Mt19937.__ctor(customSeed); | foreach(_; 0..999) | threadLocal!Mt19937.rand!uint; | assert(3460025646u == threadLocal!Mt19937.rand!uint); | } | /// | @nogc nothrow @safe version(mir_random_test) unittest | { | import mir.random; | import mir.random.engine.xorshift; | | alias gen = threadLocal!Xorshift1024StarPhi; | | //If you want to you can call the generator's opCall instead of using | //rand!T but it is somewhat clunky because of the ambiguity of | //@property syntax: () looks like optional function parentheses. | static assert(!__traits(compiles, {ulong x0 = gen();}));//<-- Won't work | static assert(is(typeof(gen()) == Xorshift1024StarPhi));//<-- because the type is this. | ulong x1 = gen.opCall();//<-- This works though. | ulong x2 = gen()();//<-- This also works. | | //But instead of any of those you should really just use gen.rand!T. | ulong x3 = gen.rand!ulong; | } |// /// |// @nogc nothrow pure @safe version(mir_random_test) unittest |// { |// //If you want something like Phobos std.random.rndGen and |// //don't care about the specific algorithm you can do this: |// alias rndGen = threadLocal!Random; |// } | | @nogc nothrow @system version(mir_random_test) unittest | { | //Verify Returns same instance every time per thread. | import mir.random; | import mir.random.engine.xorshift; | | Xorshift1024StarPhi* addr = &(threadLocal!Xorshift1024StarPhi()); | Xorshift1024StarPhi* sameAddr = &(threadLocal!Xorshift1024StarPhi()); | assert(addr is sameAddr); | assert(sameAddr is threadLocalPtr!Xorshift1024StarPhi); | } | | /++ | Sets or resets the _seed of `threadLocal!Engine` using the given arguments. | It is not necessary to call this except if you wish to ensure the | PRNG uses a known _seed. | +/ | void setThreadLocalSeed(Engine, A...)(auto ref A seed) | if (isSaturatedRandomEngine!Engine && is(Engine == struct) | && A.length >= 1 && is(typeof((ref A a) => Engine(a)))) | { | TL!Engine.initialized = true; | TL!Engine.engine.__ctor(seed); | } | /// | @nogc nothrow @system version(mir_random_test) unittest | { | import mir.random; | | alias rnd = threadLocal!Random; | | setThreadLocalSeed!Random(123); | immutable float x = rnd.rand!float; | | assert(x != rnd.rand!float); | | setThreadLocalSeed!Random(123); | immutable float y = rnd.rand!float; | | assert(x == y); | } |} |else |{ | static assert(!THREAD_LOCAL_STORAGE_AVAILABLE); | | @property ref Random rne()() | { | static assert(0, "Thread-local storage not available!"); | } | | template threadLocal(T) | { | static assert(0, "Thread-local storage not available!"); | } | | template threadLocalPtr(T) | { | static assert(0, "Thread-local storage not available!"); | } | | template threadLocalInitialized(T) | { | static assert(0, "Thread-local storage not available!"); | } | | template setThreadLocalSeed(T, A...) | { | static assert(0, "Thread-local storage not available!"); | } |} | |version(linux) |{ | import mir.linux._asm.unistd; | enum bool LINUX_NR_GETRANDOM = (__traits(compiles, {enum e = NR_getrandom;})); | //If X86_64 or X86 are missing there is a problem with the library. | static if (!LINUX_NR_GETRANDOM) | { | version (X86_64) | static assert(0, "Missing linux syscall constants!"); | version (X86) | static assert(0, "Missing linux syscall constants!"); | } |} |else | enum bool LINUX_NR_GETRANDOM = false; | |static if (LINUX_NR_GETRANDOM) |{ | // getrandom was introduced in Linux 3.17 | private __gshared bool getRandomFailedENOSYS = false; | | private extern(C) int syscall(size_t ident, size_t n, size_t arg1, size_t arg2) @nogc nothrow; | | /* | * Flags for getrandom(2) | * | * GRND_NONBLOCK Don't block and return EAGAIN instead | * GRND_RANDOM Use the /dev/random pool instead of /dev/urandom | */ | private enum GRND_NONBLOCK = 0x0001; | private enum GRND_RANDOM = 0x0002; | | private enum GETRANDOM = NR_getrandom; | | /* | http://man7.org/linux/man-pages/man2/getrandom.2.html | If the urandom source has been initialized, reads of up to 256 bytes | will always return as many bytes as requested and will not be | interrupted by signals. No such guarantees apply for larger buffer | sizes. | */ | private ptrdiff_t genRandomImplSysBlocking()(scope void* ptr, size_t len) @nogc nothrow @system | { 0000000| while (len > 0) | { 0000000| auto res = syscall(GETRANDOM, cast(size_t) ptr, len, 0); 0000000| if (res >= 0) | { 0000000| len -= res; 0000000| ptr += res; | } | else | { 0000000| return res; | } | } 0000000| return 0; | } | | /* | * If the GRND_NONBLOCK flag is set, then | * getrandom() does not block in these cases, but instead | * immediately returns -1 with errno set to EAGAIN. | */ | private ptrdiff_t genRandomImplSysNonBlocking()(scope void* ptr, size_t len) @nogc nothrow @system | { 0000000| return syscall(GETRANDOM, cast(size_t) ptr, len, GRND_NONBLOCK); | } |} | |version(AnyARC4Random) |extern(C) private @nogc nothrow |{ | void arc4random_buf(scope void* buf, size_t nbytes) @system; | uint arc4random() @trusted; |} | |version(Darwin) |{ | //On Darwin /dev/random is identical to /dev/urandom (neither blocks | //when there is low system entropy) so there is no point mucking | //about with file descriptors. Just use arc4random_buf for both. |} |else version(Posix) |{ | import core.stdc.stdio : fclose, feof, ferror, fopen, fread; | alias IOType = typeof(fopen("a", "b")); | private __gshared IOType fdRandom; | version (SecureARC4Random) | { | //Don't need /dev/urandom if we have arc4random_buf. | } | else | private __gshared IOType fdURandom; | | | /* The /dev/random device is a legacy interface which dates back to a | time where the cryptographic primitives used in the implementation of | /dev/urandom were not widely trusted. It will return random bytes | only within the estimated number of bits of fresh noise in the | entropy pool, blocking if necessary. /dev/random is suitable for | applications that need high quality randomness, and can afford | indeterminate delays. | | When the entropy pool is empty, reads from /dev/random will block | until additional environmental noise is gathered. | */ | private ptrdiff_t genRandomImplFileBlocking()(scope void* ptr, size_t len) @nogc nothrow @system | { 0000000| if (fdRandom is null) | { 0000000| fdRandom = fopen("/dev/random", "r"); 0000000| if (fdRandom is null) 0000000| return -1; | } | 0000000| while (len > 0) | { 0000000| auto res = fread(ptr, 1, len, fdRandom); 0000000| len -= res; 0000000| ptr += res; | // check for possible permanent errors 0000000| if (len != 0) | { 0000000| if (fdRandom.ferror) 0000000| return -1; | 0000000| if (fdRandom.feof) 0000000| return -1; | } | } | 0000000| return 0; | } |} | |version (SecureARC4Random) |{ | //Don't need /dev/urandom if we have arc4random_buf. |} |else version(Posix) |{ | /** | When read, the /dev/urandom device returns random bytes using a | pseudorandom number generator seeded from the entropy pool. Reads | from this device do not block (i.e., the CPU is not yielded), but can | incur an appreciable delay when requesting large amounts of data. | When read during early boot time, /dev/urandom may return data prior | to the entropy pool being initialized. | */ | private ptrdiff_t genRandomImplFileNonBlocking()(scope void* ptr, size_t len) @nogc nothrow @system | { 0000000| if (fdURandom is null) | { 0000000| fdURandom = fopen("/dev/urandom", "r"); 0000000| if (fdURandom is null) 0000000| return -1; | } | 0000000| auto res = fread(ptr, 1, len, fdURandom); | // check for possible errors 0000000| if (res != len) | { 0000000| if (fdURandom.ferror) 0000000| return -1; | 0000000| if (fdURandom.feof) 0000000| return -1; | } 0000000| return res; | } |} | |version(Windows) |{ | // the wincrypt headers in druntime are broken for x64! | private alias ULONG_PTR = size_t; // uint in druntime | private alias BOOL = bool; | private alias DWORD = uint; | private alias LPCWSTR = wchar*; | private alias PBYTE = ubyte*; | private alias HCRYPTPROV = ULONG_PTR; | private alias LPCSTR = const(char)*; | | private extern(Windows) BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE) @nogc @safe nothrow; | private extern(Windows) BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD) @nogc nothrow; | private extern(Windows) BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD) @nogc nothrow; | private extern(Windows) BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR) @nogc nothrow; | | private __gshared ULONG_PTR hProvider; | | private auto initGetRandom()() @nogc @trusted nothrow | { | import core.sys.windows.winbase : GetLastError; | import core.sys.windows.winerror : NTE_BAD_KEYSET; | import core.sys.windows.wincrypt : PROV_RSA_FULL, CRYPT_NEWKEYSET, CRYPT_VERIFYCONTEXT, CRYPT_SILENT; | | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa379886(v=vs.85).aspx | // For performance reasons, we recommend that you set the pszContainer | // parameter to NULL and the dwFlags parameter to CRYPT_VERIFYCONTEXT | // in all situations where you do not require a persisted key. | // CRYPT_SILENT is intended for use with applications for which the UI cannot be displayed by the CSP. | if (!CryptAcquireContextW(&hProvider, null, null, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) | { | if (GetLastError() == NTE_BAD_KEYSET) | { | // Attempt to create default container | if (!CryptAcquireContextA(&hProvider, null, null, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_SILENT)) | return 1; | } | else | { | return 1; | } | } | | return 0; | } |} | |/++ |Constructs the mir random seed generators. |This constructor needs to be called once $(I before) |other calls in `mir.random.engine`. | |Automatically called by DRuntime. |+/ |extern(C) void mir_random_engine_ctor() @system nothrow @nogc |{ | version(Windows) | { | if (hProvider == 0) | initGetRandom; | } |} | |/++ |Destructs the mir random seed generators. | |Automatically called by DRuntime. |+/ |extern(C) void mir_random_engine_dtor() @system nothrow @nogc |{ | version(Windows) | { | if (hProvider > 0) | CryptReleaseContext(hProvider, 0); | } | else | version(Darwin) | { | | } | else | version(Posix) | { 0000000| if (fdRandom !is null) 0000000| fdRandom.fclose; | | version (SecureARC4Random) | { | //Don't need /dev/urandom if we have arc4random_buf. | } 0000000| else if (fdURandom !is null) 0000000| fdURandom.fclose; | } |} | | |version(D_BetterC) |{ | pragma(crt_constructor) | extern(C) void mir_random_engine_ctor_() @system nothrow @nogc | { | mir_random_engine_ctor(); | } | | pragma(crt_destructor) | extern(C) void mir_random_engine_dtor_() @system nothrow @nogc | { | mir_random_engine_dtor(); | } |} |else |{ | /// Automatically calls the extern(C) module constructor | shared static this() | { 1| mir_random_engine_ctor(); | } | | /// Automatically calls the extern(C) module destructor | shared static ~this() | { 0000000| mir_random_engine_dtor(); | } |} | |/++ |Fills a buffer with random data. |If not enough entropy has been gathered, it will block. | |Note that on Mac OS X this method will never block. | |Params: | ptr = pointer to the buffer to fill | len = length of the buffer (in bytes) | |Returns: | A non-zero integer if an error occurred. |+/ |extern(C) ptrdiff_t mir_random_genRandomBlocking(scope void* ptr , size_t len) @nogc nothrow @system |{ | version(Windows) | { | static if (DWORD.max >= size_t.max) | while(!CryptGenRandom(hProvider, len, cast(PBYTE) ptr)) {} | else | while (len != 0) | { | import mir.utility : min; | const n = min(DWORD.max, len); | if (CryptGenRandom(hProvider, cast(DWORD) n, cast(PBYTE) ptr)) | { | len -= n; | } | } | return 0; | } | else version (Darwin) | { | arc4random_buf(ptr, len); | return 0; | } | else | { | static if (LINUX_NR_GETRANDOM) 0000000| if (!getRandomFailedENOSYS) // harmless data race | { | import core.stdc.errno; 0000000| ptrdiff_t result = genRandomImplSysBlocking(ptr, len); 0000000| if (result >= 0) 0000000| return result; 0000000| if (errno != ENOSYS) 0000000| return result; 0000000| getRandomFailedENOSYS = true; // harmless data race | } 0000000| return genRandomImplFileBlocking(ptr, len); | } |} | |/// ditto |alias genRandomBlocking = mir_random_genRandomBlocking; | |/// ditto |ptrdiff_t genRandomBlocking()(scope ubyte[] buffer) @nogc nothrow @trusted |{ | pragma(inline, true); | return mir_random_genRandomBlocking(buffer.ptr, buffer.length); |} | |/// |@safe nothrow version(mir_random_test) unittest |{ | ubyte[] buf = new ubyte[10]; | genRandomBlocking(buf); | | int sum; | foreach (b; buf) | sum += b; | | assert(sum > 0, "Only zero points generated"); |} | |@nogc nothrow @safe version(mir_random_test) unittest |{ | ubyte[10] buf; | genRandomBlocking(buf); | | int sum; | foreach (b; buf) | sum += b; | | assert(sum > 0, "Only zero points generated"); |} | |/++ |Fills a buffer with random data. |If not enough entropy has been gathered, it won't block. |Hence the error code should be inspected. | |On Linux >= 3.17 genRandomNonBlocking is guaranteed to succeed for 256 bytes and |fewer. | |On Mac OS X, OpenBSD, and NetBSD genRandomNonBlocking is guaranteed to |succeed for any number of bytes. | |Params: | buffer = the buffer to fill | len = length of the buffer (in bytes) | |Returns: | The number of bytes filled - a negative number if an error occurred |+/ |extern(C) size_t mir_random_genRandomNonBlocking(scope void* ptr, size_t len) @nogc nothrow @system |{ | version(Windows) | { | static if (DWORD.max < size_t.max) | if (len > DWORD.max) | len = DWORD.max; | if (!CryptGenRandom(hProvider, cast(DWORD) len, cast(PBYTE) ptr)) | return -1; | return len; | } | else version(SecureARC4Random) | { | arc4random_buf(ptr, len); | return len; | } | else | { | static if (LINUX_NR_GETRANDOM) 0000000| if (!getRandomFailedENOSYS) // harmless data race | { | import core.stdc.errno; 0000000| ptrdiff_t result = genRandomImplSysNonBlocking(ptr, len); 0000000| if (result >= 0) 0000000| return result; 0000000| if (errno != ENOSYS) 0000000| return result; 0000000| getRandomFailedENOSYS = true; // harmless data race | } 0000000| return genRandomImplFileNonBlocking(ptr, len); | } |} |/// ditto |alias genRandomNonBlocking = mir_random_genRandomNonBlocking; |/// ditto |size_t genRandomNonBlocking()(scope ubyte[] buffer) @nogc nothrow @trusted |{ | pragma(inline, true); | return mir_random_genRandomNonBlocking(buffer.ptr, buffer.length); |} | |/// |@safe nothrow version(mir_random_test) unittest |{ | ubyte[] buf = new ubyte[10]; | genRandomNonBlocking(buf); | | int sum; | foreach (b; buf) | sum += b; | | assert(sum > 0, "Only zero points generated"); |} | |@nogc nothrow @safe |version(mir_random_test) unittest |{ | ubyte[10] buf; | genRandomNonBlocking(buf); | | int sum; | foreach (b; buf) | sum += b; | | assert(sum > 0, "Only zero points generated"); |} ../../../.dub/packages/mir-random-2.2.16/mir-random/source/mir/random/engine/package.d is 1% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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; |import std.range.primitives: isInfinite, isInputRange, ElementType; | |/** | * 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 && !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; | | 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) | { | auto length = r.length; | if (length == 0) | return null; | | import mir.conv : emplaceRef; | import std.array: uninitializedArray; | | auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))(); | | static if (isInputRange!Range) | { | foreach(ref e; result) | { | emplaceRef!E(e, r.front); | r.popFront; | } | } | else | static if (isPointer!Range) | { | auto it = result; | foreach(ref f; *r) | { | emplaceRef!E(it[0], f); | it = it[1 .. $]; | } | } | else | { | auto it = result; | foreach (f; r) | { | import mir.functional: forward; | emplaceRef!E(it[0], forward!f); | it = it[1 .. $]; | } | } | | return (() @trusted => cast(E[]) result)(); | } | else | { | import mir.appender: ScopedBuffer; | | if (false) | { | ScopedBuffer!(Unqual!E) a; | static if (isInputRange!Range) | for (; !r.empty; r.popFront) | a.put(r.front); | else | static if (isPointer!Range) | { | foreach (e; *r) | a.put(forward!e); | } | else | { | foreach (e; r) | a.put(forward!e); | } | } | | return () @trusted { | ScopedBuffer!(Unqual!E) a = void; | a.initialize; | | static if (isInputRange!Range) | for (; !r.empty; r.popFront) | a.put(r.front); | else | static if (isPointer!Range) | { | foreach (e; *r) | a.put(forward!e); | } | else | { | foreach (e; r) | a.put(forward!e); | } | | import std.array: uninitializedArray; | auto ret = uninitializedArray!(Unqual!E[])(a.length); | a.moveDataAndEmplaceTo(ret); | return ret; | } (); | } |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | auto a = array([1, 2, 3, 4, 5][]); | assert(a == [ 1, 2, 3, 4, 5 ]); |} | |@safe pure nothrow version(mir_test) unittest |{ | import mir.algorithm.iteration : equal; | struct Foo | { | int a; | } | auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]); | 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() {} | } | | auto arr = (new MyRange).array; | assert(arr.empty); |} | |@system pure nothrow version(mir_test) unittest |{ | immutable int[] a = [1, 2, 3, 4]; | auto b = (&a).array; | 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) | { | return a == foo.a; | } | } | auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]); | 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;} | auto a = array(immutable(S).init.repeat(5)); | assert(a.length == 5); |} | |/// |@safe version(mir_test) unittest |{ | assert("Hello D".array == "Hello D"); | assert("Hello D"w.array == "Hello D"w); | assert("Hello D"d.array == "Hello D"d); |} | |@system version(mir_test) unittest |{ | // @system due to array!string | import std.conv : to; | | static struct TestArray { int x; string toString() @safe { return to!string(x); } } | | static struct OpAssign | { | uint num; | this(uint num) { this.num = num; } | | // Templating opAssign to make sure the bugs with opAssign being | // templated are fixed. | void opAssign(T)(T rhs) { this.num = rhs.num; } | } | | static struct OpApply | { | int opApply(scope int delegate(ref int) dg) | { | int res; | foreach (i; 0 .. 10) | { | res = dg(i); | if (res) break; | } | | return res; | } | } | | auto a = array([1, 2, 3, 4, 5][]); | assert(a == [ 1, 2, 3, 4, 5 ]); | | auto b = array([TestArray(1), TestArray(2)][]); | assert(b == [TestArray(1), TestArray(2)]); | | class C | { | int x; | this(int y) { x = y; } | override string toString() const @safe { return to!string(x); } | } | auto c = array([new C(1), new C(2)][]); | assert(c[0].x == 1); | assert(c[1].x == 2); | | auto d = array([1.0, 2.2, 3][]); | assert(is(typeof(d) == double[])); | assert(d == [1.0, 2.2, 3]); | | auto e = [OpAssign(1), OpAssign(2)]; | auto f = array(e); | assert(e == f); | | assert(array(OpApply.init) == [0,1,2,3,4,5,6,7,8,9]); | assert(array("ABC") == "ABC"); | assert(array("ABC".dup) == "ABC"); |} | |//Bug# 8233 |@safe version(mir_test) unittest |{ | assert(array("hello world"d) == "hello world"d); | immutable a = [1, 2, 3, 4, 5]; | assert(array(a) == a); | const b = a; | 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)) | { | auto arr = [T(1), T(2), T(3), T(4)]; | assert(array(arr) == arr); | } |} | |@safe version(mir_test) unittest |{ | //9824 | static struct S | { | @disable void opAssign(S); | int i; | } | auto arr = [S(0), S(1), S(2)]; | 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(); | this(int v) { val = v; } | } | static immutable r = S(1).repeat(2).array(); | assert(equal(r, [S(1), S(1)])); |} | |@safe version(mir_test) unittest |{ | //Turn down infinity: | static assert(!is(typeof( | repeat(1).array() | ))); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/array/allocation.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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) |{ | /// | package T* _payload; | package ref mir_rc_context context() inout scope return pure nothrow @nogc @trusted @property | { | assert(_payload); | return (cast(mir_rc_context*)_payload)[-1]; | } | 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 | { | auto t = this._payload; | this._payload = rhs._payload; | rhs._payload = t; | } | | /// | this(typeof(null)) | { | } | | /// | mixin CommonRCImpl; | | /// | pragma(inline, true) | bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc | { | return !this; | } | | /// ditto | bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc | { | return opIndex() == rhs.opIndex(); | } | | /// | int opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc | { | return __cmp(opIndex(), rhs.opIndex()); | } | | /// | size_t toHash() @trusted scope const pure nothrow @nogc | { | return hashOf(opIndex()); | } | | /// | ~this() nothrow | { | static if (hasElaborateDestructor!T || hasDestructor!T) | { | if (false) // break @safe and pure attributes | { | Unqual!T* object; | (*object).__xdtor; | } | } | if (this) | { | (() @trusted { mir_rc_decrease_counter(context); })(); | } | } | | /// | size_t length() @trusted scope pure nothrow @nogc const @property | { | return _payload !is null ? context.length : 0; | } | | /// | inout(T)* ptr() @system scope inout | { | return _payload; | } | | /// | ref opIndex(size_t i) @trusted scope inout | { | assert(_payload); | assert(i < context.length); | return _payload[i]; | } | | /// | inout(T)[] opIndex() @trusted scope inout | { | return _payload !is null ? _payload[0 .. context.length] : null; | } | | /// | size_t opDollar(size_t pos : 0)() @trusted scope pure nothrow @nogc const | { | return length; | } | | /// | auto asSlice()() @property | { | import mir.ndslice.slice: mir_slice; | alias It = mir_rci!T; | 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; | 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`. | +/ | this(size_t length, bool initialize = true, bool deallocate = true) @trusted @nogc | { | if (length == 0) | return; | Unqual!T[] ar; | () @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 | auto ctx = mir_rc_create(mir_get_type_info!T, length, mir_get_payload_ptr!T, initialize, deallocate); | if (!ctx) | { | version(D_Exceptions) | throw allocationError; | else | assert(0, allocationExcMsg); | } | _payload = cast(T*)(ctx + 1); | ar = cast(Unqual!T[])_payload[0 .. length]; | } (); | if (initialize || hasElaborateAssign!(Unqual!T)) | { | import mir.conv: uninitializedFillDefault; | 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)) | this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc | { | if (rhs) | { | this._payload = cast(typeof(this._payload))rhs._payload; | 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._payload = cast(typeof(this._payload))rhs._payload; | 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._payload = cast(typeof(this._payload))rhs._payload; | mir_rc_increase_counter(context); | } | } | | this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc | { | if (rhs) | { | this._payload = rhs._payload; | mir_rc_increase_counter(context); | } | } | | /// | ref opAssign(typeof(null)) return @trusted // pure nothrow @nogc | { | this = typeof(this).init; | } | | /// | ref opAssign(return typeof(this) rhs) return @trusted // pure nothrow @nogc | { | this.proxySwap(rhs); | 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; | } |} | |/// ditto |alias RCArray = mir_rcarray; | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | auto a = RCArray!double(10); | foreach(i, ref e; a) | e = i; | auto b = a; | assert(b[$ - 1] == 9); | foreach(i, ref e; b) | assert(e == i); | b[4] = 100; | assert(a[4] == 100); | | import mir.ndslice.slice; | | 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))); | | auto r = a[]; // scope array | static assert(is(typeof(r) == double[])); | | auto fs = r.sliced; // scope fast random access range (ndslice) | static assert(is(typeof(fs) == Slice!(double*))); |} | |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 | { | return .rcarray!(ForeachType!Range)(range); | } |} | |/// ditto |RCArray!V rcarray(T = void, V)(V[] values...) | if (is(T == void) && hasIndirections!V) |{ | return .rcarray(values, true); |} | |/// ditto |RCArray!V rcarray(T = void, V)(scope V[] values...) | if (is(T == void) && !hasIndirections!V) |{ | return .rcarray(values, true); |} | |/// ditto |RCArray!V rcarray(T = void, V)(V[] values, bool deallocate) | if (is(T == void) && hasIndirections!V) |{ | return .rcarray!V(values, deallocate); |} | |/// ditto |RCArray!V rcarray(T = void, V)(scope V[] values, bool deallocate) | if (is(T == void) && !hasIndirections!V) |{ | return .rcarray!V(values, deallocate); |} | |/// ditto |template rcarray(T) | if(!is(T == E[], E) && !is(T == void)) |{ | import std.range.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) | { | return .rcarray!T(range.field); | } | else static if (hasLength!Range) | { | import mir.conv: emplaceRef; | auto ret = RCArray!T(range.length, false); | size_t i; | static if (isInputRange!Range) | for (; !range.empty; range.popFront) | 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); | return ret; | } | else | { | import mir.appender: ScopedBuffer; | import mir.conv: emplaceRef; | if (false) | { | ScopedBuffer!T a; | static if (isInputRange!Range) | for (; !range.empty; range.popFront) | a.put(range.front); | else | static if (isPointer!Range) | foreach (e; *range) | a.put(e); | else | foreach (e; range) | a.put(e); | scope values = a.data; | auto ret = RCArray!T(values.length, false); | } | return ()@trusted { | ScopedBuffer!T a = void; | a.initialize; | static if (isInputRange!Range) | for (; !range.empty; range.popFront) | a.put(range.front); | else | static if (isPointer!Range) | foreach (e; *range) | a.put(e); | else | foreach (e; range) | a.put(e); | scope values = a.data; | auto ret = RCArray!T(values.length, false); | a.moveDataAndEmplaceTo(ret[]); | 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) | { | return .rcarray!T(values, true); | } | | /// ditto | RCArray!T rcarray(V)(V[] values, bool deallocate) | if (hasIndirections!V) | { | 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; | auto lhs = ret[]; | foreach (i, ref e; values) | lhs[i].emplaceRef!T(e); | } | return ret; | } | | /// ditto | RCArray!T rcarray(V)(scope V[] values, bool deallocate) | if (!hasIndirections!V) | { | auto ret = mir_rcarray!T(values.length, false); | 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; | auto lhs = ret[]; | foreach (i, ref e; values) | lhs[i].emplaceRef!T(e); | } | return ret; | } |} | |/// |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | RCArray!double a = rcarray!double(1.0, 2, 5, 3); | assert(a[0] == 1); | assert(a[$ - 1] == 3); | | auto s = rcarray!char("hello!"); | assert(s[0] == 'h'); | assert(s[$ - 1] == '!'); | | alias rcstring = rcarray!(immutable char); | auto r = rcstring("string"); | assert(r[0] == 's'); | 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]; | auto result = numbers.filter!"a > 3".rcarray!(immutable double); | static assert(is(typeof(result) == RCArray!(immutable double))); | 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) |{ | return RCArray!T(length, false, deallocate); |} | |/// |@safe pure nothrow @nogc unittest |{ | auto a = mininitRcarray!double(5); | assert(a.length == 5); | assert(a._counter == 1); | a[][] = 0; // a.opIndex()[] = 0; |} | |/++ |Thread safe reference counting iterator. |+/ |struct mir_rci(T) |{ | import mir.ndslice.slice: Slice; | import mir.ndslice.iterator: IotaIterator; | | /// | T* _iterator; | | /// | RCArray!T _array; | | /// | this(RCArray!T array) | { | this._iterator = (()@trusted => array.ptr)(); | this._array.proxySwap(array); | } | | /// | this(T* _iterator, RCArray!T array) | { | this._iterator = _iterator; | this._array.proxySwap(array); | } | | /// | inout(T)* lightScope()() scope return inout @property @trusted | { | debug | { | assert(_array._payload <= _iterator); | assert(_iterator is null || _iterator <= _array._payload + _array.length); | } | return _iterator; | } | | /// | ref opAssign(typeof(null)) scope return nothrow | { | pragma(inline, true); | _iterator = null; | _array = null; | return this; | } | | /// | ref opAssign(return typeof(this) rhs) scope return @trusted | { | _iterator = rhs._iterator; | _array.proxySwap(rhs._array); | return this; | } | | /// | ref opAssign(Q)(return mir_rci!Q rhs) scope return nothrow | if (isImplicitlyConvertible!(Q*, T*)) | { | import core.lifetime: move; | _iterator = rhs._iterator; | _array = move(rhs._array); | return this; | } | | /// | mir_rci!(const T) lightConst()() scope return const nothrow @property | { 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 | { | assert(_iterator); | assert(_array._payload); | assert(_array._payload <= _iterator); | assert(_iterator <= _array._payload + _array.length); | } | return *_iterator; | } | | /// | ref inout(T) opIndex(ptrdiff_t index) inout scope return @trusted | { | debug | { | assert(_iterator); | assert(_array._payload); | assert(_array._payload <= _iterator + index); | assert(_iterator + index <= _array._payload + _array.length); | } | 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 | { | assert(i <= j, "RCI!T.opSlice!0: the left opSlice boundary must be less than or equal to the right bound."); | } | do | { | 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; | auto it = this; | it += slice._iterator._index; | return Slice!(RCI!T)(slice.length, it.move); | } | | /// ditto | auto opIndex(Slice!(IotaIterator!size_t) slice) const | { | import core.lifetime: move; | auto it = lightConst; | it += slice._iterator._index; | 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 == "-") | { 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 | { return this._iterator - right._iterator; } | | /// | bool opEquals()(scope ref const typeof(this) right) scope const | { return this._iterator == right._iterator; } | | /// | int opCmp()(scope ref const typeof(this) right) scope const | { 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; | auto slice = mir_rcarray!double(10).asSlice; | static assert(isIterator!(RCI!double)); | static assert(is(typeof(slice) == Slice!(RCI!double))); | auto matrix = slice.sliced(2, 5); | static assert(is(typeof(matrix) == Slice!(RCI!double, 2))); | slice[7] = 44; | assert(matrix[1, 2] == 44); |} | |/// |version(mir_test) |@safe @nogc unittest |{ | import mir.ndslice.slice; | import mir.rc.array; | | alias rcvec = Slice!(RCI!double); | | RCI!double a, b; | a = b; | | RCI!(const double) ca, cb; | ca = cb; | ca = cast(const) cb; | | void foo(scope ref rcvec x, scope ref rcvec y) | { | x[] = y[]; | x[1] = y[1]; | x[1 .. $] += y[1 .. $]; | 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) | { | b = a; | } | | @safe void bari(ref immutable mir_rcarray!(immutable double) a, ref mir_rcarray!(immutable double) b) | { | b = a; | } | | @safe void foo(ref const RCI!(const double) a, ref RCI!(const double) b) | { | b = a; | } | | @safe void fooi(ref immutable RCI!(immutable double) a, ref RCI!(immutable double) b) | { | 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) | { | b = a; | } | | @safe void gooi(ref immutable Series!(RCI!(immutable double), RCI!(const S)) a, ref Series!(RCI!(immutable double), RCI!(const S)) b) | { | b = a; | } | | struct C | { | Series!(RCI!(const S), RCI!(const S)) a; | Series!(RCI!(const S), RCI!(const S)) b; | } | | C a, b; | a = b; | a = cast(const) b; |} | |version(mir_test) |unittest |{ | import mir.ndslice.slice: Slice; | static RCArray!int foo() @safe | { | auto ret = RCArray!int(10); | return ret; | } | | | static Slice!(RCI!int) bat() @safe | { | auto ret = RCArray!int(10); | return ret.asSlice; | } | | static Slice!(RCI!int) bar() @safe | { | auto ret = RCArray!int(10); | auto d = ret.asSlice; | 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; | } | | S[1] d = [S(1)]; | auto r = rcarray(d); |} | |version(mir_test) |unittest |{ | import mir.small_string; | alias S = SmallString!32u; | auto ars = [S("123"), S("422")]; | alias R = mir_rcarray!S; | auto rc = ars.rcarray!S; | | RCArray!int value = null; | value = null; |} | ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/rc/array.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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; | auto p = slice.canonical; | } | else | { | alias p = slice; | } | auto ret = typeof(return)(chunkLengths, p); | foreach (i; Iota!(Dimensions.length)) | ret._norm!i; | 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) |{ | 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 | auto sl = iota(11); | // 0 1 2 | 3 4 5 | 6 7 8 | 9 10 | auto ch = sl.chunks(3); | | static assert(isChunks!(typeof(ch)) == [0]); // isChunks returns dimension indices | | assert(ch.length == 4); | assert(ch.shape == cast(size_t[1])[4]); | | // 0 1 2 | assert(ch.front == iota([3], 0)); | ch.popFront; | | // 3 4 5 | assert(ch.front == iota([3], 3)); | assert(ch.length == 3); | | // 9 10 | assert(ch[$ - 1] == ch.back); | assert(ch.back == iota([2], 9)); | | ch.popBack; | assert(ch.back == iota([3], 6)); | | assert(ch[$ - 1 .. $].length == 1); | assert(ch[$ .. $].length == 0); | 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 | 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. | auto ch = sl.chunks!(1, 0)(4, 3); | | assert(ch.shape == [3, 4]); | assert(ch.slice == sl); | assert(ch.front.slice == sl[0 .. $, 0 .. 4]); | | assert(ch.front.front == sl[0 .. 3, 0 .. 4]); | | assert(ch.front!0[1] == sl[3 .. 6, 0 .. 4]); | assert(ch.front!1[1] == sl[0 .. 3, 4 .. 8]); | | assert (ch[$ - 1, $ - 1] == [[98, 99], [108, 109]]); | | static assert(isChunks!(typeof(ch)) == [1, 0]); // isChunks returns dimension indices | | assert(ch.length == 3); | assert(ch.length!1 == 4); | | ch.popFront; | assert(ch.front.front == sl[0 .. 3, 4 .. 8]); | ch.popFront!1; | assert(ch.front.front == sl[3 .. 6, 4 .. 8]); | | assert(ch.back.slice == sl[3 .. $, 8 .. $]); | ch.popBack; | 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 | 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 | auto ch = sl.chunks!1(4); | | assert(ch.slice == sl); | assert(ch.front == sl[0 .. $, 0 .. 4]); | | 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 | auto sl = iota(11); | // 0 1 2 | 3 4 5 | 6 7 8 | 9 10 | auto ch = sl.chunks(3); | // 3 | 12 | 21 | 19 | auto s = ch.slicedField.map!sum; | assert(s == [3, 12, 21, 19]); |} | |/++ |+/ |struct Chunks(size_t[] dimensions, Iterator, size_t N = 1, SliceKind kind = Contiguous) |{ |@optmath: | | /++ | Chunk shape. | +/ | size_t[dimensions.length] chunkLengths()() @property { return _chunkLengths; } | /// ditto | size_t[dimensions.length] _chunkLengths; | | /// | auto lightConst()() const @property | { | import mir.qualifier; | 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. | +/ | Slice!(Iterator, N, kind) slice()() @property { return _slice; } | /// | Slice!(Iterator, N, kind) _slice; | | private auto _norm(size_t dimensionIndex = 0)() @property | { | assert(_chunkLengths[dimensionIndex]); | enum dimension = dimensions[dimensionIndex]; | if (_expect(_slice._lengths[dimension] < _chunkLengths[dimensionIndex], false) && _slice._lengths[dimension]) | _chunkLengths[dimensionIndex] = _slice._lengths[dimension]; | } | | private auto _wrap(size_t dimensionIndex, S)(ref S ret) | { | static if (dimensions.length == 1) | { | return ret; | } | else | { | size_t[dimensions.length - 1] rcl; | foreach (i, j; AliasSeq!(Iota!dimensionIndex, Iota!(dimensionIndex + 1, dimensions.length))) | rcl[i] = _chunkLengths[j]; | enum newDims = dimensions[0 .. dimensionIndex] ~ dimensions[dimensionIndex + 1 .. $]; | return .Chunks!(newDims, Iterator, N, typeof(ret).kind)(rcl, ret); | } | } | | private ref size_t sliceLength(size_t dimensionIndex)() @property | { | enum dimension = dimensions[dimensionIndex]; | return _slice._lengths[dimension]; | } | | /// ndslice-like primitives | bool empty(size_t dimensionIndex = 0)() const @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; | return _slice.empty!(dimension); | } | | /// | size_t[dimensions.length] shape()() const @property | { | typeof(return) ret; | foreach(dimensionIndex; Iota!(ret.length)) | { | enum dimension = dimensions[dimensionIndex]; | auto l = _slice._lengths[dimension]; | auto n = _chunkLengths[dimensionIndex]; | ret[dimensionIndex] = l / n + (l % n != 0); | } | return ret; | } | | /// ditto | size_t length(size_t dimensionIndex = 0)() const @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; | auto l = _slice._lengths[dimension]; | auto n = _chunkLengths[dimensionIndex]; | return l / n + (l % n != 0); | } | | /// ditto | auto front(size_t dimensionIndex = 0)() @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; | assert(_chunkLengths[dimensionIndex] <= _slice._lengths[dimension]); | auto ret = _slice.selectFront!dimension(_chunkLengths[dimensionIndex]); | return _wrap!dimensionIndex(ret); | } | | /// | auto back(size_t dimensionIndex = 0)() @property | if (dimensionIndex < dimensions.length) | { | assert(!empty!dimensionIndex); | enum dimension = dimensions[dimensionIndex]; | auto l = _slice._lengths[dimension]; | auto n = _chunkLengths[dimensionIndex]; | auto rshift = l % n; | rshift = !rshift ? n : rshift; | auto len = _slice._lengths[dimension]; | auto ret = _slice.select!dimension(len - rshift, len); | return _wrap!dimensionIndex(ret); | } | | /// ditto | void popFront(size_t dimensionIndex = 0)() | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; | assert(!empty!dimensionIndex); | _slice.popFrontExactly!dimension(_chunkLengths[dimensionIndex]); | _norm!dimensionIndex; | } | | /// ditto | void popBack(size_t dimensionIndex = 0)() | if (dimensionIndex < dimensions.length) | { | assert(!empty!dimensionIndex); | enum dimension = dimensions[dimensionIndex]; | auto l = _slice._lengths[dimension]; | auto n = _chunkLengths[dimensionIndex]; | auto rshift = l % n; | rshift = !rshift ? n : rshift; | _slice.popBackExactly!dimension(rshift); | _norm!dimensionIndex; | } | | /// ditto | Slice!(IotaIterator!size_t) opSlice(size_t dimensionIndex)(size_t i, size_t j) const | if (dimensionIndex < dimensions.length) | in | { | 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."; | assert(j <= length!dimensionIndex, | "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); | } | do | { | 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 | { | 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."; | assert(j <= length!dimensionIndex, | "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); | } | do | { | return typeof(return)(i, j); | } | | /// ditto | ChunksDollar!() opDollar(size_t dimensionIndex)() @property | { | enum dimension = dimensions[dimensionIndex]; | return ChunksDollar!()(_slice._lengths[dimension], _chunkLengths[dimensionIndex]); | } | | /// ditto | auto opIndex(Slices...)(Slices slices) | if (Slices.length <= dimensions.length) | { | static if (slices.length == 0) | { | return this; | } | else | { | alias slice = slices[0]; | alias S = Slices[0]; | static if (isIndex!S) | { | auto next = this.select!0(slice); | } | else | static if (is_Slice!S) | { | auto i = slice._iterator._index; | auto j = i + slice._lengths[0]; | auto next = this.select!0(i, j); | } | else | { | auto next = this.select!0(slice.i, slice.j); | } | static if (slices.length > 1) | { | return next[slices[1 .. $]]; | } | else | { | 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 | { | return this; | } | | /// | auto select(size_t dimensionIndex = 0)(size_t index) @property | if (dimensionIndex < dimensions.length) | { | enum dimension = dimensions[dimensionIndex]; | auto chl = _chunkLengths[dimensionIndex]; | auto shiftL = chl * index; | assert(shiftL <= _slice._lengths[dimension]); | auto shiftR = shiftL + chl; | if (_expect(shiftR > _slice._lengths[dimension], false)) | { | shiftR = _slice._lengths[dimension]; | } | auto ret = _slice.select!dimension(shiftL, shiftR); | return _wrap!dimensionIndex(ret); | } | | /// ditto | auto select(size_t dimensionIndex = 0)(size_t i, size_t j) @property | if (dimensionIndex < dimensions.length) | { | assert(i <= j); | enum dimension = dimensions[dimensionIndex]; | auto chl = _chunkLengths[dimensionIndex]; | auto shiftL = chl * i; | auto shiftR = chl * j; | assert(shiftL <= _slice._lengths[dimension]); | assert(shiftR <= _slice._lengths[dimension]); | if (_expect(shiftR > _slice._lengths[dimension], false)) | { | shiftR = _slice._lengths[dimension]; | if (_expect(shiftL > _slice._lengths[dimension], false)) | { | shiftL = _slice._lengths[dimension]; | } | } | auto ret = _slice.select!dimension(shiftL, shiftR); | import std.meta: aliasSeqOf; | 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 | { | 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 |{ | foreach (ref follower; followers) | { | assert(follower.sliceLength!dimmensionIndex == master.sliceLength!dimmensionIndex); | assert(follower._chunkLengths[dimmensionIndex] == master._chunkLengths[dimmensionIndex]); | } |} |do |{ | master._slice.popFrontExactly!(isChunks!Master[dimmensionIndex])(master._chunkLengths[dimmensionIndex]); | foreach (i, ref follower; followers) | { | follower._slice.popFrontExactly!(isChunks!(Followers[i])[dimmensionIndex])(master._chunkLengths[dimmensionIndex]); | // hint for optimizer | follower.sliceLength!dimmensionIndex = master.sliceLength!dimmensionIndex; | } | if (_expect(master.sliceLength!dimmensionIndex < master._chunkLengths[dimmensionIndex], false) && master.sliceLength!dimmensionIndex) | { | master._chunkLengths[dimmensionIndex] = master.sliceLength!dimmensionIndex; | foreach(ref follower; followers) | { | follower._chunkLengths[dimmensionIndex] = master._chunkLengths[dimmensionIndex]; | } | } |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.chunks: chunks; | import mir.ndslice.topology: iota; | | auto a = iota(10, 20).chunks!(0, 1)(3, 7); | auto b = iota(20, 10).chunks!(1, 0)(3, 7); | | auto as = a; | auto bs = b; | | as.popFront; | bs.popFront; | | popFrontTuple(a, b); | | assert(as.slice == a.slice); | assert(bs.slice == b.slice); | | assert(as.chunkLengths == a.chunkLengths); | assert(bs.chunkLengths == b.chunkLengths); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/chunks.d has no code <<<<<< EOF # path=./source-mir-sparse-blas-gemm.lst |/++ |License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |Copyright: Copyright © 2016-, Ilya Yaroshenko |Authors: Ilya Yaroshenko |+/ |module mir.sparse.blas.gemm; | |import std.traits; |import mir.ndslice.slice; |import mir.ndslice.iterator; |import mir.ndslice.allocation: slice; |import mir.sparse; |import mir.series; | |/++ |General matrix-matrix multiplication. | |Params: | alpha = scalar | a = sparse matrix (CSR format) | b = dense matrix | beta = scalar | c = dense matrix |Returns: | `c = alpha * a × b + beta * c` if beta does not equal null and `c = alpha * a × b` otherwise. |+/ |void gemm( | CR, | CL, | SliceKind kind1, T1, I1, J1, SliceKind kind2, Iterator2, SliceKind kind3, Iterator3) |( | in CR alpha, | Slice!(ChopIterator!(J1*, Series!(I1*, T1*)), 1, kind1) a, | Slice!(Iterator2, 2, kind2) b, | in CL beta, | Slice!(Iterator3, 2, kind3) c) |in |{ 1| assert(a.length!0 == c.length!0); 1| assert(b.length!1 == c.length!1); |} |body |{ | import mir.ndslice.topology: universal; | import mir.ndslice.dynamic: transposed; 1| auto ct = c.universal.transposed; 14| foreach (x; b.universal.transposed) | { | import mir.sparse.blas.gemv: gemv; 4| gemv(alpha, a, x, beta, ct.front); 4| ct.popFront; | } |} | |/// |unittest |{ | import mir.ndslice; | import mir.sparse; | 1| auto sp = sparse!int(3, 5); 1| sp[] = | [[-5, 1, 7, 7, -4], | [-1, -5, 6, 3, -3], | [-5, -2, -3, 6, 0]]; | 1| auto a = sp.compress; | 1| auto b = slice!double(5, 4); 1| b[] = | [[-5.0, -3, 3, 1], | [4.0, 3, 6, 4], | [-4.0, -2, -2, 2], | [-1.0, 9, 4, 8], | [9.0, 8, 3, -2]]; | 1| auto c = slice!double(3, 4); | 1| gemm(1.0, a, b, 0, c); | 1| assert(c == | [[-42.0, 35, -7, 77], | [-69.0, -21, -42, 21], | [23.0, 69, 3, 29]]); |} | | |/++ |General matrix-matrix multiplication with transformation. | |Params: | alpha = scalar | a = sparse matrix (CSR format) | b = dense matrix | beta = scalar | c = dense matrix |Returns: | `c = alpha * aáµ€ × b + beta * c` if beta does not equal null and `c = alpha * aáµ€ × b` otherwise. |+/ |void gemtm( | CR, | CL, | SliceKind kind1, T1, I1, J1, SliceKind kind2, Iterator2, SliceKind kind3, Iterator3) |( | in CR alpha, | Slice!(ChopIterator!(J1*, Series!(I1*, T1*)), 1, kind1) a, | Slice!(Iterator2, 2, kind2) b, | in CL beta, | Slice!(Iterator3, 2, kind3) c) |in |{ 1| assert(a.length!0 == b.length!0); 1| assert(b.length!1 == c.length!1); |} |body |{ | import mir.ndslice.topology: universal; | import mir.ndslice.dynamic: transposed; 1| auto ct = c.universal.transposed; 14| foreach (x; b.universal.transposed) | { | import mir.sparse.blas.gemv: gemtv; 4| gemtv(alpha, a, x, beta, ct.front); 4| ct.popFront; | } |} | | |/// |unittest |{ | import mir.ndslice; | import mir.sparse; | 1| auto sp = sparse!int(5, 3); 1| sp[] = | [[-5, -1, -5], | [1, -5, -2], | [7, 6, -3], | [7, 3, 6], | [-4, -3, 0]]; | 1| auto a = sp.compress; | 1| auto b = slice!double(5, 4); 1| b[] = | [[-5.0, -3, 3, 1], | [4.0, 3, 6, 4], | [-4.0, -2, -2, 2], | [-1.0, 9, 4, 8], | [9.0, 8, 3, -2]]; | 1| auto c = slice!double(3, 4); | 1| gemtm(1.0, a, b, 0, c); | 1| assert(c == | [[-42.0, 35, -7, 77], | [-69.0, -21, -42, 21], | [23.0, 69, 3, 29]]); |} | |/++ |Selective general matrix multiplication with selector sparse matrix. |Params: | a = dense matrix | b = dense matrix | c = sparse matrix (CSR format) |Returns: | `c[available indexes] = (a × b)[available indexes]`. |+/ |void selectiveGemm(string op = "", SliceKind kind1, SliceKind kind2, SliceKind kind3, T, T3, I3, J3) |(Slice!(T*, 2, kind1) a, Slice!(T*, 2, kind2) b, Slice!(ChopIterator!(J3*, Series!(I3*, T3*)), 1, kind3) c) |in |{ 1| assert(a.length!1 == b.length!0); 1| assert(c.length!0 == a.length!0); 11| foreach (r; c) 3| if (r.index.length) 2| assert(r.index[$-1] < b.length!1); |} |body |{ | import mir.ndslice.topology: universal; | import mir.ndslice.dynamic: transposed; | import mir.sparse.blas.gemv: selectiveGemv; | 1| auto bt = b.universal.transposed; 11| foreach (r; c) | { 3| selectiveGemv!op(bt, a.front, r); 3| a.popFront; | } |} | |/// |unittest |{ | import mir.ndslice; | import mir.sparse; | 1| auto a = slice!double(3, 5); 1| a[] = | [[-5, 1, 7, 7, -4], | [-1, -5, 6, 3, -3], | [-5, -2, -3, 6, 0]]; | 1| auto b = slice!double(5, 4); 1| b[] = | [[-5.0, -3, 3, 1], | [4.0, 3, 6, 4], | [-4.0, -2, -2, 2], | [-1.0, 9, 4, 8], | [9.0, 8, 3, -2]]; | | // a * b == | // [[-42.0, 35, -7, 77], | // [-69.0, -21, -42, 21], | // [23.0, 69, 3, 29]]); | 1| auto cs = sparse!double(3, 4); 1| cs[0, 2] = 1; 1| cs[0, 1] = 3; 1| cs[2, 3] = 2; | 1| auto c = cs.compress; | 1| selectiveGemm!"*"(a, b, c); 1| assert(c.length == 3); 1| assert(c[0].index == [1, 2]); 1| assert(c[0].value == [105, -7]); 1| assert(c[1].empty); 1| assert(c[2].index == [3]); 1| assert(c[2].value == [58]); |} source/mir/sparse/blas/gemm.d is 100% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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) | { | 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; | auto r = s.repeat(1).unpack; | static if (dim) | { | import mir.ndslice.dynamic: transposed; | return r.transposed!(Iota!(1, dim + 1)); | } | else | { | 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; | return Concatenation!(dim, Slices)(forward!slices); | } | else | { | import core.lifetime: move; | static assert(minN + 1 == maxN); | alias S = staticMap!(_Expose!(maxN, dim), Slices); | Concatenation!(dim, S) ret; | foreach (i, ref e; ret._slices) | e = _expose!(maxN, dim)(move(slices[i])); | return ret; | } | } | else | { | import core.lifetime: forward; | 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 | auto vector = size_t.init.repeat([3]); | | // 1 2 3 | // 4 5 6 | auto matrix = iota([2, 3], 1); | | assert(concatenation(vector, matrix).slice == [ | [0, 0, 0], | [1, 2, 3], | [4, 5, 6], | ]); | | vector.popFront; | 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 | auto a = iota(2, 3); | // 0, 1 | // 2, 3 | auto b = iota(2, 2); | // 0, 1, 2, 3, 4 | auto c = iota(1, 5); | | // 0, 1, 2, 0, 1 | // 3, 4, 5, 2, 3 | // | // 0, 1, 2, 3, 4 | // construction phase | auto s = concatenation(concatenation!1(a, b), c); | | // allocation phase | auto d = s.slice; | assert(d == [ | [0, 1, 2, 0, 1], | [3, 4, 5, 2, 3], | [0, 1, 2, 3, 4], | ]); | | // optimal fragmentation for output/writing/buffering | auto testData = [ | [0, 1, 2], [0, 1], | [3, 4, 5], [2, 3], | [0, 1, 2, 3, 4], | ]; | size_t i; | s.forEachFragment!((fragment) { | pragma(inline, false); //reduces template bloat | assert(fragment == testData[i++]); | }); | assert(i == testData.length); | | // lazy ndslice view | assert(s.slicedNdField == d); |} | |/// 1D |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | import mir.ndslice.slice : slicedNdField; | | size_t i; | auto a = 3.iota; | auto b = iota([6], a.length); | auto s = concatenation(a, b); | assert(s.length == a.length + b.length); | // fast iteration with until | s.until!((elem){ assert(elem == i++); return false; }); | // allocation with slice | assert(s.slice == s.length.iota); | // 1D or multidimensional assignment | auto d = slice!double(s.length); | d[] = s; | assert(d == s.length.iota); | d.opIndexOpAssign!"+"(s); | assert(d == iota([s.length], 0, 2)); | | // lazy ndslice view | 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; | 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) | { | size_t length; | foreach(ref slice; _slices) | length += slice.length!d; | return length; | } | else | { | 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 | { | typeof(return) ret; | foreach(i; Iota!N) | ret[i] = length!i; | return ret; | } | | /// Multidimensional input range primitives | bool empty(size_t d = 0)() const @property | { | static if (d == dim) | { | foreach(ref slice; _slices) | if (!slice.empty!d) | return false; | return true; | } | else | { | return _slices[0].empty!d; | } | } | | /// ditto | void popFront(size_t d = 0)() | { | static if (d == dim) | { | foreach(i, ref slice; _slices) | { | static if (i != Slices.length - 1) | if (slice.empty!d) | continue; | return slice.popFront!d; | } | } | else | { | foreach_reverse (ref slice; _slices) | slice.popFront!d; | } | } | | /// ditto | auto front(size_t d = 0)() | { | static if (d == dim) | { | foreach(i, ref slice; _slices) | { | static if (i != Slices.length - 1) | if (slice.empty!d) | continue; | return slice.front!d; | } | } | else | { | import mir.ndslice.internal: frontOfDim; | enum elemDim = d < dim ? dim - 1 : dim; | return concatenation!elemDim(frontOfDim!(d, _slices)); | } | } | | /// Simplest multidimensional random access primitive | auto opIndex()(size_t[N] indices...) | { | foreach(i, ref slice; _slices[0 .. $-1]) | { | ptrdiff_t diff = indices[dim] - slice.length!dim; | if (diff < 0) | return slice[indices]; | indices[dim] = diff; | } | assert(indices[dim] < _slices[$-1].length!dim); | 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; | auto slices = st._slices; | 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) |{ | return .pad!([Iota!N], [Repeat!(N, direction)])(s, value, lengths); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([3], 1) | .pad(0, [2]) | .slice; | | assert(pad == [0, 0, 1, 2, 3, 0, 0]); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([2, 2], 1) | .pad(0, [2, 1]) | .slice; | | 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; | | size_t[N] len; | auto _len = s.shape; | foreach(i; Iota!(len.length)) | static if (i != d) | len[i] = _len[i]; | else | len[i] = lengths[$ - 1]; | | auto p = repeat(value, len); | static if (q == "both") | auto r = concatenation!d(p, s, p); | else | static if (q == "pre") | auto r = concatenation!d(p, s); | else | static if (q == "post") | auto r = concatenation!d(s, p); | else | static assert(0, `allowed directions are "both", "pre", and "post"`); | | static if (dimensions.length == 1) | return r; | else | 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; | | auto pad = iota([2, 2], 1) | .pad!([1], ["pre"])(0, [2]) | .slice; | | assert(pad == [ | [0, 0, 1, 2], | [0, 0, 3, 4]]); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([2, 2], 1) | .pad!([0, 1], ["both", "post"])(0, [2, 1]) | .slice; | | 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...) |{ | return .padWrap!([Iota!N], [Repeat!(N, direction)])(s, lengths); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([3], 1) | .padWrap([2]) | .slice; | | assert(pad == [2, 3, 1, 2, 3, 1, 2]); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([2, 2], 1) | .padWrap([2, 1]) | .slice; | | 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; | auto _s = s.canonical; | } | | 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") | { | auto _pre = _s; | _pre.popFrontExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias pre = _pre; | else | auto pre = next(_pre, lengths[0 .. $ - 1]); | } | | static if (q == "post" || q == "both") | { | auto _post = _s; | _post.popBackExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias post = _post; | else | auto post = next(_post, lengths[0 .. $ - 1]); | } | | static if (dimensions.length == 1) | alias r = s; | else | auto r = next(s, lengths[0 .. $ - 1]); | | static if (q == "both") | return concatenation!d(pre, r, post); | else | static if (q == "pre") | return concatenation!d(pre, r); | else | static if (q == "post") | 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; | | auto pad = iota([2, 3], 1) | .padWrap!([1], ["pre"])([1]) | .slice; | | assert(pad == [ | [3, 1, 2, 3], | [6, 4, 5, 6]]); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([2, 2], 1) | .padWrap!([0, 1], ["both", "post"])([2, 1]) | .slice; | | 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...) |{ | return .padSymmetric!([Iota!N], [Repeat!(N, direction)])(s, lengths); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([3], 1) | .padSymmetric([2]) | .slice; | | assert(pad == [2, 1, 1, 2, 3, 3, 2]); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([2, 2], 1) | .padSymmetric([2, 1]) | .slice; | | 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; | auto __s = s.canonical; | } | else | { | alias __s = s; | } | | static if (kind == Universal || d != N - 1) | { | auto _s = __s.reversed!d; | } | else | static if (N == 1) | { | import mir.ndslice.topology: retro; | auto _s = s.retro; | } | else | { | import mir.ndslice.topology: retro; | auto _s = __s.retro.reversed!(Iota!d, Iota!(d + 1, N)); | } | | 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") | { | auto _pre = _s; | _pre.popFrontExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias pre = _pre; | else | auto pre = next(_pre, lengths[0 .. $ - 1]); | } | | static if (q == "post" || q == "both") | { | auto _post = _s; | _post.popBackExactly!d(s.length!d - lengths[$ - 1]); | static if (dimensions.length == 1) | alias post = _post; | else | auto post = next(_post, lengths[0 .. $ - 1]); | } | | static if (dimensions.length == 1) | alias r = s; | else | auto r = next(s, lengths[0 .. $ - 1]); | | static if (q == "both") | return concatenation!d(pre, r, post); | else | static if (q == "pre") | return concatenation!d(pre, r); | else | static if (q == "post") | 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; | | auto pad = iota([2, 3], 1) | .padSymmetric!([1], ["pre"])([2]) | .slice; | | 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; | | auto pad = iota([2, 2], 1) | .padSymmetric!([0, 1], ["both", "post"])([2, 1]) | .slice; | | 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...) |{ | return .padEdge!([Iota!N], [Repeat!(N, direction)])(s, lengths); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([3], 1) | .padEdge([2]) | .slice; | | assert(pad == [1, 1, 1, 2, 3, 3, 3]); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: iota; | | auto pad = iota([2, 2], 1) | .padEdge([2, 1]) | .slice; | | 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; | auto _s = s.canonical; | } | else | { | import mir.ndslice.topology: universal; | auto _s = s.universal; | } | | static if (dimensions.length != 1) | alias next = .padEdge!(dimensions[0 .. $ - 1], directions[0 .. $ - 1]); | | static if (q == "pre" || q == "both") | { | auto _pre = _s; | _pre._strides[d] = 0; | _pre._lengths[d] = lengths[$ - 1]; | static if (dimensions.length == 1) | alias pre = _pre; | else | auto pre = next(_pre, lengths[0 .. $ - 1]); | | } | | static if (q == "post" || q == "both") | { | auto _post = _s; | _post._iterator += _post.backIndex!d; | _post._strides[d] = 0; | _post._lengths[d] = lengths[$ - 1]; | static if (dimensions.length == 1) | alias post = _post; | else | auto post = next(_post, lengths[0 .. $ - 1]); | } | | static if (dimensions.length == 1) | alias r = s; | else | auto r = next( s, lengths[0 .. $ - 1]); | | static if (q == "both") | return concatenation!d(pre, r, post); | else | static if (q == "pre") | return concatenation!d(pre, r); | else | static if (q == "post") | 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; | | auto pad = iota([2, 3], 1) | .padEdge!([0], ["pre"])([2]) | .slice; | | 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; | | auto pad = iota([2, 2], 1) | .padEdge!([0, 1], ["both", "post"])([2, 1]) | .slice; | | 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) | { | pred(sl); | } | else | static if (kind == Contiguous) | { | import mir.ndslice.topology: flattened; | 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) | { | foreach (i, ref slice; st._slices) | .forEachFragment!pred(slice); | } | else | { | if (!st.empty) do | { | st.applyFront!(0, .forEachFragment!pred); | st.popFront; | } | 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; | if (!sl.empty) do | { | if (f(sl.front)) | return true; | sl.popFront; | } | while(!sl.empty); | return false; | } | | /++ | Specialization for concatenations | Params: | st = $(LREF Concatenation) | +/ | bool until(size_t dim, Slices...)(Concatenation!(dim, Slices) st) | { | static if (dim == 0) | { | foreach (i, ref slice; st._slices) | { | if (.until!pred(slice)) | return true; | } | } | else | { | if (!st.empty) do | { | if (st.applyFront!(0, .until!pred)) | return true; | st.popFront; | } | while(!st.empty); | } | return false; | } | } | else | alias until = .until!(naryFun!pred); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/concatenation.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-core-1.1.69-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 = 5; | assert(v == 5); | assert(v.kind == JsonValue.Kind.integer); | | 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 std.meta: AliasSeq, anySatisfy, allSatisfy, staticMap, templateOr; | import std.traits: | hasElaborateAssign, | hasElaborateCopyConstructor, | hasElaborateDestructor, | 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 | { | 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 | { | 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: | 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) | static if (allSatisfy!(isSimpleAggregateType, _ReflectionTypes)) | { | import mir.reflection: isPublic, hasField, isProperty; | import std.meta: ApplyRight, Filter, templateNot, templateOr; | import std.traits: hasMember; | | 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 = | (is(Q == class) && __traits(compiles, new Q(forward!args))) || | ((is(Q == struct) || is(Q == union)) && __traits(compiles, 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 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; }}); | } | } | } | | static foreach (int i, T; AllowedTypes) | { | /// Zero cost always nothrow `get` alternative | auto ref trustedGet(E)() @trusted @property return inout nothrow | if (is(E == T)) | { | assert (i == _identifier_); | static if (is(T == typeof(null))) | return null; | else | static if (is(T == void)) | return; | else | return _storage_.payload[i]; | } | | /++ | Throws: Exception if the storage contains value of other type | +/ | auto ref get(E)() @property return inout | if (is(E == T)) | { | import mir.utility: _expect; | static if (AllowedTypes.length > 1) | { | if (_expect(i != _identifier_, false)) | { | throw variantException; | } | } | return trustedGet!T; | } | | /++ | Checks if the storage stores an allowed type. | +/ | bool _is(E)() const @property nothrow @nogc | if (is(E == T)) | { | return _identifier_ == i; | } | | static if (is(T == void)) | { | /// Defined if `AllowedTypes` contains `void` | static Algebraic _void() | { | Algebraic ret; | ret._storage_ = () { | import core.lifetime: forward; | mixin(`_Storage_ ret = { _member_` ~ i.stringof ~ ` : _Void!().init };`); | return ret; | } (); | ret._identifier_ = i; | return ret; | } | } | else | { | /// | static if (isCopyable!(const T) || is(Unqual!T == T)) | this(T value) | { | import core.lifetime: forward; | static if (is(T == typeof(null))) | auto rhs = _Null!()(); | else | alias rhs = forward!value; | | static if (__VERSION__ < 2094 && anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) | { | _storage_.bytes = () @trusted { | auto ret = _StorageI!i(rhs); | return ret.bytes; | } (); | } | else | { | _storage_ = () { | mixin(`_Storage_ ret = { _member_` ~ i.stringof ~ ` : rhs };`); | return ret; | } (); | } | static if (_Payload.length > 1) | _identifier_ = i; | } | | /// ditto | static if (isCopyable!(const T)) | this(const T value) const | { | static if (is(T == typeof(null))) | auto rhs = _Null!()(); | else | alias rhs = value; | static if (__VERSION__ < 2094 && anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) | { | _storage_.bytes = () const @trusted { | auto ret = const _StorageI!i(rhs); | return ret.bytes; | } (); | } | else | { | _storage_ = () { | mixin(`const _Storage_ ret = { _member_` ~ i.stringof ~ ` : rhs };`); | return ret; | } (); | } | static if (_Payload.length > 1) | _identifier_ = i; | } | | /// ditto | static if (isCopyable!(immutable T)) | this(immutable T value) immutable | { | static if (is(T == typeof(null))) | auto rhs = _Null!()(); | else | alias rhs = value; | static if (__VERSION__ < 2094 && anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) | { | _storage_.bytes = () const @trusted { | auto ret = immutable _StorageI!i(rhs); | return ret.bytes; | } (); | } | else | { | _storage_ = () { | mixin(`immutable _Storage_ ret = { _member_` ~ i.stringof ~ ` : rhs };`); | return ret; | } (); | } | static if (_Payload.length > 1) | _identifier_ = i; | } | | static if (__traits(compiles, (ref T a, ref T b) { moveEmplace(a, b); })) | /// | ref opAssign(T rhs) return @trusted | { | static if (anySatisfy!(hasElaborateDestructor, AllowedTypes)) | this.__dtor(); | __ctor(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; | } | } | } |} | |/++ |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.69/mir-core/source/mir/algebraic.d is 0% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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) |{ | 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 (!isStaticArray!Iterator && N | && !is(Iterator : Slice!(_Iterator, _N, kind), _Iterator, size_t _N, SliceKind kind)) |{ | alias C = ImplicitlyUnqual!(typeof(iterator)); 0000000| size_t[N] _lengths; | foreach (i; Iota!N) 0000000| _lengths[i] = lengths[i]; 0000000| ptrdiff_t[1] _strides = 0; | static if (isDynamicArray!Iterator) | { | assert(lengthsProduct(_lengths) <= iterator.length, | "array length should be greater or equal to the product of constructed ndslice lengths"); | auto ptr = iterator.length ? &iterator[0] : null; | return Slice!(typeof(C.init[0])*, N)(_lengths, ptr); | } | else | { | // break safety 0000000| if (false) | { 0000000| ++iterator; 0000000| --iterator; 0000000| iterator += 34; 0000000| iterator -= 34; | } | import core.lifetime: move; 0000000| 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 | { | return index; | } | | 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); | | auto slice = Iterator().sliced(20, 10); | assert(slice[1, 2] == 12); | auto sCopy = slice.save; | 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); 0000000| return Slice!(T*)([array.length], array.ptr); |} | |/// Creates a slice from an array. |@safe pure nothrow version(mir_test) unittest |{ | auto slice = new int[10].sliced; | 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) |{ | auto structure = typeof(return)._Structure.init; | structure[0] = lengths; | static if (kind != Contiguous) | { | import mir.ndslice.topology: iota; | structure[1] = structure[0].iota.strides; | } | import core.lifetime: move; | return typeof(return)(structure, slice._iterator.move); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | auto data = new int[24]; | foreach (i, ref e; data) | e = cast(int)i; | auto a = data[0..10].sliced(10)[0..6].sliced(2, 3); | auto b = iota!int(10)[0..6].sliced(2, 3); | assert(a == b); | a[] += b; | foreach (i, e; data[0..6]) | assert(e == 2*i); | foreach (i, e; data[6..$]) | 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) 0000000| assert(lengths.lengthsProduct <= field.length, "Length product should be less or equal to the field length."); 0000000| return FieldIterator!Field(0, field).sliced(lengths); |} | |///ditto |auto slicedField(Field)(Field field) | if(hasLength!Field) |{ 0000000| 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; | auto slice = 10.iota.slicedField; | 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) | { | auto shape = field.shape; | foreach (i; 0 .. N) | assert(lengths[i] <= shape[i], "Lengths should fit into ndfield's shape."); | } | import mir.ndslice.topology: indexed, ndiota; | return indexed(field, ndiota(lengths)); |} | |///ditto |auto slicedNdField(ndField)(ndField field) | if(hasShape!ndField) |{ | 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 |------- |+/ |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) | { 0000000| 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; 0000000| assert(_indices[E] < _lengths[E], indexError!(E, N)); 0000000| ptrdiff_t ball = this._stride!E; 0000000| ptrdiff_t stride = _indices[E] * ball; | foreach_reverse (i; Iota!E) //static | { | ball *= _lengths[i + 1]; | assert(_indices[i] < _lengths[i], indexError!(i, N)); | stride += ball * _indices[i]; | } | } | else | static if (kind == Canonical) | { | enum E = I - 1; | assert(_indices[E] < _lengths[E], indexError!(E, N)); | static if (I == N) | size_t stride = _indices[E]; | else | size_t stride = _strides[E] * _indices[E]; | foreach_reverse (i; Iota!E) //static | { | assert(_indices[i] < _lengths[i], indexError!(i, N)); | stride += _strides[i] * _indices[i]; | } | } | else | { | enum E = I - 1; | assert(_indices[E] < _lengths[E], indexError!(E, N)); | size_t stride = _strides[E] * _indices[E]; | foreach_reverse (i; Iota!E) //static | { | assert(_indices[i] < _lengths[i], indexError!(i, N)); | stride += _strides[i] * _indices[i]; | } | } 0000000| 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 | { | uint[8] array = [1, 2, 3, 4, 5, 6, 7, 8]; | auto slice = Slice!(uint*, 2, Universal)([2, 2], [4, 1], array.ptr); | | assert(&slice[0, 0] == &array[0]); | assert(&slice[0, 1] == &array[1]); | assert(&slice[1, 0] == &array[4]); | assert(&slice[1, 1] == &array[5]); | assert(slice == [[1, 2], [5, 6]]); | | array[2] = 42; | assert(slice == [[1, 2], [5, 6]]); | | array[1] = 99; | 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 | { 0000000| auto ret = Slice!(LightScopeOf!Iterator, N, kind, staticMap!(LightScopeOf, Labels)) | (_structure, .lightScope(_iterator)); | foreach(i; Iota!L) | ret._labels[i] = .lightScope(_labels[i]); 0000000| return ret; | } | | /// ditto | auto lightScope()() scope const return @property | { 0000000| auto ret = Slice!(LightConstOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightConstOfLightScopeOf, Labels)) | (_structure, .lightScope(_iterator)); | foreach(i; Iota!L) | ret._labels[i] = .lightScope(_labels[i]); 0000000| 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 | { | auto ret = typeof(return)(_structure, .lightImmutable(_iterator)); | foreach(i; Iota!L) | ret._labels[i] = .lightImmutable(_labels[i]); | return ret; | } | | /// Returns: Mutable slice over const data. | Slice!(LightConstOf!Iterator, N, kind, staticMap!(LightConstOf, Labels)) lightConst()() scope return const @property @trusted | { | auto ret = typeof(return)(_structure, .lightConst(_iterator)); | foreach(i; Iota!L) | ret._labels[i] = .lightConst(_labels[i]); | 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) | { | 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; | assert(rhs.length == _lengths[d], "ndslice: labels dimension mismatch"); | _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 | { | 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 | { | 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) | { | return lightConst.opIndex(indices); | } | /// `opIndex` overload for immutable slice | auto ref opIndex(Indexes...)(Indexes indices) immutable @trusted | if (isPureSlice!Indexes || isIndexedSlice!Indexes) | { | 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 | { | 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); 0000000| 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 | { | return bar[i]; | } | | int get(size_t i) const | { | return bar[i]; | } | | int get(size_t i) inout | { | return bar[i]; | } | } | } | | static if (doUnittest) | /// | version(mir_test) unittest | { | Slice!(double*, 2, Universal) nn; | Slice!(immutable(double)*, 2, Universal) ni; | Slice!(const(double)*, 2, Universal) nc; | | const Slice!(double*, 2, Universal) cn; | const Slice!(immutable(double)*, 2, Universal) ci; | const Slice!(const(double)*, 2, Universal) cc; | | immutable Slice!(double*, 2, Universal) in_; | immutable Slice!(immutable(double)*, 2, Universal) ii; | immutable Slice!(const(double)*, 2, Universal) ic; | | nc = nc; nc = cn; nc = in_; | nc = nc; nc = cc; nc = ic; | nc = ni; nc = ci; nc = ii; | | void fun(T, size_t N)(Slice!(const(T)*, N, Universal) sl) | { | //... | } | | fun(nn); fun(cn); fun(in_); | fun(nc); fun(cc); fun(ic); | 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))); | | ni = ci[]; | ni = in_[]; | ni = ii[]; | ni = ic[]; | } | } | | /++ | Iterator | Returns: | Iterator (pointer) to the $(LREF .Slice.first) element. | +/ | auto iterator()() inout scope return @property | { | 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 | { | 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]))) | { | return _iterator[size_t(0) .. elementCount]; | } | else | { | import mir.ndslice.topology: flattened; | return this.flattened; | } | } | | /// ditto | auto field()() scope const return @trusted @property | { | return this.lightConst.field; | } | | /// ditto | auto field()() scope immutable return @trusted @property | { | return this.lightImmutable.field; | } | | static if (doUnittest) | /// | @safe version(mir_test) unittest | { | auto arr = [1, 2, 3, 4]; | auto sl0 = arr.sliced; | auto sl1 = arr.slicedField; | | assert(sl0.field is arr); | assert(sl1.field is arr); | | arr = arr[1 .. $]; | sl0 = sl0[1 .. $]; | sl1 = sl1[1 .. $]; | | assert(sl0.field is arr); | assert(sl1.field is arr); | assert((cast(const)sl1).field is arr); | ()@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 | { 0000000| return _lengths[0 .. N]; | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | 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; | size_t[3] s = [3, 4, 5]; | 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) | return _strides[0 .. N]; | else | { 0000000| typeof(return) ret; | static if (kind == Canonical) | { | foreach (i; Iota!S) | ret[i] = _strides[i]; | ret[$-1] = 1; | } | else | { 0000000| ret[$ - 1] = _stride!(N - 1); | foreach_reverse (i; Iota!(N - 1)) 0000000| ret[i] = ret[i + 1] * _lengths[i + 1]; | } 0000000| return ret; | } | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow | version(mir_test) unittest | { | import mir.ndslice.topology : iota; | size_t[3] s = [20, 5, 1]; | 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; | 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; | size_t[3] s = [20 * 42, 5 * 42, 1 * 42]; | 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 | { | return typeof(return)(_lengths, strides); | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | 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; | 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; | 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 | { | return this; | } | | static if (doUnittest) | /// Save range | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | 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*)` | 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) | { 0000000| return _lengths[dimension]; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | auto slice = iota(3, 4, 5); | assert(slice.length == 3); | assert(slice.length!0 == 3); | assert(slice.length!1 == 4); | 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) | { | return _strides[dimension]; | } | else | static if (dimension + 1 == N) | { 0000000| return 1; | } | else | { | size_t ball = _lengths[$ - 1]; | foreach_reverse(i; Iota!(dimension + 1, N - 1)) | ball *= _lengths[i]; | return ball; | } | | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | auto slice = iota(3, 4, 5); | assert(slice._stride == 20); | assert(slice._stride!0 == 20); | assert(slice._stride!1 == 5); | 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; | 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) | { 0000000| return _lengths[dimension] == 0; | } | | static if (N == 1) | { | ///ditto | auto ref front(size_t dimension = 0)() scope return @trusted @property | if (dimension == 0) | { 0000000| assert(!empty!dimension); 0000000| return *_iterator; | } | | ///ditto | auto ref front(size_t dimension = 0)() scope return @trusted @property const | if (dimension == 0) | { | assert(!empty!dimension); | 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) | { | typeof(return)._Structure structure_ = typeof(return)._Structure.init; | | foreach (i; Iota!(typeof(return).N)) | { | enum j = i >= dimension ? i + 1 : i; | structure_[0][i] = _lengths[j]; | } | | static if (!typeof(return).S || typeof(return).S + 1 == S) | alias s = _strides; | else | auto s = strides; | | foreach (i; Iota!(typeof(return).S)) | { | enum j = i >= dimension ? i + 1 : i; | structure_[1][i] = s[j]; | } | | 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 | { | return t = v; | } | assert(!empty!dimension); | static if (__traits(compiles, *_iterator = value)) | return *_iterator = value; | else | 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) | { 0000000| assert(!empty!dimension); 0000000| return _iterator[backIndex]; | } | else | auto ref Element!dimension | back(size_t dimension = 0)() scope return @trusted @property | if (dimension < N) | { | assert(!empty!dimension); | auto structure_ = typeof(return)._Structure.init; | | foreach (i; Iota!(typeof(return).N)) | { | enum j = i >= dimension ? i + 1 : i; | structure_[0][i] = _lengths[j]; | } | | static if (!typeof(return).S || typeof(return).S + 1 == S) | alias s =_strides; | else | auto s = strides; | | foreach (i; Iota!(typeof(return).S)) | { | enum j = i >= dimension ? i + 1 : i; | structure_[1][i] = s[j]; | } | | 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)) | { 0000000| assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0."); 0000000| _lengths[dimension]--; | static if ((kind == Contiguous || kind == Canonical) && dimension + 1 == N) 0000000| ++_iterator; | else | static if (kind == Canonical || kind == Universal) | _iterator += _strides[dimension]; | else | _iterator += _stride!dimension; | } | | ///ditto | void popBack(size_t dimension = 0)() @safe scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { 0000000| assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0."); 0000000| --_lengths[dimension]; | } | | ///ditto | void popFrontExactly(size_t dimension = 0)(size_t n) @trusted scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { | assert(n <= _lengths[dimension], | __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof); | _lengths[dimension] -= n; | _iterator += _stride!dimension * n; | } | | ///ditto | void popBackExactly(size_t dimension = 0)(size_t n) @safe scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { | assert(n <= _lengths[dimension], | __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof); | _lengths[dimension] -= n; | } | | ///ditto | void popFrontN(size_t dimension = 0)(size_t n) @trusted scope | if (dimension < N && (dimension == 0 || kind != Contiguous)) | { | 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; | auto slice = iota(10, 20, 30).canonical; | | static assert(isRandomAccessRange!(typeof(slice))); | static assert(hasSlicing!(typeof(slice))); | static assert(hasLength!(typeof(slice))); | | assert(slice.shape == cast(size_t[3])[10, 20, 30]); | slice.popFront; | slice.popFront!1; | slice.popBackExactly!2(4); | assert(slice.shape == cast(size_t[3])[9, 19, 26]); | | auto matrix = slice.front!1; | assert(matrix.shape == cast(size_t[2])[9, 26]); | | auto column = matrix.back!1; | assert(column.shape == cast(size_t[1])[9]); | | slice.popFrontExactly!1(slice.length!1); | assert(slice.empty == false); | assert(slice.empty!1 == true); | assert(slice.empty!2 == false); | assert(slice.shape == cast(size_t[3])[9, 0, 26]); | | assert(slice.back.front!1.empty); | | slice.popFrontN!0(40); | slice.popFrontN!2(40); | assert(slice.shape == cast(size_t[3])[0, 0, 0]); | } | | package(mir) ptrdiff_t lastIndex()() @safe @property scope const | { | static if (kind == Contiguous) | { 0000000| return elementCount - 1; | } | else | { | auto strides = strides; | ptrdiff_t shift = 0; | foreach(i; Iota!N) | shift += strides[i] * (_lengths[i] - 1); | return shift; | } | } | | static if (N > 1) | { | /// Accesses the first deep element of the slice. | auto ref first()() scope return @trusted @property | { | assert(!anyEmpty); | 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; | auto f = 5; | assert([2, 3].iota(f).first == f); | } | | /// Accesses the last deep element of the slice. | auto ref last()() @trusted scope return @property | { | assert(!anyEmpty); | 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; | auto f = 5; | assert([2, 3].iota(f).last == f + 2 * 3 - 1); | } | | static if (kind_ != SliceKind.contiguous) | /// Peforms `popFrontAll` for all dimensions | void popFrontAll() | { | assert(!anyEmpty); | foreach(d; Iota!N_) | popFront!d; | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology: iota, canonical; | auto v = [2, 3].iota.canonical; | v.popFrontAll; | assert(v == [[4, 5]]); | } | | static if (kind_ != SliceKind.contiguous) | /// Peforms `popBackAll` for all dimensions | void popBackAll() | { | assert(!anyEmpty); | foreach(d; Iota!N_) | popBack!d; | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology: iota, canonical; | auto v = [2, 3].iota.canonical; | v.popBackAll; | 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; | return this.lightScope.unpack.anyRUEmpty; | } | else | 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 | { 0000000| return _lengths[0 .. N].anyEmptyShape; | } | | static if (doUnittest) | /// | @safe pure nothrow @nogc version(mir_test) unittest | { | import mir.ndslice.topology : iota, canonical; | auto s = iota(2, 3).canonical; | assert(!s.anyEmpty); | s.popFrontExactly!1(3); | 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) | index[i] = _lengths[i] - index[i]; | 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; | auto s = iota(2, 3); | assert(s[$ - 1, $ - 2] == s.backward([1, 2])); | } | | /++ | Returns: Total number of elements in a slice | +/ | size_t elementCount()() @safe @property scope const | { 0000000| size_t len = 1; | foreach (i; Iota!N) 0000000| len *= _lengths[i]; 0000000| return len; | } | | static if (doUnittest) | /// Regular slice | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | 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; | auto slice = iota(3, 4, 5, 6, 7, 8); | auto p = slice.pack!2; | assert(p.elementCount == 360); | assert(p[0, 0, 0, 0].elementCount == 56); | 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; | auto ret = this.canonical; | } | else | { | auto ret = this; | } | auto len = end - begin; | assert(len <= ret._lengths[dimension]); | ret._lengths[dimension] = len; | ret._iterator += ret._stride!dimension * begin; | return ret; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | auto sl = iota(3, 4); | 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; | auto ret = this.canonical; | } | else | { | auto ret = this; | } | assert(n <= ret._lengths[dimension]); | ret._lengths[dimension] = n; | return ret; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | auto sl = iota(3, 4); | 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; | auto ret = this.canonical; | } | else | { | auto ret = this; | } | assert(n <= ret._lengths[dimension]); | ret._iterator += ret._stride!dimension * (ret._lengths[dimension] - n); | ret._lengths[dimension] = n; | return ret; | } | | static if (doUnittest) | /// | @safe @nogc pure nothrow version(mir_test) unittest | { | import mir.ndslice.topology : iota; | auto sl = iota(3, 4); | 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) | ) | { 0000000| if (this._lengths != rslice._lengths) 0000000| return false; 0000000| if (anyEmpty) 0000000| return true; 0000000| if (this._iterator == rslice._iterator) | { 0000000| auto ls = this.strides; 0000000| auto rs = rslice.strides; | foreach (i; Iota!N) | { 0000000| if (this._lengths[i] != 1 && ls[i] != rs[i]) 0000000| goto L; | } 0000000| return true; | } | } | L: | | static if (is(Iterator == IotaIterator!UL, UL) && is(IteratorR == Iterator)) | { 0000000| return false; | } | else | { | import mir.algorithm.iteration : equal; 0000000| return equal(this.lightScope, rslice.lightScope); | } | } | | /// ditto | bool opEquals(T)(scope const(T)[] arr) @trusted scope const | { | auto slice = this.lightConst; | if (slice.length != arr.length) | return false; | if (arr.length) do | { | if (slice.front != arr[0]) | return false; | slice.popFront; | arr = arr[1 .. $]; | } | while (arr.length); | return true; | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_test) unittest | { | auto a = [1, 2, 3, 4].sliced(2, 2); | | assert(a != [1, 2, 3, 4, 5, 6].sliced(2, 3)); | assert(a != [[1, 2, 3], [4, 5, 6]]); | | assert(a == [1, 2, 3, 4].sliced(2, 2)); | assert(a == [[1, 2], [3, 4]]); | | assert(a != [9, 2, 3, 4].sliced(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; | 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 | { | 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."; | assert(j <= _lengths[dimension], | "Slice.opSlice!" ~ dimension.stringof ~ errorMsg); | } | do | { | return typeof(return)(j - i, typeof(return).Iterator(i)); | } | | /++ | $(BOLD Fully defined index) | +/ | auto ref opIndex()(size_t[N] _indices...) scope return @trusted | { 0000000| return _iterator[indexStride(_indices)]; | } | | /// ditto | auto ref opIndex()(size_t[N] _indices...) scope return const @trusted | { | static if (is(typeof(_iterator[indexStride(_indices)]))) | 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)]))) | 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) | return Ret(_lengths[I .. N], _strides[I .. S], _iterator + indexStride(_indices)); | else | 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); | 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); | auto structure_ = Ret._Structure.init; | | enum bool shrink = kind == Canonical && slices.length == N; | static if (shrink) | { | { | enum i = Slices.length - 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 | { | stride += slice._iterator._index; | structure_[0][j!i] = slice._lengths[0]; | } | } | } | static if (kind == Universal || kind == Canonical) | { | foreach_reverse (i, slice; slices[0 .. $ - shrink]) //static | { | static if (isIndex!(Slices[i])) | { | assert(slice < _lengths[i], "Slice.opIndex: index must be less than length"); | stride += _strides[i] * slice; | } | else | { | stride += _strides[i] * slice._iterator._index; | structure_[0][j!i] = slice._lengths[0]; | structure_[1][j!i] = _strides[i]; | } | } | } | else | { | ptrdiff_t ball = this._stride!(slices.length - 1); | foreach_reverse (i, slice; slices) //static | { | static if (isIndex!(Slices[i])) | { | assert(slice < _lengths[i], "Slice.opIndex: index must be less than length"); | stride += ball * slice; | } | else | { | stride += ball * slice._iterator._index; | structure_[0][j!i] = slice._lengths[0]; | static if (j!i < Ret.S) | structure_[1][j!i] = ball; | } | static if (i) | ball *= _lengths[i]; | } | } | foreach (i; Iota!(Slices.length, N)) | structure_[0][i - F] = _lengths[i]; | foreach (i; Iota!(Slices.length, N)) | static if (Ret.S > i - F) | structure_[1][i - F] = _strides[i]; | | return Ret(structure_, _iterator + stride); | } | else | { 0000000| return this; | } | } | | static if (doUnittest) | /// | pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto slice = slice!int(5, 3); | | /// Fully defined slice | assert(slice[] == slice); | auto sublice = slice[0..$-2, 1..$]; | | /// Partially defined slice | auto row = slice[3]; | 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 | auto index = slices.cartesian; | return this.indexed(index); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation: slice; | auto sli = slice!int(4, 3); | auto idx = slice!(size_t[2])(3); | 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; | sli[idx] = 1; | | assert(sli == [ | [0, 0, 1], | [0, 0, 0], | [1, 0, 0], | [0, 1, 0], | ]); | | foreach (row; sli[[1, 3].sliced]) | row[] += 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; | 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); | auto minor = sli[[0, $ / 2, $ - 1].sliced, [0, $ / 2, $ - 1].sliced]; | | minor[] = iota!int([3, 3], 1); | | 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 | return this.map!(op ~ "a"); | } | | static if (doUnittest) | /// | version(mir_test) unittest | { | import mir.ndslice.topology; | | auto payload = [1, 2, 3, 4]; | auto s = iota([payload.length], payload.ptr); // slice of references; | assert(s[1] == payload.ptr + 1); | | auto c = *s; // the same as s.map!"*a" | assert(c[1] == *s[1]); | | *s[1] = 3; | 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; | 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; | 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 | auto s = iota([4]); | // 0 1 2 0 | assert(s % 3 == iota([4]).map!"a % 3"); | // 0 2 4 6 | 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 | auto s = iota([4]); | // 0 1 4 9 | 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; | 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; | | auto s = iota([2, 3]); | auto c = iota([2, 3], 5, 8); | 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 | { | 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; | | auto result = (() @trusted => this.shape.uninitSlice!(Unqual!E))(); | | import mir.algorithm.iteration: each; | each!(emplaceRef!(Unqual!E))(result, this); | | 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; | auto x = 3.iota!int; | Slice!(immutable(int)*) imm = x.idup; | Slice!(int*) mut = imm.dup; | assert(imm == x); | assert(mut == x); | } | | /++ | Duplicates slice. | Returns: GC-allocated Contiguous immutable slice. | See_also: $(LREF .Slice.dup) | +/ | Slice!(immutable(DeepElement)*, N) | idup()() scope @property | { | 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; | | auto result = (() @trusted => this.shape.uninitSlice!(Unqual!E))(); | | import mir.algorithm.iteration: each; | each!(emplaceRef!(immutable E))(result, this); | alias R = typeof(return); | 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; | auto x = 3.iota!int; | Slice!(int*) mut = x.dup; | Slice!(immutable(int)*) imm = mut.idup; | assert(imm == x); | 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 | { 0000000| assert(n < elementCount, "indexStrideValue: n must be less than elementCount"); 0000000| assert(n >= 0, "indexStrideValue: n must be greater than or equal to zero"); | | static if (kind == Contiguous) { 0000000| return n; | } else { | ptrdiff_t _shift; | foreach_reverse (i; Iota!(1, N)) | { | immutable v = n / ptrdiff_t(_lengths[i]); | n %= ptrdiff_t(_lengths[i]); | static if (i == S) | _shift += n; | else | _shift += n * _strides[i]; | n = v; | } | _shift += n * _strides[0]; | 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 | { 0000000| return _iterator[indexStrideValue(index)]; | } | | /// | version(mir_test) | @safe pure @nogc nothrow | unittest | { | import mir.ndslice.topology: iota, flattened; | | auto x = iota(2, 3, 4); | 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; | this.flattened.opIndexOpAssignImplSlice!op(value.flattened); | } | else | { | auto ls = this; | do | { | static if (N > RN) | { | ls.front.opIndexOpAssignImplSlice!op(value); | } | else | { | static if (ls.N == 1) | { | static if (isInstanceOf!(SliceIterator, Iterator)) | { | static if (isSlice!(typeof(value.front))) | ls.front.opIndexOpAssignImplSlice!op(value.front); | else | static if (isDynamicArray!(typeof(value.front))) | ls.front.opIndexOpAssignImplSlice!op(value.front); | else | 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 | ls.front.opIndexOpAssignImplSlice!op(value.front); | value.popFront; | } | ls.popFront; | } | 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) | { | auto sl = this.lightScope.opIndex(slices); | assert(_checkAssignLengths(sl, value)); | if(!sl.anyRUEmpty) | sl.opIndexOpAssignImplSlice!""(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | auto b = [1, 2, 3, 4].sliced(2, 2); | | a[0..$, 0..$-1] = b; | assert(a == [[1, 2, 0], [3, 4, 0]]); | | // fills both rows with b[0] | a[0..$, 0..$-1] = b[0]; | assert(a == [[1, 2, 0], [1, 2, 0]]); | | a[1, 0..$-1] = b[1]; | assert(a[1] == [3, 4, 0]); | | a[1, 0..$-1][] = b[0]; | 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; | auto a = slice!int(4, 4); | a.blocks(2, 2)[] = iota!int(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; | auto a = slice!int(4, 4); | a.blocks(2, 2)[] = iota!int(2, 2, 2).pack!1; | | 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 | { | auto ls = this; | 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 | 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];"); | value = value[1 .. $]; | ls.popFront; | } | while (ls.length); | } | else | static if (N == DynamicArrayDimensionsCount!(T[])) | { | do | { | ls.front.opIndexOpAssignImplArray!op(value[0]); | value = value[1 .. $]; | ls.popFront; | } | while (ls.length); | } | else | { | do | { | ls.front.opIndexOpAssignImplArray!op(value); | ls.popFront; | } | 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) | { | auto sl = this.lightScope.opIndex(slices); | sl.opIndexOpAssignImplArray!""(value); | } | | static if (doUnittest) | /// | pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | auto b = [[1, 2], [3, 4]]; | | a[] = [[1, 2, 3], [4, 5, 6]]; | assert(a == [[1, 2, 3], [4, 5, 6]]); | | a[0..$, 0..$-1] = [[1, 2], [3, 4]]; | assert(a == [[1, 2, 3], [3, 4, 6]]); | | a[0..$, 0..$-1] = [1, 2]; | assert(a == [[1, 2, 3], [1, 2, 6]]); | | a[1, 0..$-1] = [3, 4]; | assert(a[1] == [3, 4, 6]); | | a[1, 0..$-1][] = [3, 4]; | 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; | auto a = slice!int(4, 4); | a.blocks(2, 2)[] = [[0, 1], [2, 3]]; | | 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 | { | auto sl = this; | static if (concatenationDimension!T) | { | if (!sl.empty) do | { | static if (op == "") | sl.front.opIndexAssign(value.front); | else | sl.front.opIndexOpAssign!op(value.front); | value.popFront; | sl.popFront; | } | while(!sl.empty); | } | else | { | foreach (ref slice; value._slices) | { | static if (op == "") | sl[0 .. slice.length].opIndexAssign(slice); | else | sl[0 .. slice.length].opIndexOpAssign!op(slice); | | sl = sl[slice.length .. $]; | } | assert(sl.empty); | } | } | | /// | void opIndexAssign(T, Slices...)(T concatenation, Slices slices) scope return | if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && isConcatenation!T) | { | auto sl = this.lightScope.opIndex(slices); | static assert(typeof(sl).N == T.N, "incompatible dimension count"); | 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) | { | auto sl = this.lightScope.opIndex(slices); | if(!sl.anyRUEmpty) | sl.opIndexOpAssignImplValue!""(value); | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | | a[] = 9; | assert(a == [[9, 9, 9], [9, 9, 9]]); | | a[0..$, 0..$-1] = 1; | assert(a == [[1, 1, 9], [1, 1, 9]]); | | a[0..$, 0..$-1] = 2; | assert(a == [[2, 2, 9], [2, 2, 9]]); | | a[1, 0..$-1] = 3; | //assert(a[1] == [3, 3, 9]); | | a[1, 0..$-1] = 4; | //assert(a[1] == [4, 4, 9]); | | a[1, 0..$-1][] = 5; | | 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; | auto a = slice!int(2, 3).pack!1; | | 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; | } 0000000| return _iterator[indexStride(_indices)] = value; | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | | a[1, 2] = 3; | assert(a[1, 2] == 3); | } | | static if (doUnittest) | @safe pure nothrow version(mir_test) unittest | { | auto a = new int[6].sliced(2, 3); | | a[[1, 2]] = 3; | 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 | { | return mixin(`t` ~ op ~ `= v`); | } | auto str = indexStride(_indices); | static if (op == "^^" && isFloatingPoint!DeepElement && isFloatingPoint!(typeof(value))) | { | import mir.math.common: pow; | _iterator[str] = pow(_iterator[str], value); | } | else | return mixin (`_iterator[str] ` ~ op ~ `= value`); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | | a[1, 2] += 3; | assert(a[1, 2] == 3); | } | | static if (doUnittest) | @safe pure nothrow version(mir_test) unittest | { | auto a = new int[6].sliced(2, 3); | | a[[1, 2]] += 3; | 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) | { | auto sl = this.lightScope.opIndex(slices); | assert(_checkAssignLengths(sl, value)); | if(!sl.anyRUEmpty) | sl.opIndexOpAssignImplSlice!op(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | auto b = [1, 2, 3, 4].sliced(2, 2); | | a[0..$, 0..$-1] += b; | assert(a == [[1, 2, 0], [3, 4, 0]]); | | a[0..$, 0..$-1] += b[0]; | assert(a == [[2, 4, 0], [4, 6, 0]]); | | a[1, 0..$-1] += b[1]; | assert(a[1] == [7, 10, 0]); | | a[1, 0..$-1][] += b[0]; | 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; | auto a = slice!size_t(4, 4); | a.blocks(2, 2)[] += iota(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; | auto a = slice!size_t(4, 4); | a.blocks(2, 2)[] += iota(2, 2, 2).pack!1; | | 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) | { | auto sl = this.lightScope.opIndex(slices); | sl.opIndexOpAssignImplArray!op(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation : slice; | auto a = slice!int(2, 3); | | a[0..$, 0..$-1] += [[1, 2], [3, 4]]; | assert(a == [[1, 2, 0], [3, 4, 0]]); | | a[0..$, 0..$-1] += [1, 2]; | assert(a == [[2, 4, 0], [4, 6, 0]]); | | a[1, 0..$-1] += [3, 4]; | assert(a[1] == [7, 10, 0]); | | a[1, 0..$-1][] += [1, 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; | auto a = slice!int(4, 4); | a.blocks(2, 2)[] += [[0, 1], [2, 3]]; | | 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; | this.flattened.opIndexOpAssignImplValue!op(value); | } | else | { | auto ls = this; | do | { | static if (N == 1) | { | static if (isInstanceOf!(SliceIterator, Iterator)) | ls.front.opIndexOpAssignImplValue!op(value); | else | mixin (`ls.front ` ~ op ~ `= value;`); | } | else | ls.front.opIndexOpAssignImplValue!op(value); | ls.popFront; | } | 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) | { | auto sl = this.lightScope.opIndex(slices); | if(!sl.anyRUEmpty) | sl.opIndexOpAssignImplValue!op(value); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | | a[] += 1; | assert(a == [[1, 1, 1], [1, 1, 1]]); | | a[0..$, 0..$-1] += 2; | assert(a == [[3, 3, 1], [3, 3, 1]]); | | a[1, 0..$-1] += 3; | 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) | { | auto sl = this.lightScope.opIndex(slices); | static assert(typeof(sl).N == concatenation.N); | 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; | auto a = slice!int(2, 3).pack!1; | | a[] += 9; | 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 | { | return mixin(op ~ `t`); | } | return mixin (op ~ `_iterator[indexStride(_indices)]`); | } | | static if (doUnittest) | /// | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | | ++a[1, 2]; | assert(a[1, 2] == 1); | } | | // Issue 16473 | static if (doUnittest) | @safe pure nothrow version(mir_test) unittest | { | import mir.ndslice.allocation; | auto sl = slice!double(2, 5); | auto d = -sl[0, 1]; | } | | static if (doUnittest) | @safe pure nothrow version(mir_test) unittest | { | auto a = new int[6].sliced(2, 3); | | ++a[[1, 2]]; | assert(a[[1, 2]] == 1); | } | | private void opIndexUnaryImpl(string op, Slices...)(Slices slices) scope | { | auto ls = this; | do | { | static if (N == 1) | { | static if (isInstanceOf!(SliceIterator, Iterator)) | ls.front.opIndexUnaryImpl!op; | else | mixin (op ~ `ls.front;`); | } | else | ls.front.opIndexUnaryImpl!op; | ls.popFront; | } | 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 == `--`)) | { | auto sl = this.lightScope.opIndex(slices); | if (!sl.anyRUEmpty) | sl.opIndexUnaryImpl!op; | } | | static if (doUnittest) | /// | @safe pure nothrow | version(mir_test) unittest | { | import mir.ndslice.allocation; | auto a = slice!int(2, 3); | | ++a[]; | assert(a == [[1, 1, 1], [1, 1, 1]]); | | --a[1, 0..$-1]; | | 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; | auto tensor = iota(3, 4, 5).slice; | | assert(tensor[1, 2] == tensor[1][2]); | assert(tensor[1, 2, 3] == tensor[1][2][3]); | | assert( tensor[0..$, 0..$, 4] == tensor.universal.transposed!2[4]); | assert(&tensor[0..$, 0..$, 4][1, 2] is &tensor[1, 2, 4]); | | tensor[1, 2, 3]++; //`opIndex` returns value by reference. | --tensor[1, 2, 3]; //`opUnary` | | ++tensor[]; | tensor[] -= 1; | | // `opIndexAssing` accepts only fully defined indices and slices. | // Use an additional empty slice `[]`. | static assert(!__traits(compiles, tensor[0 .. 2] *= 2)); | | tensor[0 .. 2][] *= 2; //OK, empty slice | tensor[0 .. 2, 3, 0..$] /= 2; //OK, 3 index or slice positions are defined. | | //fully defined index may be replaced by a static array | size_t[3] index = [1, 2, 3]; | 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; | | auto tensor = slice!int(3, 4, 5).universal; | auto matrix = slice!int(3, 4).universal; | auto vector = slice!int(3); | | foreach (i; 0..3) | vector[i] = i; | | // fills matrix columns | matrix.transposed[] = vector; | | // fills tensor with vector | // transposed tensor shape is (4, 5, 3) | // vector shape is ( 3) | tensor.transposed!(1, 2)[] = vector; | | // transposed tensor shape is (5, 3, 4) | // matrix shape is ( 3, 4) | tensor.transposed!2[] += matrix; | | // transposed tensor shape is (5, 4, 3) | // transposed matrix shape is ( 4, 3) | 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) | { | string[][] data = str.lineSplitter.filter!(not!empty).map!split.array; | | size_t rows = data .length.enforce!"empty input"; | size_t columns = data[0].length.enforce!"empty first row"; | | data.all!(a => a.length == columns).enforce!"rows have different lengths"; | auto slice = slice!int(rows, columns); | foreach (i, line; data) | foreach (j, num; line) | slice[i, j] = num.fromString!int; | return slice; | } | | auto input = "\r1 2 3\r\n 4 5 6\n"; | | auto matrix = toMatrix(input); | assert(matrix == [[1, 2, 3], [4, 5, 6]]); | | // back to text | import std.format; | auto text2 = format("%(%(%s %)\n%)\n", matrix); | assert(text2 == "1 2 3\n4 5 6\n"); |} | |// Slicing |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | auto a = iota(10, 20, 30, 40); | auto b = a[0..$, 10, 4 .. 27, 4]; | auto c = b[2 .. 9, 5 .. 10]; | auto d = b[3..$, $-2]; | assert(b[4, 17] == a[4, 10, 21, 4]); | assert(c[1, 2] == a[3, 10, 11, 4]); | 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; | | auto fun(ref sizediff_t x) { x *= 3; } | | auto tensor = iota(8, 9, 10).slice; | | ++tensor[]; | fun(tensor[0, 0, 0]); | | assert(tensor[0, 0, 0] == 3); | | tensor[0, 0, 0] *= 4; | tensor[0, 0, 0]--; | 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; | | auto matrix = 72 | .iota | //.map!(i => BigInt(i)) | .array | .sliced(8, 9); | | matrix[3 .. 6, 2] += 100; | foreach (i; 0 .. 8) | foreach (j; 0 .. 9) | if (i >= 3 && i < 6 && j == 2) | assert(matrix[i, j] >= 100); | else | assert(matrix[i, j] < 100); |} | |// Operator overloading. # 3 |pure nothrow version(mir_test) unittest |{ | import mir.ndslice.allocation; | import mir.ndslice.topology : iota; | | auto matrix = iota(8, 9).slice; | matrix[] = matrix; | matrix[] += matrix; | assert(matrix[2, 3] == (2 * 9 + 3) * 2); | | auto vec = iota([9], 100); | matrix[] = vec; | foreach (v; matrix) | assert(v == vec); | | matrix[] += vec; | foreach (vector; matrix) | foreach (elem; vector) | 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; | Array!int ar; | ar.length = 12; | auto arSl = ar[].slicedField(3, 4); |} | |// Test for map #1 |version(mir_test) unittest |{ | import mir.ndslice.topology: map, byDim; | auto slice = [1, 2, 3, 4].sliced(2, 2); | | auto r = slice.byDim!0.map!(a => a.map!(a => a * 6)); | assert(r.front.front == 6); | assert(r.front.back == 12); | assert(r.back.front == 18); | assert(r.back.back == 24); | assert(r[0][0] == 6); | assert(r[0][1] == 12); | assert(r[1][0] == 18); | 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; | auto data = [1, 2, 3, 4]; | static assert(hasSlicing!(typeof(data))); | static assert(isForwardRange!(typeof(data))); | static assert(isRandomAccessRange!(typeof(data))); | auto slice = data.sliced(2, 2); | static assert(hasSlicing!(typeof(slice))); | static assert(isForwardRange!(typeof(slice))); | static assert(isRandomAccessRange!(typeof(slice))); | 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))); | assert(r.front.front == 6); | assert(r.front.back == 12); | assert(r.back.front == 18); | assert(r.back.back == 24); | assert(r[0][0] == 6); | assert(r[0][1] == 12); | assert(r[1][0] == 18); | 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; | return _checkAssignLengths(ls.unpack, rs); | } | else | static if (isInstanceOf!(SliceIterator, RIterator)) | { | import mir.ndslice.topology: unpack; | return _checkAssignLengths(ls, rs.unpack); | } | else | { | foreach (i; Iota!(0, RN)) | if (ls._lengths[i + LN - RN] != rs._lengths[i]) | return false; | return true; | } |} | |@safe pure nothrow @nogc version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | | assert(_checkAssignLengths(iota(2, 2), iota(2, 2))); | assert(!_checkAssignLengths(iota(2, 2), iota(2, 3))); | assert(!_checkAssignLengths(iota(2, 2), iota(3, 2))); | assert(!_checkAssignLengths(iota(2, 2), iota(3, 3))); |} | |pure nothrow version(mir_test) unittest |{ | auto slice = new int[15].slicedField(5, 3); | | /// Fully defined slice | assert(slice[] == slice); | auto sublice = slice[0..$-2, 1..$]; | | /// Partially defined slice | auto row = slice[3]; | auto col = slice[0..$, 1]; |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | auto b = [1, 2, 3, 4].sliced(2, 2); | | a[0..$, 0..$-1] = b; | assert(a == [[1, 2, 0], [3, 4, 0]]); | | a[0..$, 0..$-1] = b[0]; | assert(a == [[1, 2, 0], [1, 2, 0]]); | | a[1, 0..$-1] = b[1]; | assert(a[1] == [3, 4, 0]); | | a[1, 0..$-1][] = b[0]; | assert(a[1] == [1, 2, 0]); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | auto b = [[1, 2], [3, 4]]; | | a[] = [[1, 2, 3], [4, 5, 6]]; | assert(a == [[1, 2, 3], [4, 5, 6]]); | | a[0..$, 0..$-1] = [[1, 2], [3, 4]]; | assert(a == [[1, 2, 3], [3, 4, 6]]); | | a[0..$, 0..$-1] = [1, 2]; | assert(a == [[1, 2, 3], [1, 2, 6]]); | | a[1, 0..$-1] = [3, 4]; | assert(a[1] == [3, 4, 6]); | | a[1, 0..$-1][] = [3, 4]; | assert(a[1] == [3, 4, 6]); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | a[] = 9; | //assert(a == [[9, 9, 9], [9, 9, 9]]); | | a[0..$, 0..$-1] = 1; | //assert(a == [[1, 1, 9], [1, 1, 9]]); | | a[0..$, 0..$-1] = 2; | //assert(a == [[2, 2, 9], [2, 2, 9]]); | | a[1, 0..$-1] = 3; | //assert(a[1] == [3, 3, 9]); | | a[1, 0..$-1] = 4; | //assert(a[1] == [4, 4, 9]); | | a[1, 0..$-1][] = 5; | //assert(a[1] == [5, 5, 9]); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | a[1, 2] = 3; | assert(a[1, 2] == 3); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | a[[1, 2]] = 3; | assert(a[[1, 2]] == 3); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | a[1, 2] += 3; | assert(a[1, 2] == 3); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | a[[1, 2]] += 3; | assert(a[[1, 2]] == 3); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | auto b = [1, 2, 3, 4].sliced(2, 2); | | a[0..$, 0..$-1] += b; | assert(a == [[1, 2, 0], [3, 4, 0]]); | | a[0..$, 0..$-1] += b[0]; | assert(a == [[2, 4, 0], [4, 6, 0]]); | | a[1, 0..$-1] += b[1]; | assert(a[1] == [7, 10, 0]); | | a[1, 0..$-1][] += b[0]; | assert(a[1] == [8, 12, 0]); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | a[0..$, 0..$-1] += [[1, 2], [3, 4]]; | assert(a == [[1, 2, 0], [3, 4, 0]]); | | a[0..$, 0..$-1] += [1, 2]; | assert(a == [[2, 4, 0], [4, 6, 0]]); | | a[1, 0..$-1] += [3, 4]; | assert(a[1] == [7, 10, 0]); | | a[1, 0..$-1][] += [1, 2]; | assert(a[1] == [8, 12, 0]); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | a[] += 1; | assert(a == [[1, 1, 1], [1, 1, 1]]); | | a[0..$, 0..$-1] += 2; | assert(a == [[3, 3, 1], [3, 3, 1]]); | | a[1, 0..$-1] += 3; | assert(a[1] == [6, 6, 1]); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | ++a[1, 2]; | assert(a[1, 2] == 1); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | ++a[[1, 2]]; | assert(a[[1, 2]] == 1); |} | |pure nothrow version(mir_test) unittest |{ | auto a = new int[6].slicedField(2, 3); | | ++a[]; | assert(a == [[1, 1, 1], [1, 1, 1]]); | | --a[1, 0..$-1]; | assert(a[1] == [0, 0, 1]); |} | |version(mir_test) unittest |{ | import mir.ndslice.topology: iota, universal; | | auto sl = iota(3, 4).universal; | 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; | auto s = iota(2, 3); | assert(s.front!1 == [0, 3]); | 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] != '=')) |{ | 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 == "") | return lside.opIndexAssign(rside); | else | return lside.opIndexOpAssign!op(rside); |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.topology: iota; | import mir.ndslice.allocation: slice; | auto scalar = 3; | auto vector = 3.iota.slice; // [0, 1, 2] | | // scalar = 5; | scalar.ndassign = 5; | assert(scalar == 5); | | // vector[] = vector * 2; | vector.ndassign = vector * 2; | assert(vector == [0, 2, 4]); | | // vector[] += scalar; | vector.ndassign!"+"= scalar; | assert(vector == [5, 7, 9]); |} | |version(mir_test) pure nothrow unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: universal; | | auto df = slice!(double, int, int)(2, 3).universal; | df.label[] = [1, 2]; | df.label!1[] = [1, 2, 3]; | auto lsdf = df.lightScope; | assert(lsdf.label!0[0] == 1); | assert(lsdf.label!1[1] == 2); | | auto immdf = (cast(immutable)df).lightImmutable; | assert(immdf.label!0[0] == 1); | assert(immdf.label!1[1] == 2); | | auto constdf = df.lightConst; | assert(constdf.label!0[0] == 1); | assert(constdf.label!1[1] == 2); | | auto constdf2 = df.toConst; | assert(constdf2.label!0[0] == 1); | assert(constdf2.label!1[1] == 2); | | auto immdf2 = (cast(immutable)df).toImmutable; | assert(immdf2.label!0[0] == 1); | assert(immdf2.label!1[1] == 2); |} | |version(mir_test) pure nothrow unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.topology: universal; | | auto df = slice!(double, int, int)(2, 3).universal; | df[] = 5; | | Slice!(double*, 2, Universal) values = df.values; | assert(values[0][0] == 5); | Slice!(LightConstOf!(double*), 2, Universal) constvalues = df.values; | assert(constvalues[0][0] == 5); | Slice!(LightImmutableOf!(double*), 2, Universal) immvalues = (cast(immutable)df).values; | assert(immvalues[0][0] == 5); |} | |version(mir_test) @safe unittest |{ | import mir.ndslice.allocation; | auto a = rcslice!double([2, 3], 0); | auto b = rcslice!double([2, 3], 0); | a[1, 2] = 3; | b[] = a; | assert(a == b); |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | | auto m = iota(2, 3, 4); // Contiguous Matrix | auto mFlat = m.flattened; | | for (size_t i = 0; i < m.elementCount; i++) { | assert(m.accessFlat(i) == mFlat[i]); | } |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | | auto m = iota(3, 4); // Contiguous Matrix | auto x = m.front; // Contiguous Vector | | for (size_t i = 0; i < x.elementCount; i++) { | assert(x.accessFlat(i) == m[0, i]); | } |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | | auto m = iota(3, 4); // Contiguous Matrix | auto x = m[0 .. $, 0 .. $ - 1]; // Canonical Matrix | auto xFlat = x.flattened; | | for (size_t i = 0; i < x.elementCount; i++) { | assert(x.accessFlat(i) == xFlat[i]); | } |} | | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | | auto m = iota(2, 3, 4); // Contiguous Matrix | auto x = m[0 .. $, 0 .. $, 0 .. $ - 1]; // Canonical Matrix | auto xFlat = x.flattened; | | for (size_t i = 0; i < x.elementCount; i++) { | 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; | | auto m = iota(2, 3, 4); // Contiguous Matrix | auto x = m.transposed!(2, 1, 0); // Universal Matrix | auto xFlat = x.flattened; | | for (size_t i = 0; i < x.elementCount; i++) { | 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; | | auto m = iota(3, 4); // Contiguous Matrix | auto x = m.transposed; // Universal Matrix | auto xFlat = x.flattened; | | for (size_t i = 0; i < x.elementCount; i++) { | assert(x.accessFlat(i) == xFlat[i]); | } |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened, diagonal; | | auto m = iota(3, 4); // Contiguous Matrix | auto x = m.diagonal; // Universal Vector | | for (size_t i = 0; i < x.elementCount; i++) { | assert(x.accessFlat(i) == m[i, i]); | } |} | |version(mir_test) |@safe pure @nogc nothrow |unittest |{ | import mir.ndslice.topology: iota, flattened; | | auto m = iota(3, 4); // Contiguous Matrix | auto x = m.front!1; // Universal Vector | | for (size_t i = 0; i < x.elementCount; i++) { | 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; |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/slice.d is 0% covered <<<<<< EOF # path=./source-mir-sparse-blas-dot.lst |/** |License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). | |Authors: Ilya Yaroshenko |*/ |module mir.sparse.blas.dot; | |import std.traits; |import mir.ndslice.slice; |import mir.sparse; |import mir.series; | |/++ |Dot product of two vectors | |Params: | x = sparse vector | y = sparse vector |Returns: | scalar `xáµ€ × y` |+/ |Unqual!(CommonType!(T1, T2)) dot( | V1 : Series!(I1*, T1*), | V2 : Series!(I2*, T2*), | T1, T2, I1, I2) |(V1 x, V2 y) |{ 1| return dot!(typeof(return))(x, y); |} | |/// ditto |D dot( | D, | V1 : Series!(I1*, T1*), | V2 : Series!(I2*, T2*), | T1, T2, I1, I2) |(V1 x, V2 y) |{ | 2| typeof(return) s = 0; | 2| uint done = 2; 2| Unqual!I1 ai0 = void; 2| Unqual!I2 bi0 = void; | 4| if (x.length && y.length) for (;;) | { 8| bi0 = y.index[0]; 8| if (x.index[0] < bi0) | { | do | { 4| x.popFront; 4| if (x.length == 0) | { 0000000| break; | } | } 4| while (x.index[0] < bi0); 4| done = 2; | } 8| if (--done == 0) | { 2| goto L; | } 6| ai0 = x.index[0]; 6| if (y.index[0] < ai0) | { | do | { 4| y.popFront; 4| if (y.length == 0) | { 0000000| break; | } | } 4| while (y.index[0] < ai0); 4| done = 2; | } 6| if (--done == 0) | { 2| goto L; | } 4| continue; | L: 4| s = x.value[0] * y.value[0] + s; 4| x.popFront; 4| if (x.length == 0) | { 0000000| break; | } 4| y.popFront; 4| if (y.length == 0) | { 2| break; | } | } | 2| return s; |} | |/// |unittest |{ | import mir.series; | 1| auto x = series([0u, 3, 5, 9, 100], [1, 3, 4, 9, 10]); 1| auto y = series([1u, 3, 4, 9], [1, 10, 100, 1000]); | // x = [1, 0, 0, 3, 0, 4, 0, 0, 0, 9, ... ,10] | // y = [0, 1, 0, 10, 0, 0, 0, 0, 0, 1000] 1| assert(dot(x, y) == 9030); 1| assert(dot!double(x, y) == 9030); |} | |/++ |Dot product of two vectors. |Params: | x = sparse vector | y = dense vector |Returns: | scalar `x × y` |+/ |Unqual!(CommonType!(T1, ForeachType!V2)) dot( | V1 : Series!(I1*, T1*), | T1, I1, V2) |(V1 x, V2 y) | if (isDynamicArray!V2 || isSlice!V2) |{ 21| return dot!(typeof(return))(x, y); |} | |///ditto |D dot( | D, | V1 : Series!(I1*, T1*), | T1, I1, V2) |(V1 x, V2 y) | if (isDynamicArray!V2 || isSlice!V2) |in |{ 21| if (x.length) 21| assert(x.index[$-1] < y.length); |} |body |{ | | import mir.internal.utility; | | alias T2 = ForeachType!V2; | | alias F = Unqual!(CommonType!(T1, T2)); 21| F s = 0; 324| foreach (size_t i; 0 .. x.index.length) | { 87| s = y[x.index[i]] * x.value[i] + s; | } | 21| return s; |} | |/// |unittest |{ | import mir.series; | 1| auto x = [0u, 3, 5, 9, 10].series([1.0, 3, 4, 9, 13]); 1| auto y = [0.0, 1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; | // x: [1, 0, 0, 3, 0, 4, 0, 0, 0, 9, 13, 0, 0, 0] | // y: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] 1| auto r = 0 + 3 * 3 + 4 * 5 + 9 * 9 + 13 * 10; 1| assert(dot(x, y) == r); 1| assert(dot(x, y.sliced) == r); 1| assert(dot(x, y.slicedField) == r); |} source/mir/sparse/blas/dot.d is 94% covered <<<<<< EOF # path=./source-mir-glas-l2.lst |/++ |$(H2 Level 2) | |$(SCRIPT inhibitQuickIndex = 1;) | |This is a submodule of $(MREF mir,glas). | |The Level 2 BLAS perform matrix-vector operations. | |Note: GLAS is singe thread for now. | |$(BOOKTABLE $(H2 Matrix-vector operations), | |$(TR $(TH Function Name) $(TH Description)) |$(T2 gemv, general matrix-vector multiplication, $(RED partially optimized)) |) | |License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |Copyright: Copyright © 2016-, Ilya Yaroshenko |Authors: Ilya Yaroshenko | |Macros: |T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) |SUBMODULE = $(MREF_ALTTEXT $1, mir, glas, $1) |SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, glas, $1)$(NBSP) |NDSLICEREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) |+/ |module mir.glas.l2; | |import std.traits; |import std.meta; | |import mir.math.common; |import mir.internal.utility; |import mir.ndslice.slice; | |import mir.glas.l1; | |import mir.math.common: fastmath; | |@fastmath: | |/++ |$(RED DRAFT) |Performs general matrix-vector multiplication. | |Pseudo_code: `y := alpha A × x + beta y`. | |Params: | alpha = scalar | asl = `m ⨉ n` matrix | xsl = `n ⨉ 1` vector | beta = scalar. When `beta` is supplied as zero then the vector `ysl` need not be set on input. | ysl = `m ⨉ 1` vector | |Note: | GLAS does not require transposition parameters. | Use $(NDSLICEREF iteration, transposed) | to perform zero cost `Slice` transposition. | |BLAS: SGEMV, DGEMV, (CGEMV, ZGEMV are not implemented for now) |+/ |nothrow @nogc @system |void gemv(A, B, C, | SliceKind kindA, | SliceKind kindB, | SliceKind kindC, | ) |( | C alpha, | Slice!(const(A)*, 2, kindA) asl, | Slice!(const(B)*, 1, kindB) xsl, | C beta, | Slice!(C*, 1, kindC) ysl, |) | if (allSatisfy!(isNumeric, A, B, C)) |in |{ 1| assert(asl.length!0 == ysl.length, "constraint: asl.length!0 == ysl.length"); 1| assert(asl.length!1 == xsl.length, "constraint: asl.length!1 == xsl.length"); |} |body |{ | import mir.ndslice.dynamic: reversed; | static assert(is(Unqual!C == C), msgWrongType); 1| if (ysl.empty) 0000000| return; 1| if (beta == 0) | { 1| ysl[] = 0; | } | else 0000000| if (beta == 1) | { 0000000| ysl[] *= beta; | } 1| if (xsl.empty) 0000000| return; | do | { 3| ysl.front += alpha * dot(asl.front, xsl); 3| asl.popFront; 3| ysl.popFront; | } 3| while (ysl.length); |} | |/// |unittest |{ | import mir.ndslice; | 1| auto a = slice!double(3, 5); 1| a[] = | [[-5, 1, 7, 7, -4], | [-1, -5, 6, 3, -3], | [-5, -2, -3, 6, 0]]; | 1| auto b = slice!double(5); 1| b[] = | [-5.0, | 4.0, | -4.0, | -1.0, | 9.0]; | 1| auto c = slice!double(3); | 1| gemv!(double, double, double)(1.0, a, b, 0.0, c); | 1| assert(c == | [-42.0, | -69.0, | 23.0]); |} source/mir/glas/l2.d is 80% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-random-2.2.16-mir-random-source-mir-random-engine-mersenne_twister.lst |/++ |The Mersenne Twister generator. | |Copyright: Copyright Andrei Alexandrescu 2008 - 2009, Ilya Yaroshenko 2016-. |License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). |Authors: $(HTTP erdani.org, Andrei Alexandrescu) Ilya Yaroshenko (rework) |+/ |module mir.random.engine.mersenne_twister; | |import std.traits; | |/++ |The $(LUCKY Mersenne Twister) generator. |+/ |struct MersenneTwisterEngine(UIntType, size_t w, size_t n, size_t m, size_t r, | UIntType a, size_t u, UIntType d, size_t s, | UIntType b, size_t t, | UIntType c, size_t l, UIntType f) | if (isUnsigned!UIntType) |{ | /// | enum isRandomEngine = true; | | static assert(0 < w && w <= UIntType.sizeof * 8); | static assert(1 <= m && m <= n); | static assert(0 <= r && 0 <= u && 0 <= s && 0 <= t && 0 <= l); | static assert(r <= w && u <= w && s <= w && t <= w && l <= w); | static assert(0 <= a && 0 <= b && 0 <= c); | | @disable this(); | @disable this(this); | | /// Largest generated value. | enum UIntType max = UIntType.max >> (UIntType.sizeof * 8u - w); | static assert(a <= max && b <= max && c <= max && f <= max); | | private enum UIntType lowerMask = (cast(UIntType) 1u << r) - 1; | private enum UIntType upperMask = ~lowerMask & max; | | /** | Parameters for the generator. | */ | enum size_t wordSize = w; | enum size_t stateSize = n; /// ditto | enum size_t shiftSize = m; /// ditto | enum size_t maskBits = r; /// ditto | enum UIntType xorMask = a; /// ditto | enum size_t temperingU = u; /// ditto | enum UIntType temperingD = d; /// ditto | enum size_t temperingS = s; /// ditto | enum UIntType temperingB = b; /// ditto | enum size_t temperingT = t; /// ditto | enum UIntType temperingC = c; /// ditto | enum size_t temperingL = l; /// ditto | enum UIntType initializationMultiplier = f; /// ditto | | | /// The default seed value. | enum UIntType defaultSeed = 5489; | | /++ | Current reversed payload index with initial value equals to `n-1` | +/ | size_t index = void; | | private UIntType _z = void; | | /++ | Reversed(!) payload. | +/ | UIntType[n] data = void; | | /* | * Marker indicating it's safe to construct from void | * (i.e. the constructor doesn't depend on the struct | * being in an initially valid state). | * Non-public because we don't want to commit to this | * design. | */ | package enum bool _isVoidInitOkay = true; | | /++ | Constructs a MersenneTwisterEngine object. | +/ 0000000| this(UIntType value) @safe pure nothrow @nogc | { | static if (max == UIntType.max) 0000000| data[$-1] = value; | else | data[$-1] = value & max; 0000000| foreach_reverse (size_t i, ref e; data[0 .. $-1]) | { 0000000| e = f * (data[i + 1] ^ (data[i + 1] >> (w - 2))) + cast(UIntType)(n - (i + 1)); | static if (max != UIntType.max) | e &= max; | } 0000000| index = n-1; 0000000| opCall(); | } | | /++ | Constructs a MersenneTwisterEngine object. | | Note that `MersenneTwisterEngine([123])` will not result in | the same initial state as `MersenneTwisterEngine(123)`. | +/ | this()(scope const(UIntType)[] array) @safe pure nothrow @nogc | { | static if (is(UIntType == uint)) | { | enum UIntType f2 = 1664525u; | enum UIntType f3 = 1566083941u; | } | else static if (is(UIntType == ulong)) | { | enum UIntType f2 = 3935559000370003845uL; | enum UIntType f3 = 2862933555777941757uL; | } | else | static assert(0, "init by slice only supported if UIntType is uint or ulong!"); | | data[$-1] = cast(UIntType) (19650218u & max); | foreach_reverse (size_t i, ref e; data[0 .. $-1]) | { | e = f * (data[i + 1] ^ (data[i + 1] >> (w - 2))) + cast(UIntType)(n - (i + 1)); | static if (max != UIntType.max) | e &= max; | } | index = n-1; | if (array.length == 0) | { | opCall(); | return; | } | | size_t final_mix_index = void; | | if (array.length >= n) | { | size_t j = 0; | //Handle all but tail. | while (array.length - j >= n - 1) | { | foreach_reverse (i, ref e; data[0 .. $-1]) | { | e = (e ^ ((data[i+1] ^ (data[i+1] >> (w - 2))) * f2)) | + array[j] + cast(UIntType) j; | static if (max != UIntType.max) | e &= max; | ++j; | } | data[$ - 1] = data[0]; | } | //Handle tail. | size_t i = n - 2; | while (j < array.length) | { | data[i] = (data[i] ^ ((data[i+1] ^ (data[i+1] >> (w - 2))) * f2)) | + array[j] + cast(UIntType) j; | static if (max != UIntType.max) | data[i] &= max; | ++j; | --i; | } | //Set the index for use by the next pass. | final_mix_index = i; | } | else | { | size_t i = n - 2; | //Handle all but tail. | while (i >= array.length) | { | foreach (j; 0 .. array.length) | { | data[i] = (data[i] ^ ((data[i+1] ^ (data[i+1] >> (w - 2))) * f2)) | + array[j] + cast(UIntType) j; | static if (max != UIntType.max) | data[i] &= max; | --i; | } | } | //Handle tail. | size_t j = 0; | while (i != cast(size_t) -1) | { | data[i] = (data[i] ^ ((data[i+1] ^ (data[i+1] >> (w - 2))) * f2)) | + array[j] + cast(UIntType) j; | static if (max != UIntType.max) | data[i] &= max; | ++j; | --i; | } | data[$ - 1] = data[0]; | i = n - 2; | data[i] = (data[i] ^ ((data[i+1] ^ (data[i+1] >> (w - 2))) * f2)) | + array[j] + cast(UIntType) j; | static if (max != UIntType.max) | data[i] &= max; | //Set the index for use by the next pass. | final_mix_index = n - 2; | } | | foreach_reverse (i, ref e; data[0 .. final_mix_index]) | { | e = (e ^ ((data[i+1] ^ (data[i+1] >> (w - 2))) * f3)) | - cast(UIntType)(n - (i + 1)); | static if (max != UIntType.max) | e &= max; | } | foreach_reverse (i, ref e; data[final_mix_index .. n-1]) | { | e = (e ^ ((data[i+1] ^ (data[i+1] >> (w - 2))) * f3)) | - cast(UIntType)(n - (i + 1)); | static if (max != UIntType.max) | e &= max; | } | data[$-1] = (cast(UIntType)1) << ((UIntType.sizeof * 8) - 1); /* MSB is 1; assuring non-zero initial array */ | opCall(); | } | | /++ | Advances the generator. | +/ | UIntType opCall() @safe pure nothrow @nogc | { | // This function blends two nominally independent | // processes: (i) calculation of the next random | // variate from the cached previous `data` entry | // `_z`, and (ii) updating `data[index]` and `_z` | // and advancing the `index` value to the next in | // sequence. | // | // By interweaving the steps involved in these | // procedures, rather than performing each of | // them separately in sequence, the variables | // are kept 'hot' in CPU registers, allowing | // for significantly faster performance. 0000000| sizediff_t index = this.index; 0000000| sizediff_t next = index - 1; 0000000| if(next < 0) 0000000| next = n - 1; 0000000| auto z = _z; 0000000| sizediff_t conj = index - m; 0000000| if(conj < 0) 0000000| conj = index - m + n; | static if (d == UIntType.max) 0000000| z ^= (z >> u); | else 0000000| z ^= (z >> u) & d; 0000000| auto q = data[index] & upperMask; 0000000| auto p = data[next] & lowerMask; 0000000| z ^= (z << s) & b; 0000000| auto y = q | p; 0000000| auto x = y >> 1; 0000000| z ^= (z << t) & c; 0000000| if (y & 1) 0000000| x ^= a; 0000000| auto e = data[conj] ^ x; 0000000| z ^= (z >> l); 0000000| _z = data[index] = e; 0000000| this.index = next; 0000000| return z; | } |} | |/++ |A $(D MersenneTwisterEngine) instantiated with the parameters of the |original engine $(HTTP en.wikipedia.org/wiki/Mersenne_Twister, |MT19937), generating uniformly-distributed 32-bit numbers with a |period of 2 to the power of 19937. | |This is recommended for random number generation on 32-bit systems |unless memory is severely restricted, in which case a |$(REF_ALTTEXT Xorshift, Xorshift, mir, random, engine, xorshift) |would be the generator of choice. |+/ |alias Mt19937 = MersenneTwisterEngine!(uint, 32, 624, 397, 31, | 0x9908b0df, 11, 0xffffffff, 7, | 0x9d2c5680, 15, | 0xefc60000, 18, 1812433253); | |/// |@safe version(mir_random_test) unittest |{ | import mir.random.engine; | | // bit-masking by generator maximum is necessary | // to handle 64-bit `unpredictableSeed` | auto gen = Mt19937(unpredictableSeed & Mt19937.max); | auto n = gen(); | | import std.traits; | static assert(is(ReturnType!gen == uint)); |} | |/++ |A $(D MersenneTwisterEngine) instantiated with the parameters of the |original engine $(HTTP en.wikipedia.org/wiki/Mersenne_Twister, |MT19937), generating uniformly-distributed 64-bit numbers with a |period of 2 to the power of 19937. | |This is recommended for random number generation on 64-bit systems |unless memory is severely restricted, in which case a |$(REF_ALTTEXT Xorshift, Xorshift, mir, random, engine, xorshift) |would be the generator of choice. |+/ |alias Mt19937_64 = MersenneTwisterEngine!(ulong, 64, 312, 156, 31, | 0xb5026f5aa96619e9, 29, 0x5555555555555555, 17, | 0x71d67fffeda60000, 37, | 0xfff7eee000000000, 43, 6364136223846793005); | |/// |@safe version(mir_random_test) unittest |{ | import mir.random.engine; | | auto gen = Mt19937_64(unpredictableSeed); | auto n = gen(); | | import std.traits; | static assert(is(ReturnType!gen == ulong)); |} | |@safe nothrow version(mir_random_test) unittest |{ | import mir.random.engine; | | static assert(isSaturatedRandomEngine!Mt19937); | static assert(isSaturatedRandomEngine!Mt19937_64); | auto gen = Mt19937(Mt19937.defaultSeed); | foreach(_; 0 .. 9999) | gen(); | assert(gen() == 4123659995); | | auto gen64 = Mt19937_64(Mt19937_64.defaultSeed); | foreach(_; 0 .. 9999) | gen64(); | assert(gen64() == 9981545732273789042uL); |} | |version(mir_random_test) unittest |{ | enum val = [1341017984, 62051482162767]; | alias MT(UIntType, uint w) = MersenneTwisterEngine!(UIntType, w, 624, 397, 31, | 0x9908b0df, 11, 0xffffffff, 7, | 0x9d2c5680, 15, | 0xefc60000, 18, 1812433253); | | import std.meta: AliasSeq; | foreach (i, R; AliasSeq!(MT!(ulong, 32), MT!(ulong, 48))) | { | static if (R.wordSize == 48) static assert(R.max == 0xFFFFFFFFFFFF); | auto a = R(R.defaultSeed); | foreach(_; 0..999) | a(); | assert(val[i] == a()); | } |} | |@safe nothrow @nogc version(mir_random_test) unittest |{ | //Verify that seeding with an array gives the same result as the reference | //implementation. | | //32-bit: www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/mt19937ar.tgz | immutable uint[4] seed32 = [0x123u, 0x234u, 0x345u, 0x456u]; | auto gen32 = Mt19937(seed32); | foreach(_; 0..999) | gen32(); | assert(3460025646u == gen32()); | | //64-bit: www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/mt19937-64.tgz | immutable ulong[4] seed64 = [0x12345uL, 0x23456uL, 0x34567uL, 0x45678uL]; | auto gen64 = Mt19937_64(seed64); | foreach(_; 0..999) | gen64(); | assert(994412663058993407uL == gen64()); |} ../../../.dub/packages/mir-random-2.2.16/mir-random/source/mir/random/engine/mersenne_twister.d is 0% covered <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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) | { | return true; | } | else | { | import mir.utility: min; | enum Y = min(slice.S, N); | foreach(i; Iota!Y) | if (slice._stride!i < 0) | slice = slice.reversed!i; | static if (N == 1) | return slice._stride!0 == 1; | else | static if (N == 2 && kind == Canonical) | { | return slice._stride!0 == slice.length!1; | } | else | { | import mir.series: series, sort; | import mir.ndslice.topology: zip, iota; | auto l = slice._lengths[0 .. Y]; | auto s = slice._strides[0 .. Y]; | s.series(l).sort!"a > b"; | return slice.shape.iota.strides == slice.strides; | } | } |} | |/// |version(mir_test) unittest |{ | import mir.ndslice.topology: iota; | | auto g = iota(2, 3); //contiguous | auto c = g.reversed!0; //canonical | auto u = g.transposed.allReversed; //universal | | assert(g.normalizeStructure); | assert(c.normalizeStructure); | assert(u.normalizeStructure); | | assert(c == g); | assert(u == g); | | c.popFront!1; | u.popFront!1; | | assert(!c.normalizeStructure); | 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; | auto slice = _slice.canonical; | } | else | { | import mir.ndslice.topology: universal; | 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; | 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) |{ | return slice.swapped!(0, 1); |} | |/// Template |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | | assert(iota(3, 4, 5, 6) | .swapped!(2, 1) | .shape == cast(size_t[4])[3, 5, 4, 6]); | | 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; | | assert(iota(3, 4, 5, 6) | .swapped(1, 2) | .shape == cast(size_t[4])[3, 5, 4, 6]); | | 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; | 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; | 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; | 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) |{ | return .rotated!(0, 1)(slice, k); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | auto slice = iota(2, 3); | | auto a = [[0, 1, 2], | [3, 4, 5]]; | | auto b = [[2, 5], | [1, 4], | [0, 3]]; | | auto c = [[5, 4, 3], | [2, 1, 0]]; | | auto d = [[3, 0], | [4, 1], | [5, 2]]; | | assert(slice.rotated ( 4) == a); | assert(slice.rotated!(0, 1)(-4) == a); | assert(slice.rotated (1, 0, 8) == a); | | assert(slice.rotated == b); | assert(slice.rotated!(0, 1)(-3) == b); | assert(slice.rotated (1, 0, 3) == b); | | assert(slice.rotated ( 6) == c); | assert(slice.rotated!(0, 1)( 2) == c); | assert(slice.rotated (0, 1, -2) == c); | | assert(slice.rotated ( 7) == d); | assert(slice.rotated!(0, 1)( 3) == d); | 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; | auto slice = _slice.universal; | } | with(slice) foreach (i; Iota!(N / 2)) | { | swap(_lengths[i], _lengths[N - i - 1]); | swap(_strides[i], _strides[N - i - 1]); | } | return slice; |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | 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) |{ | assert(dimensions.length <= N); | size_t[N] ctr; | uint[N] mask; | foreach (i, ref dimension; dimensions) | { | mask[dimension] = true; | ctr[i] = dimension; | } | size_t j = dimensions.length; | foreach (i, e; mask) | if (e == false) | ctr[j++] = i; | 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)]) | { | return _slice; | } | else | { | import core.lifetime: move; | 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; | auto slice = _slice.move.universal; | } | else | { | import mir.ndslice.topology: canonical; | 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; | auto slice = _slice.move.universal; | | mixin (DimensionsCountRTError); | foreach (dimension; dimensions) | mixin (DimensionRTError); | assert(dimensions.isValidPartialPermutation!(N), | "Failed to complete permutation of dimensions." | ~ tailErrorMessage!()); | immutable perm = completeTranspose!(N)(dimensions); | assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); | mixin (_transposedCode); |} | |///ditto |Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice) |{ | return .transposed!(1, 0)(slice); |} | |/// Template |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota; | | assert(iota(3, 4, 5, 6, 7) | .transposed!(3, 1, 0) | .shape == cast(size_t[5])[6, 4, 3, 5, 7]); | | 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; | | assert(iota(3, 4, 5, 6, 7) | .transposed(3, 1, 0) | .shape == cast(size_t[5])[6, 4, 3, 5, 7]); | | 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; | | assert(iota(3, 4, 5, 6, 7) | .transposed(3) | .shape == cast(size_t[5])[6, 3, 4, 5, 7]); | | 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; | 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; | auto slice = _slice.move.universal; | foreach (dimension; Iota!N) | { | mixin (_reversedCode); | } | return slice; |} | |/// |@safe @nogc pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.topology: iota, retro; | 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; | 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; | auto slice = _slice.universal; | } | else | { | import mir.ndslice.topology: canonical; | auto slice = _slice.canonical; | } | foreach (i, dimension; Dimensions) | { | mixin DimensionCTError; | mixin (_reversedCode); | } | 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; | auto slice = _slice.universal; | foreach (dimension; dimensions) | mixin (DimensionRTError); | foreach (i; Iota!(0, M)) | { | auto dimension = dimensions[i]; | mixin (_reversedCode); | } | return slice; |} | |/// ditto |auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) |{ | return .reversed!0(slice); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.topology: iota; | | auto slice = iota([2, 2], 1); | assert(slice == [[1, 2], [3, 4]]); | | // Default | assert(slice.reversed == [[3, 4], [1, 2]]); | | // Template | assert(slice.reversed! 0 == [[3, 4], [1, 2]]); | assert(slice.reversed! 1 == [[2, 1], [4, 3]]); | assert(slice.reversed!(0, 1) == [[4, 3], [2, 1]]); | assert(slice.reversed!(1, 0) == [[4, 3], [2, 1]]); | assert(slice.reversed!(1, 1) == [[1, 2], [3, 4]]); | assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]); | | // Function | assert(slice.reversed (0) == [[3, 4], [1, 2]]); | assert(slice.reversed (1) == [[2, 1], [4, 3]]); | assert(slice.reversed (0, 1) == [[4, 3], [2, 1]]); | assert(slice.reversed (1, 0) == [[4, 3], [2, 1]]); | assert(slice.reversed (1, 1) == [[1, 2], [3, 4]]); | assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.topology: iota, canonical; | auto slice = iota([2, 2], 1).canonical; | assert(slice == [[1, 2], [3, 4]]); | | // Template | assert(slice.reversed! 0 == [[3, 4], [1, 2]]); | assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]); | | // Function | assert(slice.reversed (0) == [[3, 4], [1, 2]]); | 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; | auto i0 = iota([4], 0); auto r0 = i0.retro; | auto i1 = iota([4], 4); auto r1 = i1.retro; | auto i2 = iota([4], 8); auto r2 = i2.retro; | auto slice = iota(3, 4).universal; | assert(slice .flattened.equal(concatenation(i0, i1, i2))); | // Template | assert(slice.reversed!(0) .flattened.equal(concatenation(i2, i1, i0))); | assert(slice.reversed!(1) .flattened.equal(concatenation(r0, r1, r2))); | assert(slice.reversed!(0, 1) .flattened.equal(concatenation(r2, r1, r0))); | assert(slice.reversed!(1, 0) .flattened.equal(concatenation(r2, r1, r0))); | assert(slice.reversed!(1, 1) .flattened.equal(concatenation(i0, i1, i2))); | assert(slice.reversed!(0, 0, 0).flattened.equal(concatenation(i2, i1, i0))); | // Function | assert(slice.reversed (0) .flattened.equal(concatenation(i2, i1, i0))); | assert(slice.reversed (1) .flattened.equal(concatenation(r0, r1, r2))); | assert(slice.reversed (0, 1) .flattened.equal(concatenation(r2, r1, r0))); | assert(slice.reversed (1, 0) .flattened.equal(concatenation(r2, r1, r0))); | assert(slice.reversed (1, 1) .flattened.equal(concatenation(i0, i1, i2))); | 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; | 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 | 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; | 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; | auto slice = _slice.universal; | } | else | { | import mir.ndslice.topology: canonical; | auto slice = _slice.canonical; | } | foreach (i, dimension; Dimensions) | { | mixin DimensionCTError; | immutable factor = factors[i]; | mixin (_stridedCode); | } | 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; | auto slice = _slice.universal; | mixin (DimensionRTError); | mixin (_stridedCode); | return slice; |} | |/// |pure nothrow version(mir_test) unittest |{ | import mir.ndslice.topology: iota; | auto slice = iota(3, 4); | | assert(slice | == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]); | | // Template | assert(slice.strided!0(2) | == [[0,1,2,3], [8,9,10,11]]); | | assert(slice.strided!1(3) | == [[0, 3], [4, 7], [8, 11]]); | | assert(slice.strided!(0, 1)(2, 3) | == [[0, 3], [8, 11]]); | | // Function | assert(slice.strided(0, 2) | == [[0,1,2,3], [8,9,10,11]]); | | assert(slice.strided(1, 3) | == [[0, 3], [4, 7], [8, 11]]); | | 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; | auto slice = iota(3, 4).canonical; | | assert(slice | == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]); | | // Template | assert(slice.strided!0(2) | == [[0,1,2,3], [8,9,10,11]]); | | // Function | 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; | auto i0 = iota([4], 0); auto s0 = stride(i0, 3); | auto i1 = iota([4], 4); auto s1 = stride(i1, 3); | auto i2 = iota([4], 8); auto s2 = stride(i2, 3); | auto slice = iota(3, 4).universal; | assert(slice .flattened.equal(concatenation(i0, i1, i2))); | // Template | assert(slice.strided!0(2) .flattened.equal(concatenation(i0, i2))); | assert(slice.strided!1(3) .flattened.equal(concatenation(s0, s1, s2))); | assert(slice.strided!(0, 1)(2, 3).flattened.equal(concatenation(s0, s2))); | // Function | assert(slice.strided(0, 2).flattened.equal(concatenation(i0, i2))); | assert(slice.strided(1, 3).flattened.equal(concatenation(s0, s1, s2))); | 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 |{ | size_t length = slice._lengths[0]; | foreach (i; Iota!(1, N)) | if (length > slice._lengths[i]) | length = slice._lengths[i]; | foreach (i; Iota!N) | slice._lengths[i] = length; | return slice; |} | |/// ditto |Slice!(Iterator, N, Canonical) dropToHypercube(Iterator, size_t N)(Slice!(Iterator, N) slice) | if (N > 1) |{ | import mir.ndslice.topology: canonical; | return slice.canonical.dropToHypercube; |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.topology: iota, canonical, universal; | | assert(iota(5, 3, 6, 7) | .dropToHypercube | .shape == cast(size_t[4])[3, 3, 3, 3]); | | assert(iota(5, 3, 6, 7) | .universal | .dropToHypercube | .shape == cast(size_t[4])[3, 3, 3, 3]); | | assert(4.iota.dropToHypercube == 4.iota); |} ../../../.dub/packages/mir-algorithm-3.10.60/mir-algorithm/source/mir/ndslice/dynamic.d has no code <<<<<< EOF # path=./..-..-..-.dub-packages-mir-algorithm-3.10.60-mir-algorithm-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) | { | 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; | auto slice = iota(2, 3).asKindOf!Universal; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | assert(slice._lengths == [2, 3]); | assert(slice._strides == [3, 1]); |} | |/// Canonical |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice: Canonical; | auto slice = iota(2, 3).asKindOf!Canonical; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | assert(slice._lengths == [2, 3]); | assert(slice._strides == [3]); |} | |/// Contiguous |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice: Contiguous; | auto slice = iota(2, 3).asKindOf!Contiguous; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | assert(slice._lengths == [2, 3]); | 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) | { | return slice; | } | else | static if (is(Iterator : RetroIterator!It, It)) | { | return slice.move.retro.universal.retro; | } | else | { | alias Ret = Slice!(Iterator, N, Universal, Labels); | size_t[Ret.N] lengths; | auto strides = sizediff_t[Ret.S].init; | foreach (i; Iota!(slice.N)) | lengths[i] = slice._lengths[i]; | static if (kind == Canonical) | { | foreach (i; Iota!(slice.S)) | strides[i] = slice._strides[i]; | strides[$-1] = 1; | } | else | { | ptrdiff_t ball = 1; | foreach_reverse (i; Iota!(Ret.S)) | { | strides[i] = ball; | static if (i) | ball *= slice._lengths[i]; | } | } | return Ret(lengths, strides, slice._iterator.move, slice._labels); | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | auto slice = iota(2, 3).universal; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | assert(slice._lengths == [2, 3]); | assert(slice._strides == [3, 1]); |} | |@safe pure nothrow |version(mir_test) unittest |{ | auto slice = iota(2, 3).canonical.universal; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | assert(slice._lengths == [2, 3]); | assert(slice._strides == [3, 1]); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | | auto dataframe = slice!(double, int, string)(2, 3); | dataframe.label[] = [1, 2]; | dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | | auto universaldf = dataframe.universal; | assert(universaldf._lengths == [2, 3]); | assert(universaldf._strides == [3, 1]); | | assert(is(typeof(universaldf) == | Slice!(double*, 2, Universal, int*, string*))); | assert(universaldf.label!0[0] == 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) | return slice; | else | { | alias Ret = typeof(return); | size_t[Ret.N] lengths; | auto strides = sizediff_t[Ret.S].init; | foreach (i; Iota!(slice.N)) | lengths[i] = slice._lengths[i]; | ptrdiff_t ball = 1; | foreach_reverse (i; Iota!(Ret.S)) | { | ball *= slice._lengths[i + 1]; | strides[i] = ball; | } | return Ret(lengths, strides, slice._iterator.move, slice._labels); | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | auto slice = iota(2, 3).canonical; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | assert(slice._lengths == [2, 3]); | assert(slice._strides == [3]); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | | auto dataframe = slice!(double, int, string)(2, 3); | dataframe.label[] = [1, 2]; | dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | | auto canonicaldf = dataframe.canonical; | assert(canonicaldf._lengths == [2, 3]); | assert(canonicaldf._strides == [3]); | | assert(is(typeof(canonicaldf) == | Slice!(double*, 2, Canonical, int*, string*))); | assert(canonicaldf.label!0[0] == 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) | return slice.canonical; | else | static if (kind == Canonical) | return slice; | else | { | import mir.utility: swap; | assert(slice._lengths[N - 1] <= 1 || slice._strides[N - 1] == 1); | typeof(return) ret; | ret._lengths = slice._lengths; | ret._strides = slice._strides[0 .. $ - 1]; | swap(ret._iterator, slice._iterator); | foreach(i, _; Labels) | swap(ret._labels[i], slice._labels[i]); | return ret; | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | auto slice = iota(2, 3).universal.assumeCanonical; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | assert(slice._lengths == [2, 3]); | assert(slice._strides == [3]); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.allocation: slice; | | auto dataframe = slice!(double, int, string)(2, 3); | dataframe.label[] = [1, 2]; | dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | | auto assmcanonicaldf = dataframe.assumeCanonical; | assert(assmcanonicaldf._lengths == [2, 3]); | assert(assmcanonicaldf._strides == [3]); | | assert(is(typeof(assmcanonicaldf) == | Slice!(double*, 2, Canonical, int*, string*))); | assert(assmcanonicaldf.label!0[0] == 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) | return slice; | else | { | import mir.utility: swap; | typeof(return) ret; | ret._lengths = slice._lengths; | swap(ret._iterator, slice._iterator); | foreach(i, _; Labels) | swap(ret._labels[i], slice._labels[i]); | return ret; | } |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | auto slice = iota(2, 3).universal.assumeContiguous; | assert(slice == [[0, 1, 2], [3, 4, 5]]); | 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; | | auto dataframe = slice!(double, int, string)(2, 3); | dataframe.label[] = [1, 2]; | dataframe.label!1[] = ["Label1", "Label2", "Label3"]; | | auto assmcontdf = dataframe.canonical.assumeContiguous; | assert(assmcontdf._lengths == [2, 3]); | static assert(assmcontdf._strides.length == 0); | | assert(is(typeof(assmcontdf) == | Slice!(double*, 2, Contiguous, int*, string*))); | assert(assmcontdf.label!0[0] == 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)) | { | assert(slice._lengths[i] == slice._lengths[0]); | slice._lengths[i] = slice._lengths[0]; | } |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | auto b = iota(5, 5); | | assumeHypercube(b); | | 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)) | { | assert(slices[i]._lengths == slices[0]._lengths); | slices[i]._lengths = slices[0]._lengths; | } |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | auto a = iota(5, 5); | auto b = iota(5, 5); | | assumeHypercube(a); // first use this one, if applicable | assumeSameShape(a, b); // | | assert(a == iota(5, 5)); | assert(b == iota(5, 5)); |} | |/++ |+/ |auto assumeFieldsHaveZeroShift(Iterator, size_t N, SliceKind kind) | (Slice!(Iterator, N, kind) slice) | if (__traits(hasMember, Iterator, "assumeFieldsHaveZeroShift")) |{ | 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; | return slice.move.ipack!(N - P); |} | |/// |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice: sliced, Slice; | | auto a = iota(3, 4, 5, 6); | auto b = a.pack!2; | | static immutable res1 = [3, 4]; | static immutable res2 = [5, 6]; | assert(b.shape == res1); | assert(b[0, 0].shape == res2); | assert(a == b.unpack); | 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; | auto sl = slice.move.universal; | static if (It.Element.kind == Contiguous) | 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 | 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; | | auto a = iota(3, 4, 5, 6); | auto b = a.ipack!2; | | static immutable res1 = [3, 4]; | static immutable res2 = [5, 6]; | assert(b.shape == res1); | assert(b[0, 0].shape == res2); | 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); | size_t[N + M] lengths; | auto strides = sizediff_t[Ret.S].init; | auto outerStrides = slice.strides; | auto innerStrides = Slice!(Iterator, M, innerKind)( | slice._iterator._structure, | slice._iterator._iterator, | ).strides; | foreach(i; Iota!N) | lengths[i] = slice._lengths[i]; | foreach(i; Iota!N) | strides[i] = outerStrides[i]; | foreach(i; Iota!M) | lengths[N + i] = slice._iterator._structure[0][i]; | foreach(i; Iota!(Ret.S - N)) | strides[N + i] = innerStrides[i]; | 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; | 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; | auto slice = iota(3, 4, 5, 6, 7, 8, 9, 10, 11).universal; | 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 |{ | auto a = iota(3, 4, 5, 6, 7, 8, 9, 10, 11); | auto b = a.pack!2.unpack; | static assert(is(typeof(a.canonical) == typeof(b))); | 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; | 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; | 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; | 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; | 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; | 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; | auto slice = iota(2, 3); | static immutable array = | [[0, 1, 2], | [3, 4, 5]]; | | assert(slice == array); | | static assert(is(DeepElementType!(typeof(slice)) == sizediff_t)); |} | |/// |pure nothrow @nogc |version(mir_test) unittest |{ | int[6] data; | auto slice = iota([2, 3], data.ptr); | assert(slice[0, 0] == data.ptr); | assert(slice[0, 1] == data.ptr + 1); | assert(slice[1, 0] == data.ptr + 3); |} | |/// |@safe pure nothrow @nogc |version(mir_test) unittest |{ | auto im = iota([10, 5], 100); | assert(im[2, 1] == 111); // 100 + 2 * 5 + 1 | | //slicing works correctly | auto cm = im[1 .. $, 3 .. $]; | assert(cm[2, 1] == 119); // 119 = 100 + (1 + 2) * 5 + (3 + 1) |} | |/// `iota` with step |@safe pure nothrow version(mir_test) unittest |{ | auto sl = iota([2, 3], 10, 10); | | 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); | size_t[Ret.N] lengths; | auto strides = sizediff_t[Ret.S].init; | lengths[0] = slice._lengths[0]; | foreach (i; Iota!(1, N)) | if (lengths[0] > slice._lengths[i]) | lengths[0] = slice._lengths[i]; | foreach (i; Iota!(1, Ret.N)) | lengths[i] = slice._lengths[i + N - 1]; | auto rstrides = slice.strides; | strides[0] = rstrides[0]; | foreach (i; Iota!(1, N)) | strides[0] += rstrides[i]; | foreach (i; Iota!(1, Ret.S)) | strides[i] = rstrides[i + N - 1]; | 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]; | assert(iota(2, 3).diagonal == d); |} | |/// Non-square matrix |@safe pure nothrow version(mir_test) unittest |{ | // ------- | // | 0 1 | | // | 2 3 | | // | 4 5 | | // ------- | //-> | // | 0 3 | | | 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; | | auto slice = slice!int(3, 3); | int i; | foreach (ref e; slice.diagonal) | e = ++i; | 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]; | auto a = iota(2, 3).canonical; | a.popFront!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]; | 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]; | auto a = iota(2, 2, 3).canonical; | a.popFront!2; | 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]]; | | auto slice = iota(3, 3, 3) | .pack!2 | .evertPack | .diagonal | .evertPack; | | 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; | 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; | 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]; | 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 |{ | foreach (i, length; rlengths_) | assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" | ~ tailErrorMessage!()); |} |do |{ | size_t[N] lengths; | size_t[N] rlengths = rlengths_; | sizediff_t[N] strides; | foreach (dimension; Iota!N) | lengths[dimension] = slice._lengths[dimension] / rlengths[dimension]; | auto rstrides = slice.strides; | foreach (i; Iota!N) | { | strides[i] = rstrides[i]; | if (lengths[i]) //do not remove `if (...)` | strides[i] *= rlengths[i]; | } | 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; | auto slice = slice!int(5, 8); | auto blocks = slice.blocks(2, 3); | int i; | foreach (blocksRaw; blocks) | foreach (block; blocksRaw) | block[] = ++i; | | 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]]]]); | | 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; | auto slice = slice!int(5, 8); | auto blocks = slice.blocks(2, 3); | auto diagonalBlocks = blocks.diagonal.unpack; | | diagonalBlocks[0][] = 1; | diagonalBlocks[1][] = 2; | | assert(diagonalBlocks == | [[[1, 1, 1], [1, 1, 1]], | [[2, 2, 2], [2, 2, 2]]]); | | 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]]]]); | | 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; | auto slice = slice!int(5, 13); | auto blocks = slice | .pack!1 | .evertPack | .blocks(3) | .unpack; | | int i; | foreach (block; blocks) | block[] = ++i; | | 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 |{ | foreach (i, length; rlengths) | assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" | ~ tailErrorMessage!()); |} |do |{ | size_t[N] rls = rlengths; | size_t[N] lengths; | foreach (dimension; Iota!N) | lengths[dimension] = slice._lengths[dimension] >= rls[dimension] ? | slice._lengths[dimension] - rls[dimension] + 1 : 0; | auto rstrides = slice.strides; | static if (typeof(return).DeepElement.S) | return typeof(return)( | lengths, | rstrides, | typeof(return).Iterator( | rls, | rstrides[0 .. typeof(return).DeepElement.S], | slice._iterator)); | else | 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; | auto slice = slice!int(5, 8); | auto windows = slice.windows(2, 3); | | int i; | foreach (windowsRaw; windows) | foreach (window; windowsRaw) | ++window[]; | | 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; | auto slice = slice!int(5, 8); | auto windows = slice.windows(2, 3); | windows[1, 2][] = 1; | windows[1, 2][0, 1] += 1; | windows.unpack[1, 2, 0, 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; | auto slice = slice!int(8, 8); | auto windows = slice.windows(3, 3); | | auto multidiagonal = windows | .diagonal | .unpack; | foreach (window; multidiagonal) | window[] += 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; | auto slice = slice!int(5, 8); | auto windows = slice | .pack!1 | .evertPack | .windows(3) | .unpack; | | foreach (window; windows) | window[] += 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; | | auto overlappingBlocks = iota(5, 5) | .windows(3, 3) | .universal | .strided!(0, 1)(2, 2); | | 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 |{ | auto w = iota(9, 9).windows(3, 3); | 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); | auto structure = Ret._Structure.init; | alias lengths = structure[0]; | foreach (i; Iota!M) | lengths[i] = rlengths[i]; | | /// Code size optimization | immutable size_t eco = slice.elementCount; | size_t ecn = lengths[0 .. rlengths.length].iota.elementCount; | if (eco == 0) | { | err = ReshapeError.empty; | goto R; | } | foreach (i; Iota!M) | if (lengths[i] == -1) | { | ecn = -ecn; | lengths[i] = eco / ecn; | ecn *= lengths[i]; | break; | } | if (eco != ecn) | { | err = ReshapeError.total; | goto R; | } | static if (kind == Universal) | { | for (size_t oi, ni, oj, nj; oi < N && ni < M; oi = oj, ni = nj) | { | size_t op = slice._lengths[oj++]; | size_t np = lengths[nj++]; | | for (;;) | { | if (op < np) | op *= slice._lengths[oj++]; | if (op > np) | np *= lengths[nj++]; | if (op == np) | break; | } | while (oj < N && slice._lengths[oj] == 1) oj++; | while (nj < M && lengths[nj] == 1) nj++; | | for (size_t l = oi, r = oi + 1; r < oj; r++) | if (slice._lengths[r] != 1) | { | if (slice._strides[l] != slice._lengths[r] * slice._strides[r]) | { | err = ReshapeError.incompatible; | goto R; | } | l = r; | } | assert((oi == N) == (ni == M)); | | structure[1][nj - 1] = slice._strides[oj - 1]; | foreach_reverse (i; ni .. nj - 1) | 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]; | err = 0; | return Ret(structure, slice._iterator); | R: | return Ret(structure, slice._iterator.init); | } |} | |/// |@safe nothrow pure |version(mir_test) unittest |{ | import mir.ndslice.dynamic : allReversed; | int err; | auto slice = iota(3, 4) | .universal | .allReversed | .reshape([-1, 3], err); | assert(err == 0); | 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) | { | int err; | // Tries to reshape without allocation | auto ret = sl.reshape(lengths, err); | if (!err) | return ret; | if (err == ReshapeError.incompatible) | // allocates, flattens, reshapes with `sliced`, converts to universal kind | return sl.slice.flattened.sliced(cast(size_t[M])lengths).universal; | throw new Exception("total elements count is different or equals to zero"); | } | | auto sl = iota!int(3, 4) | .slice | .universal | .reversed!0; | | 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; | auto slice = iota(1, 1, 3, 2, 1, 2, 1).universal.allReversed; | int err; | assert(slice.reshape([1, -1, 1, 1, 3, 1], err) == | [[[[[[11], [10], [9]]]], | [[[[ 8], [ 7], [6]]]], | [[[[ 5], [ 4], [3]]]], | [[[[ 2], [ 1], [0]]]]]]); | assert(err == 0); |} | |// Issue 15919 |nothrow @nogc @safe pure |version(mir_test) unittest |{ | int err; | assert(iota(3, 4, 5, 6, 7).pack!2.reshape([4, 3, 5], err)[0, 0, 0].shape == cast(size_t[2])[6, 7]); | assert(err == 0); |} | |nothrow @nogc @safe pure version(mir_test) unittest |{ | import mir.ndslice.slice; | | int err; | auto e = iota(1); | // resize to the wrong dimension | auto s = e.reshape([2], err); | assert(err == ReshapeError.total); | e.popFront; | // test with an empty slice | e.reshape([1], err); | assert(err == ReshapeError.empty); |} | |nothrow @nogc @safe pure |version(mir_test) unittest |{ | auto pElements = iota(3, 4, 5, 6, 7) | .pack!2 | .flattened; | assert(pElements[0][0] == iota(7)); | 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; | size_t[typeof(return).N] lengths; | sizediff_t[typeof(return)._iterator._indices.length] indices; | lengths[0] = slice.elementCount; | 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) | { | return slice; | } | else | { | import core.lifetime: move; 0000000| size_t[typeof(return).N] lengths; 0000000| lengths[0] = slice.elementCount; 0000000| return typeof(return)(lengths, slice._iterator.move); | } |} | |/// ditto |Slice!(StrideIterator!Iterator) | flattened | (Iterator) | (Slice!(Iterator, 1, Universal) slice) |{ | import core.lifetime: move; | return slice.move.hideStride; |} | |version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | auto sl1 = iota(2, 3).slice.universal.pack!1.flattened; | auto sl2 = iota(2, 3).slice.canonical.pack!1.flattened; | auto sl3 = iota(2, 3).slice.pack!1.flattened; |} | |/// Regular slice |@safe @nogc pure nothrow version(mir_test) unittest |{ | assert(iota(4, 5).flattened == iota(20)); | assert(iota(4, 5).canonical.flattened == iota(20)); | assert(iota(4, 5).universal.flattened == iota(20)); |} | |@safe @nogc pure nothrow version(mir_test) unittest |{ | assert(iota(4).flattened == iota(4)); | assert(iota(4).canonical.flattened == iota(4)); | 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; | assert(iota(3, 4, 5, 6, 7).pack!2.flattened[1] == iota([6, 7], 6 * 7)); |} | |/// Properties |@safe pure nothrow version(mir_test) unittest |{ | auto elems = iota(3, 4).universal.flattened; | | elems.popFrontExactly(2); | assert(elems.front == 2); | /// `_index` is available only for canonical and universal ndslices. | assert(elems._iterator._indices == [0, 2]); | | elems.popBackExactly(2); | assert(elems.back == 9); | assert(elems.length == 8); |} | |/// Index property |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice; | auto slice = new long[20].sliced(5, 4); | | for (auto elems = slice.universal.flattened; !elems.empty; elems.popFront) | { | ptrdiff_t[2] index = elems._iterator._indices; | elems.front = index[0] * 10 + index[1] * 3; | } | 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 |{ | auto elems = iota(3, 4).universal.flattened; | assert(elems.front == 0); | assert(elems.save[1] == 1); |} | |/++ |Random access and slicing |+/ |nothrow version(mir_test) unittest |{ | import mir.ndslice.allocation: slice; | import mir.ndslice.slice: sliced; | | auto elems = iota(4, 5).slice.flattened; | | elems = elems[11 .. $ - 2]; | | assert(elems.length == 7); | assert(elems.front == 11); | assert(elems.back == 17); | | foreach (i; 0 .. 7) | assert(elems[i] == i + 11); | | // assign an element | elems[2 .. 6] = -1; | assert(elems[2 .. 6] == repeat(-1, 4)); | | // assign an array | static ar = [-1, -2, -3, -4]; | elems[2 .. 6] = ar; | assert(elems[2 .. 6] == ar); | | // assign a slice | ar[] *= 2; | auto sl = ar.sliced(ar.length); | elems[2 .. 6] = sl; | assert(elems[2 .. 6] == sl); |} | |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.dynamic : allReversed; | | auto slice = iota(3, 4, 5); | | foreach (ref e; slice.universal.flattened.retro) | { | //... | } | | foreach_reverse (ref e; slice.universal.flattened) | { | //... | } | | foreach (ref e; slice.universal.allReversed.flattened) | { | //... | } |} | |@safe @nogc pure nothrow version(mir_test) unittest |{ | import std.range.primitives : isRandomAccessRange, hasSlicing; | 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; | auto elems = iota(4, 5).universal.everted.flattened; | static assert(isRandomAccessRange!(typeof(elems))); | | elems = elems[11 .. $ - 2]; | auto elems2 = elems; | foreach (i; 0 .. 7) | { | assert(elems[i] == elems2.front); | elems2.popFront; | } |} | |@safe @nogc pure nothrow version(mir_test) unittest |{ | import mir.ndslice.slice; | import mir.ndslice.dynamic; | import std.range.primitives : isRandomAccessRange, hasLength; | | auto range = (3 * 4 * 5 * 6 * 7).iota; | auto slice0 = range.sliced(3, 4, 5, 6, 7).universal; | auto slice1 = slice0.transposed!(2, 1).pack!2; | auto elems0 = slice0.flattened; | auto elems1 = slice1.flattened; | | foreach (S; AliasSeq!(typeof(elems0), typeof(elems1))) | { | static assert(isRandomAccessRange!S); | static assert(hasLength!S); | } | | assert(elems0.length == slice0.elementCount); | assert(elems1.length == 5 * 4 * 3); | | auto elems2 = elems1; | foreach (q; slice1) | foreach (w; q) | foreach (e; w) | { | assert(!elems2.empty); | assert(e == elems2.front); | elems2.popFront; | } | assert(elems2.empty); | | elems0.popFront(); | elems0.popFrontExactly(slice0.elementCount - 14); | assert(elems0.length == 13); | assert(elems0 == range[slice0.elementCount - 13 .. slice0.elementCount]); | | 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 |{ | auto s = iota(3, 4).flattened; | foreach (_; 0 .. s.length) | 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) |{ | return FieldIterator!(ndIotaField!N)(0, ndIotaField!N(lengths[1 .. $])).sliced(lengths); |} | |/// |@safe pure nothrow @nogc version(mir_test) unittest |{ | auto slice = ndiota(2, 3); | static immutable array = | [[[0, 0], [0, 1], [0, 2]], | [[1, 0], [1, 1], [1, 2]]]; | | assert(slice == array); |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | auto im = ndiota(7, 9); | | assert(im[2, 1] == [2, 1]); | | //slicing works correctly | auto cm = im[1 .. $, 4 .. $]; | assert(cm[2, 1] == [3, 5]); |} | |version(mir_test) unittest |{ | auto r = ndiota(1); | auto d = r.front; | 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)) |{ 0000000| Repeat!(N, LinspaceField!T) fields; | foreach(i; Iota!N) | { 0000000| assert(lengths[i] > 1, "linspace: all lengths must be greater then 1."); 0000000| fields[i] = LinspaceField!T(lengths[i], intervals[i][0], intervals[i][1]); | } | static if (N == 1) 0000000| return slicedField(fields); | else | return cartesian(fields); |} | |// example from readme |version(mir_test) unittest |{ | import mir.ndslice; | // import std.stdio: writefln; | | enum fmt = "%(%(%.2f %)\n%)\n"; | | auto a = magic(5).as!float; | // writefln(fmt, a); | | auto b = linspace!float([5, 5], [1f, 2f], [0f, 1f]).map!"a * a + b"; | // writefln(fmt, b); | | auto c = slice!float(5, 5); | c[] = transposed(a + b / 2); |} | |/// 1D |@safe pure nothrow |version(mir_test) unittest |{ | auto s = linspace!double([5], [1.0, 2.0]); | assert(s == [1.0, 1.25, 1.5, 1.75, 2.0]); | | // reverse order | assert(linspace!double([5], [2.0, 1.0]) == s.retro); | | // remove endpoint | s.popBack; | assert(s == [1.0, 1.25, 1.5, 1.75]); |} | |/// 2D |@safe pure nothrow |version(mir_test) unittest |{ | import mir.functional: refTuple; | | auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]); | | 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)], | ]); | | 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 |{ | auto s = linspace!cdouble([3], [1.0 + 0i, 2.0 + 4i]); | 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) |{ | size_t[M] ls = lengths; | 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; | size_t[M] ls = lengths; | return typeof(return)( | ls, | sizediff_t[M].init, | typeof(return).Iterator( | slice._structure, | move(slice._iterator))); |} | |/// |@safe pure nothrow |version(mir_test) unittest |{ | auto sl = iota(3).repeat(4); | 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; | | auto sl = iota(3) | .repeat(4) | .unpack | .universal | .transposed; | | 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; | | auto sl = iota([3], 6).slice; | auto slC = sl.repeat(2, 3); | sl[1] = 4; | 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; | | auto sl = repeat(4.0, 2, 3); | assert(sl == [[4.0, 4.0, 4.0], | [4.0, 4.0, 4.0]]); | | static assert(is(DeepElementType!(typeof(sl)) == double)); | | sl[1, 1] = 3; | 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) |{ | assert(slice.length); | static if (kind == Universal) | return slice.hideStride.cycle(length); | else | 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); | assert(loopLength <= slice.length); | static if (kind == Universal) | return slice.hideStride.cycle!loopLength(length); | else | return CycleField!(Iterator, loopLength)(slice._iterator).slicedField(length); |} | |/// ditto |auto cycle(T)(T[] array, size_t length) |{ | return cycle(array.sliced, length); |} | |/// ditto |auto cycle(size_t loopLength, T)(T[] array, size_t length) |{ | 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 |{ | auto slice = iota(3); | assert(slice.cycle(7) == [0, 1, 2, 0, 1, 2, 0]); | assert(slice.cycle!2(7) == [0, 1, 0, 1, 0, 1, 0]); | assert([0, 1, 2].cycle(7) == [0, 1, 2, 0, 1, 2, 0]); | 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 |{ | assert (factor > 0, "factor must be positive."); |} |do |{ | static if (kind == Contiguous) | return slice.universal.stride(factor); | else | { | import mir.ndslice.dynamic: strided; | 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) | { | return stride(slice.move.ipack!1.map!(.stride!factor)); | } | else | static if (kind == Contiguous) | { | immutable rem = slice._lengths[0] % factor; | slice._lengths[0] /= factor; | if (rem) | slice._lengths[0]++; | return Slice!(StrideIterator!(Iterator, factor), 1, kind)(slice._structure, StrideIterator!(Iterator, factor)(move(slice._iterator))); | } | else | { | 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 |{ | auto slice = iota(6); | static immutable str = [0, 2, 4]; | assert(slice.stride(2) == str); // runtime factor | assert(slice.stride!2 == str); // compile time factor | assert(slice.stride == str); // default compile time factor is 2 | assert(slice.universal.stride(2) == str); |} | |/// ND-compile time |@safe pure nothrow @nogc version(mir_test) unittest |{ | auto slice = iota(4, 6); | static immutable str = [[0, 2, 4], [12, 14, 16]]; | assert(slice.stride!2 == str); // compile time factor | 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) | { 0000000| size_t[slice.N] lengths; | foreach (i; Iota!(slice.N)) 0000000| lengths[i] = slice._lengths[i]; | static if (slice.S) | { | sizediff_t[slice.S] strides; | foreach (i; Iota!(slice.S)) | 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); | slice._iterator._iterator -= slice.lastIndex; | return Ret(structure, slice._iterator._iterator.move); | } | else | { | alias Ret = Slice!(RetroIterator!Iterator, N, kind); 0000000| slice._iterator += slice.lastIndex; 0000000| return Ret(structure, RetroIterator!Iterator(slice._iterator.move)); | } | } | else | { | import mir.ndslice.dynamic: allReversed; | return slice.move.allReversed; | } |} | |/// ditto |auto retro(T)(T[] array) |{ | 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 | { | 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 | { | bool empty()() const { return _source.empty; } | static if (hasLength!Range) | auto length()() const { return _source.length; } | 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; | } | } | } | | 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 |{ | auto slice = iota(2, 3); | static immutable reversed = [[5, 4, 3], [2, 1, 0]]; | assert(slice.retro == reversed); | assert(slice.canonical.retro == reversed); | 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; | | 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) | { | 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); | auto structure_ = Ret._Structure.init; | foreach(i; Iota!(Ret.N)) | structure_[0][i] = slice._lengths[i]; | structure_[0][$ - 1] *= I.sizeof * 8; | foreach(i; Iota!(Ret.S)) | structure_[1][i] = slice._strides[i]; | static if (simplified) | return Ret(structure_, It(slice._iterator._index * I.sizeof * 8, BitField!Field(slice._iterator._field.move))); | else | return Ret(structure_, It(0, BitField!Iterator(slice._iterator.move))); | } |} | |/// ditto |auto bitwise(T)(T[] array) |{ | 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 |{ | size_t[10] data; | auto bits = data[].bitwise; | assert(bits.length == data.length * size_t.sizeof * 8); | bits[111] = true; | assert(bits[111]); | | bits.popFront; | assert(bits[110]); | bits[] = true; | bits[110] = false; | bits = bits[10 .. $]; | assert(bits[100] == false); |} | |@safe pure nothrow @nogc |version(mir_test) unittest |{ | size_t[10] data; | auto slice = FieldIterator!(size_t[])(0, data[]).sliced(10); | slice.popFrontExactly(2); | auto bits_normal = data[].sliced.bitwise; | auto bits = slice.bitwise; | assert(bits.length == (data.length - 2) * size_t.sizeof * 8); | bits[111] = true; | assert(bits[111]); | assert(bits_normal[111 + size_t.sizeof * 2 * 8]); | auto ubits = slice.universal.bitwise; | assert(bits.map!"~a" == bits.map!"!a"); | static assert (is(typeof(bits.map!"~a") == typeof(bits.map!"!a"))); | assert(bits.map!"~a" == bits.map!"!!!a"); | static assert (!is(typeof(bits.map!"~a") == typeof(bits.map!"!!!a"))); | assert(bits == ubits); | | bits.popFront; | assert(bits[110]); | bits[] = true; | bits[110] = false; | bits = bits[10 .. $]; | 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; | 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); | auto structure = Ret._Structure.init; | foreach(i; Iota!(Ret.N)) | structure[0][i] = slice._lengths[i]; | structure[0][$ - 1] *= I.sizeof * 8; | 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 | return Ret(structure, It(0, BitpackField!(Iterator, pack)(slice._iterator.move))); |} | |/// ditto |auto bitpack(size_t pack, T)(T[] array) |{ | 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 |{ | size_t[10] data; | // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. | auto packs = data[].bitpack!6; | assert(packs.length == data.length * size_t.sizeof * 8 / 6); | packs[$ - 1] = 24; | assert(packs[$ - 1] == 24); | | packs.popFront; | 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; | auto structure = slice._structure; | structure[0][$ - 1] /= group; | return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator.move)); |} | |/// ditto |auto bytegroup(size_t pack, DestinationType, T)(T[] array) |{ | 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; | | ubyte[20] data; | // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. | auto int24ar = data[].bytegroup!(3, int); // 24 bit integers | assert(int24ar.length == data.length / 3); | | enum checkInt = ((1 << 20) - 1); | | int24ar[3] = checkInt; | assert(int24ar[3] == checkInt); | | int24ar.popFront; | 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; | ushort[20] data; | // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. | auto int48ar = data[].sliced.bytegroup!(3, long); // 48 bit integers | assert(int48ar.length == data.length / 3); | | enum checkInt = ((1L << 44) - 1); | | int48ar[3] = checkInt; | assert(int48ar[3] == checkInt); | | int48ar.popFront; | 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 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."); | return Slice!(MIterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator.move)); | } | | /// ditto | auto map(T)(T[] array) | { | 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 std.range.primitives: isInputRange; | static assert (isInputRange!Range, "map can work with ndslice, array, or an input range."); | 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 | { | return _input.empty; | } | } | | void popFront() | { | assert(!empty, "Attempting to popFront an empty map."); | _input.popFront(); | } | | auto ref front() @property | { | assert(!empty, "Attempting to fetch the front of an empty map."); | 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; | auto s = iota(2, 3).map!(a => a * 3); | assert(s == [[ 0, 3, 6], | [ 9, 12, 15]]); |} | |/// String lambdas |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota; | 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; | 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 || ------ | // ------- | --- --- | | // -------------- | auto s = iota(2, 3) | .windows(2, 2) | .map!sum; | | 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 | auto sl1 = iota(2, 3); | // 1 2 3 | // 4 5 6 | auto sl2 = iota([2, 3], 1); | | auto z = zip(sl1, sl2); | | assert(zip(sl1, sl2).map!"a + b" == sl1 + sl2); | 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; | | auto sl = iota(2, 3); | auto s = sl.map!("a + a", "a * a"); | | auto sums = [[0, 2, 4], [6, 8, 10]]; | auto products = [[0, 1, 4], [9, 16, 25]]; | | assert(s.map!"a[0]" == sl + sl); | 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"; | 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; | auto a = iota(2, 3).map!"a + 10".map!(pipe!("a * 2", "a + 1")); | auto b = iota(2, 3).map!(pipe!("a + 10", "a * 2", "a + 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; | | 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. | assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); | assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); | assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); | 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; | | auto x = [1, 2, 3].sliced; | auto y = [1, 2].sliced; | | auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; | assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); | auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; | assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); | auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; | assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); | auto s4 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!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) { | return reduce!fmax(0.0, matrix.alongDim!1.map!mean); | } | // 1 2 | // 3 4 | auto matrix = iota([2, 2], 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 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); | 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 { | double factor; this(double f) { factor = f; } | auto opCall(long x) const {return x * factor; } | auto lightConst()() const @property { return Mul(factor); } | } | | auto callable = Mul(3); | auto s = iota(2, 3).vmap(callable); | | 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; | this(double f) {factor = f;} | auto opCall(S)(S x) { return x.sum * factor; } | | auto lightConst()() const @property { return Callable(factor); } | auto lightImmutable()() immutable @property { return Callable(factor); } | } | | auto callable = Callable(0.5); | | auto s = iota(2, 3) | .windows(2, 2) | .vmap(callable); | | assert(s == [[4, 6]]); |} | |/// Zipped tensors |@safe pure nothrow |version(mir_test) unittest |{ | import mir.ndslice.topology : iota, zip; | | struct Callable | { | double factor; | this(double f) {factor = f;} | auto opCall(S, T)(S x, T y) { return x + y * factor; } | | auto lightConst()() const { return Callable(factor); } | auto lightImmutable()() immutable { return Callable(factor); } | } | | auto callable = Callable(10); | | // 0 1 2 | // 3 4 5 | auto sl1 = iota(2, 3); | // 1 2 3 | // 4 5 6 | auto sl2 = iota([2, 3], 1); | | auto z = zip(sl1, sl2); | | 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 map 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; | | 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; | this(double f) {factor = f;} | auto opCall(U)(U x) const {return x.mean + factor; } | auto lightConst()() const @property { return Callable(factor); } | } | | auto callable = Callable(0.0); | | // Use byDim/alongDim with map to compute callable of row/column. | assert(x.byDim!0.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); | assert(x.byDim!1.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); | assert(x.alongDim!1.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); | assert(x.alongDim!0.vmap(callable).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, alongDim, map; | import mir.ndslice.fuse: fuse; | import mir.ndslice.slice: sliced; | | static struct Mul(T) | { | T factor; | this(T f) { factor = f; } | auto opCall(U)(U x) {return x * factor; } | auto lightConst()() const @property { return Mul!(typeof(factor.lightConst))(factor.lightConst); } | } | | auto a = [1, 2, 3].sliced; | auto b = [1, 2].sliced; | auto A = Mul!(typeof(a))(a); | auto B = Mul!(typeof(b))(b); | | auto x = [ | [0, 1, 2], | [3, 4, 5] | ].fuse; | | auto s1 = x.byDim!0.vmap(A).fuse; | assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); | auto s2 = x.byDim!1.vmap(B).fuse!1; | assert(s2 == [[ 0, 1, 2], | [ 6, 8, 10]]); | auto s3 = x.alongDim!1.vmap(A).fuse; | assert(s1 == [[ 0, 2, 6], | [ 3, 8, 15]]); | auto s4 = x.alongDim!0.vmap(B).fuse!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) | return Slice!(StrideIterator!Iterator)( | slice._lengths, | StrideIterator!Iterator(slice._strides[0], move(slice._iterator))); | else | 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 | return slice; |} | |/++ |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, | ) |{ | assert(original.shape == caches.shape, "caches.shape should be equal to original.shape"); | assert(original.shape == flags.shape, "flags.shape should be equal to original.shape"); | 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; | | int[] funCalls; | | auto v = 5.iota!int | .map!((i) { | funCalls ~= i; | return 2 ^^ i; | }); | auto flags = v.length.bitSlice; | auto cache = v.length.uninitSlice!int; | // cached lazy slice: 1 2 4 8 16 | auto sl = v.cached(cache, flags); | | assert(funCalls == []); | assert(sl[1] == 2); // remember result | assert(funCalls == [1]); | assert(sl[1] == 2); // reuse result | assert(funCalls == [1]); | | assert(sl[0] == 1); | assert(funCalls == [1, 0]); | funCalls = []; | | // set values directly | sl[1 .. 3] = 5; | assert(sl[1] == 5); | assert(sl[2] == 5); | // no function calls | 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; | | int[] funCalls; | | auto v = 5.iota!int | .map!((i) { | funCalls ~= i; | return 2 ^^ i; | }) | .as!(immutable int); | auto flags = v.length.bitSlice; | auto cache = v.length.uninitSlice!(immutable int); | | // cached lazy slice: 1 2 4 8 16 | auto sl = v.cached(cache, flags); | | static assert(is(DeepElementType!(typeof(sl)) == immutable int)); | | assert(funCalls == []); | assert(sl[1] == 2); // remember result | assert(funCalls == [1]); | assert(sl[1] == 2); // reuse result | assert(funCalls == [1]); | | assert(sl[0] == 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; | 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; | | int[] funCalls; | | // cached lazy slice: 1 2 4 8 16 | auto sl = 5.iota!int | .map!((i) { | funCalls ~= i; | return 2 ^^ i; | }) | .cachedGC; | | assert(funCalls == []); | assert(sl[1] == 2); // remember result | assert(funCalls == [1]); | assert(sl[1] == 2); // reuse result | assert(funCalls == [1]); | | assert(sl[0] == 1); | assert(funCalls == [1, 0]); | funCalls = []; | | // set values directly | sl[1 .. 3] = 5; | assert(sl[1] == 5); | assert(sl[2] == 5); | // no function calls | 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; | | int[] funCalls; | | // cached lazy slice: 1 2 4 8 16 | auto sl = 5.iota!int | .map!((i) { | funCalls ~= i; | return 2 ^^ i; | }) | .as!(immutable int) | .cachedGC; | | static assert(is(DeepElementType!(typeof(sl)) == immutable int)); | | assert(funCalls == []); | assert(sl[1] == 2); // remember result | assert(funCalls == [1]); | assert(sl[1] == 2); // reuse result | assert(funCalls == [1]); | | assert(sl[0] == 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)) | return slice; | else | static if (is(Iterator : T*)) | return slice.toConst; | else | { | import core.lifetime: move; | import mir.conv: to; | return map!(to!T)(slice.move); | } | } | | /// ditto | auto as(S)(S[] array) | { | 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; | 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; | | auto matrix = slice!double([2, 2], 0); | auto stringMatrixView = matrix.as!int; | assert(stringMatrixView == | [[0, 0], | [0, 0]]); | | matrix.diagonal[] = 1; | assert(stringMatrixView == | [[1, 0], | [0, 1]]); | | /// allocate new slice composed of strings | 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; | | Slice!(double*, 2) matrix = slice!double([2, 2], 0); | Slice!(const(double)*, 2) const_matrix = matrix.as!(const double); |} | |/// Ranges |@safe pure nothrow version(mir_test) unittest |{ | import mir.algorithm.iteration: filter, equal; | 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; | return typeof(return)( | indices._structure, | IndexIterator!(Iterator, Field)( | indices._iterator.move, | source)); |} | |/// ditto |auto indexed(Field, S)(Field source, S[] indices) |{ | 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 |{ | auto source = [1, 2, 3, 4, 5]; | auto indices = [4, 3, 1, 2, 0, 4]; | auto ind = source.indexed(indices); | assert(ind == [5, 4, 2, 3, 1, 5]); | | assert(ind.retro == source.indexed(indices.retro)); | | ind[3] += 10; // for index 2 | // 0 1 2 3 4 | 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; | return typeof(return)( | slices._structure, | SubSliceIterator!(Iterator, Sliceable)(slices._iterator.move, sliceable.move) | ); |} | |/// ditto |auto subSlices(S, Sliceable)(Sliceable sliceable, S[] slices) |{ | 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; | auto subs =[ | staticArray(2, 4), | staticArray(2, 10), | ]; | auto sliceable = 10.iota; | | auto r = sliceable.subSlices(subs); | 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; | sizediff_t length = bounds._lengths[0] <= 1 ? 0 : bounds._lengths[0] - 1; | static if (hasLength!Sliceable) | { | if (length && bounds[length - 1] > sliceable.length) | { | version (D_Exceptions) | throw choppedException; | else | assert(0, choppedExceptionMsg); | } | } | | return typeof(return)([size_t(length)], ChopIterator!(Iterator, Sliceable)(bounds._iterator.move, sliceable.move)); |} | |/// ditto |auto chopped(S, Sliceable)(Sliceable sliceable, S[] bounds) |{ | 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; | auto pairwiseIndexes = [2, 4, 10].sliced; | auto sliceable = 10.iota; | | auto r = sliceable.chopped(pairwiseIndexes); | 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"); | assert(slices[i + 1]._lengths == slices[0]._lengths, "zip: all slices must have the same lengths"); | static if (sameStrides) | 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); | auto structure = Ret._Structure.init; | structure[0] = slices[0]._lengths; | foreach (i; Iota!(Ret.S)) | structure[1][i] = slices[0]._strides[i]; | return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")")); | } | } | else | { | return .zip(toSlices!slices); | } | } |} | |/// |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : flattened, iota; | | auto alpha = iota!int(4, 3); | auto beta = slice!int(4, 3).universal; | | auto m = zip!true(alpha, beta); | foreach (r; m) | foreach (e; r) | e.b = e.a; | assert(alpha == beta); | | beta[] = 0; | foreach (e; m.flattened) | e.b = cast(int)e.a; | assert(alpha == beta); |} | |@safe pure nothrow version(mir_test) unittest |{ | import mir.ndslice.allocation : slice; | import mir.ndslice.topology : flattened, iota; | | auto alpha = iota!int(4).universal; | auto beta = new int[4]; | | auto m = zip(alpha, beta); | foreach (e; m) | e.b = e.a; | 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`); | 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; | | auto alpha = iota!int(4, 3); | auto beta = iota!int([4, 3], 1).slice; | | auto m = zip(alpha, beta); | | static assert(is(typeof(unzip!'a'(m)) == typeof(alpha))); | static assert(is(typeof(unzip!'b'(m)) == typeof(beta))); | | assert(m.unzip!'a' == alpha); | 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) | { | return slideAlong(slice.move.canonical); | } | else | static if (N == 1 && kind == Universal) | { | return slideAlong(slice.move.flattened); | } | else | { | alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); | enum dimension = Dimensions[$ - 1]; | size_t len = slice._lengths[dimension] - (params - 1); | if (sizediff_t(len) <= 0) // overfow | len = 0; | slice._lengths[dimension] = len; | static if (dimension + 1 == N || kind == Universal) | { | alias I = SlideIterator!(Iterator, params, fun); | auto ret = Slice!(I, N, kind)(slice._structure, I(move(slice._iterator))); | } | else | { | alias Z = ZipIterator!(Repeat!(params, Iterator)); | Z z; | foreach_reverse (p; Iota!(1, params)) | z._iterators[p] = slice._iterator + slice._strides[dimension] * p; | z._iterators[0] = move(slice._iterator); | alias M = MapIterator!(Z, fun); | auto ret = Slice!(M, N, kind)(slice._structure, M(move(z))); | } | static if (Dimensions.length == 1) | { | return ret; | } | else | { | 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 |{ | auto data = [4, 5].iota; | | alias scaled = a => a * 0.25; | | auto v = data.slideAlong!(3, "a + 2 * b + c", 0).map!scaled; | auto h = data.slideAlong!(3, "a + 2 * b + c", 1).map!scaled; | | assert(v == [4, 5].iota[1 .. $ - 1, 0 .. $]); | 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; | 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 |{ | auto data = 10.iota; | auto sw = data.slide!(3, "a + 2 * b + c"); | | import mir.utility: max; | assert(sw.length == max(0, cast(ptrdiff_t)data.length - 3 + 1)); | assert(sw == sw.length.iota.map!"(a + 1) * 4"); | assert(sw == [4, 8, 12, 16, 20, 24, 28, 32]); |} | |/++ |ND-use case |+/ |@safe pure nothrow @nogc version(mir_test) unittest |{ | auto data = [4, 5].iota; | | enum factor = 1.0 / 4 ^^ data.shape.length; | alias scaled = a => a * factor; | | auto sw = data.slide!(3, "a + 2 * b + c").map!scaled; | | 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; | 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 | 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; | 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 | | 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 | auto s = iota(3, 4); | import std.stdio; | assert(iota(3, 4).byDim!0.diff == [ | [4, 4, 4, 4], | [4, 4, 4, 4]]); | 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; | auto ret = slice.move.canonical; | } | else | { | alias ret = slice; | } | ret.popFrontAll; | ret.popBackAll; | return ret; |} | |/// |version(mir_test) unittest |{ | 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) | { | 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); | | size_t shift; | foreach (dimension; Iota!N) | { | slice._lengths[dimension] -= 2; | if (sizediff_t(slice._lengths[dimension]) <= 0) // overfow | slice._lengths[dimension] = 0; | shift += slice._stride!dimension; | } | | Z z; | z._iterator = move(slice._iterator); | z._iterator += shift; | foreach (dimension; Iota!(N - around)) | { | z._neighbours[dimension][0] = z._iterator - slice._strides[dimension]; | z._neighbours[dimension][1] = z._iterator + slice._strides[dimension]; | } | 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; | | auto wn = [4, 5].iota.withNeighboursSum; | assert(wn.all!"a[0] == a[1] * 0.25"); | 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; | | auto wn = [4, 5].iota.withNeighboursSum.universal; | assert(wn.all!"a[0] == a[1] * 0.25"); | 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)) |{ | return Cartesian!NdFields(fields).slicedNdField; |} | |/// 1D x 1D |version(mir_test) unittest |{ | auto a = [10, 20, 30]; | auto b = [ 1, 2, 3]; | | auto c = cartesian(a, b) | .map!"a + b"; | | assert(c == [ | [11, 12, 13], | [21, 22, 23], | [31, 32, 33]]); |} | |/// 1D x 2D |version(mir_test) unittest |{ | auto a = [10, 20, 30]; | auto b = iota([2, 3], 1); | | auto c = cartesian(a, b) | .map!"a + b"; | | assert(c.shape == [3, 2, 3]); | | 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 |{ | auto u = [100, 200]; | auto v = [10, 20, 30]; | auto w = [1, 2]; | | auto c = cartesian(u, v, w) | .map!"a + b + c"; | | assert(c.shape == [2, 3, 2]); | | 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)) | { | 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 | auto a = slice!double([4, 4], 0); | a.diagonal[] = 1; | | auto b = [ 1, -1, | -1, 1].sliced(2, 2); | | auto c = kronecker(a, b); | | 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 |{ | auto a = iota([3], 1); | | auto b = [ 1, -1]; | | auto c = kronecker(a, b); | | 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; | | auto a = [ 1, 2, | 3, 4].sliced(2, 2); | | auto b = [ 1, 0, | 0, 1].sliced(2, 2); | | auto c = [ 1, -1, | -1, 1].sliced(2, 2); | | auto d = kronecker(a, b, c); | | 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) |{ 0000000| assert(length > 0); | static if (is(size_t == ulong)) | assert(length <= uint.max); | else 0000000| assert(length <= ushort.max); | import mir.ndslice.field: MagicField; 0000000| 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) | { | auto n = matrix.length; | auto c = n * (n * n + 1) / 2; // magic number | return // check shape | matrix.length!0 > 0 && matrix.length!0 == matrix.length!1 | && // each row sum should equal magic number | matrix.byDim!0.map!sum == c.repeat(n) | && // each columns sum should equal magic number | matrix.byDim!1.map!sum == c.repeat(n) | && // diagonal sum should equal magic number | matrix.diagonal.sum == c | && // antidiagonal sum should equal magic number | matrix.antidiagonal.sum == c; | } | | assert(isMagic(magic(1))); | assert(!isMagic(magic(2))); // 2x2 magic square does not exist | foreach(n; 3 .. 24) | assert(isMagic(magic(n))); | 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 == "-") |{ | 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 == "+") | size_t length = 1; | else | size_t length = n; | 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; | | auto pck = 15.iota; | auto inc = pck.stairs!"+"(5); | auto dec = pck.stairs!"-"(5); | | assert(inc == [ | [0], | [1, 2], | [3, 4, 5], | [6, 7, 8, 9], | [10, 11, 12, 13, 14]]); | assert(inc[1 .. $][2] == [6, 7, 8, 9]); | | assert(dec == [ | [0, 1, 2, 3, 4], | [5, 6, 7, 8], | [9, 10, 11], | [12, 13], | [14]]); | 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 == "-") |{ | assert(slice.length!0 == slice.length!1, "stairs: input slice must be a square matrix."); | static if (type == "+") | { | return slice | .pack!1 | .map!"a" | .zip([slice.length].iota!size_t(1)) | .map!"a[0 .. b]"; | } | else | { | 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; | | auto gen = [3, 3].iota.as!double; | auto inc = gen.stairs!"+"; | auto dec = gen.stairs!"-"; | | assert(inc == [ | [0], | [3, 4], | [6, 7, 8]]); | | 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 | auto n = gen.length; | auto m = n * (n + 1) / 2; | // allocate memory | import mir.ndslice.allocation: uninitSlice; | auto lowerData = m.uninitSlice!double; | auto upperData = m.uninitSlice!double; | // construct packed stairs | auto lower = lowerData.stairs!"+"(n); | auto upper = upperData.stairs!"-"(n); | // copy data | import mir.algorithm.iteration: each; | each!"a[] = b"(lower, inc); | each!"a[] = b"(upper, dec); | | assert(&lower[0][0] is &lowerData[0]); | assert(&upper[0][0] is &upperData[0]); | | assert(lowerData == [0, 3, 4, 6, 7, 8]); | 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 $(