19.6 内核架构的模板技巧

相关章节

在本节中你将学到…

  • 内核参数化设计的核心思想
  • 类型相等包装器的实现原理
  • 延迟计算(Lazy evaluation)的工作机制
  • 区间算术在几何计算中的应用
  • CGAL内核的高级模板技巧

19.6.1 概念解释

什么是内核参数化设计?

CGAL的内核(Kernel)是几何算法的基石,它定义了几何对象(点、向量、线等)和基本操作(距离计算、方向判断等)。内核参数化设计允许用户选择不同的数值类型和坐标系,而无需修改算法代码。

类比理解: 想象内核是一个厨房的基础设施。你可以选择:

  • 燃气灶 vs 电磁炉(不同数值类型:double vs Gmpq)
  • 中式厨房 vs 西式厨房(不同坐标系:笛卡尔 vs 齐次)
  • 家用厨房 vs 商用厨房(不同精度级别)

但无论选择哪种,厨师(算法)的操作方式保持不变。

内核架构的核心组件

┌─────────────────────────────────────────────────────────────────┐
│                        内核架构概览                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │   Kernel     │───>│  几何对象    │    │  函数对象    │      │
│  │  (参数化)    │    │  Point_2     │    │  Less_xy_2   │      │
│  │              │    │  Segment_3   │    │  Orientation │      │
│  └──────────────┘    │  Plane_3     │    │  Distance    │      │
│         │            └──────────────┘    └──────────────┘      │
│         │                                                       │
│         ▼            ┌──────────────┐    ┌──────────────┐      │
│  ┌──────────────┐    │   数值类型   │    │   坐标系     │      │
│  │  Type_equality│   │   double     │    │  Cartesian   │      │
│  │   _wrapper   │    │   Gmpq       │    │  Homogeneous │      │
│  └──────────────┘    │   Interval   │    │              │      │
│                      └──────────────┘    └──────────────┘      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

19.6.2 为什么需要这些技巧?

问题1:类型循环依赖

// 问题:Point_2需要知道Kernel,Kernel又包含Point_2
template <typename Kernel>
class Point_2 {
    // 使用Kernel的功能
};
 
template <typename FT>
class MyKernel {
    typedef Point_2<MyKernel> Point_2;  // 循环依赖!
};

问题2:精确度与性能的平衡

// 精确计算很慢,近似计算可能出错
double orient(const Point& p, const Point& q, const Point& r) {
    return (q.x() - p.x()) * (r.y() - p.y()) - 
           (q.y() - p.y()) * (r.x() - p.x());
    // 浮点误差可能导致错误的几何判断!
}

问题3:代码复用

// 相同的算法需要支持:
// - Cartesian<double>(快速但不精确)
// - Cartesian<Gmpq>(精确但慢)
// - Homogeneous<leda_integer>(另一种表示)
// 不应该为每种组合写不同的代码!

19.6.3 代码示例

基础示例:类型相等包装器

#include <iostream>
#include <type_traits>
 
// ============================================
// 类型相等包装器原理
// ============================================
 
// 基础内核(只提供基本类型)
template <typename Kernel_>
struct KernelBase {
    // 基础几何对象定义
    template <typename K>
    struct Point_2_base {
        double x_, y_;
        Point_2_base(double x = 0, double y = 0) : x_(x), y_(y) {}
        double x() const { return x_; }
        double y() const { return y_; }
    };
    
    template <typename K>
    struct Vector_2_base {
        double x_, y_;
        Vector_2_base(double x = 0, double y = 0) : x_(x), y_(y) {}
        double x() const { return x_; }
        double y() const { return y_; }
    };
};
 
// 类型相等包装器
// 确保 Kernel::Point_2 和 Point_2<Kernel> 是同一类型
template <typename K_base, typename Kernel_>
struct Type_equality_wrapper : public K_base {
    // 通过typedef确保类型相等
    typedef typename K_base::template Point_2_base<Kernel_> Point_2;
    typedef typename K_base::template Vector_2_base<Kernel_> Vector_2;
};
 
// 完整的内核定义
template <typename FT = double>
struct MyKernel {
    // 首先定义内核类型本身
    typedef MyKernel<FT> Self;
    
    // 使用类型相等包装器
    typedef Type_equality_wrapper<KernelBase<Self>, Self> Kernel_base;
    
    // 导出几何对象类型
    typedef typename Kernel_base::Point_2 Point_2;
    typedef typename Kernel_base::Vector_2 Vector_2;
    
    // 数值类型
    typedef FT FT_type;
};
 
