refactor: 重组项目目录结构

以讲义内容为骨架迁移到标准目录格式:
- officefile/ 主内容(12章 + 附录 + CC4SI补充)
- dofile/ 代码示例(11个Python脚本)
- data/ 图片资源
- output/ 生成输出(忽略)
- Archive/ 归档旧目录(忽略)
- .claude/skills/ 保留markdown-to-docx工具链
- .pandoc/ 保留CSL和本地化配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 14:00:56 +08:00
parent af083069d0
commit 219232de74
91 changed files with 39365 additions and 10 deletions
@@ -0,0 +1,957 @@
"""
空间推理示例 (Spatial Reasoning Example)
=======================================
本示例展示空间智能系统中的空间推理方法。
空间推理是从已知空间事实推导新知识的过程。
核心概念:
1. 定性推理 - 使用定性术语描述空间关系
2. 定量推理 - 使用精确数值计算
3. 空间逻辑 - 形式化的空间推理规则
4. 路径规划 - 寻找最优路径
5. 可见性分析 - 判断视线可见性
应用场景:
- 导航与路径规划
- 空间查询与分析
- 地理推理系统
- 机器人导航
作者: CC4SI 项目组
"""
import math
import heapq
from typing import List, Dict, Tuple, Optional, Set, Any
from dataclasses import dataclass, field
from enum import Enum
from abc import ABC, abstractmethod
import random
# 导入空间表征示例中的基础类
import sys
import os
sys.path.append(os.path.dirname(__file__))
try:
from spatial_representation import Point, LineString, Polygon, Envelope
except ImportError:
# 如果导入失败,定义简化版本
@dataclass
class Point:
x: float
y: float
def distance_to(self, other):
return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
def __repr__(self):
return f"({self.x:.2f}, {self.y:.2f})"
# ============================================================================
# 定性空间推理
# ============================================================================
class CardinalDirection(Enum):
"""基本方向"""
NORTH = "N"
SOUTH = "S"
EAST = "E"
WEST = "W"
NORTHEAST = "NE"
NORTHWEST = "NW"
SOUTHEAST = "SE"
SOUTHWEST = "SW"
@classmethod
def from_angle(cls, angle: float) -> 'CardinalDirection':
"""
从角度获取方向
Args:
angle: 角度 (度, 0=东, 90=北)
Returns:
方向枚举
"""
# 归一化到0-360
angle = angle % 360
if angle >= 337.5 or angle < 22.5:
return cls.EAST
elif 22.5 <= angle < 67.5:
return cls.NORTHEAST
elif 67.5 <= angle < 112.5:
return cls.NORTH
elif 112.5 <= angle < 157.5:
return cls.NORTHWEST
elif 157.5 <= angle < 202.5:
return cls.WEST
elif 202.5 <= angle < 247.5:
return cls.SOUTHWEST
elif 247.5 <= angle < 292.5:
return cls.SOUTH
else: # 292.5 <= angle < 337.5
return cls.SOUTHEAST
@dataclass
class QualitativeRelation:
"""定性空间关系"""
relation_type: str # "direction", "distance", "topology"
value: str # 如 "north", "near", "inside"
confidence: float = 1.0
def __repr__(self) -> str:
return f"{self.relation_type}={self.value} (conf={self.confidence:.2f})"
class QualitativeReasoner:
"""
定性空间推理器
使用定性术语进行空间推理。
"""
def __init__(self):
self.facts: List[Tuple[str, str, QualitativeRelation]] = []
def add_fact(self, entity1: str, entity2: str,
relation: QualitativeRelation) -> None:
"""添加空间事实"""
self.facts.append((entity1, entity2, relation))
def infer_direction(self, from_point: Point, to_point: Point) -> QualitativeRelation:
"""
推断两点之间的方向关系
Args:
from_point: 起始点
to_point: 目标点
Returns:
方向关系
"""
dx = to_point.x - from_point.x
dy = to_point.y - from_point.y
# 计算角度 (从东开始逆时针)
angle = math.degrees(math.atan2(dy, dx))
direction = CardinalDirection.from_angle(angle)
return QualitativeRelation(
relation_type="direction",
value=direction.value,
confidence=1.0
)
def infer_distance_category(self, p1: Point, p2: Point,
thresholds: Dict[str, float] = None) -> QualitativeRelation:
"""
推断距离类别
Args:
p1: 第一个点
p2: 第二个点
thresholds: 距离阈值字典
Returns:
距离类别关系
"""
if thresholds is None:
thresholds = {
"very_close": 100,
"close": 500,
"moderate": 1000,
"far": 5000
}
dist = p1.distance_to(p2)
if dist < thresholds.get("very_close", 100):
category = "very_close"
elif dist < thresholds.get("close", 500):
category = "close"
elif dist < thresholds.get("moderate", 1000):
category = "moderate"
else:
category = "far"
return QualitativeRelation(
relation_type="distance",
value=category,
confidence=1.0
)
def compose_relations(self, rel1: QualitativeRelation,
rel2: QualitativeRelation) -> QualitativeRelation:
"""
组合两个关系
例如: A在B的北边,B在C的东边 -> A在C的东北边
"""
if rel1.relation_type == "direction" and rel2.relation_type == "direction":
# 方向组合
return self._compose_directions(rel1.value, rel2.value)
return QualitativeRelation(
relation_type="unknown",
value="unknown",
confidence=0.5
)
def _compose_directions(self, dir1: str, dir2: str) -> QualitativeRelation:
"""组合两个方向"""
direction_map = {
"N": (0, 1), "S": (0, -1), "E": (1, 0), "W": (-1, 0),
"NE": (1, 1), "NW": (-1, 1), "SE": (1, -1), "SW": (-1, -1)
}
if dir1 in direction_map and dir2 in direction_map:
v1 = direction_map[dir1]
v2 = direction_map[dir2]
# 向量相加
result = (v1[0] + v2[0], v1[1] + v2[1])
# 找到最接近的方向
best_dir = "unknown"
best_dot = -1
for name, vec in direction_map.items():
dot = result[0] * vec[0] + result[1] * vec[1]
if dot > best_dot:
best_dot = dot
best_dir = name
return QualitativeRelation(
relation_type="direction",
value=best_dir,
confidence=0.8
)
return QualitativeRelation("direction", "unknown", 0.3)
# ============================================================================
# 路径规划
# ============================================================================
@dataclass
class Node:
"""图节点"""
id: str
point: Point
neighbors: List[str] = field(default_factory=list)
def __hash__(self):
return hash(self.id)
@dataclass
class Edge:
"""图边"""
from_node: str
to_node: str
weight: float # 权重 (如距离)
bidirectional: bool = True
class SpatialGraph:
"""
空间图
用于路径规划的空间网络结构。
"""
def __init__(self):
self.nodes: Dict[str, Node] = {}
self.edges: List[Edge] = []
def add_node(self, id: str, point: Point) -> Node:
"""添加节点"""
node = Node(id=id, point=point)
self.nodes[id] = node
return node
def add_edge(self, from_id: str, to_id: str, weight: float = None,
bidirectional: bool = True) -> None:
"""添加边"""
if from_id not in self.nodes or to_id not in self.nodes:
raise ValueError("节点不存在")
# 如果未指定权重,使用欧氏距离
if weight is None:
weight = self.nodes[from_id].point.distance_to(self.nodes[to_id].point)
edge = Edge(from_id, to_id, weight, bidirectional)
self.edges.append(edge)
# 更新邻接关系
self.nodes[from_id].neighbors.append(to_id)
if bidirectional:
self.nodes[to_id].neighbors.append(from_id)
def get_edge_weight(self, from_id: str, to_id: str) -> float:
"""获取边的权重"""
for edge in self.edges:
if edge.from_node == from_id and edge.to_node == to_id:
return edge.weight
if edge.bidirectional and edge.from_node == to_id and edge.to_node == from_id:
return edge.weight
return float('inf')
def shortest_path(self, start_id: str, end_id: str) -> Optional[List[str]]:
"""
使用Dijkstra算法计算最短路径
Args:
start_id: 起始节点ID
end_id: 目标节点ID
Returns:
节点ID列表,表示路径
"""
if start_id not in self.nodes or end_id not in self.nodes:
return None
# 优先队列: (距离, 节点ID)
pq = [(0, start_id)]
# 距离字典
distances = {node_id: float('inf') for node_id in self.nodes}
distances[start_id] = 0
# 前驱节点
previous = {start_id: None}
# 已访问
visited = set()
while pq:
current_dist, current_id = heapq.heappop(pq)
if current_id in visited:
continue
visited.add(current_id)
if current_id == end_id:
break
# 检查所有邻居
for neighbor_id in self.nodes[current_id].neighbors:
if neighbor_id in visited:
continue
edge_weight = self.get_edge_weight(current_id, neighbor_id)
new_dist = current_dist + edge_weight
if new_dist < distances[neighbor_id]:
distances[neighbor_id] = new_dist
previous[neighbor_id] = current_id
heapq.heappush(pq, (new_dist, neighbor_id))
# 重建路径
if distances[end_id] == float('inf'):
return None
path = []
current = end_id
while current is not None:
path.append(current)
current = previous.get(current)
path.reverse()
return path
def shortest_path_distance(self, start_id: str, end_id: str) -> float:
"""获取最短路径距离"""
path = self.shortest_path(start_id, end_id)
if not path:
return float('inf')
total = 0.0
for i in range(len(path) - 1):
total += self.get_edge_weight(path[i], path[i + 1])
return total
# ============================================================================
# A*路径规划
# ============================================================================
class AStarPlanner:
"""
A*路径规划器
使用启发式搜索的高效路径规划算法。
"""
def __init__(self, graph: SpatialGraph):
self.graph = graph
def heuristic(self, node_id: str, goal_id: str) -> float:
"""
启发式函数 (使用欧氏距离)
Args:
node_id: 当前节点
goal_id: 目标节点
Returns:
启发式估计值
"""
if node_id not in self.graph.nodes or goal_id not in self.graph.nodes:
return 0.0
return self.graph.nodes[node_id].point.distance_to(
self.graph.nodes[goal_id].point
)
def plan(self, start_id: str, goal_id: str) -> Optional[Tuple[List[str], float]]:
"""
规划路径
Args:
start_id: 起始节点ID
goal_id: 目标节点ID
Returns:
(路径节点列表, 总距离) 或 None
"""
if start_id not in self.graph.nodes or goal_id not in self.graph.nodes:
return None
# 开集和闭集
open_set = {start_id}
closed_set = set()
# g值: 从起点到当前节点的实际距离
g_score = {node_id: float('inf') for node_id in self.graph.nodes}
g_score[start_id] = 0
# f值: g值 + 启发式值
f_score = {node_id: float('inf') for node_id in self.graph.nodes}
f_score[start_id] = self.heuristic(start_id, goal_id)
# 前驱节点
came_from = {}
while open_set:
# 获取f值最小的节点
current = min(open_set, key=lambda x: f_score[x])
if current == goal_id:
# 重建路径
path = [current]
total_distance = g_score[current]
while current in came_from:
current = came_from[current]
path.append(current)
path.reverse()
return (path, total_distance)
open_set.remove(current)
closed_set.add(current)
# 检查邻居
for neighbor in self.graph.nodes[current].neighbors:
if neighbor in closed_set:
continue
# 计算 tentative_g_score
edge_weight = self.graph.get_edge_weight(current, neighbor)
tentative_g = g_score[current] + edge_weight
if neighbor not in open_set:
open_set.add(neighbor)
elif tentative_g >= g_score[neighbor]:
continue
# 更新
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self.heuristic(neighbor, goal_id)
return None # 没有找到路径
# ============================================================================
# 可见性分析
# ============================================================================
class VisibilityAnalyzer:
"""
可见性分析器
判断点之间的可见性,考虑障碍物。
"""
def __init__(self, obstacles: List[Polygon] = None):
"""
初始化可见性分析器
Args:
obstacles: 障碍物多边形列表
"""
self.obstacles = obstacles or []
def add_obstacle(self, obstacle: Polygon) -> None:
"""添加障碍物"""
self.obstacles.append(obstacle)
def is_visible(self, p1: Point, p2: Point,
tolerance: float = 1e-6) -> bool:
"""
判断两点之间是否可见
Args:
p1: 第一个点
p2: 第二个点
tolerance: 容差
Returns:
是否可见
"""
# 检视线是否与任何障碍物相交
for obstacle in self.obstacles:
if self._line_intersects_polygon(p1, p2, obstacle):
return False
return True
def _line_intersects_polygon(self, p1: Point, p2: Point,
polygon: Polygon) -> bool:
"""判断线段是否与多边形相交"""
# 首先检查包围盒
line_min_x = min(p1.x, p2.x)
line_max_x = max(p1.x, p2.x)
line_min_y = min(p1.y, p2.y)
line_max_y = max(p1.y, p2.y)
poly_min_x = min(p.x for p in polygon.exterior)
poly_max_x = max(p.x for p in polygon.exterior)
poly_min_y = min(p.y for p in polygon.exterior)
poly_max_y = max(p.y for p in polygon.exterior)
# 包围盒不相交
if line_max_x < poly_min_x or line_min_x > poly_max_x or \
line_max_y < poly_min_y or line_min_y > poly_max_y:
return False
# 检查线段是否与多边形的任何边相交
n = len(polygon.exterior)
for i in range(n):
v1 = polygon.exterior[i]
v2 = polygon.exterior[(i + 1) % n]
if self._segments_intersect(p1, p2, v1, v2):
return True
return False
def _segments_intersect(self, p1: Point, p2: Point,
p3: Point, p4: Point) -> bool:
"""判断两条线段是否相交"""
def orientation(a, b, c):
val = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y)
if abs(val) < 1e-10:
return 0 # 共线
return 1 if val > 0 else 2 # 顺时针/逆时针
def on_segment(a, b, c):
return min(a.x, c.x) <= b.x <= max(a.x, c.x) and \
min(a.y, c.y) <= b.y <= max(a.y, c.y)
o1 = orientation(p1, p2, p3)
o2 = orientation(p1, p2, p4)
o3 = orientation(p3, p4, p1)
o4 = orientation(p3, p4, p2)
# 一般情况
if o1 != o2 and o3 != o4:
return True
# 特殊情况
if o1 == 0 and on_segment(p1, p3, p2):
return True
if o2 == 0 and on_segment(p1, p4, p2):
return True
if o3 == 0 and on_segment(p3, p1, p4):
return True
if o4 == 0 and on_segment(p3, p2, p4):
return True
return False
def viewshed(self, observer: Point, radius: float,
num_rays: int = 360) -> List[Tuple[Point, bool]]:
"""
计算视域 (可视范围)
Args:
observer: 观察点
radius: 视距
num_rays: 射线数量
Returns:
(点, 可见性) 列表
"""
results = []
for i in range(num_rays):
angle = 2 * math.pi * i / num_rays
target = Point(
observer.x + radius * math.cos(angle),
observer.y + radius * math.sin(angle)
)
visible = self.is_visible(observer, target)
results.append((target, visible))
return results
# ============================================================================
# 空间推理引擎
# ============================================================================
class SpatialReasoningEngine:
"""
空间推理引擎
集成多种空间推理功能的综合引擎。
"""
def __init__(self):
self.qualitative_reasoner = QualitativeReasoner()
self.graph = SpatialGraph()
self.visibility_analyzer = VisibilityAnalyzer()
self.astar_planner = None
def build_road_network(self, points: List[Tuple[str, Point]],
connections: List[Tuple[str, str]]) -> None:
"""
构建道路网络
Args:
points: (节点ID, 点) 列表
connections: (节点1, 节点2) 连接列表
"""
for id, point in points:
self.graph.add_node(id, point)
for id1, id2 in connections:
self.graph.add_edge(id1, id2)
self.astar_planner = AStarPlanner(self.graph)
def navigate(self, start: str, goal: str) -> Optional[Dict[str, Any]]:
"""
导航规划
Args:
start: 起始点ID
goal: 目标点ID
Returns:
导航结果字典
"""
if not self.astar_planner:
return None
result = self.astar_planner.plan(start, goal)
if result:
path, distance = result
# 计算方向指示
directions = []
for i in range(len(path) - 1):
from_node = self.graph.nodes[path[i]]
to_node = self.graph.nodes[path[i + 1]]
relation = self.qualitative_reasoner.infer_direction(
from_node.point, to_node.point
)
directions.append({
"from": path[i],
"to": path[i + 1],
"direction": relation.value,
"distance": from_node.point.distance_to(to_node.point)
})
return {
"path": path,
"total_distance": distance,
"num_steps": len(path) - 1,
"directions": directions
}
return None
def query_relation(self, entity1: str, entity2: str,
point1: Point, point2: Point) -> Dict[str, Any]:
"""
查询两个实体之间的空间关系
Args:
entity1: 实体1名称
entity2: 实体2名称
point1: 实体1位置
point2: 实体2位置
Returns:
关系字典
"""
direction = self.qualitative_reasoner.infer_direction(point1, point2)
distance_cat = self.qualitative_reasoner.infer_distance_category(point1, point2)
actual_distance = point1.distance_to(point2)
return {
"entity1": entity1,
"entity2": entity2,
"direction": direction.value,
"distance_category": distance_cat.value,
"actual_distance": actual_distance,
"bearing": math.degrees(math.atan2(
point2.y - point1.y,
point2.x - point1.x
))
}
# ============================================================================
# 主程序
# ========================================================================
def main():
"""主程序 - 演示空间推理的使用"""
print("="*70)
print("空间推理示例演示")
print("="*70)
# ========================================================================
# 1. 定性空间推理
# ========================================================================
print("\n[部分 1] 定性空间推理")
print("-" * 50)
reasoner = QualitativeReasoner()
# 创建几个地标点
landmarks = {
"西湖": Point(120.148, 30.259),
"钱塘江": Point(120.250, 30.200),
"滨江": Point(120.350, 30.220),
"萧山": Point(120.400, 30.150)
}
print("\n地标位置:")
for name, point in landmarks.items():
print(f" {name}: {point}")
# 推断方向关系
print("\n方向关系:")
for i, (name1, point1) in enumerate(list(landmarks.items())[:-1]):
name2 = list(landmarks.keys())[i + 1]
point2 = landmarks[name2]
relation = reasoner.infer_direction(point1, point2)
dist_relation = reasoner.infer_distance_category(point1, point2)
print(f" {name1} -> {name2}:")
print(f" 方向: {relation.value}")
print(f" 距离类别: {dist_relation.value}")
# ========================================================================
# 2. 路径规划
# ========================================================================
print("\n\n[部分 2] 路径规划")
print("-" * 50)
# 创建道路网络
network_points = [
("A", Point(0, 0)),
("B", Point(50, 30)),
("C", Point(100, 50)),
("D", Point(30, 80)),
("E", Point(80, 100)),
("F", Point(120, 120)),
("G", Point(150, 60))
]
connections = [
("A", "B"), ("B", "C"), ("A", "D"),
("B", "D"), ("D", "E"), ("C", "E"),
("E", "F"), ("C", "G"), ("G", "F")
]
graph = SpatialGraph()
for id, point in network_points:
graph.add_node(id, point)
for id1, id2 in connections:
graph.add_edge(id1, id2)
print("\n道路网络:")
print(f" 节点数: {len(graph.nodes)}")
print(f" 边数: {len(graph.edges)}")
# Dijkstra最短路径
print("\nDijkstra最短路径 (A -> F):")
dijkstra_path = graph.shortest_path("A", "F")
if dijkstra_path:
print(f" 路径: {' -> '.join(dijkstra_path)}")
distance = graph.shortest_path_distance("A", "F")
print(f" 总距离: {distance:.2f}")
# A*路径规划
print("\nA*路径规划 (A -> F):")
astar = AStarPlanner(graph)
astar_result = astar.plan("A", "F")
if astar_result:
path, dist = astar_result
print(f" 路径: {' -> '.join(path)}")
print(f" 总距离: {dist:.2f}")
# ========================================================================
# 3. 可见性分析
# ========================================================================
print("\n\n[部分 3] 可见性分析")
print("-" * 50)
# 创建障碍物
obstacle1 = Polygon(exterior=[
Point(60, 40),
Point(80, 40),
Point(80, 70),
Point(60, 70),
Point(60, 40)
])
obstacle2 = Polygon(exterior=[
Point(100, 80),
Point(130, 80),
Point(130, 110),
Point(100, 110),
Point(100, 80)
])
visibility = VisibilityAnalyzer([obstacle1, obstacle2])
print("\n障碍物:")
print(f" 障碍物1: {obstacle1}")
print(f" 障碍物2: {obstacle2}")
# 测试可见性
observer = Point(30, 50)
test_points = [
("目标A", Point(90, 50)), # 被障碍物1遮挡
("目标B", Point(120, 120)), # 被障碍物2遮挡
("目标C", Point(150, 30)), # 可见
("目标D", Point(50, 90)) # 可见
]
print(f"\n从观察点 {observer} 观察:")
for name, target in test_points:
visible = visibility.is_visible(observer, target)
status = "可见" if visible else "不可见"
print(f" {name} {target}: {status}")
# ========================================================================
# 4. 综合推理引擎
# ========================================================================
print("\n\n[部分 4] 综合空间推理引擎")
print("-" * 50)
engine = SpatialReasoningEngine()
# 构建城市路网
city_points = [
("火车站", Point(100, 100)),
("市政府", Point(150, 120)),
("西湖", Point(200, 100)),
("钱江新城", Point(180, 180)),
("滨江", Point(120, 200)),
("萧山机场", Point(250, 250))
]
city_connections = [
("火车站", "市政府"),
("火车站", "滨江"),
("市政府", "西湖"),
("市政府", "钱江新城"),
("滨江", "钱江新城"),
("钱江新城", "萧山机场"),
("西湖", "萧山机场")
]
engine.build_road_network(city_points, city_connections)
print("\n城市路网:")
for name, point in city_points:
print(f" {name}: {point}")
# 导航示例
print("\n导航示例: 从 火车站 到 萧山机场")
nav_result = engine.navigate("火车站", "萧山机场")
if nav_result:
print(f"\n路径规划结果:")
print(f" 路径: {' -> '.join(nav_result['path'])}")
print(f" 总距离: {nav_result['total_distance']:.2f}")
print(f" 步数: {nav_result['num_steps']}")
print(f"\n详细指引:")
for i, step in enumerate(nav_result['directions'], 1):
dir_map = {
"N": "向北", "S": "向南", "E": "向东", "W": "向西",
"NE": "向东北", "NW": "向西北", "SE": "向东南", "SW": "向西南"
}
direction_cn = dir_map.get(step['direction'], step['direction'])
print(f" {i}. 从 {step['from']} {direction_cn} 前往 {step['to']} "
f"(距离: {step['distance']:.1f})")
# 空间关系查询
print("\n空间关系查询:")
relation = engine.query_relation(
"火车站", "西湖",
city_points[0][1], city_points[2][1]
)
print(f" {relation['entity1']} 相对于 {relation['entity2']}:")
print(f" 方向: {relation['direction']}")
print(f" 距离: {relation['actual_distance']:.2f}")
print(f" 方位角: {relation['bearing']:.1f}°")
# ========================================================================
# 5. 多路径比较
# ========================================================================
print("\n\n[部分 5] 多路径比较")
print("-" * 50)
destinations = ["市政府", "西湖", "钱江新城", "滨江", "萧山机场"]
start = "火车站"
print(f"\n{start} 到各目的地的距离:")
results = []
for dest in destinations:
if dest == start:
continue
path_info = engine.navigate(start, dest)
if path_info:
results.append((dest, path_info['total_distance'], path_info['path']))
results.sort(key=lambda x: x[1])
for i, (dest, dist, path) in enumerate(results, 1):
print(f" {i}. {dest:8s}: {dist:6.1f} (路径: {' -> '.join(path)})")
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()