# 02.3 多准则决策

## 核心问题

> 当多个目标相互冲突时，如何做出"最优"决策？
> 专家的判断经验如何转化为可计算的权重？

---

## 概念讲解

### 什么是多准则决策分析 (MCDA)

多准则决策分析是一种在多个、通常是冲突的准则下评估和选择替代方案的方法论。

```
┌─────────────────────────────────────────────────────────────┐
│                  多准则决策问题的结构                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    决策目标                          │   │
│  │            "选择最适合生态修复的区域"                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    评估准则                          │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │  生态重要性 │  │  实施可行性 │  │  成本效益   │    │   │
│  │  │   (Weight)  │  │   (Weight)  │  │   (Weight)  │    │   │
│  │  │     0.4     │  │     0.3     │  │     0.3     │    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   替代方案                           │   │
│  │  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐            │   │
│  │  │ 区域A │  │ 区域B │  │ 区域C │  │ 区域D │  ...     │   │
│  │  └──────┘  └──────┘  └──────┘  └──────┘            │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    决策结果                          │   │
│  │              综合得分 + 排名 + 稳健性分析              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### MCDA的核心组成部分

| 组成部分 | 描述 | 空间应用示例 |
|---------|------|-------------|
| **准则 (Criteria)** | 评估标准，反映决策目标 | 生境质量、连通性、建设成本 |
| **权重 (Weights)** | 准则的相对重要性 | 生态重要性0.5，经济成本0.3，社会因素0.2 |
| **得分 (Scores)** | 各方案在各准则下的表现 | 每个栅格的生境适宜性指数 |
| **标准化 (Normalization)** | 将不同单位转换为可比尺度 | 0-1标准化、排名转换 |
| **集结规则 (Aggregation)** | 合并多准则得分的方法 | 加权求和、加权乘积、TOPSIS |

### 常用的MCDA方法

```
┌─────────────────────────────────────────────────────────────┐
│                     MCDA方法分类                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 加权线性组合 (WLC) / 简单加权法                          │
│     ┌─────────────────────────────────────────────────┐     │
│     │  Score = Σ(weight_i × score_i)                   │     │
│     │                                                  │     │
│     │  优点：简单、直观、易于理解                       │     │
│     │  缺点：允许补偿（一个准则的差可被另一个优弥补）   │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
│  2. 层次分析法 (AHP)                                        │
│     ┌─────────────────────────────────────────────────┐     │
│     │  通过成对比较确定权重                             │     │
│     │                                                  │     │
│     │  优点：结构化、一致性检验                         │     │
│     │  缺点：比较次数多(n(n-1)/2)、可能存在不一致       │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
│  3. TOPSIS (逼近理想解排序法)                               │
│     ┌─────────────────────────────────────────────────┐     │
│     │  选择距正理想解最近、负理想解最远的方案           │     │
│     │                                                  │     │
│     │  优点：考虑方案与理想解的相对距离                 │     │
│     │  缺点：对权重敏感                                 │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
│  4. OWA (有序加权平均)                                      │
│     ┌─────────────────────────────────────────────────┐     │
│     │  允许控制"风险态度"(ORness)                       │     │
│     │                                                  │     │
│     │  优点：灵活的风险偏好建模                         │     │
│     │  缺点：需要确定orness参数                         │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 标准化方法

不同准则有不同的量纲，需要标准化：

```
┌─────────────────────────────────────────────────────────────┐
│                      标准化方法                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 最大-最小标准化 (Min-Max)                                │
│     ┌─────────────────────────────────────────────────┐     │
│     │  x_norm = (x - min) / (max - min)                │     │
│     │  适用：有明确最大最小值，线性关系                  │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
│  2. Z-score标准化                                           │
│     ┌─────────────────────────────────────────────────┐     │
│     │  x_norm = (x - mean) / std                       │     │
│     │  适用：正态分布数据，异常值敏感                    │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
│  3. 分位数转换 (Quantile)                                   │
│     ┌─────────────────────────────────────────────────┐     │
│     │  x_norm = percentile_rank(x)                     │     │
│     │  适用：分布未知，需要稳健性                        │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
│  4. 目标导向标准化                                           │
│     ┌─────────────────────────────────────────────────┐     │
│     │  效益型: x_norm = x / target                     │     │
│     │  成本型: x_norm = target / x                     │     │
│     │  适用：有明确目标值                                │     │
│     └─────────────────────────────────────────────────┘     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## 设计原理

### AHP层次分析法

AHP通过成对比较确定权重，具有结构化和一致性检验的优点：

```python
import numpy as np
from typing import List, Dict, Tuple, Optional

