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:
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user