// 验证类型相等
int main() {
    typedef MyKernel<> Kernel;
    
    // 这两种方式创建的点应该是相同类型
    Kernel::Point_2 p1(1.0, 2.0);
    
    std::cout << "Point coordinates: (" << p1.x() << ", " << p1.y() << ")" << std::endl;
    
    // 验证类型相等
    static_assert(std::is_same<
        Kernel::Point_2, 
        typename Kernel::Kernel_base::Point_2
    >::value, "Types should be equal");
    
    std::cout << "Type equality verified\!" << std::endl;
    
    return 0;
}

中级示例:延迟计算基础

#include <iostream>
#include <memory>
#include <math>
 
// ============================================
// 延迟计算(Lazy Evaluation)基础
// ============================================
 
// 抽象基类:延迟计算的表示
template <typename ExactType>
struct LazyRep {
    mutable bool computed;
    mutable ExactType exact_value;
    
    LazyRep() : computed(false) {}
    virtual ~LazyRep() = default;
    
    ExactType exact() const {
        if (\!computed) {
            exact_value = compute_exact();
            computed = true;
        }
        return exact_value;
    }
    
protected:
    virtual ExactType compute_exact() const = 0;
};
 
// 延迟计算的句柄类
template <typename ExactType>
class Lazy {
    std::shared_ptr<LazyRep<ExactType>> rep;
    
public:
    Lazy() = default;
    explicit Lazy(LazyRep<ExactType>* r) : rep(r) {}
    
    ExactType exact() const {
        return rep ? rep->exact() : ExactType();
    }
    
    bool is_lazy() const { return rep \!= nullptr; }
};
 
// 具体实现:常量
template <typename ExactType>
struct LazyConstant : LazyRep<ExactType> {
    ExactType value;
    
    explicit LazyConstant(ExactType v) : value(v) {}
    
protected:
    ExactType compute_exact() const override {
        return value;
    }
};
 
// 具体实现:加法操作
template <typename ExactType>
struct LazyAdd : LazyRep<ExactType> {
    Lazy<ExactType> left, right;
    
    LazyAdd(Lazy<ExactType> l, Lazy<ExactType> r) 
        : left(std::move(l)), right(std::move(r)) {}
    
protected:
    ExactType compute_exact() const override {
        return left.exact() + right.exact();
    }
};
 
// 具体实现:乘法操作
template <typename ExactType>
struct LazyMultiply : LazyRep<ExactType> {
    Lazy<ExactType> left, right;
    
    LazyMultiply(Lazy<ExactType> l, Lazy<ExactType> r)
        : left(std::move(l)), right(std::move(r)) {}
    
protected:
    ExactType compute_exact() const override {
        return left.exact() * right.exact();
    }
};
 
// 延迟计算的数值类型
template <typename ExactType>
class LazyNumber {
    Lazy<ExactType> lazy;
    double approx;  // 近似值用于快速判断
    
public:
    LazyNumber() : approx(0) {}
    
    // 从精确值构造
    LazyNumber(ExactType exact, double approx_val) 
        : lazy(new LazyConstant<ExactType>(exact)), approx(approx_val) {}
    
    // 从近似值构造(延迟精确计算)
    explicit LazyNumber(double approx_val) : approx(approx_val) {}
    
    // 从延迟操作构造
    LazyNumber(Lazy<ExactType> l, double approx_val) 
        : lazy(std::move(l)), approx(approx_val) {}
    
    double approximation() const { return approx; }
    ExactType exact() const { return lazy.exact(); }
    
    // 加法
    LazyNumber operator+(const LazyNumber& other) const {
        double new_approx = approx + other.approx;
        auto add_rep = new LazyAdd<ExactType>(lazy, other.lazy);
        return LazyNumber(Lazy<ExactType>(add_rep), new_approx);
    }
    
    // 乘法
    LazyNumber operator*(const LazyNumber& other) const {
        double new_approx = approx * other.approx;
        auto mul_rep = new LazyMultiply<ExactType>(lazy, other.lazy);
        return LazyNumber(Lazy<ExactType>(mul_rep), new_approx);
    }
};
 
// 使用示例
int main() {
    // 创建延迟计算的数值
    LazyNumber<double> a(3.0, 3.0);  // 精确值=3.0,近似值=3.0
    LazyNumber<double> b(4.0, 4.0);
    LazyNumber<double> c(5.0, 5.0);
    
    // 构建表达式树:(a + b) * c
    auto sum = a + b;  // 不计算,只构建树
    auto result = sum * c;  // 继续构建树
    
    std::cout << "Approximation: " << result.approximation() << std::endl;  // 35.0
    std::cout << "Exact: " << result.exact() << std::endl;  // 35.0(延迟计算)
    
    return 0;
}

