Python实例题:Python计算拓扑学

目录

Python实例题

题目

代码实现

实现原理

点集拓扑:

代数拓扑:

拓扑数据分析:

可视化:

关键代码解析

1. 点集拓扑

2. 代数拓扑

3. 拓扑数据分析

使用说明

安装依赖:

基本用法:

示例输出:

扩展建议

增强功能:

用户界面:

性能优化:

教学辅助:

Python实例题

题目

Python计算拓扑学

代码实现

import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from scipy.spatial import Delaunay
from sklearn.metrics import pairwise_distances
import gudhi as gd

class Topology:
    """拓扑学计算类,支持点集拓扑和代数拓扑计算"""
    
    def __init__(self):
        """初始化拓扑学计算器"""
        pass
    
    # ================ 点集拓扑 ================
    
    def generate_open_set(self, points, center, radius):
        """
        生成度量空间中的开集
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            center: 中心点,形状为(n_dim,)
            radius: 半径
            
        返回:
            numpy数组: 开集中的点
        """
        # 计算各点到中心点的距离
        distances = np.linalg.norm(points - center, axis=1)
        
        # 返回距离小于半径的点
        return points[distances < radius]
    
    def is_open(self, points, subset, metric='euclidean', eps=1e-6):
        """
        判断子集是否为开集
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            subset: 待判断的子集,形状为(n_subset_points, n_dim)
            metric: 距离度量,默认为欧几里得距离
            eps: 容差
            
        返回:
            bool: 如果是开集返回True,否则返回False
        """
        # 计算子集中每个点的最近邻点(不包括自身)
        subset_indices = [np.where((points == p).all(axis=1))[0][0] for p in subset]
        
        # 计算点集的距离矩阵
        dist_matrix = pairwise_distances(points, metric=metric)
        
        is_open = True
        
        # 检查子集中每个点是否存在一个完全包含在子集中的开球
        for i in subset_indices:
            # 获取该点到子集中其他点的距离
            dist_to_subset = dist_matrix[i, subset_indices]
            
            # 排除自身距离(为0)
            dist_to_subset = dist_to_subset[dist_to_subset > eps]
            
            if len(dist_to_subset) == 0:
                # 如果子集中只有一个点,默认认为是开集
                continue
                
            # 计算最小距离
            min_dist = np.min(dist_to_subset)
            
            # 生成以该点为中心,min_dist为半径的开球
            open_ball = self.generate_open_set(points, points[i], min_dist - eps)
            
            # 检查开球是否完全包含在子集中
            open_ball_indices = [np.where((points == p).all(axis=1))[0][0] for p in open_ball]
            
            if not all(idx in subset_indices for idx in open_ball_indices):
                is_open = False
                break
                
        return is_open
    
    def closure(self, points, subset, metric='euclidean'):
        """
        计算集合的闭包
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            subset: 待计算闭包的子集,形状为(n_subset_points, n_dim)
            metric: 距离度量,默认为欧几里得距离
            
        返回:
            numpy数组: 闭包中的点
        """
        # 计算点集的距离矩阵
        dist_matrix = pairwise_distances(points, metric=metric)
        
        # 获取子集中的点在原数据中的索引
        subset_indices = [np.where((points == p).all(axis=1))[0][0] for p in subset]
        
        # 闭包包含所有极限点
        closure_indices = set(subset_indices)
        
        # 检查每个点是否为极限点
        for i in range(len(points)):
            if i in subset_indices:
                continue
                
            # 获取该点到子集中各点的距离
            dist_to_subset = dist_matrix[i, subset_indices]
            
            # 如果存在任意小的距离(除了0),则该点是极限点
            if np.any(dist_to_subset < 1e-6):
                closure_indices.add(i)
                
        return points[list(closure_indices)]
    
    def boundary(self, points, subset, metric='euclidean'):
        """
        计算集合的边界
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            subset: 待计算边界的子集,形状为(n_subset_points, n_dim)
            metric: 距离度量,默认为欧几里得距离
            
        返回:
            numpy数组: 边界中的点
        """
        # 计算闭包
        closure_set = self.closure(points, subset, metric)
        
        # 计算补集
        subset_indices = [np.where((points == p).all(axis=1))[0][0] for p in subset]
        complement_indices = [i for i in range(len(points)) if i not in subset_indices]
        complement = points[complement_indices]
        
        # 计算补集的闭包
        closure_complement = self.closure(points, complement, metric)
        
        # 边界是闭包和补集闭包的交集
        boundary_indices = []
        
        for p in closure_set:
            if any(np.allclose(p, q) for q in closure_complement):
                boundary_indices.append(np.where((points == p).all(axis=1))[0][0])
                
        return points[boundary_indices]
    
    # ================ 代数拓扑 ================
    
    def simplicial_complex(self, points, max_dim=2):
        """
        构建点集的单纯复形(使用Delaunay三角剖分)
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            max_dim: 最大单形维度,默认为2(三角形)
            
        返回:
            list: 单纯复形的单形列表,每个单形表示为顶点索引的元组
        """
        # 构建Delaunay三角剖分
        tri = Delaunay(points)
        
        # 获取所有单形
        simplices = []
        
        # 添加0-单形(顶点)
        for i in range(len(points)):
            simplices.append((i,))
            
        # 添加1-单形(边)
        edges = set()
        for simplex in tri.simplices:
            for i in range(len(simplex)):
                for j in range(i+1, len(simplex)):
                    edge = (simplex[i], simplex[j])
                    edges.add(tuple(sorted(edge)))
        
        simplices.extend(list(edges))
        
        # 添加2-单形(三角形)
        if max_dim >= 2:
            for simplex in tri.simplices:
                # 确保每个三角形只添加一次
                triangle = tuple(sorted(simplex))
                simplices.append(triangle)
        
        # 添加更高维度的单形(如果需要)
        if max_dim > 2:
            print("警告: 仅支持到2-单形(三角形)")
        
        return simplices
    
    def visualize_simplicial_complex(self, points, simplices, title=None):
        """
        可视化单纯复形
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            simplices: 单纯复形的单形列表
            title: 图像标题,默认为None
        """
        # 创建图形
        plt.figure(figsize=(10, 8))
        
        # 提取顶点
        vertices = [s for s in simplices if len(s) == 1]
        
        # 提取边
        edges = [s for s in simplices if len(s) == 2]
        
        # 提取三角形
        triangles = [s for s in simplices if len(s) == 3]
        
        # 绘制顶点
        plt.scatter(points[:, 0], points[:, 1], s=100, color='blue')
        
        # 绘制边
        for edge in edges:
            p1 = points[edge[0]]
            p2 = points[edge[1]]
            plt.plot([p1[0], p2[0]], [p1[1], p2[1]], 'k-')
        
        # 绘制三角形
        for triangle in triangles:
            p1 = points[triangle[0]]
            p2 = points[triangle[1]]
            p3 = points[triangle[2]]
            plt.fill([p1[0], p2[0], p3[0]], [p1[1], p2[1], p3[1]], 'r', alpha=0.3)
        
        # 设置标题和坐标轴
        if title:
            plt.title(title, fontsize=14)
        
        plt.axis('equal')
        plt.grid(True)
        plt.show()
    
    def compute_betti_numbers(self, points, max_dim=2, max_edge_length=1.0):
        """
        计算点集的Betti数(拓扑不变量)
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            max_dim: 最大同调维度,默认为2
            max_edge_length: 最大边长度,控制Rips复形的大小
            
        返回:
            list: Betti数列表,索引i对应第i个Betti数
        """
        # 创建Rips复形
        rips = gd.RipsComplex(points=points, max_edge_length=max_edge_length)
        
        # 构建单纯复形
        simplex_tree = rips.create_simplex_tree(max_dimension=max_dim)
        
        # 计算持久同调
        persistence = simplex_tree.persistence()
        
        # 计算Betti数
        betti_numbers = [0] * (max_dim + 1)
        
        for interval in persistence:
            dim = interval[0]
            if interval[1][1] == float('inf'):
                # 无限持久的特征对应Betti数
                betti_numbers[dim] += 1
        
        return betti_numbers
    
    def visualize_persistence_diagram(self, points, max_dim=2, max_edge_length=1.0):
        """
        可视化持久同调图
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            max_dim: 最大同调维度,默认为2
            max_edge_length: 最大边长度,控制Rips复形的大小
        """
        # 创建Rips复形
        rips = gd.RipsComplex(points=points, max_edge_length=max_edge_length)
        
        # 构建单纯复形
        simplex_tree = rips.create_simplex_tree(max_dimension=max_dim)
        
        # 计算持久同调
        persistence = simplex_tree.persistence()
        
        # 绘制持久图
        gd.plot_persistence_diagram(persistence)
        plt.title('持久同调图')
        plt.show()
    
    # ================ 拓扑数据分析 ================
    
    def persistent_homology(self, points, max_dim=2, max_edge_length=1.0):
        """
        计算持久同调
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            max_dim: 最大同调维度,默认为2
            max_edge_length: 最大边长度,控制Rips复形的大小
            
        返回:
            list: 持久同调的出生-死亡对列表
        """
        # 创建Rips复形
        rips = gd.RipsComplex(points=points, max_edge_length=max_edge_length)
        
        # 构建单纯复形
        simplex_tree = rips.create_simplex_tree(max_dimension=max_dim)
        
        # 计算持久同调
        persistence = simplex_tree.persistence()
        
        return persistence
    
    def vietoris_rips_complex(self, points, radius, max_dim=2):
        """
        构建Vietoris-Rips复形
        
        参数:
            points: 空间中的点集,形状为(n_points, n_dim)
            radius: Rips复形的半径
            max_dim: 最大单形维度,默认为2
            
        返回:
            gudhi.SimplexTree: Vietoris-Rips复形
        """
        # 创建Rips复形
        rips = gd.RipsComplex(points=points, max_edge_length=2*radius)
        
        # 构建单纯复形
        simplex_tree = rips.create_simplex_tree(max_dimension=max_dim)
        
        return simplex_tree


