08b5854c56
Add batch evaluation/gradient functions for SDF trees (sdf_torch.h/cpp). Add sdf_gradient.h with finite-difference gradient utilities. Add Python modules vde.sdf (high-level SDF API) and vde.torch_sdf (PyTorch autograd). Add test_sdf_torch_bridge.cpp with batch eval/gradient tests. Integrate into vde_sdf library and test suite.
169 lines
6.3 KiB
C++
169 lines
6.3 KiB
C++
#include <gtest/gtest.h>
|
|
#include "vde/sdf/sdf_torch.h"
|
|
#include "vde/sdf/sdf_gradient.h"
|
|
#include "vde/sdf/sdf_tree.h"
|
|
#include "vde/sdf/sdf_primitives.h"
|
|
#include "vde/core/point.h"
|
|
#include <random>
|
|
#include <cmath>
|
|
#include <vector>
|
|
|
|
using namespace vde::sdf;
|
|
using vde::core::Point3D;
|
|
|
|
constexpr double EPS = 1e-5;
|
|
|
|
// ═══════════════════════════════════════════════════
|
|
// Helper: generate random points
|
|
// ═══════════════════════════════════════════════════
|
|
|
|
std::vector<double> random_points(int n) {
|
|
std::mt19937 rng(42);
|
|
std::uniform_real_distribution<double> dist(-3.0, 3.0);
|
|
std::vector<double> pts(3 * n);
|
|
for (int i = 0; i < 3 * n; ++i) {
|
|
pts[i] = dist(rng);
|
|
}
|
|
return pts;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════
|
|
// evaluate_batch
|
|
// ═══════════════════════════════════════════════════
|
|
|
|
TEST(SdfTorchBridge, EvaluateBatch_MatchesSequential) {
|
|
auto root = SdfNode::sphere(2.0);
|
|
const int n = 100;
|
|
auto pts_arr = random_points(n);
|
|
|
|
std::vector<double> batch_dist(n);
|
|
evaluate_batch(root, pts_arr.data(), n, batch_dist.data());
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
Point3D p(pts_arr[i*3], pts_arr[i*3+1], pts_arr[i*3+2]);
|
|
double expected = evaluate(root, p);
|
|
EXPECT_NEAR(batch_dist[i], expected, EPS);
|
|
}
|
|
}
|
|
|
|
TEST(SdfTorchBridge, EvaluateBatch_CSGTree) {
|
|
auto a = SdfNode::sphere(1.5);
|
|
auto b = SdfNode::box(Point3D(0.8, 0.8, 0.8));
|
|
auto root = SdfNode::op_union(a, b);
|
|
const int n = 50;
|
|
auto pts_arr = random_points(n);
|
|
|
|
std::vector<double> batch_dist(n);
|
|
evaluate_batch(root, pts_arr.data(), n, batch_dist.data());
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
Point3D p(pts_arr[i*3], pts_arr[i*3+1], pts_arr[i*3+2]);
|
|
double expected = evaluate(root, p);
|
|
EXPECT_NEAR(batch_dist[i], expected, EPS);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════
|
|
// gradient_batch
|
|
// ═══════════════════════════════════════════════════
|
|
|
|
TEST(SdfTorchBridge, GradientBatch_MatchesSequential) {
|
|
auto root = SdfNode::sphere(2.0);
|
|
const int n = 100;
|
|
auto pts_arr = random_points(n);
|
|
|
|
std::vector<double> batch_grad(3 * n);
|
|
gradient_batch(root, pts_arr.data(), n, batch_grad.data());
|
|
|
|
auto fn = [&](const Point3D& p) { return evaluate(root, p); };
|
|
double h = 1e-6;
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
Point3D p(pts_arr[i*3], pts_arr[i*3+1], pts_arr[i*3+2]);
|
|
|
|
// Sequential gradient via central FD
|
|
double dfdx = (fn(Point3D(p.x()+h, p.y(), p.z())) -
|
|
fn(Point3D(p.x()-h, p.y(), p.z()))) / (2.0*h);
|
|
double dfdy = (fn(Point3D(p.x(), p.y()+h, p.z())) -
|
|
fn(Point3D(p.x(), p.y()-h, p.z()))) / (2.0*h);
|
|
double dfdz = (fn(Point3D(p.x(), p.y(), p.z()+h)) -
|
|
fn(Point3D(p.x(), p.y(), p.z()-h))) / (2.0*h);
|
|
|
|
EXPECT_NEAR(batch_grad[i*3], dfdx, 1e-4);
|
|
EXPECT_NEAR(batch_grad[i*3+1], dfdy, 1e-4);
|
|
EXPECT_NEAR(batch_grad[i*3+2], dfdz, 1e-4);
|
|
}
|
|
}
|
|
|
|
TEST(SdfTorchBridge, GradientBatch_Box) {
|
|
auto root = SdfNode::box(Point3D(2.0, 1.0, 3.0));
|
|
const int n = 50;
|
|
auto pts_arr = random_points(n);
|
|
|
|
std::vector<double> batch_grad(3 * n);
|
|
gradient_batch(root, pts_arr.data(), n, batch_grad.data());
|
|
|
|
// Check that gradient at origin (inside box) is zero or close
|
|
Point3D origin(0, 0, 0);
|
|
Vector3D g = gradient([&](const Point3D& p) { return evaluate(root, p); }, origin);
|
|
// Gradient near origin should point outward (or be near-internal max)
|
|
(void)g; // just verifying no crash
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
// All gradients should be finite
|
|
EXPECT_TRUE(std::isfinite(batch_grad[i*3]));
|
|
EXPECT_TRUE(std::isfinite(batch_grad[i*3+1]));
|
|
EXPECT_TRUE(std::isfinite(batch_grad[i*3+2]));
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════
|
|
// evaluate_with_gradient_batch
|
|
// ═══════════════════════════════════════════════════
|
|
|
|
TEST(SdfTorchBridge, EvaluateWithGradientBatch_BothCorrect) {
|
|
auto root = SdfNode::sphere(1.5);
|
|
const int n = 50;
|
|
auto pts_arr = random_points(n);
|
|
|
|
std::vector<double> batch_dist(n);
|
|
std::vector<double> batch_grad(3 * n);
|
|
evaluate_with_gradient_batch(root, pts_arr.data(), n,
|
|
batch_dist.data(), batch_grad.data());
|
|
|
|
// Compare distances against sequential
|
|
std::vector<double> seq_dist(n);
|
|
evaluate_batch(root, pts_arr.data(), n, seq_dist.data());
|
|
for (int i = 0; i < n; ++i) {
|
|
EXPECT_NEAR(batch_dist[i], seq_dist[i], EPS);
|
|
}
|
|
|
|
// Compare gradients against sequential
|
|
std::vector<double> seq_grad(3 * n);
|
|
gradient_batch(root, pts_arr.data(), n, seq_grad.data());
|
|
for (int i = 0; i < 3 * n; ++i) {
|
|
EXPECT_NEAR(batch_grad[i], seq_grad[i], 1e-10);
|
|
}
|
|
}
|
|
|
|
TEST(SdfTorchBridge, EvaluateWithGradientBatch_CSGTree) {
|
|
// Smooth union of sphere and box
|
|
auto sphere = SdfNode::sphere(1.0);
|
|
auto box_node = SdfNode::box(Point3D(0.8, 0.8, 0.8));
|
|
auto root = SdfNode::smooth_union(sphere, box_node, 0.2);
|
|
const int n = 30;
|
|
auto pts_arr = random_points(n);
|
|
|
|
std::vector<double> batch_dist(n);
|
|
std::vector<double> batch_grad(3 * n);
|
|
evaluate_with_gradient_batch(root, pts_arr.data(), n,
|
|
batch_dist.data(), batch_grad.data());
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
EXPECT_TRUE(std::isfinite(batch_dist[i]));
|
|
EXPECT_TRUE(std::isfinite(batch_grad[i*3]));
|
|
EXPECT_TRUE(std::isfinite(batch_grad[i*3+1]));
|
|
EXPECT_TRUE(std::isfinite(batch_grad[i*3+2]));
|
|
}
|
|
}
|