ucrdtw 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []