Compare commits

...

19 Commits

Author SHA1 Message Date
E––jenY-Poltavchiny
051a3893ec Test added, examples moved to folder "examples". 2023-06-24 14:15:56 +03:00
E––jenY-Poltavchiny
c6e1eb8406 Fixed algorithm mistakes. There was "index out of bounds" situation. 2023-06-24 14:14:06 +03:00
EjenY-Poltavchiny
4ba1a1606a
Merge branch 'dev' into ejeny_branch_ 2023-06-24 12:52:46 +03:00
Gleb Minaev
d20e532c4f Merge branch 'dev' into ejeny_branch_ 2023-06-18 12:22:01 +03:00
EjenY-Poltavchiny
91f5436be5
Merge branch 'dev' into ejeny_branch_ 2023-06-16 15:42:40 +03:00
Gleb Minaev
39244ebc52 Remove extra enum class and data class. Fix docstrings and comments readability. Fix code style violations. 2023-06-16 14:08:32 +03:00
E––jenY-Poltavchiny
31be2b547b Comments fixed. 2023-06-14 23:55:25 +03:00
E––jenY-Poltavchiny
d5c3aa563e Enum class and some code corrections. 2023-06-14 04:13:39 +03:00
E––jenY-Poltavchiny
11cbf7cdc7 Deleted waste file. 2023-06-13 23:52:13 +03:00
E––jenY-Poltavchiny
1773b49b1f Changed tests 2023-06-13 03:39:29 +03:00
E––jenY-Poltavchiny
455c9d188d Changed according to the notes 2023-06-13 03:37:35 +03:00
EjenY-Poltavchiny
13da33ecde
Merge branch 'master' into ejeny_branch_ 2023-06-11 05:43:23 +03:00
E––jenY-Poltavchiny
55bcd202bf first DTW method realization 2023-06-11 05:31:09 +03:00
SPC-code
b5f85a6d86
Merge pull request #514 from SciProgCentre/dev
0.3.1
2023-05-12 22:19:48 +03:00
E––jenY-Poltavchiny
b8809d1c21 first DTW method realization 2023-04-22 09:52:14 +03:00
E––jenY-Poltavchiny
134f265700 Merge branch 'dev' into ejeny_branch_ 2023-04-17 14:04:49 +03:00
E––jenY-Poltavchiny
74a550effd Merge branch 'dev' into ejeny_branch_ 2023-04-06 22:46:03 +03:00
E––jenY-Poltavchiny
e7b56e4972 New file in example dir 2023-04-05 01:25:58 +03:00
E––jenY-Poltavchiny
b6b8ac7de5 Hello world! 2023-04-04 19:58:32 +03:00
3 changed files with 177 additions and 0 deletions

View File

