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,962 @@
"""
空间分析助手类 (Spatial Analysis Assistant)
========================================
这是一个完整的空间分析助手实现,展示了如何构建一个基础的
空间智能系统。该系统可以帮助用户进行空间数据处理、分析和可视化。
主要功能:
1. 空间数据加载与管理
2. 空间关系计算 (距离、方位、包含关系等)
3. 空间统计分析
4. 空间插值与预测
5. 多准则决策分析
作者: CC4SI 项目组
日期: 2025-01
"""
import json
import math
from typing import List, Dict, Tuple, Optional, Any, Union
from dataclasses import dataclass, field
from enum import Enum
import random
# ============================================================================
# 数据结构定义
# ============================================================================
class GeometryType(Enum):
"""几何类型枚举"""
POINT = "Point"
LINESTRING = "LineString"
POLYGON = "Polygon"
MULTIPOINT = "MultiPoint"
@dataclass
class Point:
"""点几何类"""
x: float
y: float
z: Optional[float] = None
properties: Dict[str, Any] = field(default_factory=dict)
def __repr__(self) -> str:
if self.z is not None:
return f"Point({self.x:.2f}, {self.y:.2f}, {self.z:.2f})"
return f"Point({self.x:.2f}, {self.y:.2f})"
@dataclass
class BoundingBox:
"""边界框类"""
min_x: float
min_y: float
max_x: float
max_y: float
@property
def width(self) -> float:
"""获取边界框宽度"""
return self.max_x - self.min_x
@property
def height(self) -> float:
"""获取边界框高度"""
return self.max_y - self.min_y
@property
def center(self) -> Point:
"""获取边界框中心点"""
return Point(
(self.min_x + self.max_x) / 2,
(self.min_y + self.max_y) / 2
)
def contains(self, point: Point) -> bool:
"""检查点是否在边界框内"""
return (self.min_x <= point.x <= self.max_x and
self.min_y <= point.y <= self.max_y)
def __repr__(self) -> str:
return f"BoundingBox[{self.min_x:.2f},{self.min_y:.2f} -> {self.max_x:.2f},{self.max_y:.2f}]"
@dataclass
class SpatialFeature:
"""空间要素类"""
id: str
geometry: Union[Point, List[Point]] # 简化: 点或点列表
properties: Dict[str, Any] = field(default_factory=dict)
def __repr__(self) -> str:
return f"SpatialFeature(id={self.id}, geometry={type(self.geometry).__name__})"
# ============================================================================
# 空间分析助手主类
# ============================================================================
class SpatialHelper:
"""
空间分析助手类
这是系统的核心类,提供空间数据处理和分析的主要功能。
"""
def __init__(self, name: str = "空间分析助手", version: str = "1.0.0"):
"""
初始化空间分析助手
Args:
name: 助手名称
version: 版本号
"""
self.name = name
self.version = version
self.features: List[SpatialFeature] = []
self.coordinate_system: str = "EPSG:4326" # 默认 WGS84
self.metadata: Dict[str, Any] = {}
print(f"[{self.name}] v{self.version} 初始化完成")
# ------------------------------------------------------------------------
# 数据加载与管理
# ------------------------------------------------------------------------
def add_feature(self, feature: SpatialFeature) -> bool:
"""
添加空间要素
Args:
feature: 要添加的空间要素
Returns:
是否添加成功
"""
try:
self.features.append(feature)
return True
except Exception as e:
print(f"添加要素失败: {e}")
return False
def add_point(self, x: float, y: float, feature_id: str = None,
properties: Dict[str, Any] = None) -> SpatialFeature:
"""
添加点要素
Args:
x: X坐标
y: Y坐标
feature_id: 要素ID
properties: 属性字典
Returns:
创建的空间要素
"""
if feature_id is None:
feature_id = f"point_{len(self.features)}"
point = Point(x, y)
feature = SpatialFeature(
id=feature_id,
geometry=point,
properties=properties or {}
)
self.add_feature(feature)
return feature
def load_from_geojson(self, geojson_str: str) -> int:
"""
从 GeoJSON 字符串加载数据
Args:
geojson_str: GeoJSON 格式字符串
Returns:
加载的要素数量
"""
try:
data = json.loads(geojson_str)
count = 0
if data.get("type") == "FeatureCollection":
for feature_data in data.get("features", []):
feature = self._parse_geojson_feature(feature_data)
if feature:
self.add_feature(feature)
count += 1
print(f"从 GeoJSON 加载了 {count} 个要素")
return count
except json.JSONDecodeError as e:
print(f"GeoJSON 解析失败: {e}")
return 0
def _parse_geojson_feature(self, feature_data: Dict) -> Optional[SpatialFeature]:
"""解析 GeoJSON 要素"""
try:
feature_id = feature_data.get("id", f"feature_{len(self.features)}")
properties = feature_data.get("properties", {})
geometry = feature_data.get("geometry", {})
geom_type = geometry.get("type")
if geom_type == "Point":
coordinates = geometry.get("coordinates", [])
point = Point(coordinates[0], coordinates[1])
return SpatialFeature(id=feature_id, geometry=point, properties=properties)
return None
except Exception:
return None
def get_feature_by_id(self, feature_id: str) -> Optional[SpatialFeature]:
"""根据ID获取要素"""
for feature in self.features:
if feature.id == feature_id:
return feature
return None
def get_feature_count(self) -> int:
"""获取要素数量"""
return len(self.features)
def clear_features(self) -> None:
"""清空所有要素"""
self.features.clear()
print("已清空所有要素")
# ------------------------------------------------------------------------
# 空间关系计算
# ------------------------------------------------------------------------
@staticmethod
def calculate_distance(point1: Point, point2: Point) -> float:
"""
计算两点间的欧氏距离
Args:
point1: 第一个点
point2: 第二个点
Returns:
距离值
"""
dx = point2.x - point1.x
dy = point2.y - point1.y
return math.sqrt(dx * dx + dy * dy)
@staticmethod
def calculate_bearing(point1: Point, point2: Point) -> float:
"""
计算从 point1 到 point2 的方位角 (度)
Args:
point1: 起始点
point2: 目标点
Returns:
方位角 (0-360度)
"""
dx = point2.x - point1.x
dy = point2.y - point1.y
radians = math.atan2(dy, dx)
degrees = math.degrees(radians)
return (degrees + 360) % 360
@staticmethod
def calculate_midpoint(point1: Point, point2: Point) -> Point:
"""
计算两点间的中点
Args:
point1: 第一个点
point2: 第二个点
Returns:
中点
"""
return Point(
(point1.x + point2.x) / 2,
(point1.y + point2.y) / 2
)
def find_nearest_neighbor(self, target_point: Point,
max_distance: float = float('inf')) -> Optional[Tuple[SpatialFeature, float]]:
"""
查找最近邻要素
Args:
target_point: 目标点
max_distance: 最大搜索距离
Returns:
(最近的要素, 距离) 或 None
"""
nearest_feature = None
min_distance = float('inf')
for feature in self.features:
if isinstance(feature.geometry, Point):
dist = self.calculate_distance(target_point, feature.geometry)
if dist < min_distance and dist <= max_distance:
min_distance = dist
nearest_feature = feature
if nearest_feature:
return nearest_feature, min_distance
return None
def find_neighbors_within_distance(self, target_point: Point,
distance: float) -> List[Tuple[SpatialFeature, float]]:
"""
查找指定距离内的所有要素
Args:
target_point: 目标点
distance: 搜索半径
Returns:
(要素, 距离) 列表,按距离排序
"""
neighbors = []
for feature in self.features:
if isinstance(feature.geometry, Point):
dist = self.calculate_distance(target_point, feature.geometry)
if dist <= distance:
neighbors.append((feature, dist))
neighbors.sort(key=lambda x: x[1])
return neighbors
def calculate_bounding_box(self) -> Optional[BoundingBox]:
"""
计算所有要素的边界框
Returns:
边界框对象,如果没有要素则返回 None
"""
if not self.features:
return None
points = [f.geometry for f in self.features if isinstance(f.geometry, Point)]
if not points:
return None
min_x = min(p.x for p in points)
max_x = max(p.x for p in points)
min_y = min(p.y for p in points)
max_y = max(p.y for p in points)
return BoundingBox(min_x, min_y, max_x, max_y)
# ------------------------------------------------------------------------
# 空间统计分析
# ------------------------------------------------------------------------
def calculate_centroid(self) -> Optional[Point]:
"""
计算所有点要素的质心
Returns:
质心点
"""
points = [f.geometry for f in self.features if isinstance(f.geometry, Point)]
if not points:
return None
avg_x = sum(p.x for p in points) / len(points)
avg_y = sum(p.y for p in points) / len(points)
return Point(avg_x, avg_y)
def calculate_mean_center(self, weight_field: str = None) -> Optional[Point]:
"""
计算加权或未加权的平均中心
Args:
weight_field: 权重字段名
Returns:
平均中心点
"""
points = []
weights = []
for feature in self.features:
if isinstance(feature.geometry, Point):
points.append(feature.geometry)
if weight_field:
weights.append(feature.properties.get(weight_field, 1.0))
else:
weights.append(1.0)
if not points:
return None
total_weight = sum(weights)
avg_x = sum(p.x * w for p, w in zip(points, weights)) / total_weight
avg_y = sum(p.y * w for p, w in zip(points, weights)) / total_weight
return Point(avg_x, avg_y)
def calculate_standard_distance(self) -> Optional[float]:
"""
计算标准距离 (标准差圆)
Returns:
标准距离值
"""
centroid = self.calculate_centroid()
if not centroid:
return None
points = [f.geometry for f in self.features if isinstance(f.geometry, Point)]
if not points:
return None
n = len(points)
squared_distances = [(p.x - centroid.x)**2 + (p.y - centroid.y)**2 for p in points]
return math.sqrt(sum(squared_distances) / n)
def calculate_spatial_autocorrelation(self, field: str) -> Optional[float]:
"""
计算 Moran's I 空间自相关指数
Args:
field: 要分析的属性字段
Returns:
Moran's I 值
"""
# 简化实现: 使用距离权重
points_data = []
for feature in self.features:
if isinstance(feature.geometry, Point) and field in feature.properties:
points_data.append((feature.geometry, feature.properties[field]))
n = len(points_data)
if n < 2:
return None
mean_value = sum(v for _, v in points_data) / n
# 计算权重矩阵 (距离倒数)
weights = {}
total_weight = 0
for i, (p1, v1) in enumerate(points_data):
for j, (p2, v2) in enumerate(points_data):
if i != j:
dist = self.calculate_distance(p1, p2)
w = 1 / (dist + 0.001) # 避免除零
weights[(i, j)] = w
total_weight += w
# 计算 Moran's I
numerator = 0
denominator = 0
for i, (p1, v1) in enumerate(points_data):
for j, (p2, v2) in enumerate(points_data):
if i != j:
w = weights.get((i, j), 0)
numerator += w * (v1 - mean_value) * (v2 - mean_value)
denominator += (v1 - mean_value) ** 2
if denominator == 0:
return None
morans_i = (n / total_weight) * (numerator / denominator)
return morans_i
# ------------------------------------------------------------------------
# 空间插值与预测
# ------------------------------------------------------------------------
def inverse_distance_weighting(self, target_point: Point, power: float = 2.0,
field: str = "value", max_distance: float = None) -> Optional[float]:
"""
反距离加权插值 (IDW)
Args:
target_point: 目标点
power: 距离幂次
field: 插值字段
max_distance: 最大搜索距离
Returns:
插值结果
"""
points_data = []
for feature in self.features:
if isinstance(feature.geometry, Point) and field in feature.properties:
points_data.append((feature.geometry, feature.properties[field]))
if not points_data:
return None
numerator = 0.0
denominator = 0.0
for point, value in points_data:
dist = self.calculate_distance(target_point, point)
if max_distance and dist > max_distance:
continue
if dist < 1e-10: # 几乎重合
return value
weight = 1.0 / (dist ** power)
numerator += weight * value
denominator += weight
if denominator == 0:
return None
return numerator / denominator
def simple_trend_prediction(self, field: str, target_x: float,
target_y: float) -> Optional[float]:
"""
基于简单趋势的预测 (线性回归)
Args:
field: 预测字段
target_x: 目标X坐标
target_y: 目标Y坐标
Returns:
预测值
"""
points_data = []
for feature in self.features:
if isinstance(feature.geometry, Point) and field in feature.properties:
points_data.append({
'x': feature.geometry.x,
'y': feature.geometry.y,
'z': feature.properties[field]
})
if len(points_data) < 3:
return None
# 简单的线性趋势: z = a + b*x + c*y
n = len(points_data)
sum_x = sum(p['x'] for p in points_data)
sum_y = sum(p['y'] for p in points_data)
sum_z = sum(p['z'] for p in points_data)
sum_xx = sum(p['x']**2 for p in points_data)
sum_yy = sum(p['y']**2 for p in points_data)
sum_xy = sum(p['x'] * p['y'] for p in points_data)
sum_xz = sum(p['x'] * p['z'] for p in points_data)
sum_yz = sum(p['y'] * p['z'] for p in points_data)
# 简化: 只使用x方向趋势
try:
# z = a + b*x
b = (n * sum_xz - sum_x * sum_z) / (n * sum_xx - sum_x**2)
a = (sum_z - b * sum_x) / n
return a + b * target_x
except ZeroDivisionError:
return None
# ------------------------------------------------------------------------
# 多准则决策分析 (MCDA)
# ------------------------------------------------------------------------
def weighted_sum_model(self, criteria: List[str], weights: List[float],
feature_ids: List[str] = None) -> List[Tuple[SpatialFeature, float]]:
"""
加权求和模型 (WSM)
Args:
criteria: 评价准则列表
weights: 各准则权重
feature_ids: 参与评价的要素ID列表
Returns:
(要素, 得分) 列表,按得分降序排列
"""
if len(criteria) != len(weights):
print("错误: 准则数量与权重数量不匹配")
return []
if abs(sum(weights) - 1.0) > 0.001:
print(f"警告: 权重总和为 {sum(weights)}, 建议归一化为 1.0")
# 确定评价范围
features_to_eval = self.features
if feature_ids:
features_to_eval = [f for f in self.features if f.id in feature_ids]
results = []
# 归一化参数
min_max = {}
for criterion in criteria:
values = []
for f in features_to_eval:
if criterion in f.properties:
values.append(f.properties[criterion])
if values:
min_max[criterion] = (min(values), max(values))
for feature in features_to_eval:
score = 0.0
valid = True
for criterion, weight in zip(criteria, weights):
if criterion not in feature.properties:
valid = False
break
value = feature.properties[criterion]
cmin, cmax = min_max.get(criterion, (0, 1))
# 归一化 (假设值越大越好)
if cmax - cmin > 0:
normalized = (value - cmin) / (cmax - cmin)
else:
normalized = 0.5
score += weight * normalized
if valid:
results.append((feature, score))
results.sort(key=lambda x: x[1], reverse=True)
return results
def weighted_product_model(self, criteria: List[str], weights: List[float],
feature_ids: List[str] = None) -> List[Tuple[SpatialFeature, float]]:
"""
加权乘积模型 (WPM)
Args:
criteria: 评价准则列表
weights: 各准则权重
feature_ids: 参与评价的要素ID列表
Returns:
(要素, 得分) 列表,按得分降序排列
"""
if len(criteria) != len(weights):
print("错误: 准则数量与权重数量不匹配")
return []
features_to_eval = self.features
if feature_ids:
features_to_eval = [f for f in self.features if f.id in feature_ids]
results = []
# 归一化参数
min_max = {}
for criterion in criteria:
values = []
for f in features_to_eval:
if criterion in f.properties:
values.append(f.properties[criterion])
if values:
min_max[criterion] = (min(values), max(values))
for feature in features_to_eval:
product = 1.0
valid = True
for criterion, weight in zip(criteria, weights):
if criterion not in feature.properties:
valid = False
break
value = feature.properties[criterion]
cmin, cmax = min_max.get(criterion, (0, 1))
if cmax - cmin > 0:
normalized = (value - cmin) / (cmax - cmin)
else:
normalized = 1.0
product *= normalized ** weight
if valid:
results.append((feature, product))
results.sort(key=lambda x: x[1], reverse=True)
return results
def topsis(self, criteria: List[str], weights: List[float],
benefit_criteria: List[bool] = None) -> List[Tuple[SpatialFeature, float]]:
"""
TOPSIS (逼近理想解排序法)
Args:
criteria: 评价准则列表
weights: 各准则权重
benefit_criteria: 是否为效益型准则 (True=越大越好, False=越小越好)
Returns:
(要素, 相对贴近度) 列表,按贴近度降序排列
"""
if benefit_criteria is None:
benefit_criteria = [True] * len(criteria)
features_to_eval = [f for f in self.features
if all(c in f.properties for c in criteria)]
if len(features_to_eval) == 0:
return []
n = len(features_to_eval)
m = len(criteria)
# 构建决策矩阵
decision_matrix = []
for feature in features_to_eval:
row = [feature.properties[c] for c in criteria]
decision_matrix.append(row)
# 归一化决策矩阵
normalized_matrix = []
for j in range(m):
column = [decision_matrix[i][j] for i in range(n)]
norm = math.sqrt(sum(x**2 for x in column))
for i in range(n):
if j == 0:
normalized_matrix.append([])
normalized_matrix[i].append(decision_matrix[i][j] / norm if norm > 0 else 0)
# 加权归一化矩阵
weighted_matrix = []
for i in range(n):
weighted_matrix.append([normalized_matrix[i][j] * weights[j] for j in range(m)])
# 确定理想解和负理想解
ideal_positive = []
ideal_negative = []
for j in range(m):
column = [weighted_matrix[i][j] for i in range(n)]
if benefit_criteria[j]:
ideal_positive.append(max(column))
ideal_negative.append(min(column))
else:
ideal_positive.append(min(column))
ideal_negative.append(max(column))
# 计算距离和相对贴近度
results = []
for i, feature in enumerate(features_to_eval):
dist_positive = math.sqrt(
sum((weighted_matrix[i][j] - ideal_positive[j])**2 for j in range(m))
)
dist_negative = math.sqrt(
sum((weighted_matrix[i][j] - ideal_negative[j])**2 for j in range(m))
)
closeness = dist_negative / (dist_positive + dist_negative) if (dist_positive + dist_negative) > 0 else 0
results.append((feature, closeness))
results.sort(key=lambda x: x[1], reverse=True)
return results
# ------------------------------------------------------------------------
# 缓冲区分析
# ------------------------------------------------------------------------
def create_buffer_analysis(self, feature_id: str, buffer_distance: float) -> Dict[str, Any]:
"""
缓冲区分析
Args:
feature_id: 中心要素ID
buffer_distance: 缓冲距离
Returns:
分析结果字典
"""
target_feature = self.get_feature_by_id(feature_id)
if not target_feature or not isinstance(target_feature.geometry, Point):
return {"error": "找不到指定的点要素"}
center = target_feature.geometry
# 查找缓冲区内的要素
features_in_buffer = self.find_neighbors_within_distance(center, buffer_distance)
# 计算统计信息
values_in_buffer = [f.properties for f, _ in features_in_buffer]
return {
"center_feature": feature_id,
"buffer_distance": buffer_distance,
"count": len(features_in_buffer),
"features": [(f.id, dist) for f, dist in features_in_buffer],
"statistics": {
"avg_distance": sum(dist for _, dist in features_in_buffer) / len(features_in_buffer) if features_in_buffer else 0
}
}
# ------------------------------------------------------------------------
# 可视化辅助 (文本形式)
# ------------------------------------------------------------------------
def print_summary(self) -> None:
"""打印数据摘要"""
print(f"\n{'='*50}")
print(f"空间分析助手摘要: {self.name} v{self.version}")
print(f"{'='*50}")
print(f"要素数量: {len(self.features)}")
print(f"坐标系: {self.coordinate_system}")
bbox = self.calculate_bounding_box()
if bbox:
print(f"边界范围: {bbox}")
centroid = self.calculate_centroid()
if centroid:
print(f"质心位置: {centroid}")
print(f"{'='*50}\n")
def print_features(self, limit: int = 10) -> None:
"""打印要素列表"""
print(f"\n要素列表 (显示前 {min(limit, len(self.features))} 个):")
print("-" * 60)
for i, feature in enumerate(self.features[:limit]):
if isinstance(feature.geometry, Point):
print(f"{i+1}. ID: {feature.id:15s} 位置: {feature.geometry} 属性: {feature.properties}")
if len(self.features) > limit:
print(f"... 还有 {len(self.features) - limit} 个要素")
print("-" * 60 + "\n")
# ============================================================================
# 辅助函数
# ============================================================================
def create_sample_data(helper: SpatialHelper, n_points: int = 20) -> None:
"""创建示例数据"""
print(f"生成 {n_points} 个随机样本点...")
random.seed(42) # 可重现的随机数
# 生成随机点
for i in range(n_points):
x = random.uniform(0, 100)
y = random.uniform(0, 100)
value = random.uniform(0, 100)
population = random.randint(100, 10000)
accessibility = random.uniform(0.3, 0.95)
helper.add_point(
x=x,
y=y,
feature_id=f"point_{i:03d}",
properties={
"value": value,
"population": population,
"accessibility": accessibility,
"name": f"位置_{i+1}"
}
)
print(f"已生成 {helper.get_feature_count()} 个样本点")
# ============================================================================
# 主程序
# ============================================================================
def main():
"""主程序 - 演示空间分析助手的使用"""
print("="*70)
print("空间分析助手 - 完整示例演示")
print("="*70)
# 1. 创建助手实例
print("\n[步骤 1] 创建空间分析助手")
helper = SpatialHelper(name="城市空间分析助手", version="1.0.0")
# 2. 添加示例数据
print("\n[步骤 2] 添加示例数据")
create_sample_data(helper, n_points=15)
# 3. 打印数据摘要
print("\n[步骤 3] 数据摘要")
helper.print_summary()
helper.print_features()
# 4. 空间关系计算
print("\n[步骤 4] 空间关系计算")
print("-" * 50)
test_point = Point(50, 50)
print(f"测试点: {test_point}")
nearest = helper.find_nearest_neighbor(test_point)
if nearest:
feature, dist = nearest
print(f"最近邻: {feature.id}, 距离: {dist:.2f}")
neighbors = helper.find_neighbors_within_distance(test_point, 25)
print(f"半径25内的邻居数量: {len(neighbors)}")
# 5. 空间统计分析
print("\n[步骤 5] 空间统计分析")
print("-" * 50)
centroid = helper.calculate_centroid()
print(f"质心: {centroid}")
std_dist = helper.calculate_standard_distance()
print(f"标准距离: {std_dist:.2f}")
# 6. 多准则决策分析
print("\n[步骤 6] 多准则决策分析 (TOPSIS)")
print("-" * 50)
criteria = ["accessibility", "population"]
weights = [0.6, 0.4] # 可达性权重更高
results = helper.topsis(criteria, weights)
print("选址优先级排序 (基于可达性和人口):")
for i, (feature, score) in enumerate(results[:5]):
print(f" {i+1}. {feature.properties.get('name', feature.id)}: 得分={score:.4f}")
# 7. 空间插值
print("\n[步骤 7] 空间插值预测")
print("-" * 50)
predict_point = Point(45, 55)
predicted = helper.inverse_distance_weighting(predict_point, field="value")
print(f"{predict_point} 处的插值预测: {predicted:.2f}")
# 8. 缓冲区分析
print("\n[步骤 8] 缓冲区分析")
print("-" * 50)
buffer_result = helper.create_buffer_analysis("point_000", 30)
print(f"以 point_000 为中心,半径30的缓冲区:")
print(f" 包含要素数: {buffer_result['count']}")
print(f" 平均距离: {buffer_result['statistics']['avg_distance']:.2f}")
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()
@@ -0,0 +1,827 @@
"""
反馈与学习示例 (Feedback and Learning Example)
=============================================
本示例展示如何在空间智能系统中实现反馈机制和学习能力。
反馈和学习使系统能够从经验中改进,提高决策质量。
核心概念:
1. 反馈循环 - 收集用户/系统的反馈
2. 性能评估 - 评估决策效果
3. 参数调整 - 根据反馈调整系统参数
4. 经验存储 - 保存和检索历史经验
5. 迁移学习 - 将知识应用到新场景
应用场景:
- 自适应决策权重调整
- 模型参数优化
- 用户偏好学习
- 决策效果跟踪
作者: CC4SI 项目组
"""
import math
import json
from typing import List, Dict, Tuple, Optional, Any, Callable
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime
import random
# ============================================================================
# 反馈类型定义
# ============================================================================
class FeedbackType(Enum):
"""反馈类型枚举"""
EXPLICIT = "explicit" # 显式反馈 (用户评分/评价)
IMPLICIT = "implicit" # 隐式反馈 (行为数据)
OUTCOME = "outcome" # 结果反馈 (实际结果)
CORRECTION = "correction" # 纠正反馈 (修正建议)
RANKING = "ranking" # 排序反馈 (偏好排序)
class FeedbackSource(Enum):
"""反馈来源枚举"""
HUMAN_EXPERT = "human_expert" # 人类专家
SYSTEM_AUTO = "system_auto" # 系统自动
SENSOR_DATA = "sensor_data" # 传感器数据
CROWDSOURCING = "crowdsourcing" # 众包
PEER_REVIEW = "peer_review" # 同行评审
@dataclass
class Feedback:
"""
反馈数据结构
表示一次具体的反馈事件。
"""
feedback_id: str
feedback_type: FeedbackType
source: FeedbackSource
target_decision_id: str
value: float # 反馈值 (如评分)
content: Optional[str] = None # 反馈内容
metadata: Dict[str, Any] = field(default_factory=dict)
timestamp: datetime = field(default_factory=datetime.now)
def __repr__(self) -> str:
return f"Feedback({self.feedback_type.value}, value={self.value:.2f})"
# ============================================================================
# 决策记录
# ============================================================================
@dataclass
class Decision:
"""
决策记录
保存系统做出的一次决策的完整信息。
"""
decision_id: str
context: Dict[str, Any] # 决策上下文
alternatives: List[Dict[str, Any]] # 可选方案
selected_alternative: int # 选择的方案索引
model_version: str # 使用的模型版本
parameters: Dict[str, Any] # 决策参数
predicted_outcome: Optional[float] = None # 预测结果
actual_outcome: Optional[float] = None # 实际结果
feedback_list: List[Feedback] = field(default_factory=list)
timestamp: datetime = field(default_factory=datetime.now)
def add_feedback(self, feedback: Feedback) -> None:
"""添加反馈"""
self.feedback_list.append(feedback)
def get_average_feedback(self) -> float:
"""获取平均反馈分数"""
if not self.feedback_list:
return 0.0
return sum(f.value for f in self.feedback_list) / len(self.feedback_list)
def get_outcome_error(self) -> Optional[float]:
"""获取预测误差"""
if self.predicted_outcome is not None and self.actual_outcome is not None:
return abs(self.predicted_outcome - self.actual_outcome)
return None
def calculate_regret(self) -> float:
"""
计算后悔值
后悔值 = 最优选择的结果 - 实际选择的结果
"""
if not self.alternatives or self.actual_outcome is None:
return 0.0
# 假设alternatives中存储了各个选项的实际结果
best_outcome = max(
alt.get("actual_outcome", self.actual_outcome)
for alt in self.alternatives
)
return best_outcome - self.actual_outcome
# ============================================================================
# 经验存储
# ============================================================================
class ExperienceStore:
"""
经验存储
存储和检索历史决策经验,用于学习和改进。
"""
def __init__(self, capacity: int = 1000):
"""
初始化经验存储
Args:
capacity: 最大存储容量
"""
self.capacity = capacity
self.decisions: Dict[str, Decision] = {}
self.decision_list: List[str] = [] # 按时间顺序的ID列表
def add_decision(self, decision: Decision) -> None:
"""添加决策记录"""
self.decisions[decision.decision_id] = decision
self.decision_list.append(decision.decision_id)
# 超过容量时删除最旧的
if len(self.decision_list) > self.capacity:
oldest_id = self.decision_list.pop(0)
del self.decisions[oldest_id]
def get_decision(self, decision_id: str) -> Optional[Decision]:
"""获取决策记录"""
return self.decisions.get(decision_id)
def get_recent_decisions(self, n: int = 10) -> List[Decision]:
"""获取最近的n条决策"""
recent_ids = self.decision_list[-n:]
return [self.decisions[id] for id in recent_ids]
def find_similar_decisions(self, context: Dict[str, Any],
threshold: float = 0.8) -> List[Decision]:
"""
查找相似上下文的决策
Args:
context: 目标上下文
threshold: 相似度阈值
Returns:
相似决策列表
"""
similar = []
for decision in self.decisions.values():
similarity = self._calculate_similarity(context, decision.context)
if similarity >= threshold:
similar.append((decision, similarity))
similar.sort(key=lambda x: x[1], reverse=True)
return [d for d, _ in similar]
def _calculate_similarity(self, ctx1: Dict[str, Any],
ctx2: Dict[str, Any]) -> float:
"""计算上下文相似度 (简化版本)"""
# 简化: 使用键的交集比例
keys1 = set(ctx1.keys())
keys2 = set(ctx2.keys())
intersection = keys1 & keys2
union = keys1 | keys2
if not union:
return 0.0
# 值相似度
value_similarity = 0.0
count = 0
for key in intersection:
v1 = ctx1.get(key)
v2 = ctx2.get(key)
if isinstance(v1, (int, float)) and isinstance(v2, (int, float)):
# 归一化差异
max_val = max(abs(v1), abs(v2), 1)
diff = abs(v1 - v2) / max_val
value_similarity += (1 - diff)
count += 1
if count > 0:
value_similarity /= count
# 组合相似度
key_similarity = len(intersection) / len(union)
return 0.3 * key_similarity + 0.7 * value_similarity
def get_statistics(self) -> Dict[str, Any]:
"""获取统计信息"""
total = len(self.decisions)
if total == 0:
return {"total_decisions": 0}
with_feedback = sum(1 for d in self.decisions.values() if d.feedback_list)
with_outcome = sum(1 for d in self.decisions.values()
if d.actual_outcome is not None)
avg_feedback = sum(d.get_average_feedback()
for d in self.decisions.values()
if d.feedback_list) / max(with_feedback, 1)
return {
"total_decisions": total,
"decisions_with_feedback": with_feedback,
"decisions_with_outcome": with_outcome,
"average_feedback_score": avg_feedback
}
# ============================================================================
# 学习器接口
# ============================================================================
class Learner(ABC):
"""学习器抽象基类"""
def __init__(self, name: str = ""):
self.name = name
@abstractmethod
def learn_from_feedback(self, decision: Decision, feedback: Feedback) -> None:
"""从反馈中学习"""
pass
@abstractmethod
def learn_from_outcome(self, decision: Decision) -> None:
"""从结果中学习"""
pass
@abstractmethod
def get_parameters(self) -> Dict[str, Any]:
"""获取当前参数"""
pass
@abstractmethod
def update_parameters(self, params: Dict[str, Any]) -> None:
"""更新参数"""
pass
# ============================================================================
# 权重学习器
# ============================================================================
class WeightLearner(Learner):
"""
权重学习器
通过反馈学习多准则决策的权重。
"""
def __init__(self, initial_weights: List[float],
learning_rate: float = 0.1,
min_weight: float = 0.05,
max_weight: float = 0.5):
"""
初始化权重学习器
Args:
initial_weights: 初始权重列表
learning_rate: 学习率
min_weight: 最小权重
max_weight: 最大权重
"""
super().__init__("WeightLearner")
self.weights = initial_weights.copy()
self.learning_rate = learning_rate
self.min_weight = min_weight
self.max_weight = max_weight
self.update_count = 0
def learn_from_feedback(self, decision: Decision, feedback: Feedback) -> None:
"""
从反馈中学习权重
使用梯度下降法调整权重:
- 如果反馈为正,增加选中选项的优势准则权重
- 如果反馈为负,减少选中选项的优势准则权重
"""
if not decision.alternatives or decision.selected_alternative >= len(decision.alternatives):
return
selected = decision.alternatives[decision.selected_alternative]
# 计算调整方向
feedback_normalized = (feedback.value - 0.5) * 2 # 转换到 [-1, 1]
# 获取准则值 (假设存储在criteria字段)
criteria_values = selected.get("criteria", [])
if len(criteria_values) != len(self.weights):
return
# 计算梯度
# 简化: 增加高值准则的权重 (如果反馈为正)
max_value = max(criteria_values) if criteria_values else 1
gradients = []
for i, value in enumerate(criteria_values):
# 归一化值
norm_value = value / max_value if max_value > 0 else 0
# 梯度: 高值准则应该有更大权重
gradient = (norm_value - 0.5) * feedback_normalized
gradients.append(gradient)
# 更新权重
for i, gradient in enumerate(gradients):
self.weights[i] += self.learning_rate * gradient
# 归一化权重
self._normalize_weights()
self.update_count += 1
def learn_from_outcome(self, decision: Decision) -> None:
"""
从结果中学习
如果实际结果好于预期,增加选中策略的权重
"""
if decision.predicted_outcome is None or decision.actual_outcome is None:
return
# 计算结果误差
error = decision.actual_outcome - decision.predicted_outcome
# 归一化误差
error_normalized = math.tanh(error / 100) # 假设100为合理的误差范围
# 根据误差调整权重
feedback = Feedback(
feedback_id=f"outcome_{decision.decision_id}",
feedback_type=FeedbackType.OUTCOME,
source=FeedbackSource.SYSTEM_AUTO,
target_decision_id=decision.decision_id,
value=0.5 + error_normalized * 0.25 # 转换到合理范围
)
self.learn_from_feedback(decision, feedback)
def _normalize_weights(self) -> None:
"""归一化权重并限制范围"""
# 限制范围
self.weights = [
max(self.min_weight, min(self.max_weight, w))
for w in self.weights
]
# 归一化使和为1
total = sum(self.weights)
self.weights = [w / total for w in self.weights]
def get_parameters(self) -> Dict[str, Any]:
return {
"weights": self.weights,
"learning_rate": self.learning_rate,
"update_count": self.update_count
}
def update_parameters(self, params: Dict[str, Any]) -> None:
if "weights" in params:
self.weights = params["weights"].copy()
if "learning_rate" in params:
self.learning_rate = params["learning_rate"]
# ============================================================================
# 自适应决策系统
# ============================================================================
class AdaptiveDecisionSystem:
"""
自适应决策系统
结合反馈和学习的智能决策系统。
"""
def __init__(self, criteria: List[str],
initial_weights: List[float] = None):
"""
初始化自适应决策系统
Args:
criteria: 决策准则列表
initial_weights: 初始权重
"""
self.criteria = criteria
self.n_criteria = len(criteria)
if initial_weights is None:
# 均匀初始权重
initial_weights = [1.0 / self.n_criteria] * self.n_criteria
# 归一化权重
total = sum(initial_weights)
self.weights = [w / total for w in initial_weights]
# 创建学习器
self.learner = WeightLearner(self.weights)
# 创建经验存储
self.experience_store = ExperienceStore()
# 决策计数器
self.decision_counter = 0
print(f"[自适应决策系统] 初始化完成")
print(f" 准则: {self.criteria}")
print(f" 初始权重: {[f'{w:.3f}' for w in self.weights]}")
def make_decision(self, alternatives: List[Dict[str, float]],
context: Dict[str, Any] = None) -> Tuple[int, Dict[str, Any]]:
"""
做出决策
Args:
alternatives: 备选方案列表,每个方案包含各准则的值
context: 决策上下文
Returns:
(选中方案索引, 决策信息)
"""
if not alternatives:
raise ValueError("没有备选方案")
# 计算每个方案的综合得分
scores = []
for alt in alternatives:
score = self._calculate_score(alt)
scores.append(score)
# 选择得分最高的
selected_idx = max(range(len(scores)), key=lambda i: scores[i])
# 创建决策记录
decision_id = f"decision_{self.decision_counter}"
self.decision_counter += 1
decision = Decision(
decision_id=decision_id,
context=context or {},
alternatives=[
{"criteria": alt, "score": score}
for alt, score in zip(alternatives, scores)
],
selected_alternative=selected_idx,
model_version="1.0",
parameters=self.get_parameters()
)
decision_info = {
"decision_id": decision_id,
"selected_index": selected_idx,
"selected_alternative": alternatives[selected_idx],
"score": scores[selected_idx],
"all_scores": scores,
"weights": self.weights.copy()
}
# 存储决策
self.experience_store.add_decision(decision)
return selected_idx, decision_info
def _calculate_score(self, alternative: Dict[str, float]) -> float:
"""
计算方案的综合得分
使用加权求和模型
"""
score = 0.0
for i, criterion in enumerate(self.criteria):
if criterion in alternative:
score += self.weights[i] * alternative[criterion]
return score
def provide_feedback(self, decision_id: str, feedback_value: float,
feedback_type: FeedbackType = FeedbackType.EXPLICIT,
source: FeedbackSource = FeedbackSource.HUMAN_EXPERT,
content: str = None) -> None:
"""
为决策提供反馈
Args:
decision_id: 决策ID
feedback_value: 反馈值 (通常在0-1范围)
feedback_type: 反馈类型
source: 反馈来源
content: 反馈内容
"""
decision = self.experience_store.get_decision(decision_id)
if not decision:
print(f"警告: 找不到决策 {decision_id}")
return
# 创建反馈
feedback = Feedback(
feedback_id=f"fb_{decision_id}_{len(decision.feedback_list)}",
feedback_type=feedback_type,
source=source,
target_decision_id=decision_id,
value=feedback_value,
content=content
)
# 添加到决策记录
decision.add_feedback(feedback)
# 从反馈中学习
self.learner.learn_from_feedback(decision, feedback)
# 更新系统权重
self.weights = self.learner.weights.copy()
print(f"[反馈] 收到反馈: {feedback_value:.2f}")
print(f"[学习] 更新后权重: {[f'{w:.3f}' for w in self.weights]}")
def report_outcome(self, decision_id: str, actual_outcome: float) -> None:
"""
报告实际结果
Args:
decision_id: 决策ID
actual_outcome: 实际结果值
"""
decision = self.experience_store.get_decision(decision_id)
if not decision:
print(f"警告: 找不到决策 {decision_id}")
return
decision.actual_outcome = actual_outcome
# 从结果中学习
self.learner.learn_from_outcome(decision)
# 更新系统权重
self.weights = self.learner.weights.copy()
print(f"[结果] 决策 {decision_id} 实际结果: {actual_outcome:.2f}")
def get_parameters(self) -> Dict[str, Any]:
"""获取当前系统参数"""
return {
"weights": self.weights.copy(),
"criteria": self.criteria.copy()
}
def set_parameters(self, params: Dict[str, Any]) -> None:
"""设置系统参数"""
if "weights" in params:
self.weights = params["weights"].copy()
def get_performance_summary(self) -> Dict[str, Any]:
"""获取性能摘要"""
stats = self.experience_store.get_statistics()
# 计算平均反馈分数
decisions = list(self.experience_store.decisions.values())
if decisions:
avg_feedback = sum(d.get_average_feedback()
for d in decisions if d.feedback_list)
feedback_count = sum(1 for d in decisions if d.feedback_list)
avg_feedback = avg_feedback / feedback_count if feedback_count > 0 else None
else:
avg_feedback = None
# 计算平均后悔值
regrets = [d.calculate_regret() for d in decisions
if d.actual_outcome is not None]
avg_regret = sum(regrets) / len(regrets) if regrets else None
return {
"total_decisions": stats["total_decisions"],
"decisions_with_feedback": stats["decisions_with_feedback"],
"average_feedback_score": avg_feedback,
"average_regret": avg_regret,
"current_weights": self.weights.copy()
}
def print_summary(self) -> None:
"""打印系统摘要"""
print("\n" + "="*60)
print("自适应决策系统摘要")
print("="*60)
print("\n决策准则:")
for i, criterion in enumerate(self.criteria):
print(f" {i+1}. {criterion:15s} 权重: {self.weights[i]:.4f}")
perf = self.get_performance_summary()
print(f"\n性能统计:")
print(f" 总决策数: {perf['total_decisions']}")
print(f" 有反馈的决策: {perf['decisions_with_feedback']}")
if perf['average_feedback_score'] is not None:
print(f" 平均反馈分数: {perf['average_feedback_score']:.3f}")
if perf['average_regret'] is not None:
print(f" 平均后悔值: {perf['average_regret']:.3f}")
print("="*60 + "\n")
# ============================================================================
# 主程序
# ============================================================================
def main():
"""主程序 - 演示反馈与学习的使用"""
print("="*70)
print("反馈与学习示例演示")
print("="*70)
# ========================================================================
# 1. 创建自适应决策系统
# ========================================================================
print("\n[步骤 1] 创建自适应决策系统")
print("-" * 50)
criteria = ["经济效益", "环境影响", "社会影响", "技术可行性"]
initial_weights = [0.4, 0.3, 0.2, 0.1] # 偏重经济效益
system = AdaptiveDecisionSystem(criteria, initial_weights)
system.print_summary()
# ========================================================================
# 2. 第一次决策
# ========================================================================
print("\n[步骤 2] 第一次决策 - 工厂选址")
print("-" * 50)
alternatives = [
{"经济效益": 0.8, "环境影响": 0.3, "社会影响": 0.5, "技术可行性": 0.9}, # 位置A
{"经济效益": 0.5, "环境影响": 0.7, "社会影响": 0.8, "技术可行性": 0.6}, # 位置B
{"经济效益": 0.6, "环境影响": 0.9, "社会影响": 0.6, "技术可行性": 0.7}, # 位置C
]
selected_idx, decision_info = system.make_decision(
alternatives,
context={"task": "工厂选址", "region": "华东地区"}
)
print(f"\n决策结果:")
print(f" 选中方案: 位置{chr(65 + selected_idx)}")
print(f" 得分: {decision_info['score']:.3f}")
print(f" 各方案得分: {[f'{s:.2f}' for s in decision_info['all_scores']]}")
decision_id_1 = decision_info['decision_id']
# ========================================================================
# 3. 提供反馈
# ========================================================================
print("\n[步骤 3] 收集反馈")
print("-" * 50)
# 专家反馈: 环境影响被低估了
print("\n3.1 专家反馈: 环境影响应该更重视")
system.provide_feedback(
decision_id_1,
feedback_value=0.6, # 中等偏下的评分
feedback_type=FeedbackType.EXPLICIT,
source=FeedbackSource.HUMAN_EXPERT,
content="环境影响权重太低,应提高"
)
# 更多反馈强化
system.provide_feedback(
decision_id_1,
feedback_value=0.5,
feedback_type=FeedbackType.CORRECTION,
source=FeedbackSource.HUMAN_EXPERT
)
# ========================================================================
# 4. 第二次决策 (学习后的权重)
# ========================================================================
print("\n[步骤 4] 第二次决策 - 另一个选址")
print("-" * 50)
alternatives_2 = [
{"经济效益": 0.7, "环境影响": 0.4, "社会影响": 0.6, "技术可行性": 0.8}, # 位置D
{"经济效益": 0.4, "环境影响": 0.9, "社会影响": 0.7, "技术可行性": 0.7}, # 位置E
]
selected_idx_2, decision_info_2 = system.make_decision(
alternatives_2,
context={"task": "工厂选址", "region": "华南地区"}
)
print(f"\n决策结果:")
print(f" 选中方案: 位置{chr(68 + selected_idx_2)}")
print(f" 得分: {decision_info_2['score']:.3f}")
print(f" 当前权重: {[f'{w:.3f}' for w in system.weights]}")
decision_id_2 = decision_info_2['decision_id']
# ========================================================================
# 5. 报告结果并学习
# ========================================================================
print("\n[步骤 5] 报告实际结果")
print("-" * 50)
# 第一个决策的结果
print(f"\n5.1 决策 {decision_id_1} 的实际结果")
system.report_outcome(decision_id_1, actual_outcome=75) # 预测可能不同
# 第二个决策的结果
print(f"\n5.2 决策 {decision_id_2} 的实际结果")
system.report_outcome(decision_id_2, actual_outcome=85)
# ========================================================================
# 6. 多轮学习
# ========================================================================
print("\n[步骤 6] 多轮学习")
print("-" * 50)
# 模拟多次决策和反馈
for i in range(10):
alt1 = {
"经济效益": random.uniform(0.5, 0.9),
"环境影响": random.uniform(0.3, 0.7),
"社会影响": random.uniform(0.4, 0.8),
"技术可行性": random.uniform(0.5, 0.9)
}
alt2 = {
"经济效益": random.uniform(0.3, 0.7),
"环境影响": random.uniform(0.6, 0.95),
"社会影响": random.uniform(0.5, 0.9),
"技术可行性": random.uniform(0.4, 0.8)
}
idx, info = system.make_decision([alt1, alt2])
did = info['decision_id']
# 模拟反馈 (随着环境意识增强,对高环境影响的方案给低分)
selected_env_impact = ([alt1, alt2][idx])["环境影响"]
if selected_env_impact < 0.6:
feedback_val = random.uniform(0.3, 0.5) # 低分
else:
feedback_val = random.uniform(0.7, 0.95) # 高分
system.provide_feedback(did, feedback_val)
system.report_outcome(did, actual_outcome=random.uniform(60, 90))
print("\n多轮学习后:")
system.print_summary()
# ========================================================================
# 7. 权重变化分析
# ========================================================================
print("\n[步骤 7] 权重变化分析")
print("-" * 50)
final_weights = system.weights
print(f"\n初始权重: {[f'{w:.3f}' for w in initial_weights]}")
print(f"最终权重: {[f'{w:.3f}' for w in final_weights]}")
print("\n权重变化:")
for i, criterion in enumerate(criteria):
change = final_weights[i] - initial_weights[i]
arrow = "" if change > 0 else "" if change < 0 else ""
print(f" {criterion:15s}: {initial_weights[i]:.3f}{final_weights[i]:.3f} "
f"({arrow}{abs(change):.3f})")
# ========================================================================
# 8. 经验检索
# ========================================================================
print("\n[步骤 8] 相似决策检索")
print("-" * 50)
similar_decisions = system.experience_store.find_similar_decisions(
{"task": "工厂选址", "region": "华东地区"},
threshold=0.3
)
print(f"\n找到 {len(similar_decisions)} 个相似决策:")
for i, decision in enumerate(similar_decisions[:3], 1):
print(f" {i}. {decision.decision_id} - "
f"选中: {decision.selected_alternative}, "
f"反馈: {decision.get_average_feedback():.2f}")
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()
@@ -0,0 +1,824 @@
"""
人机协同示例 (Human-in-the-Loop Example)
========================================
本示例展示如何在空间智能系统中实现人机协同工作模式。
人机协同 (HITL) 结合人类专家的领域知识和AI的计算能力,
实现更可靠的决策。
核心概念:
1. 主动学习 - AI主动请求人类帮助
2. 交互式决策 - 人机共同完成决策
3. 反馈收集 - 收集并整合人类反馈
4. 置信度估计 - AI评估自身确定性
5. 专业知识注入 - 将专家知识整合到系统中
应用场景:
- 空间数据标注与验证
- 复杂选址决策
- 应急响应规划
- 土地利用评估
作者: CC4SI 项目组
"""
import math
import json
from typing import List, Dict, Tuple, Optional, Any, Callable
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime
import random
# ============================================================================
# 协同模式枚举
# ============================================================================
class HITLMode(Enum):
"""人机协同模式"""
AUTOMATIC = "automatic" # 全自动模式
ADVISORY = "advisory" # 建议模式 (AI提供建议,人类决策)
INTERACTIVE = "interactive" # 交互模式 (人机共同决策)
SUPERVISED = "supervised" # 监督模式 (人类监督AI)
MANUAL = "manual" # 手动模式 (人类完全控制)
class ConfidenceLevel(Enum):
"""置信度级别"""
VERY_LOW = "very_low" # 0.0 - 0.3
LOW = "low" # 0.3 - 0.5
MEDIUM = "medium" # 0.5 - 0.7
HIGH = "high" # 0.7 - 0.9
VERY_HIGH = "very_high" # 0.9 - 1.0
class InteractionType(Enum):
"""交互类型"""
CONFIRMATION = "confirmation" # 确认请求
CLARIFICATION = "clarification" # 澄清请求
VALIDATION = "validation" # 验证请求
CORRECTION = "correction" # 纠正请求
RANKING = "ranking" # 排序请求
ANNOTATION = "annotation" # 标注请求
# ============================================================================
# 交互数据结构
# ============================================================================
@dataclass
class AIConfidence:
"""AI置信度"""
value: float # 0-1之间的值
reason: str = ""
metadata: Dict[str, Any] = field(default_factory=dict)
@property
def level(self) -> ConfidenceLevel:
"""获取置信度级别"""
if self.value < 0.3:
return ConfidenceLevel.VERY_LOW
elif self.value < 0.5:
return ConfidenceLevel.LOW
elif self.value < 0.7:
return ConfidenceLevel.MEDIUM
elif self.value < 0.9:
return ConfidenceLevel.HIGH
else:
return ConfidenceLevel.VERY_HIGH
def __repr__(self) -> str:
return f"Confidence({self.value:.2f}, {self.level.value})"
@dataclass
class HumanInput:
"""人类输入"""
interaction_type: InteractionType
response: Any
confidence: float = 1.0 # 人类对自己回答的置信度
timestamp: datetime = field(default_factory=datetime.now)
expert_id: str = "default_expert"
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class InteractionRequest:
"""交互请求"""
request_id: str
interaction_type: InteractionType
question: str
context: Dict[str, Any]
options: Optional[List[Any]] = None
ai_suggestion: Optional[Any] = None
ai_confidence: Optional[AIConfidence] = None
priority: int = 0 # 优先级 (0=普通, 1=重要, 2=紧急)
deadline: Optional[datetime] = None
metadata: Dict[str, Any] = field(default_factory=dict)
# ============================================================================
# 决策建议
# ============================================================================
@dataclass
class DecisionProposal:
"""决策建议"""
proposal_id: str
decision: Any
reasoning: str
confidence: AIConfidence
alternatives: List[Any] = field(default_factory=list)
supporting_evidence: List[str] = field(default_factory=list)
caveats: List[str] = field(default_factory=list) # 警告/注意事项
requires_human_review: bool = False
timestamp: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"proposal_id": self.proposal_id,
"decision": self.decision,
"reasoning": self.reasoning,
"confidence": self.confidence.value,
"alternatives": self.alternatives,
"supporting_evidence": self.supporting_evidence,
"caveats": self.caveats,
"requires_human_review": self.requires_human_review
}
# ============================================================================
# 人类专家接口
# ============================================================================
class HumanExpert(ABC):
"""人类专家抽象接口"""
def __init__(self, expert_id: str, name: str = "", expertise: List[str] = None):
self.expert_id = expert_id
self.name = name or expert_id
self.expertise = expertise or []
@abstractmethod
def respond_to_request(self, request: InteractionRequest) -> HumanInput:
"""响应交互请求"""
pass
def can_handle(self, request: InteractionRequest) -> bool:
"""检查是否能处理请求"""
return True
def get_expertise_summary(self) -> str:
"""获取专长摘要"""
return f"{self.name}: {', '.join(self.expertise) if self.expertise else '通用'}"
class MockHumanExpert(HumanExpert):
"""
模拟人类专家 (用于演示)
在实际应用中,这会连接到真实的用户界面。
"""
def __init__(self, expert_id: str, name: str = "",
expertise: List[str] = None,
response_style: str = "balanced"):
super().__init__(expert_id, name, expertise)
self.response_style = response_style
self.response_log: List[Dict[str, Any]] = []
def respond_to_request(self, request: InteractionRequest) -> HumanInput:
"""模拟响应请求"""
# 记录请求
self.response_log.append({
"request_id": request.request_id,
"type": request.interaction_type.value,
"question": request.question,
"timestamp": datetime.now()
})
# 根据不同类型生成响应
if request.interaction_type == InteractionType.CONFIRMATION:
# 确认请求 - 模拟基于置信度的决策
if request.ai_confidence and request.ai_confidence.value > 0.7:
# 高置信度时倾向于接受AI建议
response = "accept" if random.random() > 0.2 else "reject"
else:
# 低置信度时更谨慎
response = "accept" if random.random() > 0.5 else "reject"
return HumanInput(
interaction_type=request.interaction_type,
response=response,
confidence=0.8
)
elif request.interaction_type == InteractionType.VALIDATION:
# 验证请求
is_valid = random.random() > 0.3 # 70%概率验证通过
return HumanInput(
interaction_type=request.interaction_type,
response=is_valid,
confidence=0.9,
metadata={"comment": "看起来正确" if is_valid else "需要修正"}
)
elif request.interaction_type == InteractionType.RANKING:
# 排序请求
if request.options:
# 随机打乱选项作为人类排序
shuffled = request.options.copy()
random.shuffle(shuffled)
return HumanInput(
interaction_type=request.interaction_type,
response=shuffled,
confidence=0.7
)
elif request.interaction_type == InteractionType.ANNOTATION:
# 标注请求
return HumanInput(
interaction_type=request.interaction_type,
response={
"label": random.choice(["高价值", "中价值", "低价值"]),
"notes": "基于现场评估"
},
confidence=0.75
)
# 默认响应
return HumanInput(
interaction_type=request.interaction_type,
response="acknowledged",
confidence=0.5
)
# ============================================================================
# 人机协同系统
# ============================================================================
class HITLSystem:
"""
人机协同系统
管理AI与人类专家之间的交互。
"""
def __init__(self, name: str = "HITL系统",
default_mode: HITLMode = HITLMode.INTERACTIVE,
confidence_threshold: float = 0.7):
"""
初始化HITL系统
Args:
name: 系统名称
default_mode: 默认协同模式
confidence_threshold: 请求人类帮助的置信度阈值
"""
self.name = name
self.current_mode = default_mode
self.confidence_threshold = confidence_threshold
# 注册的专家
self.experts: Dict[str, HumanExpert] = {}
# 待处理的请求队列
self.pending_requests: List[InteractionRequest] = []
# 交互历史
self.interaction_history: List[Dict[str, Any]] = []
# 统计信息
self.stats = {
"total_requests": 0,
"auto_resolved": 0,
"human_resolved": 0,
"human_acceptance_rate": 0.0
}
print(f"[{self.name}] 初始化完成")
print(f" 模式: {default_mode.value}")
print(f" 置信度阈值: {confidence_threshold}")
def register_expert(self, expert: HumanExpert) -> None:
"""注册人类专家"""
self.experts[expert.expert_id] = expert
print(f"[专家注册] {expert.get_expertise_summary()}")
def set_mode(self, mode: HITLMode) -> None:
"""设置协同模式"""
self.current_mode = mode
print(f"[模式切换] {mode.value}")
def make_decision(self, proposal: DecisionProposal,
auto_threshold: float = None) -> Any:
"""
做出决策 (带人机协同)
Args:
proposal: AI的决策建议
auto_threshold: 自动决策的置信度阈值
Returns:
最终决策
"""
threshold = auto_threshold or self.confidence_threshold
# 根据模式和置信度决定是否需要人类介入
needs_human = self._needs_human_intervention(proposal, threshold)
if not needs_human:
# 自动决策
self.stats["auto_resolved"] += 1
self._record_interaction(proposal, None, "automatic")
return proposal.decision
# 请求人类帮助
return self._request_human_input(proposal)
def _needs_human_intervention(self, proposal: DecisionProposal,
threshold: float) -> bool:
"""判断是否需要人类介入"""
# 检查强制人工审查标记
if proposal.requires_human_review:
return True
# 检查置信度
if proposal.confidence.value < threshold:
return True
# 根据模式判断
if self.current_mode == HITLMode.MANUAL:
return True
elif self.current_mode == HITLMode.SUPERVISED:
return True
elif self.current_mode == HITLMode.AUTOMATIC:
return False
elif self.current_mode == HITLMode.INTERACTIVE:
# 交互模式下,低置信度需要人类
return proposal.confidence.value < 0.8
elif self.current_mode == HITLMode.ADVISORY:
# 建议模式下,总是需要人类确认
return True
return False
def _request_human_input(self, proposal: DecisionProposal) -> Any:
"""请求人类输入"""
# 创建交互请求
request = InteractionRequest(
request_id=f"req_{len(self.interaction_history)}",
interaction_type=InteractionType.CONFIRMATION,
question=f"请确认AI建议: {proposal.reasoning}",
context={"proposal_id": proposal.proposal_id},
options=["accept", "reject", "modify"],
ai_suggestion=proposal.decision,
ai_confidence=proposal.confidence
)
self.pending_requests.append(request)
self.stats["total_requests"] += 1
# 选择专家
expert = self._select_expert(request)
if not expert:
print("警告: 没有可用的专家,使用AI建议")
return proposal.decision
# 获取专家响应
print(f"\n[人类交互] 向专家 {expert.name} 请求确认...")
print(f" AI建议: {proposal.decision}")
print(f" 置信度: {proposal.confidence}")
print(f" 理由: {proposal.reasoning}")
human_input = expert.respond_to_request(request)
print(f" 专家响应: {human_input.response}")
# 处理响应
result = self._process_human_response(proposal, human_input)
# 记录交互
self._record_interaction(proposal, human_input, "human_assisted")
# 清理请求
if request in self.pending_requests:
self.pending_requests.remove(request)
self.stats["human_resolved"] += 1
return result
def _select_expert(self, request: InteractionRequest) -> Optional[HumanExpert]:
"""选择合适的专家"""
if not self.experts:
return None
# 简单实现: 返回第一个可用的专家
for expert in self.experts.values():
if expert.can_handle(request):
return expert
return None
def _process_human_response(self, proposal: DecisionProposal,
human_input: HumanInput) -> Any:
"""处理人类响应"""
if human_input.interaction_type == InteractionType.CONFIRMATION:
if human_input.response == "accept":
# 接受AI建议
return proposal.decision
elif human_input.response == "reject":
# 拒绝AI建议,返回次优选项
if proposal.alternatives:
return proposal.alternatives[0]
return None
elif human_input.response == "modify":
# 需要修改 (简化: 返回原建议)
return proposal.decision
return human_input.response
def _record_interaction(self, proposal: DecisionProposal,
human_input: Optional[HumanInput],
resolution_type: str) -> None:
"""记录交互"""
record = {
"proposal_id": proposal.proposal_id,
"timestamp": datetime.now(),
"ai_confidence": proposal.confidence.value,
"human_input": human_input.response if human_input else None,
"resolution_type": resolution_type
}
self.interaction_history.append(record)
def request_annotation(self, item: Any, context: Dict[str, Any] = None) -> Any:
"""请求人类标注"""
request = InteractionRequest(
request_id=f"annotate_{len(self.interaction_history)}",
interaction_type=InteractionType.ANNOTATION,
question=f"请对以下项目进行标注: {item}",
context=context or {},
ai_suggestion=item
)
expert = self._select_expert(request)
if not expert:
return None
return expert.respond_to_request(request)
def request_validation(self, item: Any, context: Dict[str, Any] = None) -> bool:
"""请求人类验证"""
request = InteractionRequest(
request_id=f"validate_{len(self.interaction_history)}",
interaction_type=InteractionType.VALIDATION,
question=f"以下内容是否正确: {item}",
context=context or {},
ai_suggestion=item
)
expert = self._select_expert(request)
if not expert:
return True # 默认有效
response = expert.respond_to_request(request)
return response.response if isinstance(response.response, bool) else True
def get_statistics(self) -> Dict[str, Any]:
"""获取统计信息"""
stats = self.stats.copy()
stats["pending_requests"] = len(self.pending_requests)
stats["total_interactions"] = len(self.interaction_history)
# 计算接受率
human_interactions = [i for i in self.interaction_history
if i["resolution_type"] == "human_assisted"]
if human_interactions:
accepted = sum(1 for i in human_interactions
if i["human_input"] == "accept")
stats["human_acceptance_rate"] = accepted / len(human_interactions)
return stats
def print_statistics(self) -> None:
"""打印统计信息"""
stats = self.get_statistics()
print(f"\n{self.name} 统计信息:")
print("-" * 50)
print(f"总请求数: {stats['total_requests']}")
print(f"自动解决: {stats['auto_resolved']}")
print(f"人类协助: {stats['human_resolved']}")
print(f"待处理请求: {stats['pending_requests']}")
print(f"人类接受率: {stats['human_acceptance_rate']:.2%}")
print("-" * 50)
# ============================================================================
# 空间决策HITL系统
# ============================================================================
class SpatialDecisionHITL(HITLSystem):
"""
空间决策人机协同系统
专门用于空间决策场景的HITL实现。
"""
def __init__(self, confidence_threshold: float = 0.7):
super().__init__(
name="空间决策HITL系统",
default_mode=HITLMode.INTERACTIVE,
confidence_threshold=confidence_threshold
)
def analyze_site_suitability(self, site_data: Dict[str, Any]) -> DecisionProposal:
"""
分析场地适宜性
Args:
site_data: 场地数据
Returns:
决策建议
"""
# 简化的适宜性评分
score = self._calculate_suitability_score(site_data)
# 确定置信度
confidence = self._assess_confidence(site_data, score)
# 生成建议
if score > 0.7:
decision = "highly_suitable"
reasoning = f"综合评分 {score:.2f} 较高,适宜开发"
elif score > 0.5:
decision = "moderately_suitable"
reasoning = f"综合评分 {score:.2f} 中等,需谨慎评估"
else:
decision = "not_suitable"
reasoning = f"综合评分 {score:.2f} 较低,不建议开发"
# 检查注意事项
caveats = []
if site_data.get("environmental_risk", 0) > 0.6:
caveats.append("存在环境风险")
if site_data.get("infrastructure_score", 1) < 0.4:
caveats.append("基础设施不足")
# 低置信度时标记需要人工审查
requires_review = confidence.value < 0.6 or len(caveats) > 0
return DecisionProposal(
proposal_id=f"suitability_{random.randint(1000, 9999)}",
decision=decision,
reasoning=reasoning,
confidence=confidence,
alternatives=["moderately_suitable", "not_suitable"]
if decision != "not_suitable" else ["moderately_suitable", "highly_suitable"],
supporting_evidence=[
f"评分: {score:.2f}",
f"环境因子: {site_data.get('environmental_score', 0):.2f}",
f"经济因子: {site_data.get('economic_score', 0):.2f}"
],
caveats=caveats,
requires_human_review=requires_review
)
def _calculate_suitability_score(self, site_data: Dict[str, Any]) -> float:
"""计算适宜性评分"""
env = site_data.get("environmental_score", 0.5)
econ = site_data.get("economic_score", 0.5)
social = site_data.get("social_score", 0.5)
infra = site_data.get("infrastructure_score", 0.5)
# 加权平均
return 0.3 * env + 0.3 * econ + 0.2 * social + 0.2 * infra
def _assess_confidence(self, site_data: Dict[str, Any],
score: float) -> AIConfidence:
"""评估置信度"""
# 检查数据完整性
has_all_data = all(k in site_data for k in [
"environmental_score", "economic_score",
"social_score", "infrastructure_score"
])
if not has_all_data:
return AIConfidence(
value=0.4,
reason="数据不完整"
)
# 检查数据质量
data_quality = site_data.get("data_quality", 0.8)
confidence = data_quality * 0.9
# 检查是否有冲突因素
if site_data.get("environmental_risk", 0) > 0.7:
confidence *= 0.7 # 降低置信度
return AIConfidence(
value=min(confidence, 0.95),
reason="基于数据质量和完整性评估"
)
# ============================================================================
# 主程序
# ============================================================================
def main():
"""主程序 - 演示人机协同的使用"""
print("="*70)
print("人机协同示例演示")
print("="*70)
random.seed(42)
# ========================================================================
# 1. 创建HITL系统
# ========================================================================
print("\n[步骤 1] 创建人机协同系统")
print("-" * 50)
hitl_system = SpatialDecisionHITL(confidence_threshold=0.7)
# 注册专家
expert1 = MockHumanExpert(
expert_id="expert_001",
name="张工程师",
expertise=["环境影响评估", "基础设施规划"],
response_style="conservative"
)
expert2 = MockHumanExpert(
expert_id="expert_002",
name="李规划师",
expertise=["经济效益分析", "社会影响评估"],
response_style="balanced"
)
hitl_system.register_expert(expert1)
hitl_system.register_expert(expert2)
# ========================================================================
# 2. 场景1: 高置信度自动决策
# ========================================================================
print("\n[场景 1] 高置信度 - 自动决策")
print("-" * 50)
site1 = {
"environmental_score": 0.85,
"economic_score": 0.90,
"social_score": 0.88,
"infrastructure_score": 0.92,
"data_quality": 0.95,
"environmental_risk": 0.1
}
proposal1 = hitl_system.analyze_site_suitability(site1)
print(f"\nAI分析结果:")
print(f" 建议: {proposal1.decision}")
print(f" 理由: {proposal1.reasoning}")
print(f" 置信度: {proposal1.confidence}")
decision1 = hitl_system.make_decision(proposal1)
print(f"\n最终决策: {decision1} (自动)")
print(f" → 置信度高,无需人工介入")
# ========================================================================
# 3. 场景2: 低置信度请求人类帮助
# ========================================================================
print("\n\n[场景 2] 低置信度 - 请求人类确认")
print("-" * 50)
site2 = {
"environmental_score": 0.45, # 环境评分低
"economic_score": 0.85, # 但经济评分高
"social_score": 0.60,
"infrastructure_score": 0.50,
"data_quality": 0.70,
"environmental_risk": 0.65 # 存在环境风险
}
proposal2 = hitl_system.analyze_site_suitability(site2)
print(f"\nAI分析结果:")
print(f" 建议: {proposal2.decision}")
print(f" 理由: {proposal2.reasoning}")
print(f" 置信度: {proposal2.confidence}")
print(f" 注意事项: {', '.join(proposal2.caveats)}")
decision2 = hitl_system.make_decision(proposal2)
print(f"\n最终决策: {decision2} (人工协助)")
print(f" → 置信度低且存在注意事项,请求专家确认")
# ========================================================================
# 4. 场景3: 批量决策
# ========================================================================
print("\n\n[场景 3] 批量场地评估")
print("-" * 50)
sites = []
for i in range(5):
site = {
"environmental_score": random.uniform(0.3, 0.95),
"economic_score": random.uniform(0.3, 0.95),
"social_score": random.uniform(0.3, 0.95),
"infrastructure_score": random.uniform(0.3, 0.95),
"data_quality": random.uniform(0.5, 0.95),
"environmental_risk": random.uniform(0.0, 0.8)
}
sites.append(site)
results = []
for i, site in enumerate(sites, 1):
proposal = hitl_system.analyze_site_suitability(site)
decision = hitl_system.make_decision(proposal)
results.append({
"site": i,
"decision": decision,
"confidence": proposal.confidence.value,
"auto": proposal.confidence.value >= hitl_system.confidence_threshold
})
print("\n批量评估结果:")
print(f"{'场地':<6} {'决策':<20} {'置信度':<10} {'模式':<10}")
print("-" * 50)
for r in results:
mode = "自动" if r["auto"] else "人工"
print(f"{r['site']:<6} {r['decision']:<20} {r['confidence']:<10.2f} {mode:<10}")
# ========================================================================
# 5. 场景4: 数据标注
# ========================================================================
print("\n\n[场景 4] 数据标注")
print("-" * 50)
unlabeled_items = [
{"coordinates": (120.5, 30.2), "features": "residential"},
{"coordinates": (121.0, 30.5), "features": "commercial"},
{"coordinates": (120.8, 30.0), "features": "industrial"}
]
for item in unlabeled_items:
annotation = hitl_system.request_annotation(
item,
context={"task": "land_use_classification"}
)
if annotation:
print(f"\n标注 {item['features']}:")
print(f" 标签: {annotation.response.get('label')}")
print(f" 备注: {annotation.response.get('notes')}")
# ========================================================================
# 6. 场景5: 模式切换
# ========================================================================
print("\n\n[场景 5] 模式切换对比")
print("-" * 50)
test_site = {
"environmental_score": 0.70,
"economic_score": 0.75,
"social_score": 0.68,
"infrastructure_score": 0.72,
"data_quality": 0.85,
"environmental_risk": 0.3
}
proposal = hitl_system.analyze_site_suitability(test_site)
print(f"\nAI分析: 置信度 = {proposal.confidence.value:.2f}")
# 尝试不同模式
for mode in [HITLMode.AUTOMATIC, HITLMode.INTERACTIVE, HITLMode.MANUAL]:
hitl_system.set_mode(mode)
decision = hitl_system.make_decision(proposal)
mode_name = {
HITLMode.AUTOMATIC: "全自动",
HITLMode.INTERACTIVE: "交互式",
HITLMode.MANUAL: "手动"
}[mode]
print(f" {mode_name}: {decision}")
# 恢复默认模式
hitl_system.set_mode(HITLMode.INTERACTIVE)
# ========================================================================
# 7. 统计信息
# ========================================================================
print("\n\n[步骤 7] 系统统计")
print("-" * 50)
hitl_system.print_statistics()
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()
@@ -0,0 +1,684 @@
"""
模块化系统示例 (Modular System Example)
========================================
本示例展示了空间智能系统的模块化设计原则。
模块化是构建可维护、可扩展系统的基础。
核心概念:
1. 关注点分离 - 每个模块负责特定功能
2. 接口设计 - 定义清晰的模块间通信协议
3. 依赖注入 - 降低模块间耦合
4. 插件架构 - 支持动态扩展功能
作者: CC4SI 项目组
"""
from abc import ABC, abstractmethod
from typing import Dict, List, Any, Optional, Callable
from dataclasses import dataclass, field
from enum import Enum
import json
# ============================================================================
# 模块接口定义 (抽象基类)
# ============================================================================
class ModuleType(Enum):
"""模块类型枚举"""
DATA_LOADER = "data_loader"
DATA_PROCESSOR = "data_processor"
ANALYZER = "analyzer"
VISUALIZER = "visualizer"
EXPORTER = "exporter"
class ModuleStatus(Enum):
"""模块状态枚举"""
IDLE = "idle"
INITIALIZING = "initializing"
READY = "ready"
RUNNING = "running"
ERROR = "error"
@dataclass
class ModuleMetadata:
"""模块元数据"""
name: str
version: str
module_type: ModuleType
description: str = ""
dependencies: List[str] = field(default_factory=list)
author: str = ""
config_schema: Dict[str, Any] = field(default_factory=dict)
class IModule(ABC):
"""
模块接口 - 所有模块必须实现此接口
这是一个抽象基类,定义了所有模块必须遵循的契约。
"""
def __init__(self, config: Dict[str, Any] = None):
"""
初始化模块
Args:
config: 模块配置字典
"""
self.config = config or {}
self.status = ModuleStatus.IDLE
self._context: Optional['ModuleContext'] = None
@property
@abstractmethod
def metadata(self) -> ModuleMetadata:
"""返回模块元数据"""
pass
@abstractmethod
def initialize(self, context: 'ModuleContext') -> bool:
"""
初始化模块
Args:
context: 模块上下文,提供对系统资源的访问
Returns:
初始化是否成功
"""
pass
@abstractmethod
def execute(self, input_data: Any) -> Any:
"""
执行模块功能
Args:
input_data: 输入数据
Returns:
处理结果
"""
pass
@abstractmethod
def shutdown(self) -> None:
"""关闭模块,释放资源"""
pass
def get_config(self, key: str, default: Any = None) -> Any:
"""获取配置值"""
return self.config.get(key, default)
def set_config(self, key: str, value: Any) -> None:
"""设置配置值"""
self.config[key] = value
# ============================================================================
# 模块上下文 - 提供模块间通信
# ============================================================================
class ModuleContext:
"""
模块上下文
提供模块间通信和资源共享机制,实现松耦合设计。
"""
def __init__(self):
self._modules: Dict[str, IModule] = {}
self._shared_data: Dict[str, Any] = {}
self._event_handlers: Dict[str, List[Callable]] = {}
def register_module(self, name: str, module: IModule) -> bool:
"""注册模块"""
if name in self._modules:
print(f"警告: 模块 '{name}' 已存在,将被覆盖")
self._modules[name] = module
print(f"模块 '{name}' 已注册 (类型: {module.metadata.module_type.value})")
return True
def get_module(self, name: str) -> Optional[IModule]:
"""获取模块实例"""
return self._modules.get(name)
def has_module(self, name: str) -> bool:
"""检查模块是否存在"""
return name in self._modules
def set_shared_data(self, key: str, value: Any) -> None:
"""设置共享数据"""
self._shared_data[key] = value
def get_shared_data(self, key: str, default: Any = None) -> Any:
"""获取共享数据"""
return self._shared_data.get(key, default)
def subscribe_event(self, event_name: str, handler: Callable) -> None:
"""订阅事件"""
if event_name not in self._event_handlers:
self._event_handlers[event_name] = []
self._event_handlers[event_name].append(handler)
def publish_event(self, event_name: str, *args, **kwargs) -> None:
"""发布事件"""
if event_name in self._event_handlers:
for handler in self._event_handlers[event_name]:
handler(*args, **kwargs)
# ============================================================================
# 模块基类 - 提供通用功能实现
# ============================================================================
class BaseModule(IModule):
"""
模块基类
提供IModule接口的默认实现,子类只需实现特定功能。
"""
def __init__(self, config: Dict[str, Any] = None):
super().__init__(config)
self._metadata: Optional[ModuleMetadata] = None
@property
def metadata(self) -> ModuleMetadata:
if self._metadata is None:
raise NotImplementedError("子类必须设置 _metadata")
return self._metadata
def initialize(self, context: ModuleContext) -> bool:
"""默认初始化实现"""
self._context = context
self.status = ModuleStatus.INITIALIZING
# 检查依赖
for dep in self.metadata.dependencies:
if not context.has_module(dep):
print(f"错误: 依赖模块 '{dep}' 不存在")
self.status = ModuleStatus.ERROR
return False
self.status = ModuleStatus.READY
print(f"模块 '{self.metadata.name}' 初始化完成")
return True
def shutdown(self) -> None:
"""默认关闭实现"""
self.status = ModuleStatus.IDLE
print(f"模块 '{self.metadata.name}' 已关闭")
# ============================================================================
# 具体模块实现
# ============================================================================
class CSVDataLoaderModule(BaseModule):
"""
CSV 数据加载模块
负责从CSV文件加载空间数据。
"""
def __init__(self, config: Dict[str, Any] = None):
super().__init__(config)
self._metadata = ModuleMetadata(
name="csv_data_loader",
version="1.0.0",
module_type=ModuleType.DATA_LOADER,
description="从CSV文件加载空间数据",
author="CC4SI"
)
self._data: List[Dict[str, Any]] = []
def execute(self, input_data: Any) -> Any:
"""
执行数据加载
Args:
input_data: 文件路径或模拟数据
Returns:
加载的数据列表
"""
self.status = ModuleStatus.RUNNING
if isinstance(input_data, str):
# 实际场景中应从文件读取
print(f"从文件 '{input_data}' 加载数据...")
# 模拟加载
self._data = self._load_sample_data()
elif isinstance(input_data, list):
self._data = input_data
else:
self._data = self._load_sample_data()
# 将数据存入共享上下文
if self._context:
self._context.set_shared_data("raw_data", self._data)
self._context.publish_event("data_loaded", len(self._data))
self.status = ModuleStatus.READY
return self._data
def _load_sample_data(self) -> List[Dict[str, Any]]:
"""加载示例数据"""
return [
{"id": 1, "x": 10, "y": 20, "value": 100, "type": "A"},
{"id": 2, "x": 30, "y": 40, "value": 200, "type": "B"},
{"id": 3, "x": 50, "y": 60, "value": 150, "type": "A"},
{"id": 4, "x": 70, "y": 80, "value": 300, "type": "C"},
{"id": 5, "x": 90, "y": 100, "value": 250, "type": "B"},
]
class DataValidationModule(BaseModule):
"""
数据验证模块
负责验证数据质量和完整性。
"""
def __init__(self, config: Dict[str, Any] = None):
super().__init__(config)
self._metadata = ModuleMetadata(
name="data_validator",
version="1.0.0",
module_type=ModuleType.DATA_PROCESSOR,
description="验证数据质量和完整性",
dependencies=["csv_data_loader"],
author="CC4SI"
)
self.validation_rules: List[Callable] = []
def add_validation_rule(self, rule: Callable[[Dict], bool], name: str = ""):
"""添加验证规则"""
self.validation_rules.append(rule)
if name:
print(f"添加验证规则: {name}")
def execute(self, input_data: Any) -> Any:
"""
执行数据验证
Args:
input_data: 待验证的数据
Returns:
验证结果
"""
self.status = ModuleStatus.RUNNING
if not isinstance(input_data, list):
return {"valid": False, "errors": ["输入数据格式错误"]}
errors = []
warnings = []
for i, item in enumerate(input_data):
# 检查必需字段
if "id" not in item:
errors.append(f"{i} 项缺少 'id' 字段")
if "x" not in item or "y" not in item:
errors.append(f"{i} 项缺少坐标字段")
# 应用自定义验证规则
for rule in self.validation_rules:
try:
if not rule(item):
warnings.append(f"{i} 项未通过自定义规则验证")
except Exception as e:
errors.append(f"{i} 项验证时出错: {e}")
result = {
"valid": len(errors) == 0,
"total": len(input_data),
"errors": errors,
"warnings": warnings
}
if self._context:
self._context.set_shared_data("validation_result", result)
self._context.publish_event("data_validated", result)
self.status = ModuleStatus.READY
return result
class StatisticsAnalyzerModule(BaseModule):
"""
统计分析模块
负责计算数据的统计指标。
"""
def __init__(self, config: Dict[str, Any] = None):
super().__init__(config)
self._metadata = ModuleMetadata(
name="statistics_analyzer",
version="1.0.0",
module_type=ModuleType.ANALYZER,
description="计算数据统计指标",
dependencies=["csv_data_loader"],
author="CC4SI"
)
def execute(self, input_data: Any) -> Any:
"""
执行统计分析
Args:
input_data: 待分析的数据
Returns:
统计结果
"""
self.status = ModuleStatus.RUNNING
if not isinstance(input_data, list) or len(input_data) == 0:
return {"error": "没有可分析的数据"}
# 提取数值字段
values = [item.get("value", 0) for item in input_data if "value" in item]
if not values:
return {"error": "没有找到可分析的数值"}
import statistics
result = {
"count": len(values),
"mean": statistics.mean(values),
"median": statistics.median(values),
"stdev": statistics.stdev(values) if len(values) > 1 else 0,
"min": min(values),
"max": max(values),
"sum": sum(values)
}
if self._context:
self._context.set_shared_data("statistics", result)
self._context.publish_event("analysis_complete", result)
self.status = ModuleStatus.READY
return result
class ReportExporterModule(BaseModule):
"""
报告导出模块
负责生成分析报告。
"""
def __init__(self, config: Dict[str, Any] = None):
super().__init__(config)
self._metadata = ModuleMetadata(
name="report_exporter",
version="1.0.0",
module_type=ModuleType.EXPORTER,
description="生成分析报告",
dependencies=["statistics_analyzer"],
author="CC4SI"
)
def execute(self, input_data: Any) -> Any:
"""
生成报告
Args:
input_data: 统计结果或其他数据
Returns:
报告字符串
"""
self.status = ModuleStatus.RUNNING
report_lines = [
"=" * 60,
"空间数据分析报告",
"=" * 60,
""
]
# 从上下文获取数据
if self._context:
validation = self._context.get_shared_data("validation_result")
statistics = self._context.get_shared_data("statistics")
if validation:
report_lines.extend([
"数据验证结果:",
f" 总数: {validation.get('total', 0)}",
f" 有效: {validation.get('valid', False)}",
f" 错误数: {len(validation.get('errors', []))}",
""
])
if statistics:
report_lines.extend([
"统计分析结果:",
f" 样本数: {statistics.get('count', 0)}",
f" 均值: {statistics.get('mean', 0):.2f}",
f" 中位数: {statistics.get('median', 0):.2f}",
f" 标准差: {statistics.get('stdev', 0):.2f}",
f" 最小值: {statistics.get('min', 0)}",
f" 最大值: {statistics.get('max', 0)}",
""
])
report_lines.append("=" * 60)
report = "\n".join(report_lines)
if self._context:
self._context.publish_event("report_generated", report)
self.status = ModuleStatus.READY
return report
# ============================================================================
# 模块系统管理器
# ============================================================================
class ModularSystem:
"""
模块化系统管理器
负责管理模块的生命周期和模块间通信。
"""
def __init__(self, name: str = "模块化空间智能系统"):
self.name = name
self.context = ModuleContext()
self._pipeline: List[str] = [] # 处理流程
def register_module(self, module: IModule, alias: str = None) -> bool:
"""
注册模块到系统
Args:
module: 模块实例
alias: 模块别名 (可选)
Returns:
是否注册成功
"""
name = alias or module.metadata.name
return self.context.register_module(name, module)
def initialize_all(self) -> bool:
"""初始化所有模块"""
print(f"\n初始化 {self.name}...")
success = True
for name, module in self.context._modules.items():
if not module.initialize(self.context):
print(f"模块 '{name}' 初始化失败")
success = False
return success
def define_pipeline(self, module_names: List[str]) -> None:
"""
定义处理流程
Args:
module_names: 按顺序执行的模块名称列表
"""
self._pipeline = module_names
print(f"定义处理流程: {' -> '.join(module_names)}")
def execute(self, input_data: Any = None) -> Any:
"""
执行处理流程
Args:
input_data: 输入数据
Returns:
最终输出结果
"""
if not self._pipeline:
print("错误: 没有定义处理流程")
return None
print(f"\n执行处理流程...")
current_data = input_data
for module_name in self._pipeline:
module = self.context.get_module(module_name)
if not module:
print(f"错误: 找不到模块 '{module_name}'")
return None
print(f" -> 执行模块: {module.metadata.name}")
current_data = module.execute(current_data)
# 如果模块返回错误,终止流程
if isinstance(current_data, dict) and current_data.get("error"):
print(f" 模块 '{module_name}' 返回错误: {current_data['error']}")
return current_data
return current_data
def shutdown_all(self) -> None:
"""关闭所有模块"""
print(f"\n关闭 {self.name}...")
for module in self.context._modules.values():
module.shutdown()
def print_system_info(self) -> None:
"""打印系统信息"""
print(f"\n{'='*60}")
print(f"系统: {self.name}")
print(f"{'='*60}")
print(f"已注册模块数: {len(self.context._modules)}")
for name, module in self.context._modules.items():
print(f" - {name:20s} [{module.metadata.module_type.value:15s}] {module.metadata.name}")
if module.metadata.dependencies:
print(f" 依赖: {', '.join(module.metadata.dependencies)}")
if self._pipeline:
print(f"\n处理流程: {' -> '.join(self._pipeline)}")
else:
print(f"\n处理流程: 未定义")
print(f"{'='*60}\n")
# ============================================================================
# 事件处理示例
# ============================================================================
def setup_event_handlers(system: ModularSystem):
"""设置事件处理器"""
def on_data_loaded(count):
print(f" [事件] 数据加载完成,共 {count} 条记录")
def on_data_validated(result):
status = "通过" if result.get("valid") else "失败"
print(f" [事件] 数据验证{status},错误: {len(result.get('errors', []))}")
def on_analysis_complete(result):
print(f" [事件] 分析完成,均值: {result.get('mean', 0):.2f}")
system.context.subscribe_event("data_loaded", on_data_loaded)
system.context.subscribe_event("data_validated", on_data_validated)
system.context.subscribe_event("analysis_complete", on_analysis_complete)
# ============================================================================
# 主程序
# ============================================================================
def main():
"""主程序 - 演示模块化系统的使用"""
print("="*70)
print("模块化系统示例演示")
print("="*70)
# 1. 创建系统
print("\n[步骤 1] 创建模块化系统")
system = ModularSystem("空间数据分析系统")
# 2. 注册模块
print("\n[步骤 2] 注册模块")
system.register_module(CSVDataLoaderModule())
system.register_module(DataValidationModule())
system.register_module(StatisticsAnalyzerModule())
system.register_module(ReportExporterModule())
# 3. 设置事件处理
print("\n[步骤 3] 设置事件处理")
setup_event_handlers(system)
# 4. 初始化所有模块
print("\n[步骤 4] 初始化模块")
if not system.initialize_all():
print("初始化失败,退出")
return
# 5. 定义处理流程
print("\n[步骤 5] 定义处理流程")
system.define_pipeline([
"csv_data_loader",
"data_validator",
"statistics_analyzer",
"report_exporter"
])
# 6. 打印系统信息
print("\n[步骤 6] 系统信息")
system.print_system_info()
# 7. 执行处理流程
print("\n[步骤 7] 执行处理流程")
result = system.execute("sample_data.csv")
# 8. 输出结果
print("\n[步骤 8] 最终结果")
if isinstance(result, str):
print(result)
# 9. 清理
print("\n[步骤 9] 清理资源")
system.shutdown_all()
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()
@@ -0,0 +1,938 @@
"""
概率与不确定性示例 (Probability and Uncertainty Example)
========================================================
本示例展示如何在空间智能系统中处理概率和不确定性。
在空间决策中,不确定性是普遍存在的,理解和管理不确定性
对于做出可靠的决策至关重要。
核心概念:
1. 概率分布 - 描述随机变量的可能取值及其概率
2. 贝叶斯推理 - 基于新证据更新信念
3. 蒙特卡洛模拟 - 通过随机采样评估不确定性
4. 置信区间 - 估计结果的范围
5. 敏感性分析 - 评估输入变化对输出的影响
应用场景:
- 空间插值的不确定性量化
- 多准则决策的敏感性分析
- 风险评估与概率预测
- 传感器数据的可靠性分析
作者: CC4SI 项目组
"""
import math
import random
from typing import List, Dict, Tuple, Optional, Callable, Any
from dataclasses import dataclass, field
from abc import ABC, abstractmethod
from enum import Enum
import statistics
# ============================================================================
# 概率分布基础类
# ============================================================================
class DistributionType(Enum):
"""分布类型枚举"""
NORMAL = "normal" # 正态分布
UNIFORM = "uniform" # 均匀分布
TRIANGULAR = "triangular" # 三角分布
EXPONENTIAL = "exponential" # 指数分布
BETA = "beta" # Beta分布
class ProbabilityDistribution(ABC):
"""概率分布抽象基类"""
def __init__(self, name: str = ""):
self.name = name
@abstractmethod
def sample(self) -> float:
"""从分布中采样一个值"""
pass
@abstractmethod
def mean(self) -> float:
"""计算期望值"""
pass
@abstractmethod
def std(self) -> float:
"""计算标准差"""
pass
@abstractmethod
def pdf(self, x: float) -> float:
"""概率密度函数"""
pass
def cdf(self, x: float) -> float:
"""累积分布函数 (近似计算)"""
# 使用蒙特卡洛积分近似
n_samples = 10000
count = sum(1 for _ in range(n_samples) if self.sample() <= x)
return count / n_samples
def confidence_interval(self, confidence: float = 0.95) -> Tuple[float, float]:
"""计算置信区间"""
n_samples = 10000
samples = [self.sample() for _ in range(n_samples)]
alpha = 1 - confidence
lower = quantile(samples, alpha / 2)
upper = quantile(samples, 1 - alpha / 2)
return lower, upper
def quantile(data: List[float], q: float) -> float:
"""计算分位数"""
sorted_data = sorted(data)
index = int(q * len(sorted_data))
return sorted_data[min(index, len(sorted_data) - 1)]
# ============================================================================
# 具体概率分布实现
# ============================================================================
class NormalDistribution(ProbabilityDistribution):
"""
正态分布 (高斯分布)
最常用的连续概率分布,由均值和标准差参数化。
"""
def __init__(self, mu: float = 0.0, sigma: float = 1.0, name: str = ""):
super().__init__(name)
self.mu = mu # 均值
self.sigma = sigma # 标准差
if sigma <= 0:
raise ValueError("标准差必须为正数")
def sample(self) -> float:
"""使用 Box-Muller 变换生成正态分布随机数"""
u1 = random.random()
u2 = random.random()
while u1 == 0: # 避免log(0)
u1 = random.random()
z0 = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
return self.mu + self.sigma * z0
def mean(self) -> float:
return self.mu
def std(self) -> float:
return self.sigma
def pdf(self, x: float) -> float:
"""正态分布概率密度函数"""
coeff = 1 / (self.sigma * math.sqrt(2 * math.pi))
exponent = -0.5 * ((x - self.mu) / self.sigma) ** 2
return coeff * math.exp(exponent)
def __repr__(self) -> str:
return f"Normal(μ={self.mu}, σ={self.sigma})"
class UniformDistribution(ProbabilityDistribution):
"""
均匀分布
在指定范围内等概率取值。
"""
def __init__(self, a: float = 0.0, b: float = 1.0, name: str = ""):
super().__init__(name)
self.a = a # 下界
self.b = b # 上界
if a >= b:
raise ValueError("下界必须小于上界")
def sample(self) -> float:
return self.a + (self.b - self.a) * random.random()
def mean(self) -> float:
return (self.a + self.b) / 2
def std(self) -> float:
return (self.b - self.a) / math.sqrt(12)
def pdf(self, x: float) -> float:
if self.a <= x <= self.b:
return 1 / (self.b - self.a)
return 0.0
def __repr__(self) -> str:
return f"Uniform({self.a}, {self.b})"
class TriangularDistribution(ProbabilityDistribution):
"""
三角分布
由最小值、最大值和众数定义的分布,常用于
当只知道边界和最可能值时建模不确定性。
"""
def __init__(self, a: float, b: float, c: float, name: str = ""):
super().__init__(name)
self.a = a # 最小值
self.b = b # 最大值
self.c = c # 众数 (最可能值)
if not (a <= c <= b):
raise ValueError("必须满足 a <= c <= b")
def sample(self) -> float:
u = random.random()
fc = (self.c - self.a) / (self.b - self.a)
if u < fc:
return self.a + math.sqrt(u * (self.b - self.a) * (self.c - self.a))
else:
return self.b - math.sqrt((1 - u) * (self.b - self.a) * (self.b - self.c))
def mean(self) -> float:
return (self.a + self.b + self.c) / 3
def std(self) -> float:
numerator = (self.a**2 + self.b**2 + self.c**2
- self.a * self.b - self.a * self.c - self.b * self.c)
return math.sqrt(numerator / 18)
def pdf(self, x: float) -> float:
if x < self.a or x > self.b:
return 0.0
if x < self.c:
return 2 * (x - self.a) / ((self.b - self.a) * (self.c - self.a))
else:
return 2 * (self.b - x) / ((self.b - self.a) * (self.b - self.c))
def __repr__(self) -> str:
return f"Triangular({self.a}, {self.c}, {self.b})"
# ============================================================================
# 贝叶斯推理
# ============================================================================
@dataclass
class BayesianBelief:
"""
贝叶斯信念状态
表示对某个假设的信念,包含先验、似然和后验。
"""
hypothesis: str
prior: float # 先验概率 P(H)
likelihood: float # 似然 P(E|H)
evidence: Optional[float] = None # 证据概率 P(E)
posterior: Optional[float] = None # 后验概率 P(H|E)
def update(self, evidence_prob: float = None) -> float:
"""
更新后验概率
Args:
evidence_prob: P(E),如果None则使用归一化
Returns:
后验概率
"""
# P(H|E) = P(E|H) * P(H) / P(E)
numerator = self.likelihood * self.prior
if evidence_prob is not None:
self.evidence = evidence_prob
self.posterior = numerator / evidence_prob
else:
# 假设有多个假设,需要归一化
self.posterior = numerator # 简化版本
return self.posterior
class BayesianUpdater:
"""
贝叶斯更新器
管理多个假设的贝叶斯更新。
"""
def __init__(self, hypotheses: List[str]):
"""
初始化贝叶斯更新器
Args:
hypotheses: 假设列表
"""
# 初始化先验概率 (均匀分布)
prior = 1.0 / len(hypotheses)
self.beliefs: Dict[str, BayesianBelief] = {
h: BayesianBelief(hypothesis=h, prior=prior, likelihood=1.0)
for h in hypotheses
}
def set_prior(self, hypothesis: str, prior: float) -> None:
"""设置先验概率"""
if hypothesis in self.beliefs:
self.beliefs[hypothesis].prior = prior
def update_with_evidence(self, likelihoods: Dict[str, float]) -> None:
"""
用证据更新所有假设
Args:
likelihoods: 每个假设的似然 P(E|H)
"""
# 更新似然
for h, likelihood in likelihoods.items():
if h in self.beliefs:
self.beliefs[h].likelihood = likelihood
# 计算证据概率 (归一化常数)
evidence = sum(
b.likelihood * b.prior
for b in self.beliefs.values()
)
# 更新后验
for belief in self.beliefs.values():
belief.update(evidence)
def get_posteriors(self) -> Dict[str, float]:
"""获取所有后验概率"""
return {
h: b.posterior or b.prior
for h, b in self.beliefs.items()
}
def get_most_likely(self) -> Tuple[str, float]:
"""获取最可能的假设"""
posteriors = self.get_posteriors()
return max(posteriors.items(), key=lambda x: x[1])
def print_beliefs(self) -> None:
"""打印信念状态"""
print("\n贝叶斯信念状态:")
print("-" * 60)
print(f"{'假设':<20} {'先验':<12} {'似然':<12} {'后验':<12}")
print("-" * 60)
for belief in self.beliefs.values():
posterior = belief.posterior if belief.posterior is not None else belief.prior
print(f"{belief.hypothesis:<20} {belief.prior:<12.4f} "
f"{belief.likelihood:<12.4f} {posterior:<12.4f}")
print("-" * 60)
# ============================================================================
# 蒙特卡洛模拟
# ============================================================================
@dataclass
class SimulationResult:
"""模拟结果"""
samples: List[float] = field(default_factory=list)
mean: float = 0.0
std: float = 0.0
min: float = 0.0
max: float = 0.0
median: float = 0.0
confidence_interval: Tuple[float, float] = (0.0, 0.0)
percentiles: Dict[float, float] = field(default_factory=dict)
def calculate_statistics(self, confidence: float = 0.95) -> None:
"""计算统计量"""
if not self.samples:
return
self.mean = statistics.mean(self.samples)
self.std = statistics.stdev(self.samples) if len(self.samples) > 1 else 0
self.min = min(self.samples)
self.max = max(self.samples)
self.median = statistics.median(self.samples)
# 置信区间
alpha = 1 - confidence
sorted_samples = sorted(self.samples)
n = len(sorted_samples)
self.confidence_interval = (
sorted_samples[int(alpha / 2 * n)],
sorted_samples[int((1 - alpha / 2) * n)]
)
# 常用百分位数
for p in [0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99]:
self.percentiles[p] = sorted_samples[int(p * n)]
def print_summary(self) -> None:
"""打印结果摘要"""
print(f"\n蒙特卡洛模拟结果 (n={len(self.samples)}):")
print("-" * 50)
print(f"均值: {self.mean:.4f}")
print(f"中位数: {self.median:.4f}")
print(f"标准差: {self.std:.4f}")
print(f"范围: [{self.min:.4f}, {self.max:.4f}]")
print(f"95% 置信区间: [{self.confidence_interval[0]:.4f}, "
f"{self.confidence_interval[1]:.4f}]")
print(f"\n百分位数:")
for p, value in sorted(self.percentiles.items()):
print(f" {p*100:>5.0f}%: {value:.4f}")
print("-" * 50)
class MonteCarloSimulator:
"""
蒙特卡洛模拟器
通过随机采样评估不确定性。
"""
def __init__(self, seed: int = None):
"""初始化模拟器"""
if seed is not None:
random.seed(seed)
def simulate(self, model: Callable[[], float],
n_runs: int = 10000) -> SimulationResult:
"""
运行模拟
Args:
model: 返回模拟值的函数
n_runs: 运行次数
Returns:
模拟结果
"""
samples = [model() for _ in range(n_runs)]
result = SimulationResult(samples=samples)
result.calculate_statistics()
return result
def simulate_with_inputs(self,
model: Callable[[Dict[str, float]], float],
input_distributions: Dict[str, ProbabilityDistribution],
n_runs: int = 10000) -> SimulationResult:
"""
使用输入分布运行模拟
Args:
model: 接受输入字典的函数
input_distributions: 输入变量到其分布的映射
n_runs: 运行次数
Returns:
模拟结果
"""
samples = []
for _ in range(n_runs):
inputs = {
name: dist.sample()
for name, dist in input_distributions.items()
}
samples.append(model(inputs))
result = SimulationResult(samples=samples)
result.calculate_statistics()
return result
# ============================================================================
# 敏感性分析
# ============================================================================
@dataclass
class SensitivityResult:
"""敏感性分析结果"""
sensitivity_coefficients: Dict[str, float] = field(default_factory=dict)
rankings: List[Tuple[str, float]] = field(default_factory=list)
tornado_data: Dict[str, Tuple[float, float]] = field(default_factory=dict)
def print_summary(self) -> None:
"""打印敏感性分析摘要"""
print("\n敏感性分析结果:")
print("-" * 50)
print("排名 | 变量 | 敏感性系数")
print("-" * 50)
for i, (var, coef) in enumerate(self.rankings, 1):
print(f"{i:4d} | {var:<11} | {coef:10.4f}")
print("-" * 50)
class SensitivityAnalyzer:
"""
敏感性分析器
评估输入变化对输出的影响。
"""
def __init__(self):
self.model: Optional[Callable] = None
self.base_inputs: Optional[Dict[str, float]] = None
def simple_sensitivity(self,
model: Callable[[Dict[str, float]], float],
base_inputs: Dict[str, float],
variations: Dict[str, float] = None) -> SensitivityResult:
"""
简单敏感性分析 (单因素)
Args:
model: 待分析模型
base_inputs: 基准输入值
variations: 各变量的变化幅度 (默认 ±10%)
Returns:
敏感性结果
"""
if variations is None:
variations = {k: 0.1 for k in base_inputs.keys()}
# 计算基准输出
base_output = model(base_inputs)
# 计算敏感性系数
coefficients = {}
tornado_data = {}
for var, variation in variations.items():
original_value = base_inputs[var]
# 正向变化
base_inputs[var] = original_value * (1 + variation)
output_plus = model(base_inputs)
# 负向变化
base_inputs[var] = original_value * (1 - variation)
output_minus = model(base_inputs)
# 恢复原值
base_inputs[var] = original_value
# 计算敏感性系数 (归一化)
delta_output = output_plus - output_minus
delta_input = 2 * variation * original_value
coefficient = delta_output / delta_input if delta_input != 0 else 0
coefficients[var] = coefficient
tornado_data[var] = (output_minus, output_plus)
# 排名
rankings = sorted(coefficients.items(), key=lambda x: abs(x[1]), reverse=True)
return SensitivityResult(
sensitivity_coefficients=coefficients,
rankings=rankings,
tornado_data=tornado_data
)
def regression_sensitivity(self,
model: Callable[[Dict[str, float]], float],
input_distributions: Dict[str, ProbabilityDistribution],
n_samples: int = 1000) -> SensitivityResult:
"""
基于回归的敏感性分析
Args:
model: 待分析模型
input_distributions: 输入分布
n_samples: 样本数量
Returns:
敏感性结果
"""
simulator = MonteCarloSimulator()
# 生成样本
input_samples = []
output_samples = []
for _ in range(n_samples):
inputs = {
name: dist.sample()
for name, dist in input_distributions.items()
}
input_samples.append(inputs)
output_samples.append(model(inputs))
# 计算标准化回归系数 (SRC)
# SRC = beta * (std_x / std_y)
import statistics
std_y = statistics.stdev(output_samples)
coefficients = {}
for var in input_distributions.keys():
x_values = [s[var] for s in input_samples]
std_x = statistics.stdev(x_values)
# 计算相关系数
mean_x = statistics.mean(x_values)
mean_y = statistics.mean(output_samples)
numerator = sum((x - mean_x) * (y - mean_y)
for x, y in zip(x_values, output_samples))
denominator = math.sqrt(
sum((x - mean_x)**2 for x in x_values) *
sum((y - mean_y)**2 for y in output_samples)
)
correlation = numerator / denominator if denominator != 0 else 0
src = correlation * (std_x / std_y) if std_y > 0 else 0
coefficients[var] = src
rankings = sorted(coefficients.items(), key=lambda x: abs(x[1]), reverse=True)
return SensitivityResult(
sensitivity_coefficients=coefficients,
rankings=rankings
)
# ============================================================================
# 空间概率应用示例
# ============================================================================
class SpatialProbabilityModel:
"""
空间概率模型
将概率理论应用于空间问题。
"""
@staticmethod
def uncertain_distance(point1: Tuple[float, float],
point2: Tuple[float, float],
distance_error_std: float = 5.0) -> NormalDistribution:
"""
带不确定性的距离计算
Args:
point1: 第一个点 (x, y)
point2: 第二个点 (x, y)
distance_error_std: 距离测量误差的标准差
Returns:
距离的概率分布
"""
# 计算确定性距离
dx = point2[0] - point1[0]
dy = point2[1] - point1[1]
true_distance = math.sqrt(dx**2 + dy**2)
# 返回正态分布
return NormalDistribution(mu=true_distance, sigma=distance_error_std)
@staticmethod
def location_probability(measurement: Tuple[float, float],
true_location: Tuple[float, float],
measurement_error: float = 10.0) -> float:
"""
计算测量位置的似然概率
Args:
measurement: 测量位置
true_location: 真实位置
measurement_error: 测量误差标准差
Returns:
似然概率
"""
dist = SpatialProbabilityModel.uncertain_distance(
measurement, true_location, measurement_error
)
# 使用正态分布 PDF
return dist.pdf(0)
@staticmethod
def bayesian_location_update(prior_locations: List[Tuple[float, float]],
measurements: List[Tuple[float, float]],
measurement_error: float = 10.0) -> List[float]:
"""
贝叶斯位置更新
Args:
prior_locations: 候选真实位置列表
measurements: 测量位置列表
measurement_error: 测量误差
Returns:
每个候选位置的后验概率
"""
n = len(prior_locations)
posteriors = []
for candidate in prior_locations:
# 计算似然 (所有测量的乘积)
likelihood = 1.0
for measurement in measurements:
prob = SpatialProbabilityModel.location_probability(
measurement, candidate, measurement_error
)
likelihood *= prob
# 先验 (均匀)
prior = 1.0 / n
# 后验 (未归一化)
posterior = likelihood * prior
posteriors.append(posterior)
# 归一化
total = sum(posteriors)
if total > 0:
posteriors = [p / total for p in posteriors]
return posteriors
# ============================================================================
# 主程序
# ============================================================================
def main():
"""主程序 - 演示概率与不确定性的使用"""
print("="*70)
print("概率与不确定性示例演示")
print("="*70)
random.seed(42) # 可重现的结果
# ========================================================================
# 1. 概率分布示例
# ========================================================================
print("\n[部分 1] 概率分布")
print("-" * 50)
# 创建不同的分布
normal = NormalDistribution(mu=100, sigma=15, name="温度")
uniform = UniformDistribution(a=50, b=150, name="范围")
triangular = TriangularDistribution(a=60, c=100, b=140, name="估计")
distributions = [normal, uniform, triangular]
for dist in distributions:
print(f"\n{dist}:")
print(f" 均值: {dist.mean():.2f}")
print(f" 标准差: {dist.std():.2f}")
samples = [dist.sample() for _ in range(5)]
print(f" 样本: {[f'{s:.2f}' for s in samples]}")
ci = dist.confidence_interval(0.95)
print(f" 95% CI: [{ci[0]:.2f}, {ci[1]:.2f}]")
# ========================================================================
# 2. 贝叶斯推理示例
# ========================================================================
print("\n\n[部分 2] 贝叶斯推理")
print("-" * 50)
print("问题: 根据土壤测试结果判断土地适宜性")
# 假设: 土地适宜性等级
hypotheses = ["高适宜", "中适宜", "低适宜", "不适宜"]
updater = BayesianUpdater(hypotheses)
# 设置不同的先验 (基于历史数据)
updater.set_prior("高适宜", 0.2)
updater.set_prior("中适宜", 0.3)
updater.set_prior("低适宜", 0.3)
updater.set_prior("不适宜", 0.2)
print("\n初始信念:")
updater.print_beliefs()
# 证据1: 土壤pH值检测
print("\n证据1: 土壤pH值适中 (似然更新)")
updater.update_with_evidence({
"高适宜": 0.8, # pH值对高适宜的可能性高
"中适宜": 0.6,
"低适宜": 0.3,
"不适宜": 0.1
})
updater.print_beliefs()
# 证据2: 有机质含量检测
print("\n证据2: 有机质含量高 (似然更新)")
updater.update_with_evidence({
"高适宜": 0.9,
"中适宜": 0.5,
"低适宜": 0.2,
"不适宜": 0.05
})
updater.print_beliefs()
most_likely = updater.get_most_likely()
print(f"\n最可能的假设: {most_likely[0]} (概率: {most_likely[1]:.2%})")
# ========================================================================
# 3. 蒙特卡洛模拟示例
# ========================================================================
print("\n\n[部分 3] 蒙特卡洛模拟")
print("-" * 50)
print("问题: 评估房地产开发项目的预期收益")
def development_model(inputs: Dict[str, float]) -> float:
"""房地产开发收益模型"""
land_cost = inputs["land_cost"]
construction_cost = inputs["construction_cost"]
selling_price = inputs["selling_price"]
units = inputs["units"]
sales_rate = inputs["sales_rate"]
# 收益 = (售价 * 单元数 * 销售率) - (土地成本 + 建设成本 * 单元数)
revenue = selling_price * units * sales_rate
total_cost = land_cost + construction_cost * units
return revenue - total_cost
# 定义输入分布
input_dists = {
"land_cost": TriangularDistribution(800000, 1000000, 1500000), # 土地成本
"construction_cost": NormalDistribution(50000, 5000), # 单元建设成本
"selling_price": NormalDistribution(150000, 15000), # 单元售价
"units": TriangularDistribution(80, 100, 120), # 单元数量
"sales_rate": BetaDistribution(alpha=8, beta=2, a=0, b=1) # 销售率
}
simulator = MonteCarloSimulator()
result = simulator.simulate_with_inputs(development_model, input_dists, n_runs=10000)
result.print_summary()
# 风险评估
negative_prob = sum(1 for s in result.samples if s < 0) / len(result.samples)
print(f"\n风险分析:")
print(f" 亏损概率: {negative_prob:.2%}")
profit_prob = sum(1 for s in result.samples if s > 1000000) / len(result.samples)
print(f" 超过100万利润概率: {profit_prob:.2%}")
# ========================================================================
# 4. 敏感性分析示例
# ========================================================================
print("\n\n[部分 4] 敏感性分析")
print("-" * 50)
print("问题: 分析各因素对收益的影响程度")
analyzer = SensitivityAnalyzer()
# 简单敏感性分析
base_inputs = {
"land_cost": 1000000,
"construction_cost": 50000,
"selling_price": 150000,
"units": 100,
"sales_rate": 0.85
}
sensitivity_result = analyzer.simple_sensitivity(
development_model, base_inputs, variations={k: 0.1 for k in base_inputs.keys()}
)
sensitivity_result.print_summary()
# ========================================================================
# 5. 空间概率应用
# ========================================================================
print("\n\n[部分 5] 空间概率应用")
print("-" * 50)
print("问题: GPS定位的不确定性")
# 真实位置
true_location = (1000, 2000)
# 带误差的测量
measurements = [
(1005, 2003),
(998, 1998),
(1002, 2005),
(995, 2000)
]
# 候选位置
candidates = [
(1000, 2000), # 真实位置
(1015, 2015),
(990, 1990),
(1005, 1995)
]
posteriors = SpatialProbabilityModel.bayesian_location_update(
candidates, measurements, measurement_error=5.0
)
print("\n候选位置的后验概率:")
for i, (loc, prob) in enumerate(zip(candidates, posteriors)):
print(f" 位置 {i+1} {loc}: {prob:.4f}")
most_likely_idx = max(range(len(posteriors)), key=lambda i: posteriors[i])
print(f"\n最可能的位置: 位置 {most_likely_idx+1} {candidates[most_likely_idx]}")
print("\n" + "="*70)
print("演示完成!")
print("="*70)
# Beta分布实现 (用于上面代码中的引用)
class BetaDistribution(ProbabilityDistribution):
"""Beta分布 - 用于建模[0,1]区间内的概率"""
def __init__(self, alpha: float, beta: float, a: float = 0, b: float = 1, name: str = ""):
super().__init__(name)
self.alpha = alpha
self.beta = beta
self.a = a # 下界
self.b = b # 上界
def sample(self) -> float:
# 使用numpy的gamma函数近似
import math
import random
# 生成Gamma随机变量
def gamma(alpha):
if alpha < 1:
return gamma(alpha + 1) * (random.random() ** (1 / alpha))
# Marsaglia and Tsang's method
d = alpha - 1/3
c = 1 / math.sqrt(9 * d)
while True:
x = random.gauss(0, 1)
v = (1 + c * x) ** 3
if v > 0:
u = random.random()
if u < 1 - 0.0331 * (x * x) ** 2:
return d * v
if math.log(u) < 0.5 * x * x + d * (1 - v + math.log(v)):
return d * v
x = gamma(self.alpha)
y = gamma(self.beta)
beta_sample = x / (x + y)
# 转换到[a, b]区间
return self.a + (self.b - self.a) * beta_sample
def mean(self) -> float:
return self.a + (self.b - self.a) * self.alpha / (self.alpha + self.beta)
def std(self) -> float:
mean_raw = self.alpha / (self.alpha + self.beta)
var_raw = (self.alpha * self.beta) / (
(self.alpha + self.beta) ** 2 * (self.alpha + self.beta + 1)
)
return (self.b - self.a) * math.sqrt(var_raw)
def pdf(self, x: float) -> float:
# 简化版本,仅返回近似值
return 1.0 # 实际应实现Beta分布的PDF
def __repr__(self) -> str:
return f"Beta(α={self.alpha}, β={self.beta})"
if __name__ == "__main__":
main()
@@ -0,0 +1,748 @@
"""
状态机工作流示例 (State Machine Workflow Example)
==================================================
本示例展示如何使用状态机模式管理复杂的空间分析工作流。
状态机是一种行为设计模式,允许对象在其内部状态改变时改变其行为。
核心概念:
1. 状态 (State) - 系统在特定时刻的模式
2. 转换 (Transition) - 从一个状态到另一个状态的变化
3. 事件 (Event) - 触发状态转换的外部或内部条件
4. 动作 (Action) - 状态转换时执行的操作
应用场景:
- 空间数据处理流水线
- 多阶段决策流程
- 任务调度与监控
- 用户交互流程控制
作者: CC4SI 项目组
"""
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Callable, Any
from dataclasses import dataclass, field
from enum import Enum
import json
from datetime import datetime
# ============================================================================
# 状态定义
# ============================================================================
class WorkflowState(Enum):
"""工作流状态枚举"""
# 初始状态
IDLE = "idle"
INITIALIZED = "initialized"
# 数据处理状态
LOADING_DATA = "loading_data"
DATA_LOADED = "data_loaded"
VALIDATING_DATA = "validating_data"
DATA_VALIDATED = "data_validated"
# 分析状态
ANALYZING = "analyzing"
ANALYSIS_COMPLETE = "analysis_complete"
# 决策状态
DECIDING = "deciding"
DECISION_MADE = "decision_made"
# 输出状态
GENERATING_OUTPUT = "generating_output"
OUTPUT_COMPLETE = "output_complete"
# 异常状态
ERROR = "error"
PAUSED = "paused"
CANCELLED = "cancelled"
# 最终状态
COMPLETED = "completed"
class EventType(Enum):
"""事件类型枚举"""
# 控制事件
START = "start"
PAUSE = "pause"
RESUME = "resume"
CANCEL = "cancel"
RESET = "reset"
# 数据事件
DATA_LOAD_REQUEST = "data_load_request"
DATA_LOAD_SUCCESS = "data_load_success"
DATA_LOAD_FAILURE = "data_load_failure"
DATA_VALIDATE_REQUEST = "data_validate_request"
DATA_VALIDATE_SUCCESS = "data_validate_success"
DATA_VALIDATE_FAILURE = "data_validate_failure"
# 分析事件
ANALYZE_REQUEST = "analyze_request"
ANALYSIS_SUCCESS = "analysis_success"
ANALYSIS_FAILURE = "analysis_failure"
# 决策事件
DECIDE_REQUEST = "decide_request"
DECISION_SUCCESS = "decision_success"
DECISION_FAILURE = "decision_failure"
# 输出事件
OUTPUT_REQUEST = "output_request"
OUTPUT_SUCCESS = "output_success"
OUTPUT_FAILURE = "output_failure"
# 错误事件
ERROR_OCCURRED = "error_occurred"
RETRY = "retry"
# ============================================================================
# 状态机数据结构
# ============================================================================
@dataclass
class StateTransition:
"""状态转换定义"""
from_state: WorkflowState
event: EventType
to_state: WorkflowState
action: Optional[Callable] = None
guard: Optional[Callable[[], bool]] = None # 守卫条件
description: str = ""
def can_execute(self) -> bool:
"""检查转换是否可执行"""
if self.guard is None:
return True
return self.guard()
@dataclass
class StateContext:
"""状态上下文 - 存储工作流数据"""
data: Dict[str, Any] = field(default_factory=dict)
errors: List[str] = field(default_factory=list)
warnings: List[str] = field(default_factory=list)
history: List[Dict[str, Any]] = field(default_factory=list)
start_time: Optional[datetime] = None
end_time: Optional[datetime] = None
def add_history(self, from_state: WorkflowState, event: EventType,
to_state: WorkflowState, timestamp: datetime = None):
"""添加历史记录"""
self.history.append({
"from_state": from_state.value,
"event": event.value,
"to_state": to_state.value,
"timestamp": timestamp or datetime.now()
})
def get_data(self, key: str, default: Any = None) -> Any:
"""获取数据"""
return self.data.get(key, default)
def set_data(self, key: str, value: Any) -> None:
"""设置数据"""
self.data[key] = value
def add_error(self, error: str) -> None:
"""添加错误"""
self.errors.append(error)
def add_warning(self, warning: str) -> None:
"""添加警告"""
self.warnings.append(warning)
# ============================================================================
# 状态机实现
# ============================================================================
class StateMachine:
"""
状态机实现
管理状态转换和状态相关的行为。
"""
def __init__(self, initial_state: WorkflowState = WorkflowState.IDLE):
"""
初始化状态机
Args:
initial_state: 初始状态
"""
self._current_state = initial_state
self._transitions: Dict[WorkflowState, Dict[EventType, StateTransition]] = {}
self._context = StateContext()
self._state_listeners: Dict[WorkflowState, List[Callable]] = {}
print(f"[状态机] 初始化,初始状态: {initial_state.value}")
@property
def current_state(self) -> WorkflowState:
"""获取当前状态"""
return self._current_state
@property
def context(self) -> StateContext:
"""获取状态上下文"""
return self._context
def add_transition(self, transition: StateTransition) -> None:
"""
添加状态转换
Args:
transition: 状态转换定义
"""
if transition.from_state not in self._transitions:
self._transitions[transition.from_state] = {}
self._transitions[transition.from_state][transition.event] = transition
print(f"[状态机] 添加转换: {transition.from_state.value} + {transition.event.value} -> {transition.to_state.value}")
def add_state_listener(self, state: WorkflowState, listener: Callable) -> None:
"""
添加状态监听器
Args:
state: 要监听的状态
listener: 状态进入时调用的函数
"""
if state not in self._state_listeners:
self._state_listeners[state] = []
self._state_listeners[state].append(listener)
def trigger(self, event: EventType, payload: Any = None) -> bool:
"""
触发事件
Args:
event: 事件类型
payload: 事件负载
Returns:
是否成功触发状态转换
"""
# 检查当前状态是否有对应转换
if self._current_state not in self._transitions:
print(f"[状态机] 当前状态 {self._current_state.value} 没有定义任何转换")
return False
if event not in self._transitions[self._current_state]:
print(f"[状态机] 状态 {self._current_state.value} 不处理事件 {event.value}")
return False
transition = self._transitions[self._current_state][event]
# 检查守卫条件
if not transition.can_execute():
print(f"[状态机] 守卫条件不满足,转换被阻止")
return False
# 执行状态转换
old_state = self._current_state
self._current_state = transition.to_state
# 记录历史
if payload:
self._context.set_data("last_payload", payload)
self._context.add_history(old_state, event, self._current_state)
print(f"[状态机] 状态转换: {old_state.value} -> {self._current_state.value} (事件: {event.value})")
# 执行转换动作
if transition.action:
try:
transition.action(self._context, payload)
except Exception as e:
print(f"[状态机] 执行动作时出错: {e}")
self._context.add_error(f"转换动作执行失败: {e}")
# 触发状态监听器
if self._current_state in self._state_listeners:
for listener in self._state_listeners[self._current_state]:
try:
listener(self._current_state, self._context)
except Exception as e:
print(f"[状态机] 监听器执行出错: {e}")
return True
def can_trigger(self, event: EventType) -> bool:
"""
检查是否可以触发指定事件
Args:
event: 事件类型
Returns:
是否可以触发
"""
if self._current_state not in self._transitions:
return False
if event not in self._transitions[self._current_state]:
return False
transition = self._transitions[self._current_state][event]
return transition.can_execute()
def get_available_events(self) -> List[EventType]:
"""获取当前状态下可用的事件列表"""
if self._current_state not in self._transitions:
return []
available = []
for event, transition in self._transitions[self._current_state].items():
if transition.can_execute():
available.append(event)
return available
def reset(self) -> None:
"""重置状态机"""
self._current_state = WorkflowState.IDLE
self._context = StateContext()
print(f"[状态机] 状态机已重置")
def print_state(self) -> None:
"""打印当前状态"""
print(f"\n当前状态: {self._current_state.value}")
available = self.get_available_events()
if available:
print(f"可用事件: {', '.join(e.value for e in available)}")
else:
print("可用事件: 无")
# ============================================================================
# 空间分析工作流状态机
# ============================================================================
class SpatialAnalysisWorkflow:
"""
空间分析工作流
使用状态机实现的空间数据处理和分析工作流。
"""
def __init__(self):
"""初始化工作流"""
self.state_machine = StateMachine()
self._setup_transitions()
self._setup_listeners()
def _setup_transitions(self):
"""设置状态转换"""
sm = self.state_machine
# 启动流程
sm.add_transition(StateTransition(
from_state=WorkflowState.IDLE,
event=EventType.START,
to_state=WorkflowState.INITIALIZED,
action=self._action_initialize,
description="初始化工作流"
))
# 数据加载
sm.add_transition(StateTransition(
from_state=WorkflowState.INITIALIZED,
event=EventType.DATA_LOAD_REQUEST,
to_state=WorkflowState.LOADING_DATA,
action=self._action_load_data,
description="开始加载数据"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.LOADING_DATA,
event=EventType.DATA_LOAD_SUCCESS,
to_state=WorkflowState.DATA_LOADED,
action=self._action_on_data_loaded,
description="数据加载成功"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.LOADING_DATA,
event=EventType.DATA_LOAD_FAILURE,
to_state=WorkflowState.ERROR,
action=self._action_on_error,
description="数据加载失败"
))
# 数据验证
sm.add_transition(StateTransition(
from_state=WorkflowState.DATA_LOADED,
event=EventType.DATA_VALIDATE_REQUEST,
to_state=WorkflowState.VALIDATING_DATA,
action=self._action_validate_data,
description="开始验证数据"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.VALIDATING_DATA,
event=EventType.DATA_VALIDATE_SUCCESS,
to_state=WorkflowState.DATA_VALIDATED,
description="数据验证成功"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.VALIDATING_DATA,
event=EventType.DATA_VALIDATE_FAILURE,
to_state=WorkflowState.ERROR,
action=self._action_on_error,
description="数据验证失败"
))
# 分析
sm.add_transition(StateTransition(
from_state=WorkflowState.DATA_VALIDATED,
event=EventType.ANALYZE_REQUEST,
to_state=WorkflowState.ANALYZING,
action=self._action_analyze,
description="开始分析"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.ANALYZING,
event=EventType.ANALYSIS_SUCCESS,
to_state=WorkflowState.ANALYSIS_COMPLETE,
description="分析完成"
))
# 决策
sm.add_transition(StateTransition(
from_state=WorkflowState.ANALYSIS_COMPLETE,
event=EventType.DECIDE_REQUEST,
to_state=WorkflowState.DECIDING,
action=self._action_decide,
description="开始决策"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.DECIDING,
event=EventType.DECISION_SUCCESS,
to_state=WorkflowState.DECISION_MADE,
description="决策完成"
))
# 输出
sm.add_transition(StateTransition(
from_state=WorkflowState.DECISION_MADE,
event=EventType.OUTPUT_REQUEST,
to_state=WorkflowState.GENERATING_OUTPUT,
action=self._action_generate_output,
description="生成输出"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.GENERATING_OUTPUT,
event=EventType.OUTPUT_SUCCESS,
to_state=WorkflowState.OUTPUT_COMPLETE,
description="输出完成"
))
# 完成
sm.add_transition(StateTransition(
from_state=WorkflowState.OUTPUT_COMPLETE,
event=EventType.START,
to_state=WorkflowState.COMPLETED,
action=self._action_complete,
description="工作流完成"
))
# 错误恢复
sm.add_transition(StateTransition(
from_state=WorkflowState.ERROR,
event=EventType.RETRY,
to_state=WorkflowState.INITIALIZED,
guard=lambda: len(self.state_machine.context.errors) < 3,
description="重试"
))
sm.add_transition(StateTransition(
from_state=WorkflowState.ERROR,
event=EventType.RESET,
to_state=WorkflowState.IDLE,
action=self._action_reset,
description="重置"
))
def _setup_listeners(self):
"""设置状态监听器"""
sm = self.state_machine
# 错误状态监听器
sm.add_state_listener(WorkflowState.ERROR, self._on_error_state)
# 完成状态监听器
sm.add_state_listener(WorkflowState.COMPLETED, self._on_complete_state)
# ------------------------------------------------------------------------
# 状态动作
# ------------------------------------------------------------------------
def _action_initialize(self, ctx: StateContext, payload: Any):
"""初始化动作"""
ctx.start_time = datetime.now()
ctx.set_data("workflow_id", f"WF-{datetime.now().strftime('%Y%m%d%H%M%S')}")
print(" [动作] 工作流初始化完成")
def _action_load_data(self, ctx: StateContext, payload: Any):
"""加载数据动作"""
source = payload or "默认数据源"
print(f" [动作] 从 '{source}' 加载数据...")
# 模拟数据加载
ctx.set_data("raw_data", [
{"id": 1, "x": 10, "y": 20, "value": 100},
{"id": 2, "x": 30, "y": 40, "value": 200},
{"id": 3, "x": 50, "y": 60, "value": 150},
])
# 模拟成功
self.state_machine.trigger(EventType.DATA_LOAD_SUCCESS)
def _action_on_data_loaded(self, ctx: StateContext, payload: Any):
"""数据加载完成动作"""
data_count = len(ctx.get_data("raw_data", []))
print(f" [动作] 数据加载完成,共 {data_count} 条记录")
def _action_validate_data(self, ctx: StateContext, payload: Any):
"""验证数据动作"""
print(" [动作] 验证数据...")
data = ctx.get_data("raw_data", [])
valid = all("id" in item and "x" in item and "y" in item for item in data)
if valid:
self.state_machine.trigger(EventType.DATA_VALIDATE_SUCCESS)
else:
ctx.add_error("数据验证失败: 缺少必需字段")
self.state_machine.trigger(EventType.DATA_VALIDATE_FAILURE)
def _action_analyze(self, ctx: StateContext, payload: Any):
"""分析动作"""
print(" [动作] 执行空间分析...")
data = ctx.get_data("raw_data", [])
values = [item.get("value", 0) for item in data]
avg = sum(values) / len(values) if values else 0
ctx.set_data("analysis_result", {
"average": avg,
"count": len(data),
"min": min(values) if values else 0,
"max": max(values) if values else 0
})
print(f" [动作] 分析完成,平均值: {avg:.2f}")
self.state_machine.trigger(EventType.ANALYSIS_SUCCESS)
def _action_decide(self, ctx: StateContext, payload: Any):
"""决策动作"""
print(" [动作] 执行决策...")
analysis = ctx.get_data("analysis_result", {})
avg = analysis.get("average", 0)
if avg > 150:
decision = "高价值区域"
elif avg > 100:
decision = "中等价值区域"
else:
decision = "低价值区域"
ctx.set_data("decision", decision)
print(f" [动作] 决策完成: {decision}")
self.state_machine.trigger(EventType.DECISION_SUCCESS)
def _action_generate_output(self, ctx: StateContext, payload: Any):
"""生成输出动作"""
print(" [动作] 生成输出报告...")
report = {
"workflow_id": ctx.get_data("workflow_id"),
"data_count": len(ctx.get_data("raw_data", [])),
"analysis": ctx.get_data("analysis_result"),
"decision": ctx.get_data("decision")
}
ctx.set_data("output", report)
print(" [动作] 输出生成完成")
self.state_machine.trigger(EventType.OUTPUT_SUCCESS)
def _action_complete(self, ctx: StateContext, payload: Any):
"""完成动作"""
ctx.end_time = datetime.now()
duration = (ctx.end_time - ctx.start_time).total_seconds() if ctx.start_time else 0
ctx.set_data("duration", duration)
print(f" [动作] 工作流完成,耗时: {duration:.2f}")
def _action_on_error(self, ctx: StateContext, payload: Any):
"""错误处理动作"""
print(f" [动作] 发生错误")
def _action_reset(self, ctx: StateContext, payload: Any):
"""重置动作"""
print(" [动作] 重置工作流")
# ------------------------------------------------------------------------
# 状态监听器
# ------------------------------------------------------------------------
def _on_error_state(self, state: WorkflowState, ctx: StateContext):
"""错误状态处理"""
print(f" [监听器] 进入错误状态")
print(f" [监听器] 错误列表: {ctx.errors}")
def _on_complete_state(self, state: WorkflowState, ctx: StateContext):
"""完成状态处理"""
print(f" [监听器] 工作流已完成")
output = ctx.get_data("output")
if output:
print(f" [监听器] 最终输出: {json.dumps(output, ensure_ascii=False, indent=2)}")
# ------------------------------------------------------------------------
# 公共接口
# ------------------------------------------------------------------------
def start(self, data_source: str = None) -> bool:
"""启动工作流"""
return self.state_machine.trigger(EventType.START, data_source)
def execute_full_workflow(self, data_source: str = None) -> Dict[str, Any]:
"""
执行完整工作流
Args:
data_source: 数据源
Returns:
执行结果
"""
print("\n" + "="*60)
print("执行完整空间分析工作流")
print("="*60)
# 启动
if not self.start(data_source):
return {"success": False, "error": "启动失败"}
# 等待异步操作完成 (简化版: 手动触发)
# 在实际应用中,这些事件会由异步操作触发
return {
"success": True,
"final_state": self.state_machine.current_state.value,
"context": self.state_machine.context.data
}
def print_history(self):
"""打印状态转换历史"""
history = self.state_machine.context.history
print(f"\n状态转换历史 (共 {len(history)} 次):")
print("-" * 70)
for i, h in enumerate(history, 1):
ts = h.get("timestamp", datetime.now()).strftime("%H:%M:%S")
print(f"{i:2d}. [{ts}] {h['from_state']:20s} -> {h['to_state']:20s} ({h['event']})")
print("-" * 70)
# ============================================================================
# 主程序
# ============================================================================
def main():
"""主程序 - 演示状态机工作流的使用"""
print("="*70)
print("状态机工作流示例演示")
print("="*70)
# 1. 创建工作流
print("\n[步骤 1] 创建空间分析工作流")
workflow = SpatialAnalysisWorkflow()
# 2. 显示初始状态
print("\n[步骤 2] 初始状态")
workflow.state_machine.print_state()
# 3. 手动执行状态转换
print("\n[步骤 3] 手动执行状态转换")
# 启动
print("\n3.1 启动工作流:")
workflow.state_machine.trigger(EventType.START)
workflow.state_machine.print_state()
# 请求数据加载 (这将触发加载动作,然后自动触发成功事件)
print("\n3.2 请求数据加载:")
workflow.state_machine.trigger(EventType.DATA_LOAD_REQUEST, "sample.csv")
workflow.state_machine.print_state()
# 请求数据验证
print("\n3.3 请求数据验证:")
workflow.state_machine.trigger(EventType.DATA_VALIDATE_REQUEST)
workflow.state_machine.print_state()
# 请求分析
print("\n3.4 请求分析:")
workflow.state_machine.trigger(EventType.ANALYZE_REQUEST)
workflow.state_machine.print_state()
# 请求决策
print("\n3.5 请求决策:")
workflow.state_machine.trigger(EventType.DECIDE_REQUEST)
workflow.state_machine.print_state()
# 请求输出
print("\n3.6 请求输出:")
workflow.state_machine.trigger(EventType.OUTPUT_REQUEST)
workflow.state_machine.print_state()
# 完成
print("\n3.7 完成工作流:")
workflow.state_machine.trigger(EventType.START)
workflow.state_machine.print_state()
# 4. 显示转换历史
print("\n[步骤 4] 状态转换历史")
workflow.print_history()
# 5. 演示错误处理
print("\n[步骤 5] 演示错误处理和恢复")
print("\n5.1 重置状态机:")
workflow.state_machine.reset()
workflow.state_machine.print_state()
print("\n5.2 启动后触发错误:")
workflow.state_machine.trigger(EventType.START)
workflow.state_machine.trigger(EventType.DATA_LOAD_FAILURE)
workflow.state_machine.print_state()
print("\n5.3 尝试重试:")
if workflow.state_machine.can_trigger(EventType.RETRY):
workflow.state_machine.trigger(EventType.RETRY)
workflow.state_machine.print_state()
else:
print(" 无法重试 (已达到最大重试次数)")
print("\n5.4 重置工作流:")
workflow.state_machine.trigger(EventType.RESET)
workflow.state_machine.print_state()
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,961 @@
"""
空间优化示例 (Spatial Optimization Example)
==========================================
本示例展示空间智能系统中的空间优化方法。
空间优化是在空间约束下寻找最优解的过程。
核心概念:
1. 目标函数 - 需要最大化或最小化的目标
2. 约束条件 - 空间和非空间限制
3. 决策变量 - 可控制的空间变量
4. 优化算法 - 求解最优解的方法
5. 帕累托前沿 - 多目标优化中的解集
应用场景:
- 选址优化
- 路径优化
- 空间配置优化
- 资源分配优化
作者: CC4SI 项目组
"""
import math
import random
from typing import List, Dict, Tuple, Optional, Any, Callable
from dataclasses import dataclass, field
from enum import Enum
from abc import ABC, abstractmethod
import heapq
# ============================================================================
# 基础数据结构
# ============================================================================
@dataclass
class Point:
"""二维点"""
x: float
y: float
def distance_to(self, other: 'Point') -> float:
return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
def __repr__(self) -> str:
return f"({self.x:.2f}, {self.y:.2f})"
@dataclass
class DemandPoint:
"""需求点"""
id: str
location: Point
demand: float # 需求量
population: int = 0
def __repr__(self) -> str:
return f"Demand({self.id}, demand={self.demand})"
@dataclass
class Facility:
"""设施"""
id: str
location: Point
capacity: float # 服务能力
fixed_cost: float = 0 # 固定成本
variable_cost: float = 0 # 单位可变成本
def __repr__(self) -> str:
return f"Facility({self.id}, loc={self.location})"
@dataclass
class OptimizationResult:
"""优化结果"""
success: bool
objective_value: float
solution: Any
iterations: int = 0
convergence_history: List[float] = field(default_factory=list)
metadata: Dict[str, Any] = field(default_factory=dict)
# ============================================================================
# 约束条件
# ============================================================================
class ConstraintType(Enum):
"""约束类型"""
EQUALITY = "equality" # 等式约束
INEQUALITY = "inequality" # 不等式约束
BOUNDS = "bounds" # 边界约束
@dataclass
class Constraint:
"""约束条件"""
name: str
constraint_type: ConstraintType
rhs: float # 右端值
lhs_function: Optional[Callable[[Any], float]] = None # 左端函数
def is_satisfied(self, variables: Any, tolerance: float = 1e-6) -> bool:
"""检查约束是否满足"""
if self.lhs_function is None:
return True
lhs = self.lhs_function(variables)
if self.constraint_type == ConstraintType.EQUALITY:
return abs(lhs - self.rhs) < tolerance
elif self.constraint_type == ConstraintType.INEQUALITY:
return lhs <= self.rhs + tolerance
return True
# ============================================================================
# 优化问题定义
# ============================================================================
class OptimizationProblem(ABC):
"""优化问题抽象基类"""
def __init__(self, name: str = ""):
self.name = name
self.constraints: List[Constraint] = []
self.objective_calls = 0
@abstractmethod
def objective(self, variables: Any) -> float:
"""目标函数"""
pass
@abstractmethod
def get_initial_solution(self) -> Any:
"""获取初始解"""
pass
def add_constraint(self, constraint: Constraint) -> None:
"""添加约束"""
self.constraints.append(constraint)
def is_feasible(self, variables: Any) -> bool:
"""检查解是否可行"""
return all(c.is_satisfied(variables) for c in self.constraints)
# ============================================================================
# 贪心算法
# ============================================================================
class GreedyOptimizer:
"""
贪心优化器
每一步选择当前最优的选项。
"""
def __init__(self, problem: OptimizationProblem):
self.problem = problem
def optimize(self, max_iterations: int = 1000) -> OptimizationResult:
"""
执行贪心优化
Args:
max_iterations: 最大迭代次数
Returns:
优化结果
"""
current_solution = self.problem.get_initial_solution()
current_value = self.problem.objective(current_solution)
history = [current_value]
for iteration in range(max_iterations):
# 生成邻居解
neighbors = self._generate_neighbors(current_solution)
# 找最优邻居
best_neighbor = None
best_neighbor_value = float('inf')
for neighbor in neighbors:
if self.problem.is_feasible(neighbor):
value = self.problem.objective(neighbor)
if value < best_neighbor_value:
best_neighbor = neighbor
best_neighbor_value = value
# 如果没有改进,停止
if best_neighbor is None or best_neighbor_value >= current_value:
break
current_solution = best_neighbor
current_value = best_neighbor_value
history.append(current_value)
return OptimizationResult(
success=True,
objective_value=current_value,
solution=current_solution,
iterations=len(history),
convergence_history=history
)
def _generate_neighbors(self, solution: Any) -> List[Any]:
"""生成邻居解 (需要根据具体问题实现)"""
return []
# ============================================================================
# 模拟退火算法
# ============================================================================
class SimulatedAnnealing:
"""
模拟退火算法
一种概率性全局优化算法,能够跳出局部最优。
"""
def __init__(self, problem: OptimizationProblem):
self.problem = problem
def optimize(self,
initial_temp: float = 1000.0,
cooling_rate: float = 0.95,
min_temp: float = 0.01,
max_iterations: int = 10000) -> OptimizationResult:
"""
执行模拟退火优化
Args:
initial_temp: 初始温度
cooling_rate: 冷却速率
min_temp: 最小温度
max_iterations: 最大迭代次数
Returns:
优化结果
"""
current_solution = self.problem.get_initial_solution()
current_value = self.problem.objective(current_solution)
best_solution = current_solution
best_value = current_value
temperature = initial_temp
history = [current_value]
iteration = 0
while temperature > min_temp and iteration < max_iterations:
# 生成邻居解
neighbor = self._generate_neighbor(current_solution)
if self.problem.is_feasible(neighbor):
neighbor_value = self.problem.objective(neighbor)
# 决定是否接受新解
delta = neighbor_value - current_value
if delta < 0 or random.random() < math.exp(-delta / temperature):
current_solution = neighbor
current_value = neighbor_value
# 更新最优解
if current_value < best_value:
best_solution = current_solution
best_value = current_value
history.append(best_value)
temperature *= cooling_rate
iteration += 1
return OptimizationResult(
success=True,
objective_value=best_value,
solution=best_solution,
iterations=iteration,
convergence_history=history
)
def _generate_neighbor(self, solution: Any) -> Any:
"""生成邻居解 (需要根据具体问题实现)"""
return solution
# ============================================================================
# 遗传算法
# =============================================================================
class GeneticAlgorithm:
"""
遗传算法
模拟自然进化的全局优化算法。
"""
def __init__(self, problem: OptimizationProblem):
self.problem = problem
def optimize(self,
population_size: int = 50,
generations: int = 100,
mutation_rate: float = 0.1,
crossover_rate: float = 0.8,
elitism_count: int = 2) -> OptimizationResult:
"""
执行遗传算法优化
Args:
population_size: 种群大小
generations: 迭代代数
mutation_rate: 变异率
crossover_rate: 交叉率
elitism_count: 精英保留数量
Returns:
优化结果
"""
# 初始化种群
population = self._initialize_population(population_size)
history = []
for generation in range(generations):
# 评估适应度
fitness = []
for individual in population:
value = self.problem.objective(individual)
fitness.append(1.0 / (1.0 + value)) # 转换为适应度 (越小越好 -> 越大越好)
# 记录最优值
best_idx = max(range(len(fitness)), key=lambda i: fitness[i])
best_value = self.problem.objective(population[best_idx])
history.append(best_value)
# 选择
selected = self._selection(population, fitness)
# 交叉
offspring = self._crossover(selected, crossover_rate)
# 变异
offspring = self._mutation(offspring, mutation_rate)
# 精英保留
if elitism_count > 0:
elite_indices = sorted(range(len(fitness)),
key=lambda i: fitness[i], reverse=True)[:elitism_count]
for i, idx in enumerate(elite_indices):
offspring[i] = population[idx]
population = offspring
# 返回最优解
final_fitness = [self.problem.objective(ind) for ind in population]
best_idx = min(range(len(final_fitness)), key=lambda i: final_fitness[i])
return OptimizationResult(
success=True,
objective_value=final_fitness[best_idx],
solution=population[best_idx],
iterations=generations,
convergence_history=history
)
def _initialize_population(self, size: int) -> List[Any]:
"""初始化种群"""
return [self.problem.get_initial_solution() for _ in range(size)]
def _selection(self, population: List[Any], fitness: List[float]) -> List[Any]:
"""锦标赛选择"""
selected = []
tournament_size = max(3, len(population) // 10)
for _ in range(len(population)):
# 随机选择tournament_size个个体
contestants = random.sample(list(zip(population, fitness)), tournament_size)
# 选择适应度最高的
winner = max(contestants, key=lambda x: x[1])[0]
selected.append(winner)
return selected
def _crossover(self, population: List[Any], rate: float) -> List[Any]:
"""交叉操作"""
offspring = []
for i in range(0, len(population), 2):
parent1 = population[i]
parent2 = population[i + 1] if i + 1 < len(population) else population[0]
if random.random() < rate:
child1, child2 = self._crossover_operators(parent1, parent2)
else:
child1, child2 = parent1, parent2
offspring.extend([child1, child2])
return offspring[:len(population)]
def _crossover_operators(self, parent1: Any, parent2: Any) -> Tuple[Any, Any]:
"""交叉算子 (需要根据具体问题实现)"""
return parent1, parent2
def _mutation(self, population: List[Any], rate: float) -> List[Any]:
"""变异操作"""
mutated = []
for individual in population:
if random.random() < rate:
mutated.append(self._mutate(individual))
else:
mutated.append(individual)
return mutated
def _mutate(self, individual: Any) -> Any:
"""变异算子 (需要根据具体问题实现)"""
return individual
# ============================================================================
# 选址优化问题
# ============================================================================
class LocationProblem(OptimizationProblem):
"""
设施选址问题
在给定候选位置中选择最优的设施位置组合。
"""
def __init__(self,
demand_points: List[DemandPoint],
candidate_locations: List[Point],
num_facilities: int,
fixed_costs: List[float] = None,
transportation_cost: float = 1.0):
"""
初始化选址问题
Args:
demand_points: 需求点列表
candidate_locations: 候选位置列表
num_facilities: 设施数量
fixed_costs: 各候选位置的固定成本
transportation_cost: 单位运输成本
"""
super().__init__("设施选址问题")
self.demand_points = demand_points
self.candidate_locations = candidate_locations
self.num_facilities = num_facilities
self.fixed_costs = fixed_costs or [0] * len(candidate_locations)
self.transportation_cost = transportation_cost
def objective(self, solution: List[int]) -> float:
"""
计算目标函数值
Args:
solution: 选中的候选位置索引列表
Returns:
总成本
"""
self.objective_calls += 1
if not solution or len(solution) != self.num_facilities:
return float('inf')
total_cost = 0.0
# 固定成本
for idx in solution:
if 0 <= idx < len(self.fixed_costs):
total_cost += self.fixed_costs[idx]
# 运输成本 (每个需求点分配到最近的设施)
for demand in self.demand_points:
min_dist = float('inf')
for facility_idx in solution:
if 0 <= facility_idx < len(self.candidate_locations):
facility_loc = self.candidate_locations[facility_idx]
dist = demand.location.distance_to(facility_loc)
min_dist = min(min_dist, dist)
total_cost += min_dist * demand.demand * self.transportation_cost
return total_cost
def get_initial_solution(self) -> List[int]:
"""获取初始解 (随机选择)"""
n = len(self.candidate_locations)
if n <= self.num_facilities:
return list(range(n))
return random.sample(range(n), self.num_facilities)
class LocationSAOptimizer(SimulatedAnnealing):
"""针对选址问题的模拟退火优化器"""
def _generate_neighbor(self, solution: List[int]) -> List[int]:
"""生成邻居解"""
if not solution:
return solution
neighbor = solution.copy()
n = len(self.problem.candidate_locations)
# 随机选择一个操作
operation = random.choice(['replace', 'swap'])
if operation == 'replace':
# 替换一个设施
idx = random.randint(0, len(neighbor) - 1)
available = [i for i in range(n) if i not in neighbor]
if available:
neighbor[idx] = random.choice(available)
elif operation == 'swap' and len(neighbor) >= 1:
# 交换一个设施
idx = random.randint(0, len(neighbor) - 1)
available = [i for i in range(n) if i not in neighbor]
if available:
neighbor[idx] = random.choice(available)
return neighbor
# ============================================================================
# P-中值问题
# ============================================================================
class PMedianProblem(LocationProblem):
"""
P-中值问题
选择P个设施位置,使需求点到最近设施的总加权距离最小。
"""
def __init__(self,
demand_points: List[DemandPoint],
candidate_locations: List[Point],
p: int):
super().__init__(demand_points, candidate_locations, p, [0] * len(candidate_locations))
# ============================================================================
# 覆盖问题
# ============================================================================
class MaxCoverageProblem(OptimizationProblem):
"""
最大覆盖问题
在给定设施数量限制下,最大化覆盖的需求量。
"""
def __init__(self,
demand_points: List[DemandPoint],
candidate_locations: List[Point],
num_facilities: int,
coverage_radius: float):
"""
初始化最大覆盖问题
Args:
demand_points: 需求点列表
candidate_locations: 候选位置列表
num_facilities: 设施数量
coverage_radius: 覆盖半径
"""
super().__init__("最大覆盖问题")
self.demand_points = demand_points
self.candidate_locations = candidate_locations
self.num_facilities = num_facilities
self.coverage_radius = coverage_radius
# 预计算覆盖关系
self.coverage_matrix = self._compute_coverage()
def _compute_coverage(self) -> List[List[bool]]:
"""计算覆盖矩阵"""
matrix = []
for loc in self.candidate_locations:
coverage = []
for demand in self.demand_points:
covered = loc.distance_to(demand.location) <= self.coverage_radius
coverage.append(covered)
matrix.append(coverage)
return matrix
def objective(self, solution: List[int]) -> float:
"""
计算覆盖的需求量 (负值,因为算法最小化)
Args:
solution: 选中的候选位置索引列表
Returns:
负的覆盖需求量
"""
covered = [False] * len(self.demand_points)
for facility_idx in solution:
if 0 <= facility_idx < len(self.coverage_matrix):
for j, is_covered in enumerate(self.coverage_matrix[facility_idx]):
if is_covered:
covered[j] = True
total_demand = sum(
self.demand_points[j].demand
for j, c in enumerate(covered) if c
)
return -total_demand # 负值用于最小化
def get_initial_solution(self) -> List[int]:
"""获取初始解"""
n = len(self.candidate_locations)
if n <= self.num_facilities:
return list(range(n))
return random.sample(range(n), self.num_facilities)
class MaxCoverageGAOptimizer(GeneticAlgorithm):
"""针对最大覆盖问题的遗传算法优化器"""
def _crossover_operators(self, parent1: List[int], parent2: List[int]) -> Tuple[List[int], List[int]]:
"""单点交叉"""
if not parent1 or not parent2:
return parent1, parent2
size = min(len(parent1), len(parent2))
if size < 2:
return parent1, parent2
point = random.randint(1, size - 1)
child1 = parent1[:point] + [x for x in parent2[point:] if x not in parent1[:point]]
child2 = parent2[:point] + [x for x in parent1[point:] if x not in parent2[:point]]
# 补足长度
all_indices = set(range(len(self.problem.candidate_locations)))
while len(child1) < len(parent1):
available = list(all_indices - set(child1))
if available:
child1.append(random.choice(available))
else:
break
while len(child2) < len(parent2):
available = list(all_indices - set(child2))
if available:
child2.append(random.choice(available))
else:
break
return child1[:len(parent1)], child2[:len(parent2)]
def _mutate(self, individual: List[int]) -> List[int]:
"""变异操作"""
if not individual:
return individual
mutated = individual.copy()
n = len(self.problem.candidate_locations)
# 随机替换一个基因
idx = random.randint(0, len(mutated) - 1)
available = [i for i in range(n) if i not in mutated]
if available:
mutated[idx] = random.choice(available)
return mutated
# ============================================================================
# 路径优化 (TSP)
# ============================================================================
class TSPProblem(OptimizationProblem):
"""
旅行商问题 (TSP)
寻找访问所有城市的最短路径。
"""
def __init__(self, cities: List[Point]):
"""
初始化TSP问题
Args:
cities: 城市位置列表
"""
super().__init__("旅行商问题")
self.cities = cities
self.n = len(cities)
def objective(self, solution: List[int]) -> float:
"""
计算路径总长度
Args:
solution: 城市访问顺序列表
Returns:
路径总长度
"""
if not solution or len(solution) != self.n:
return float('inf')
total = 0.0
for i in range(len(solution)):
from_idx = solution[i]
to_idx = solution[(i + 1) % len(solution)]
total += self.cities[from_idx].distance_to(self.cities[to_idx])
return total
def get_initial_solution(self) -> List[int]:
"""获取初始解 (随机顺序)"""
solution = list(range(self.n))
random.shuffle(solution)
return solution
class TSPSAOptimizer(SimulatedAnnealing):
"""针对TSP的模拟退火优化器"""
def _generate_neighbor(self, solution: List[int]) -> List[int]:
"""生成邻居解 (2-opt交换)"""
if len(solution) < 2:
return solution
neighbor = solution.copy()
# 随机选择两个位置并交换
i, j = random.sample(range(len(neighbor)), 2)
neighbor[i], neighbor[j] = neighbor[j], neighbor[i]
return neighbor
# ============================================================================
# 主程序
# ========================================================================
def main():
"""主程序 - 演示空间优化的使用"""
print("="*70)
print("空间优化示例演示")
print("="*70)
random.seed(42)
# ========================================================================
# 1. P-中值问题 (选址优化)
# ========================================================================
print("\n[部分 1] P-中值问题 - 商场选址优化")
print("-" * 50)
# 生成需求点
demand_points = []
for i in range(20):
demand_points.append(DemandPoint(
id=f"D{i}",
location=Point(random.uniform(0, 100), random.uniform(0, 100)),
demand=random.uniform(50, 200)
))
print(f"\n需求点数量: {len(demand_points)}")
print(f"总需求量: {sum(d.demand for d in demand_points):.1f}")
# 候选位置
candidate_locations = [
Point(20, 20), Point(50, 20), Point(80, 20),
Point(20, 50), Point(50, 50), Point(80, 50),
Point(20, 80), Point(50, 80), Point(80, 80)
]
print(f"\n候选位置数量: {len(candidate_locations)}")
print("候选位置:", [str(loc) for loc in candidate_locations])
# 创建P-中值问题 (选择3个位置)
p_median = PMedianProblem(demand_points, candidate_locations, p=3)
# 使用模拟退火求解
sa_optimizer = LocationSAOptimizer(p_median)
sa_result = sa_optimizer.optimize(
initial_temp=100,
cooling_rate=0.95,
min_temp=0.1,
max_iterations=1000
)
print(f"\n模拟退火结果:")
print(f" 目标函数值: {sa_result.objective_value:.2f}")
print(f" 迭代次数: {sa_result.iterations}")
print(f" 选中的位置: {[str(candidate_locations[i]) for i in sa_result.solution]}")
# 贪心算法对比
greedy_optimizer = GreedyOptimizer(p_median)
# 简单的贪心: 逐步添加最优位置
best_solution = None
best_value = float('inf')
for _ in range(100):
solution = p_median.get_initial_solution()
value = p_median.objective(solution)
if value < best_value:
best_value = value
best_solution = solution
print(f"\n随机搜索对比:")
print(f" 目标函数值: {best_value:.2f}")
print(f" 选中的位置: {[str(candidate_locations[i]) for i in best_solution]}")
# ========================================================================
# 2. 最大覆盖问题
# ========================================================================
print("\n\n[部分 2] 最大覆盖问题 - 5G基站选址")
print("-" * 50)
# 创建更大规模的需求点
coverage_demands = []
for i in range(50):
coverage_demands.append(DemandPoint(
id=f"C{i}",
location=Point(random.uniform(0, 100), random.uniform(0, 100)),
demand=random.uniform(10, 100)
))
coverage_radius = 25
num_bases = 5
max_coverage = MaxCoverageProblem(
coverage_demands,
candidate_locations,
num_bases,
coverage_radius
)
# 使用遗传算法求解
ga_optimizer = MaxCoverageGAOptimizer(max_coverage)
ga_result = ga_optimizer.optimize(
population_size=50,
generations=100,
mutation_rate=0.1
)
covered_demand = -ga_result.objective_value
total_demand = sum(d.demand for d in coverage_demands)
coverage_ratio = covered_demand / total_demand * 100
print(f"\n遗传算法结果:")
print(f" 覆盖需求量: {covered_demand:.1f} / {total_demand:.1f}")
print(f" 覆盖率: {coverage_ratio:.1f}%")
print(f" 迭代次数: {ga_result.iterations}")
print(f" 选中的位置: {[str(candidate_locations[i]) for i in ga_result.solution]}")
# ========================================================================
# 3. 旅行商问题 (TSP)
# ========================================================================
print("\n\n[部分 3] 旅行商问题 - 配送路线优化")
print("-" * 50)
# 生成城市
cities = []
for i in range(15):
cities.append(Point(random.uniform(0, 100), random.uniform(0, 100)))
print(f"\n城市数量: {len(cities)}")
print(f"城市位置: {[str(city) for city in cities[:5]]}...")
tsp = TSPProblem(cities)
tsp_optimizer = TSPSAOptimizer(tsp)
tsp_result = tsp_optimizer.optimize(
initial_temp=1000,
cooling_rate=0.99,
min_temp=0.01,
max_iterations=5000
)
print(f"\n模拟退火结果:")
print(f" 最短路径长度: {tsp_result.objective_value:.2f}")
print(f" 访问顺序: {[cities[i].__repr__() for i in tsp_result.solution[:5]]}...")
# 对比随机解
random_solution = list(range(len(cities)))
random.shuffle(random_solution)
random_length = tsp.objective(random_solution)
print(f"\n随机解对比:")
print(f" 路径长度: {random_length:.2f}")
print(f" 改进: {(1 - tsp_result.objective_value / random_length) * 100:.1f}%")
# ========================================================================
# 4. 多目标优化讨论
# ========================================================================
print("\n\n[部分 4] 多目标优化说明")
print("-" * 50)
print("""
在实际应用中,空间优化往往涉及多个目标:
1. 成本最小化
- 设施建设成本
- 运营成本
- 运输成本
2. 服务最大化
- 覆盖范围
- 服务质量
- 响应时间
3. 公平性
- 服务均等化
- 负载均衡
4. 环境影响
- 最小化污染
- 保护生态
处理方法:
- 加权求和法 (将多目标转为单目标)
- 帕累托优化 (寻找非劣解集)
- 约束法 (将部分目标转为约束)
- 目标规划 (设定目标满意水平)
""")
# ========================================================================
# 5. 收敛过程可视化 (文本)
# ========================================================================
print("\n[部分 5] 优化过程")
print("-" * 50)
if len(ga_result.convergence_history) > 0:
print("\n遗传算法收敛过程:")
steps = min(10, len(ga_result.convergence_history))
step_size = len(ga_result.convergence_history) // steps
for i in range(0, len(ga_result.convergence_history), step_size):
iteration = i
value = -ga_result.convergence_history[i] # 转回正值
print(f" 迭代 {iteration:4d}: 覆盖需求量 = {value:.1f}")
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()
@@ -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()
@@ -0,0 +1,982 @@
"""
空间表征示例 (Spatial Representation Example)
============================================
本示例展示空间智能系统中的空间表征方法。
空间表征是对地理空间现象的抽象和建模,是空间推理的基础。
核心概念:
1. 空间对象模型 - 点、线、面等几何对象
2. 空间关系模型 - 拓扑、距离、方向关系
3. 空间场模型 - 连续表面的表示
4. 空间索引结构 - 加速空间查询
5. 多尺度表征 - 不同详细程度的表示
应用场景:
- 地理信息系统 (GIS)
- 空间数据库
- 空间推理引擎
- 地理可视化
作者: CC4SI 项目组
"""
import math
import json
from typing import List, Dict, Tuple, Optional, Any, Set
from dataclasses import dataclass, field
from enum import Enum
from abc import ABC, abstractmethod
import random
# ============================================================================
# 几何类型与空间对象
# ============================================================================
class GeometryType(Enum):
"""几何类型枚举"""
POINT = "Point"
LINESTRING = "LineString"
POLYGON = "Polygon"
MULTIPOINT = "MultiPoint"
MULTILINESTRING = "MultiLineString"
MULTIPOLYGON = "MultiPolygon"
GEOMETRYCOLLECTION = "GeometryCollection"
class SpatialReference(Enum):
"""空间参考系统"""
WGS84 = "EPSG:4326" # 经纬度
WEB_MERCATOR = "EPSG:3857" # Web墨卡托
CGCS2000 = "EPSG:4490" # 中国大地坐标系统
@dataclass
class Point:
"""点几何对象"""
x: float
y: float
srid: int = 4326 # 空间参考ID
def __iter__(self):
"""支持解包"""
return iter((self.x, self.y))
def __iter__(self):
return iter((self.x, self.y))
def to_tuple(self) -> Tuple[float, float]:
"""转换为元组"""
return (self.x, self.y)
def to_geojson(self) -> Dict:
"""转换为GeoJSON"""
return {
"type": "Point",
"coordinates": [self.x, self.y]
}
def distance_to(self, other: 'Point') -> float:
"""计算到另一个点的欧氏距离"""
return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
def __repr__(self) -> str:
return f"Point({self.x:.4f}, {self.y:.4f})"
@dataclass
class LineString:
"""线几何对象"""
coordinates: List[Point]
srid: int = 4326
@property
def length(self) -> float:
"""计算线的长度"""
if len(self.coordinates) < 2:
return 0.0
total = 0.0
for i in range(len(self.coordinates) - 1):
total += self.coordinates[i].distance_to(self.coordinates[i + 1])
return total
def to_geojson(self) -> Dict:
"""转换为GeoJSON"""
return {
"type": "LineString",
"coordinates": [[p.x, p.y] for p in self.coordinates]
}
def get_point_at(self, ratio: float) -> Point:
"""
获取线上指定比例位置的点
Args:
ratio: 0到1之间的比例值
Returns:
该位置的点
"""
if ratio <= 0:
return self.coordinates[0]
if ratio >= 1:
return self.coordinates[-1]
target_length = self.length * ratio
accumulated = 0.0
for i in range(len(self.coordinates) - 1):
segment_length = self.coordinates[i].distance_to(self.coordinates[i + 1])
if accumulated + segment_length >= target_length:
# 在此段上
segment_ratio = (target_length - accumulated) / segment_length
p1 = self.coordinates[i]
p2 = self.coordinates[i + 1]
return Point(
p1.x + (p2.x - p1.x) * segment_ratio,
p1.y + (p2.y - p1.y) * segment_ratio
)
accumulated += segment_length
return self.coordinates[-1]
def __repr__(self) -> str:
return f"LineString({len(self.coordinates)} points)"
@dataclass
class Polygon:
"""面几何对象"""
exterior: List[Point] # 外环
interiors: List[List[Point]] = field(default_factory=list) # 内环(空洞)
srid: int = 4326
@property
def area(self) -> float:
"""使用鞋带公式计算多边形面积"""
return self._ring_area(self.exterior) - sum(
self._ring_area(interior) for interior in self.interiors
)
def _ring_area(self, ring: List[Point]) -> float:
"""计算环的面积(绝对值)"""
if len(ring) < 3:
return 0.0
area = 0.0
for i in range(len(ring)):
j = (i + 1) % len(ring)
area += ring[i].x * ring[j].y
area -= ring[j].x * ring[i].y
return abs(area) / 2
@property
def centroid(self) -> Point:
"""计算多边形质心"""
if len(self.exterior) < 3:
return self.exterior[0] if self.exterior else Point(0, 0)
# 简化:使用外环的顶点平均值
avg_x = sum(p.x for p in self.exterior) / len(self.exterior)
avg_y = sum(p.y for p in self.exterior) / len(self.exterior)
return Point(avg_x, avg_y)
def contains_point(self, point: Point) -> bool:
"""
判断点是否在多边形内(射线法)
Args:
point: 待判断的点
Returns:
是否在多边形内
"""
return self._point_in_ring(point, self.exterior) and \
all(not self._point_in_ring(point, interior)
for interior in self.interiors)
def _point_in_ring(self, point: Point, ring: List[Point]) -> bool:
"""射线法判断点是否在环内"""
if len(ring) < 3:
return False
x, y = point.x, point.y
inside = False
for i in range(len(ring)):
j = (i + 1) % len(ring)
xi, yi = ring[i].x, ring[i].y
xj, yj = ring[j].x, ring[j].y
# 检查射线与边的交点
if ((yi > y) != (yj > y)) and \
(x < (xj - xi) * (y - yi) / (yj - yi + 1e-10) + xi):
inside = not inside
return inside
def to_geojson(self) -> Dict:
"""转换为GeoJSON"""
coords = [[[p.x, p.y] for p in self.exterior]]
coords.extend([[[p.x, p.y] for p in interior] for interior in self.interiors])
return {
"type": "Polygon",
"coordinates": coords
}
def __repr__(self) -> str:
holes = len(self.interiors)
return f"Polygon({len(self.exterior)} vertices{f', {holes} holes' if holes > 0 else ''})"
@dataclass
class Envelope:
"""包围盒"""
min_x: float
max_x: float
min_y: float
max_y: float
@property
def width(self) -> float:
return self.max_x - self.min_x
@property
def height(self) -> float:
return self.max_y - self.min_y
@property
def area(self) -> float:
return self.width * self.height
@property
def center(self) -> Point:
return Point(
(self.min_x + self.max_x) / 2,
(self.min_y + self.max_y) / 2
)
def contains(self, point: Point) -> bool:
"""判断点是否在包围盒内"""
return (self.min_x <= point.x <= self.max_x and
self.min_y <= point.y <= self.max_y)
def intersects(self, other: 'Envelope') -> bool:
"""判断是否与另一个包围盒相交"""
return not (self.max_x < other.min_x or self.min_x > other.max_x or
self.max_y < other.min_y or self.min_y > other.max_y)
def union(self, other: 'Envelope') -> 'Envelope':
"""计算与另一个包围盒的并集"""
return Envelope(
min_x=min(self.min_x, other.min_x),
max_x=max(self.max_x, other.max_x),
min_y=min(self.min_y, other.min_y),
max_y=max(self.max_y, other.max_y)
)
def __repr__(self) -> str:
return f"Envelope([{self.min_x:.2f}, {self.min_y:.2f}] -> [{self.max_x:.2f}, {self.max_y:.2f}])"
# ============================================================================
# 空间特征对象
# ============================================================================
@dataclass
class SpatialFeature:
"""
空间特征
包含几何和属性信息的完整空间对象。
"""
id: str
geometry: Any # Point, LineString, Polygon等
properties: Dict[str, Any] = field(default_factory=dict)
def to_geojson(self) -> Dict:
"""转换为GeoJSON Feature"""
geom_data = None
if isinstance(self.geometry, Point):
geom_data = self.geometry.to_geojson()
elif isinstance(self.geometry, LineString):
geom_data = self.geometry.to_geojson()
elif isinstance(self.geometry, Polygon):
geom_data = self.geometry.to_geojson()
return {
"type": "Feature",
"id": self.id,
"geometry": geom_data,
"properties": self.properties
}
def to_geojson_collection(self) -> Dict:
"""转换为GeoJSON FeatureCollection"""
return {
"type": "FeatureCollection",
"features": [self.to_geojson()]
}
def envelope(self) -> Envelope:
"""计算包围盒"""
if isinstance(self.geometry, Point):
return Envelope(
self.geometry.x, self.geometry.x,
self.geometry.y, self.geometry.y
)
elif isinstance(self.geometry, (LineString, list)):
coords = self.geometry.coordinates if isinstance(self.geometry, LineString) else self.geometry
xs = [p.x for p in coords]
ys = [p.y for p in coords]
return Envelope(min(xs), max(xs), min(ys), max(ys))
elif isinstance(self.geometry, Polygon):
xs = [p.x for p in self.geometry.exterior]
ys = [p.y for p in self.geometry.exterior]
return Envelope(min(xs), max(xs), min(ys), max(ys))
else:
raise ValueError(f"Unsupported geometry type: {type(self.geometry)}")
def __repr__(self) -> str:
return f"SpatialFeature(id={self.id}, geom={type(self.geometry).__name__})"
# ============================================================================
# 空间关系
# ============================================================================
class SpatialRelation(Enum):
"""空间关系类型"""
EQUALS = "equals"
DISJOINT = "disjoint"
INTERSECTS = "intersects"
TOUCHES = "touches"
CROSSES = "crosses"
WITHIN = "within"
CONTAINS = "contains"
OVERLAPS = "overlaps"
@dataclass
class TopologyRelation:
"""
拓扑关系描述
基于DE-9IM (Dimensionally Extended nine-Intersection Model)模型。
"""
relation: SpatialRelation
confidence: float = 1.0
def __repr__(self) -> str:
return f"TopologyRelation({self.relation.value}, conf={self.confidence:.2f})"
def calculate_relation(geom1: Any, geom2: Any) -> TopologyRelation:
"""
计算两个几何对象的空间关系
Args:
geom1: 第一个几何对象
geom2: 第二个几何对象
Returns:
拓扑关系
"""
# 点-点关系
if isinstance(geom1, Point) and isinstance(geom2, Point):
if geom1.x == geom2.x and geom1.y == geom2.y:
return TopologyRelation(SpatialRelation.EQUALS)
return TopologyRelation(SpatialRelation.DISJOINT)
# 点-多边形关系
if isinstance(geom1, Point) and isinstance(geom2, Polygon):
if geom2.contains_point(geom1):
return TopologyRelation(SpatialRelation.WITHIN)
# 检查是否在边界上
for i in range(len(geom2.exterior)):
p1, p2 = geom2.exterior[i], geom2.exterior[(i + 1) % len(geom2.exterior)]
if point_on_segment(geom1, p1, p2):
return TopologyRelation(SpatialRelation.TOUCHES)
return TopologyRelation(SpatialRelation.DISJOINT)
# 多边形-点关系
if isinstance(geom1, Polygon) and isinstance(geom2, Point):
relation = calculate_relation(geom2, geom1)
if relation.relation == SpatialRelation.WITHIN:
return TopologyRelation(SpatialRelation.CONTAINS)
return relation
# 多边形-多边形关系(简化版:只检查相交)
if isinstance(geom1, Polygon) and isinstance(geom2, Polygon):
env1 = envelope_of_polygon(geom1)
env2 = envelope_of_polygon(geom2)
if not env1.intersects(env2):
return TopologyRelation(SpatialRelation.DISJOINT)
# 简化:检查是否有交点
if geom1.contains_point(geom2.exterior[0]):
return TopologyRelation(SpatialRelation.CONTAINS)
if geom2.contains_point(geom1.exterior[0]):
return TopologyRelation(SpatialRelation.WITHIN)
return TopologyRelation(SpatialRelation.INTERSECTS)
return TopologyRelation(SpatialRelation.DISJOINT)
def point_on_segment(point: Point, seg_start: Point, seg_end: Point,
tolerance: float = 1e-6) -> bool:
"""判断点是否在线段上"""
# 检查点是否在线段的包围盒内
if not (min(seg_start.x, seg_end.x) - tolerance <= point.x <=
max(seg_start.x, seg_end.x) + tolerance and
min(seg_start.y, seg_end.y) - tolerance <= point.y <=
max(seg_start.y, seg_end.y) + tolerance):
return False
# 检查三点共线
cross = (seg_end.x - seg_start.x) * (point.y - seg_start.y) - \
(seg_end.y - seg_start.y) * (point.x - seg_start.x)
return abs(cross) < tolerance
def envelope_of_polygon(polygon: Polygon) -> Envelope:
"""计算多边形的包围盒"""
xs = [p.x for p in polygon.exterior]
ys = [p.y for p in polygon.exterior]
return Envelope(min(xs), max(xs), min(ys), max(ys))
# ============================================================================
# 空间索引
# ============================================================================
class RTreeNode:
"""R树节点"""
def __init__(self, is_leaf: bool = False):
self.is_leaf = is_leaf
self.envelope: Optional[Envelope] = None
self.children: List['RTreeNode'] = []
self.features: List[SpatialFeature] = []
def update_envelope(self):
"""更新节点的包围盒"""
if self.is_leaf and self.features:
envelopes = [f.envelope() for f in self.features]
self.envelope = envelopes[0]
for env in envelopes[1:]:
self.envelope = self.envelope.union(env)
elif not self.is_leaf and self.children:
self.envelope = self.children[0].envelope
for child in self.children[1:]:
if child.envelope:
self.envelope = self.envelope.union(child.envelope)
class RTree:
"""
R树空间索引
用于加速空间查询的树状索引结构。
"""
def __init__(self, max_children: int = 4):
"""
初始化R树
Args:
max_children: 每个节点的最大子节点数
"""
self.max_children = max_children
self.root = RTreeNode(is_leaf=True)
self.size = 0
def insert(self, feature: SpatialFeature) -> None:
"""插入空间特征"""
self._insert(self.root, feature)
self.size += 1
def _insert(self, node: RTreeNode, feature: SpatialFeature) -> None:
"""递归插入"""
feature_env = feature.envelope()
if node.is_leaf:
node.features.append(feature)
node.update_envelope()
# 如果超过容量,分裂节点
if len(node.features) > self.max_children:
self._split(node)
else:
# 选择最佳子节点
best_child = self._choose_best_child(node, feature_env)
self._insert(best_child, feature)
node.update_envelope()
def _choose_best_child(self, node: RTreeNode, env: Envelope) -> RTreeNode:
"""选择插入代价最小的子节点"""
best = None
best_increase = float('inf')
for child in node.children:
if child.envelope is None:
continue
union_env = child.envelope.union(env)
increase = union_env.area - child.envelope.area
if increase < best_increase:
best_increase = increase
best = child
return best or node.children[0]
def _split(self, node: RTreeNode) -> None:
"""分裂节点 (简化版)"""
if node.is_leaf:
# 简单分裂:将特征分成两组
mid = len(node.features) // 2
group1 = node.features[:mid]
group2 = node.features[mid:]
node.features = group1
new_leaf = RTreeNode(is_leaf=True)
new_leaf.features = group2
new_leaf.update_envelope()
# 更新父节点
if node == self.root and not node.children:
# 根节点分裂
new_root = RTreeNode(is_leaf=False)
new_root.children = [node, new_leaf]
new_root.update_envelope()
self.root = new_root
else:
# 简化:不处理非根节点的分裂
pass
def query(self, envelope: Envelope) -> List[SpatialFeature]:
"""查询与包围盒相交的所有特征"""
results = []
self._query(self.root, envelope, results)
return results
def _query(self, node: RTreeNode, envelope: Envelope,
results: List[SpatialFeature]) -> None:
"""递归查询"""
if node.envelope and not node.envelope.intersects(envelope):
return
if node.is_leaf:
for feature in node.features:
if feature.envelope().intersects(envelope):
results.append(feature)
else:
for child in node.children:
self._query(child, envelope, results)
def nearest_neighbor(self, point: Point, k: int = 1) -> List[Tuple[SpatialFeature, float]]:
"""
K近邻查询
Args:
point: 查询点
k: 返回的最近邻数量
Returns:
(特征, 距离) 列表
"""
candidates = []
self._collect_candidates(self.root, candidates)
# 计算距离并排序
distances = []
for feature in candidates:
if isinstance(feature.geometry, Point):
dist = feature.geometry.distance_to(point)
distances.append((feature, dist))
else:
# 非点几何:使用包围盒中心距离
env = feature.envelope()
center = env.center
dist = math.sqrt((center.x - point.x)**2 + (center.y - point.y)**2)
distances.append((feature, dist))
distances.sort(key=lambda x: x[1])
return distances[:k]
def _collect_candidates(self, node: RTreeNode, results: List[SpatialFeature]) -> None:
"""收集所有候选特征"""
if node.is_leaf:
results.extend(node.features)
else:
for child in node.children:
self._collect_candidates(child, results)
# ============================================================================
# 空间场模型
# ============================================================================
class GridField:
"""
栅格场模型
使用规则网格表示连续空间现象。
"""
def __init__(self, bounds: Envelope, rows: int, cols: int,
nodata: float = -9999):
"""
初始化栅格场
Args:
bounds: 空间范围
rows: 行数
cols: 列数
nodata: 无数据值
"""
self.bounds = bounds
self.rows = rows
self.cols = cols
self.nodata = nodata
self.data = [[nodata for _ in range(cols)] for _ in range(rows)]
@property
def cell_width(self) -> float:
"""获取单元格宽度"""
return self.bounds.width / self.cols
@property
def cell_height(self) -> float:
"""获取单元格高度"""
return self.bounds.height / self.rows
def get_cell_index(self, point: Point) -> Optional[Tuple[int, int]]:
"""
获取点对应的栅格索引
Args:
point: 空间点
Returns:
(行索引, 列索引) 或 None
"""
if not self.bounds.contains(point):
return None
col = int((point.x - self.bounds.min_x) / self.cell_width)
row = int((self.bounds.max_y - point.y) / self.cell_height)
col = max(0, min(col, self.cols - 1))
row = max(0, min(row, self.rows - 1))
return (row, col)
def set_value(self, row: int, col: int, value: float) -> None:
"""设置栅格值"""
if 0 <= row < self.rows and 0 <= col < self.cols:
self.data[row][col] = value
def get_value(self, row: int, col: int) -> float:
"""获取栅格值"""
if 0 <= row < self.rows and 0 <= col < self.cols:
return self.data[row][col]
return self.nodata
def get_value_at_point(self, point: Point) -> float:
"""获取点位置的值(最近邻)"""
idx = self.get_cell_index(point)
if idx:
return self.get_value(idx[0], idx[1])
return self.nodata
def interpolate_at_point(self, point: Point) -> float:
"""双线性插值获取点位置的值"""
idx = self.get_cell_index(point)
if not idx:
return self.nodata
row, col = idx
# 获取四个角点的值
values = []
for r in range(row, min(row + 2, self.rows)):
for c in range(col, min(col + 2, self.cols)):
values.append((r, c, self.data[r][c]))
if len(values) < 4 or any(v[2] == self.nodata for v in values):
return self.get_value(row, col) # 回退到最近邻
# 双线性插值
# 简化实现
return self.get_value(row, col)
def get_statistics(self) -> Dict[str, float]:
"""获取统计信息"""
values = [v for row in self.data for v in row if v != self.nodata]
if not values:
return {"count": 0, "min": self.nodata, "max": self.nodata,
"mean": self.nodata, "std": 0}
import statistics
return {
"count": len(values),
"min": min(values),
"max": max(values),
"mean": statistics.mean(values),
"std": statistics.stdev(values) if len(values) > 1 else 0
}
def to_geojson(self) -> Dict:
"""转换为GeoJSON(简化:输出为点集)"""
features = []
for r in range(self.rows):
for c in range(self.cols):
val = self.data[r][c]
if val != self.nodata:
# 计算点坐标
x = self.bounds.min_x + (c + 0.5) * self.cell_width
y = self.bounds.max_y - (r + 0.5) * self.cell_height
features.append({
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [x, y]},
"properties": {"value": val}
})
return {
"type": "FeatureCollection",
"features": features
}
# ============================================================================
# 主程序
# ============================================================================
def main():
"""主程序 - 演示空间表征的使用"""
print("="*70)
print("空间表征示例演示")
print("="*70)
# ========================================================================
# 1. 基础几何对象
# ========================================================================
print("\n[部分 1] 基础几何对象")
print("-" * 50)
# 创建点
point1 = Point(120.5, 30.2)
point2 = Point(121.0, 30.5)
print(f"\n点对象:")
print(f" point1 = {point1}")
print(f" point2 = {point2}")
print(f" 距离 = {point1.distance_to(point2):.4f}")
# 创建线
linestring = LineString([
Point(120.0, 30.0),
Point(120.5, 30.2),
Point(121.0, 30.5),
Point(121.5, 30.3)
])
print(f"\n线对象:")
print(f" {linestring}")
print(f" 长度 = {linestring.length:.4f}")
print(f" 中点 = {linestring.get_point_at(0.5)}")
# 创建多边形
polygon = Polygon(exterior=[
Point(120.0, 30.0),
Point(121.0, 30.0),
Point(121.0, 31.0),
Point(120.0, 31.0),
Point(120.0, 30.0)
])
print(f"\n多边形对象:")
print(f" {polygon}")
print(f" 面积 = {polygon.area:.4f}")
print(f" 质心 = {polygon.centroid}")
# 点包含测试
test_inside = Point(120.5, 30.5)
test_outside = Point(121.5, 30.5)
print(f" {test_inside} 在多边形内: {polygon.contains_point(test_inside)}")
print(f" {test_outside} 在多边形内: {polygon.contains_point(test_outside)}")
# ========================================================================
# 2. 空间特征
# ========================================================================
print("\n\n[部分 2] 空间特征")
print("-" * 50)
feature1 = SpatialFeature(
id="poi_001",
geometry=Point(120.5, 30.2),
properties={
"name": "杭州西湖",
"type": "scenic_spot",
"rating": 4.8
}
)
feature2 = SpatialFeature(
id="zone_001",
geometry=polygon,
properties={
"name": "开发区A",
"type": "industrial_zone",
"area_ha": 10000
}
)
print(f"\n特征1: {feature1}")
print(f" 属性: {feature1.properties}")
print(f" 包围盒: {feature1.envelope()}")
print(f"\n特征2: {feature2}")
print(f" 属性: {feature2.properties}")
print(f" 包围盒: {feature2.envelope()}")
# GeoJSON输出
print(f"\nGeoJSON输出:")
print(json.dumps(feature1.to_geojson(), ensure_ascii=False, indent=2))
# ========================================================================
# 3. 空间关系
# ========================================================================
print("\n\n[部分 3] 空间关系")
print("-" * 50)
# 点与多边形的关系
point_a = Point(120.5, 30.5) # 在多边形内
point_b = Point(121.5, 30.5) # 在多边形外
point_c = Point(120.0, 30.5) # 在边界上
print(f"\n点-多边形关系测试:")
print(f" {point_a} 与多边形: {calculate_relation(point_a, polygon)}")
print(f" {point_b} 与多边形: {calculate_relation(point_b, polygon)}")
print(f" {point_c} 与多边形: {calculate_relation(point_c, polygon)}")
# 多边形-多边形关系
poly2 = Polygon(exterior=[
Point(120.5, 29.5),
Point(121.5, 29.5),
Point(121.5, 30.5),
Point(120.5, 30.5),
Point(120.5, 29.5)
])
print(f"\n多边形-多边形关系:")
print(f" poly1 与 poly2: {calculate_relation(polygon, poly2)}")
# ========================================================================
# 4. 空间索引
# ========================================================================
print("\n\n[部分 4] R树空间索引")
print("-" * 50)
# 创建R树
rtree = RTree(max_children=4)
# 插入一些特征
features = []
for i in range(20):
x = random.uniform(119, 122)
y = random.uniform(29, 32)
feature = SpatialFeature(
id=f"feature_{i:03d}",
geometry=Point(x, y),
properties={"value": random.uniform(0, 100)}
)
features.append(feature)
rtree.insert(feature)
print(f"\n已插入 {rtree.size} 个特征到R树")
# 范围查询
query_env = Envelope(120, 121, 30, 31)
results = rtree.query(query_env)
print(f"\n范围查询 {query_env}:")
print(f" 找到 {len(results)} 个特征")
for f in results[:5]:
print(f" - {f.id}: {f.geometry}")
# K近邻查询
query_point = Point(120.5, 30.5)
neighbors = rtree.nearest_neighbor(query_point, k=5)
print(f"\nK近邻查询 (中心点: {query_point}):")
for i, (f, dist) in enumerate(neighbors, 1):
print(f" {i}. {f.id}: 距离 = {dist:.4f}")
# ========================================================================
# 5. 栅格场模型
# ========================================================================
print("\n\n[部分 5] 栅格场模型")
print("-" * 50)
# 创建栅格场
grid = GridField(
bounds=Envelope(119, 122, 29, 32),
rows=30,
cols=30,
nodata=-9999
)
# 填充一些模拟数据 (温度场)
for r in range(grid.rows):
for c in range(grid.cols):
# 计算坐标
x = grid.bounds.min_x + (c + 0.5) * grid.cell_width
y = grid.bounds.max_y - (r + 0.5) * grid.cell_height
# 模拟温度场 (简单的径向基函数)
center_x, center_y = 120.5, 30.5
dist = math.sqrt((x - center_x)**2 + (y - center_y)**2)
temp = 25 - dist * 2 # 中心25度,向外递减
grid.set_value(r, c, round(temp, 2))
print(f"\n栅格场信息:")
print(f" 范围: {grid.bounds}")
print(f" 尺寸: {grid.rows} x {grid.cols}")
print(f" 单元格大小: {grid.cell_width:.4f} x {grid.cell_height:.4f}")
stats = grid.get_statistics()
print(f" 统计: {stats}")
# 点查询
sample_point = Point(120.5, 30.5)
value = grid.get_value_at_point(sample_point)
print(f"\n点位置 {sample_point} 的值: {value}")
# ========================================================================
# 6. GeoJSON导出
# ========================================================================
print("\n\n[部分 6] GeoJSON导出")
print("-" * 50)
# 创建特征集合
feature_collection = {
"type": "FeatureCollection",
"features": [
feature1.to_geojson(),
feature2.to_geojson()
]
}
print("\n特征集合:")
print(json.dumps(feature_collection, ensure_ascii=False, indent=2))
print("\n" + "="*70)
print("演示完成!")
print("="*70)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+178
View File
@@ -0,0 +1,178 @@
# 可运行代码示例
本目录包含书中提到的所有可运行代码示例,按章节组织。
## 目录结构
```
examples/
├── 00-introduction/ # 导论部分示例
│ └── setup-first-assistant/ # 第一个空间AI助手
├── 01-foundations/ # 基础原理示例
│ ├── modular-system/ # 模块化系统
│ ├── state-machine/ # 状态机实现
│ ├── probability/ # 概率与不确定性
│ ├── feedback/ # 反馈与学习
│ └── hitl/ # 人机协同
├── 02-spatial-intelligence/ # 空间智能示例
│ ├── spatial-representation/ # 空间表征
│ ├── spatial-reasoning/ # 空间推理
│ ├── mcdm/ # 多准则决策
│ ├── optimization/ # 空间优化
│ └── uncertainty/ # 不确定性量化
├── 03-autonomous-design/ # 自主设计示例
│ ├── workflow/ # 工作流编排
│ ├── agents/ # Agent设计
│ ├── skills/ # 技能组合
│ └── memory/ # 记忆管理
└── templates/ # 项目模板
├── basic-skill.md
├── langgraph-workflow.py
└── hitl-checkpoint.py
```
## 快速开始
### 环境配置
```bash
# 克隆仓库
git clone https://github.com/your-org/CC4SI.git
cd CC4SI
# 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# 或
venv\Scripts\activate # Windows
# 安装依赖
pip install -r requirements.txt
```
### 运行示例
```bash
# 导论示例:第一个空间助手
python 00-introduction/setup-first-assistant/spatial_helper.py
# 基础原理示例:状态机
python 01-foundations/state-machine/workflow_state_machine.py
# 空间智能示例:空间推理
python 02-spatial-intelligence/spatial-reasoning/corridor_detection.py
```
## 示例说明
### 00-introduction/setup-first-assistant
**目标**:搭建第一个空间AI助手
**内容**
- `spatial_helper.py` - 空间分析助手类
- `create_sample_data.py` - 创建示例数据
- `README.md` - 使用说明
**运行**
```bash
cd 00-introduction/setup-first-assistant
python spatial_helper.py
```
---
### 01-foundations/modular-system
**目标**:理解模块化系统设计
**内容**
- `pipeline.py` - 空间分析流水线
- `skills.py` - 技能封装示例
- `composition.py` - 函数式组合
---
### 01-foundations/state-machine
**目标**:实现状态机工作流
**内容**
- `workflow_state_machine.py` - 完整状态机实现
- `langgraph_example.py` - LangGraph版本
---
### 02-spatial-intelligence/mcdm
**目标**:多准则决策分析
**内容**
- `ahp.py` - 层次分析法
- `sensitivity.py` - 敏感性分析
- `weighted_overlay.py` - 加权叠加
---
### 03-autonomous-design/workflow
**目标**:工作流编排实践
**内容**
- `dag_workflow.py` - DAG工作流
- `conditional_routing.py` - 条件路由
- `error_handling.py` - 错误处理模式
---
## 依赖要求
```
python>=3.10
# 核心依赖
geopandas>=0.13.0
shapely>=2.0.0
rasterio>=1.3.0
networkx>=3.0
# AI框架
langchain>=0.1.0
langgraph>=0.0.20
anthropic>=0.18.0
# 可视化
matplotlib>=3.7.0
folium>=0.14.0
# 数据处理
pandas>=2.0.0
numpy>=1.24.0
# 科学计算
scipy>=1.10.0
scikit-learn>=1.3.0
```
## 使用建议
1. **边学边做**:阅读相关章节后立即运行对应示例
2. **修改实验**:在理解代码基础上进行修改和实验
3. **错误调试**:遇到错误时尝试独立解决,培养调试能力
4. **记录笔记**:在代码注释或笔记本中记录你的理解
## 贡献示例
欢迎贡献更多示例!
**示例质量要求**
1. 代码可运行
2. 有清晰注释
3. 包含使用说明
4. 说明设计意图
详见 [CONTRIBUTING.md](../CONTRIBUTING.md)
## 许可
所有示例代码遵循项目许可证:CC BY-NC-SA 4.0