From 55bcd202bf74b8fdd8a920efd4a708a15f09053a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=C2=96=C2=96jenY-Poltavchiny?= Date: Sun, 11 Jun 2023 05:31:09 +0300 Subject: [PATCH] first DTW method realization --- .../space/kscience/kmath/ejml/_generated.kt | 2 +- .../kmath/series/DynamicTimeWarping.kt | 65 +++++++++---------- .../space/kscience/kmath/series/DTWTest.kt | 41 ++++++++++++ 3 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 kmath-stat/src/commonTest/kotlin/space/kscience/kmath/series/DTWTest.kt diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt index c56583fa8..8ad7f7293 100644 --- a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt +++ b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt @@ -19,9 +19,9 @@ import org.ejml.sparse.csc.factory.DecompositionFactory_DSCC import org.ejml.sparse.csc.factory.DecompositionFactory_FSCC import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC -import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.linear.* import space.kscience.kmath.linear.Matrix +import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.nd.StructureFeature import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.FloatField diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/DynamicTimeWarping.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/DynamicTimeWarping.kt index 840348bbb..9f6bb528e 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/DynamicTimeWarping.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/series/DynamicTimeWarping.kt @@ -5,6 +5,11 @@ package space.kscience.kmath.series +import space.kscience.kmath.nd.* +import space.kscience.kmath.nd.DoubleBufferND +import space.kscience.kmath.nd.ShapeND +import space.kscience.kmath.nd.ndAlgebra +import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.structures.DoubleBuffer import kotlin.math.abs @@ -12,9 +17,11 @@ public const val LEFT_OFFSET : Int = -1 public const val BOTTOM_OFFSET : Int = 1 public const val DIAGONAL_OFFSET : Int = 0 + + // TODO: Change container for alignMatrix to kmath special ND structure public data class DynamicTimeWarpingData(val totalCost : Double = 0.0, - val alignMatrix : Array = Array(0) {BooleanArray(0)}) { + val alignMatrix : IntBufferND = IntRingOpsND.structureND(ShapeND(0, 0)) { (i, j) -> 0}) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false @@ -22,16 +29,9 @@ public data class DynamicTimeWarpingData(val totalCost : Double = 0.0, other as DynamicTimeWarpingData if (totalCost != other.totalCost) return false - if (!alignMatrix.contentDeepEquals(other.alignMatrix)) return false return true } - - override fun hashCode(): Int { - var result = totalCost.hashCode() - result = 31 * result + alignMatrix.contentDeepHashCode() - return result - } } /** @@ -42,25 +42,20 @@ public data class DynamicTimeWarpingData(val totalCost : Double = 0.0, * There is special cases for i = 0 or j = 0. */ -public fun costMatrix(series1 : DoubleBuffer, series2 : DoubleBuffer) : Array { - val dtwMatrix: Array = Array(series1.size){ row -> - DoubleArray(series2.size) { col -> - abs(series1[row] - series2[col]) - } +public fun costMatrix(series1 : DoubleBuffer, series2 : DoubleBuffer) : DoubleBufferND { + val dtwMatrix = DoubleField.ndAlgebra.structureND(ShapeND(series1.size, series2.size)) { + (row, col) -> abs(series1[row] - series2[col]) } - - for (i in dtwMatrix.indices) { - for (j in dtwMatrix[i].indices) { - dtwMatrix[i][j] += when { - i == 0 && j == 0 -> 0.0 - i == 0 -> dtwMatrix[i][j-1] - j == 0 -> dtwMatrix[i-1][j] - else -> minOf( - dtwMatrix[i][j-1], - dtwMatrix[i-1][j], - dtwMatrix[i-1][j-1] - ) - } + for ( (row, col) in dtwMatrix.indices) { + dtwMatrix[row, col] += when { + row == 0 && col == 0 -> 0.0 + row == 0 -> dtwMatrix[row, col - 1] + col == 0 -> dtwMatrix[row - 1, col] + else -> minOf( + dtwMatrix[row, col - 1], + dtwMatrix[row - 1, col], + dtwMatrix[row - 1, col - 1] + ) } } return dtwMatrix @@ -95,27 +90,27 @@ public fun dynamicTimeWarping(series1 : DoubleBuffer, series2 : DoubleBuffer) : var cost = 0.0 var pathLength = 0 val costMatrix = costMatrix(series1, series2) - val alignMatrix : Array = Array(costMatrix.size) { BooleanArray(costMatrix.first().size) } - val indexes = PathIndices(alignMatrix.lastIndex, alignMatrix.last().lastIndex) + val alignMatrix: IntBufferND = IntRingOpsND.structureND(ShapeND(series1.size, series2.size)) {(row, col) -> 0} + val indexes = PathIndices(alignMatrix.indices.shape.first() - 1, alignMatrix.indices.shape.last() - 1) with(indexes) { - alignMatrix[id_x][id_y] = true - cost += costMatrix[id_x][id_y] + alignMatrix[id_x, id_y] = 1 + cost += costMatrix[id_x, id_y] pathLength++ while (id_x != 0 || id_y != 0) { when { - id_x == 0 || costMatrix[id_x][id_y] == costMatrix[id_x][id_y - 1] + abs(series1[id_x] - series2[id_y]) -> { + id_x == 0 || costMatrix[id_x, id_y] == costMatrix[id_x, id_y - 1] + abs(series1[id_x] - series2[id_y]) -> { moveOption(LEFT_OFFSET) } - id_y == 0 || costMatrix[id_x][id_y] == costMatrix[id_x - 1][id_y] + abs(series1[id_x] - series2[id_y]) -> { + id_y == 0 || costMatrix[id_x, id_y] == costMatrix[id_x - 1, id_y] + abs(series1[id_x] - series2[id_y]) -> { moveOption(BOTTOM_OFFSET) } - costMatrix[id_x][id_y] == costMatrix[id_x - 1][id_y - 1] + abs(series1[id_x] - series2[id_y]) -> { + costMatrix[id_x, id_y] == costMatrix[id_x - 1, id_y - 1] + abs(series1[id_x] - series2[id_y]) -> { moveOption(DIAGONAL_OFFSET) } } - alignMatrix[id_x][id_y] = true - cost += costMatrix[id_x][id_y] + alignMatrix[id_x, id_y] = 1 + cost += costMatrix[id_x, id_y] pathLength++ } cost /= pathLength diff --git a/kmath-stat/src/commonTest/kotlin/space/kscience/kmath/series/DTWTest.kt b/kmath-stat/src/commonTest/kotlin/space/kscience/kmath/series/DTWTest.kt new file mode 100644 index 000000000..649899fdb --- /dev/null +++ b/kmath-stat/src/commonTest/kotlin/space/kscience/kmath/series/DTWTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.series + +import space.kscience.kmath.nd.* +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.algebra +import space.kscience.kmath.operations.bufferAlgebra +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.structures.asBuffer +import kotlin.test.Test +import kotlin.test.assertEquals + +class DTWTest { + + @Test + fun someData() : Unit { + with(Double.algebra.bufferAlgebra) { + val firstSequence: DoubleArray = doubleArrayOf(0.0, 2.0, 3.0, 1.0, 3.0, 0.1, 0.0, 1.0) + val secondSequence: DoubleArray = doubleArrayOf(1.0, 0.0, 3.0, 0.0, 0.0, 3.0, 2.0, 0.0, 2.0) + + val seriesOne: DoubleBuffer = firstSequence.asBuffer() + val seriesTwo: DoubleBuffer = secondSequence.asBuffer() + + val result = dynamicTimeWarping(seriesOne, seriesTwo) + println("Total penalty coefficient: ${result.totalCost}") + print("Alignment: ") + println(result.alignMatrix) + for ((i , j) in result.alignMatrix.indices) { + if (result.alignMatrix[i, j] == 1) { + print("[$i, $j] ") + } + } + } + } +} + +