Python实例题
题目
代码实现
实现原理
点集拓扑:
代数拓扑:
拓扑数据分析:
可视化:
关键代码解析
1. 点集拓扑
2. 代数拓扑
3. 拓扑数据分析
使用说明
安装依赖:
基本用法:
示例输出:
扩展建议
增强功能:
用户界面:
性能优化:
教学辅助:
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()
这个拓扑学计算工具基于以下技术实现:
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)]
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
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]