高级示例:区间算术

#include <iostream>
#include <math>
#include <algorithm>
#include <iomanip>
 
// ============================================
// 区间算术实现
// ============================================
 
// 区间类:表示一个数值范围 [inf, sup]
class Interval {
    double inf_, sup_;
    
public:
    // 构造函数
    Interval() : inf_(0), sup_(0) {}
    Interval(double v) : inf_(v), sup_(v) {}
    Interval(double i, double s) : inf_(i), sup_(s) {
        if (i > s) std::swap(inf_, sup_);
    }
    
    // 访问器
    double inf() const { return inf_; }
    double sup() const { return sup_; }
    double mid() const { return (inf_ + sup_) / 2; }
    double width() const { return sup_ - inf_; }
    
    // 判断区间是否包含0
    bool contains_zero() const { return inf_ <= 0 && sup_ >= 0; }
    
    // 判断区间是否完全为正/负
    bool is_positive() const { return inf_ > 0; }
    bool is_negative() const { return sup_ < 0; }
    
    // 判断是否为点区间(精确值已知)
    bool is_point() const { return inf_ == sup_; }
    
    // 区间加法(向下/向上舍入)
    Interval operator+(const Interval& other) const {
        return Interval(
            std::nextafter(inf_ + other.inf_, -INFINITY),  // 向下舍入
            std::nextafter(sup_ + other.sup_, INFINITY)    // 向上舍入
        );
    }
    
    // 区间减法
    Interval operator-(const Interval& other) const {
        return Interval(
            std::nextafter(inf_ - other.sup_, -INFINITY),
            std::nextafter(sup_ - other.inf_, INFINITY)
        );
    }
    
    // 区间乘法(需要考虑所有组合)
    Interval operator*(const Interval& other) const {
        double a = inf_, b = sup_, c = other.inf_, d = other.sup_;
        double products[4] = {a*c, a*d, b*c, b*d};
        return Interval(
            std::nextafter(*std::min_element(products, products+4), -INFINITY),
            std::nextafter(*std::max_element(products, products+4), INFINITY)
        );
    }
    
    // 区间除法
    Interval operator/(const Interval& other) const {
        if (other.contains_zero()) {
            // 除数包含0,结果为整个实数范围
            return Interval(-INFINITY, INFINITY);
        }
        double a = inf_, b = sup_, c = other.inf_, d = other.sup_;
        double quotients[4] = {a/c, a/d, b/c, b/d};
        return Interval(
            std::nextafter(*std::min_element(quotients, quotients+4), -INFINITY),
            std::nextafter(*std::max_element(quotients, quotients+4), INFINITY)
        );
    }
    
    // 输出
    friend std::ostream& operator<<(std::ostream& os, const Interval& i) {
        os << "[" << std::setprecision(6) << i.inf_ << ", " << i.sup_ << "]";
        return os;
    }
};
 
// ============================================
// 使用区间算术进行鲁棒几何计算
// ============================================
 
struct Point {
    Interval x, y;
    Point(const Interval& x_, const Interval& y_) : x(x_), y(y_) {}
    Point(double x_, double y_) : x(x_), y(y_) {}
};
 
// 使用区间算术计算方向
// 返回值:
//   正区间:确定左转
//   负区间:确定右转
//   包含0的区间:不确定,需要精确计算
Interval orientation_interval(const Point& p, const Point& q, const Point& r) {
    // 计算 (q-p) x (r-p)
    Interval qpx = q.x - p.x;
    Interval qpy = q.y - p.y;
    Interval rpx = r.x - p.x;
    Interval rpy = r.y - p.y;
    
    return qpx * rpy - qpy * rpx;
}
 
// 鲁棒的方向判断
enum Orientation {
    LEFT_TURN,
    RIGHT_TURN,
    COLLINEAR,
    UNCERTAIN  // 区间算术无法确定
};
 
Orientation robust_orientation(const Point& p, const Point& q, const Point& r) {
    Interval orient = orientation_interval(p, q, r);
    
    if (orient.is_positive()) return LEFT_TURN;
    if (orient.is_negative()) return RIGHT_TURN;
    if (orient.contains_zero()) {
        if (orient.is_point()) return COLLINEAR;
        return UNCERTAIN;  // 需要精确计算
    }
    return UNCERTAIN;
}
 
// ============================================
// 演示
// ============================================
 