class AHPAnalyzer:
    """
    层次分析法 (Analytic Hierarchy Process) 实现

    核心思想：通过成对比较构建判断矩阵，计算权重
    """

    # Saaty标度：1-9及其倒数
    SAATY_SCALE = {
        1: "同等重要",
        3: "稍微重要",
        5: "明显重要",
        7: "强烈重要",
        9: "极端重要",
        2: "介于1和3之间",
        4: "介于3和5之间",
        6: "介于5和7之间",
        8: "介于7和9之间"
    }

    def __init__(self, criteria: List[str]):
        """
        Args:
            criteria: 准则列表
        """
        self.criteria = criteria
        self.n = len(criteria)
        self.comparison_matrix = None
        self.weights = None
        self.consistency_ratio = None

    def set_comparison_matrix(self, matrix: np.ndarray):
        """
        直接设置比较矩阵

        Args:
            matrix: n×n的判断矩阵，matrix[i,j]表示i相对于j的重要性
        """
        if matrix.shape != (self.n, self.n):
            raise ValueError(f"矩阵大小应为{self.n}×{self.n}")

        # 确保对角线为1
        np.fill_diagonal(matrix, 1)

        self.comparison_matrix = matrix

    def build_from_pairs(self, pairs: Dict[Tuple[str, str], float]):
        """
        从成对比较构建矩阵

        Args:
            pairs: {(criterion_i, criterion_j): value} 表示i相对于j的重要性
                   如果j相对于i，则value应为1/value
        """
        self.comparison_matrix = np.eye(self.n)

        criterion_to_idx = {c: i for i, c in enumerate(self.criteria)}

        for (ci, cj), value in pairs.items():
            i, j = criterion_to_idx[ci], criterion_to_idx[cj]
            self.comparison_matrix[i, j] = value
            self.comparison_matrix[j, i] = 1.0 / value

    def compute_weights(self, method: str = 'eigenvector') -> np.ndarray:
        """
        计算权重

        Args:
            method: 'eigenvector'(特征向量法) 或 'geometric'(几何平均法)

        Returns:
            权重向量
        """
        if self.comparison_matrix is None:
            raise ValueError("请先设置比较矩阵")

        if method == 'eigenvector':
            # 特征向量法：求最大特征值对应的特征向量
            eigenvalues, eigenvectors = np.linalg.eig(self.comparison_matrix)
            max_idx = np.argmax(eigenvalues.real)
            weights = eigenvectors[:, max_idx].real
            # 归一化
            weights = weights / weights.sum()

        elif method == 'geometric':
            # 几何平均法
            weights = np.exp(np.log(self.comparison_matrix).mean(axis=1))
            weights = weights / weights.sum()

        else:
            raise ValueError(f"未知方法: {method}")

        self.weights = weights
        return weights

    def compute_consistency(self) -> Dict[str, float]:
        """
        计算一致性指标

        Returns:
            包含一致性相关指标的字典
        """
        if self.comparison_matrix is None or self.weights is None:
            raise ValueError("请先设置比较矩阵并计算权重")

        # 计算最大特征值
        weighted_sum = self.comparison_matrix @ self.weights
        lambda_max = (weighted_sum / self.weights).mean()

        # 一致性指标 CI
        n = self.n
        ci = (lambda_max - n) / (n - 1) if n > 1 else 0

        # 随机一致性指标 RI (Saaty给出的标准值)
        ri_table = {1: 0, 2: 0, 3: 0.58, 4: 0.90, 5: 1.12,
                    6: 1.24, 7: 1.32, 8: 1.41, 9: 1.45, 10: 1.49}
        ri = ri_table.get(n, 1.49)

        # 一致性比率 CR
        cr = ci / ri if ri > 0 else 0

        self.consistency_ratio = cr

        return {
            'lambda_max': lambda_max,
            'CI': ci,
            'RI': ri,
            'CR': cr,
            'consistent': cr < 0.1
        }

    def get_weights_dict(self) -> Dict[str, float]:
        """返回准则-权重字典"""
        if self.weights is None:
            self.compute_weights()
        return {c: w for c, w in zip(self.criteria, self.weights)}

# 使用示例
def example_ahp():
    """AHP使用示例：生态源地选址"""
    criteria = ['生态价值', '连通性', '实施成本', '社会接受度']

    ahp = AHPAnalyzer(criteria)

    # 设置成对比较（示例数据）
    pairs = {
        ('生态价值', '连通性'): 2,      # 生态价值比连通性稍微重要
        ('生态价值', '实施成本'): 5,      # 生态价值比成本明显重要
        ('生态价值', '社会接受度'): 3,    # 生态价值比社会接受度稍微重要
        ('连通性', '实施成本'): 3,        # 连通性比成本稍微重要
        ('连通性', '社会接受度'): 1,      # 连通性与社会接受度同等重要
        ('实施成本', '社会接受度'): 1/2,  # 社会接受度比成本稍微重要
    }

    ahp.build_from_pairs(pairs)
    weights = ahp.compute_weights()
    consistency = ahp.compute_consistency()

    print("准则权重:")
    for c, w in ahp.get_weights_dict().items():
        print(f"  {c}: {w:.3f}")

    print(f"\n一致性比率: {consistency['CR']:.3f}")
    print(f"一致性{'合格' if consistency['consistent'] else '不合格'}")

    return ahp

if __name__ == "__main__":
    example_ahp()
```

### TOPSIS方法

TOPSIS通过计算与理想解的距离进行排序：

```python
import numpy as np
from typing import List, Dict, Callable, Optional

class TOPSISAnalyzer:
    """
    TOPSIS (逼近理想解排序法) 实现

    核心思想：选择距离正理想解最近、负理想解最远的方案
    """

    def __init__(self,
                 criteria: List[str],
                 directions: Optional[List[str]] = None):
        """
        Args:
            criteria: 准则列表
            directions: 每个准则的方向，'benefit'(效益型)或'cost'(成本型)
        """
        self.criteria = criteria
        self.directions = directions or ['benefit'] * len(criteria)
        self.weights = None
        self.normalized_matrix = None
        self.ideal_positive = None
        self.ideal_negative = None
        self.scores = None

    def set_weights(self, weights: np.ndarray):
        """设置权重"""
        if len(weights) != len(self.criteria):
            raise ValueError("权重数量与准则数量不匹配")
        self.weights = np.array(weights) / np.sum(weights)

    def normalize(self, decision_matrix: np.ndarray) -> np.ndarray:
        """
        向量标准化

        Args:
            decision_matrix: m×n矩阵，m个方案，n个准则

        Returns:
            标准化后的矩阵
        """
        # 向量标准化：每个列向量除以其范数
        norm = np.sqrt((decision_matrix ** 2).sum(axis=0))
        normalized = decision_matrix / norm

        # 处理除零
        normalized = np.nan_to_num(normalized)

        self.normalized_matrix = normalized
        return normalized

    def compute_ideal_solutions(self, weighted_matrix: np.ndarray):
        """
        计算正理想解和负理想解

        Args:
            weighted_matrix: 加权标准化决策矩阵
        """
        n = weighted_matrix.shape[1]

        ideal_positive = np.zeros(n)
        ideal_negative = np.zeros(n)

        for j in range(n):
            if self.directions[j] == 'benefit':
                ideal_positive[j] = weighted_matrix[:, j].max()
                ideal_negative[j] = weighted_matrix[:, j].min()
            else:  # cost
                ideal_positive[j] = weighted_matrix[:, j].min()
                ideal_negative[j] = weighted_matrix[:, j].max()

        self.ideal_positive = ideal_positive
        self.ideal_negative = ideal_negative

        return ideal_positive, ideal_negative

    def compute_scores(self, decision_matrix: np.ndarray) -> np.ndarray:
        """
        计算TOPSIS得分

        Args:
            decision_matrix: m×n决策矩阵

        Returns:
            得分向量 (0-1之间，越大越好)
        """
        # 标准化
        normalized = self.normalize(decision_matrix)

        # 加权
        if self.weights is None:
            self.weights = np.ones(len(self.criteria)) / len(self.criteria)

        weighted = normalized * self.weights

        # 计算理想解
        self.compute_ideal_solutions(weighted)

        # 计算距离
        m = weighted.shape[0]
        d_positive = np.zeros(m)
        d_negative = np.zeros(m)

        for i in range(m):
            d_positive[i] = np.sqrt(
                ((weighted[i] - self.ideal_positive) ** 2).sum()
            )
            d_negative[i] = np.sqrt(
                ((weighted[i] - self.ideal_negative) ** 2).sum()
            )

        # 计算相对贴近度
        scores = d_negative / (d_positive + d_negative)
        scores = np.nan_to_num(scores)

        self.scores = scores
        return scores

    def rank(self, decision_matrix: np.ndarray) -> List[int]:
        """
        返回方案排名

        Returns:
            排名索引列表（从最优到最差）
        """
        scores = self.compute_scores(decision_matrix)
        return np.argsort(-scores).tolist()

# 使用示例
def example_topsis():
    """TOPSIS使用示例：生态修复区域选择"""
    # 准则
    criteria = ['生境适宜性', '连通性指数', '实施成本', '社会效益']
    directions = ['benefit', 'benefit', 'cost', 'benefit']

    # 权重
    weights = [0.35, 0.25, 0.20, 0.20]

    # 决策矩阵：5个候选区域在4个准则下的得分
    # 注意：成本已经是负向的（数值越小越好）
    decision_matrix = np.array([
        [0.85, 0.72, 500, 0.65],  # 区域A
        [0.78, 0.85, 450, 0.70],  # 区域B
        [0.92, 0.68, 600, 0.55],  # 区域C
        [0.70, 0.90, 400, 0.80],  # 区域D
        [0.88, 0.75, 550, 0.60],  # 区域E
    ])

    # 对成本型准则进行预处理（转为效益型）
    # 成本越高，得分越低
    cost_col = decision_matrix[:, 2].copy()
    decision_matrix[:, 2] = cost_col.max() - cost_col

    topsis = TOPSISAnalyzer(criteria, directions)
    topsis.set_weights(weights)
    scores = topsis.compute_scores(decision_matrix)
    ranking = topsis.rank(decision_matrix)

    print("区域排名 (TOPSIS):")
    region_names = ['A', 'B', 'C', 'D', 'E']
    for rank, idx in enumerate(ranking, 1):
        print(f"  第{rank}名: 区域{region_names[idx]} (得分: {scores[idx]:.3f})")

    return topsis

if __name__ == "__main__":
    example_topsis()
```

### 敏感性分析

敏感性分析是MCDA的重要组成部分，评估权重变化对决策结果的影响：

```python
import numpy as np
from typing import List, Dict, Tuple, Callable
import matplotlib.pyplot as plt

class SensitivityAnalyzer:
    """
    权重敏感性分析

    评估权重变化对决策结果的影响程度
    """

    def __init__(self,
                 decision_matrix: np.ndarray,
                 weights: np.ndarray,
                 criteria: List[str],
                 directions: List[str] = None):
        """
        Args:
            decision_matrix: 决策矩阵
            weights: 初始权重
            criteria: 准则名称
            directions: 准则方向
        """
        self.decision_matrix = decision_matrix
        self.base_weights = np.array(weights) / np.sum(weights)
        self.criteria = criteria
        self.directions = directions or ['benefit'] * len(criteria)

    def one_at_a_time(self, variation: float = 0.1) -> Dict[str, Dict]:
        """
        单因素敏感性分析 (OAT)

        每次改变一个准则的权重，观察排名变化

        Args:
            variation: 权重变化幅度（相对于原权重的比例）

        Returns:
            每个准则变化时的排名变化
        """
        n = len(self.criteria)
        base_ranking = self._compute_ranking(self.base_weights)

        results = {}

        for i, criterion in enumerate(self.criteria):
            results[criterion] = {
                'weight_variations': [],
                'rankings': []
            }

            # 增加权重
            for delta in np.linspace(-variation, variation, 21):
                new_weights = self.base_weights.copy()

                # 调整第i个权重
                new_weights[i] = self.base_weights[i] * (1 + delta)

                # 重新归一化（保持和为1）
                if new_weights.sum() > 0:
                    new_weights = new_weights / new_weights.sum()
                else:
                    new_weights = self.base_weights.copy()

                # 计算新排名
                ranking = self._compute_ranking(new_weights)

                results[criterion]['weight_variations'].append(delta)
                results[criterion]['rankings'].append(ranking)

        return results

    def tornado_analysis(self, variation: float = 0.2) -> Dict:
        """
        龙卷风图分析

        计算每个准则权重变化对最优方案得分的影响

        Args:
            variation: 权重变化幅度

        Returns:
            龙卷风图数据
        """
        from topsis import TOPSISAnalyzer

        base_scores = self._compute_scores(self.base_weights)
        base_best_score = base_scores.max()

        impacts = []

        for i, criterion in enumerate(self.criteria):
            impact_high = 0
            impact_low = 0

            # 增加权重
            for sign in [1, -1]:
                new_weights = self.base_weights.copy()
                new_weights[i] = self.base_weights[i] * (1 + sign * variation)
                new_weights = new_weights / new_weights.sum()

                new_scores = self._compute_scores(new_weights)
                new_best = new_scores.max()

                change = new_best - base_best_score

                if sign > 0:
                    impact_high = change
                else:
                    impact_low = change

            impacts.append({
                'criterion': criterion,
                'high': impact_high,
                'low': impact_low,
                'range': impact_high - impact_low
            })

        # 按影响范围排序
        impacts.sort(key=lambda x: abs(x['range']), reverse=True)

        return {
            'base_score': base_best_score,
            'impacts': impacts
        }

    def monte_carlo(self, n_simulations: int = 1000,
                    weight_std: float = 0.1) -> Dict:
        """
        蒙特卡洛敏感性分析

        随机扰动权重，观察结果的统计分布

        Args:
            n_simulations: 模拟次数
            weight_std: 权重扰动的标准差

        Returns:
            统计结果
        """
        n_alternatives = self.decision_matrix.shape[0]
        rank_counts = np.zeros(n_alternatives, dtype=int)
        scores_history = []

        for _ in range(n_simulations):
            # 生成随机权重
            random_weights = np.random.normal(
                self.base_weights,
                weight_std
            )

            # 确保非负并归一化
            random_weights = np.maximum(random_weights, 0)
            random_weights = random_weights / random_weights.sum()

            # 计算排名
            ranking = self._compute_ranking(random_weights)
            rank_counts[ranking[0]] += 1  # 统计第一名

            # 记录得分
            scores = self._compute_scores(random_weights)
            scores_history.append(scores)

        # 计算每个方案排第一的概率
        probabilities = rank_counts / n_simulations

        # 得分的统计量
        scores_array = np.array(scores_history)
        scores_stats = {
            'mean': scores_array.mean(axis=0),
            'std': scores_array.std(axis=0),
            'min': scores_array.min(axis=0),
            'max': scores_array.max(axis=0),
            'percentile_5': np.percentile(scores_array, 5, axis=0),
            'percentile_95': np.percentile(scores_array, 95, axis=0)
        }

        return {
            'rank_probabilities': probabilities,
            'scores_stats': scores_stats
        }

    def _compute_scores(self, weights: np.ndarray) -> np.ndarray:
        """计算给定权重下的得分"""
        # 简单加权求和
        normalized = self._normalize_matrix(self.decision_matrix)
        scores = (normalized * weights).sum(axis=1)
        return scores

    def _compute_ranking(self, weights: np.ndarray) -> List[int]:
        """计算给定权重下的排名"""
        scores = self._compute_scores(weights)
        return np.argsort(-scores).tolist()

    def _normalize_matrix(self, matrix: np.ndarray) -> np.ndarray:
        """标准化决策矩阵"""
        normalized = matrix.copy()
        for j, direction in enumerate(self.directions):
            col = matrix[:, j]
            if direction == 'benefit':
                normalized[:, j] = (col - col.min()) / (col.max() - col.min())
            else:  # cost
                normalized[:, j] = (col.max() - col) / (col.max() - col.min())
        return normalized

# 使用示例
def example_sensitivity():
    """敏感性分析示例"""
    # 决策矩阵
    decision_matrix = np.array([
        [0.85, 0.72, 0.65],
        [0.78, 0.85, 0.70],
        [0.92, 0.68, 0.55],
        [0.70, 0.90, 0.80],
    ])

    weights = [0.4, 0.35, 0.25]
    criteria = ['生态价值', '连通性', '可行性']

    analyzer = SensitivityAnalyzer(
        decision_matrix, weights, criteria,
        directions=['benefit', 'benefit', 'benefit']
    )

    # 蒙特卡洛分析
    mc_results = analyzer.monte_carlo(n_simulations=1000, weight_std=0.1)

    print("各方案排名第一的概率:")
    for i, prob in enumerate(mc_results['rank_probabilities']):
        print(f"  方案{i+1}: {prob:.1%}")

    # 龙卷风分析
    tornado = analyzer.tornado_analysis(variation=0.2)

    print("\n准则重要性排序:")
    for impact in tornado['impacts']:
        print(f"  {impact['criterion']}: 影响±{abs(impact['range']):.3f}")

    return analyzer
```

---

## 代码示例

### 空间多准则决策分析

```python
"""
空间多准则决策分析完整示例
"""
import numpy as np
import matplotlib.pyplot as plt
from typing import List, Dict, Optional, Callable
from dataclasses import dataclass

@dataclass
class MCDCriteria:
    """决策准则"""
    name: str
    direction: str  # 'benefit' 或 'cost'
    weight: float
    raster: Optional[np.ndarray] = None  # 栅格数据
    normalize_func: Optional[Callable] = None  # 标准化函数

class SpatialMCDA:
    """
    空间多准则决策分析

    用于栅格数据的多准则评估
    """

    def __init__(self, shape: tuple, nodata: float = -9999):
        """
        Args:
            shape: 栅格形状 (rows, cols)
            nodata: 无数据值
        """
        self.shape = shape
        self.nodata = nodata
        self.criteria: List[MCDCriteria] = []
        self.result: Optional[np.ndarray] = None
        self.mask: Optional[np.ndarray] = None

    def add_criterion(self, name: str, direction: str, weight: float,
                      raster: Optional[np.ndarray] = None):
        """
        添加准则

        Args:
            name: 准则名称
            direction: 'benefit'(越大越好) 或 'cost'(越小越好)
            weight: 权重
            raster: 栅格数据
        """
        criterion = MCDCriteria(
            name=name,
            direction=direction,
            weight=weight,
            raster=np.asarray(raster) if raster is not None else None
        )
        self.criteria.append(criterion)

    def set_mask(self, mask: np.ndarray):
        """
        设置分析掩模

        Args:
            mask: True表示有效区域
        """
        self.mask = np.asarray(mask, dtype=bool)

    def normalize_all(self, method: str = 'minmax') -> List[np.ndarray]:
        """
        标准化所有准则

        Args:
            method: 'minmax', 'zscore', 或 'quantile'

        Returns:
            标准化后的栅格列表
        """
        normalized = []

        for criterion in self.criteria:
            if criterion.raster is None:
                raise ValueError(f"准则 {criterion.name} 没有栅格数据")

            data = criterion.raster.copy()

            # 处理无数据值
            valid_mask = data != self.nodata

            if method == 'minmax':
                # 最大-最小标准化
                valid_data = data[valid_mask]
                min_val, max_val = valid_data.min(), valid_data.max()

                if criterion.direction == 'benefit':
                    data[valid_mask] = (valid_data - min_val) / (max_val - min_val)
                else:  # cost
                    data[valid_mask] = (max_val - valid_data) / (max_val - min_val)

            elif method == 'zscore':
                # Z-score标准化
                valid_data = data[valid_mask]
                mean_val, std_val = valid_data.mean(), valid_data.std()

                data[valid_mask] = (valid_data - mean_val) / std_val

                # 转换到0-1
                data[valid_mask] = (data[valid_mask] - data[valid_mask].min()) / \
                                   (data[valid_mask].max() - data[valid_mask].min())

            elif method == 'quantile':
                # 分位数转换
                valid_data = data[valid_mask]
                ranks = np.argsort(np.argsort(valid_data))
                data[valid_mask] = ranks / (len(ranks) - 1)

                if criterion.direction == 'cost':
                    data[valid_mask] = 1 - data[valid_mask]

            else:
                raise ValueError(f"未知方法: {method}")

            # 应用掩模
            if self.mask is not None:
                data[~self.mask] = self.nodata

            normalized.append(data)

        return normalized

    def compute(self, method: str = 'wlc') -> np.ndarray:
        """
        计算综合评估结果

        Args:
            method: 'wlc'(加权线性组合) 或 'geometric'(几何平均)

        Returns:
            评估结果栅格
        """
        # 标准化
        normalized = self.normalize_all()

        # 归一化权重
        weights = np.array([c.weight for c in self.criteria])
        weights = weights / weights.sum()

        # 初始化结果
        result = np.zeros(self.shape)

        if method == 'wlc':
            # 加权线性组合
            for i, norm_data in enumerate(normalized):
                valid_mask = norm_data != self.nodata
                result[valid_mask] += norm_data[valid_mask] * weights[i]

        elif method == 'geometric':
            # 加权几何平均
            result.fill(1)
            for i, norm_data in enumerate(normalized):
                valid_mask = norm_data != self.nodata
                result[valid_mask] *= np.power(norm_data[valid_mask], weights[i])

        else:
            raise ValueError(f"未知方法: {method}")

        # 应用掩模
        if self.mask is not None:
            result[~self.mask] = self.nodata

        self.result = result
        return result

    def get_rankings(self, n_classes: int = 5) -> np.ndarray:
        """
        将连续结果分级

        Args:
            n_classes: 分类数

        Returns:
            分级结果 (1=最低, n_classes=最高)
        """
        if self.result is None:
            self.compute()

        result = self.result.copy()
        valid_mask = result != self.nodata

        # 分位数分级
        valid_data = result[valid_mask]
        thresholds = np.percentile(valid_data,
                                   np.linspace(0, 100, n_classes + 1))

        rankings = np.zeros_like(result, dtype=int)
        for i in range(n_classes):
            mask = (result >= thresholds[i]) & (result < thresholds[i + 1])
            rankings[mask] = i + 1
        rankings[result >= thresholds[-1]] = n_classes
        rankings[~valid_mask] = 0

        return rankings

    def sensitivity_analysis(self, criterion_name: str,
                             weight_variation: float = 0.2) -> np.ndarray:
        """
        单准则权重敏感性分析

        Args:
            criterion_name: 要分析的准则名称
            weight_variation: 权重变化范围 (±)

        Returns:
            不同权重下的结果差异
        """
        base_result = self.compute()
        variations = []

        # 找到准则索引
        criterion_idx = None
        for i, c in enumerate(self.criteria):
            if c.name == criterion_name:
                criterion_idx = i
                break

        if criterion_idx is None:
            raise ValueError(f"未找到准则: {criterion_name}")

        # 变化权重
        for delta in np.linspace(-weight_variation, weight_variation, 11):
            # 修改权重
            original_weight = self.criteria[criterion_idx].weight
            self.criteria[criterion_idx].weight = original_weight * (1 + delta)

            # 重新计算
            new_result = self.compute()
            variations.append(new_result.copy())

            # 恢复权重
            self.criteria[criterion_idx].weight = original_weight

        # 计算标准差作为敏感性度量
        variations = np.array(variations)
        sensitivity = np.std(variations, axis=0)

        return sensitivity

# 使用示例
def example_spatial_mcda():
    """空间多准则决策分析示例"""
    # 创建示例数据
    rows, cols = 100, 100
    mcda = SpatialMCDA(shape=(rows, cols))

    # 创建掩模（例如：排除研究区外的区域）
    mask = np.zeros((rows, cols), dtype=bool)
    mask[20:80, 20:80] = True
    mcda.set_mask(mask)

    # 添加准则
    np.random.seed(42)

    # 准则1：生境适宜性（效益型，权重0.4）
    habitat = np.random.rand(rows, cols) * 0.6 + 0.2
    habitat[~mask] = mcda.nodata
    mcda.add_criterion('生境适宜性', 'benefit', 0.4, habitat)

    # 准则2：连通性（效益型，权重0.3）
    connectivity = np.random.rand(rows, cols) * 0.7 + 0.15
    connectivity[~mask] = mcda.nodata
    mcda.add_criterion('连通性', 'benefit', 0.3, connectivity)

    # 准则3：实施成本（成本型，权重0.3）
    cost = np.random.rand(rows, cols) * 0.8 + 0.1
    cost[~mask] = mcda.nodata
    mcda.add_criterion('实施成本', 'cost', 0.3, cost)

    # 计算综合评估
    result = mcda.compute(method='wlc')

    print(f"综合评估结果:")
    print(f"  有效区域均值: {result[mask].mean():.3f}")
    print(f"  有效区域范围: [{result[mask].min():.3f}, {result[mask].max():.3f}]")

    # 分级
    rankings = mcda.get_rankings(n_classes=5)
    print(f"\n分级统计:")
    for i in range(1, 6):
        count = (rankings == i).sum()
        print(f"  等级{i}: {count} 个像元")

    # 敏感性分析
    sensitivity = mcda.sensitivity_analysis('生境适宜性', weight_variation=0.3)
    print(f"\n敏感性分析 (生境适宜性权重±30%):")
    print(f"  结果最大变化: {sensitivity[mask].max():.3f}")
    print(f"  结果平均变化: {sensitivity[mask].mean():.3f}")

    return mcda

if __name__ == "__main__":
    example_spatial_mcda()
```

---

## 案例分析

### ENAgent中的生态系统服务评估

ENAgent使用多准则决策分析评估生态系统的综合服务价值：

```python
class EcosystemServiceAssessment:
    """
    生态系统服务评估模块

    基于多准则决策分析评估区域生态系统服务价值
    """

    # 生态系统服务类型
    SERVICE_TYPES = {
        'provisioning': '供给服务',      # 食物、淡水、木材等
        'regulating': '调节服务',         # 气候调节、洪水调节等
        'cultural': '文化服务',           # 游憩、美学价值等
        'supporting': '支持服务'          # 土壤形成、营养循环等
    }

    def __init__(self, study_area_boundary):
        """
        Args:
            study_area_boundary: 研究区边界(Shapely Polygon)
        """
        self.boundary = study_area_boundary
        self.services = {}
        self.weights = {}
        self.assessment_result = None

    def add_service_layer(self, service_type: str,
                          value_layer: np.ndarray,
                          weight: float = 1.0):
        """
        添加生态系统服务图层

        Args:
            service_type: 服务类型
            value_layer: 价值评估栅格
            weight: 该服务的权重
        """
        self.services[service_type] = value_layer
        self.weights[service_type] = weight

    def assess_habitat_quality(self, land_use_raster,
                                threat_layers: Dict[str, np.ndarray],
                                sensitivity_table: Dict) -> np.ndarray:
        """
        评估生境质量 (基于InVEST模型思想)

        Args:
            land_use_raster: 土地利用栅格
            threat_layers: 威胁因子图层 {'name': array}
            sensitivity_table: 土地类型对威胁的敏感性

        Returns:
            生境质量指数栅格
        """
        rows, cols = land_use_raster.shape
        habitat_quality = np.zeros((rows, cols))

        # 计算退化程度
        degradation = np.zeros((rows, cols))

        for threat_name, threat_layer in threat_layers.items():
            # 对每个威胁因子计算影响
            # 这里简化处理，实际需要考虑距离衰减等
            threat_impact = threat_layer * 0.5  # 简化权重

            for land_type, sensitivity in sensitivity_table.items():
                mask = (land_use_raster == land_type)
                # 获取该土地类型对当前威胁的敏感性
                sens = sensitivity.get(threat_name, 0.5)
                degradation[mask] += threat_impact[mask] * sens

        # 退化程度归一化到0-1
        degradation = np.clip(degradation, 0, 1)

        # 计算生境质量
        for land_type in np.unique(land_use_raster):
            mask = (land_use_raster == land_type)
            # 生境适宜性 (简化: 林地=高, 建设用地=低)
            habitat_suitability = self._get_habitat_suitability(land_type)
            habitat_quality[mask] = habitat_suitability * (1 - degradation[mask])

        return habitat_quality

    def assess_carbon_storage(self, land_use_raster,
                              carbon_table: Dict[int, Dict[str, float]]) -> np.ndarray:
        """
        评估碳储量

        Args:
            land_use_raster: 土地利用栅格
            carbon_table: {土地类型: {'above': x, 'below': y, 'soil': z}}

        Returns:
            碳储量栅格
        """
        carbon_storage = np.zeros_like(land_use_raster, dtype=float)

        for land_type, carbon_values in carbon_table.items():
            mask = (land_use_raster == land_type)
            # 总碳储量 = 地上 + 地下 + 土壤
            total_carbon = (carbon_values.get('above', 0) +
                           carbon_values.get('below', 0) +
                           carbon_values.get('soil', 0))
            carbon_storage[mask] = total_carbon

        return carbon_storage

    def multi_service_assessment(self) -> np.ndarray:
        """
        多服务综合评估

        Returns:
            综合生态系统服务指数
        """
        if not self.services:
            raise ValueError("请先添加服务图层")

        # 归一化权重
        total_weight = sum(self.weights.values())

        # 初始化结果
        shape = next(iter(self.services.values())).shape
        result = np.zeros(shape)

        # 加权求和
        for service_type, layer in self.services.items():
            weight = self.weights[service_type] / total_weight
            result += layer * weight

        self.assessment_result = result
        return result

    def identify_priority_areas(self, threshold_percentile: float = 0.75) -> np.ndarray:
        """
        识别优先保护区域

        Args:
            threshold_percentile: 分位数阈值

        Returns:
            优先区域布尔栅格
        """
        if self.assessment_result is None:
            self.multi_service_assessment()

        threshold = np.percentile(
            self.assessment_result[self.assessment_result > 0],
            threshold_percentile * 100
        )

        priority_areas = self.assessment_result >= threshold
        return priority_areas

    def _get_habitat_suitability(self, land_type) -> float:
        """获取土地类型的生境适宜性"""
        # 简化版：根据土地类型返回适宜性
        suitability_map = {
            1: 1.0,    # 森林
            2: 0.8,    # 灌木
            3: 0.6,    # 草地
            4: 0.4,    # 湿地
            5: 0.2,    # 耕地
            6: 0.0,    # 建设用地
            7: 0.3     # 裸地
        }
        return suitability_map.get(land_type, 0.5)

# 使用示例
def example_enagent_assessment():
    """ENAgent生态系统服务评估示例"""
    from shapely.geometry import box

    # 创建研究区
    study_area = box(0, 0, 10000, 10000)

    # 创建评估器
    assessor = EcosystemServiceAssessment(study_area)

    # 模拟土地利用数据
    rows, cols = 100, 100
    np.random.seed(42)
    land_use = np.random.choice([1, 2, 3, 4, 5, 6], size=(rows, cols), p=[0.3, 0.15, 0.2, 0.1, 0.15, 0.1])

    # 评估生境质量
    threat_layers = {
        'roads': np.random.rand(rows, cols) * 0.8,
        'urban': np.random.rand(rows, cols) * 0.6
    }

    sensitivity = {
        1: {'roads': 0.3, 'urban': 0.8},  # 森林对城市扩张敏感
        2: {'roads': 0.5, 'urban': 0.6},
        3: {'roads': 0.7, 'urban': 0.4},
        4: {'roads': 0.8, 'urban': 0.9},
        5: {'roads': 0.2, 'urban': 0.1},
        6: {'roads': 0.0, 'urban': 0.0}
    }

    habitat_quality = assessor.assess_habitat_quality(
        land_use, threat_layers, sensitivity
    )

    # 评估碳储量
    carbon_table = {
        1: {'above': 150, 'below': 40, 'soil': 100},
        2: {'above': 60, 'below': 20, 'soil': 80},
        3: {'above': 20, 'below': 50, 'soil': 100},
        4: {'above': 80, 'below': 100, 'soil': 150},
        5: {'above': 10, 'below': 10, 'soil': 80},
        6: {'above': 0, 'below': 0, 'soil': 20}
    }

    carbon_storage = assessor.assess_carbon_storage(land_use, carbon_table)

    # 添加服务图层
    assessor.add_service_layer('habitat_quality', habitat_quality, weight=0.5)
    assessor.add_service_layer('carbon_storage', carbon_storage / 300, weight=0.3)

    # 水文调节服务（模拟）
    water_regulation = np.random.rand(rows, cols) * 0.5 + 0.3
    assessor.add_service_layer('water_regulation', water_regulation, weight=0.2)

    # 综合评估
    result = assessor.multi_service_assessment()

    print("生态系统服务综合评估:")
    print(f"  平均服务指数: {result.mean():.3f}")
    print(f"  高服务区域 (>0.6): {(result > 0.6).sum()} 个像元")

    # 识别优先区域
    priority = assessor.identify_priority_areas(threshold_percentile=0.75)
    print(f"  优先保护区域: {priority.sum()} 个像元 ({priority.sum()/priority.size*100:.1f}%)")

    return assessor

if __name__ == "__main__":
    example_enagent_assessment()
```

---

## 反思与延伸

### 思考问题

1. **权重来源**：专家判断、文献参考、数据分析，哪种权重确定方式更可靠？

2. **准则独立性**：当准则之间存在相关性时，MCDA结果会怎样变化？

3. **不确定性**：除了权重，数据本身的不确定性如何在MCDA中考虑？

4. **阈值效应**：某些准则是否存在关键阈值？如何处理？

5. **空间异质性**：不同区域的权重是否应该不同？

### 延伸阅读

- **"Multi-Criteria Decision Analysis: Methods and Software"** - MCDA方法综述
- **"Spatial Decision Support Systems"** - 空间决策支持系统
- InVEST模型文档 - 生态系统服务评估实践
- **"Decision Analysis"** (Howard) - 决策分析基础理论

---

## 关键要点

1. **MCDA处理多目标冲突**：通过系统化方法整合多个准则，支持复杂决策

2. **权重是核心**：权重的确定是MCDA的关键，需要专家知识或数据支持

3. **标准化必不可少**：不同量纲的准则必须标准化才能比较和合并

4. **敏感性分析很重要**：评估权重变化对结果的影响，增强决策稳健性

5. **与GIS结合才有意义**：空间MCDA将决策框架落实到地理空间
