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