int main() {
    std::cout << std::setprecision(10);
    
    // 基本区间运算
    Interval a(1.0, 2.0);
    Interval b(3.0, 4.0);
    
    std::cout << "Interval arithmetic demo:" << std::endl;
    std::cout << "a = " << a << std::endl;
    std::cout << "b = " << b << std::endl;
    std::cout << "a + b = " << (a + b) << std::endl;
    std::cout << "a * b = " << (a * b) << std::endl;
    
    // 几何方向判断
    std::cout << "\nOrientation tests:" << std::endl;
    
    // 明显的左转
    Point p1(0, 0), q1(1, 0), r1(0.5, 1);
    Interval orient1 = orientation_interval(p1, q1, r1);
    std::cout << "Left turn orientation: " << orient1 << std::endl;
    std::cout << "Result: " << robust_orientation(p1, q1, r1) << std::endl;
    
    // 接近共线的情况(可能不确定)
    Point p2(0, 0), q2(1, 0), r2(2, 0.0000001);
    Interval orient2 = orientation_interval(p2, q2, r2);
    std::cout << "\nNear-collinear orientation: " << orient2 << std::endl;
    std::cout << "Contains zero: " << orient2.contains_zero() << std::endl;
    
    return 0;
}

19.6.4 CGAL中的应用

CGAL的Type_equality_wrapper

// 来自 CGAL/Kernel/Type_equality_wrapper.h
namespace CGAL {
 
template < typename K_base, typename Kernel_ >
struct Type_equality_wrapper : public K_base {
    typedef K_base Kernel_base;
 
    // 使用宏批量定义类型相等
    #define CGAL_Kernel_obj(X) typedef CGAL::X<Kernel_> X;
    #include <CGAL/Kernel/interface_macros.h>
    
    // 额外的类型定义
    typedef CGAL::Conic_2<Kernel_> Conic_2;
    typedef CGAL::Aff_transformation_2<Kernel_> Aff_transformation_2;
};
 
} // namespace CGAL

CGAL的Lazy_exact_nt

// 来自 CGAL/Lazy_exact_nt.h(简化展示)
namespace CGAL {
 
template <class ET>  // ET = Exact Type
class Lazy_exact_nt : public Lazy<Interval_nt<false>, ET, To_interval<ET> > {
public:
    typedef ET ET_type;
    typedef Interval_nt<false> AT_type;  // Approximation Type
    
    // 构造:从近似值开始,延迟精确计算
    Lazy_exact_nt(double d) : base_type(new Lazy_exact_Cst<ET>(d)) {}
    
    // 构造:从精确值
    Lazy_exact_nt(const ET& e) : base_type(new Lazy_exact_Ex_Cst<ET>(e)) {}
    
    // 操作符:构建延迟计算图
    Lazy_exact_nt operator+(const Lazy_exact_nt& other) const {
        return Lazy_exact_nt(
            new Lazy_exact_Add<ET>(*this, other),
            this->approx() + other.approx()  // 同时计算近似值
        );
    }
    
    // 获取精确值(触发计算)
    const ET& exact() const {
        if (this->is_lazy()) {
            this->update_exact();  // 计算并缓存
        }
        return *this->ptr();
    }
    
    // 获取近似值(快速)
    const AT_type& approx() const {
        return this->approx_;  // 直接返回,不计算
    }
};
 
} // namespace CGAL

CGAL的Interval_nt

// 来自 CGAL/Interval_nt.h(简化展示)
namespace CGAL {
 
template <bool Protected = true>
class Interval_nt {
    double inf_, sup_;
    
public:
    // 保护舍入模式的构造
    Interval_nt(double i, double s) {
        if (Protected) {
            // 设置舍入模式
            int old_mode = CGAL::FPU_get_and_set_cw(CGAL_FE_TONEAREST);
            inf_ = i;
            sup_ = s;
            CGAL::FPU_set_cw(old_mode);
        } else {
            inf_ = i;
            sup_ = s;
        }
    }
    
    // 安全的算术运算
    Interval_nt operator+(const Interval_nt& other) const {
        Protector P;  // RAII保护舍入模式
        return Interval_nt(
            CGAL_IA_FORCE_TO_DOUBLE(inf_ + other.inf_),  // 向下舍入
            CGAL_IA_FORCE_TO_DOUBLE(sup_ + other.sup_)   // 向上舍入
        );
    }
    
    // 判断区间关系
    bool is_point() const { return inf_ == sup_; }
    bool is_same(const Interval_nt& other) const {
        return inf_ == other.inf_ && sup_ == other.sup_;
    }
    
