diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 097d52723..7ea768c63 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -14,7 +14,7 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run /** * The base interface for all nd-algebra implementations * @param T the type of nd-structure element - * @param C the type of the context + * @param C the type of the element context * @param N the type of the structure */ interface NDAlgebra> { @@ -112,10 +112,13 @@ interface NDField, N : NDStructure> : Field, NDRing() + /** - * Create a nd-field for [Double] values + * Create a nd-field for [Double] values or pull it from cache if it was created previously */ - fun real(shape: IntArray) = RealNDField(shape) + fun real(shape: IntArray) = realNDFieldCache.getOrPut(shape){RealNDField(shape)} /** * Create a nd-field with boxing generic buffer diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 0fdb53f07..c97f959f3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -7,6 +7,9 @@ import scientifik.kmath.operations.Space /** * The root for all [NDStructure] based algebra elements. Does not implement algebra element root because of problems with recursive self-types + * @param T the type of the element of the structure + * @param C the type of the context for the element + * @param N the type of the underlying [NDStructure] */ interface NDElement> : NDStructure { @@ -16,9 +19,6 @@ interface NDElement> : NDStructure { fun N.wrap(): NDElement - fun mapIndexed(transform: C.(index: IntArray, T) -> T) = context.mapIndexed(unwrap(), transform).wrap() - fun map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap() - companion object { /** * Create a optimized NDArray of doubles @@ -61,10 +61,17 @@ interface NDElement> : NDStructure { } } + +fun > NDElement.mapIndexed(transform: C.(index: IntArray, T) -> T) = + context.mapIndexed(unwrap(), transform).wrap() + +fun > NDElement.map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap() + + /** * Element by element application of any operation on elements to the whole [NDElement] */ -operator fun Function1.invoke(ndElement: NDElement) = +operator fun > Function1.invoke(ndElement: NDElement) = ndElement.map { value -> this@invoke(value) } /* plus and minus */ @@ -72,13 +79,13 @@ operator fun Function1.invoke(ndElement: NDElement) = /** * Summation operation for [NDElement] and single element */ -operator fun > NDElement.plus(arg: T) = +operator fun , N : NDStructure> NDElement.plus(arg: T) = map { value -> arg + value } /** * Subtraction operation between [NDElement] and single element */ -operator fun > NDElement.minus(arg: T) = +operator fun , N : NDStructure> NDElement.minus(arg: T) = map { value -> arg - value } /* prod and div */ @@ -86,13 +93,13 @@ operator fun > NDElement.minus(arg: T) = /** * Product operation for [NDElement] and single element */ -operator fun > NDElement.times(arg: T) = +operator fun , N : NDStructure> NDElement.times(arg: T) = map { value -> arg * value } /** * Division operation between [NDElement] and single element */ -operator fun > NDElement.div(arg: T) = +operator fun , N : NDStructure> NDElement.div(arg: T) = map { value -> arg / value } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/CreationRoutines.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDFactories.kt similarity index 64% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/CreationRoutines.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDFactories.kt index 51432bc5b..03f716168 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/CreationRoutines.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDFactories.kt @@ -1,14 +1,19 @@ package scientifik.kmath.structures import scientifik.kmath.operations.RealField.power -import kotlin.math.* +import kotlin.math.ceil +import kotlin.math.log +import kotlin.math.min +import kotlin.math.sign - -object RealFactory { +/** + * Numpy-like factories for [RealNDElement] + */ +object RealNDFactory { /** - * Create a NDArray filled with ones + * Get a [RealNDElement] filled with [RealNDField.one]. Due to caching all instances with the same shape point to the same object */ - fun ones(vararg shape: Int) = NDElement.real(shape) { 1.0 } + fun ones(vararg shape: Int) = NDField.real(shape).one /** * Create a 2D NDArray, with ones on the diagonal and zeros elsewhere. @@ -31,40 +36,37 @@ object RealFactory { * Return evenly spaced values within a given interval. * * Values are generated within the half-open interval [start, stop) (in other words, the interval including start but excluding stop). - * @param range use it like: - * (start..stop) to step */ - fun range(range: Pair, Double>) = - NDElement.real1D(ceil((range.first.endInclusive - range.first.start) / range.second).toInt()) { i -> range.first.start + i * range.second } + fun range(range: ClosedFloatingPointRange, step: Double = 1.0) = + NDElement.real1D(ceil((range.endInclusive - range.start) / step).toInt()) { i -> + range.start + i * step + } /** * Return evenly spaced numbers over a specified interval. - * @param range use it like: - * (start..stop) to number - * start is starting value, finaly value depend from endPoint parameter + * @param range start is starting value, final value depend from endPoint parameter * @param endPoint If True, right boundary of range is the last sample. Otherwise, it is not included. */ - fun linSpace( - range: Pair, Int>, + fun linspace( + range: ClosedFloatingPointRange, + num: Int = 100, endPoint: Boolean = true - ): Pair { - val div = if (endPoint) (range.second - 1) else range.second - val delta = range.first.start - range.first.endInclusive - if (range.second > 1) { + ): RealNDElement { + val div = if (endPoint) (num - 1) else num + val delta = range.start - range.endInclusive + return if (num > 1) { val step = delta / div if (step == 0.0) { error("Bad ranges: step = $step") } - val result = NDElement.real1D(range.second) { - if (endPoint and (it == range.second - 1)) { - range.first.endInclusive + NDElement.real1D(num) { + if (endPoint and (it == num - 1)) { + range.endInclusive } - range.first.start + it * step + range.start + it * step } - return result to step } else { - val step = Double.NaN - return NDElement.real1D(1) { range.first.start } to step + NDElement.real1D(1) { range.start } } } @@ -77,29 +79,25 @@ object RealFactory { * @param endPoint If True, power(base,stop) is the last sample. Otherwise, it is not included. * @param base - The base of the log space. */ - fun logSpace( - range: Pair, Int>, + fun logspace( + range: ClosedFloatingPointRange, + num: Int = 100, endPoint: Boolean = true, base: Double = 10.0 - ): RealNDElement { - val lin = linSpace(range, endPoint).first - val tempFun = { x: Double -> power(base, x) } - return tempFun(lin) // FIXME: RealNDElement.map return not suitable type ( `linSpace(range, endPoint).first.map{power(base, it}`) - } + ) = linspace(range, num, endPoint).map { power(base, it) } /** * Return numbers spaced evenly on a log scale (a geometric progression). * - * This is similar to [logSpace], but with endpoints specified directly. Each output sample is a constant multiple of the previous. + * This is similar to [logspace], but with endpoints specified directly. Each output sample is a constant multiple of the previous. * @param range use it like: * (start..stop) to number * start is starting value, finaly value depend from endPoint parameter * @param endPoint If True, right boundary of range is the last sample. Otherwise, it is not included. */ - fun geomSpace(range: Pair, Int>, endPoint: Boolean = true): RealNDElement { - var start = range.first.start - var stop = range.first.endInclusive - val num = range.second + fun geomspace(range: ClosedFloatingPointRange, num : Int = 100, endPoint: Boolean = true): RealNDElement { + var start = range.start + var stop = range.endInclusive if (start == 0.0 || stop == 0.0) { error("Geometric sequence cannot include zero") } @@ -110,10 +108,9 @@ object RealFactory { outSign = -outSign } - val logRange = logSpace((log(start, 10.0)..log(stop, 10.0) to num), endPoint = endPoint) - val function = { x: Double -> outSign * x } - return function(logRange) // FIXME: `outSign*log_` --- don't define times operator - + return logspace(log(start, 10.0)..log(stop, 10.0), num, endPoint = endPoint).map { + outSign * it + } } /** @@ -126,12 +123,11 @@ object RealFactory { error("Input must be 2D NDArray") } val size = min(array.shape[0], array.shape[0]) - if (offset >= 0) { - return NDElement.real1D(size) { i -> array[i, i + offset] } + return if (offset >= 0) { + NDElement.real1D(size) { i -> array[i, i + offset] } } else { - return NDElement.real1D(size) { i -> array[i - offset, i] } + NDElement.real1D(size) { i -> array[i - offset, i] } } - } /** @@ -144,13 +140,13 @@ object RealFactory { error("Input must be 1D NDArray") } val size = array.shape[0] - if (offset >= 0) { - return NDElement.real2D(size, size + offset) { i, j -> - if (i == j + offset) array[i] else 0.0 + return if (offset < 0) { + NDElement.real2D(size - offset, size) { i, j -> + if (i - offset == j) array[j] else 0.0 } } else { - return NDElement.real2D(size - offset, size) { i, j -> - if (i - offset == j) array[j] else 0.0 + NDElement.real2D(size, size + offset) { i, j -> + if (i == j + offset) array[i] else 0.0 } } } @@ -166,12 +162,12 @@ object RealFactory { error("Input must be 1D NDArray") } val size = if (nCols == 0) array.shape[0] else nCols - if (increasing) { - return NDElement.real2D(array.shape[0], size) { i, j -> + return if (increasing) { + NDElement.real2D(array.shape[0], size) { i, j -> power(array[i], j) } } else { - return NDElement.real2D(array.shape[0], size) { i, j -> + NDElement.real2D(array.shape[0], size) { i, j -> power(array[i], size - j - 1) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index bc5832e1c..d652bb8a8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -86,11 +86,25 @@ inline fun BufferedNDField.produceInline(crossinline initiali return BufferedNDFieldElement(this, DoubleBuffer(array)) } +/** + * Map one [RealNDElement] using function with indexes + */ +inline fun RealNDElement.mapIndexed(crossinline transform: RealField.(index: IntArray, Double) -> Double) = + context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) } + +/** + * Map one [RealNDElement] using function without indexes + */ +inline fun RealNDElement.map(crossinline transform: RealField.(Double) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> RealField.transform(buffer[offset]) } + return BufferedNDFieldElement(context, DoubleBuffer(array)) +} + /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ operator fun Function1.invoke(ndElement: RealNDElement) = - ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } + ndElement.map { this@invoke(it) } /* plus and minus */ @@ -99,10 +113,10 @@ operator fun Function1.invoke(ndElement: RealNDElement) = * Summation operation for [BufferedNDElement] and single element */ operator fun RealNDElement.plus(arg: Double) = - context.produceInline { i -> buffer[i] + arg } + map { it + arg } /** * Subtraction operation between [BufferedNDElement] and single element */ operator fun RealNDElement.minus(arg: Double) = - context.produceInline { i -> buffer[i] - arg } + map { it - arg } diff --git a/settings.gradle.kts b/settings.gradle.kts index a4464d01f..ca738647e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,8 +2,7 @@ pluginManagement { repositories { mavenCentral() maven("https://plugins.gradle.org/m2/") - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } - maven { setUrl("https://plugins.gradle.org/m2/") } + maven ("https://dl.bintray.com/kotlin/kotlin-eap") } }