Skip to content

Commit 8f6d9c1

Browse files
committed
perf: fuse RRA normalized distance and precompute z-norm windows (Phase 4).
Replace per-call vector allocations in _normalized_distance with fused slice z-norm/PAA and thread_local work buffers; precompute w_size z-normed windows for the common equal-length case.
1 parent f569288 commit 8f6d9c1

3 files changed

Lines changed: 204 additions & 24 deletions

File tree

inst/include/jmotif.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ std::vector<double> _znorm(const std::vector<double>& ts, double threshold);
329329
void _znorm_slice(const std::vector<double>& ts, int start, int end,
330330
double threshold, std::vector<double>& out);
331331
std::vector<double> _paa2(const std::vector<double>& ts, int paa_num);
332+
void _paa2_range(const std::vector<double>& ts, int start, int end, int paa_num,
333+
std::vector<double>& out);
332334
double _euclidean_dist(std::vector<double>* seq1, std::vector<double>* seq2);
333335
double _early_abandoned_dist(std::vector<double>* seq1, std::vector<double>* seq2,
334336
double upper_limit);

src/paa.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,59 @@ std::vector<double> _paa2(const std::vector<double>& ts, int paa_num) {
8080
}
8181
return res;
8282
}
83+
84+
void _paa2_range(const std::vector<double>& ts, int start, int end, int paa_num,
85+
std::vector<double>& out) {
86+
87+
int len = end - start;
88+
89+
if(len < paa_num){
90+
stop("'paa_num' size is invalid");
91+
}
92+
93+
if (len == paa_num) {
94+
out.resize(paa_num);
95+
for (int i = 0; i < paa_num; i++) {
96+
out[i] = ts[start + i];
97+
}
98+
return;
99+
}
100+
101+
out.assign(paa_num, 0.0);
102+
double points_per_segment = (double) len / (double) paa_num;
103+
104+
std::vector<double> breaks(paa_num + 1, 0);
105+
for(int i = 0; i < paa_num + 1; i++){
106+
breaks[i] = i * points_per_segment;
107+
}
108+
breaks[paa_num] = (double) len;
109+
110+
for(int i = 0; i < paa_num; i++){
111+
112+
double seg_start = breaks[i];
113+
double seg_end = breaks[i+1];
114+
115+
double frac_begin = ceil(seg_start) - seg_start;
116+
double frac_end = seg_end - floor(seg_end);
117+
118+
int full_begin = (int)floor(seg_start);
119+
int full_end = (int)ceil(seg_end);
120+
if(full_end > len) {
121+
full_end = len;
122+
}
123+
124+
double sum_of_elems = 0.0;
125+
for (int j = full_begin; j < full_end; j++) {
126+
double v = ts[start + j];
127+
if (j == full_begin && frac_begin > 0) {
128+
v *= frac_begin;
129+
}
130+
if (j == full_end - 1 && frac_end > 0) {
131+
v *= frac_end;
132+
}
133+
sum_of_elems += v;
134+
}
135+
136+
out[i] = sum_of_elems / points_per_segment;
137+
}
138+
}

src/rra.cpp