# 示例使用
def example_usage():
    topo = Topology()
    
    print("\n===== 点集拓扑示例 =====")
    
    # 生成一些二维点
    points = np.array([
        [0, 0], [1, 0], [2, 0],
        [0, 1], [1, 1], [2, 1],
        [0, 2], [1, 2], [2, 2]
    ])
    
    # 定义一个子集
    subset = np.array([[0, 0], [1, 0], [0, 1]])
    
    print("点集:")
    print(points)
    
    print("\n子集:")
    print(subset)
    
    # 判断子集是否为开集
    is_open = topo.is_open(points, subset)
    print(f"\n子集是否为开集: {is_open}")
    
    # 计算闭包
    closure = topo.closure(points, subset)
    print("\n闭包:")
    print(closure)
    
    # 计算边界
    boundary = topo.boundary(points, subset)
    print("\n边界:")
    print(boundary)
    
    # 可视化
    plt.figure(figsize=(10, 8))
    plt.scatter(points[:, 0], points[:, 1], s=100, color='gray', alpha=0.5)
    plt.scatter(subset[:, 0], subset[:, 1], s=100, color='blue')
    plt.scatter(closure[:, 0], closure[:, 1], s=100, color='green', marker='x')
    plt.scatter(boundary[:, 0], boundary[:, 1], s=100, color='red', marker='^')
    plt.title('点集、子集、闭包和边界')
    plt.grid(True)
    plt.legend(['点集', '子集', '闭包', '边界'])
    plt.axis('equal')
    plt.show()
    
    print("\n===== 代数拓扑示例 =====")
    
    # 生成圆环上的点
    theta = np.linspace(0, 2*np.pi, 20)
    circle_points = np.array([
        np.cos(theta),
        np.sin(theta)
    ]).T
    
    # 构建单纯复形
    simplices = topo.simplicial_complex(circle_points, max_dim=2)
    
    # 可视化单纯复形
    topo.visualize_simplicial_complex(circle_points, simplices, "圆环的单纯复形")
    
    # 计算Betti数
    betti_numbers = topo.compute_betti_numbers(circle_points, max_dim=2, max_edge_length=1.5)
    print(f"\nBetti数: {betti_numbers}")
    print("解释: Betti0=1(连通分量数), Betti1=1(洞的数量)")
    
    # 可视化持久同调图
    topo.visualize_persistence_diagram(circle_points, max_dim=2, max_edge_length=1.5)
    
    print("\n===== 拓扑数据分析示例 =====")
    
    # 生成二维环面上的点
    n_points = 100
    theta = np.random.uniform(0, 2*np.pi, n_points)
    phi = np.random.uniform(0, 2*np.pi, n_points)
    R = 2  # 大环半径
    r = 1  # 小环半径
    
    torus_points = np.zeros((n_points, 3))
    torus_points[:, 0] = (R + r * np.cos(theta)) * np.cos(phi)
    torus_points[:, 1] = (R + r * np.cos(theta)) * np.sin(phi)
    torus_points[:, 2] = r * np.sin(theta)
    
    # 计算持久同调
    persistence = topo.persistent_homology(torus_points, max_dim=2, max_edge_length=2.0)
    
    # 可视化持久同调
    gd.plot_persistence_diagram(persistence)
    plt.title('环面的持久同调图')
    plt.show()
    
    # 计算Betti数
    betti_numbers = topo.compute_betti_numbers(torus_points, max_dim=2, max_edge_length=2.0)
    print(f"\n环面的Betti数: {betti_numbers}")
    print("解释: Betti0=1(连通分量数), Betti1=2(一维洞的数量), Betti2=1(二维洞的数量)")