    // 与0比较
    bool is_positive() const { return inf_ > 0; }
    bool is_negative() const { return sup_ < 0; }
    bool is_zero() const { return inf_ <= 0 && sup_ >= 0; }
    Sign sign() const {
        if (is_positive()) return POSITIVE;
        if (is_negative()) return NEGATIVE;
        return ZERO;  // 实际上可能是不确定的
    }
};
 
} // namespace CGAL

19.6.5 常见陷阱

陷阱1:舍入模式未正确设置

// 错误:未保护舍入模式
Interval_nt add_unsafe(const Interval_nt& a, const Interval_nt& b) {
    // 舍入模式不确定,结果可能错误
    return Interval_nt(a.inf() + b.inf(), a.sup() + b.sup());
}
 
// 正确:使用RAII保护舍入模式
Interval_nt add_safe(const Interval_nt& a, const Interval_nt& b) {
    Interval_nt::Protector P;  // 设置并恢复舍入模式
    return Interval_nt(
        CGAL_IA_FORCE_TO_DOUBLE(a.inf() + b.inf()),
        CGAL_IA_FORCE_TO_DOUBLE(a.sup() + b.sup())
    );
}

陷阱2:延迟计算的内存管理

// 危险:内存泄漏
template <typename ET>
Lazy_exact_nt<ET> operator+(const Lazy_exact_nt<ET>& a, 
                              const Lazy_exact_nt<ET>& b) {
    // 每次加法都分配新内存,没有释放机制
    return Lazy_exact_nt<ET>(new Lazy_exact_Add<ET>(a, b));
}
 
// 正确:使用智能指针管理
template <typename ET>
class Lazy_exact_nt {
    std::shared_ptr<Lazy_exact_nt_rep<ET>> rep;
    // ...
};

陷阱3:区间算术的过度保守

// 问题:区间算术可能过于保守
Interval a(1.0, 2.0);
Interval b(3.0, 4.0);
Interval c = a - a;  // 结果是 [-1, 1],不是 [0, 0]!
 
// 解决方案:识别并简化相同表达式
// 在CGAL中,这需要表达式模板或符号分析

19.6.6 最佳实践

实践1:内核选择策略

// 根据需求选择合适的内核
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
 
// 快速但不精确(适用于大多数情况)
typedef CGAL::Exact_predicates_inexact_constructions_kernel K_fast;
 
// 完全精确(需要精确构造时)
typedef CGAL::Exact_predicates_exact_constructions_kernel K_exact;
 
// 自定义内核(特殊需求)
typedef CGAL::Cartesian<CGAL::Lazy_exact_nt<CGAL::Gmpq>>> K_lazy;

实践2:延迟计算的显式转换

// 明确何时需要精确值
Lazy_exact_nt<Gmpq> lazy_value = /* ... */;
 
// 使用近似值进行快速过滤
if (lazy_value.approx().is_positive()) {
    // 确定为正,无需精确计算
}
 
// 必要时才获取精确值
if (lazy_value.approx().contains_zero()) {
    // 不确定,需要精确值
    Gmpq exact = lazy_value.exact();
    // ...
}

实践3:区间算术的过滤策略

// 多级过滤策略
template <typename Kernel>
Sign orientation(const Point& p, const Point& q, const Point& r) {
    // 第1级:区间算术快速判断
    Interval_nt<> orient_interval = compute_interval(p, q, r);
    if (orient_interval.is_positive()) return POSITIVE;
    if (orient_interval.is_negative()) return NEGATIVE;
    
    // 第2级:精确计算
    return compute_exact(p, q, r);
}

本节要点

  1. 类型相等包装器解决循环依赖:通过模板参数传递内核类型,确保Kernel::Point_2Point_2<Kernel>是同一类型。

  2. 延迟计算平衡性能与精度:只在需要时进行精确计算,大部分时间使用快速近似值。

  3. 区间算术提供鲁棒性保证:通过维护数值范围,可以检测和避免浮点误差导致的几何错误。

  4. 过滤策略优化性能:先使用快速方法判断,只在必要时进行精确计算。

  5. 舍入模式管理至关重要:区间算术需要精确控制浮点舍入方向。

  6. 内核选择影响整体性能:根据应用需求选择合适的内核类型。


进一步阅读

  • CGAL文档:Kernel Manual中的”Kernel Design”章节
  • 《The Design and Implementation of CGAL》:CGAL设计的原始论文
  • 《Robust Geometric Computing》:鲁棒几何计算的深入讨论