C++#
The SphericalHarmonics
class automatically initializes and internally stores pre-factors and
buffers, but the compute
, compute_with_gradients
and compute_with_hessians
functions
require the user to provide std::vector
s to store the results. Since these std::vector
s
are only resized if their shape is not appropriate, this allows the user to reutilize the
same memory across multiple calls, thereby avoiding unnecessary memory allocations. This is
illustrated in the example below. See the C++ API for alternative calls
using bare arrays or individual samples.
/** @file example.cpp
* @brief Usage example for the C++ API
*/
#include <cmath>
#include <cstdio>
#include "sphericart.hpp"
int main() {
/* ===== set up the calculation ===== */
// hard-coded parameters for the example
size_t n_samples = 10000;
size_t l_max = 10;
// initializes samples
auto xyz = std::vector<double>(n_samples * 3, 0.0);
for (size_t i = 0; i < n_samples * 3; ++i) {
xyz[i] = (double)rand() / (double)RAND_MAX * 2.0 - 1.0;
}
// to avoid unnecessary allocations, calculators can use pre-allocated
// memory, however one can provide uninitialized vectors that will be
// automatically reshaped
auto sph = std::vector<double>(n_samples * (l_max + 1) * (l_max + 1), 0.0);
auto dsph = std::vector<double>(n_samples * 3 * (l_max + 1) * (l_max + 1), 0.0);
auto ddsph = std::vector<double>(n_samples * 3 * 3 * (l_max + 1) * (l_max + 1), 0.0);
// the class is templated, so one can also use 32-bit float operations
auto xyz_f = std::vector<float>(n_samples * 3, 0.0);
for (size_t i = 0; i < n_samples * 3; ++i) {
xyz_f[i] = (float)xyz[i];
}
auto sph_f = std::vector<float>(n_samples * (l_max + 1) * (l_max + 1), 0.0);
auto dsph_f = std::vector<float>(n_samples * 3 * (l_max + 1) * (l_max + 1), 0.0);
auto ddsph_f = std::vector<float>(n_samples * 3 * 3 * (l_max + 1) * (l_max + 1), 0.0);
// the class can be used to compute the for a full arrays of points (as
// above) or on individual samples - this is deduced from the size of the
// array
auto xyz_sample = std::vector<double>(3, 0.0);
auto sph_sample = std::vector<double>((l_max + 1) * (l_max + 1), 0.0);
auto dsph_sample = std::vector<double>(3 * (l_max + 1) * (l_max + 1), 0.0);
auto ddsph_sample = std::vector<double>(3 * 3 * (l_max + 1) * (l_max + 1), 0.0);
/* ===== API calls ===== */
// internal buffers and numerical factors are initalized at construction
auto calculator = sphericart::SphericalHarmonics<double>(l_max);
calculator.compute(xyz, sph); // no gradients
calculator.compute_with_gradients(xyz, sph, dsph); // gradients
calculator.compute_with_hessians(xyz, sph, dsph,
ddsph); // gradients and hessians
// the single-sample evaluation provides direct access to the main
// calculator, avoiding the loop over samples and allowing e.g. custom
// parallelization
calculator.compute(xyz_sample, sph_sample); // no gradients
calculator.compute_with_gradients(xyz_sample, sph_sample,
dsph_sample); // gradients
calculator.compute_with_hessians(
xyz_sample,
sph_sample,
dsph_sample,
ddsph_sample
); // gradients and hessians
// float version
auto calculator_f = sphericart::SphericalHarmonics<float>(l_max);
calculator_f.compute(xyz_f, sph_f); // no gradients
calculator_f.compute_with_gradients(xyz_f, sph_f, dsph_f); // gradients
calculator_f.compute_with_hessians(xyz_f, sph_f, dsph_f,
ddsph_f); // gradients and hessians
/* ===== check results ===== */
double sph_error = 0.0, sph_norm = 0.0;
for (size_t i = 0; i < n_samples * (l_max + 1) * (l_max + 1); ++i) {
sph_error += (sph_f[i] - sph[i]) * (sph_f[i] - sph[i]);
sph_norm += sph[i] * sph[i];
}
printf("Float vs double relative error: %12.8e\n", sqrt(sph_error / sph_norm));
return 0;
}