if __name__ == "__main__":
    example_usage()    

实现原理

这个拓扑学计算工具基于以下技术实现:

  • 点集拓扑

    • 实现开集、闭包、边界等基本概念的计算
    • 基于距离度量定义拓扑结构
    • 提供拓扑空间的可视化和分析
  • 代数拓扑

    • 使用 Delaunay 三角剖分构建单纯复形
    • 计算 Betti 数等拓扑不变量
    • 支持持久同调分析和可视化
  • 拓扑数据分析

    • 实现 Vietoris-Rips 复形构建
    • 提供持久同调计算和分析
    • 使用 GUDHI 库进行高效的拓扑计算
  • 可视化

    • 使用 Matplotlib 绘制点集和拓扑结构
    • 可视化单纯复形和持久同调图
    • 提供交互式的拓扑分析结果展示

关键代码解析

1. 点集拓扑

def is_open(self, points, subset, metric='euclidean', eps=1e-6):
    """判断子集是否为开集"""
    subset_indices = [np.where((points == p).all(axis=1))[0][0] for p in subset]
    dist_matrix = pairwise_distances(points, metric=metric)
    
    is_open = True
    
    for i in subset_indices:
        dist_to_subset = dist_matrix[i, subset_indices]
        dist_to_subset = dist_to_subset[dist_to_subset > eps]
        
        if len(dist_to_subset) == 0:
            continue
            
        min_dist = np.min(dist_to_subset)
        open_ball = self.generate_open_set(points, points[i], min_dist - eps)
        open_ball_indices = [np.where((points == p).all(axis=1))[0][0] for p in open_ball]
        
        if not all(idx in subset_indices for idx in open_ball_indices):
            is_open = False
            break
            
    return is_open

