69888621cd
v9.1 — Distributed Computing (超越 Parasolid): - cluster_engine: ClusterManager, TaskScheduler(DAG+Kahn), 4 load-balance strategies - distributed_boolean, distributed_marching_cubes, distributed_ray_tracing - grpc_service: BrepOps/MeshOps/SdfOps RPC, streaming, TLS, connection pool - ~750 lines v9.2 — Cloud-Native + KBE + WASM + Digital Twin (34/34 tests passing): - cloud_native: CloudSession, OperationalTransform, DeltaSync, Serverless, ObjectStorage - knowledge_engine: CheckMate(13 rules), RuleEngine, DesignTable, GA+Adam optimizer - vde_wasm: WasmBridge, WebWorkerPool, SharedArrayBuffer, IndexedDB - dt_engine: DigitalTwin, MQTT/OPC-UA, RealTimeSync, PredictiveMaintenance(RUL) - 3950 lines, 34 tests all passing Pending: AI/ML integration (retrying) 18 files, ~4700 lines
235 lines
7.6 KiB
C++
235 lines
7.6 KiB
C++
#include <gtest/gtest.h>
|
|
#include "vde/ai/feature_learning.h"
|
|
|
|
using namespace vde::ai;
|
|
using namespace vde::brep;
|
|
using namespace vde::core;
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// FeatureClassifier 测试 (4 项)
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(FeatureClassifierTest, DefaultConstruction) {
|
|
FeatureClassifier classifier;
|
|
EXPECT_FALSE(classifier.is_model_loaded());
|
|
EXPECT_TRUE(classifier.model_version().empty());
|
|
}
|
|
|
|
TEST(FeatureClassifierTest, LoadModelSucceeds) {
|
|
FeatureClassifier classifier;
|
|
EXPECT_TRUE(classifier.load_model("models/feature_gnn.onnx"));
|
|
EXPECT_TRUE(classifier.is_model_loaded());
|
|
EXPECT_FALSE(classifier.model_version().empty());
|
|
}
|
|
|
|
TEST(FeatureClassifierTest, ClassifyEmptyBody) {
|
|
FeatureClassifier classifier;
|
|
classifier.load_model("models/dummy.onnx");
|
|
|
|
BrepModel empty_body;
|
|
auto result = classifier.classify_features(empty_body);
|
|
|
|
EXPECT_TRUE(result.success);
|
|
EXPECT_TRUE(result.features.empty());
|
|
EXPECT_EQ(result.hole_count, 0);
|
|
}
|
|
|
|
TEST(FeatureClassifierTest, BuildFaceGraphOnBox) {
|
|
FeatureClassifier classifier;
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
auto graph = classifier.build_face_graph(box);
|
|
|
|
// 一个盒子有 6 个面(具体取决于 make_box 实现)
|
|
EXPECT_GT(graph.nodes.size(), 0u);
|
|
// 面之间应有邻接关系
|
|
EXPECT_GT(graph.edges.size(), 0u);
|
|
EXPECT_EQ(graph.edges.size(), graph.edge_features.size());
|
|
}
|
|
|
|
TEST(FeatureClassifierTest, EncodeFaceNodeReturnsCorrectDim) {
|
|
FeatureClassifier classifier;
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
// 对每个面编码
|
|
const auto nf = box.num_faces();
|
|
if (nf > 0) {
|
|
auto feats = classifier.encode_face_node(box, 0);
|
|
EXPECT_EQ(feats.size(), 8u); // kNodeFeatureDim = 8
|
|
}
|
|
}
|
|
|
|
TEST(FeatureClassifierTest, EncodeEdgeFeatureOnBox) {
|
|
FeatureClassifier classifier;
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
auto graph = classifier.build_face_graph(box);
|
|
if (!graph.edges.empty()) {
|
|
double edge_feat = classifier.encode_edge_feature(
|
|
box, graph.edges[0].first, graph.edges[0].second);
|
|
// 盒子边应为凸边(正值)
|
|
EXPECT_GE(edge_feat, -1.0);
|
|
EXPECT_LE(edge_feat, 1.0);
|
|
}
|
|
}
|
|
|
|
TEST(FeatureClassifierTest, ClassifyFeaturesOnBox) {
|
|
FeatureClassifier classifier;
|
|
classifier.load_model("models/dummy.onnx");
|
|
|
|
auto box = make_box(2, 2, 2);
|
|
auto result = classifier.classify_features(box);
|
|
|
|
EXPECT_TRUE(result.success);
|
|
EXPECT_TRUE(result.error_message.empty());
|
|
// 盒子可能在启发式分类下被归为某些特征
|
|
}
|
|
|
|
TEST(FeatureClassifierTest, AggregateFeaturesMergesAdjacent) {
|
|
FeatureClassifier classifier;
|
|
auto box = make_box(2, 2, 2);
|
|
auto graph = classifier.build_face_graph(box);
|
|
|
|
// 模拟面标签(全标记为 Fillet)
|
|
std::vector<std::pair<FeatureType, double>> labels(
|
|
graph.nodes.size(), {FeatureType::Fillet, 0.8});
|
|
|
|
auto features = classifier.aggregate_features(graph, labels, box);
|
|
|
|
// 连通的面会被聚合
|
|
EXPECT_GE(features.size(), 1u);
|
|
for (const auto& f : features) {
|
|
EXPECT_EQ(f.type, FeatureType::Fillet);
|
|
EXPECT_GT(f.confidence, 0.0);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// SimilaritySearch 测试 (4 项)
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(SimilaritySearchTest, ComputeESFOnBox) {
|
|
SimilaritySearch search;
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
auto esf = search.compute_esf(box, 500);
|
|
|
|
// 640 维直方图
|
|
EXPECT_EQ(esf.histogram.size(), 640u);
|
|
}
|
|
|
|
TEST(SimilaritySearchTest, ComputeFPFHOnBox) {
|
|
SimilaritySearch search;
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
auto fpfh = search.compute_fpfh(box, 300);
|
|
|
|
// 33 维直方图
|
|
EXPECT_EQ(fpfh.histogram.size(), 33u);
|
|
}
|
|
|
|
TEST(SimilaritySearchTest, ShapeDescriptorOnBox) {
|
|
SimilaritySearch search;
|
|
auto box = make_box(2, 2, 2);
|
|
|
|
auto desc = search.compute_shape_descriptor(box, "box_001", "/path/to/box.step", 500, 300);
|
|
|
|
EXPECT_EQ(desc.model_id, "box_001");
|
|
EXPECT_EQ(desc.model_path, "/path/to/box.step");
|
|
EXPECT_EQ(desc.esf.histogram.size(), 640u);
|
|
EXPECT_EQ(desc.fpfh.histogram.size(), 33u);
|
|
}
|
|
|
|
TEST(SimilaritySearchTest, IndexAndQuery) {
|
|
SimilaritySearch search;
|
|
|
|
auto box1 = make_box(2, 2, 2);
|
|
auto box2 = make_box(3, 3, 3);
|
|
|
|
auto desc1 = search.compute_shape_descriptor(box1, "box1", "", 500, 300);
|
|
auto desc2 = search.compute_shape_descriptor(box2, "box2", "", 500, 300);
|
|
|
|
search.build_index({desc1, desc2});
|
|
EXPECT_EQ(search.index_size(), 2u);
|
|
|
|
// 查询
|
|
auto matches = search.query(box1, 5);
|
|
EXPECT_GE(matches.size(), 1u);
|
|
|
|
// 自身应最相似
|
|
if (matches.size() >= 1) {
|
|
EXPECT_EQ(matches[0].model_id, "box1");
|
|
EXPECT_GT(matches[0].similarity, 0.0);
|
|
}
|
|
}
|
|
|
|
TEST(SimilaritySearchTest, ESFDistanceSelfIsZero) {
|
|
SimilaritySearch search;
|
|
auto box = make_box(2, 2, 2);
|
|
auto esf = search.compute_esf(box, 500);
|
|
|
|
double dist = SimilaritySearch::esf_distance(esf, esf);
|
|
EXPECT_NEAR(dist, 0.0, 1e-10);
|
|
}
|
|
|
|
TEST(SimilaritySearchTest, FPFHDistanceSelfIsZero) {
|
|
SimilaritySearch search;
|
|
auto box = make_box(2, 2, 2);
|
|
auto fpfh = search.compute_fpfh(box, 300);
|
|
|
|
double dist = SimilaritySearch::fpfh_distance(fpfh, fpfh);
|
|
EXPECT_NEAR(dist, 0.0, 1e-10);
|
|
}
|
|
|
|
TEST(SimilaritySearchTest, CombinedSimilarityInRange) {
|
|
auto box1 = make_box(2, 2, 2);
|
|
auto box2 = make_box(3, 3, 3);
|
|
|
|
SimilaritySearch search;
|
|
auto desc1 = search.compute_shape_descriptor(box1, "", "", 500, 300);
|
|
auto desc2 = search.compute_shape_descriptor(box2, "", "", 500, 300);
|
|
|
|
double sim = SimilaritySearch::combined_similarity(desc1, desc2);
|
|
EXPECT_GE(sim, 0.0);
|
|
EXPECT_LE(sim, 1.0);
|
|
}
|
|
|
|
TEST(SimilaritySearchTest, IndexClear) {
|
|
SimilaritySearch search;
|
|
auto box = make_box(2, 2, 2);
|
|
auto desc = search.compute_shape_descriptor(box);
|
|
search.add_to_index(desc);
|
|
EXPECT_EQ(search.index_size(), 1u);
|
|
|
|
search.clear_index();
|
|
EXPECT_EQ(search.index_size(), 0u);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// 辅助函数测试 (2 项)
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(FeatureLearningAuxTest, SampleSurfacePointsOnBox) {
|
|
auto box = make_box(2, 2, 2);
|
|
auto pts = sample_surface_points(box, 200);
|
|
|
|
EXPECT_GE(pts.size(), 0u);
|
|
// 采样点应在表面上(近似包围盒内)
|
|
for (const auto& p : pts) {
|
|
EXPECT_GE(p.x(), -1.5);
|
|
EXPECT_LE(p.x(), 1.5);
|
|
}
|
|
}
|
|
|
|
TEST(FeatureLearningAuxTest, FaceCentroidsOnBox) {
|
|
auto box = make_box(2, 2, 2);
|
|
auto centroids = face_centroids(box);
|
|
|
|
EXPECT_EQ(centroids.size(), box.num_faces());
|
|
for (const auto& c : centroids) {
|
|
EXPECT_TRUE(std::isfinite(c.x()));
|
|
EXPECT_TRUE(std::isfinite(c.y()));
|
|
EXPECT_TRUE(std::isfinite(c.z()));
|
|
}
|
|
}
|