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 6394f145..f0c5ad26 100644
Binary files a/ndslice.graffle and b/ndslice.graffle differ
diff --git a/ndslice.svg b/ndslice.svg
index cd0d9c23..725f0c7c 100644
--- a/ndslice.svg
+++ b/ndslice.svg
@@ -191,6 +191,9 @@
+
+
+
@@ -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;