def closure(self, points, subset, metric='euclidean'):
    """计算集合的闭包"""
    dist_matrix = pairwise_distances(points, metric=metric)
    subset_indices = [np.where((points == p).all(axis=1))[0][0] for p in subset]
    
    closure_indices = set(subset_indices)
    
    for i in range(len(points)):
        if i in subset_indices:
            continue
            
        dist_to_subset = dist_matrix[i, subset_indices]
        
        if np.any(dist_to_subset < 1e-6):
            closure_indices.add(i)
            
    return points[list(closure_indices)]

2. 代数拓扑

def simplicial_complex(self, points, max_dim=2):
    """构建点集的单纯复形"""
    tri = Delaunay(points)
    simplices = []
    
    # 添加顶点
    for i in range(len(points)):
        simplices.append((i,))
        
    # 添加边
    edges = set()
    for simplex in tri.simplices:
        for i in range(len(simplex)):
            for j in range(i+1, len(simplex)):
                edge = (simplex[i], simplex[j])
                edges.add(tuple(sorted(edge)))
    
    simplices.extend(list(edges))
    
    # 添加三角形
    if max_dim >= 2:
        for simplex in tri.simplices:
            triangle = tuple(sorted(simplex))
            simplices.append(triangle)
    
    return simplices

def compute_betti_numbers(self, points, max_dim=2, max_edge_length=1.0):
    """计算点集的Betti数"""
    rips = gd.RipsComplex(points=points, max_edge_length=max_edge_length)
    simplex_tree = rips.create_simplex_tree(max_dimension=max_dim)
    persistence = simplex_tree.persistence()
    
    betti_numbers = [0] * (max_dim + 1)
    
    for interval in persistence:
        dim = interval[0]
        if interval[1][1] == float('inf'):
            betti_numbers[dim] += 1
    
    return betti_numbers

