Skip to content

Commit 4c7abb8

Browse files
authored
Merge pull request #11 from shapelets/feature/findBestNOccurrences
Add mass and findBestNOccurrences functions
2 parents f1d2f94 + aa0f973 commit 4c7abb8

File tree

4 files changed

+191
-21
lines changed

4 files changed

+191
-21
lines changed

.CI/travis/install-java.sh

Lines changed: 0 additions & 11 deletions
This file was deleted.

.travis.yml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@ matrix:
1111
include:
1212
- os: linux
1313
dist: xenial
14-
env:
15-
- JAVA_HOME='/usr/lib/jvm/java-8-oracle'
14+
jdk: openjdk8
1615
cache:
1716
directories:
1817
- ${TRAVIS_BUILD_DIR}/installers
1918
- ${HOME}/.m2
2019
- ${HOME}/.conan
2120
- ${TRAVIS_BUILD_DIR}/cmake
2221
install:
23-
- source .CI/travis/install-java.sh
2422
- source .CI/travis/install-arrayfire.sh
2523
- source .CI/travis/install-khiva.sh
2624
script:
@@ -30,8 +28,7 @@ matrix:
3028

3129
- os: osx
3230
osx_image: xcode9.3
33-
env:
34-
- JDK='Oracle JDK 8'
31+
jdk: oraclejdk8
3532
cache:
3633
directories:
3734
- ${TRAVIS_BUILD_DIR}/installers
@@ -43,11 +40,6 @@ matrix:
4340
install:
4441
- source .CI/travis/install-arrayfire.sh
4542
- source .CI/travis/install-khiva.sh
46-
- brew update
47-
- brew tap caskroom/cask
48-
- brew tap caskroom/versions
49-
- brew cask info java8
50-
- brew cask install java8
5143
script:
5244
- source .CI/travis/build-and-test.sh
5345
after_success:

src/main/kotlin/io/shapelets/khiva/Matrix.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ package io.shapelets.khiva
1313
* Khiva Matrix Profile class containing matrix profile methods.
1414
*/
1515
object Matrix : Library() {
16+
@JvmStatic
17+
private external fun mass(query: Long, tss: Long): LongArray
18+
19+
@JvmStatic
20+
private external fun findBestNOccurrences(query: Long, tss: Long, n: Long): LongArray
21+
1622
@JvmStatic
1723
private external fun stomp(a: Long, b: Long, m: Long): LongArray
1824

@@ -25,6 +31,62 @@ object Matrix : Library() {
2531
@JvmStatic
2632
private external fun findBestNDiscords(profile: Long, index: Long, m: Long, n: Long, selfJoin: Boolean): LongArray
2733

34+
/**
35+
* Mueen's Algorithm for Similarity Search.
36+
*
37+
* The result has the following structure:
38+
* - 1st dimension corresponds to the index of the subsequence in the time series.
39+
* - 2nd dimension corresponds to the number of queries.
40+
* - 3rd dimension corresponds to the number of time series.
41+
*
42+
* For example, the distance in the position (1, 2, 3) correspond to the distance of the third query to the fourth time
43+
* series for the second subsequence in the time series.
44+
*
45+
* [1] Chin-Chia Michael Yeh, Yan Zhu, Liudmila Ulanova, Nurjahan Begum, Yifei Ding, Hoang Anh Dau, Diego Furtado Silva,
46+
* Abdullah Mueen, Eamonn Keogh (2016). Matrix Profile I: All Pairs Similarity Joins for Time Series: A Unifying View
47+
* that Includes Motifs, Discords and Shapelets. IEEE ICDM 2016.
48+
*
49+
* @param query Array whose first dimension is the length of the query time series and the second dimension is the
50+
* number of queries.
51+
* @param tss Array whose first dimension is the length of the time series and the second dimension is the number of
52+
* time series.
53+
* @return Array with the distances.
54+
*/
55+
fun mass(query: Array, tss: Array): Array {
56+
val refs = mass(query.reference, tss.reference)
57+
query.reference = refs[0]
58+
tss.reference = refs[1]
59+
return Array(refs[2])
60+
}
61+
62+
63+
/**
64+
* Calculates the N best matches of several queries in several time series.
65+
*
66+
* The result has the following structure:
67+
* - 1st dimension corresponds to the nth best match.
68+
* - 2nd dimension corresponds to the number of queries.
69+
* - 3rd dimension corresponds to the number of time series.
70+
*
71+
* For example, the distance in the position (1, 2, 3) corresponds to the second best distance of the third query in the
72+
* fourth time series. The index in the position (1, 2, 3) is the is the index of the subsequence which leads to the
73+
* second best distance of the third query in the fourth time series.
74+
*
75+
* @param query Array whose first dimension is the length of the query time series and the second dimension is the
76+
* number of queries.
77+
* @param tss Array whose first dimension is the length of the time series and the second dimension is the number of
78+
* time series.
79+
* @param n Number of matches to return.
80+
* @return Array or arrays with the distances and indexes.
81+
*/
82+
fun findBestNOccurrences(query: Array, tss: Array, n: Long): kotlin.Array<Array> {
83+
val refs = findBestNOccurrences(query.reference, tss.reference, n)
84+
query.reference = refs[0]
85+
tss.reference = refs[1]
86+
return arrayOf(Array(refs[2]), Array(refs[3]))
87+
}
88+
89+
2890
/**
2991
* STOMP algorithm to calculate the matrix profile between 'arrA' and 'arrB' using a subsequence length
3092
* of 'm'.

src/test/kotlin/io/shapelets/khiva/MatrixTest.kt

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,109 @@ import org.junit.Test
1515

1616
class MatrixTest {
1717

18+
@Test
19+
@Throws(Exception::class)
20+
fun testMass() {
21+
val tss = doubleArrayOf(10.0, 10.0, 10.0, 11.0, 12.0, 11.0, 10.0, 10.0, 11.0, 12.0, 11.0, 14.0, 10.0, 10.0)
22+
val dimsTss = longArrayOf(14, 1, 1, 1)
23+
24+
val query = doubleArrayOf(4.0, 3.0, 8.0)
25+
val dimsQuery = longArrayOf(3, 1, 1, 1)
26+
27+
Array(tss, dimsTss).use { t ->
28+
Array(query, dimsQuery).use { q ->
29+
30+
val expectedDistance = doubleArrayOf(
31+
1.732051, 0.328954, 1.210135, 3.150851, 3.245858, 2.822044,
32+
0.328954, 1.210135, 3.150851, 0.248097, 3.30187, 2.82205)
33+
val result = Matrix.mass(q, t)
34+
val distances = result.getData<DoubleArray>()
35+
36+
Assert.assertArrayEquals(expectedDistance, distances, 1e-3)
37+
38+
result.close()
39+
}
40+
}
41+
42+
}
43+
44+
@Test
45+
@Throws(Exception::class)
46+
fun testMassMultiple() {
47+
val tss = doubleArrayOf(10.0, 10.0, 10.0, 11.0, 12.0, 11.0, 10.0, 10.0, 11.0, 12.0, 11.0, 14.0, 10.0, 10.0)
48+
val dimsTss = longArrayOf(7, 2, 1, 1)
49+
50+
val query = doubleArrayOf(10.0, 10.0, 11.0, 11.0, 10.0, 11.0, 10.0, 10.0)
51+
val dimsQuery = longArrayOf(4, 2, 1, 1)
52+
53+
Array(tss, dimsTss).use { t ->
54+
Array(query, dimsQuery).use { q ->
55+
56+
val expectedDistance = doubleArrayOf(
57+
1.8388, 0.8739, 1.5307, 3.6955, 3.2660, 3.4897, 2.8284, 1.2116,
58+
1.5307, 2.1758, 2.5783, 3.7550, 2.8284, 2.8284, 3.2159, 0.5020)
59+
val result = Matrix.mass(q, t)
60+
val distances = result.getData<DoubleArray>()
61+
62+
Assert.assertArrayEquals(expectedDistance, distances, 1e-3)
63+
64+
result.close()
65+
}
66+
}
67+
68+
}
69+
70+
@Test
71+
@Throws(Exception::class)
72+
fun testFindBestNOccurrences() {
73+
val tss = doubleArrayOf(10.0, 10.0, 11.0, 11.0, 12.0, 11.0, 10.0, 10.0, 11.0, 12.0, 11.0, 10.0, 10.0, 11.0, 10.0, 10.0, 11.0, 11.0, 12.0, 11.0, 10.0, 10.0, 11.0, 12.0, 11.0, 10.0, 10.0, 11.0)
74+
val dimsTss = longArrayOf(28, 1, 1, 1)
75+
76+
val query = doubleArrayOf(10.0, 11.0, 12.0)
77+
val dimsQuery = longArrayOf(3, 1, 1, 1)
78+
79+
Array(tss, dimsTss).use { t ->
80+
Array(query, dimsQuery).use { q ->
81+
val result = Matrix.findBestNOccurrences(q, t, 1)
82+
val distances = result[0].getData<DoubleArray>()
83+
val indexes = result[1].getData<IntArray>()
84+
85+
Assert.assertEquals(distances[0], 0.0, DELTA)
86+
Assert.assertEquals(indexes[0], 7)
87+
88+
result[0].close()
89+
result[1].close()
90+
}
91+
}
92+
93+
}
94+
95+
@Test
96+
@Throws(Exception::class)
97+
fun testFindBestNOccurrencesMultipleQueries() {
98+
val tss = doubleArrayOf(10.0, 10.0, 11.0, 11.0, 10.0, 11.0, 10.0, 10.0, 11.0, 11.0, 10.0, 11.0, 10.0, 10.0, 11.0, 10.0, 10.0, 11.0, 10.0, 11.0, 11.0, 10.0, 11.0, 11.0, 14.0, 10.0, 11.0, 10.0)
99+
val dimsTss = longArrayOf(14, 2, 1, 1)
100+
101+
val query = doubleArrayOf(11.0, 11.0, 10.0, 11.0, 10.0, 11.0, 11.0, 12.0)
102+
val dimsQuery = longArrayOf(4, 2, 1, 1)
103+
104+
Array(tss, dimsTss).use { t ->
105+
Array(query, dimsQuery).use { q ->
106+
val result = Matrix.findBestNOccurrences(q, t, 4)
107+
108+
val distance = getSingleValueDouble(result[0], 2, 0, 1, 0)
109+
Assert.assertEquals(distance, 1.83880, 1e-3)
110+
111+
val index = getSingleValueInt(result[1], 3, 1, 0, 0)
112+
Assert.assertEquals(index.toLong(), 2)
113+
114+
result[0].close()
115+
result[1].close()
116+
}
117+
}
118+
119+
}
120+
18121

19122
@Test
20123
@Throws(Exception::class)
@@ -277,3 +380,27 @@ class MatrixTest {
277380
}
278381
}
279382
}
383+
384+
private fun getSingleValueDouble(arr: Array, dim0: Long, dim1: Long, dim2: Long, dim3: Long): Double {
385+
val data = arr.getData<DoubleArray>()
386+
387+
val dims4 = arr.dims
388+
var offset = dims4[0] * dims4[1] * dims4[2] * dim3
389+
offset += dims4[0] * dims4[1] * dim2
390+
offset += dims4[0] * dim1
391+
offset += dim0
392+
393+
return data[offset.toInt()]
394+
}
395+
396+
private fun getSingleValueInt(arr: Array, dim0: Long, dim1: Long, dim2: Long, dim3: Long): Int {
397+
val data = arr.getData<IntArray>()
398+
399+
val dims4 = arr.dims
400+
var offset = dims4[0] * dims4[1] * dims4[2] * dim3
401+
offset += dims4[0] * dims4[1] * dim2
402+
offset += dims4[0] * dim1
403+
offset += dim0
404+
405+
return data[offset.toInt()]
406+
}

0 commit comments

Comments
 (0)