24std::vector<double> fd_slopes(
const std::vector<double>& x,
const std::vector<double>& y) {
25 const std::size_t n = x.size();
26 std::vector<double> m(n, 0.0);
30 m[0] = (y[1] - y[0]) / (x[1] - x[0]);
31 m[n - 1] = (y[n - 1] - y[n - 2]) / (x[n - 1] - x[n - 2]);
32 for (std::size_t i = 1; i + 1 < n; ++i) {
33 m[i] = (y[i + 1] - y[i - 1]) / (x[i + 1] - x[i - 1]);
41std::vector<double> spline_slopes(
const std::vector<double>& x,
const std::vector<double>& y) {
43 const std::size_t n = x.size();
44 std::vector<double> m(n, 0.0);
45 for (std::size_t i = 0; i < n; ++i) {
46 m[i] = spline->eval_da(x[i]);
58std::vector<double> pchip_slopes(
const std::vector<double>& x,
const std::vector<double>& y) {
59 const std::size_t n = x.size();
60 std::vector<double> m(n, 0.0);
65 const double slope = (y[1] - y[0]) / (x[1] - x[0]);
70 std::vector<double> h(n - 1, 0.0);
71 std::vector<double> delta(n - 1, 0.0);
72 for (std::size_t i = 0; i + 1 < n; ++i) {
73 h[i] = x[i + 1] - x[i];
74 delta[i] = (y[i + 1] - y[i]) / h[i];
77 for (std::size_t i = 1; i + 1 < n; ++i) {
78 if (delta[i - 1] * delta[i] <= 0.0) {
81 const double w1 = 2.0 * h[i] + h[i - 1];
82 const double w2 = h[i] + 2.0 * h[i - 1];
83 m[i] = (w1 + w2) / (w1 / delta[i - 1] + w2 / delta[i]);
88 m[0] = ((2.0 * h[0] + h[1]) * delta[0] - h[0] * delta[1]) / (h[0] + h[1]);
89 m[n - 1] = ((2.0 * h[n - 2] + h[n - 3]) * delta[n - 2] - h[n - 2] * delta[n - 3]) / (h[n - 2] + h[n - 3]);
91 if (m[0] * delta[0] <= 0.0) {
93 }
else if (std::abs(m[0]) > 3.0 * std::abs(delta[0])) {
94 m[0] = 3.0 * delta[0];
96 if (m[n - 1] * delta[n - 2] <= 0.0) {
98 }
else if (std::abs(m[n - 1]) > 3.0 * std::abs(delta[n - 2])) {
99 m[n - 1] = 3.0 * delta[n - 2];
104std::vector<double> compute_slopes(
const std::vector<double>& x,
const std::vector<double>& y,
SlopeSource src) {
107 return fd_slopes(x, y);
109 return spline_slopes(x, y);
111 return pchip_slopes(x, y);
113 return fd_slopes(x, y);
120 const std::size_t NX = x_grid.size();
121 const std::size_t NY = y_grid.size();
122 if (NX < 2 || NY < 2) {
123 throw std::invalid_argument(
"build_svd: x_grid and y_grid must each have >= 2 entries");
125 if (M.size() != NX * NY) {
126 throw std::invalid_argument(
"build_svd: M must be NX * NY entries in row-major order");
128 for (std::size_t i = 1; i < NX; ++i) {
129 if (!(x_grid[i] > x_grid[i - 1])) {
130 throw std::invalid_argument(
"build_svd: x_grid must be strictly increasing");
133 for (std::size_t j = 1; j < NY; ++j) {
134 if (!(y_grid[j] > y_grid[j - 1])) {
135 throw std::invalid_argument(
"build_svd: y_grid must be strictly increasing");
138 const std::size_t full_rank = std::min(NX, NY);
139 if (opts.
rank <= 0 ||
static_cast<std::size_t
>(opts.
rank) > full_rank) {
140 throw std::invalid_argument(
"build_svd: rank must be in (0, min(NX, NY)]");
142 for (
const double v : M) {
143 if (!std::isfinite(v)) {
144 throw std::invalid_argument(
"build_svd: M contains a non-finite entry");
148 const auto r =
static_cast<std::size_t
>(opts.
rank);
151 using RowMajorMat = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
152 Eigen::Map<const RowMajorMat> M_map(M.data(),
static_cast<Eigen::Index
>(NX),
static_cast<Eigen::Index
>(NY));
158#if defined(EIGEN_WORLD_VERSION) && (EIGEN_WORLD_VERSION > 3 || (EIGEN_WORLD_VERSION == 3 && EIGEN_MAJOR_VERSION >= 5))
159 Eigen::BDCSVD<Eigen::MatrixXd, Eigen::ComputeThinU | Eigen::ComputeThinV> svd(M_map);
161 Eigen::BDCSVD<Eigen::MatrixXd> svd(M_map, Eigen::ComputeThinU | Eigen::ComputeThinV);
163 if (svd.info() != Eigen::Success) {
164 throw std::runtime_error(
"build_svd: BDCSVD failed to converge");
167 const Eigen::MatrixXd& U_full = svd.matrixU();
168 const Eigen::MatrixXd& V_full = svd.matrixV();
169 const Eigen::VectorXd& s_full = svd.singularValues();
172 d.
NX =
static_cast<std::int32_t
>(NX);
173 d.
NY =
static_cast<std::int32_t
>(NY);
180 for (std::size_t k = 0; k < r; ++k) {
181 d.
S[k] = s_full(
static_cast<Eigen::Index
>(k));
184 d.
U.assign(NX * r, 0.0);
185 for (std::size_t i = 0; i < NX; ++i) {
186 for (std::size_t k = 0; k < r; ++k) {
187 d.
U[i * r + k] = U_full(
static_cast<Eigen::Index
>(i),
static_cast<Eigen::Index
>(k));
191 d.
V_S.assign(NY * r, 0.0);
192 for (std::size_t j = 0; j < NY; ++j) {
193 for (std::size_t k = 0; k < r; ++k) {
194 d.
V_S[j * r + k] = V_full(
static_cast<Eigen::Index
>(j),
static_cast<Eigen::Index
>(k)) * d.
S[k];
199 d.
dU_dx.assign(NX * r, 0.0);
201 std::vector<double> u_col(NX);
202 std::vector<double> v_col(NY);
203 for (std::size_t k = 0; k < r; ++k) {
204 for (std::size_t i = 0; i < NX; ++i) {
205 u_col[i] = d.
U[i * r + k];
207 const std::vector<double> u_slopes = compute_slopes(x_grid, u_col, opts.
slope_source);
208 for (std::size_t i = 0; i < NX; ++i) {
209 d.
dU_dx[i * r + k] = u_slopes[i];
211 for (std::size_t j = 0; j < NY; ++j) {
212 v_col[j] = d.
V_S[j * r + k];
214 const std::vector<double> v_slopes = compute_slopes(y_grid, v_col, opts.
slope_source);
215 for (std::size_t j = 0; j < NY; ++j) {
216 d.
dV_S_dy[j * r + k] = v_slopes[j];