3. 拓扑数据分析

def persistent_homology(self, points, max_dim=2, max_edge_length=1.0):
    """计算持久同调"""
    rips = gd.RipsComplex(points=points, max_edge_length=max_edge_length)
    simplex_tree = rips.create_simplex_tree(max_dimension=max_dim)
    persistence = simplex_tree.persistence()
    
    return persistence

def vietoris_rips_complex(self, points, radius, max_dim=2):
    """构建Vietoris-Rips复形"""
    rips = gd.RipsComplex(points=points, max_edge_length=2*radius)
    simplex_tree = rips.create_simplex_tree(max_dimension=max_dim)
    
    return simplex_tree

使用说明

  • 安装依赖

pip install numpy matplotlib networkx scipy gudhi
  • 基本用法

from topology import Topology

# 创建拓扑计算器实例
topo = Topology()

# 生成一些二维点
points = np.array([
    [0, 0], [1, 0], [2, 0],
    [0, 1], [1, 1], [2, 1],
    [0, 2], [1, 2], [2, 2]
])

# 定义一个子集
subset = np.array([[0, 0], [1, 0], [0, 1]])

# 判断子集是否为开集
is_open = topo.is_open(points, subset)
print(f"子集是否为开集: {is_open}")

# 计算闭包
closure = topo.closure(points, subset)
print("闭包:")
print(closure)

# 生成圆环上的点
theta = np.linspace(0, 2*np.pi, 20)
circle_points = np.array([
    np.cos(theta),
    np.sin(theta)
]).T

# 构建单纯复形
simplices = topo.simplicial_complex(circle_points, max_dim=2)

# 可视化单纯复形
topo.visualize_simplicial_complex(circle_points, simplices, "圆环的单纯复形")

# 计算Betti数
betti_numbers = topo.compute_betti_numbers(circle_points, max_dim=2, max_edge_length=1.5)
print(f"Betti数: {betti_numbers}")
  • 示例输出

子集是否为开集: False
闭包:
[[0. 0.]
 [1. 0.]
 [0. 1.]]
Betti数: [1, 1, 0]

扩展建议

  • 增强功能

    • 添加更多拓扑不变量的计算(如欧拉特征)
    • 实现更复杂的单纯复形构建方法
    • 增加高维拓扑结构的支持和可视化
    • 支持流形学习和拓扑降维
  • 用户界面

    • 开发命令行交互界面
    • 创建图形界面(如使用 Tkinter 或 PyQt)
    • 实现 Web 界面(如使用 Flask 或 Django)
  • 性能优化

    • 针对大规模数据进行优化
    • 添加并行计算支持
    • 优化持久同调计算的效率
  • 教学辅助

    • 添加拓扑学概念解释和公式推导
    • 提供交互式可视化(如动态调整 Rips 复形参数)
    • 实现练习题生成和解答功能

你可能感兴趣的:(实例,python,拓扑学,开发语言)