From 63e1ddb1e3472bb95059402d72d4ffb851c71591 Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Wed, 26 May 2021 12:21:36 -0700 Subject: [PATCH 01/24] introduce stateful API --- tsne/bh_sne_src/sptree.cpp | 4 +- tsne/bh_sne_src/tsne.cpp | 144 ++++++++++++++++++++++++++++--------- tsne/bh_sne_src/tsne.h | 25 ++++++- tsne/bh_sne_src/vptree.h | 4 +- 4 files changed, 138 insertions(+), 39 deletions(-) diff --git a/tsne/bh_sne_src/sptree.cpp b/tsne/bh_sne_src/sptree.cpp index dbad881..0c3dc8b 100644 --- a/tsne/bh_sne_src/sptree.cpp +++ b/tsne/bh_sne_src/sptree.cpp @@ -31,14 +31,14 @@ * */ +#include "sptree.h" + #include #include #include #include #include -#include "sptree.h" - #pragma GCC visibility push(hidden) using std::array; diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index da43130..4465ec6 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -31,6 +31,8 @@ * */ +#include "tsne.h" + #include #include #include @@ -42,7 +44,6 @@ #include #include "sptree.h" -#include "tsne.h" #include "vptree.h" #pragma GCC visibility push(hidden) @@ -56,6 +57,16 @@ using std::move; using std::sqrt; using std::vector; +struct TSNEState { + std::vector P; + std::vector row_P; + std::vector col_P; + std::vector val_P; + std::vector dY; + std::vector uY; + std::vector gains; +}; + namespace { template @@ -576,11 +587,12 @@ double randn() { return x; } -// Perform t-SNE +// Initialize t-SNE template -void run(double* X, int N, int D, double* Y, double perplexity, double theta, - int rand_seed, bool skip_random_init, double* init, bool use_init, - int max_iter, int stop_lying_iter, int mom_switch_iter) { +struct TSNE* init_tsne(double* X, int N, int D, double* Y, double perplexity, + double theta, int rand_seed, bool skip_random_init, + double* init, bool use_init, int max_iter, + int stop_lying_iter, int mom_switch_iter) { // Set random seed if (skip_random_init != true) { if (rand_seed >= 0) { @@ -605,11 +617,7 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta, "Using max_iter = %d, stop_lying_iter = %d, mom_switch_iter = %d\n", max_iter, stop_lying_iter, mom_switch_iter); - // Set learning parameters - float total_time = .0; clock_t start, end; - double momentum = .5, final_momentum = .8; - double eta = 200.0; // Allocate some memory vector dY(N * NDIMS); @@ -706,16 +714,58 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta, "%f)!\nLearning embedding...\n", (float)(end - start) / CLOCKS_PER_SEC, (double)row_P[N] / ((double)N * (double)N)); - start = clock(); - for (int iter = 0; iter < max_iter; iter++) { + TSNEState* state = new TSNEState; + state->P = P; + state->row_P = row_P; + state->col_P = col_P; + state->val_P = val_P; + state->dY = dY; + state->uY = uY; + state->gains = gains; + TSNE* tsne = new TSNE; + tsne->N = N; + tsne->Y = Y; + tsne->no_dims = NDIMS; + tsne->theta = theta; + tsne->max_iter = max_iter; + tsne->stop_lying_iter = stop_lying_iter; + tsne->mom_switch_iter = mom_switch_iter; + tsne->state = state; + tsne->iter = 0; + tsne->total_time = 0; + + return tsne; +} + +// Optimize t-SNE +template +bool run_n(int n, struct TSNE* tsne) { + // Set learning parameters + double momentum = .5, final_momentum = .8; + double eta = 200.0; + + // Extract state + int N = tsne->N; + double* Y = tsne->Y; + double theta = tsne->theta; + bool exact = (theta == .0) ? true : false; + TSNEState* state = tsne->state; + vector& dY = state->dY; + vector& uY = state->uY; + clock_t start = clock(), end; + int max_iter = std::min(tsne->iter + n, tsne->max_iter); + + for (int& iter = tsne->iter; iter < max_iter; iter++) { // Compute (approximate) gradient if (exact) - computeExactGradient(P, Y, N, dY); + computeExactGradient(state->P, Y, N, dY); else - computeGradient(row_P, col_P, val_P, Y, N, dY, theta); + computeGradient(state->row_P, state->col_P, state->val_P, Y, N, dY, + theta); // Update gains + vector& gains = state->gains; for (int i = 0; i < N * NDIMS; i++) gains[i] = (sign(dY[i]) != sign(uY[i])) ? (gains[i] + .2) : (gains[i] * .8); @@ -733,16 +783,16 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta, zeroMean(Y, N); // Stop lying about the P-values after a while, and switch momentum - if (iter == stop_lying_iter) { + if (iter == tsne->stop_lying_iter) { if (exact) { for (int i = 0; i < N * N; i++) - P[i] /= 12.0; + state->P[i] /= 12.0; } else { - for (int i = 0; i < row_P[N]; i++) - val_P[i] /= 12.0; + for (int i = 0; i < state->row_P[N]; i++) + state->val_P[i] /= 12.0; } } - if (iter == mom_switch_iter) + if (iter == tsne->mom_switch_iter) momentum = final_momentum; // Print out progress @@ -750,15 +800,16 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta, end = clock(); double C = .0; if (exact) { - C = evaluateError(P, Y, N); + C = evaluateError(state->P, Y, N); } else { // doing approximate computation here! - C = evaluateError(row_P, col_P, val_P, Y, N, theta); + C = evaluateError(state->row_P, state->col_P, state->val_P, Y, N, + theta); } if (iter == 0) fprintf(stderr, "Iteration %d: error is %f\n", iter + 1, C); else { - total_time += (float)(end - start) / CLOCKS_PER_SEC; + tsne->total_time += (float)(end - start) / CLOCKS_PER_SEC; fprintf(stderr, "Iteration %d: error is %f (50 iterations in %4.2f seconds)\n", iter, C, (float)(end - start) / CLOCKS_PER_SEC); @@ -767,34 +818,59 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta, } } end = clock(); - total_time += (float)(end - start) / CLOCKS_PER_SEC; + tsne->total_time += (float)(end - start) / CLOCKS_PER_SEC; - fprintf(stderr, "Fitting performed in %4.2f seconds.\n", total_time); + if (tsne->iter >= tsne->max_iter) { + fprintf(stderr, "Fitting performed in %4.2f seconds.\n", tsne->total_time); + return true; + } + return false; } } // namespace extern "C" { - -void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, - double perplexity, double theta, int rand_seed, - bool skip_random_init, double* init, bool use_init, - int max_iter, int stop_lying_iter, int mom_switch_iter) { +struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, + int no_dims, double perplexity, double theta, + int rand_seed, bool skip_random_init, + double* init, bool use_init, int max_iter, + int stop_lying_iter, int mom_switch_iter) { assert(no_dims == 2 || no_dims == 3); switch (no_dims) { case 2: - run<2>(X, N, D, Y, perplexity, theta, rand_seed, skip_random_init, init, - use_init, max_iter, stop_lying_iter, mom_switch_iter); - break; + return init_tsne<2>(X, N, D, Y, perplexity, theta, rand_seed, + skip_random_init, init, use_init, max_iter, + stop_lying_iter, mom_switch_iter); case 3: - run<3>(X, N, D, Y, perplexity, theta, rand_seed, skip_random_init, init, - use_init, max_iter, stop_lying_iter, mom_switch_iter); - break; + return init_tsne<3>(X, N, D, Y, perplexity, theta, rand_seed, + skip_random_init, init, use_init, max_iter, + stop_lying_iter, mom_switch_iter); default: throw "invalid dimension"; } } +bool DLL_PUBLIC run_n(int n, struct TSNE* tsne) { + switch (tsne->no_dims) { + case 2: + return run_n<2>(n, tsne); + case 3: + return run_n<3>(n, tsne); + default: + throw "invalid dimension"; + } +} + +void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, + double perplexity, double theta, int rand_seed, + bool skip_random_init, double* init, bool use_init, + int max_iter, int stop_lying_iter, int mom_switch_iter) { + struct TSNE* tsne = init_tsne(X, N, D, Y, no_dims, perplexity, theta, + rand_seed, skip_random_init, init, use_init, + max_iter, stop_lying_iter, mom_switch_iter); + run_n(max_iter, tsne); +} + } // extern "C" #pragma GCC visibility pop diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index 6ec8e05..e48eea3 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -49,12 +49,35 @@ #endif extern "C" { - +// stateless t-SNE void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, int rand_seed, bool skip_random_init, double* init, bool use_init, int max_iter = 1000, int stop_lying_iter = 250, int mom_switch_iter = 250); +// stateful t-SNE +struct TSNEState; +struct TSNE { + // parameters + int N; + double* Y; + int no_dims; + double theta; + int max_iter; + int stop_lying_iter; + int mom_switch_iter; + struct TSNEState* state; + int iter; + float total_time; +}; +struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, + int no_dims, double perplexity, double theta, + int rand_seed, bool skip_random_init, + double* init, bool use_init, + int max_iter = 1000, + int stop_lying_iter = 250, + int mom_switch_iter = 250); +bool DLL_PUBLIC run_n(int n, struct TSNE* tsne); } #endif diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index c9387ce..b7bdd49 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -58,8 +58,8 @@ class euclidean_distance final { euclidean_distance(const euclidean_distance&) = default; double operator()(const unsigned int t1, const unsigned int t2) const { double dd = .0; - const double* x1 = data+t1*_D; - const double* x2 = data+t2*_D; + const double* x1 = data + t1 * _D; + const double* x2 = data + t2 * _D; for (int d = 0; d < _D; d++) { double diff = (x1[d] - x2[d]); dd += diff * diff; From 77a7e1b0ab95f06fe348b6c305f385d7cae1e58d Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Wed, 26 May 2021 13:04:50 -0700 Subject: [PATCH 02/24] add API to free memory, use it --- tsne/bh_sne_src/tsne.cpp | 6 ++++++ tsne/bh_sne_src/tsne.h | 1 + 2 files changed, 7 insertions(+) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 4465ec6..2e050d5 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -861,6 +861,11 @@ bool DLL_PUBLIC run_n(int n, struct TSNE* tsne) { } } +void DLL_PUBLIC free_tsne(struct TSNE* tsne) { + delete tsne->state; + delete tsne; +} + void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, int rand_seed, bool skip_random_init, double* init, bool use_init, @@ -869,6 +874,7 @@ void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, rand_seed, skip_random_init, init, use_init, max_iter, stop_lying_iter, mom_switch_iter); run_n(max_iter, tsne); + free_tsne(tsne); } } // extern "C" diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index e48eea3..18c98d8 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -78,6 +78,7 @@ struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, int stop_lying_iter = 250, int mom_switch_iter = 250); bool DLL_PUBLIC run_n(int n, struct TSNE* tsne); +void DLL_PUBLIC free_tsne(struct TSNE* tsne); } #endif From 1be20c48af83f773ed5779c7805541d4de808e18 Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Wed, 26 May 2021 13:08:21 -0700 Subject: [PATCH 03/24] safer free_tsne API --- tsne/bh_sne_src/tsne.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 2e050d5..75e3ef0 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -862,6 +862,8 @@ bool DLL_PUBLIC run_n(int n, struct TSNE* tsne) { } void DLL_PUBLIC free_tsne(struct TSNE* tsne) { + if (tsne == nullptr) + return; delete tsne->state; delete tsne; } From 00bc9fc24c2ae77f681060f6e95501766fa34949 Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Wed, 26 May 2021 17:55:44 -0700 Subject: [PATCH 04/24] fix elapsed time reporting --- tsne/bh_sne_src/tsne.cpp | 15 +++++++++++---- tsne/bh_sne_src/tsne.h | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 75e3ef0..0018fa8 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -734,6 +734,7 @@ struct TSNE* init_tsne(double* X, int N, int D, double* Y, double perplexity, tsne->state = state; tsne->iter = 0; tsne->total_time = 0; + tsne->residual_time = 0; return tsne; } @@ -754,6 +755,7 @@ bool run_n(int n, struct TSNE* tsne) { vector& dY = state->dY; vector& uY = state->uY; clock_t start = clock(), end; + float elapsed; int max_iter = std::min(tsne->iter + n, tsne->max_iter); for (int& iter = tsne->iter; iter < max_iter; iter++) { @@ -796,7 +798,7 @@ bool run_n(int n, struct TSNE* tsne) { momentum = final_momentum; // Print out progress - if (iter > 0 && (iter % 50 == 0 || iter == max_iter - 1)) { + if (iter > 0 && (iter % 50 == 0 || iter == tsne->max_iter - 1)) { end = clock(); double C = .0; if (exact) { @@ -809,16 +811,21 @@ bool run_n(int n, struct TSNE* tsne) { if (iter == 0) fprintf(stderr, "Iteration %d: error is %f\n", iter + 1, C); else { - tsne->total_time += (float)(end - start) / CLOCKS_PER_SEC; + elapsed = (float)(end - start) / CLOCKS_PER_SEC; + tsne->total_time += elapsed; + tsne->residual_time += elapsed; fprintf(stderr, "Iteration %d: error is %f (50 iterations in %4.2f seconds)\n", - iter, C, (float)(end - start) / CLOCKS_PER_SEC); + iter, C, tsne->residual_time); + tsne->residual_time = 0; } start = clock(); } } end = clock(); - tsne->total_time += (float)(end - start) / CLOCKS_PER_SEC; + elapsed = (float)(end - start) / CLOCKS_PER_SEC; + tsne->total_time += elapsed; + tsne->residual_time += elapsed; if (tsne->iter >= tsne->max_iter) { fprintf(stderr, "Fitting performed in %4.2f seconds.\n", tsne->total_time); diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index 18c98d8..7edfc04 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -69,6 +69,7 @@ struct TSNE { struct TSNEState* state; int iter; float total_time; + float residual_time; }; struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, From 86ba1734591c1b2abff307145c5c51be495025e8 Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Wed, 26 May 2021 23:27:10 -0700 Subject: [PATCH 05/24] fix reviewer comments this stuffs most pointer badness into extern "C" blocks --- tsne/bh_sne_src/tsne.cpp | 172 ++++++++++++++++++++------------------- tsne/bh_sne_src/tsne.h | 18 +--- 2 files changed, 92 insertions(+), 98 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 0018fa8..f482f22 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -55,9 +56,30 @@ using std::fprintf; using std::log; using std::move; using std::sqrt; +using std::unique_ptr; using std::vector; +namespace { + struct TSNEState { + public: + bool step_by(int n); + + private: + template + bool step_by_impl(int n); + + public: + int N; + double* Y; + int no_dims; + double theta; + int max_iter; + int stop_lying_iter; + int mom_switch_iter; + int iter; + float total_time; + float residual_time; std::vector P; std::vector row_P; std::vector col_P; @@ -67,8 +89,6 @@ struct TSNEState { std::vector gains; }; -namespace { - template void run(double* X, int N, int D, double* Y, double perplexity, double theta, int rand_seed, bool skip_random_init, double* init, bool use_init, @@ -589,10 +609,11 @@ double randn() { // Initialize t-SNE template -struct TSNE* init_tsne(double* X, int N, int D, double* Y, double perplexity, - double theta, int rand_seed, bool skip_random_init, - double* init, bool use_init, int max_iter, - int stop_lying_iter, int mom_switch_iter) { +unique_ptr make_tsne_impl(double* X, int N, int D, double* Y, + double perplexity, double theta, + int rand_seed, bool skip_random_init, + double* init, bool use_init, int max_iter, + int stop_lying_iter, int mom_switch_iter) { // Set random seed if (skip_random_init != true) { if (rand_seed >= 0) { @@ -715,59 +736,34 @@ struct TSNE* init_tsne(double* X, int N, int D, double* Y, double perplexity, (float)(end - start) / CLOCKS_PER_SEC, (double)row_P[N] / ((double)N * (double)N)); - TSNEState* state = new TSNEState; - state->P = P; - state->row_P = row_P; - state->col_P = col_P; - state->val_P = val_P; - state->dY = dY; - state->uY = uY; - state->gains = gains; - TSNE* tsne = new TSNE; - tsne->N = N; - tsne->Y = Y; - tsne->no_dims = NDIMS; - tsne->theta = theta; - tsne->max_iter = max_iter; - tsne->stop_lying_iter = stop_lying_iter; - tsne->mom_switch_iter = mom_switch_iter; - tsne->state = state; - tsne->iter = 0; - tsne->total_time = 0; - tsne->residual_time = 0; + auto tsne = std::unique_ptr(new TSNEState{ + N, Y, NDIMS, theta, max_iter, stop_lying_iter, mom_switch_iter, 0, .0, .0, + P, row_P, col_P, val_P, dY, uY, gains}); return tsne; } // Optimize t-SNE template -bool run_n(int n, struct TSNE* tsne) { +bool TSNEState::step_by_impl(int step) { // Set learning parameters double momentum = .5, final_momentum = .8; double eta = 200.0; // Extract state - int N = tsne->N; - double* Y = tsne->Y; - double theta = tsne->theta; bool exact = (theta == .0) ? true : false; - TSNEState* state = tsne->state; - vector& dY = state->dY; - vector& uY = state->uY; clock_t start = clock(), end; float elapsed; - int max_iter = std::min(tsne->iter + n, tsne->max_iter); + int iter_until = std::min(iter + step, max_iter); - for (int& iter = tsne->iter; iter < max_iter; iter++) { + for (; iter < iter_until; iter++) { // Compute (approximate) gradient if (exact) - computeExactGradient(state->P, Y, N, dY); + computeExactGradient(P, Y, N, dY); else - computeGradient(state->row_P, state->col_P, state->val_P, Y, N, dY, - theta); + computeGradient(row_P, col_P, val_P, Y, N, dY, theta); // Update gains - vector& gains = state->gains; for (int i = 0; i < N * NDIMS; i++) gains[i] = (sign(dY[i]) != sign(uY[i])) ? (gains[i] + .2) : (gains[i] * .8); @@ -785,105 +781,117 @@ bool run_n(int n, struct TSNE* tsne) { zeroMean(Y, N); // Stop lying about the P-values after a while, and switch momentum - if (iter == tsne->stop_lying_iter) { + if (iter == stop_lying_iter) { if (exact) { for (int i = 0; i < N * N; i++) - state->P[i] /= 12.0; + P[i] /= 12.0; } else { - for (int i = 0; i < state->row_P[N]; i++) - state->val_P[i] /= 12.0; + for (int i = 0; i < row_P[N]; i++) + val_P[i] /= 12.0; } } - if (iter == tsne->mom_switch_iter) + if (iter == mom_switch_iter) momentum = final_momentum; // Print out progress - if (iter > 0 && (iter % 50 == 0 || iter == tsne->max_iter - 1)) { + if (iter > 0 && (iter % 50 == 0 || iter == max_iter - 1)) { end = clock(); double C = .0; if (exact) { - C = evaluateError(state->P, Y, N); + C = evaluateError(P, Y, N); } else { // doing approximate computation here! - C = evaluateError(state->row_P, state->col_P, state->val_P, Y, N, - theta); + C = evaluateError(row_P, col_P, val_P, Y, N, theta); } if (iter == 0) fprintf(stderr, "Iteration %d: error is %f\n", iter + 1, C); else { elapsed = (float)(end - start) / CLOCKS_PER_SEC; - tsne->total_time += elapsed; - tsne->residual_time += elapsed; + total_time += elapsed; + residual_time += elapsed; fprintf(stderr, "Iteration %d: error is %f (50 iterations in %4.2f seconds)\n", - iter, C, tsne->residual_time); - tsne->residual_time = 0; + iter, C, residual_time); + residual_time = 0; } start = clock(); } } end = clock(); elapsed = (float)(end - start) / CLOCKS_PER_SEC; - tsne->total_time += elapsed; - tsne->residual_time += elapsed; + total_time += elapsed; + residual_time += elapsed; - if (tsne->iter >= tsne->max_iter) { - fprintf(stderr, "Fitting performed in %4.2f seconds.\n", tsne->total_time); + if (iter >= max_iter) { + fprintf(stderr, "Fitting performed in %4.2f seconds.\n", total_time); return true; } return false; } -} // namespace - -extern "C" { -struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, - int no_dims, double perplexity, double theta, - int rand_seed, bool skip_random_init, - double* init, bool use_init, int max_iter, - int stop_lying_iter, int mom_switch_iter) { - assert(no_dims == 2 || no_dims == 3); +unique_ptr make_tsne(double* X, int N, int D, double* Y, int no_dims, + double perplexity, double theta, int rand_seed, + bool skip_random_init, double* init, + bool use_init, int max_iter, + int stop_lying_iter, int mom_switch_iter) { switch (no_dims) { case 2: - return init_tsne<2>(X, N, D, Y, perplexity, theta, rand_seed, - skip_random_init, init, use_init, max_iter, - stop_lying_iter, mom_switch_iter); + return make_tsne_impl<2>(X, N, D, Y, perplexity, theta, rand_seed, + skip_random_init, init, use_init, max_iter, + stop_lying_iter, mom_switch_iter); case 3: - return init_tsne<3>(X, N, D, Y, perplexity, theta, rand_seed, - skip_random_init, init, use_init, max_iter, - stop_lying_iter, mom_switch_iter); + return make_tsne_impl<3>(X, N, D, Y, perplexity, theta, rand_seed, + skip_random_init, init, use_init, max_iter, + stop_lying_iter, mom_switch_iter); default: throw "invalid dimension"; } } -bool DLL_PUBLIC run_n(int n, struct TSNE* tsne) { - switch (tsne->no_dims) { +bool TSNEState::step_by(int step) { + switch (no_dims) { case 2: - return run_n<2>(n, tsne); + return step_by_impl<2>(step); case 3: - return run_n<3>(n, tsne); + return step_by_impl<3>(step); default: throw "invalid dimension"; } } +} // namespace + +extern "C" { +struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, + int no_dims, double perplexity, double theta, + int rand_seed, bool skip_random_init, + double* init, bool use_init, int max_iter, + int stop_lying_iter, int mom_switch_iter) { + assert(no_dims == 2 || no_dims == 3); + auto tsne = make_tsne(X, N, D, Y, no_dims, perplexity, theta, rand_seed, + skip_random_init, init, use_init, max_iter, + stop_lying_iter, mom_switch_iter); + return (TSNE*)tsne.release(); +} + +bool DLL_PUBLIC step_tsne_by(struct TSNE* tsne, int step) { + return ((TSNEState*)tsne)->step_by(step); +} + void DLL_PUBLIC free_tsne(struct TSNE* tsne) { if (tsne == nullptr) return; - delete tsne->state; - delete tsne; + delete (TSNEState*)tsne; } void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, int rand_seed, bool skip_random_init, double* init, bool use_init, int max_iter, int stop_lying_iter, int mom_switch_iter) { - struct TSNE* tsne = init_tsne(X, N, D, Y, no_dims, perplexity, theta, - rand_seed, skip_random_init, init, use_init, - max_iter, stop_lying_iter, mom_switch_iter); - run_n(max_iter, tsne); - free_tsne(tsne); + auto tsne = make_tsne(X, N, D, Y, no_dims, perplexity, theta, rand_seed, + skip_random_init, init, use_init, max_iter, + stop_lying_iter, mom_switch_iter); + tsne->step_by(max_iter); } } // extern "C" diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index 7edfc04..5159298 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -56,21 +56,7 @@ void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, int max_iter = 1000, int stop_lying_iter = 250, int mom_switch_iter = 250); // stateful t-SNE -struct TSNEState; -struct TSNE { - // parameters - int N; - double* Y; - int no_dims; - double theta; - int max_iter; - int stop_lying_iter; - int mom_switch_iter; - struct TSNEState* state; - int iter; - float total_time; - float residual_time; -}; +struct TSNE; struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, int rand_seed, bool skip_random_init, @@ -78,7 +64,7 @@ struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, int max_iter = 1000, int stop_lying_iter = 250, int mom_switch_iter = 250); -bool DLL_PUBLIC run_n(int n, struct TSNE* tsne); +bool DLL_PUBLIC step_tsne_by(struct TSNE* tsne, int step); void DLL_PUBLIC free_tsne(struct TSNE* tsne); } From 05be25f143731beefd4dcb5631433216c647d219 Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Wed, 9 Jun 2021 10:39:48 -0700 Subject: [PATCH 06/24] constify all the things use reinterpret/static cast and other minor niggles from review --- tsne/bh_sne_src/tsne.cpp | 204 ++++++++++++++++++++-------------- tsne/bh_sne_src/tsne.h | 4 +- tsne/bh_sne_src/vptree.h | 2 +- tsne/tests/test_stability.npy | Bin 2528 -> 2528 bytes 4 files changed, 124 insertions(+), 86 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index f482f22..bf546a8 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -62,6 +62,32 @@ using std::vector; namespace { struct TSNEState { + public: + TSNEState(int N, double* const Y, int no_dims, double theta, int max_iter, + int stop_lying_iter, int mom_switch_iter, int iter, + float total_time, float residual_time, vector P, + vector row_P, vector col_P, + vector val_P, vector dY, vector uY, + vector gains) + : N(N), + Y(Y), + no_dims(no_dims), + theta(theta), + max_iter(max_iter), + stop_lying_iter(stop_lying_iter), + mom_switch_iter(mom_switch_iter), + iter(iter), + total_time(total_time), + residual_time(residual_time), + P(P), + row_P(row_P), + col_P(col_P), + val_P(val_P), + dY(dY), + uY(uY), + gains(gains) { + } + public: bool step_by(int n); @@ -69,24 +95,24 @@ struct TSNEState { template bool step_by_impl(int n); - public: - int N; - double* Y; - int no_dims; - double theta; - int max_iter; - int stop_lying_iter; - int mom_switch_iter; + private: + const int N; + double* const Y; + const int no_dims; + const double theta; + const int max_iter; + const int stop_lying_iter; + const int mom_switch_iter; int iter; float total_time; float residual_time; - std::vector P; - std::vector row_P; - std::vector col_P; - std::vector val_P; - std::vector dY; - std::vector uY; - std::vector gains; + vector P; + vector row_P; + vector col_P; + vector val_P; + vector dY; + vector uY; + vector gains; }; template @@ -97,34 +123,34 @@ void run(double* X, int N, int D, double* Y, double perplexity, double theta, template void computeGradient(const vector& inp_row_P, const vector& inp_col_P, - const vector& inp_val_P, double* Y, int N, - vector& dC, double theta); + const vector& inp_val_P, const double* Y, int N, + vector* _dC, double theta); template -void computeExactGradient(const vector& P, double* Y, int N, - double* dC); +void computeExactGradient(const vector& P, const double* Y, int N, + vector* _dC); template -double evaluateError(const vector& P, double* Y, int N); +double evaluateError(const vector& P, const double* Y, int N); template double evaluateError(const vector& row_P, const vector& col_P, - const vector& val_P, double* Y, int N, + const vector& val_P, const double* Y, int N, double theta); void zeroMean(double* X, int N, int D); template void zeroMean(double* X, int N); -void computeGaussianPerplexity(double* X, int N, int D, double* P, +void computeGaussianPerplexity(const double* X, int N, int D, double* P, double perplexity); -void computeGaussianPerplexity(double* X, int N, int D, +void computeGaussianPerplexity(const double* X, int N, int D, vector* _row_P, vector* _col_P, vector* _val_P, double perplexity, int K); -vector computeSquaredEuclideanDistance(double* X, int N, int D); +vector computeSquaredEuclideanDistance(const double* X, int N, int D); double randn(); void symmetrizeMatrix(vector* row_P, vector* col_P, vector* val_P, int N); -static inline double sign(double x) { +static inline double sign(const double x) { return (x == .0 ? .0 : (x < .0 ? -1.0 : 1.0)); } @@ -132,8 +158,9 @@ static inline double sign(double x) { template void computeGradient(const vector& inp_row_P, const vector& inp_col_P, - const vector& inp_val_P, double* Y, int N, - vector& dC, double theta) { + const vector& inp_val_P, double* const Y, + const int N, vector* const _dC, + const double theta) { // Construct space-partitioning tree on current map SPTree tree(Y, N); @@ -146,6 +173,7 @@ void computeGradient(const vector& inp_row_P, tree.computeNonEdgeForces(n, theta, neg_f.data() + n * NDIMS, &sum_Q); // Compute final t-SNE gradient + vector& dC = *_dC; for (int i = 0; i < N * NDIMS; i++) { dC[i] = pos_f[i] - (neg_f[i] / sum_Q); } @@ -153,9 +181,10 @@ void computeGradient(const vector& inp_row_P, // Compute gradient of the t-SNE cost function (exact) template -void computeExactGradient(const vector& P, double* Y, int N, - vector& dC) { +void computeExactGradient(const vector& P, double* const Y, const int N, + vector* const _dC) { // Make sure the current gradient contains zeros + vector& dC = *_dC; dC.assign(N * D, 0.0); // Compute the squared Euclidean distance matrix @@ -196,7 +225,8 @@ void computeExactGradient(const vector& P, double* Y, int N, // Evaluate t-SNE cost function (exactly) template -double evaluateError(const vector& P, double* Y, int N) { +double evaluateError(const vector& P, const double* const Y, + const int N) { // Compute the squared Euclidean distance matrix vector DD = computeSquaredEuclideanDistance(Y, N, D); vector Q(N * N); @@ -229,8 +259,8 @@ double evaluateError(const vector& P, double* Y, int N) { template double evaluateError(const vector& row_P, const vector& col_P, - const vector& val_P, double* Y, int N, - double theta) { + const vector& val_P, const double* const Y, + const int N, const double theta) { // Get estimate of normalization term array buff; buff.fill(0); @@ -265,8 +295,8 @@ double evaluateError(const vector& row_P, } // Compute input similarities with a fixed perplexity -void computeGaussianPerplexity(double* X, int N, int D, double* P, - double perplexity) { +void computeGaussianPerplexity(const double* const X, const int N, const int D, + double* const P, const double perplexity) { // Compute the squared Euclidean distance matrix vector DD = computeSquaredEuclideanDistance(X, N, D); @@ -330,11 +360,11 @@ void computeGaussianPerplexity(double* X, int N, int D, double* P, } // Compute input similarities with a fixed perplexity using ball trees. -void computeGaussianPerplexity(double* X, int N, int D, +void computeGaussianPerplexity(const double* const X, const int N, const int D, vector* _row_P, vector* _col_P, - vector* _val_P, double perplexity, - int K) { + vector* _val_P, const double perplexity, + const int K) { if (perplexity > K) fprintf(stderr, "Perplexity should be lower than K!\n"); @@ -433,7 +463,7 @@ void computeGaussianPerplexity(double* X, int N, int D, // Symmetrizes a sparse matrix void symmetrizeMatrix(vector* _row_P, vector* _col_P, vector* _val_P, - int N) { + const int N) { // Get sparse matrix vector& row_P = *_row_P; vector& col_P = *_col_P; @@ -520,7 +550,8 @@ void symmetrizeMatrix(vector* _row_P, } // Compute squared Euclidean distance matrix -vector computeSquaredEuclideanDistance(double* X, int N, int D) { +vector computeSquaredEuclideanDistance(const double* const X, + const int N, const int D) { vector DD(N * N); const double* XnD = X; for (int n = 0; n < N; ++n, XnD += D) { @@ -543,7 +574,7 @@ vector computeSquaredEuclideanDistance(double* X, int N, int D) { // Makes data zero-mean template -void zeroMean(double* X, int N) { +void zeroMean(double* const X, const int N) { // Compute data mean array mean; mean.fill(0); @@ -555,7 +586,7 @@ void zeroMean(double* X, int N) { nD += D; } for (int d = 0; d < D; d++) { - mean[d] /= (double)N; + mean[d] /= static_cast(N); } // Subtract data mean @@ -569,7 +600,7 @@ void zeroMean(double* X, int N) { } // Makes data zero-mean -void zeroMean(double* X, int N, int D) { +void zeroMean(double* const X, const int N, const int D) { // Compute data mean vector mean(D); int nD = 0; @@ -580,7 +611,7 @@ void zeroMean(double* X, int N, int D) { nD += D; } for (int d = 0; d < D; d++) { - mean[d] /= (double)N; + mean[d] /= static_cast(N); } // Subtract data mean @@ -597,8 +628,8 @@ void zeroMean(double* X, int N, int D) { double randn() { double x, y, radius; do { - x = 2 * (rand() / ((double)RAND_MAX + 1)) - 1; - y = 2 * (rand() / ((double)RAND_MAX + 1)) - 1; + x = 2 * (rand() / (static_cast(RAND_MAX) + 1)) - 1; + y = 2 * (rand() / (static_cast(RAND_MAX) + 1)) - 1; radius = (x * x) + (y * y); } while ((radius >= 1.0) || (radius == 0.0)); radius = sqrt(-2 * log(radius) / radius); @@ -609,11 +640,11 @@ double randn() { // Initialize t-SNE template -unique_ptr make_tsne_impl(double* X, int N, int D, double* Y, - double perplexity, double theta, - int rand_seed, bool skip_random_init, - double* init, bool use_init, int max_iter, - int stop_lying_iter, int mom_switch_iter) { +unique_ptr make_tsne_impl( + double* const X, const int N, const int D, double* const Y, + const double perplexity, const double theta, const int rand_seed, + const bool skip_random_init, const double* const init, const bool use_init, + const int max_iter, const int stop_lying_iter, const int mom_switch_iter) { // Set random seed if (skip_random_init != true) { if (rand_seed >= 0) { @@ -691,7 +722,7 @@ unique_ptr make_tsne_impl(double* X, int N, int D, double* Y, // Compute asymmetric pairwise input similarities computeGaussianPerplexity(X, N, D, &row_P, &col_P, &val_P, perplexity, - (int)(3 * perplexity)); + static_cast(3 * perplexity)); // Symmetrize input similarities symmetrizeMatrix(&row_P, &col_P, &val_P, N); @@ -728,24 +759,23 @@ unique_ptr make_tsne_impl(double* X, int N, int D, double* Y, fprintf(stderr, "Input similarities computed in %4.2f seconds!\nLearning " "embedding...\n", - (float)(end - start) / CLOCKS_PER_SEC); + static_cast(end - start) / CLOCKS_PER_SEC); else fprintf(stderr, "Input similarities computed in %4.2f seconds (sparsity = " "%f)!\nLearning embedding...\n", - (float)(end - start) / CLOCKS_PER_SEC, - (double)row_P[N] / ((double)N * (double)N)); - - auto tsne = std::unique_ptr(new TSNEState{ - N, Y, NDIMS, theta, max_iter, stop_lying_iter, mom_switch_iter, 0, .0, .0, - P, row_P, col_P, val_P, dY, uY, gains}); + static_cast(end - start) / CLOCKS_PER_SEC, + static_cast(row_P[N]) / + (static_cast(N) * static_cast(N))); - return tsne; + return std::make_unique(N, Y, NDIMS, theta, max_iter, + stop_lying_iter, mom_switch_iter, 0, .0, + .0, P, row_P, col_P, val_P, dY, uY, gains); } // Optimize t-SNE template -bool TSNEState::step_by_impl(int step) { +bool TSNEState::step_by_impl(const int step) { // Set learning parameters double momentum = .5, final_momentum = .8; double eta = 200.0; @@ -759,9 +789,9 @@ bool TSNEState::step_by_impl(int step) { for (; iter < iter_until; iter++) { // Compute (approximate) gradient if (exact) - computeExactGradient(P, Y, N, dY); + computeExactGradient(P, Y, N, &dY); else - computeGradient(row_P, col_P, val_P, Y, N, dY, theta); + computeGradient(row_P, col_P, val_P, Y, N, &dY, theta); // Update gains for (int i = 0; i < N * NDIMS; i++) @@ -829,11 +859,14 @@ bool TSNEState::step_by_impl(int step) { return false; } -unique_ptr make_tsne(double* X, int N, int D, double* Y, int no_dims, - double perplexity, double theta, int rand_seed, - bool skip_random_init, double* init, - bool use_init, int max_iter, - int stop_lying_iter, int mom_switch_iter) { +unique_ptr make_tsne(double* const X, const int N, const int D, + double* const Y, const int no_dims, + const double perplexity, const double theta, + const int rand_seed, + const bool skip_random_init, + const double* const init, const bool use_init, + const int max_iter, const int stop_lying_iter, + const int mom_switch_iter) { switch (no_dims) { case 2: return make_tsne_impl<2>(X, N, D, Y, perplexity, theta, rand_seed, @@ -844,7 +877,7 @@ unique_ptr make_tsne(double* X, int N, int D, double* Y, int no_dims, skip_random_init, init, use_init, max_iter, stop_lying_iter, mom_switch_iter); default: - throw "invalid dimension"; + throw "unsupported dimension"; } } @@ -855,39 +888,44 @@ bool TSNEState::step_by(int step) { case 3: return step_by_impl<3>(step); default: - throw "invalid dimension"; + throw "unsupported dimension"; } } } // namespace extern "C" { -struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, - int no_dims, double perplexity, double theta, - int rand_seed, bool skip_random_init, - double* init, bool use_init, int max_iter, - int stop_lying_iter, int mom_switch_iter) { +struct TSNE* DLL_PUBLIC init_tsne(double* const X, const int N, const int D, + double* const Y, const int no_dims, + const double perplexity, const double theta, + const int rand_seed, + const bool skip_random_init, + const double* const init, const bool use_init, + const int max_iter, const int stop_lying_iter, + const int mom_switch_iter) { assert(no_dims == 2 || no_dims == 3); auto tsne = make_tsne(X, N, D, Y, no_dims, perplexity, theta, rand_seed, skip_random_init, init, use_init, max_iter, stop_lying_iter, mom_switch_iter); - return (TSNE*)tsne.release(); + return reinterpret_cast(tsne.release()); } -bool DLL_PUBLIC step_tsne_by(struct TSNE* tsne, int step) { - return ((TSNEState*)tsne)->step_by(step); +bool DLL_PUBLIC step_tsne_by(struct TSNE* const tsne, const int step) { + return reinterpret_cast(tsne)->step_by(step); } -void DLL_PUBLIC free_tsne(struct TSNE* tsne) { +void DLL_PUBLIC free_tsne(struct TSNE* const tsne) { if (tsne == nullptr) return; - delete (TSNEState*)tsne; + delete reinterpret_cast(tsne); } -void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, - double perplexity, double theta, int rand_seed, - bool skip_random_init, double* init, bool use_init, - int max_iter, int stop_lying_iter, int mom_switch_iter) { +void DLL_PUBLIC run(double* const X, const int N, const int D, double* const Y, + const int no_dims, const double perplexity, + const double theta, const int rand_seed, + const bool skip_random_init, const double* const init, + const bool use_init, const int max_iter, + const int stop_lying_iter, const int mom_switch_iter) { auto tsne = make_tsne(X, N, D, Y, no_dims, perplexity, theta, rand_seed, skip_random_init, init, use_init, max_iter, stop_lying_iter, mom_switch_iter); diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index 5159298..0778b9a 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -52,7 +52,7 @@ extern "C" { // stateless t-SNE void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, int rand_seed, - bool skip_random_init, double* init, bool use_init, + bool skip_random_init, const double* init, bool use_init, int max_iter = 1000, int stop_lying_iter = 250, int mom_switch_iter = 250); // stateful t-SNE @@ -60,7 +60,7 @@ struct TSNE; struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, int no_dims, double perplexity, double theta, int rand_seed, bool skip_random_init, - double* init, bool use_init, + const double* init, bool use_init, int max_iter = 1000, int stop_lying_iter = 250, int mom_switch_iter = 250); diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index b7bdd49..2330b10 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -49,7 +49,7 @@ class euclidean_distance final { const int _D; - const double* data; + const double* const data; public: explicit euclidean_distance(int D, const double* data) : _D(D), data(data) { diff --git a/tsne/tests/test_stability.npy b/tsne/tests/test_stability.npy index 25cd4decd3a927ac6bf69d99821b25329e803efc..e080c6ff0ff45e0325e145be8c38932b7a548641 100644 GIT binary patch literal 2528 zcmbVL`8O1LAI2cW7-MWRmccB>Y{rbO7~jg3%WJ!Z5RxS#vhR|VlC5xYZ*|?9m(sCQ zwwsUbYEeSEQBEPfO0uQ}dA;Y{zu^7+_B`i&&Urr1^Lz>%TcQGSv1 zU1Yj>pedb2rU!;cMo0RF`GiOM2mFt>_6>~+*yf{xd`|{!V?Cac0gG(NV3AYE|L4dg z@QoX8{zXCNrcxMe3lE*w+BtOnR~q`QkLYImnu9)wmE}(4QjlJSutZUki!2|kG$}h! z(Om0db}5U4Sl;6)S^^4+&|)9H+|EOcCs2=Cs7UFmI%ztKj|7h1q0M8uNc-DB){7u6 zA~bc*OP-*js4pTf&M5KF3YTFOgrlMZxQzFEKd?}PsC(mN5DleYdlOUm8y7vkHyfCz zLPcZ4<4cjMe6;3OC91lEhN1)&^S$W?h*-ukD&}tERFV6BE*#{0d_XSAih`p1=)(Ro z7UE1ZyLRePkl%7{Uz3>tMUUKxG`dJZ4{D}r*hd7&OeO)_Xh}hyS;y?K34A0!<*?e1 zrJ~(Oub%Si;3121WnJsNROEQ)S)IvCJ}MU5v_JGK1(jo!YRzj6kq#p>Qf{7xOitXZ zyL-_PH6;F)@}mdH5Z71Bn&G0BjG+BLoTZ~A84uBzL>}U8 z5@qjwrXxWzC!wm?039Dr+7o(`jvUIw!n6yxNXO^rPrB7KbYW)nU5hghb@hiFBSlit znbP086cr4S`dZQAkTn&(QZDk8tmh$IP--~4oQn8&I@C^hagi92tl&|y?N6{V@rwJl zpPoJ{S1M^}{NPYaS+pTCGq}aGo6$u+wAF0=bUqSu3mom;ML}(?ZsvBf0wky6F4yEn zLv`-v?v))}WE9yKr;tHGmpEyqEH?v`Qc&)Nzr4Nwy!A@&Lql|P_Vwmgn=TTsXB?M2 z$V2S)o}1qHk$aGhr3-tMT=JB>*Qb_8j5INh&896J8>ADBrhJyy^XzU z=|n@n#MC*G%voqh>#P`QNf+VIdHdY0V5F^N5;XZSR9Z*Yks>cv5WD0{jRc+alPY-2g>6`R2qD)PZ&+m1x5z121g!TwAX)tmVJ5 z`O>Ej#|y0vo-HAPAk2MyV}%TMaa4@-2RR5#tn>MCP6{UPETwzSsK7zywOi%NnvlQb zL9#Bv!UNSPp+Y(V7P}v|n|l*reD&xr7loQ2bM6Jgx5xvE`!i6pd%aWG!K{z)P=nS@ z0k@<@1Kzor&pB1ffKX~NAX6X>Rt+a}cYMG?;QJ<1|C-ItmJpVEUb_OM7RA~RED>O1 z?bTG2trArEd=b(oWgz*+4M|;fMNp<=l+Bw-uvGKJ;jjP?DHTVjt2%dp?4a~5w`d}O z4&z;pqXvY|6f3hlw;e7v(!nNnl~Yq}C-(h79A4sSB+Z1Tdbqw0GHyhYFb=C2AfK zpoc-ojA_w^IM++6j~WyKlb)GAa+nBTAMCLt+Nr=)mrKZ2jT)FbbQ^{Ss=@U)3h}3N z)ggT-;paYoB8+J%uFSfULBGj-SZ+5FPG3*h5f`Ncj-n^3jnwe)eQ(K{0HX#=7ef># z-0<+2C$3(3UmOyxIvku34jwv&&qt+7g2%A1;7|n)?scqXdOQ_}tpG7GtQ{Vt2M>FW z^W@-7#k0_c!wSF-P79m7tplCn*-o8<(m>*ib~9oKkX-)|bG1Sqj5-^eQvM{tg`IDy zD*_@k$$#20I;#aa2Q#kZ7OH~%v6X|n-YS9n@K<&1L3!{_Yxo|LuK=Gudbd+AVWGs> zfS%YZ1GA$ybGMG|1dlx!zp6GI5JprcR^=4IJ|@ZR(JLaz>U*T*BRuT0J@(ADR1Vq) zHsF z;CR2-u9bEz;5Zg$YxuI@XZBx{o`*D{a+qCyZCDTfmA6v3Ux5TJSCd`8FS20yu2J$+ zdo?J@aJtrPPlJa;O!}rk8ARvuo}$}SAd4Isbu1zRe_hJLSd{?_s-%-Mdt^ZA#0SSR zbzO)$@Ap2HD-AWHodjbB1!@HLgVp-VkXbakS9VJeT5WO0<^C$595oGD(P zssVjNPwpkV$e<>|WWFe4!~1v)w`4&PuKKx;1Y799ThY5T zg;+Ik{dzwx%T^z@-~M^HpC+_&_vx9pGeLA_NW{An54M~AfeZH95L)1NPHhVZCdCO6 zJJqzIIpSgdej62-vDoe5$kBr=sQ41=rUlKLaEV4@LicVDPFJB8tT#pOyKT$@VYhP2 z{8|&l%U4Zz3k|={?PxtGF#rnLvf?HG5?SqaW#oKNZ4=!55h<&5h+%J3*Ys-XV|I_&GM z_!gvxgOv2j$>e-8ygV|HSZGFq4WnGWO$i1x_}W!pSX6~HLzR__JM}^M(WX8agpWx07Zc&_ z1FPK4Z**8FxRR4Lpa^oJIh19gF7%p+SX|Cih2-c8qy7>a*fYN_p8AOZvG+!5;W7jE T@8|w)A;iK9H~VanHEsAWQ1962F(Zo`}&99^erY+ONL&-+@R zvM3=;h50I8OXpzf-8<~e&mzd!v}83?mVBb%FNIvC1>8;xpO_q4d!`a&yksE!Y6Y@N;btGFnityxi?B!Wxhb~CTH5xBK? zOndCU00Pc<|B}JwpySiAFyC-}kj_ZzUFhIqfYql(>+f6$zZkru$dAB~_;&+()yDAb z(+6#mCxOlz^&(DvFoshKe_dnxh&f1qcS6@!-53s>`F?ls83!9}T9F7 z)tC;B$s4b;ndC#!`f8^fCmu?b1yKff8iK3G{KwQE6~v;nD8Odwrdv3IH;j=tMBH1J~;gBLk>C0IC41{)#P#Yyi!5VEKO%`6M9lw8<6@MF z=^GVU1NiWF$F<+ya`6vS(kC~65m@^c?34@R;@dQ*tbh4ccS1h?%dhu+@tzyQgJr_# zHyRNH9zHY-;dzFztj%4#Q9g}8%SpzA<@ttitcLlv$%erHtuq{YBjiErRA9eB5fAAy zpUD@xc#!+w%#x614n8tG^I_gh2)!AuzmWTNP&8<^Mzm7|e)rxpKd0&-`)S2*0mt;= zs+85T{?;FX4m>>XVJ!m38F{tY3|*|8v`aE0>BFHGaqs#fRRZ&Nua30-VL;X?^v)gO zVC#+4kM|-Cz>Cq^Jxt=_^WYd!A6XN7_!mg$JXtVdAHO=vnt>YwMXU-f2Hft{-fJDj z##WbI!%Pniuy}pddBx=~61-^mDPbKQ^uM;|r63bcE+p>WHpPJ8eM#SDN0yN-Ci3Va?JLgN>Q%N@EDeJZr1H5+Cjg%HLv7s;f^L&UpK*+O& zH&we{eTs9rHm-OhKO(r2V zwjtk|!h%3c+cSk`bQBvpCX}z8q{5cl=6Xw<<|JFZqdn$Inb5i*vRWR)MD-=Vh3B5u zfI#-F@495rV^XprJeL8|9oGv+yeT-gCviyYfF_L31Qe=t z&r3SiN&?m^Frf2%>4Ql*CVq}=i59dg!D3COy3+(%jK9aKZ|K;~gm>yben@BIv5Xj_;wgaPImo^MLLNI0r;CwPB0 z1NJXDAzfL=#)DFR@t4WU5C%gDWK}Yz+pAHQo>qY{uC{r;BMrOuC0m3nsKCMb<-6iE zSQwNcY22Hj4v8De7wp7Z6nyX}XT6ZF2|Zp!@z#TKST)SIIblTuM#KE3>jMjtj?1f} z9l_}< z9y#?U-KPOx{;h+kje@v!4gbIz2AG!A{ zkH)h#_0Tn(T+4D`!eBS=po@}g@&krQC#;ImjPY; zs&NeW=Fp*Xhgi;fM5v2H+No5bI|a5kujZ0^xLDg|91$*@H4+0s_(xhb5)!?SNo20pegU388;-`lwkV9Q~w>8KB zi=~e-M|+fE?=73b=0yWsnq#FtC8R>FSoq3t&6<_y*IP{l=P7}*ouFhiQV-evMvEqL zG+3@(`SrOE3;jlYE*i|rfqSS@_SJR+G`pJhLC~NIYj57|xmv--3mMHPwEw2Sy5gXc z^dTN@$|;-|k!dhIJ-MQ-h>xi@N}ESZs9;bp^L=I4 z&XnT@_)x~hf1+I(=E!lE(&h+s_a6@xMNojGZ`^R!Ukh^`&kXOYRfR@}?GCOdb+B+Y zA~VK+xfsGCDNpYV3()?>|BWh4Rbf=>T=7MD9=3&8JX>qegv)|%C0afQc?GQQ>Uj#x z6zrQZ^i)Tk;u+OD9(Bx(@>{}sg=dS$^%p|j}J+ZCG@b#Xtt>L zXEhjh8+jUcj=-}jx9XofqC@P7Umn^9tVB_Qho#ws3N(6lnx0YQV2iKd?h^+ZEJQRZ fB9)7~Djg1Ot73w)pJ1TVmxWt5Xa6cUqzL~2RUTTq From 5bef9a1ed4abe6467a0280679de283e5b94fe7ec Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Wed, 9 Jun 2021 11:25:39 -0700 Subject: [PATCH 07/24] use move and rvalue refs for TSNEState initializer --- tsne/bh_sne_src/tsne.cpp | 49 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index bf546a8..8ee4758 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -65,27 +65,27 @@ struct TSNEState { public: TSNEState(int N, double* const Y, int no_dims, double theta, int max_iter, int stop_lying_iter, int mom_switch_iter, int iter, - float total_time, float residual_time, vector P, - vector row_P, vector col_P, - vector val_P, vector dY, vector uY, - vector gains) - : N(N), - Y(Y), - no_dims(no_dims), - theta(theta), - max_iter(max_iter), - stop_lying_iter(stop_lying_iter), - mom_switch_iter(mom_switch_iter), - iter(iter), - total_time(total_time), - residual_time(residual_time), - P(P), - row_P(row_P), - col_P(col_P), - val_P(val_P), - dY(dY), - uY(uY), - gains(gains) { + float total_time, float residual_time, vector&& P, + vector&& row_P, vector&& col_P, + vector&& val_P, vector&& dY, vector&& uY, + vector&& gains) + : N{N}, + Y{Y}, + no_dims{no_dims}, + theta{theta}, + max_iter{max_iter}, + stop_lying_iter{stop_lying_iter}, + mom_switch_iter{mom_switch_iter}, + iter{iter}, + total_time{total_time}, + residual_time{residual_time}, + P{P}, + row_P{row_P}, + col_P{col_P}, + val_P{val_P}, + dY{dY}, + uY{uY}, + gains{gains} { } public: @@ -768,9 +768,10 @@ unique_ptr make_tsne_impl( static_cast(row_P[N]) / (static_cast(N) * static_cast(N))); - return std::make_unique(N, Y, NDIMS, theta, max_iter, - stop_lying_iter, mom_switch_iter, 0, .0, - .0, P, row_P, col_P, val_P, dY, uY, gains); + return std::make_unique( + N, Y, NDIMS, theta, max_iter, stop_lying_iter, mom_switch_iter, 0, .0, .0, + move(P), move(row_P), move(col_P), move(val_P), move(dY), move(uY), + move(gains)); } // Optimize t-SNE From ae57d4134a0d16b16f3426895f7430167ee1c6cb Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 17:25:04 -0700 Subject: [PATCH 08/24] Update clang-format config to match cellranger. Main change here is to not bin-pack arguments. Also add a clang-tidy config. --- .clang-format | 6 +- .clang-tidy | 8 + tsne/bh_sne_src/main.cpp | 42 +++++- tsne/bh_sne_src/sptree.cpp | 11 +- tsne/bh_sne_src/sptree.h | 18 ++- tsne/bh_sne_src/tsne.cpp | 292 +++++++++++++++++++++++++++---------- tsne/bh_sne_src/tsne.h | 32 +++- tsne/bh_sne_src/vptree.h | 20 ++- 8 files changed, 321 insertions(+), 108 deletions(-) create mode 100644 .clang-tidy diff --git a/.clang-format b/.clang-format index 1c96eda..04ec738 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,8 @@ -Language: Cpp +Language: Cpp BasedOnStyle: Google AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false -PenaltyBreakComment: 100 +BinPackArguments: false +BinPackParameters: false +ReflowComments: false diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..ea4b8ea --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,8 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,-cert-msc*,performance-*,modernize-*,-modernize-use-trailing-return-type,google-*,-modernize-avoid-c-arrays,cppcoreguidelines-*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-*,-cppcoreguidelines-init-*,-cppcoreguidelines-owning-memory,-cppcoreguidelines-avoid-c-arrays,readability-*,-readability-magic-numbers,-readability-function-cognitive-complexity,-readability-function-size,-readability-identifier-naming' +WarningsAsErrors: '' +HeaderFilterRegex: '.*' +AnalyzeTemporaryDtors: false +FormatStyle: file +... + diff --git a/tsne/bh_sne_src/main.cpp b/tsne/bh_sne_src/main.cpp index 6fe7ec5..ff574f1 100644 --- a/tsne/bh_sne_src/main.cpp +++ b/tsne/bh_sne_src/main.cpp @@ -40,8 +40,14 @@ using namespace std; // Function that loads data from a t-SNE file -bool load_data(const char* dat_file, vector* data, int* n, int* d, - int* no_dims, double* theta, double* perplexity, int* rand_seed, +bool load_data(const char* dat_file, + vector* data, + int* n, + int* d, + int* no_dims, + double* theta, + double* perplexity, + int* rand_seed, int* max_iter) { // Open file, read first 2 integers, allocate memory, and read the data FILE* h; @@ -65,8 +71,11 @@ bool load_data(const char* dat_file, vector* data, int* n, int* d, } // Function that saves map to a t-SNE file -void save_data(const char* res_file, const vector& data, - const vector& landmarks, const vector& costs, int n, +void save_data(const char* res_file, + const vector& data, + const vector& landmarks, + const vector& costs, + int n, int d) { // Open file, write first 2 integers and then the data FILE* h; @@ -122,8 +131,15 @@ int main(int argc, char* argv[]) { int rand_seed = -1; // Read the parameters and the dataset - if (load_data(dat_file_c, &data, &origN, &D, &no_dims, &theta, &perplexity, - &rand_seed, &max_iter)) { + if (load_data(dat_file_c, + &data, + &origN, + &D, + &no_dims, + &theta, + &perplexity, + &rand_seed, + &max_iter)) { // Make dummy landmarks N = origN; vector landmarks(N); @@ -133,8 +149,18 @@ int main(int argc, char* argv[]) { // Now fire up the SNE implementation vector Y(N * no_dims); vector costs(N); - run(data.data(), N, D, Y.data(), no_dims, perplexity, theta, rand_seed, - false, nullptr, false, max_iter); + run(data.data(), + N, + D, + Y.data(), + no_dims, + perplexity, + theta, + rand_seed, + false, + nullptr, + false, + max_iter); // Save the results save_data(res_file_c, Y, landmarks, costs, N, no_dims); diff --git a/tsne/bh_sne_src/sptree.cpp b/tsne/bh_sne_src/sptree.cpp index 0c3dc8b..0163859 100644 --- a/tsne/bh_sne_src/sptree.cpp +++ b/tsne/bh_sne_src/sptree.cpp @@ -272,14 +272,19 @@ unsigned int SPTree::Node::getAllIndices(unsigned int* indices, // Compute non-edge forces using Barnes-Hut algorithm template -void SPTree::computeNonEdgeForces(unsigned int point_index, double theta, - double neg_f[], double* sum_Q) const { +void SPTree::computeNonEdgeForces(unsigned int point_index, + double theta, + double neg_f[], + double* sum_Q) const { node.computeNonEdgeForces(data, point_index, theta, neg_f, sum_Q); } template [[gnu::hot]] void SPTree::Node::computeNonEdgeForces( - const double* data, unsigned int point_index, double theta, double neg_f[], + const double* data, + unsigned int point_index, + double theta, + double neg_f[], double* sum_Q) const { // Make sure that we spend no time on empty nodes or self-interactions if (cum_size == 0 || (!children && cum_size >= 1 && index == point_index)) diff --git a/tsne/bh_sne_src/sptree.h b/tsne/bh_sne_src/sptree.h index e399b60..c004cab 100644 --- a/tsne/bh_sne_src/sptree.h +++ b/tsne/bh_sne_src/sptree.h @@ -69,13 +69,16 @@ class SPTree { bool insert(const double* data, unsigned int new_index); void subdivide(const double* data); void print(const double* data) const; - void computeNonEdgeForces(const double* data, unsigned int point_index, - double theta, double neg_f[], + void computeNonEdgeForces(const double* data, + unsigned int point_index, + double theta, + double neg_f[], double* sum_Q) const; std::vector computeEdgeForces(const double* data, const unsigned int* row_P, const unsigned int* col_P, - const double* val_P, int N) const; + const double* val_P, + int N) const; unsigned int getAllIndices(unsigned int* indices, unsigned int loc) const; void init(const double* inp_corner, const double* inp_width); @@ -110,11 +113,14 @@ class SPTree { ~SPTree() = default; bool isCorrect() const; void getAllIndices(unsigned int* indices) const; - void computeNonEdgeForces(unsigned int point_index, double theta, - double neg_f[], double* sum_Q) const; + void computeNonEdgeForces(unsigned int point_index, + double theta, + double neg_f[], + double* sum_Q) const; std::vector computeEdgeForces(const unsigned int* row_P, const unsigned int* col_P, - const double* val_P, int N) const; + const double* val_P, + int N) const; void print() const; private: diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 8ee4758..d527e77 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -63,11 +63,22 @@ namespace { struct TSNEState { public: - TSNEState(int N, double* const Y, int no_dims, double theta, int max_iter, - int stop_lying_iter, int mom_switch_iter, int iter, - float total_time, float residual_time, vector&& P, - vector&& row_P, vector&& col_P, - vector&& val_P, vector&& dY, vector&& uY, + TSNEState(int N, + double* const Y, + int no_dims, + double theta, + int max_iter, + int stop_lying_iter, + int mom_switch_iter, + int iter, + float total_time, + float residual_time, + vector&& P, + vector&& row_P, + vector&& col_P, + vector&& val_P, + vector&& dY, + vector&& uY, vector&& gains) : N{N}, Y{Y}, @@ -116,39 +127,61 @@ struct TSNEState { }; template -void run(double* X, int N, int D, double* Y, double perplexity, double theta, - int rand_seed, bool skip_random_init, double* init, bool use_init, - int max_iter, int stop_lying_iter, int mom_switch_iter); +void run(double* X, + int N, + int D, + double* Y, + double perplexity, + double theta, + int rand_seed, + bool skip_random_init, + double* init, + bool use_init, + int max_iter, + int stop_lying_iter, + int mom_switch_iter); template void computeGradient(const vector& inp_row_P, const vector& inp_col_P, - const vector& inp_val_P, const double* Y, int N, - vector* _dC, double theta); + const vector& inp_val_P, + const double* Y, + int N, + vector* _dC, + double theta); template -void computeExactGradient(const vector& P, const double* Y, int N, +void computeExactGradient(const vector& P, + const double* Y, + int N, vector* _dC); template double evaluateError(const vector& P, const double* Y, int N); template double evaluateError(const vector& row_P, const vector& col_P, - const vector& val_P, const double* Y, int N, + const vector& val_P, + const double* Y, + int N, double theta); void zeroMean(double* X, int N, int D); template void zeroMean(double* X, int N); -void computeGaussianPerplexity(const double* X, int N, int D, double* P, - double perplexity); -void computeGaussianPerplexity(const double* X, int N, int D, +void computeGaussianPerplexity( + const double* X, int N, int D, double* P, double perplexity); +void computeGaussianPerplexity(const double* X, + int N, + int D, vector* _row_P, vector* _col_P, - vector* _val_P, double perplexity, + vector* _val_P, + double perplexity, int K); vector computeSquaredEuclideanDistance(const double* X, int N, int D); double randn(); -void symmetrizeMatrix(vector* row_P, vector* col_P, - vector* val_P, int N); +void symmetrizeMatrix(vector* row_P, + vector* col_P, + vector* val_P, + int N); static inline double sign(const double x) { return (x == .0 ? .0 : (x < .0 ? -1.0 : 1.0)); @@ -158,8 +191,10 @@ static inline double sign(const double x) { template void computeGradient(const vector& inp_row_P, const vector& inp_col_P, - const vector& inp_val_P, double* const Y, - const int N, vector* const _dC, + const vector& inp_val_P, + double* const Y, + const int N, + vector* const _dC, const double theta) { // Construct space-partitioning tree on current map SPTree tree(Y, N); @@ -167,8 +202,8 @@ void computeGradient(const vector& inp_row_P, // Compute all terms required for t-SNE gradient double sum_Q = .0; vector neg_f(N * NDIMS); - auto pos_f = tree.computeEdgeForces(inp_row_P.data(), inp_col_P.data(), - inp_val_P.data(), N); + auto pos_f = tree.computeEdgeForces( + inp_row_P.data(), inp_col_P.data(), inp_val_P.data(), N); for (int n = 0; n < N; n++) tree.computeNonEdgeForces(n, theta, neg_f.data() + n * NDIMS, &sum_Q); @@ -181,7 +216,9 @@ void computeGradient(const vector& inp_row_P, // Compute gradient of the t-SNE cost function (exact) template -void computeExactGradient(const vector& P, double* const Y, const int N, +void computeExactGradient(const vector& P, + double* const Y, + const int N, vector* const _dC) { // Make sure the current gradient contains zeros vector& dC = *_dC; @@ -225,7 +262,8 @@ void computeExactGradient(const vector& P, double* const Y, const int N, // Evaluate t-SNE cost function (exactly) template -double evaluateError(const vector& P, const double* const Y, +double evaluateError(const vector& P, + const double* const Y, const int N) { // Compute the squared Euclidean distance matrix vector DD = computeSquaredEuclideanDistance(Y, N, D); @@ -259,8 +297,10 @@ double evaluateError(const vector& P, const double* const Y, template double evaluateError(const vector& row_P, const vector& col_P, - const vector& val_P, const double* const Y, - const int N, const double theta) { + const vector& val_P, + const double* const Y, + const int N, + const double theta) { // Get estimate of normalization term array buff; buff.fill(0); @@ -295,8 +335,11 @@ double evaluateError(const vector& row_P, } // Compute input similarities with a fixed perplexity -void computeGaussianPerplexity(const double* const X, const int N, const int D, - double* const P, const double perplexity) { +void computeGaussianPerplexity(const double* const X, + const int N, + const int D, + double* const P, + const double perplexity) { // Compute the squared Euclidean distance matrix vector DD = computeSquaredEuclideanDistance(X, N, D); @@ -360,10 +403,13 @@ void computeGaussianPerplexity(const double* const X, const int N, const int D, } // Compute input similarities with a fixed perplexity using ball trees. -void computeGaussianPerplexity(const double* const X, const int N, const int D, +void computeGaussianPerplexity(const double* const X, + const int N, + const int D, vector* _row_P, vector* _col_P, - vector* _val_P, const double perplexity, + vector* _val_P, + const double perplexity, const int K) { if (perplexity > K) fprintf(stderr, "Perplexity should be lower than K!\n"); @@ -462,7 +508,8 @@ void computeGaussianPerplexity(const double* const X, const int N, const int D, // Symmetrizes a sparse matrix void symmetrizeMatrix(vector* _row_P, - vector* _col_P, vector* _val_P, + vector* _col_P, + vector* _val_P, const int N) { // Get sparse matrix vector& row_P = *_row_P; @@ -551,7 +598,8 @@ void symmetrizeMatrix(vector* _row_P, // Compute squared Euclidean distance matrix vector computeSquaredEuclideanDistance(const double* const X, - const int N, const int D) { + const int N, + const int D) { vector DD(N * N); const double* XnD = X; for (int n = 0; n < N; ++n, XnD += D) { @@ -640,11 +688,19 @@ double randn() { // Initialize t-SNE template -unique_ptr make_tsne_impl( - double* const X, const int N, const int D, double* const Y, - const double perplexity, const double theta, const int rand_seed, - const bool skip_random_init, const double* const init, const bool use_init, - const int max_iter, const int stop_lying_iter, const int mom_switch_iter) { +unique_ptr make_tsne_impl(double* const X, + const int N, + const int D, + double* const Y, + const double perplexity, + const double theta, + const int rand_seed, + const bool skip_random_init, + const double* const init, + const bool use_init, + const int max_iter, + const int stop_lying_iter, + const int mom_switch_iter) { // Set random seed if (skip_random_init != true) { if (rand_seed >= 0) { @@ -661,13 +717,18 @@ unique_ptr make_tsne_impl( fprintf(stderr, "Perplexity too large for the number of data points!\n"); exit(1); } - fprintf(stderr, "Using no_dims = %d, perplexity = %f, and theta = %f\n", - NDIMS, perplexity, theta); + fprintf(stderr, + "Using no_dims = %d, perplexity = %f, and theta = %f\n", + NDIMS, + perplexity, + theta); bool exact = (theta == .0) ? true : false; fprintf(stderr, "Using max_iter = %d, stop_lying_iter = %d, mom_switch_iter = %d\n", - max_iter, stop_lying_iter, mom_switch_iter); + max_iter, + stop_lying_iter, + mom_switch_iter); clock_t start, end; @@ -721,7 +782,13 @@ unique_ptr make_tsne_impl( // Compute input similarities for approximate t-SNE // Compute asymmetric pairwise input similarities - computeGaussianPerplexity(X, N, D, &row_P, &col_P, &val_P, perplexity, + computeGaussianPerplexity(X, + N, + D, + &row_P, + &col_P, + &val_P, + perplexity, static_cast(3 * perplexity)); // Symmetrize input similarities @@ -768,10 +835,23 @@ unique_ptr make_tsne_impl( static_cast(row_P[N]) / (static_cast(N) * static_cast(N))); - return std::make_unique( - N, Y, NDIMS, theta, max_iter, stop_lying_iter, mom_switch_iter, 0, .0, .0, - move(P), move(row_P), move(col_P), move(val_P), move(dY), move(uY), - move(gains)); + return std::make_unique(N, + Y, + NDIMS, + theta, + max_iter, + stop_lying_iter, + mom_switch_iter, + 0, + .0, + .0, + move(P), + move(row_P), + move(col_P), + move(val_P), + move(dY), + move(uY), + move(gains)); } // Optimize t-SNE @@ -842,7 +922,9 @@ bool TSNEState::step_by_impl(const int step) { residual_time += elapsed; fprintf(stderr, "Iteration %d: error is %f (50 iterations in %4.2f seconds)\n", - iter, C, residual_time); + iter, + C, + residual_time); residual_time = 0; } start = clock(); @@ -860,23 +942,49 @@ bool TSNEState::step_by_impl(const int step) { return false; } -unique_ptr make_tsne(double* const X, const int N, const int D, - double* const Y, const int no_dims, - const double perplexity, const double theta, +unique_ptr make_tsne(double* const X, + const int N, + const int D, + double* const Y, + const int no_dims, + const double perplexity, + const double theta, const int rand_seed, const bool skip_random_init, - const double* const init, const bool use_init, - const int max_iter, const int stop_lying_iter, + const double* const init, + const bool use_init, + const int max_iter, + const int stop_lying_iter, const int mom_switch_iter) { switch (no_dims) { case 2: - return make_tsne_impl<2>(X, N, D, Y, perplexity, theta, rand_seed, - skip_random_init, init, use_init, max_iter, - stop_lying_iter, mom_switch_iter); + return make_tsne_impl<2>(X, + N, + D, + Y, + perplexity, + theta, + rand_seed, + skip_random_init, + init, + use_init, + max_iter, + stop_lying_iter, + mom_switch_iter); case 3: - return make_tsne_impl<3>(X, N, D, Y, perplexity, theta, rand_seed, - skip_random_init, init, use_init, max_iter, - stop_lying_iter, mom_switch_iter); + return make_tsne_impl<3>(X, + N, + D, + Y, + perplexity, + theta, + rand_seed, + skip_random_init, + init, + use_init, + max_iter, + stop_lying_iter, + mom_switch_iter); default: throw "unsupported dimension"; } @@ -896,18 +1004,35 @@ bool TSNEState::step_by(int step) { } // namespace extern "C" { -struct TSNE* DLL_PUBLIC init_tsne(double* const X, const int N, const int D, - double* const Y, const int no_dims, - const double perplexity, const double theta, +struct TSNE* DLL_PUBLIC init_tsne(double* const X, + const int N, + const int D, + double* const Y, + const int no_dims, + const double perplexity, + const double theta, const int rand_seed, const bool skip_random_init, - const double* const init, const bool use_init, - const int max_iter, const int stop_lying_iter, + const double* const init, + const bool use_init, + const int max_iter, + const int stop_lying_iter, const int mom_switch_iter) { assert(no_dims == 2 || no_dims == 3); - auto tsne = make_tsne(X, N, D, Y, no_dims, perplexity, theta, rand_seed, - skip_random_init, init, use_init, max_iter, - stop_lying_iter, mom_switch_iter); + auto tsne = make_tsne(X, + N, + D, + Y, + no_dims, + perplexity, + theta, + rand_seed, + skip_random_init, + init, + use_init, + max_iter, + stop_lying_iter, + mom_switch_iter); return reinterpret_cast(tsne.release()); } @@ -921,15 +1046,34 @@ void DLL_PUBLIC free_tsne(struct TSNE* const tsne) { delete reinterpret_cast(tsne); } -void DLL_PUBLIC run(double* const X, const int N, const int D, double* const Y, - const int no_dims, const double perplexity, - const double theta, const int rand_seed, - const bool skip_random_init, const double* const init, - const bool use_init, const int max_iter, - const int stop_lying_iter, const int mom_switch_iter) { - auto tsne = make_tsne(X, N, D, Y, no_dims, perplexity, theta, rand_seed, - skip_random_init, init, use_init, max_iter, - stop_lying_iter, mom_switch_iter); +void DLL_PUBLIC run(double* const X, + const int N, + const int D, + double* const Y, + const int no_dims, + const double perplexity, + const double theta, + const int rand_seed, + const bool skip_random_init, + const double* const init, + const bool use_init, + const int max_iter, + const int stop_lying_iter, + const int mom_switch_iter) { + auto tsne = make_tsne(X, + N, + D, + Y, + no_dims, + perplexity, + theta, + rand_seed, + skip_random_init, + init, + use_init, + max_iter, + stop_lying_iter, + mom_switch_iter); tsne->step_by(max_iter); } diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index 0778b9a..f0bc356 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -50,17 +50,33 @@ extern "C" { // stateless t-SNE -void DLL_PUBLIC run(double* X, int N, int D, double* Y, int no_dims, - double perplexity, double theta, int rand_seed, - bool skip_random_init, const double* init, bool use_init, - int max_iter = 1000, int stop_lying_iter = 250, +void DLL_PUBLIC run(double* X, + int N, + int D, + double* Y, + int no_dims, + double perplexity, + double theta, + int rand_seed, + bool skip_random_init, + const double* init, + bool use_init, + int max_iter = 1000, + int stop_lying_iter = 250, int mom_switch_iter = 250); // stateful t-SNE struct TSNE; -struct TSNE* DLL_PUBLIC init_tsne(double* X, int N, int D, double* Y, - int no_dims, double perplexity, double theta, - int rand_seed, bool skip_random_init, - const double* init, bool use_init, +struct TSNE* DLL_PUBLIC init_tsne(double* X, + int N, + int D, + double* Y, + int no_dims, + double perplexity, + double theta, + int rand_seed, + bool skip_random_init, + const double* init, + bool use_init, int max_iter = 1000, int stop_lying_iter = 250, int mom_switch_iter = 250); diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index 2330b10..cd3aad2 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -84,7 +84,9 @@ class VpTree { } // Function that uses the tree to find the k nearest neighbors of target - void search(const T& target, int k, std::vector* results, + void search(const T& target, + int k, + std::vector* results, std::vector* distances) { // Use a priority queue to store intermediate results on std::priority_queue heap; @@ -159,11 +161,13 @@ class VpTree { // Partition around the median distance int median = (upper + lower) / 2; auto& lower_item = _items[lower]; - std::nth_element( - _items.begin() + lower + 1, _items.begin() + median, - _items.begin() + upper, [this, lower_item](auto& x, auto& y) { - return distance(lower_item, x) < distance(lower_item, y); - }); + std::nth_element(_items.begin() + lower + 1, + _items.begin() + median, + _items.begin() + upper, + [this, lower_item](auto& x, auto& y) { + return distance(lower_item, x) < + distance(lower_item, y); + }); // Threshold of the new node will be the distance to the median node->threshold = distance(lower_item, _items[median]); @@ -179,7 +183,9 @@ class VpTree { } // Helper function that searches the tree - void search(const Node* node, const T& target, int k, + void search(const Node* node, + const T& target, + int k, std::priority_queue& heap) { if (node == nullptr) return; // indicates that we're done here From 48300a01c11c26f01cefdd9cafe0cbb81094a1f3 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 17:30:01 -0700 Subject: [PATCH 09/24] clang-tidy google-readability-braces-around-statements --- tsne/bh_sne_src/main.cpp | 6 +- tsne/bh_sne_src/sptree.cpp | 83 ++++++++++++------- tsne/bh_sne_src/tsne.cpp | 161 ++++++++++++++++++++++++------------- tsne/bh_sne_src/vptree.h | 3 +- 4 files changed, 165 insertions(+), 88 deletions(-) diff --git a/tsne/bh_sne_src/main.cpp b/tsne/bh_sne_src/main.cpp index ff574f1..750e695 100644 --- a/tsne/bh_sne_src/main.cpp +++ b/tsne/bh_sne_src/main.cpp @@ -63,8 +63,9 @@ bool load_data(const char* dat_file, fread(max_iter, sizeof(int), 1, h); // maximum number of iterations data->resize(*d * *n); fread(data->data(), sizeof(double), *n * *d, h); // the data - if (!feof(h)) + if (!feof(h)) { fread(rand_seed, sizeof(int), 1, h); // random seed + } fclose(h); fprintf(stderr, "Read the %i x %i data matrix successfully!\n", *n, *d); return true; @@ -143,8 +144,9 @@ int main(int argc, char* argv[]) { // Make dummy landmarks N = origN; vector landmarks(N); - for (int n = 0; n < N; n++) + for (int n = 0; n < N; n++) { landmarks[n] = n; + } // Now fire up the SNE implementation vector Y(N * no_dims); diff --git a/tsne/bh_sne_src/sptree.cpp b/tsne/bh_sne_src/sptree.cpp index 0163859..75b6abb 100644 --- a/tsne/bh_sne_src/sptree.cpp +++ b/tsne/bh_sne_src/sptree.cpp @@ -47,10 +47,12 @@ using std::vector; template Cell::Cell(const double* inp_corner, const double* inp_width) { - for (int d = 0; d < NDims; d++) + for (int d = 0; d < NDims; d++) { setCorner(d, inp_corner[d]); - for (int d = 0; d < NDims; d++) + } + for (int d = 0; d < NDims; d++) { setWidth(d, inp_width[d]); + } } template @@ -78,10 +80,12 @@ template bool Cell::containsPoint(const double point[]) const { assert(point); for (int d = 0; d < NDims; d++) { - if (corner[d] - width[d] > point[d]) + if (corner[d] - width[d] > point[d]) { return false; - if (corner[d] + width[d] < point[d]) + } + if (corner[d] + width[d] < point[d]) { return false; + } } return true; } @@ -101,21 +105,25 @@ SPTree::SPTree(const double* inp_data, unsigned int N) : data(inp_data) { for (unsigned int n = 0; n < N; n++) { for (unsigned int d = 0; d < NDims; d++) { mean_Y[d] += inp_data[n * NDims + d]; - if (inp_data[nD + d] < min_Y[d]) + if (inp_data[nD + d] < min_Y[d]) { min_Y[d] = inp_data[nD + d]; - if (inp_data[nD + d] > max_Y[d]) + } + if (inp_data[nD + d] > max_Y[d]) { max_Y[d] = inp_data[nD + d]; + } } nD += NDims; } - for (int d = 0; d < NDims; d++) + for (int d = 0; d < NDims; d++) { mean_Y[d] /= (double)N; + } // Construct SPTree array width; - for (int d = 0; d < NDims; d++) + for (int d = 0; d < NDims; d++) { width[d] = fmax(max_Y[d] - mean_Y[d], mean_Y[d] - min_Y[d]) + 1e-5; + } node.init(mean_Y.data(), width.data()); fill(N); } @@ -126,13 +134,16 @@ void SPTree::Node::init(const double* inp_corner, const double* inp_width) { cum_size = 0; - for (unsigned int d = 0; d < NDims; d++) + for (unsigned int d = 0; d < NDims; d++) { boundary.setCorner(d, inp_corner[d]); - for (unsigned int d = 0; d < NDims; d++) + } + for (unsigned int d = 0; d < NDims; d++) { boundary.setWidth(d, inp_width[d]); + } - for (unsigned int d = 0; d < NDims; d++) + for (unsigned int d = 0; d < NDims; d++) { center_of_mass[d] = .0; + } } // Insert a point into the SPTree @@ -141,8 +152,9 @@ bool SPTree::Node::insert(const double* data, unsigned int new_index) { assert(data); // Ignore objects which do not belong in this quad tree const double* point = data + new_index * NDims; - if (!boundary.containsPoint(point)) + if (!boundary.containsPoint(point)) { return false; + } // Online update of cumulative size and center-of-mass ++cum_size; @@ -168,18 +180,22 @@ bool SPTree::Node::insert(const double* data, unsigned int new_index) { break; } } - if (duplicate) + if (duplicate) { return true; + } } // Otherwise, we need to subdivide the current cell - if (!children) + if (!children) { subdivide(data); + } // Find out where the point can be inserted - for (auto& child : *children) - if (child.insert(data, new_index)) + for (auto& child : *children) { + if (child.insert(data, new_index)) { return true; + } + } // Otherwise, the point cannot be inserted (this should never happen) return false; } @@ -199,10 +215,11 @@ void SPTree::Node::subdivide(const double* data) { for (unsigned int i = 0; i < no_children; i++) { unsigned int div = 1; for (unsigned int d = 0; d < NDims; d++) { - if ((i / div) % 2 == 1) + if ((i / div) % 2 == 1) { new_corner[d] = boundary.getCorner(d) - .5 * boundary.getWidth(d); - else + } else { new_corner[d] = boundary.getCorner(d) + .5 * boundary.getWidth(d); + } div *= 2; } (*children)[i].init(new_corner, new_width); @@ -220,8 +237,9 @@ void SPTree::Node::subdivide(const double* data) { // Build SPTree on dataset template void SPTree::fill(unsigned int N) { - for (unsigned int i = 0; i < N; i++) + for (unsigned int i = 0; i < N; i++) { node.insert(data, i); + } } // Checks whether the specified tree is correct @@ -234,8 +252,9 @@ template bool SPTree::Node::isCorrect(const double* data) const { if (!children && cum_size > 0) { const double* point = data + index * NDims; - if (!boundary.containsPoint(point)) + if (!boundary.containsPoint(point)) { return false; + } } if (children) { for (const auto& child : *children) { @@ -264,8 +283,9 @@ unsigned int SPTree::Node::getAllIndices(unsigned int* indices, // Gather indices in children if (children) { - for (auto& child : *children) + for (auto& child : *children) { loc = child.getAllIndices(indices, loc); + } } return loc; } @@ -287,8 +307,9 @@ template double neg_f[], double* sum_Q) const { // Make sure that we spend no time on empty nodes or self-interactions - if (cum_size == 0 || (!children && cum_size >= 1 && index == point_index)) + if (cum_size == 0 || (!children && cum_size >= 1 && index == point_index)) { return; + } // Compute distance between point and center-of-mass double sqdist = .0; @@ -312,12 +333,14 @@ template double mult = cum_size * sqdist; *sum_Q += mult; mult *= sqdist; - for (unsigned int d = 0; d < NDims; d++) + for (unsigned int d = 0; d < NDims; d++) { neg_f[d] += mult * buff[d]; + } } else { // Recursively apply Barnes-Hut to children - for (const auto& child : *children) + for (const auto& child : *children) { child.computeNonEdgeForces(data, point_index, theta, neg_f, sum_Q); + } } } @@ -356,8 +379,9 @@ vector SPTree::Node::computeEdgeForces(const double* data, sqdist = val_P[i] / sqdist; // Sum positive force - for (unsigned int d = 0; d < NDims; d++) + for (unsigned int d = 0; d < NDims; d++) { pos_f[ind1 + d] += sqdist * buff[d]; + } } ind1 += NDims; } @@ -381,18 +405,21 @@ void SPTree::Node::print(const double* data) const { fprintf(stderr, "Leaf node; data = ["); if (!children && cum_size > 0) { const double* point = data + index * NDims; - for (int d = 0; d < NDims; d++) + for (int d = 0; d < NDims; d++) { fprintf(stderr, "%f, ", point[d]); + } fprintf(stderr, " (index = %d)", index); } fprintf(stderr, "]\n"); } else { fprintf(stderr, "Intersection node with center-of-mass = ["); - for (int d = 0; d < NDims; d++) + for (int d = 0; d < NDims; d++) { fprintf(stderr, "%f, ", center_of_mass[d]); + } fprintf(stderr, "]; children are:\n"); - for (const auto& child : *children) + for (const auto& child : *children) { child.print(data); + } } } diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index d527e77..0beb100 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -204,8 +204,9 @@ void computeGradient(const vector& inp_row_P, vector neg_f(N * NDIMS); auto pos_f = tree.computeEdgeForces( inp_row_P.data(), inp_col_P.data(), inp_val_P.data(), N); - for (int n = 0; n < N; n++) + for (int n = 0; n < N; n++) { tree.computeNonEdgeForces(n, theta, neg_f.data() + n * NDIMS, &sum_Q); + } // Compute final t-SNE gradient vector& dC = *_dC; @@ -277,13 +278,15 @@ double evaluateError(const vector& P, if (n != m) { Q[nN + m] = 1 / (1 + DD[nN + m]); sum_Q += Q[nN + m]; - } else + } else { Q[nN + m] = DBL_MIN; + } } nN += N; } - for (int i = 0; i < N * N; i++) + for (int i = 0; i < N * N; i++) { Q[i] /= sum_Q; + } // Sum t-SNE error double C = .0; @@ -307,8 +310,9 @@ double evaluateError(const vector& row_P, double sum_Q = .0; { SPTree tree(Y, N); - for (int n = 0; n < N; n++) + for (int n = 0; n < N; n++) { tree.computeNonEdgeForces(n, theta, buff.data(), &sum_Q); + } } // Loop over all edges to compute t-SNE error @@ -319,12 +323,15 @@ double evaluateError(const vector& row_P, for (int i = row_P[n]; i < row_P[n + 1]; i++) { Q = .0; ind2 = col_P[i] * NDIMS; - for (int d = 0; d < NDIMS; d++) + for (int d = 0; d < NDIMS; d++) { buff[d] = Y[ind1 + d]; - for (int d = 0; d < NDIMS; d++) + } + for (int d = 0; d < NDIMS; d++) { buff[d] -= Y[ind2 + d]; - for (int d = 0; d < NDIMS; d++) + } + for (int d = 0; d < NDIMS; d++) { Q += buff[d] * buff[d]; + } Q = (1.0 / (1.0 + Q)) / sum_Q; C += val_P[i] * log((val_P[i] + FLT_MIN) / (Q + FLT_MIN)); } @@ -358,17 +365,20 @@ void computeGaussianPerplexity(const double* const X, int iter = 0; while (!found && iter < 200) { // Compute Gaussian kernel row - for (int m = 0; m < N; m++) + for (int m = 0; m < N; m++) { P[nN + m] = exp(-beta * DD[nN + m]); + } P[nN + n] = DBL_MIN; // Compute entropy of current row sum_P = DBL_MIN; - for (int m = 0; m < N; m++) + for (int m = 0; m < N; m++) { sum_P += P[nN + m]; + } double H = 0.0; - for (int m = 0; m < N; m++) + for (int m = 0; m < N; m++) { H += beta * (DD[nN + m] * P[nN + m]); + } H = (H / sum_P) + log(sum_P); // Evaluate whether the entropy is within the tolerance level @@ -378,16 +388,18 @@ void computeGaussianPerplexity(const double* const X, } else { if (Hdiff > 0) { min_beta = beta; - if (max_beta == DBL_MAX || max_beta == -DBL_MAX) + if (max_beta == DBL_MAX || max_beta == -DBL_MAX) { beta *= 2.0; - else + } else { beta = (beta + max_beta) / 2.0; + } } else { max_beta = beta; - if (min_beta == -DBL_MAX || min_beta == DBL_MAX) + if (min_beta == -DBL_MAX || min_beta == DBL_MAX) { beta /= 2.0; - else + } else { beta = (beta + min_beta) / 2.0; + } } } @@ -396,8 +408,9 @@ void computeGaussianPerplexity(const double* const X, } // Row normalize P - for (int m = 0; m < N; m++) + for (int m = 0; m < N; m++) { P[nN + m] /= sum_P; + } nN += N; } } @@ -411,8 +424,9 @@ void computeGaussianPerplexity(const double* const X, vector* _val_P, const double perplexity, const int K) { - if (perplexity > K) + if (perplexity > K) { fprintf(stderr, "Perplexity should be lower than K!\n"); + } // Allocate the memory we need _row_P->resize(N + 1); @@ -423,14 +437,16 @@ void computeGaussianPerplexity(const double* const X, vector& val_P = *_val_P; vector cur_P(N - 1); row_P[0] = 0; - for (int n = 0; n < N; n++) + for (int n = 0; n < N; n++) { row_P[n + 1] = row_P[n] + (unsigned int)K; + } // Build ball tree on data set VpTree tree((euclidean_distance(D, X))); vector obj_X(N); - for (int n = 0; n < N; n++) + for (int n = 0; n < N; n++) { obj_X[n] = n; + } tree.create(obj_X); // Loop over all points to find nearest neighbors @@ -440,8 +456,9 @@ void computeGaussianPerplexity(const double* const X, indices.reserve(N); distances.reserve(N); for (int n = 0; n < N; n++) { - if (n % 10000 == 0) + if (n % 10000 == 0) { fprintf(stderr, " - point %d of %d\n", n, N); + } // Find nearest neighbors indices.clear(); @@ -460,16 +477,19 @@ void computeGaussianPerplexity(const double* const X, double sum_P; while (!found && iter < 200) { // Compute Gaussian kernel row - for (int m = 0; m < K; m++) + for (int m = 0; m < K; m++) { cur_P[m] = exp(-beta * distances[m + 1] * distances[m + 1]); + } // Compute entropy of current row sum_P = DBL_MIN; - for (int m = 0; m < K; m++) + for (int m = 0; m < K; m++) { sum_P += cur_P[m]; + } double H = .0; - for (int m = 0; m < K; m++) + for (int m = 0; m < K; m++) { H += beta * (distances[m + 1] * distances[m + 1] * cur_P[m]); + } H = (H / sum_P) + log(sum_P); // Evaluate whether the entropy is within the tolerance level @@ -479,16 +499,18 @@ void computeGaussianPerplexity(const double* const X, } else { if (Hdiff > 0) { min_beta = beta; - if (max_beta == DBL_MAX || max_beta == -DBL_MAX) + if (max_beta == DBL_MAX || max_beta == -DBL_MAX) { beta *= 2.0; - else + } else { beta = (beta + max_beta) / 2.0; + } } else { max_beta = beta; - if (min_beta == -DBL_MAX || min_beta == DBL_MAX) + if (min_beta == -DBL_MAX || min_beta == DBL_MAX) { beta /= 2.0; - else + } else { beta = (beta + min_beta) / 2.0; + } } } @@ -497,8 +519,9 @@ void computeGaussianPerplexity(const double* const X, } // Row-normalize current row of P and store in matrix - for (unsigned int m = 0; m < K; m++) + for (unsigned int m = 0; m < K; m++) { cur_P[m] /= sum_P; + } for (unsigned int m = 0; m < K; m++) { col_P[row_P[n] + m] = indices[m + 1]; val_P[row_P[n] + m] = cur_P[m]; @@ -523,20 +546,22 @@ void symmetrizeMatrix(vector* _row_P, // Check whether element (col_P[i], n) is present bool present = false; for (int m = row_P[col_P[i]]; m < row_P[col_P[i] + 1]; m++) { - if (col_P[m] == n) + if (col_P[m] == n) { present = true; + } } - if (present) + if (present) { row_counts[n]++; - else { + } else { row_counts[n]++; row_counts[col_P[i]]++; } } } int no_elem = 0; - for (int n = 0; n < N; n++) + for (int n = 0; n < N; n++) { no_elem += row_counts[n]; + } // Allocate memory for symmetrized matrix vector sym_row_P(N + 1); @@ -545,8 +570,9 @@ void symmetrizeMatrix(vector* _row_P, // Construct new row indices for symmetric matrix sym_row_P[0] = 0; - for (int n = 0; n < N; n++) + for (int n = 0; n < N; n++) { sym_row_P[n + 1] = sym_row_P[n] + (unsigned int)row_counts[n]; + } // Fill the result matrix vector offset(N); @@ -580,15 +606,17 @@ void symmetrizeMatrix(vector* _row_P, // Update offsets if (!present || (present && n <= col_P[i])) { offset[n]++; - if (col_P[i] != n) + if (col_P[i] != n) { offset[col_P[i]]++; + } } } } // Divide the result by two - for (int i = 0; i < no_elem; i++) + for (int i = 0; i < no_elem; i++) { sym_val_P[i] /= 2.0; + } // Return symmetrized matrices *_row_P = move(sym_row_P); @@ -744,11 +772,13 @@ unique_ptr make_tsne_impl(double* const X, double max_X = .0; for (int i = 0; i < N * D; i++) { auto ax = abs(X[i]); - if (ax > max_X) + if (ax > max_X) { max_X = ax; + } } - for (int i = 0; i < N * D; i++) + for (int i = 0; i < N * D; i++) { X[i] /= max_X; + } // Compute input similarities for exact t-SNE vector P; @@ -774,10 +804,12 @@ unique_ptr make_tsne_impl(double* const X, nN += N; } double sum_P = .0; - for (int i = 0; i < N * N; i++) + for (int i = 0; i < N * N; i++) { sum_P += P[i]; - for (int i = 0; i < N * N; i++) + } + for (int i = 0; i < N * N; i++) { P[i] /= sum_P; + } } else { // Compute input similarities for approximate t-SNE @@ -794,20 +826,24 @@ unique_ptr make_tsne_impl(double* const X, // Symmetrize input similarities symmetrizeMatrix(&row_P, &col_P, &val_P, N); double sum_P = .0; - for (int i = 0; i < row_P[N]; i++) + for (int i = 0; i < row_P[N]; i++) { sum_P += val_P[i]; - for (int i = 0; i < row_P[N]; i++) + } + for (int i = 0; i < row_P[N]; i++) { val_P[i] /= sum_P; + } } end = clock(); // Lie about the P-values if (exact) { - for (int i = 0; i < N * N; i++) + for (int i = 0; i < N * N; i++) { P[i] *= 12.0; + } } else { - for (int i = 0; i < row_P[N]; i++) + for (int i = 0; i < row_P[N]; i++) { val_P[i] *= 12.0; + } } // Initialize solution (randomly or with given coordinates) @@ -822,18 +858,19 @@ unique_ptr make_tsne_impl(double* const X, } // Perform main training loop - if (exact) + if (exact) { fprintf(stderr, "Input similarities computed in %4.2f seconds!\nLearning " "embedding...\n", static_cast(end - start) / CLOCKS_PER_SEC); - else + } else { fprintf(stderr, "Input similarities computed in %4.2f seconds (sparsity = " "%f)!\nLearning embedding...\n", static_cast(end - start) / CLOCKS_PER_SEC, static_cast(row_P[N]) / (static_cast(N) * static_cast(N))); + } return std::make_unique(N, Y, @@ -869,24 +906,30 @@ bool TSNEState::step_by_impl(const int step) { for (; iter < iter_until; iter++) { // Compute (approximate) gradient - if (exact) + if (exact) { computeExactGradient(P, Y, N, &dY); - else + } else { computeGradient(row_P, col_P, val_P, Y, N, &dY, theta); + } // Update gains - for (int i = 0; i < N * NDIMS; i++) + for (int i = 0; i < N * NDIMS; i++) { gains[i] = (sign(dY[i]) != sign(uY[i])) ? (gains[i] + .2) : (gains[i] * .8); - for (int i = 0; i < N * NDIMS; i++) - if (gains[i] < .01) + } + for (int i = 0; i < N * NDIMS; i++) { + if (gains[i] < .01) { gains[i] = .01; + } + } // Perform gradient update (with momentum and gains) - for (int i = 0; i < N * NDIMS; i++) + for (int i = 0; i < N * NDIMS; i++) { uY[i] = momentum * uY[i] - eta * gains[i] * dY[i]; - for (int i = 0; i < N * NDIMS; i++) + } + for (int i = 0; i < N * NDIMS; i++) { Y[i] = Y[i] + uY[i]; + } // Make solution zero-mean zeroMean(Y, N); @@ -894,15 +937,18 @@ bool TSNEState::step_by_impl(const int step) { // Stop lying about the P-values after a while, and switch momentum if (iter == stop_lying_iter) { if (exact) { - for (int i = 0; i < N * N; i++) + for (int i = 0; i < N * N; i++) { P[i] /= 12.0; + } } else { - for (int i = 0; i < row_P[N]; i++) + for (int i = 0; i < row_P[N]; i++) { val_P[i] /= 12.0; + } } } - if (iter == mom_switch_iter) + if (iter == mom_switch_iter) { momentum = final_momentum; + } // Print out progress if (iter > 0 && (iter % 50 == 0 || iter == max_iter - 1)) { @@ -914,9 +960,9 @@ bool TSNEState::step_by_impl(const int step) { // doing approximate computation here! C = evaluateError(row_P, col_P, val_P, Y, N, theta); } - if (iter == 0) + if (iter == 0) { fprintf(stderr, "Iteration %d: error is %f\n", iter + 1, C); - else { + } else { elapsed = (float)(end - start) / CLOCKS_PER_SEC; total_time += elapsed; residual_time += elapsed; @@ -1041,8 +1087,9 @@ bool DLL_PUBLIC step_tsne_by(struct TSNE* const tsne, const int step) { } void DLL_PUBLIC free_tsne(struct TSNE* const tsne) { - if (tsne == nullptr) + if (tsne == nullptr) { return; + } delete reinterpret_cast(tsne); } diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index cd3aad2..d6c7630 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -187,8 +187,9 @@ class VpTree { const T& target, int k, std::priority_queue& heap) { - if (node == nullptr) + if (node == nullptr) { return; // indicates that we're done here + } // Compute distance between target and current node double dist = distance(_items[node->index], target); From 68cd3cef96040f1de8764e30bc2b7c6ca55aa05a Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 17:32:15 -0700 Subject: [PATCH 10/24] clang-tidy google-readability-casting --- tsne/bh_sne_src/sptree.cpp | 2 +- tsne/bh_sne_src/tsne.cpp | 10 +++++----- tsne/bh_sne_src/vptree.h | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tsne/bh_sne_src/sptree.cpp b/tsne/bh_sne_src/sptree.cpp index 75b6abb..e9367ac 100644 --- a/tsne/bh_sne_src/sptree.cpp +++ b/tsne/bh_sne_src/sptree.cpp @@ -116,7 +116,7 @@ SPTree::SPTree(const double* inp_data, unsigned int N) : data(inp_data) { } for (int d = 0; d < NDims; d++) { - mean_Y[d] /= (double)N; + mean_Y[d] /= static_cast(N); } // Construct SPTree diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 0beb100..e3a0568 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -438,7 +438,7 @@ void computeGaussianPerplexity(const double* const X, vector cur_P(N - 1); row_P[0] = 0; for (int n = 0; n < N; n++) { - row_P[n + 1] = row_P[n] + (unsigned int)K; + row_P[n + 1] = row_P[n] + static_cast(K); } // Build ball tree on data set @@ -571,7 +571,7 @@ void symmetrizeMatrix(vector* _row_P, // Construct new row indices for symmetric matrix sym_row_P[0] = 0; for (int n = 0; n < N; n++) { - sym_row_P[n + 1] = sym_row_P[n] + (unsigned int)row_counts[n]; + sym_row_P[n + 1] = sym_row_P[n] + static_cast(row_counts[n]); } // Fill the result matrix @@ -733,7 +733,7 @@ unique_ptr make_tsne_impl(double* const X, if (skip_random_init != true) { if (rand_seed >= 0) { fprintf(stderr, "Using random seed: %d\n", rand_seed); - srand((unsigned int)rand_seed); + srand(static_cast(rand_seed)); } else { fprintf(stderr, "Using current time as random seed...\n"); srand(time(nullptr)); @@ -963,7 +963,7 @@ bool TSNEState::step_by_impl(const int step) { if (iter == 0) { fprintf(stderr, "Iteration %d: error is %f\n", iter + 1, C); } else { - elapsed = (float)(end - start) / CLOCKS_PER_SEC; + elapsed = static_cast(end - start) / CLOCKS_PER_SEC; total_time += elapsed; residual_time += elapsed; fprintf(stderr, @@ -977,7 +977,7 @@ bool TSNEState::step_by_impl(const int step) { } } end = clock(); - elapsed = (float)(end - start) / CLOCKS_PER_SEC; + elapsed = static_cast(end - start) / CLOCKS_PER_SEC; total_time += elapsed; residual_time += elapsed; diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index d6c7630..7799a3c 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -155,7 +155,9 @@ class VpTree { if (upper - lower > 1) { // if we did not arrive at leaf yet // Choose an arbitrary point and move it to the start - int i = (int)((double)rand() / RAND_MAX * (upper - lower - 1)) + lower; + int i = static_cast(static_cast(rand()) / RAND_MAX * + (upper - lower - 1)) + + lower; std::swap(_items[lower], _items[i]); // Partition around the median distance From 30422ff31144713f95e459180cca5e8be3e1dcbb Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 18:30:05 -0700 Subject: [PATCH 11/24] clang-tidy modernize-use-equals-delete --- tsne/bh_sne_src/main.cpp | 6 +++++- tsne/bh_sne_src/sptree.cpp | 5 +++-- tsne/bh_sne_src/sptree.h | 8 ++++---- tsne/bh_sne_src/vptree.h | 17 ++++++++++------- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tsne/bh_sne_src/main.cpp b/tsne/bh_sne_src/main.cpp index 750e695..a5c3fbe 100644 --- a/tsne/bh_sne_src/main.cpp +++ b/tsne/bh_sne_src/main.cpp @@ -37,7 +37,11 @@ #include "tsne.h" -using namespace std; +using std::fclose; +using std::feof; +using std::fprintf; +using std::fread; +using std::vector; // Function that loads data from a t-SNE file bool load_data(const char* dat_file, diff --git a/tsne/bh_sne_src/sptree.cpp b/tsne/bh_sne_src/sptree.cpp index e9367ac..a3f61e2 100644 --- a/tsne/bh_sne_src/sptree.cpp +++ b/tsne/bh_sne_src/sptree.cpp @@ -158,8 +158,9 @@ bool SPTree::Node::insert(const double* data, unsigned int new_index) { // Online update of cumulative size and center-of-mass ++cum_size; - double mult1 = (double)(cum_size - 1) / (double)cum_size; - double mult2 = 1.0 / (double)cum_size; + double mult1 = + static_cast(cum_size - 1) / static_cast(cum_size); + double mult2 = 1.0 / static_cast(cum_size); for (unsigned int d = 0; d < NDims; d++) { center_of_mass[d] = center_of_mass[d] * mult1 + mult2 * point[d]; diff --git a/tsne/bh_sne_src/sptree.h b/tsne/bh_sne_src/sptree.h index c004cab..b77a949 100644 --- a/tsne/bh_sne_src/sptree.h +++ b/tsne/bh_sne_src/sptree.h @@ -103,12 +103,12 @@ class SPTree { Node node; - SPTree(const SPTree&) = delete; - SPTree& operator=(const SPTree&) = delete; - public: SPTree() = default; - SPTree(SPTree&&) = default; + SPTree(SPTree&&) noexcept = default; + SPTree(const SPTree&) = delete; + SPTree& operator=(SPTree&&) noexcept = default; + SPTree& operator=(const SPTree&) = delete; SPTree(const double* inp_data, unsigned int N); ~SPTree() = default; bool isCorrect() const; diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index 7799a3c..0c1d956 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -74,6 +74,9 @@ class VpTree { // Default constructor explicit VpTree(Distance&& distance) : distance(distance){}; + VpTree(const VpTree&) = delete; + VpTree& operator=(const VpTree&) = delete; + // Destructor ~VpTree() = default; @@ -119,13 +122,16 @@ class VpTree { // Single node of a VP tree (has a point and radius; left children are closer // to point than the radius) struct Node { - int index; // index of point in node - double threshold; // radius(?) + int index = 0; // index of point in node + double threshold = 0; // radius(?) std::unique_ptr left; // points closer by than threshold std::unique_ptr right; // points farther away than threshold - Node() : index(0), threshold(0.) { - } + Node() = default; + Node(Node&&) noexcept = default; + Node(const Node&) = delete; + Node& operator=(Node&&) noexcept = default; + Node& operator=(const Node&) = delete; ~Node() = default; }; @@ -245,9 +251,6 @@ class VpTree { } } } - - VpTree(const VpTree&) = delete; - VpTree& operator=(const VpTree&) = delete; }; #pragma GCC visibility pop From ed93c6b1e6c5b89cb7b2f1953ce2b118a8e6749c Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 18:31:31 -0700 Subject: [PATCH 12/24] clang-tidy bugprone-reserved-identifier --- tsne/bh_sne_src/vptree.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index 0c1d956..d4aff3c 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -48,19 +48,19 @@ #pragma GCC visibility push(hidden) class euclidean_distance final { - const int _D; + const int D; const double* const data; public: - explicit euclidean_distance(int D, const double* data) : _D(D), data(data) { + explicit euclidean_distance(int D, const double* data) : D(D), data(data) { } euclidean_distance(euclidean_distance&&) = default; euclidean_distance(const euclidean_distance&) = default; double operator()(const unsigned int t1, const unsigned int t2) const { double dd = .0; - const double* x1 = data + t1 * _D; - const double* x2 = data + t2 * _D; - for (int d = 0; d < _D; d++) { + const double* x1 = data + t1 * D; + const double* x2 = data + t2 * D; + for (int d = 0; d < D; d++) { double diff = (x1[d] - x2[d]); dd += diff * diff; } From 22a0a06f23f2b54471539dd90c1742d9d636f0bd Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 18:37:19 -0700 Subject: [PATCH 13/24] clang-tidy modernize-use-nodiscard --- tsne/bh_sne_src/sptree.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsne/bh_sne_src/sptree.h b/tsne/bh_sne_src/sptree.h index b77a949..ee579fc 100644 --- a/tsne/bh_sne_src/sptree.h +++ b/tsne/bh_sne_src/sptree.h @@ -50,8 +50,8 @@ class alignas(16) Cell { Cell(const double* inp_corner, const double* inp_width); ~Cell() = default; - double getCorner(unsigned int d) const; - double getWidth(unsigned int d) const; + [[nodiscard]] double getCorner(unsigned int d) const; + [[nodiscard]] double getWidth(unsigned int d) const; void setCorner(unsigned int d, double val); void setWidth(unsigned int d, double val); bool containsPoint(const double point[]) const; @@ -111,7 +111,7 @@ class SPTree { SPTree& operator=(const SPTree&) = delete; SPTree(const double* inp_data, unsigned int N); ~SPTree() = default; - bool isCorrect() const; + [[nodiscard]] bool isCorrect() const; void getAllIndices(unsigned int* indices) const; void computeNonEdgeForces(unsigned int point_index, double theta, From 6bc1ec3e1c8f6de92b2ab7b2f79a6b3f793c34b7 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 18:49:47 -0700 Subject: [PATCH 14/24] clang-tidy bugprone-narrowing-conversions and deadcode. --- tsne/bh_sne_src/tsne.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index e3a0568..5f29ca4 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -320,7 +320,7 @@ double evaluateError(const vector& row_P, double C = .0, Q; for (int n = 0; n < N; n++) { ind1 = n * NDIMS; - for (int i = row_P[n]; i < row_P[n + 1]; i++) { + for (unsigned int i = row_P[n]; i < row_P[n + 1]; i++) { Q = .0; ind2 = col_P[i] * NDIMS; for (int d = 0; d < NDIMS; d++) { @@ -542,10 +542,10 @@ void symmetrizeMatrix(vector* _row_P, // Count number of elements and row counts of symmetric matrix vector row_counts(N); for (int n = 0; n < N; n++) { - for (int i = row_P[n]; i < row_P[n + 1]; i++) { + for (unsigned int i = row_P[n]; i < row_P[n + 1]; i++) { // Check whether element (col_P[i], n) is present bool present = false; - for (int m = row_P[col_P[i]]; m < row_P[col_P[i] + 1]; m++) { + for (unsigned int m = row_P[col_P[i]]; m < row_P[col_P[i] + 1]; m++) { if (col_P[m] == n) { present = true; } @@ -710,7 +710,6 @@ double randn() { } while ((radius >= 1.0) || (radius == 0.0)); radius = sqrt(-2 * log(radius) / radius); x *= radius; - y *= radius; return x; } From 2f968b6f52ac77df4e94dd413efbefec4c022c84 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:15:02 -0700 Subject: [PATCH 15/24] clang-tidy: Adjust types and add constructors. --- tsne/bh_sne_src/main.cpp | 4 ++-- tsne/bh_sne_src/sptree.h | 4 ++++ tsne/bh_sne_src/tsne.cpp | 2 +- tsne/bh_sne_src/tsne.h | 3 +++ tsne/bh_sne_src/vptree.h | 7 ++++++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tsne/bh_sne_src/main.cpp b/tsne/bh_sne_src/main.cpp index a5c3fbe..4a3d7bf 100644 --- a/tsne/bh_sne_src/main.cpp +++ b/tsne/bh_sne_src/main.cpp @@ -54,8 +54,8 @@ bool load_data(const char* dat_file, int* rand_seed, int* max_iter) { // Open file, read first 2 integers, allocate memory, and read the data - FILE* h; - if ((h = fopen(dat_file, "r+b")) == nullptr) { + auto* h = fopen(dat_file, "r+b"); + if (h == nullptr) { fprintf(stderr, "Error: could not open data file.\n"); return false; } diff --git a/tsne/bh_sne_src/sptree.h b/tsne/bh_sne_src/sptree.h index ee579fc..53fe77a 100644 --- a/tsne/bh_sne_src/sptree.h +++ b/tsne/bh_sne_src/sptree.h @@ -48,6 +48,10 @@ class alignas(16) Cell { public: Cell() = default; Cell(const double* inp_corner, const double* inp_width); + Cell(const Cell&) = delete; + Cell(Cell&&) noexcept = default; + Cell& operator=(const Cell&) = delete; + Cell& operator=(Cell&&) noexcept = default; ~Cell() = default; [[nodiscard]] double getCorner(unsigned int d) const; diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 5f29ca4..04eae4c 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -442,7 +442,7 @@ void computeGaussianPerplexity(const double* const X, } // Build ball tree on data set - VpTree tree((euclidean_distance(D, X))); + VpTree tree(euclidean_distance(D, X)); vector obj_X(N); for (int n = 0; n < N; n++) { obj_X[n] = n; diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index f0bc356..5de5234 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -36,12 +36,15 @@ #if defined _WIN32 || defined __CYGWIN__ #ifdef __GNUC__ +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DLL_PUBLIC __attribute__((dllexport)) #else +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DLL_PUBLIC __declspec(dllexport) #endif #else #if __GNUC__ >= 4 +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DLL_PUBLIC __attribute__((visibility("default"))) #else #define DLL_PUBLIC diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index d4aff3c..1f7be21 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -54,8 +54,11 @@ class euclidean_distance final { public: explicit euclidean_distance(int D, const double* data) : D(D), data(data) { } - euclidean_distance(euclidean_distance&&) = default; euclidean_distance(const euclidean_distance&) = default; + euclidean_distance(euclidean_distance&&) = delete; + euclidean_distance& operator=(const euclidean_distance&) = delete; + euclidean_distance& operator=(euclidean_distance&&) = delete; + ~euclidean_distance() = default; double operator()(const unsigned int t1, const unsigned int t2) const { double dd = .0; const double* x1 = data + t1 * D; @@ -75,7 +78,9 @@ class VpTree { explicit VpTree(Distance&& distance) : distance(distance){}; VpTree(const VpTree&) = delete; + VpTree(VpTree&&) noexcept = default; VpTree& operator=(const VpTree&) = delete; + VpTree& operator=(VpTree&&) noexcept = default; // Destructor ~VpTree() = default; From 101f7b11c215d01003d3c88c1808fabfb17ff55c Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:21:34 -0700 Subject: [PATCH 16/24] clang-tidy readability-isolate-declaration --- tsne/bh_sne_src/main.cpp | 9 +++++++-- tsne/bh_sne_src/tsne.cpp | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tsne/bh_sne_src/main.cpp b/tsne/bh_sne_src/main.cpp index 4a3d7bf..552584f 100644 --- a/tsne/bh_sne_src/main.cpp +++ b/tsne/bh_sne_src/main.cpp @@ -130,8 +130,13 @@ int main(int argc, char* argv[]) { const char* res_file_c = res_file.c_str(); // Define some variables - int origN, N, D, no_dims, max_iter; - double perplexity, theta; + int origN; + int N; + int D; + int no_dims; + int max_iter; + double perplexity; + double theta; vector data; int rand_seed = -1; diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 04eae4c..595860a 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -316,8 +316,10 @@ double evaluateError(const vector& row_P, } // Loop over all edges to compute t-SNE error - int ind1, ind2; - double C = .0, Q; + int ind1; + int ind2; + double C = .0; + double Q; for (int n = 0; n < N; n++) { ind1 = n * NDIMS; for (unsigned int i = row_P[n]; i < row_P[n + 1]; i++) { @@ -702,7 +704,9 @@ void zeroMean(double* const X, const int N, const int D) { // Generates a Gaussian random number double randn() { - double x, y, radius; + double x; + double y; + double radius; do { x = 2 * (rand() / (static_cast(RAND_MAX) + 1)) - 1; y = 2 * (rand() / (static_cast(RAND_MAX) + 1)) - 1; @@ -757,7 +761,8 @@ unique_ptr make_tsne_impl(double* const X, stop_lying_iter, mom_switch_iter); - clock_t start, end; + clock_t start; + clock_t end; // Allocate some memory vector dY(N * NDIMS); @@ -894,12 +899,14 @@ unique_ptr make_tsne_impl(double* const X, template bool TSNEState::step_by_impl(const int step) { // Set learning parameters - double momentum = .5, final_momentum = .8; + double momentum = .5; + double final_momentum = .8; double eta = 200.0; // Extract state bool exact = (theta == .0) ? true : false; - clock_t start = clock(), end; + clock_t start = clock(); + clock_t end; float elapsed; int iter_until = std::min(iter + step, max_iter); From af80d6a1c46c8303a594e6cc6db5891aa073be67 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:24:00 -0700 Subject: [PATCH 17/24] clang-tidy readability-simplify-boolean-expr --- tsne/bh_sne_src/tsne.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 595860a..85d3c8f 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -733,7 +733,7 @@ unique_ptr make_tsne_impl(double* const X, const int stop_lying_iter, const int mom_switch_iter) { // Set random seed - if (skip_random_init != true) { + if (!skip_random_init) { if (rand_seed >= 0) { fprintf(stderr, "Using random seed: %d\n", rand_seed); srand(static_cast(rand_seed)); @@ -753,7 +753,7 @@ unique_ptr make_tsne_impl(double* const X, NDIMS, perplexity, theta); - bool exact = (theta == .0) ? true : false; + bool exact = theta == .0; fprintf(stderr, "Using max_iter = %d, stop_lying_iter = %d, mom_switch_iter = %d\n", @@ -904,7 +904,7 @@ bool TSNEState::step_by_impl(const int step) { double eta = 200.0; // Extract state - bool exact = (theta == .0) ? true : false; + bool exact = theta == .0; clock_t start = clock(); clock_t end; float elapsed; From 8910a41fee933e2bd6db31614f3b7fe39fecc878 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:25:25 -0700 Subject: [PATCH 18/24] clang-tidy readability-static-definition-in-anonymous-namespace --- tsne/bh_sne_src/tsne.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 85d3c8f..f4f6f86 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -183,7 +183,7 @@ void symmetrizeMatrix(vector* row_P, vector* val_P, int N); -static inline double sign(const double x) { +inline double sign(const double x) { return (x == .0 ? .0 : (x < .0 ? -1.0 : 1.0)); } From f44c5a42f9e5a9eb0ba82dc04e2cfefe222b7919 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:25:57 -0700 Subject: [PATCH 19/24] clang-tidy readability-inconsistent-declaration-parameter-name --- tsne/bh_sne_src/tsne.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index f4f6f86..22651e7 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -100,11 +100,11 @@ struct TSNEState { } public: - bool step_by(int n); + bool step_by(int step); private: template - bool step_by_impl(int n); + bool step_by_impl(int step); private: const int N; From b6b4f53f161c6e2cee1844fa113d5309c16e0903 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:26:55 -0700 Subject: [PATCH 20/24] clang-tidy readability-redundant-access-specifiers --- tsne/bh_sne_src/tsne.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 22651e7..345e77d 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -99,14 +99,12 @@ struct TSNEState { gains{gains} { } - public: bool step_by(int step); private: template bool step_by_impl(int step); - private: const int N; double* const Y; const int no_dims; From 89a3dc808a1a25c14a8a3e7635bf61ca956b0906 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:28:19 -0700 Subject: [PATCH 21/24] clang-tidy readability-implicit-bool-conversion --- tsne/bh_sne_src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsne/bh_sne_src/main.cpp b/tsne/bh_sne_src/main.cpp index 552584f..539b65f 100644 --- a/tsne/bh_sne_src/main.cpp +++ b/tsne/bh_sne_src/main.cpp @@ -67,7 +67,7 @@ bool load_data(const char* dat_file, fread(max_iter, sizeof(int), 1, h); // maximum number of iterations data->resize(*d * *n); fread(data->data(), sizeof(double), *n * *d, h); // the data - if (!feof(h)) { + if (feof(h) == 0) { fread(rand_seed, sizeof(int), 1, h); // random seed } fclose(h); From 91ec292a0fa1984966a750b6e696ff71259c5dab Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Wed, 16 Jun 2021 20:30:52 -0700 Subject: [PATCH 22/24] clang-tidy readability-non-const-parameter --- tsne/bh_sne_src/tsne.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 345e77d..3c4eee6 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -190,7 +190,7 @@ template void computeGradient(const vector& inp_row_P, const vector& inp_col_P, const vector& inp_val_P, - double* const Y, + const double* const Y, const int N, vector* const _dC, const double theta) { From 70bb52601949f50ad0eb57759c6c441c050804dd Mon Sep 17 00:00:00 2001 From: Lance Hepler Date: Thu, 19 Aug 2021 16:59:28 -0700 Subject: [PATCH 23/24] hide GCC pragmas behind ifdef __GNUC__ and visibility fixes for visual studio --- setup.py | 2 ++ tsne/bh_sne_src/sptree.cpp | 4 ++++ tsne/bh_sne_src/sptree.h | 5 +++++ tsne/bh_sne_src/tsne.cpp | 12 ++++++++---- tsne/bh_sne_src/tsne.h | 24 ++++++++++++++++-------- tsne/bh_sne_src/vptree.h | 5 +++++ 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 709dbb0..2b24167 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ 'tsne/bh_sne_src/tsne.cpp', 'tsne/bh_sne.pyx'], include_dirs=[ numpy.get_include(), 'tsne/bh_sne_src/'], + define_macros=[('BUILDING_TSNE_DLL', None)], extra_compile_args=extra_compile_args + ['-ffast-math', '-O3', '-std=c++14'], extra_link_args=['-Wl,-framework', @@ -63,6 +64,7 @@ 'tsne/bh_sne_src/tsne.cpp', 'tsne/bh_sne.pyx'], include_dirs=[ numpy.get_include(), 'tsne/bh_sne_src/'], + define_macros=[('BUILDING_TSNE_DLL', None)], extra_compile_args=opt_flags + ['-Wall', '-fPIC', '-std=c++14', '-w'], extra_link_args=ldflags, diff --git a/tsne/bh_sne_src/sptree.cpp b/tsne/bh_sne_src/sptree.cpp index a3f61e2..a94d359 100644 --- a/tsne/bh_sne_src/sptree.cpp +++ b/tsne/bh_sne_src/sptree.cpp @@ -39,7 +39,9 @@ #include #include +#ifdef __GNUC__ #pragma GCC visibility push(hidden) +#endif using std::array; using std::make_unique; @@ -428,4 +430,6 @@ void SPTree::Node::print(const double* data) const { template class SPTree<2>; template class SPTree<3>; +#ifdef __GNUC__ #pragma GCC visibility pop +#endif diff --git a/tsne/bh_sne_src/sptree.h b/tsne/bh_sne_src/sptree.h index 53fe77a..0382aaa 100644 --- a/tsne/bh_sne_src/sptree.h +++ b/tsne/bh_sne_src/sptree.h @@ -38,7 +38,9 @@ #include #include +#ifdef __GNUC__ #pragma GCC visibility push(hidden) +#endif template class alignas(16) Cell { @@ -136,5 +138,8 @@ struct SPTree<0> { enum { no_children = 1 }; }; +#ifdef __GNUC__ #pragma GCC visibility pop #endif + +#endif // !defined(SPTREE_H) diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 3c4eee6..990ae73 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -47,7 +47,9 @@ #include "sptree.h" #include "vptree.h" +#ifdef __GNUC__ #pragma GCC visibility push(hidden) +#endif using std::abs; using std::array; @@ -1054,7 +1056,7 @@ bool TSNEState::step_by(int step) { } // namespace extern "C" { -struct TSNE* DLL_PUBLIC init_tsne(double* const X, +DLL_PUBLIC struct TSNE* init_tsne(double* const X, const int N, const int D, double* const Y, @@ -1086,18 +1088,18 @@ struct TSNE* DLL_PUBLIC init_tsne(double* const X, return reinterpret_cast(tsne.release()); } -bool DLL_PUBLIC step_tsne_by(struct TSNE* const tsne, const int step) { +DLL_PUBLIC bool step_tsne_by(struct TSNE* const tsne, const int step) { return reinterpret_cast(tsne)->step_by(step); } -void DLL_PUBLIC free_tsne(struct TSNE* const tsne) { +DLL_PUBLIC void free_tsne(struct TSNE* const tsne) { if (tsne == nullptr) { return; } delete reinterpret_cast(tsne); } -void DLL_PUBLIC run(double* const X, +DLL_PUBLIC void run(double* const X, const int N, const int D, double* const Y, @@ -1130,4 +1132,6 @@ void DLL_PUBLIC run(double* const X, } // extern "C" +#ifdef __GNUC__ #pragma GCC visibility pop +#endif diff --git a/tsne/bh_sne_src/tsne.h b/tsne/bh_sne_src/tsne.h index 5de5234..8ed5462 100644 --- a/tsne/bh_sne_src/tsne.h +++ b/tsne/bh_sne_src/tsne.h @@ -35,25 +35,33 @@ #define TSNE_H #if defined _WIN32 || defined __CYGWIN__ +#ifdef BUILDING_TSNE_DLL #ifdef __GNUC__ // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DLL_PUBLIC __attribute__((dllexport)) #else // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DLL_PUBLIC __declspec(dllexport) -#endif +#endif // __GNUC__ +#else +#ifdef __GNUC__ +#define DLL_PUBLIC __attribute__ ((dllimport)) +#else +#define DLL_PUBLIC __declspec(dllimport) +#endif // __GNUC__ +#endif // BUILDING_TSNE_DLL #else #if __GNUC__ >= 4 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DLL_PUBLIC __attribute__((visibility("default"))) #else #define DLL_PUBLIC -#endif -#endif +#endif // __GNUC__ +#endif // defined _WIN32 || defined __CYGWIN__ extern "C" { // stateless t-SNE -void DLL_PUBLIC run(double* X, +DLL_PUBLIC void run(double* X, int N, int D, double* Y, @@ -69,7 +77,7 @@ void DLL_PUBLIC run(double* X, int mom_switch_iter = 250); // stateful t-SNE struct TSNE; -struct TSNE* DLL_PUBLIC init_tsne(double* X, +DLL_PUBLIC struct TSNE* init_tsne(double* X, int N, int D, double* Y, @@ -83,8 +91,8 @@ struct TSNE* DLL_PUBLIC init_tsne(double* X, int max_iter = 1000, int stop_lying_iter = 250, int mom_switch_iter = 250); -bool DLL_PUBLIC step_tsne_by(struct TSNE* tsne, int step); -void DLL_PUBLIC free_tsne(struct TSNE* tsne); +DLL_PUBLIC bool step_tsne_by(struct TSNE* tsne, int step); +DLL_PUBLIC void free_tsne(struct TSNE* tsne); } -#endif +#endif // !defined(TSNE_H) diff --git a/tsne/bh_sne_src/vptree.h b/tsne/bh_sne_src/vptree.h index 1f7be21..ec50da0 100644 --- a/tsne/bh_sne_src/vptree.h +++ b/tsne/bh_sne_src/vptree.h @@ -45,7 +45,9 @@ #include #include +#ifdef __GNUC__ #pragma GCC visibility push(hidden) +#endif class euclidean_distance final { const int D; @@ -258,5 +260,8 @@ class VpTree { } }; +#ifdef __GNUC__ #pragma GCC visibility pop +#endif + #endif // !defined(VPTREE_H) From 86beed07ec93f5f0de03650114ac0375f13fcea1 Mon Sep 17 00:00:00 2001 From: Adam Azarchs Date: Thu, 8 Dec 2022 23:01:14 -0800 Subject: [PATCH 24/24] Run clang-tidy --fix with clang15. Also update makefile to use c++17, as that's what we're using on the rust side. --- tsne/bh_sne_src/Makefile | 2 +- tsne/bh_sne_src/tsne.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tsne/bh_sne_src/Makefile b/tsne/bh_sne_src/Makefile index 95534dc..82658e4 100644 --- a/tsne/bh_sne_src/Makefile +++ b/tsne/bh_sne_src/Makefile @@ -3,7 +3,7 @@ #CFLAGS = -march=haswell -ffast-math -O3 CXX?=g++ -CFLAGS?=-ffast-math -O3 -Wall -std=c++14 +CFLAGS?=-ffast-math -O3 -Wall -std=c++17 all: bh_tsne diff --git a/tsne/bh_sne_src/tsne.cpp b/tsne/bh_sne_src/tsne.cpp index 990ae73..875a1f2 100644 --- a/tsne/bh_sne_src/tsne.cpp +++ b/tsne/bh_sne_src/tsne.cpp @@ -621,9 +621,9 @@ void symmetrizeMatrix(vector* _row_P, } // Return symmetrized matrices - *_row_P = move(sym_row_P); - *_col_P = move(sym_col_P); - *_val_P = move(sym_val_P); + *_row_P = std::move(sym_row_P); + *_col_P = std::move(sym_col_P); + *_val_P = std::move(sym_val_P); } // Compute squared Euclidean distance matrix @@ -886,13 +886,13 @@ unique_ptr make_tsne_impl(double* const X, 0, .0, .0, - move(P), - move(row_P), - move(col_P), - move(val_P), - move(dY), - move(uY), - move(gains)); + std::move(P), + std::move(row_P), + std::move(col_P), + std::move(val_P), + std::move(dY), + std::move(uY), + std::move(gains)); } // Optimize t-SNE