In [1]:
import numpy as np
import networkx as nx
import pandas as pd
from collections import defaultdict
In [2]:
class DecisionAgent:
"""
The decision agent class for the decision-making process.
"""
def __init__(self, num_ele):
self.elements = ['S' + str(i) for i in range(1, num_ele + 1)] # list of influential elements
self.adjacency_matrix = np.zeros((num_ele, num_ele)) # adjacency matrix
def set_relations(self, relations_by_ele):
"""
add the relations between the influential elements
"""
relations_pairs = [] # convert to the list of relations pairs
for key in relations_by_ele.keys():
for value in relations_by_ele[key]:
if value != '':
relations_pairs.append([key, value])
print(f">>> relations_pairs:\n{relations_pairs}")
# set the adjacency matrix
for relation in relations_pairs:
self.adjacency_matrix[self.elements.index(relation[0])][self.elements.index(relation[1])] = 1
def get_elements(self):
"""
return the list of influential elements
"""
return self.elements
def get_adjacency_matrix(self):
"""
calculate the adjacency matrix
"""
print(f">>> 原始矩阵 A:\n{self.adjacency_matrix}")
# add self-loop: A = A + I
num = self.adjacency_matrix.shape[0]
self.adjacency_matrix = self.adjacency_matrix + np.eye(num)
print(f">>> 邻接矩阵 I:\n{self.adjacency_matrix}")
def get_reachable_matrix(self):
"""
Get the reachable matrix by the adjacency matrix
"""
# set the reachable matrix
reachable_matrix, step = self.adjacency_matrix.copy(), 1
print(f">>> M{step} = I:\n{reachable_matrix}")
# move to the next step
new_reachable_matrix = np.dot(reachable_matrix, self.adjacency_matrix)
new_reachable_matrix[new_reachable_matrix > 0] = 1
while not np.array_equal(reachable_matrix, new_reachable_matrix):
reachable_matrix = new_reachable_matrix
# move to the next step
new_reachable_matrix = np.dot(reachable_matrix, self.adjacency_matrix)
new_reachable_matrix[new_reachable_matrix > 0] = 1
print(f">>> M{step+1} = M{step} * I:\n{reachable_matrix}")
step += 1
print(f">>> M{step+1} = M{step} * I:\n{reachable_matrix}")
self.reachable_matrix = reachable_matrix
print(f">>> M{step+1} = M{step} ==> R = {step}\n>>> 可达矩阵 M:\n{reachable_matrix}")
def get_reduced_matrix(self):
"""
Get the reduced matrix by the strongly connected components
"""
# construct the drected graph
self.graph = nx.from_numpy_array(self.reachable_matrix, create_using=nx.DiGraph)
# get the strongly connected components
scc = list(nx.strongly_connected_components(self.graph))
# sort the strongly connected components
scc_sorted = [sorted(component) for component in scc]
# sort the strongly connected components by the first element
scc_sorted.sort(key=lambda x: x[0])
# get the representative nodes by the minimum element in each component
representative_nodes = [min(component) for component in scc_sorted]
self.reduced_matrix = self.reachable_matrix[np.ix_(representative_nodes, representative_nodes)]
# display the results
scc_sorted = [[node + 1 for node in component] for component in scc_sorted]
representative_nodes = [node + 1 for node in representative_nodes]
self.headers = ["+".join(f"S{node}" for node in sorted(component)) for component in scc_sorted]
print(">>> 强连通分量 SCC:")
for i, component in enumerate(scc_sorted, start=1):
print(f"\t>>> 连通分量 {i}: {[f'S{node}' for node in sorted(component)]}")
print(f">>> 代表要素:\n{[f'S{node}' for node in sorted(representative_nodes)]}")
reduced_df = pd.DataFrame(self.reduced_matrix.astype(int), index=self.headers, columns=self.headers)
print(f">>> 缩减矩阵:\n{reduced_df}")
print(f">>> 要素表头:\n{self.headers}")
def compute_outdegree(self, matrix, headers):
"""
Compute the out-degree of each node in the matrix
"""
out_degrees = {header: 0 for header in headers}
for i in range(matrix.shape[0]):
for j in range(matrix.shape[1]):
if matrix[i, j] == 1:
out_degrees[headers[i]] += 1
return out_degrees
def compute_indegree(self, matrix, headers):
"""
Compute the in-degree of each node in the matrix
"""
in_degrees = {header: 0 for header in headers}
for i in range(matrix.shape[0]):
for j in range(matrix.shape[1]):
if matrix[i, j] == 1:
in_degrees[headers[j]] += 1
return in_degrees
def layer_summary(self, sorted_elem_outdegree):
"""
Layer summary of the elements by the out-degree
"""
layers = defaultdict(list)
last_outdegree = None
for element, outdegree in sorted_elem_outdegree:
if outdegree != last_outdegree:
last_outdegree = outdegree
layers[outdegree].append(element)
# sort the layers by the out-degree
sorted_layers = sorted(layers.items(), key=lambda x: x[0])
return [layer[1] for layer in sorted_layers]
def get_up_sort_sequence(self):
"""
Get the up sort sequence of the elements
"""
elem_outdegree = self.compute_outdegree(self.reduced_matrix, self.headers)
sorted_elem_outdegree = sorted(elem_outdegree.items(), key=lambda x: x[1])
print(">>> 缩减要素出度排序结果:")
for element, outdegree in sorted_elem_outdegree:
print(f"\t>>> '{element}': {outdegree}")
self.weighted_layers = self.layer_summary(sorted_elem_outdegree)
print(">>> 缩减要素分层汇总结果:")
for i, layer in enumerate(self.weighted_layers, start=1):
print(f"\t>>> 第 {i} 层: {layer}")
def get_backbone_matrix(self):
"""
Get the backbone matrix of the elements
"""
I = np.eye(self.reduced_matrix.shape[0])
self.backbone_matrix = self.reduced_matrix - np.linalg.matrix_power(self.reduced_matrix - I, 2) - I
self.backbone_matrix[self.backbone_matrix < 0] = 0
# Create a DataFrame to display the backbone matrix with headers
backbone_df = pd.DataFrame(self.backbone_matrix.astype(int), index=self.headers, columns=self.headers)
print(f">>> 骨架矩阵 S:\n{backbone_df}")
In [3]:
# 按照要素定义关系
relations_by_ele = {
'S1': ['S2', 'S3', 'S4', 'S5'],
'S2': ['S3', 'S4'],
'S3': ['S4'],
'S4': ['S5'],
'S5': ['S4'],
'S6': ['S7', 'S11'],
'S7': ['S1', 'S9'],
'S8': ['S11'],
'S9': ['S7', 'S11'],
'S10': ['S1'],
'S11': ['S6'],
'S12': ['S6'],
'S13': ['S11']
}
# Step 1: 根据要素定义关系创建决策代理
agent = DecisionAgent(len(relations_by_ele))
print(f">>> 要素集合:\n{agent.get_elements()}")
>>> 要素集合: ['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8', 'S9', 'S10', 'S11', 'S12', 'S13']
In [4]:
# Step 2: 添加要素关系
agent.set_relations(relations_by_ele)
>>> relations_pairs: [['S1', 'S2'], ['S1', 'S3'], ['S1', 'S4'], ['S1', 'S5'], ['S2', 'S3'], ['S2', 'S4'], ['S3', 'S4'], ['S4', 'S5'], ['S5', 'S4'], ['S6', 'S7'], ['S6', 'S11'], ['S7', 'S1'], ['S7', 'S9'], ['S8', 'S11'], ['S9', 'S7'], ['S9', 'S11'], ['S10', 'S1'], ['S11', 'S6'], ['S12', 'S6'], ['S13', 'S11']]
In [5]:
# Step 3: 导出原始矩阵和邻接矩阵
agent.get_adjacency_matrix()
>>> 原始矩阵 A: [[0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]] >>> 邻接矩阵 I: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 0. 1. 0. 1. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 0. 1. 0. 1. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 1.]]
In [6]:
# Step 4: 导出可达矩阵
agent.get_reachable_matrix()
>>> M1 = I: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 0. 1. 0. 1. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 0. 1. 0. 1. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 1.]] >>> M2 = M1 * I: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [1. 0. 0. 0. 0. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 0. 0. 0. 0. 1. 0. 0. 0.] [0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 1. 1. 0.] [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 1.]] >>> M3 = M2 * I: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 0. 0. 0. 0. 1. 0. 0. 0.] [1. 0. 0. 0. 0. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 1. 1. 0. 1. 0. 1. 1. 0.] [0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 1. 0. 1.]] >>> M4 = M3 * I: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 0. 0. 0. 0. 1. 1. 1. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 0. 0. 0. 0. 1. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 1. 0.] [1. 0. 0. 0. 0. 1. 1. 0. 1. 0. 1. 0. 1.]] >>> M5 = M4 * I: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 0. 0. 0. 0. 1. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 1. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 1.]] >>> M6 = M5 * I: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 0. 0. 0. 0. 1. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 1. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 1.]] >>> M6 = M5 ==> R = 5 >>> 可达矩阵 M: [[1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 0. 0. 0. 0. 1. 0. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 1. 0.] [1. 1. 1. 1. 1. 1. 1. 0. 1. 0. 1. 0. 1.]]
In [7]:
# Step 5: 计算缩减矩阵
agent.get_reduced_matrix()
>>> 强连通分量 SCC:
>>> 连通分量 1: ['S1']
>>> 连通分量 2: ['S2']
>>> 连通分量 3: ['S3']
>>> 连通分量 4: ['S4', 'S5']
>>> 连通分量 5: ['S6', 'S7', 'S9', 'S11']
>>> 连通分量 6: ['S8']
>>> 连通分量 7: ['S10']
>>> 连通分量 8: ['S12']
>>> 连通分量 9: ['S13']
>>> 代表要素:
['S1', 'S2', 'S3', 'S4', 'S6', 'S8', 'S10', 'S12', 'S13']
>>> 缩减矩阵:
S1 S2 S3 S4+S5 S6+S7+S9+S11 S8 S10 S12 S13
S1 1 1 1 1 0 0 0 0 0
S2 0 1 1 1 0 0 0 0 0
S3 0 0 1 1 0 0 0 0 0
S4+S5 0 0 0 1 0 0 0 0 0
S6+S7+S9+S11 1 1 1 1 1 0 0 0 0
S8 1 1 1 1 1 1 0 0 0
S10 1 1 1 1 0 0 1 0 0
S12 1 1 1 1 1 0 0 1 0
S13 1 1 1 1 1 0 0 0 1
>>> 要素表头:
['S1', 'S2', 'S3', 'S4+S5', 'S6+S7+S9+S11', 'S8', 'S10', 'S12', 'S13']
In [8]:
# Step 6: 得到加权排序的要素分层汇总结果
agent.get_up_sort_sequence()
>>> 缩减要素出度排序结果: >>> 'S4+S5': 1 >>> 'S3': 2 >>> 'S2': 3 >>> 'S1': 4 >>> 'S6+S7+S9+S11': 5 >>> 'S10': 5 >>> 'S8': 6 >>> 'S12': 6 >>> 'S13': 6 >>> 缩减要素分层汇总结果: >>> 第 1 层: ['S4+S5'] >>> 第 2 层: ['S3'] >>> 第 3 层: ['S2'] >>> 第 4 层: ['S1'] >>> 第 5 层: ['S6+S7+S9+S11', 'S10'] >>> 第 6 层: ['S8', 'S12', 'S13']
In [9]:
# Step 7: 得到骨架矩阵
agent.get_backbone_matrix()
>>> 骨架矩阵 S:
S1 S2 S3 S4+S5 S6+S7+S9+S11 S8 S10 S12 S13
S1 0 1 0 0 0 0 0 0 0
S2 0 0 1 0 0 0 0 0 0
S3 0 0 0 1 0 0 0 0 0
S4+S5 0 0 0 0 0 0 0 0 0
S6+S7+S9+S11 1 0 0 0 0 0 0 0 0
S8 0 0 0 0 1 0 0 0 0
S10 1 0 0 0 0 0 0 0 0
S12 0 0 0 0 1 0 0 0 0
S13 0 0 0 0 1 0 0 0 0