@ -0,0 +1,29 @@
/*
* 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.algebra
import space.kscience.kmath.operations.bufferAlgebra
import space.kscience.kmath.structures.asBuffer
fun main() = with(Double.algebra.bufferAlgebra.seriesAlgebra()) {
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 = firstSequence.asBuffer()
val seriesTwo = secondSequence.asBuffer()
val result = DoubleFieldOpsND.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] > 0.0) {
print("[$i, $j] ")
}
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.PerformancePitfall
import space.kscience.kmath.nd.*
import space.kscience.kmath.nd.DoubleBufferND
import space.kscience.kmath.nd.ShapeND
import space.kscience.kmath.structures.DoubleBuffer
import kotlin.math.abs
/**
* Stores a result of [dynamicTimeWarping]. The class contains:
* 1. [Total penalty cost][totalCost] for series alignment.
* 2. [Align matrix][alignMatrix] that describes which point of the first series matches to point of the other series.
*/
public data class DynamicTimeWarpingData(
val totalCost : Double = 0.0,
val alignMatrix : DoubleBufferND = DoubleFieldOpsND.structureND(ShapeND(0, 0)) { (_, _) -> 0.0}
)
/**
* DTW method implementation. Returns alignment matrix for two series comparing and penalty for this alignment.
*/
@OptIn(PerformancePitfall::class)
public fun DoubleFieldOpsND.dynamicTimeWarping(series1 : DoubleBuffer, series2 : DoubleBuffer) : DynamicTimeWarpingData {
// Create a special matrix of costs alignment for the two series.
val costMatrix = structureND(ShapeND(series1.size, series2.size)) { (row, col) ->
abs(series1[row] - series2[col])
}
// Initialise the cost matrix by formulas
// costMatrix[i, j] = euclideanNorm(series1(i), series2(j)) +
// min(costMatrix[i - 1, j], costMatrix[i, j - 1], costMatrix[i - 1, j - 1]).
for ((row, col) in costMatrix.indices) {
costMatrix[row, col] += when {
row == 0 && col == 0 -> continue
row == 0 -> costMatrix[row, col - 1]
col == 0 -> costMatrix[row - 1, col]
else -> minOf(
costMatrix[row, col - 1],
costMatrix[row - 1, col],
costMatrix[row - 1, col - 1]
)
}
}
// alignMatrix contains non-zero values at position where two points from series matches
// Values are penalty for concatenation of current points.
val alignMatrix = structureND(ShapeND(series1.size, series2.size)) { _ -> 0.0}
var index1 = series1.size - 1
var index2 = series2.size - 1
var cost = 0.0
var pathLength = 0
alignMatrix[index1, index2] = costMatrix[index1, index2]
cost += costMatrix[index1, index2]
pathLength++
while (index1 != 0 || index2 != 0) {
when {
index1 == 0 -> {
index2--
}
index2 == 0 -> {
index1--
}
costMatrix[index1, index2] == costMatrix[index1, index2 - 1] + abs(series1[index1] - series2[index2]) -> {
index2--
}
costMatrix[index1, index2] == costMatrix[index1 - 1, index2] + abs(series1[index1] - series2[index2]) -> {
index1--
}
costMatrix[index1, index2] == costMatrix[index1 - 1, index2 - 1] + abs(series1[index1] - series2[index2]) -> {
index1--
index2--
}
}
alignMatrix[index1, index2] = costMatrix[index1, index2]
cost += costMatrix[index1, index2]
pathLength++
}
cost /= pathLength
return DynamicTimeWarpingData(cost, alignMatrix)
}

View File

@ -0,0 +1,57 @@
/*
* 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.asBuffer
import space.kscience.kmath.structures.toDoubleBuffer
import kotlin.math.PI
import kotlin.test.Test
import kotlin.test.assertEquals
class DTWTest {
@Test
fun someData() {
val firstSequence: DoubleArray = doubleArrayOf(0.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 1.0)
val secondSequence: DoubleArray = doubleArrayOf(0.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 1.0)
val seriesOne = firstSequence.asBuffer()
val seriesTwo = secondSequence.asBuffer()
val result = DoubleFieldOpsND.dynamicTimeWarping(seriesOne, seriesTwo)
assertEquals(result.totalCost, 0.0)
}
@Test
fun pathTest() = with(Double.algebra.bufferAlgebra.seriesAlgebra()) {
val s1 = series(10) { DoubleField.sin(2 * PI * it / 100)}.toDoubleBuffer()
val s2 = series(20) {sin(PI * it / 100)}.toDoubleBuffer()
val s3 = series(20) {sin(PI * it / 100 + 2 * PI)}.toDoubleBuffer()
val resS1S2 = DoubleFieldOpsND.dynamicTimeWarping(s1, s2).alignMatrix
var pathLengthS1S2 = 0
for ((i,j) in resS1S2.indices) {
if (resS1S2[i, j] > 0.0) {
++pathLengthS1S2
}
}
val resS1S3 = DoubleFieldOpsND.dynamicTimeWarping(s1, s3).alignMatrix
var pathLengthS1S3 = 0
for ((i,j) in resS1S3.indices) {
if (resS1S2[i, j] > 0.0) {
++pathLengthS1S3
}
}
assertEquals(pathLengthS1S3, pathLengthS1S2)
}
}