From e23e956a0594171a51a828be83dab54a982cd245 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Sat, 11 Mar 2017 16:09:23 +0700 Subject: [PATCH] fix issues #1 #30 and https://github.com/libmir/mir/issues/333 --- doc/Makefile | 2 +- index.d | 4 + ndslice.graffle | Bin 14531 -> 15003 bytes ndslice.svg | 1300 +++++++++++++++++++------------- source/mir/ndslice/algorithm.d | 1 - source/mir/ndslice/field.d | 58 +- source/mir/ndslice/internal.d | 18 + source/mir/ndslice/ndfield.d | 232 ++++++ source/mir/ndslice/package.d | 11 + source/mir/ndslice/topology.d | 270 ++++++- source/mir/primitives.d | 24 + 11 files changed, 1373 insertions(+), 547 deletions(-) create mode 100644 source/mir/ndslice/ndfield.d diff --git a/doc/Makefile b/doc/Makefile index 25fd9a1a..b33f8c83 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -29,7 +29,7 @@ ARTWORK_DIR=$(DOC_SOURCE_DIR)/artwork MIR_PACKAGES = mir mir/ndslice PACKAGE_mir = conv functional primitives utility -PACKAGE_mir_ndslice = package algorithm allocation dynamic field iterator package slice sorting concatenation topology +PACKAGE_mir_ndslice = package algorithm allocation dynamic field ndfield iterator package slice sorting concatenation topology MOD_EXCLUDES=$(addprefix --ex=, mir.ndslice.internal) diff --git a/index.d b/index.d index 4ad7bea4..6c1ecfb7 100644 --- a/index.d +++ b/index.d @@ -51,6 +51,10 @@ $(BOOKTABLE , $(TDNW $(MREF mir,ndslice,field)) $(TD Field declarations) ) + $(TR + $(TDNW $(MREF mir,ndslice,ndfield)) + $(TD NdField declarations) + ) $(LEADINGROW Accessories) $(TR $(TDNW $(MREF mir,conv)) diff --git a/ndslice.graffle b/ndslice.graffle index 6394f1457e868b7051573c0aa2b0d8e898f077a0..f0c5ad26c7cf2c42ed894f40911ad039ebdebc6b 100644 GIT binary patch literal 15003 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 literal 14531 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 + + + @@ -831,36 +834,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1145,41 +1148,41 @@ - + - - - + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - + + + + + + @@ -1290,124 +1293,124 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1816,120 +1819,120 @@ - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1969,40 +1972,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2029,37 +2032,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3675,195 +3678,195 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4395,7 +4398,7 @@ - + @@ -4902,5 +4905,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/mir/ndslice/algorithm.d b/source/mir/ndslice/algorithm.d index 21239b9d..c620f3ac 100644 --- a/source/mir/ndslice/algorithm.d +++ b/source/mir/ndslice/algorithm.d @@ -123,7 +123,6 @@ template reduce(alias fun) } else alias reduce = .reduce!(naryFun!fun); - } /// Single slice diff --git a/source/mir/ndslice/field.d b/source/mir/ndslice/field.d index 16dda651..28398fc8 100644 --- a/source/mir/ndslice/field.d +++ b/source/mir/ndslice/field.d @@ -12,6 +12,7 @@ $(T2 BitwiseField, $(SUBREF topology, bitwise)) $(T2 MapField, $(SUBREF topology, map)) $(T2 ndIotaField, $(SUBREF topology, ndiota)) $(T2 RepeatField, $(SUBREF topology, repeat)) +$(T2 LinspaceField, $(SUBREF topology, linspace)) ) @@ -52,10 +53,25 @@ struct MapField(Field, alias fun) +/ static alias __map(alias fun1) = MapField__map!(Field, fun, fun1); - auto ref opIndex()(ptrdiff_t index) + auto ref opIndex(T...)(T index) { return fun(_field[index]); } + + auto length()() @property + { + return _field.length; + } + + auto shape()() @property + { + return _field.shape; + } + + auto elemenstCount()() @property + { + return _field.elemenstCount; + } } /++ @@ -250,3 +266,43 @@ struct ndIotaField(size_t N) return indexes; } } + +/++ +`LinspaceField` is used by $(SUBREF topology, linspace). ++/ +struct LinspaceField(T) +{ + /// + size_t _length; + + /// + T start, stop; + + // no fastmath + /// + T opIndex()(size_t index) + { + auto d = _length - 1; + auto v = typeof(T.init.re)(d - index); + auto w = typeof(T.init.re)(index); + v /= d; + w /= d; + auto a = v * start; + auto b = w * stop; + return a + b; + } + +@fastmath: + + size_t length()() @property + { + return _length; + } + + size_t[1] shape()() @property + { + size_t[1] ret = void; + ret[0] = _length; + return ret; + } +} diff --git a/source/mir/ndslice/internal.d b/source/mir/ndslice/internal.d index 66753f76..c418e288 100644 --- a/source/mir/ndslice/internal.d +++ b/source/mir/ndslice/internal.d @@ -8,6 +8,24 @@ import mir.internal.utility; @fastmath: +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) diff --git a/source/mir/ndslice/ndfield.d b/source/mir/ndslice/ndfield.d new file mode 100644 index 00000000..63dda473 --- /dev/null +++ b/source/mir/ndslice/ndfield.d @@ -0,0 +1,232 @@ +/++ +This is a submodule of $(MREF mir,ndslice). + +NdField is a type with `opIndex(size_t[N] index...)` primitive. +An ndslice can be created on top of a ndField using $(SUBREF slice, slicedNdField). + +$(BOOKTABLE $(H2 NdFields), +$(TR $(TH NdField Name) $(TH Used By)) +$(T2 Cartesian, $(SUBREF topology, cartesian)) +$(T2 Kronecker, $(SUBREF topology, kronecker)) +) + +See_also: $(SUBREF concatenation, concatenation). + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Copyright: Copyright © 2016-, Ilya Yaroshenko +Authors: Ilya Yaroshenko + +Macros: +SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) ++/ +module mir.ndslice.ndfield; + +import mir.internal.utility; +import mir.ndslice.internal; +import mir.ndslice.slice; +import mir.primitives; +import std.meta; + +private template _indexes(NdFields...) +{ + static if (NdFields.length == 0) + enum _indexes = ""; + else + { + alias Next = NdFields[0 .. $ - 1]; + enum i = Next.length; + enum _indexes = ._indexes!Next ~ + "_fields[" ~ i.stringof ~ "][" ~ _indexes_range!([staticMap!(DimensionCount, Next)].sum, DimensionCount!(NdFields[$ - 1])) ~ "], "; + } +} + +private template _indexes_range(size_t begin, size_t count) +{ + static if (count == 0) + enum _indexes_range = ""; + else + { + enum next = count - 1; + enum elem = begin + next; + enum _indexes_range = ._indexes_range!(begin, next) ~ "indexes[" ~ elem.stringof ~ "], "; + } +} + +/// +struct Cartesian(NdFields...) + if (NdFields.length > 1) +{ + /// + NdFields _fields; + + package enum size_t M(size_t f) = [staticMap!(DimensionCount, NdFields[0..f])].sum; + package enum size_t N = M!(NdFields.length); + + /// + size_t length(size_t d = 0)() @property + { + foreach(f, ref field; _fields) + static if (M!(f + 1) > d) + return field.length!(d - M!f); + } + + /// + size_t[N] shape()() @property + { + typeof(return) ret = void; + foreach(f, ref field; _fields) + { + static if (hasShape!(NdFields[f])) + { + auto s = field.shape; + foreach(j; Iota!(s.length)) + ret[M!f + j] = s[j]; + } + else + { + ret[M!f] = field.length; + } + } + return ret; + } + + /// + size_t elementsCount()() @property + { + size_t ret = 1; + foreach (ref field; _fields) + ret *= field.elementsCount; + ret; + } + + /// + auto opIndex()(size_t[N] indexes...) + { + import mir.functional : tuple; + return mixin("tuple(" ~ _indexes!(NdFields) ~ ")"); + } +} + +private template _kr_indexes(size_t n) +{ + static if (n == 0) + enum _kr_indexes = ""; + else + { + enum i = n - 1; + enum _kr_indexes = ._kr_indexes!i ~ "_fields[" ~ i.stringof ~ "][ind[" ~ i.stringof ~ "]], "; + } +} + +/// +struct Kronecker(alias fun, NdFields...) + if (NdFields.length > 1 && allSatisfy!(templateOr!(hasShape, hasLength), NdFields[1 .. $])) +{ + /// + NdFields _fields; + + private enum N = DimensionCount!(NdFields[$-1]); + + /// + size_t length(size_t d = 0)() @property + { + static if (d == 0) + { + size_t ret = _fields[0].length; + foreach(ref field; _fields[1 .. $]) + ret *= field.length; + } + else + { + size_t ret = _fields[0].length!d; + foreach(ref field; _fields[1 .. $]) + ret *= field.length!d; + } + return ret; + } + + + /// + size_t[N] shape()() @property + { + static if (N > 1) + { + size_t[N] ret = _fields[0].shape; + foreach(ref field; _fields[1 .. $]) + { + auto s = field.shape; + foreach(i; Iota!N) + ret[i] *= s[i]; + } + return ret; + } + else + { + size_t[1] ret = void; + ret[0] = _fields[0].length; + foreach(ref field; _fields[1 .. $]) + ret[0] *= field.length; + return ret; + } + } + + /// + size_t elementsCount()() @property + { + size_t ret = 1; + foreach (ref field; _fields) + ret *= field.elementsCount; + ret; + } + + /// + auto opIndex()(size_t[N] indexes...) + { + static if (N > 1) + size_t[N][NdFields.length] ind = void; + else + size_t[NdFields.length] ind = void; + foreach_reverse (f, ref field; _fields) + { + static if (f) + { + static if (hasShape!(NdFields[f])) + { + auto s = field.shape; + } + else + { + size_t[1] s = void; + s[0] = field.length; + } + static if (N > 1) + { + foreach(i; Iota!N) + { + ind[f][i] = indexes[i] % s[i]; + indexes[i] /= s[i]; + } + } + else + { + ind[f] = indexes[0] % s[0]; + indexes[0] /= s[0]; + } + } + else + { + static if (N > 1) + { + foreach(i; Iota!N) + ind[f][i] = indexes[i]; + } + else + { + ind[f] = indexes[0]; + } + } + } + return mixin("fun(" ~ _kr_indexes!(ind.length) ~ ")"); + } +} diff --git a/source/mir/ndslice/package.d b/source/mir/ndslice/package.d index 1d6c613d..d040810d 100644 --- a/source/mir/ndslice/package.d +++ b/source/mir/ndslice/package.d @@ -123,12 +123,15 @@ $(TR $(TDNW $(SUBMODULE topology) $(BR) $(SUBREF topology, bitwise) $(SUBREF topology, blocks) $(SUBREF topology, canonical) + $(SUBREF topology, cartesian) $(SUBREF topology, diagonal) $(SUBREF topology, evertPack) $(SUBREF topology, flattened) $(SUBREF topology, indexed) $(SUBREF topology, iota) $(SUBREF topology, ipack) + $(SUBREF topology, kronecker) + $(SUBREF topology, linspace) $(SUBREF topology, map) $(SUBREF topology, ndiota) $(SUBREF topology, pack) @@ -223,6 +226,14 @@ $(TR $(TDNW $(SUBMODULE field) ) ) +$(TR $(TDNW $(SUBMODULE ndfield) + $(BR) $(SMALL Declarations)) + $(TD + $(SUBREF ndfield, Cartesian) + $(SUBREF ndfield, Kronecker) + ) +) + )) $(H2 Example: Image Processing) diff --git a/source/mir/ndslice/topology.d b/source/mir/ndslice/topology.d index 319cf5e3..37ada589 100644 --- a/source/mir/ndslice/topology.d +++ b/source/mir/ndslice/topology.d @@ -11,6 +11,24 @@ $(T2 assumeContiguous, Converts a slice to contiguous $(SUBREF slice, SliceKind) ) +$(BOOKTABLE $(H2 Sequence Selectors), +$(TR $(TH Function Name) $(TH Description)) + +$(T2 repeat, Slice with identical values) +$(T2 iota, Contiguous Slice with initial flattened (contiguous) index.) +$(T2 ndiota, Contiguous Slice with initial multidimensional index.) +$(T2 linspace, Evenly spaced numbers over a specified interval.) + +) + +$(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)) @@ -19,9 +37,7 @@ 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 flattened, Contiguous 1-dimensional slice of all elements of a slice.) -$(T2 iota, Contiguous Slice with initial flattened (contiguous) index.) $(T2 map, Multidimensional functional map.) -$(T2 ndiota, Contiguous Slice with initial multidimensional index.) $(T2 retro, Reverses order of iteration for all dimensions) $(T2 stride, Strides 1-dimensional slice) $(T2 zip, Zips slices into a slice of tuples.) @@ -29,18 +45,17 @@ $(T2 unzip, Selects a slice from a zipped slice.) ) + $(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 repeat, Slice with identical values) $(T2 reshape, New slice with changed dimensions for the same data) $(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)) @@ -77,9 +92,11 @@ import std.meta; import mir.internal.utility; import mir.ndslice.field; +import mir.ndslice.ndfield; import mir.ndslice.internal; import mir.ndslice.iterator; import mir.ndslice.slice; +import mir.primitives; @fastmath: @@ -1630,7 +1647,6 @@ unittest /++ Returns a slice, the elements of which are equal to the initial multidimensional index value. -This is multidimensional analog of $(REF iota, std, range). For a flattened (contiguous) index, see $(LREF iota). Params: @@ -1681,6 +1697,74 @@ unittest 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. + internvals = 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)) +{ + Repeat!(N, LinspaceField!T) fields = void; + foreach(i; Iota!N) + { + assert(lengths[i] > 1, "linspace: all lengths must be greater then 1."); + fields[i] = LinspaceField!T(lengths[i], intervals[i][0], intervals[i][1]); + } + static if (N == 1) + return slicedField(fields); + else + return cartesian(fields); +} + +/// 1D +unittest +{ + auto s = linspace!double([5], [1.0, 2.0]); + assert(s == [1.0, 1.25, 1.5, 1.75, 2.0]); + + // remove endpoint + s.popBack; + assert(s == [1.0, 1.25, 1.5, 1.75]); +} + +/// 2D +unittest +{ + import mir.functional: tuple; + + auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]); + + assert(s == [ + [tuple(1.00, 0.00), tuple(1.00, 0.5), tuple(1.00, 1.0)], + [tuple(1.25, 0.00), tuple(1.25, 0.5), tuple(1.25, 1.0)], + [tuple(1.50, 0.00), tuple(1.50, 0.5), tuple(1.50, 1.0)], + [tuple(1.75, 0.00), tuple(1.75, 0.5), tuple(1.75, 1.0)], + [tuple(2.00, 0.00), tuple(2.00, 0.5), tuple(2.00, 1.0)], + ]); + + assert(s.map!"a.a * 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 +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. @@ -1688,7 +1772,6 @@ Params: lengths = list of dimension lengths Returns: `n`-dimensional slice composed of identical values, where `n` is dimension count. -See_also: $(REF repeat, std,range) +/ Slice!(Contiguous, [M], FieldIterator!(RepeatField!T)) repeat(T, size_t M)(T value, size_t[M] lengths...) @@ -2456,3 +2539,178 @@ pure nothrow unittest assert(m.unzip!'a' == alpha); assert(m.unzip!'b' == beta); } + +private enum TotalDim(NdFields...) = [staticMap!(DimensionCount, NdFields)].sum; + +/++ +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 +unittest +{ + auto a = [10, 20, 30]; + auto b = [ 1, 2, 3]; + + auto c = cartesian(a, b) + .map!"a.a + a.b"; + + assert(c == [ + [11, 12, 13], + [21, 22, 23], + [31, 32, 33]]); +} + +/// 1D x 2D +unittest +{ + auto a = [10, 20, 30]; + auto b = iota([2, 3], 1); + + auto c = cartesian(a, b) + .map!"a.a + 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 +unittest +{ + auto u = [100, 200]; + auto v = [10, 20, 30]; + auto w = [1, 2]; + + auto c = cartesian(u, v, w) + .map!"a.a + a.b + a.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) + +/ + @fastmath 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 +unittest +{ + import mir.ndslice.allocation: slice; + + // 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 +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 +unittest +{ + import mir.ndslice.allocation: slice; + + 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]]); +} diff --git a/source/mir/primitives.d b/source/mir/primitives.d index 638b00b8..22d1367c 100644 --- a/source/mir/primitives.d +++ b/source/mir/primitives.d @@ -3,6 +3,10 @@ Templates used to check primitives. +/ module mir.primitives; +import mir.ndslice.internal; + +@fastmath: + /++ Returns: `true` if `R` has a `length` member that returns an integral type implicitly convertible to `size_t`. @@ -53,3 +57,23 @@ enum bool hasShape(R) = is(typeof( static assert(hasLength!(B)); static assert(hasLength!(C)); } + +/// +template DimensionCount(T) + if (hasShape!T || hasLength!T) +{ + static if (hasShape!T) + enum size_t DimensionCount = typeof(T.shape).length; + else + enum size_t DimensionCount = 1; +} + +/// +size_t length(size_t d : 0, T)(T[] array) +{ + pragma(inline, true); + return array.length; +} + +/// +alias elementsCount = length;