DTW method realization #517

Open
EjenY-Poltavchiny wants to merge 19 commits from mrFendel/ejeny_branch_ into dev
3 changed files with 72 additions and 36 deletions
Showing only changes of commit 55bcd202bf - Show all commits

View File

@ -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.DecompositionFactory_FSCC
import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC
import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.linear.* import space.kscience.kmath.linear.*
import space.kscience.kmath.linear.Matrix import space.kscience.kmath.linear.Matrix
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.nd.StructureFeature import space.kscience.kmath.nd.StructureFeature
import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.FloatField import space.kscience.kmath.operations.FloatField

View File

@ -5,6 +5,11 @@
package space.kscience.kmath.series 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 space.kscience.kmath.structures.DoubleBuffer
import kotlin.math.abs 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 BOTTOM_OFFSET : Int = 1
public const val DIAGONAL_OFFSET : Int = 0 public const val DIAGONAL_OFFSET : Int = 0
// TODO: Change container for alignMatrix to kmath special ND structure // TODO: Change container for alignMatrix to kmath special ND structure
public data class DynamicTimeWarpingData(val totalCost : Double = 0.0, public data class DynamicTimeWarpingData(val totalCost : Double = 0.0,
val alignMatrix : Array<BooleanArray> = Array(0) {BooleanArray(0)}) { val alignMatrix : IntBufferND = IntRingOpsND.structureND(ShapeND(0, 0)) { (i, j) -> 0}) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null || this::class != other::class) return false 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 other as DynamicTimeWarpingData
if (totalCost != other.totalCost) return false if (totalCost != other.totalCost) return false
if (!alignMatrix.contentDeepEquals(other.alignMatrix)) return false
return true 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. * There is special cases for i = 0 or j = 0.
*/ */
public fun costMatrix(series1 : DoubleBuffer, series2 : DoubleBuffer) : Array<DoubleArray> { public fun costMatrix(series1 : DoubleBuffer, series2 : DoubleBuffer) : DoubleBufferND {
val dtwMatrix: Array<DoubleArray> = Array(series1.size){ row -> val dtwMatrix = DoubleField.ndAlgebra.structureND(ShapeND(series1.size, series2.size)) {
DoubleArray(series2.size) { col -> (row, col) -> abs(series1[row] - series2[col])
abs(series1[row] - series2[col])
}
} }
for ( (row, col) in dtwMatrix.indices) {
for (i in dtwMatrix.indices) { dtwMatrix[row, col] += when {
for (j in dtwMatrix[i].indices) { row == 0 && col == 0 -> 0.0
dtwMatrix[i][j] += when { row == 0 -> dtwMatrix[row, col - 1]
i == 0 && j == 0 -> 0.0 col == 0 -> dtwMatrix[row - 1, col]
i == 0 -> dtwMatrix[i][j-1] else -> minOf(
j == 0 -> dtwMatrix[i-1][j] dtwMatrix[row, col - 1],
else -> minOf( dtwMatrix[row - 1, col],
dtwMatrix[i][j-1], dtwMatrix[row - 1, col - 1]
dtwMatrix[i-1][j], )
dtwMatrix[i-1][j-1]
)
}
} }
} }
return dtwMatrix return dtwMatrix
@ -95,27 +90,27 @@ public fun dynamicTimeWarping(series1 : DoubleBuffer, series2 : DoubleBuffer) :
var cost = 0.0 var cost = 0.0
var pathLength = 0 var pathLength = 0
val costMatrix = costMatrix(series1, series2) val costMatrix = costMatrix(series1, series2)
val alignMatrix : Array<BooleanArray> = Array(costMatrix.size) { BooleanArray(costMatrix.first().size) } val alignMatrix: IntBufferND = IntRingOpsND.structureND(ShapeND(series1.size, series2.size)) {(row, col) -> 0}
val indexes = PathIndices(alignMatrix.lastIndex, alignMatrix.last().lastIndex) val indexes = PathIndices(alignMatrix.indices.shape.first() - 1, alignMatrix.indices.shape.last() - 1)
with(indexes) { with(indexes) {
alignMatrix[id_x][id_y] = true alignMatrix[id_x, id_y] = 1
cost += costMatrix[id_x][id_y] cost += costMatrix[id_x, id_y]
pathLength++ pathLength++
while (id_x != 0 || id_y != 0) { while (id_x != 0 || id_y != 0) {
when { 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) 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) 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) moveOption(DIAGONAL_OFFSET)
} }
} }
alignMatrix[id_x][id_y] = true alignMatrix[id_x, id_y] = 1
cost += costMatrix[id_x][id_y] cost += costMatrix[id_x, id_y]
pathLength++ pathLength++
} }
cost /= pathLength cost /= pathLength

View File

@ -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] ")
}
}
}
}
}