Lines changed: 146 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,146 @@ struct sort_intervals {
3131
// length, the longer is first PAA-reduced (_paa2) to the shorter length. The
3232
// result is euclidean(znorm(a), znorm(b)) / count, divided by the number of
3333
// compared points so spans of different lengths stay comparable.
34+
namespace {
35+
36+
double slice_mean(const std::vector<double>& ts, int start, int end) {
37+
double sum = 0.0;
38+
for (int i = start; i < end; i++) {
39+
sum += ts[i];
40+
}
41+
return sum / (end - start);
42+
}
43+
44+
double slice_stdev_pop(const std::vector<double>& ts, int start, int end, double mean) {
45+
double sq_sum = 0.0;
46+
for (int i = start; i < end; i++) {
47+
double d = ts[i] - mean;
48+
sq_sum += d * d;
49+
}
50+
return std::sqrt(sq_sum / (end - start));
51+
}
52+
53+
double vec_mean(const std::vector<double>& v) {
54+
double sum = 0.0;
55+
for (double x : v) {
56+
sum += x;
57+
}
58+
return sum / v.size();
59+
}
60+
61+
double vec_stdev_pop(const std::vector<double>& v, double mean) {
62+
double sq_sum = 0.0;
63+
for (double x : v) {
64+
double d = x - mean;
65+
sq_sum += d * d;
66+
}
67+
return std::sqrt(sq_sum / v.size());
68+
}
69+
70+
double znormed_rms_dist_equal(
71+
const std::vector<double>& ts, int s1, int e1, int s2, int e2,
72+
double n_threshold) {
73+
int n = e1 - s1;
74+
double m1 = slice_mean(ts, s1, e1);
75+
double m2 = slice_mean(ts, s2, e2);
76+
double sd1 = slice_stdev_pop(ts, s1, e1, m1);
77+
double sd2 = slice_stdev_pop(ts, s2, e2, m2);
78+
bool raw1 = sd1 < n_threshold;
79+
bool raw2 = sd2 < n_threshold;
80+
81+
double res = 0.0;
82+
for (int i = 0; i < n; i++) {
83+
double a = raw1 ? ts[s1 + i] : (ts[s1 + i] - m1) / sd1;
84+
double b = raw2 ? ts[s2 + i] : (ts[s2 + i] - m2) / sd2;
85+
double d = a - b;
86+
res += d * d;
87+
}
88+
return std::sqrt(res) / n;
89+
}
90+
91+
double znormed_rms_dist_slice_vec(
92+
const std::vector<double>& ts, int s1, int e1,
93+
const std::vector<double>& b, double n_threshold) {
94+
int n = e1 - s1;
95+
double m1 = slice_mean(ts, s1, e1);
96+
double m2 = vec_mean(b);
97+
double sd1 = slice_stdev_pop(ts, s1, e1, m1);
98+
double sd2 = vec_stdev_pop(b, m2);
99+
bool raw1 = sd1 < n_threshold;
100+
bool raw2 = sd2 < n_threshold;
101+
102+
double res = 0.0;
103+
for (int i = 0; i < n; i++) {
104+
double a = raw1 ? ts[s1 + i] : (ts[s1 + i] - m1) / sd1;
105+
double bv = raw2 ? b[i] : (b[i] - m2) / sd2;
106+
double d = a - bv;
107+
res += d * d;
108+
}
109+
return std::sqrt(res) / n;
110+
}
111+
112+
double znormed_rms_dist_vec_slice(
113+
const std::vector<double>& a,
114+
const std::vector<double>& ts, int s2, int e2,
115+
double n_threshold) {
116+
int n = a.size();
117+
double m1 = vec_mean(a);
118+
double m2 = slice_mean(ts, s2, e2);
119+
double sd1 = vec_stdev_pop(a, m1);
120+
double sd2 = slice_stdev_pop(ts, s2, e2, m2);
121+
bool raw1 = sd1 < n_threshold;
122+
bool raw2 = sd2 < n_threshold;
123+
124+
double res = 0.0;
125+
for (int i = 0; i < n; i++) {
126+
double av = raw1 ? a[i] : (a[i] - m1) / sd1;
127+
double b = raw2 ? ts[s2 + i] : (ts[s2 + i] - m2) / sd2;
128+
double d = av - b;
129+
res += d * d;
130+
}
131+
return std::sqrt(res) / n;
132+
}
133+
134+
double znormed_rms_dist_precomputed(
135+
const std::vector<double>& za, const std::vector<double>& zb) {
136+
double res = 0.0;
137+
int n = za.size();
138+
for (int i = 0; i < n; i++) {
139+
double d = za[i] - zb[i];
140+
res += d * d;
141+
}
142+
return std::sqrt(res) / n;
143+
}
144+
145+
} // namespace
146+
34147
double _normalized_distance(int start1, int end1, int start2, int end2,
35-
std::vector<double> *series, double n_threshold){
148+
const std::vector<double>& series, double n_threshold,
149+
int w_size,
150+
const std::vector<std::vector<double>>* znorm_windows) {
36151

37-
double res = 0;
38152
int len1 = end1 - start1;
39153
int len2 = end2 - start2;
40-
int count = std::min(len1, len2);
41-
42-
std::vector<double> a, b;
43-
if(len1 == len2){
44-
a.assign(series->begin() + start1, series->begin() + end1);
45-
b.assign(series->begin() + start2, series->begin() + end2);
46-
} else if(len1 < len2){
47-
a.assign(series->begin() + start1, series->begin() + end1);
48-
std::vector<double> longer(series->begin() + start2, series->begin() + end2);
49-
b = _paa2(longer, len1);
50-
} else {
51-
b.assign(series->begin() + start2, series->begin() + end2);
52-
std::vector<double> longer(series->begin() + start1, series->begin() + end1);
53-
a = _paa2(longer, len2);
154+
155+
if (znorm_windows != nullptr && len1 == w_size && len2 == w_size &&
156+
start1 >= 0 && start2 >= 0 &&
157+
start1 < (int)znorm_windows->size() &&
158+
start2 < (int)znorm_windows->size()) {
159+
return znormed_rms_dist_precomputed(
160+
znorm_windows->at(start1), znorm_windows->at(start2));
54161
}
55162

56-
std::vector<double> za = _znorm(a, n_threshold);
57-
std::vector<double> zb = _znorm(b, n_threshold);
58-
for(int i=0; i<count; i++){
59-
res = res + (za[i] - zb[i]) * (za[i] - zb[i]);
163+
static thread_local std::vector<double> paa_buf;
164+
165+
if (len1 == len2) {
166+
return znormed_rms_dist_equal(series, start1, end1, start2, end2, n_threshold);
167+
}
168+
if (len1 < len2) {
169+
_paa2_range(series, start2, end2, len1, paa_buf);
170+
return znormed_rms_dist_slice_vec(series, start1, end1, paa_buf, n_threshold);
60171
}
61-
return sqrt(res) / (double) count;
172+
_paa2_range(series, start1, end1, len2, paa_buf);
173+
return znormed_rms_dist_vec_slice(paa_buf, series, start2, end2, n_threshold);
62174
}
63175

64176
double _shrinked_distance(int start1, int end1, int start2, int end2, std::vector<double> *series){
@@ -98,6 +210,7 @@ rra_discord_record find_best_rra_discord(std::vector<double> *ts, int w_size,
98210
std::unordered_map<int, rule_record> *grammar, std::vector<int> *indexes,
99211
std::vector<rule_interval> *intervals,
100212
std::unordered_set<int> *global_visited_positions, double n_threshold,
213+
const std::vector<std::vector<double>>* znorm_windows,
101214
int seed = -1){
102215

103216
// *****
@@ -174,7 +287,8 @@ rra_discord_record find_best_rra_discord(std::vector<double> *ts, int w_size,
174287
// end << std::endl;
175288
distance_calls_counter++;
176289
double dist = _normalized_distance(c_interval.start, c_interval.end,
177-
start, end, ts, n_threshold);
290+
start, end, *ts, n_threshold,
291+
w_size, znorm_windows);
178292
// keep track of best so far distance
179293
if (dist < nn_distance) {
180294
// Rcout << " better nn distance found " << dist << std::endl;
@@ -255,7 +369,8 @@ rra_discord_record find_best_rra_discord(std::vector<double> *ts, int w_size,
255369
distance_calls_counter++;
256370
double dist = _normalized_distance(
257371
c_interval.start, c_interval.end,
258-
randomInterval.start, randomInterval.end, ts, n_threshold
372+
randomInterval.start, randomInterval.end, *ts, n_threshold,
373+
w_size, znorm_windows
259374
);
260375

261376
if (dist < bestSoFarDistance) {
@@ -608,14 +723,21 @@ Rcpp::DataFrame find_discords_rra(NumericVector series, int w_size, int paa_size
608723
std::unordered_set<int> global_visited_positions;
609724
// global_visited_positions.reserve(ts.size());
610725

726+
int n_windows = (int)ts.size() - w_size + 1;
727+
std::vector<std::vector<double>> znorm_windows(n_windows);
728+
for (int wi = 0; wi < n_windows; wi++) {
729+
_znorm_slice(ts, wi, wi + w_size, n_threshold, znorm_windows[wi]);
730+
}
731+
611732
std::vector<rra_discord_record> discords;
612733

613734
while((int) discords.size() < discords_num){
614735

615736
// tstart = std::chrono::system_clock::now();
616737

617738
rra_discord_record d = find_best_rra_discord(&ts, w_size, &grammar,
618-
&indexes, &intervals, &global_visited_positions, n_threshold, seed);
739+
&indexes, &intervals, &global_visited_positions, n_threshold,
740+
&znorm_windows, seed);
619741
// Rcout << d.nn_distance;
620742

621743
if(d.nn_distance<0){

0 commit comments

Comments
 (0)