ucrdtw 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 768c8ff98e2715907cd54634adecd0e339c49eac
4
+ data.tar.gz: f9693c53387ab2e9b3103bff5b1220e98b13027a
5
+ SHA512:
6
+ metadata.gz: 8e5721af3c15237f5d7dc03b93d05c45abb33da631144685ab46f82903dba14d200167e02e4f52571d684aae5fb405ac1be28d1028a5e42480c1d98c95594898
7
+ data.tar.gz: e9438ed7a11dd15d2cb53c6fa9590fa6388d75fea7d16b00e2874e33a48a69e4179c9b4551a1c0678879e6a4c59c5adc594e72090b5024efc5fc86c9673ed442
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ucrdtw.gemspec
4
+ gemspec
@@ -0,0 +1,39 @@
1
+ # Ucrdtw
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ucrdtw`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'ucrdtw'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install ucrdtw
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ 1. Fork it ( https://github.com/[my-github-username]/ucrdtw/fork )
36
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
37
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
38
+ 4. Push to the branch (`git push origin my-new-feature`)
39
+ 5. Create a new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ucrdtw"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ require 'ffi-compiler/compile_task'
2
+
3
+ FFI::Compiler::CompileTask.new('ucrdtw')
@@ -0,0 +1,1055 @@
1
+ /***********************************************************************/
2
+ /************************* DISCLAIMER **********************************/
3
+ /***********************************************************************/
4
+ /** This UCR Suite software is copyright protected (c) 2012 by **/
5
+ /** Thanawin Rakthanmanon, Bilson Campana, Abdullah Mueen, **/
6
+ /** Gustavo Batista and Eamonn Keogh. **/
7
+ /** **/
8
+ /** Unless stated otherwise, all software is provided free of charge. **/
9
+ /** As well, all software is provided on an "as is" basis without **/
10
+ /** warranty of any kind, express or implied. Under no circumstances **/
11
+ /** and under no legal theory, whether in tort, contract,or otherwise,**/
12
+ /** shall Thanawin Rakthanmanon, Bilson Campana, Abdullah Mueen, **/
13
+ /** Gustavo Batista, or Eamonn Keogh be liable to you or to any other **/
14
+ /** person for any indirect, special, incidental, or consequential **/
15
+ /** damages of any character including, without limitation, damages **/
16
+ /** for loss of goodwill, work stoppage, computer failure or **/
17
+ /** malfunction, or for any and all other damages or losses. **/
18
+ /** **/
19
+ /** If you do not agree with these terms, then you you are advised to **/
20
+ /** not use this software. **/
21
+ /***********************************************************************/
22
+ /***********************************************************************/
23
+ #include <stdio.h>
24
+ #include <stdlib.h>
25
+ #include <math.h>
26
+ #include <string.h>
27
+ #include <time.h>
28
+
29
+ #include "ucrdtw.h"
30
+
31
+ #define min(x,y) ((x)<(y)?(x):(y))
32
+ #define max(x,y) ((x)>(y)?(x):(y))
33
+ #define dist(x,y) ((x-y)*(x-y))
34
+
35
+ #define INF 1e20 // Pseudo-infinite number for this code
36
+
37
+ /// Data structure for sorting the query
38
+ typedef struct index {
39
+ double value;
40
+ int index;
41
+ } index_t;
42
+
43
+ /// Sorting function for the query, sort by abs(z_norm(q[i])) from high to low
44
+ int index_comp(const void* a, const void* b) {
45
+ index_t* x = (index_t*) a;
46
+ index_t* y = (index_t*) b;
47
+ return fabs(y->value) - fabs(x->value); // high to low
48
+ }
49
+
50
+ /// Data structure (circular array) for finding minimum and maximum for LB_Keogh envolop
51
+ typedef struct deque {
52
+ int* dq;
53
+ int size, capacity;
54
+ int f, r;
55
+ } deque_t;
56
+
57
+ /// Initial the queue at the beginning step of envelope calculation
58
+ void deque_init(deque_t* d, int capacity) {
59
+ d->capacity = capacity;
60
+ d->size = 0;
61
+ d->dq = (int*) calloc(d->capacity, sizeof(int));
62
+ d->f = 0;
63
+ d->r = d->capacity - 1;
64
+ }
65
+
66
+ /// Destroy the queue
67
+ void deque_free(deque_t* d) {
68
+ free(d->dq);
69
+ }
70
+
71
+ /// Insert to the queue at the back
72
+ void deque_push_back(deque_t* d, int v) {
73
+ d->dq[d->r] = v;
74
+ d->r--;
75
+ if (d->r < 0)
76
+ d->r = d->capacity - 1;
77
+ d->size++;
78
+ }
79
+
80
+ /// Delete the current (front) element from queue
81
+ void deque_pop_front(deque_t* d) {
82
+ d->f--;
83
+ if (d->f < 0)
84
+ d->f = d->capacity - 1;
85
+ d->size--;
86
+ }
87
+
88
+ /// Delete the last element from queue
89
+ void deque_pop_back(deque_t* d) {
90
+ d->r = (d->r + 1) % d->capacity;
91
+ d->size--;
92
+ }
93
+
94
+ /// Get the value at the current position of the circular queue
95
+ int deque_front(deque_t* d) {
96
+ int aux = d->f - 1;
97
+
98
+ if (aux < 0)
99
+ aux = d->capacity - 1;
100
+ return d->dq[aux];
101
+ }
102
+
103
+ /// Get the value at the last position of the circular queue
104
+ int deque_back(deque_t* d) {
105
+ int aux = (d->r + 1) % d->capacity;
106
+ return d->dq[aux];
107
+ }
108
+
109
+ /// Check whether or not the queue is empty
110
+ int deque_empty(deque_t* d) {
111
+ return d->size == 0;
112
+ }
113
+
114
+ /// Finding the envelope of min and max value for LB_Keogh
115
+ /// Implementation idea is introduced by Danial Lemire in his paper
116
+ /// "Faster Retrieval with a Two-Pass Dynamic-Time-Warping Lower Bound", Pattern Recognition 42(9), 2009.
117
+ void lower_upper_lemire(double *t, int len, int r, double *l, double *u) {
118
+ struct deque du, dl;
119
+ int i;
120
+
121
+ deque_init(&du, 2 * r + 2);
122
+ deque_init(&dl, 2 * r + 2);
123
+
124
+ deque_push_back(&du, 0);
125
+ deque_push_back(&dl, 0);
126
+
127
+ for (i = 1; i < len; i++) {
128
+ if (i > r) {
129
+ u[i - r - 1] = t[deque_front(&du)];
130
+ l[i - r - 1] = t[deque_front(&dl)];
131
+ }
132
+ if (t[i] > t[i - 1]) {
133
+ deque_pop_back(&du);
134
+ while (!deque_empty(&du) && t[i] > t[deque_back(&du)]) {
135
+ deque_pop_back(&du);
136
+ }
137
+ } else {
138
+ deque_pop_back(&dl);
139
+ while (!deque_empty(&dl) && t[i] < t[deque_back(&dl)]) {
140
+ deque_pop_back(&dl);
141
+ }
142
+ }
143
+ deque_push_back(&du, i);
144
+ deque_push_back(&dl, i);
145
+ if (i == 2 * r + 1 + deque_front(&du)) {
146
+ deque_pop_front(&du);
147
+ } else if (i == 2 * r + 1 + deque_front(&dl)) {
148
+ deque_pop_front(&dl);
149
+ }
150
+ }
151
+ for (i = len; i < len + r + 1; i++) {
152
+ u[i - r - 1] = t[deque_front(&du)];
153
+ l[i - r - 1] = t[deque_front(&dl)];
154
+ if (i - deque_front(&du) >= 2 * r + 1) {
155
+ deque_pop_front(&du);
156
+ }
157
+ if (i - deque_front(&dl) >= 2 * r + 1) {
158
+ deque_pop_front(&dl);
159
+ }
160
+ }
161
+ deque_free(&du);
162
+ deque_free(&dl);
163
+ }
164
+
165
+ /// Calculate quick lower bound
166
+ /// Usually, LB_Kim take time O(m) for finding top,bottom,fist and last.
167
+ /// However, because of z-normalization the top and bottom cannot give significant benefits.
168
+ /// And using the first and last points can be computed in constant time.
169
+ /// The pruning power of LB_Kim is non-trivial, especially when the query is not long, say in length 128.
170
+ double lb_kim_hierarchy(double *t, double *q, int j, int len, double mean, double std, double best_so_far) {
171
+ /// 1 point at front and back
172
+ double d, lb;
173
+ double x0 = (t[j] - mean) / std;
174
+ double y0 = (t[(len - 1 + j)] - mean) / std;
175
+ lb = dist(x0,q[0]) + dist(y0, q[len - 1]);
176
+ if (lb >= best_so_far)
177
+ return lb;
178
+
179
+ /// 2 points at front
180
+ double x1 = (t[(j + 1)] - mean) / std;
181
+ d = min(dist(x1,q[0]), dist(x0,q[1]));
182
+ d = min(d, dist(x1,q[1]));
183
+ lb += d;
184
+ if (lb >= best_so_far)
185
+ return lb;
186
+
187
+ /// 2 points at back
188
+ double y1 = (t[(len - 2 + j)] - mean) / std;
189
+ d = min(dist(y1,q[len-1]), dist(y0, q[len-2]));
190
+ d = min(d, dist(y1,q[len-2]));
191
+ lb += d;
192
+ if (lb >= best_so_far)
193
+ return lb;
194
+
195
+ /// 3 points at front
196
+ double x2 = (t[(j + 2)] - mean) / std;
197
+ d = min(dist(x0,q[2]), dist(x1, q[2]));
198
+ d = min(d, dist(x2,q[2]));
199
+ d = min(d, dist(x2,q[1]));
200
+ d = min(d, dist(x2,q[0]));
201
+ lb += d;
202
+ if (lb >= best_so_far)
203
+ return lb;
204
+
205
+ /// 3 points at back
206
+ double y2 = (t[(len - 3 + j)] - mean) / std;
207
+ d = min(dist(y0,q[len-3]), dist(y1, q[len-3]));
208
+ d = min(d, dist(y2,q[len-3]));
209
+ d = min(d, dist(y2,q[len-2]));
210
+ d = min(d, dist(y2,q[len-1]));
211
+ lb += d;
212
+
213
+ return lb;
214
+ }
215
+
216
+ /// LB_Keogh 1: Create Envelope for the query
217
+ /// Note that because the query is known, envelope can be created once at the beginning.
218
+ ///
219
+ /// Variable Explanation,
220
+ /// order : sorted indices for the query.
221
+ /// uo, lo: upper and lower envelops for the query, which already sorted.
222
+ /// t : a circular array keeping the current data.
223
+ /// j : index of the starting location in t
224
+ /// cb : (output) current bound at each position. It will be used later for early abandoning in DTW.
225
+ double lb_keogh_cumulative(int* order, double *t, double *uo, double *lo, double *cb, int j, int len, double mean, double std, double best_so_far) {
226
+ double lb = 0;
227
+ double x, d;
228
+ int i;
229
+
230
+ for (i = 0; i < len && lb < best_so_far; i++) {
231
+ x = (t[(order[i] + j)] - mean) / std;
232
+ d = 0;
233
+ if (x > uo[i]) {
234
+ d = dist(x, uo[i]);
235
+ } else if (x < lo[i]) {
236
+ d = dist(x, lo[i]);
237
+ }
238
+ lb += d;
239
+ cb[order[i]] = d;
240
+ }
241
+ return lb;
242
+ }
243
+
244
+ /// LB_Keogh 2: Create Envelop for the data
245
+ /// Note that the envelops have been created (in main function) when each data point has been read.
246
+ ///
247
+ /// Variable Explanation,
248
+ /// tz: Z-normalized data
249
+ /// qo: sorted query
250
+ /// cb: (output) current bound at each position. Used later for early abandoning in DTW.
251
+ /// l,u: lower and upper envelope of the current data
252
+ double lb_keogh_data_cumulative(int* order, double *tz, double *qo, double *cb, double *l, double *u, int len, double mean, double std, double best_so_far) {
253
+ double lb = 0;
254
+ double uu, ll, d;
255
+ int i;
256
+
257
+ for (i = 0; i < len && lb < best_so_far; i++) {
258
+ uu = (u[order[i]] - mean) / std;
259
+ ll = (l[order[i]] - mean) / std;
260
+ d = 0;
261
+ if (qo[i] > uu) {
262
+ d = dist(qo[i], uu);
263
+ } else {
264
+ if (qo[i] < ll) {
265
+ d = dist(qo[i], ll);
266
+ }
267
+ }
268
+ lb += d;
269
+ cb[order[i]] = d;
270
+ }
271
+ return lb;
272
+ }
273
+
274
+ /// Calculate Dynamic Time Wrapping distance
275
+ /// A,B: data and query, respectively
276
+ /// cb : cummulative bound used for early abandoning
277
+ /// r : size of Sakoe-Chiba warpping band
278
+ double dtw(double* A, double* B, double *cb, int m, int r, double best_so_far) {
279
+ double *cost;
280
+ double *cost_prev;
281
+ double *cost_tmp;
282
+ int i, j, k;
283
+ double x, y, z, min_cost;
284
+
285
+ /// Instead of using matrix of size O(m^2) or O(mr), we will reuse two arrays of size O(r).
286
+ cost = (double*) calloc(2 * r + 1, sizeof(double));
287
+ cost_prev = (double*) calloc(2 * r + 1, sizeof(double));
288
+ for (k = 0; k < 2 * r + 1; k++) {
289
+ cost[k] = INF;
290
+ cost_prev[k] = INF;
291
+ }
292
+
293
+ for (i = 0; i < m; i++) {
294
+ k = max(0, r - i);
295
+ min_cost = INF;
296
+
297
+ for (j = max(0, i - r); j <= min(m - 1, i + r); j++, k++) {
298
+ /// Initialize all row and column
299
+ if ((i == 0) && (j == 0)) {
300
+ cost[k] = dist(A[0], B[0]);
301
+ min_cost = cost[k];
302
+ continue;
303
+ }
304
+
305
+ if ((j - 1 < 0) || (k - 1 < 0)) {
306
+ y = INF;
307
+ } else {
308
+ y = cost[k - 1];
309
+ }
310
+ if ((i - 1 < 0) || (k + 1 > 2 * r)) {
311
+ x = INF;
312
+ } else {
313
+ x = cost_prev[k + 1];
314
+ }
315
+ if ((i - 1 < 0) || (j - 1 < 0)) {
316
+ z = INF;
317
+ } else {
318
+ z = cost_prev[k];
319
+ }
320
+
321
+ /// Classic DTW calculation
322
+ cost[k] = min( min( x, y) , z) + dist(A[i], B[j]);
323
+
324
+ /// Find minimum cost in row for early abandoning (possibly to use column instead of row).
325
+ if (cost[k] < min_cost) {
326
+ min_cost = cost[k];
327
+ }
328
+ }
329
+
330
+ /// We can abandon early if the current cummulative distance with lower bound together are larger than best_so_far
331
+ if (i + r < m - 1 && min_cost + cb[i + r + 1] >= best_so_far) {
332
+ free(cost);
333
+ free(cost_prev);
334
+ return min_cost + cb[i + r + 1];
335
+ }
336
+
337
+ /// Move current array to previous array.
338
+ cost_tmp = cost;
339
+ cost = cost_prev;
340
+ cost_prev = cost_tmp;
341
+ }
342
+ k--;
343
+
344
+ /// the DTW distance is in the last cell in the matrix of size O(m^2) or at the middle of our array.
345
+ double final_dtw = cost_prev[k];
346
+ free(cost);
347
+ free(cost_prev);
348
+ return final_dtw;
349
+ }
350
+
351
+
352
+ /// Calculate the nearest neighbor of a times series in a larger time series expressed as location and distance,
353
+ /// using the UCR suite optimizations.
354
+ int ucrdtw(double* data, long long data_size, double* query, long query_size, double warp_width, int verbose, long long* location, double* distance) {
355
+ long m = query_size;
356
+ int r = warp_width <= 1 ? floor(warp_width * m) : floor(warp_width);
357
+
358
+ double best_so_far; /// best-so-far
359
+ double *q, *t; /// data array
360
+ int *order; ///new order of the query
361
+ double *u, *l, *qo, *uo, *lo, *tz, *cb, *cb1, *cb2, *u_d, *l_d;
362
+
363
+ double d = 0.0;
364
+ long long i, j;
365
+ double ex, ex2, mean, std;
366
+
367
+ long long loc = 0;
368
+ double t1, t2;
369
+ int kim = 0, keogh = 0, keogh2 = 0;
370
+ double dist = 0, lb_kim = 0, lb_k = 0, lb_k2 = 0;
371
+ double *buffer, *u_buff, *l_buff;
372
+ index_t *q_tmp;
373
+
374
+ /// For every EPOCH points, all cumulative values, such as ex (sum), ex2 (sum square), will be restarted for reducing the floating point error.
375
+ int EPOCH = 100000;
376
+
377
+ if (verbose) {
378
+ t1 = clock();
379
+ }
380
+
381
+ /// calloc everything here
382
+ q = (double*) calloc(m, sizeof(double));
383
+ if (q == NULL) {
384
+ printf("ERROR: Memory can't be allocated!\n");
385
+ return -1;
386
+ }
387
+ memcpy((void*)q, (void*)query, m * sizeof(double));
388
+
389
+ qo = (double*) calloc(m, sizeof(double));
390
+ if (qo == NULL) {
391
+ printf("ERROR: Memory can't be allocated!\n");
392
+ return -1;
393
+ }
394
+
395
+ uo = (double*) calloc(m, sizeof(double));
396
+ if (uo == NULL) {
397
+ printf("ERROR: Memory can't be allocated!\n");
398
+ return -1;
399
+ }
400
+
401
+ lo = (double*) calloc(m, sizeof(double));
402
+ if (lo == NULL) {
403
+ printf("ERROR: Memory can't be allocated!\n");
404
+ return -1;
405
+ }
406
+
407
+ order = (int *) calloc(m, sizeof(int));
408
+ if (order == NULL) {
409
+ printf("ERROR: Memory can't be allocated!\n");
410
+ return -1;
411
+ }
412
+
413
+ q_tmp = (index_t *) calloc(m, sizeof(index_t));
414
+ if (q_tmp == NULL) {
415
+ printf("ERROR: Memory can't be allocated!\n");
416
+ return -1;
417
+ }
418
+
419
+ u = (double*) calloc(m, sizeof(double));
420
+ if (u == NULL) {
421
+ printf("ERROR: Memory can't be allocated!\n");
422
+ return -1;
423
+ }
424
+
425
+ l = (double*) calloc(m, sizeof(double));
426
+ if (l == NULL) {
427
+ printf("ERROR: Memory can't be allocated!\n");
428
+ return -1;
429
+ }
430
+
431
+ cb = (double*) calloc(m, sizeof(double));
432
+ if (cb == NULL) {
433
+ printf("ERROR: Memory can't be allocated!\n");
434
+ }
435
+
436
+ cb1 = (double*) calloc(m, sizeof(double));
437
+ if (cb1 == NULL) {
438
+ printf("ERROR: Memory can't be allocated!\n");
439
+ return -1;
440
+ }
441
+
442
+ cb2 = (double*) calloc(m, sizeof(double));
443
+ if (cb2 == NULL) {
444
+ printf("ERROR: Memory can't be allocated!\n");
445
+ return -1;
446
+ }
447
+
448
+ u_d = (double*) calloc(m, sizeof(double));
449
+ if (u == NULL) {
450
+ printf("ERROR: Memory can't be allocated!\n");
451
+ return -1;
452
+ }
453
+
454
+ l_d = (double*) calloc(m, sizeof(double));
455
+ if (l == NULL) {
456
+ printf("ERROR: Memory can't be allocated!\n");
457
+ return -1;
458
+ }
459
+
460
+ t = (double*) calloc(m, sizeof(double) * 2);
461
+ if (t == NULL) {
462
+ printf("ERROR: Memory can't be allocated!\n");
463
+ return -1;
464
+ }
465
+
466
+ tz = (double*) calloc(m, sizeof(double));
467
+ if (tz == NULL) {
468
+ printf("ERROR: Memory can't be allocated!\n");
469
+ return -1;
470
+ }
471
+
472
+ buffer = (double*) calloc(EPOCH, sizeof(double));
473
+ if (buffer == NULL) {
474
+ printf("ERROR: Memory can't be allocated!\n");
475
+ return -1;
476
+ }
477
+
478
+ u_buff = (double*) calloc(EPOCH, sizeof(double));
479
+ if (u_buff == NULL) {
480
+ printf("ERROR: Memory can't be allocated!\n");
481
+ return -1;
482
+ }
483
+
484
+ l_buff = (double*) calloc(EPOCH, sizeof(double));
485
+ if (l_buff == NULL) {
486
+ printf("ERROR: Memory can't be allocated!\n");
487
+ return -1;
488
+ }
489
+
490
+ /// Read query
491
+ best_so_far = INF;
492
+ ex = ex2 = 0;
493
+ for (i = 0; i < m; i++) {
494
+ d = q[i];
495
+ ex += d;
496
+ ex2 += d * d;
497
+ }
498
+ /// Do z-normalize the query, keep in same array, q
499
+ mean = ex / m;
500
+ std = ex2 / m;
501
+ std = sqrt(std - mean * mean);
502
+ for (i = 0; i < m; i++)
503
+ q[i] = (q[i] - mean) / std;
504
+
505
+ /// Create envelope of the query: lower envelope, l, and upper envelope, u
506
+ lower_upper_lemire(q, m, r, l, u);
507
+
508
+ /// Sort the query one time by abs(z-norm(q[i]))
509
+ for (i = 0; i < m; i++) {
510
+ q_tmp[i].value = q[i];
511
+ q_tmp[i].index = i;
512
+ }
513
+ qsort(q_tmp, m, sizeof(index_t), index_comp);
514
+
515
+ /// also create another arrays for keeping sorted envelope
516
+ for (i = 0; i < m; i++) {
517
+ int o = q_tmp[i].index;
518
+ order[i] = o;
519
+ qo[i] = q[o];
520
+ uo[i] = u[o];
521
+ lo[i] = l[o];
522
+ }
523
+
524
+ /// Initial the cummulative lower bound
525
+ for (i = 0; i < m; i++) {
526
+ cb[i] = 0;
527
+ cb1[i] = 0;
528
+ cb2[i] = 0;
529
+ }
530
+
531
+ i = 0; /// current index of the data in current chunk of size EPOCH
532
+ j = 0; /// the starting index of the data in the circular array, t
533
+ ex = ex2 = 0;
534
+ int done = 0;
535
+ int it = 0, ep = 0, k = 0;
536
+ long long I; /// the starting index of the data in current chunk of size EPOCH
537
+ long long data_index = 0;
538
+ while (!done) {
539
+ /// Read first m-1 points
540
+ ep = 0;
541
+ if (it == 0) {
542
+ for (k = 0; k < m - 1 && data_index < data_size; k++) {
543
+ buffer[k] = data[data_index++];
544
+ }
545
+ } else {
546
+ for (k = 0; k < m - 1; k++)
547
+ buffer[k] = buffer[EPOCH - m + 1 + k];
548
+ }
549
+
550
+ /// Read buffer of size EPOCH or when all data has been read.
551
+ ep = m - 1;
552
+ while (ep < EPOCH && data_index < data_size) {
553
+ buffer[ep] = data[data_index++];
554
+ ep++;
555
+ }
556
+
557
+ /// Data are read in chunk of size EPOCH.
558
+ /// When there is nothing to read, the loop is end.
559
+ if (ep <= m - 1) {
560
+ done = 1;
561
+ } else {
562
+ lower_upper_lemire(buffer, ep, r, l_buff, u_buff);
563
+ /// Do main task here..
564
+ ex = 0;
565
+ ex2 = 0;
566
+ for (i = 0; i < ep; i++) {
567
+ /// A bunch of data has been read and pick one of them at a time to use
568
+ d = buffer[i];
569
+
570
+ /// Calcualte sum and sum square
571
+ ex += d;
572
+ ex2 += d * d;
573
+
574
+ /// t is a circular array for keeping current data
575
+ t[i % m] = d;
576
+
577
+ /// Double the size for avoiding using modulo "%" operator
578
+ t[(i % m) + m] = d;
579
+
580
+ /// Start the task when there are more than m-1 points in the current chunk
581
+ if (i >= m - 1) {
582
+ mean = ex / m;
583
+ std = ex2 / m;
584
+ std = sqrt(std - mean * mean);
585
+
586
+ /// compute the start location of the data in the current circular array, t
587
+ j = (i + 1) % m;
588
+ /// the start location of the data in the current chunk
589
+ I = i - (m - 1);
590
+
591
+ /// Use a constant lower bound to prune the obvious subsequence
592
+ lb_kim = lb_kim_hierarchy(t, q, j, m, mean, std, best_so_far);
593
+
594
+ if (lb_kim < best_so_far) {
595
+ /// Use a linear time lower bound to prune; z_normalization of t will be computed on the fly.
596
+ /// uo, lo are envelope of the query.
597
+ lb_k = lb_keogh_cumulative(order, t, uo, lo, cb1, j, m, mean, std, best_so_far);
598
+ if (lb_k < best_so_far) {
599
+ /// Take another linear time to compute z_normalization of t.
600
+ /// Note that for better optimization, this can merge to the previous function.
601
+ for (k = 0; k < m; k++) {
602
+ tz[k] = (t[(k + j)] - mean) / std;
603
+ }
604
+
605
+ /// Use another lb_keogh to prune
606
+ /// qo is the sorted query. tz is unsorted z_normalized data.
607
+ /// l_buff, u_buff are big envelope for all data in this chunk
608
+ lb_k2 = lb_keogh_data_cumulative(order, tz, qo, cb2, l_buff + I, u_buff + I, m, mean, std, best_so_far);
609
+ if (lb_k2 < best_so_far) {
610
+ /// Choose better lower bound between lb_keogh and lb_keogh2 to be used in early abandoning DTW
611
+ /// Note that cb and cb2 will be cumulative summed here.
612
+ if (lb_k > lb_k2) {
613
+ cb[m - 1] = cb1[m - 1];
614
+ for (k = m - 2; k >= 0; k--)
615
+ cb[k] = cb[k + 1] + cb1[k];
616
+ } else {
617
+ cb[m - 1] = cb2[m - 1];
618
+ for (k = m - 2; k >= 0; k--)
619
+ cb[k] = cb[k + 1] + cb2[k];
620
+ }
621
+
622
+ /// Compute DTW and early abandoning if possible
623
+ dist = dtw(tz, q, cb, m, r, best_so_far);
624
+
625
+ if (dist < best_so_far) { /// Update best_so_far
626
+ /// loc is the real starting location of the nearest neighbor in the file
627
+ best_so_far = dist;
628
+ loc = (it) * (EPOCH - m + 1) + i - m + 1;
629
+ }
630
+ } else
631
+ keogh2++;
632
+ } else
633
+ keogh++;
634
+ } else
635
+ kim++;
636
+
637
+ /// Reduce absolute points from sum and sum square
638
+ ex -= t[j];
639
+ ex2 -= t[j] * t[j];
640
+ }
641
+ }
642
+
643
+ /// If the size of last chunk is less then EPOCH, then no more data and terminate.
644
+ if (ep < EPOCH)
645
+ done = 1;
646
+ else
647
+ it++;
648
+ }
649
+ }
650
+
651
+ i = (it) * (EPOCH - m + 1) + ep;
652
+
653
+ free(q);
654
+ free(qo);
655
+ free(uo);
656
+ free(lo);
657
+ free(order);
658
+ free(q_tmp);
659
+ free(u);
660
+ free(l);
661
+ free(cb);
662
+ free(cb1);
663
+ free(cb2);
664
+ free(u_d);
665
+ free(l_d);
666
+ free(t);
667
+ free(tz);
668
+ free(buffer);
669
+ free(u_buff);
670
+ free(l_buff);
671
+
672
+ if (verbose) {
673
+ t2 = clock();
674
+ printf("Location : %lld\n", loc);
675
+ printf("Distance : %.6f\n", sqrt(best_so_far));
676
+ printf("Data Scanned : %lld\n", i);
677
+ printf("Total Execution Time : %.4f secs\n", (t2 - t1) / CLOCKS_PER_SEC);
678
+ printf("\n");
679
+ printf("Pruned by LB_Kim : %6.2f%%\n", ((double) kim / i) * 100);
680
+ printf("Pruned by LB_Keogh : %6.2f%%\n", ((double) keogh / i) * 100);
681
+ printf("Pruned by LB_Keogh2 : %6.2f%%\n", ((double) keogh2 / i) * 100);
682
+ printf("DTW Calculation : %6.2f%%\n", 100 - (((double) kim + keogh + keogh2) / i * 100));
683
+ }
684
+ *location = loc;
685
+ *distance = sqrt(best_so_far);
686
+ return 0;
687
+ }
688
+
689
+ /// Calculate the nearest neighbor of a times series in a larger time series expressed as location and distance,
690
+ /// using the UCR suite optimizations. This function supports streaming the data and the query to search.
691
+ int ucrdtwf(FILE* data_file, FILE* query_file, long query_length, double warp_width, int verbose, long long* location, double* distance) {
692
+ FILE* fp = data_file;
693
+ FILE* qp = query_file;
694
+ long m = query_length;
695
+ int r = warp_width <= 1 ? floor(warp_width * m) : floor(warp_width);
696
+
697
+ double best_so_far; /// best-so-far
698
+ double *t, *q; /// data array and query array
699
+ int *order; ///new order of the query
700
+ double *u, *l, *qo, *uo, *lo, *tz, *cb, *cb1, *cb2, *u_d, *l_d;
701
+
702
+ double d;
703
+ long long i, j;
704
+ double ex, ex2, mean, std;
705
+
706
+ long long loc = 0;
707
+ double t1, t2;
708
+ int kim = 0, keogh = 0, keogh2 = 0;
709
+ double dist = 0, lb_kim = 0, lb_k = 0, lb_k2 = 0;
710
+ double *buffer, *u_buff, *l_buff;
711
+ index_t *q_tmp;
712
+
713
+ /// For every EPOCH points, all cumulative values, such as ex (sum), ex2 (sum square), will be restarted for reducing the floating point error.
714
+ int EPOCH = 100000;
715
+
716
+ if (verbose) {
717
+ t1 = clock();
718
+ }
719
+
720
+ /// calloc everything here
721
+ q = (double*) calloc(m, sizeof(double));
722
+ if (q == NULL) {
723
+ printf("ERROR: Memory can't be allocated!\n");
724
+ return -1;
725
+ }
726
+
727
+ qo = (double*) calloc(m, sizeof(double));
728
+ if (qo == NULL) {
729
+ printf("ERROR: Memory can't be allocated!\n");
730
+ return -1;
731
+ }
732
+
733
+ uo = (double*) calloc(m, sizeof(double));
734
+ if (uo == NULL) {
735
+ printf("ERROR: Memory can't be allocated!\n");
736
+ return -1;
737
+ }
738
+
739
+ lo = (double*) calloc(m, sizeof(double));
740
+ if (lo == NULL) {
741
+ printf("ERROR: Memory can't be allocated!\n");
742
+ return -1;
743
+ }
744
+
745
+ order = (int*) calloc(m, sizeof(int));
746
+ if (order == NULL) {
747
+ printf("ERROR: Memory can't be allocated!\n");
748
+ return -1;
749
+ }
750
+
751
+ q_tmp = (index_t*) calloc(m, sizeof(index_t));
752
+ if (q_tmp == NULL) {
753
+ printf("ERROR: Memory can't be allocated!\n");
754
+ return -1;
755
+ }
756
+
757
+ u = (double*) calloc(m, sizeof(double));
758
+ if (u == NULL) {
759
+ printf("ERROR: Memory can't be allocated!\n");
760
+ return -1;
761
+ }
762
+
763
+ l = (double*) calloc(m, sizeof(double));
764
+ if (l == NULL) {
765
+ printf("ERROR: Memory can't be allocated!\n");
766
+ return -1;
767
+ }
768
+
769
+ cb = (double*) calloc(m, sizeof(double));
770
+ if (cb == NULL) {
771
+ printf("ERROR: Memory can't be allocated!\n");
772
+ }
773
+
774
+ cb1 = (double*) calloc(m, sizeof(double));
775
+ if (cb1 == NULL) {
776
+ printf("ERROR: Memory can't be allocated!\n");
777
+ return -1;
778
+ }
779
+
780
+ cb2 = (double*) calloc(m, sizeof(double));
781
+ if (cb2 == NULL) {
782
+ printf("ERROR: Memory can't be allocated!\n");
783
+ return -1;
784
+ }
785
+
786
+ u_d = (double*) calloc(m, sizeof(double));
787
+ if (u == NULL) {
788
+ printf("ERROR: Memory can't be allocated!\n");
789
+ return -1;
790
+ }
791
+
792
+ l_d = (double*) calloc(m, sizeof(double));
793
+ if (l == NULL) {
794
+ printf("ERROR: Memory can't be allocated!\n");
795
+ return -1;
796
+ }
797
+
798
+ t = (double*) calloc(m, sizeof(double) * 2);
799
+ if (t == NULL) {
800
+ printf("ERROR: Memory can't be allocated!\n");
801
+ return -1;
802
+ }
803
+
804
+ tz = (double*) calloc(m, sizeof(double));
805
+ if (tz == NULL) {
806
+ printf("ERROR: Memory can't be allocated!\n");
807
+ return -1;
808
+ }
809
+
810
+ buffer = (double*) calloc(EPOCH, sizeof(double));
811
+ if (buffer == NULL) {
812
+ printf("ERROR: Memory can't be allocated!\n");
813
+ return -1;
814
+ }
815
+
816
+ u_buff = (double*) calloc(EPOCH, sizeof(double));
817
+ if (u_buff == NULL) {
818
+ printf("ERROR: Memory can't be allocated!\n");
819
+ return -1;
820
+ }
821
+
822
+ l_buff = (double*) calloc(EPOCH, sizeof(double));
823
+ if (l_buff == NULL) {
824
+ printf("ERROR: Memory can't be allocated!\n");
825
+ return -1;
826
+ }
827
+
828
+ /// Read query file
829
+ best_so_far = INF;
830
+ i = 0;
831
+ j = 0;
832
+ ex = ex2 = 0;
833
+
834
+ while (fscanf(qp, "%lf", &d) != EOF && i < m) {
835
+ ex += d;
836
+ ex2 += d * d;
837
+ q[i] = d;
838
+ i++;
839
+ }
840
+
841
+ /// Do z-normalize the query, keep in same array, q
842
+ mean = ex / m;
843
+ std = ex2 / m;
844
+ std = sqrt(std - mean * mean);
845
+ for (i = 0; i < m; i++)
846
+ q[i] = (q[i] - mean) / std;
847
+
848
+ /// Create envelope of the query: lower envelope, l, and upper envelope, u
849
+ lower_upper_lemire(q, m, r, l, u);
850
+
851
+ /// Sort the query one time by abs(z-norm(q[i]))
852
+ for (i = 0; i < m; i++) {
853
+ q_tmp[i].value = q[i];
854
+ q_tmp[i].index = i;
855
+ }
856
+ qsort(q_tmp, m, sizeof(index_t), index_comp);
857
+
858
+ /// also create another arrays for keeping sorted envelope
859
+ for (i = 0; i < m; i++) {
860
+ int o = q_tmp[i].index;
861
+ order[i] = o;
862
+ qo[i] = q[o];
863
+ uo[i] = u[o];
864
+ lo[i] = l[o];
865
+ }
866
+
867
+ /// Initial the cummulative lower bound
868
+ for (i = 0; i < m; i++) {
869
+ cb[i] = 0;
870
+ cb1[i] = 0;
871
+ cb2[i] = 0;
872
+ }
873
+
874
+ i = 0; /// current index of the data in current chunk of size EPOCH
875
+ j = 0; /// the starting index of the data in the circular array, t
876
+ ex = ex2 = 0;
877
+ int done = 0;
878
+ int it = 0, ep = 0, k = 0;
879
+ long long I; /// the starting index of the data in current chunk of size EPOCH
880
+
881
+ while (!done) {
882
+ /// Read first m-1 points
883
+ ep = 0;
884
+ if (it == 0) {
885
+ for (k = 0; k < m - 1; k++)
886
+ if (fscanf(fp, "%lf", &d) != EOF)
887
+ buffer[k] = d;
888
+ } else {
889
+ for (k = 0; k < m - 1; k++)
890
+ buffer[k] = buffer[EPOCH - m + 1 + k];
891
+ }
892
+
893
+ /// Read buffer of size EPOCH or when all data has been read.
894
+ ep = m - 1;
895
+ while (ep < EPOCH) {
896
+ if (fscanf(fp, "%lf", &d) == EOF)
897
+ break;
898
+ buffer[ep] = d;
899
+ ep++;
900
+ }
901
+
902
+ /// Data are read in chunk of size EPOCH.
903
+ /// When there is nothing to read, the loop is end.
904
+ if (ep <= m - 1) {
905
+ done = 1;
906
+ } else {
907
+ lower_upper_lemire(buffer, ep, r, l_buff, u_buff);
908
+ /// Do main task here..
909
+ ex = 0;
910
+ ex2 = 0;
911
+ for (i = 0; i < ep; i++) {
912
+ /// A bunch of data has been read and pick one of them at a time to use
913
+ d = buffer[i];
914
+
915
+ /// Calcualte sum and sum square
916
+ ex += d;
917
+ ex2 += d * d;
918
+
919
+ /// t is a circular array for keeping current data
920
+ t[i % m] = d;
921
+
922
+ /// Double the size for avoiding using modulo "%" operator
923
+ t[(i % m) + m] = d;
924
+
925
+ /// Start the task when there are more than m-1 points in the current chunk
926
+ if (i >= m - 1) {
927
+ mean = ex / m;
928
+ std = ex2 / m;
929
+ std = sqrt(std - mean * mean);
930
+
931
+ /// compute the start location of the data in the current circular array, t
932
+ j = (i + 1) % m;
933
+ /// the start location of the data in the current chunk
934
+ I = i - (m - 1);
935
+
936
+ /// Use a constant lower bound to prune the obvious subsequence
937
+ lb_kim = lb_kim_hierarchy(t, q, j, m, mean, std, best_so_far);
938
+
939
+ if (lb_kim < best_so_far) {
940
+ /// Use a linear time lower bound to prune; z_normalization of t will be computed on the fly.
941
+ /// uo, lo are envelope of the query.
942
+ lb_k = lb_keogh_cumulative(order, t, uo, lo, cb1, j, m, mean, std, best_so_far);
943
+ if (lb_k < best_so_far) {
944
+ /// Take another linear time to compute z_normalization of t.
945
+ /// Note that for better optimization, this can merge to the previous function.
946
+ for (k = 0; k < m; k++) {
947
+ tz[k] = (t[(k + j)] - mean) / std;
948
+ }
949
+
950
+ /// Use another lb_keogh to prune
951
+ /// qo is the sorted query. tz is unsorted z_normalized data.
952
+ /// l_buff, u_buff are big envelope for all data in this chunk
953
+ lb_k2 = lb_keogh_data_cumulative(order, tz, qo, cb2, l_buff + I, u_buff + I, m, mean, std, best_so_far);
954
+ if (lb_k2 < best_so_far) {
955
+ /// Choose better lower bound between lb_keogh and lb_keogh2 to be used in early abandoning DTW
956
+ /// Note that cb and cb2 will be cumulative summed here.
957
+ if (lb_k > lb_k2) {
958
+ cb[m - 1] = cb1[m - 1];
959
+ for (k = m - 2; k >= 0; k--)
960
+ cb[k] = cb[k + 1] + cb1[k];
961
+ } else {
962
+ cb[m - 1] = cb2[m - 1];
963
+ for (k = m - 2; k >= 0; k--)
964
+ cb[k] = cb[k + 1] + cb2[k];
965
+ }
966
+
967
+ /// Compute DTW and early abandoning if possible
968
+ dist = dtw(tz, q, cb, m, r, best_so_far);
969
+
970
+ if (dist < best_so_far) { /// Update best_so_far
971
+ /// loc is the real starting location of the nearest neighbor in the file
972
+ best_so_far = dist;
973
+ loc = (it) * (EPOCH - m + 1) + i - m + 1;
974
+ }
975
+ } else
976
+ keogh2++;
977
+ } else
978
+ keogh++;
979
+ } else
980
+ kim++;
981
+
982
+ /// Reduce absolute points from sum and sum square
983
+ ex -= t[j];
984
+ ex2 -= t[j] * t[j];
985
+ }
986
+ }
987
+
988
+ /// If the size of last chunk is less then EPOCH, then no more data and terminate.
989
+ if (ep < EPOCH)
990
+ done = 1;
991
+ else
992
+ it++;
993
+ }
994
+ }
995
+
996
+ i = (it) * (EPOCH - m + 1) + ep;
997
+
998
+ free(q);
999
+ free(qo);
1000
+ free(uo);
1001
+ free(lo);
1002
+ free(order);
1003
+ free(q_tmp);
1004
+ free(u);
1005
+ free(l);
1006
+ free(cb);
1007
+ free(cb1);
1008
+ free(cb2);
1009
+ free(u_d);
1010
+ free(l_d);
1011
+ free(t);
1012
+ free(tz);
1013
+ free(buffer);
1014
+ free(u_buff);
1015
+ free(l_buff);
1016
+
1017
+ if (verbose) {
1018
+ t2 = clock();
1019
+ printf("Data Scanned : %lld\n", i);
1020
+ printf("Total Execution Time : %.4f secs\n", (t2 - t1) / CLOCKS_PER_SEC);
1021
+ printf("\n");
1022
+ printf("Pruned by LB_Kim : %6.2f%%\n", ((double) kim / i) * 100);
1023
+ printf("Pruned by LB_Keogh : %6.2f%%\n", ((double) keogh / i) * 100);
1024
+ printf("Pruned by LB_Keogh2 : %6.2f%%\n", ((double) keogh2 / i) * 100);
1025
+ printf("DTW Calculation : %6.2f%%\n", 100 - (((double) kim + keogh + keogh2) / i * 100));
1026
+ }
1027
+ *location = loc;
1028
+ *distance = sqrt(best_so_far);
1029
+ return 0;
1030
+ }
1031
+
1032
+ int main(int argc, char** argv) {
1033
+ if (argc < 5) {
1034
+ printf("Error: Invalid arguments\n");
1035
+ printf("Command usage: dtwfind <data-file> <query-file> <query-length> <warp-width>\n\n");
1036
+ printf("For example: dtwfind data.txt query.txt 128 0.05\n");
1037
+ return -2;
1038
+ }
1039
+
1040
+ FILE* data_file = fopen(argv[1], "r");
1041
+ if (data_file == NULL) {
1042
+ printf("ERROR: File not found: %s\n", argv[1]);
1043
+ }
1044
+
1045
+ FILE* query_file = fopen(argv[2], "r");
1046
+ if (query_file == NULL) {
1047
+ printf("ERROR: File not found: %s\n", argv[2]);
1048
+ }
1049
+
1050
+ long long location = -1;
1051
+ double distance = -1;
1052
+ int ret = ucrdtwf(data_file, query_file, atoll(argv[3]), atof(argv[4]), 1, &location, &distance);
1053
+ printf("Location: %lld\nDistance: %.6f\n", location, distance);
1054
+ return ret;
1055
+ }
@@ -0,0 +1,4 @@
1
+
2
+ int ucrdtw(double* data, long long data_size, double* query, long query_size, double warp_width, int verbose, long long* location, double* distance);
3
+
4
+ int ucrdtwf(FILE* data_stream, FILE* query_stream, long query_length, double warp_width, int verbose, long long* location, double* distance);
@@ -0,0 +1,36 @@
1
+ require "ucrdtw/version"
2
+ require 'ffi'
3
+ require 'ffi-compiler/loader'
4
+
5
+ module Ucrdtw
6
+ extend FFI::Library
7
+ ffi_lib FFI::Compiler::Loader.find('ucrdtw')
8
+
9
+ attach_function :ucrdtw, [:pointer, :long_long, :pointer, :long, :double, :int, :pointer, :pointer], :int
10
+
11
+ class <<self
12
+ def verbose=(verbose)
13
+ @verbose = verbose
14
+ end
15
+
16
+ def verbose
17
+ @verbose ||= 0
18
+ end
19
+ end
20
+
21
+ def self.dtw(data, query, warp_width = 0.05)
22
+ dataBuf = FFI::MemoryPointer.new(:double, data.size)
23
+ dataBuf.write_array_of_double(data)
24
+
25
+ queryBuf = FFI::MemoryPointer.new(:double, query.size)
26
+ queryBuf.write_array_of_double(query)
27
+
28
+ location = FFI::MemoryPointer.new(:long_long, 1)
29
+ distance = FFI::MemoryPointer.new(:double, 1)
30
+
31
+ ucrdtw(dataBuf, data.length, queryBuf, query.length, warp_width, verbose, location, distance)
32
+
33
+ [location.read_long_long, distance.read_double]
34
+ end
35
+
36
+ end
@@ -0,0 +1,3 @@
1
+ module Ucrdtw
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ucrdtw/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ucrdtw"
8
+ spec.version = Ucrdtw::VERSION
9
+ spec.authors = ["Chris Beer"]
10
+ spec.email = ["chris@cbeer.info"]
11
+
12
+ spec.summary = %q{UCR DTW FFI wrapper}
13
+ spec.homepage = "https://github.com/cbeer/ucrdtw"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.extensions << 'ext/Rakefile'
21
+ spec.add_dependency 'ffi-compiler'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.9"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ucrdtw
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Beer
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-05-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi-compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description:
56
+ email:
57
+ - chris@cbeer.info
58
+ executables: []
59
+ extensions:
60
+ - ext/Rakefile
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
66
+ - Gemfile
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - ext/Rakefile
72
+ - ext/ucrdtw.c
73
+ - ext/ucrdtw.h
74
+ - lib/ucrdtw.rb
75
+ - lib/ucrdtw/version.rb
76
+ - ucrdtw.gemspec
77
+ homepage: https://github.com/cbeer/ucrdtw
78
+ licenses: []
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.4.5
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: UCR DTW FFI wrapper
100
+ test_files: []