2024-08-24 11:20:13 +08:00
|
|
|
import json
|
2024-08-24 16:13:37 +08:00
|
|
|
from random import shuffle
|
2024-08-24 11:20:13 +08:00
|
|
|
|
|
|
|
import networkx as nx
|
|
|
|
import pandas as pd
|
|
|
|
from mesa import Model
|
2024-08-24 16:13:37 +08:00
|
|
|
from mesa.space import MultiGrid, NetworkGrid
|
2024-08-24 11:20:13 +08:00
|
|
|
from mesa.datacollection import DataCollector
|
2024-08-24 16:13:37 +08:00
|
|
|
from mesa.time import RandomActivation
|
2024-08-24 11:20:13 +08:00
|
|
|
|
|
|
|
from firm import FirmAgent
|
|
|
|
from product import ProductAgent
|
|
|
|
|
|
|
|
|
|
|
|
class MyModel(Model):
|
|
|
|
def __init__(self, params):
|
2024-08-24 16:13:37 +08:00
|
|
|
# self.num_agents = N
|
|
|
|
|
|
|
|
# NetworkX 图对象
|
|
|
|
self.t = 0
|
|
|
|
self.network_graph = nx.DiGraph()
|
|
|
|
|
|
|
|
# NetworkGrid 用于管理网格
|
|
|
|
self.grid = NetworkGrid(self.network_graph)
|
|
|
|
|
|
|
|
self.data_collector = DataCollector(
|
|
|
|
agent_reporters={"Product": "name"}
|
|
|
|
)
|
|
|
|
self.schedule = RandomActivation(self)
|
|
|
|
self.company_agents = []
|
|
|
|
self.product_agents = []
|
2024-08-24 11:20:13 +08:00
|
|
|
|
|
|
|
# Initialize parameters from `params`
|
|
|
|
self.sample = params['sample']
|
|
|
|
self.int_stop_ts = 0
|
|
|
|
self.int_n_iter = int(params['n_iter'])
|
|
|
|
self.dct_lst_init_disrupt_firm_prod = params['dct_lst_init_disrupt_firm_prod']
|
|
|
|
# external variable
|
|
|
|
self.int_n_max_trial = int(params['n_max_trial'])
|
|
|
|
self.is_prf_size = bool(params['prf_size'])
|
|
|
|
|
|
|
|
self.remove_t = int(params['remove_t'])
|
|
|
|
self.int_netw_prf_n = int(params['netw_prf_n'])
|
|
|
|
|
|
|
|
self.product_network = None
|
|
|
|
self.firm_network = None
|
|
|
|
self.firm_prod_network = None
|
|
|
|
|
2024-08-24 16:13:37 +08:00
|
|
|
self.initialize_product_network(params)
|
2024-08-24 11:20:13 +08:00
|
|
|
self.initialize_firm_network()
|
2024-08-24 16:13:37 +08:00
|
|
|
self.initialize_firm_product_network()
|
2024-08-24 11:20:13 +08:00
|
|
|
self.initialize_agents()
|
2024-08-24 16:13:37 +08:00
|
|
|
self.initialize_disruptions()
|
2024-08-24 11:20:13 +08:00
|
|
|
|
2024-08-24 16:13:37 +08:00
|
|
|
def initialize_product_network(self, params):
|
|
|
|
""" Initialize the product network and add it to the model. """
|
|
|
|
self.product_network = nx.adjacency_graph(json.loads(params['g_bom']))
|
|
|
|
self.network_graph.add_edges_from(self.product_network.edges)
|
2024-08-24 11:20:13 +08:00
|
|
|
|
|
|
|
def initialize_firm_network(self):
|
2024-08-24 16:13:37 +08:00
|
|
|
""" Initialize the firm network and add it to the model. """
|
|
|
|
Firm = pd.read_csv("input_data/Firm_amended.csv")
|
|
|
|
Firm['Code'] = Firm['Code'].astype('string')
|
|
|
|
Firm.fillna(0, inplace=True)
|
|
|
|
Firm_attr = Firm.loc[:, ["Code", "Type_Region", "Revenue_Log"]]
|
|
|
|
firm_product = [row[row == 1].index.to_list() for _, row in Firm.loc[:, '1':].iterrows()]
|
|
|
|
Firm_attr.loc[:, 'Product_Code'] = firm_product
|
|
|
|
Firm_attr.set_index('Code', inplace=True)
|
|
|
|
|
|
|
|
self.firm_network = nx.MultiDiGraph()
|
|
|
|
self.firm_network.add_nodes_from(Firm["Code"])
|
|
|
|
|
|
|
|
firm_labels_dict = {code: Firm_attr.loc[code].to_dict() for code in self.firm_network.nodes}
|
|
|
|
nx.set_node_attributes(self.firm_network, firm_labels_dict)
|
|
|
|
|
|
|
|
def initialize_firm_product_network(self):
|
|
|
|
""" Initialize the firm-product network and add it to the model. """
|
|
|
|
Firm_Prod = pd.read_csv("input_data/Firm_amended.csv")
|
|
|
|
Firm_Prod.fillna(0, inplace=True)
|
|
|
|
firm_prod = pd.DataFrame({'bool': Firm_Prod.loc[:, '1':].stack()})
|
2024-08-24 11:20:13 +08:00
|
|
|
firm_prod = firm_prod[firm_prod['bool'] == 1].reset_index()
|
|
|
|
firm_prod.drop('bool', axis=1, inplace=True)
|
|
|
|
firm_prod.rename({'level_0': 'Firm_Code', 'level_1': 'Product_Code'}, axis=1, inplace=True)
|
|
|
|
firm_prod['Firm_Code'] = firm_prod['Firm_Code'].astype('string')
|
|
|
|
|
2024-08-24 16:13:37 +08:00
|
|
|
self.firm_prod_network = nx.MultiDiGraph()
|
|
|
|
self.firm_prod_network.add_nodes_from(firm_prod.index)
|
|
|
|
|
|
|
|
firm_prod_labels_dict = {code: firm_prod.loc[code].to_dict() for code in firm_prod.index}
|
|
|
|
nx.set_node_attributes(self.firm_prod_network, firm_prod_labels_dict)
|
|
|
|
|
|
|
|
self.add_edges_to_firm_network()
|
|
|
|
self.connect_unconnected_nodes()
|
|
|
|
|
|
|
|
def add_edges_to_firm_network(self):
|
|
|
|
""" Add edges to the firm network based on product BOM. """
|
|
|
|
Firm = pd.read_csv("input_data/Firm_amended.csv")
|
|
|
|
Firm['Code'] = Firm['Code'].astype('string')
|
|
|
|
Firm.fillna(0, inplace=True)
|
|
|
|
for node in nx.nodes(self.firm_network):
|
|
|
|
lst_pred_product_code = []
|
|
|
|
for product_code in self.firm_network.nodes[node]['Product_Code']:
|
|
|
|
lst_pred_product_code += list(self.product_network.predecessors(product_code))
|
|
|
|
lst_pred_product_code = list(set(lst_pred_product_code))
|
|
|
|
lst_pred_product_code = list(sorted(lst_pred_product_code))
|
|
|
|
|
|
|
|
for pred_product_code in lst_pred_product_code:
|
|
|
|
lst_pred_firm = Firm['Code'][Firm[pred_product_code] == 1].to_list()
|
|
|
|
n_pred_firm = self.int_netw_prf_n
|
|
|
|
if n_pred_firm > len(lst_pred_firm):
|
|
|
|
n_pred_firm = len(lst_pred_firm)
|
|
|
|
if self.is_prf_size:
|
|
|
|
lst_pred_firm_size = [self.firm_network.nodes[pred_firm]['Revenue_Log'] for pred_firm in
|
|
|
|
lst_pred_firm]
|
|
|
|
lst_prob = [size / sum(lst_pred_firm_size) for size in lst_pred_firm_size]
|
|
|
|
lst_choose_firm = self.random.choices(lst_pred_firm, k=n_pred_firm, weights=lst_prob)
|
|
|
|
else:
|
|
|
|
lst_choose_firm = self.random.choices(lst_pred_firm, k=n_pred_firm)
|
|
|
|
lst_add_edge = [(pred_firm, node, {'Product': pred_product_code}) for pred_firm in lst_choose_firm]
|
|
|
|
self.firm_network.add_edges_from(lst_add_edge)
|
|
|
|
|
|
|
|
# Add edges to firm-prod network
|
|
|
|
set_node_prod_code = set(self.firm_network.nodes[node]['Product_Code'])
|
|
|
|
set_pred_succ_code = set(self.product_network.successors(pred_product_code))
|
|
|
|
lst_use_pred_prod_code = list(set_node_prod_code & set_pred_succ_code)
|
|
|
|
|
|
|
|
for pred_firm in lst_choose_firm:
|
|
|
|
pred_node = [n for n, v in self.firm_prod_network.nodes(data=True) if
|
|
|
|
v['Firm_Code'] == pred_firm and v['Product_Code'] == pred_product_code][0]
|
|
|
|
for use_pred_prod_code in lst_use_pred_prod_code:
|
|
|
|
current_node = [n for n, v in self.firm_prod_network.nodes(data=True) if
|
|
|
|
v['Firm_Code'] == node and v['Product_Code'] == use_pred_prod_code][0]
|
|
|
|
self.firm_prod_network.add_edge(pred_node, current_node)
|
|
|
|
|
|
|
|
def connect_unconnected_nodes(self):
|
|
|
|
""" Connect unconnected nodes in the firm network. """
|
|
|
|
Firm = pd.read_csv("input_data/Firm_amended.csv")
|
|
|
|
Firm['Code'] = Firm['Code'].astype('string')
|
|
|
|
Firm.fillna(0, inplace=True)
|
|
|
|
for node in nx.nodes(self.firm_network):
|
|
|
|
if self.firm_network.degree(node) == 0:
|
|
|
|
for product_code in self.firm_network.nodes[node]['Product_Code']:
|
|
|
|
current_node = [n for n, v in self.firm_prod_network.nodes(data=True) if
|
|
|
|
v['Firm_Code'] == node and v['Product_Code'] == product_code][0]
|
|
|
|
lst_succ_product_code = list(self.product_network.successors(product_code))
|
|
|
|
for succ_product_code in lst_succ_product_code:
|
|
|
|
lst_succ_firm = Firm['Code'][Firm[succ_product_code] == 1].to_list()
|
|
|
|
n_succ_firm = self.int_netw_prf_n
|
|
|
|
if n_succ_firm > len(lst_succ_firm):
|
|
|
|
n_succ_firm = len(lst_succ_firm)
|
|
|
|
if self.is_prf_size:
|
|
|
|
lst_succ_firm_size = [self.firm_network.nodes[succ_firm]['Revenue_Log'] for succ_firm in
|
|
|
|
lst_succ_firm]
|
|
|
|
lst_prob = [size / sum(lst_succ_firm_size) for size in lst_succ_firm_size]
|
|
|
|
lst_choose_firm = self.random.choices(lst_succ_firm, k=n_succ_firm, weights=lst_prob)
|
|
|
|
else:
|
|
|
|
lst_choose_firm = self.random.choices(lst_succ_firm, k=n_succ_firm)
|
|
|
|
lst_add_edge = [(node, succ_firm, {'Product': product_code}) for succ_firm in lst_choose_firm]
|
|
|
|
self.firm_network.add_edges_from(lst_add_edge)
|
|
|
|
|
|
|
|
for succ_firm in lst_choose_firm:
|
|
|
|
succ_node = [n for n, v in self.firm_prod_network.nodes(data=True) if
|
|
|
|
v['Firm_Code'] == succ_firm and v['Product_Code'] == succ_product_code][0]
|
|
|
|
self.firm_prod_network.add_edge(current_node, succ_node)
|
2024-08-24 11:20:13 +08:00
|
|
|
|
|
|
|
def initialize_agents(self):
|
2024-08-24 16:13:37 +08:00
|
|
|
""" Initialize agents and add them to the model. """
|
|
|
|
for ag_node, attr in self.product_network.nodes(data=True):
|
|
|
|
product = ProductAgent(ag_node, self,code=attr['code'], name=attr['Name'])
|
2024-08-24 11:20:13 +08:00
|
|
|
self.schedule.add(product)
|
2024-08-24 16:13:37 +08:00
|
|
|
self.grid.place_agent(product, ag_node)
|
2024-08-24 11:20:13 +08:00
|
|
|
|
2024-08-24 16:13:37 +08:00
|
|
|
for ag_node, attr in self.firm_network.nodes(data=True):
|
2024-08-24 11:20:13 +08:00
|
|
|
firm_agent = FirmAgent(
|
2024-08-24 16:13:37 +08:00
|
|
|
ag_node, self,
|
|
|
|
code=attr['Code'],
|
2024-08-24 11:20:13 +08:00
|
|
|
type_region=attr['Type_Region'],
|
|
|
|
revenue_log=attr['Revenue_Log'],
|
2024-08-24 16:13:37 +08:00
|
|
|
a_lst_product=[self.schedule.agents[code] for code in attr['Product_Code']]
|
2024-08-24 11:20:13 +08:00
|
|
|
)
|
|
|
|
self.schedule.add(firm_agent)
|
2024-08-24 16:13:37 +08:00
|
|
|
self.grid.place_agent(firm_agent, ag_node)
|
2024-08-24 11:20:13 +08:00
|
|
|
|
|
|
|
def initialize_disruptions(self):
|
2024-08-24 16:13:37 +08:00
|
|
|
""" Initialize disruptions in the network. """
|
|
|
|
for firm_code, lst_product in self.dct_lst_init_disrupt_firm_prod.items():
|
|
|
|
for product_code in lst_product:
|
|
|
|
self.firm_network.nodes[firm_code]['Product_Code'].remove(product_code)
|
|
|
|
|
|
|
|
# Log disruptions for visualization
|
|
|
|
self.dct_lst_init_disrupt_firm_prod[firm_code].append(product_code)
|
|
|
|
|
|
|
|
def add_agent(self, agent):
|
|
|
|
if isinstance(agent, FirmAgent):
|
|
|
|
self.company_agents.append(agent)
|
|
|
|
elif isinstance(agent, ProductAgent):
|
|
|
|
self.product_agents.append(agent)
|
|
|
|
self.schedule.add(agent)
|
2024-08-24 11:20:13 +08:00
|
|
|
|
|
|
|
def step(self):
|
2024-08-24 16:13:37 +08:00
|
|
|
# 1. Remove edge to customer and disrupt customer up product
|
|
|
|
for firm in self.company_agents:
|
|
|
|
for prod in firm.dct_prod_up_prod_stat.keys():
|
|
|
|
status, ts = firm.dct_prod_up_prod_stat[prod]['p_stat'][-1]
|
|
|
|
if status == 'D' and ts == self.t - 1:
|
|
|
|
firm.remove_edge_to_cus(prod)
|
|
|
|
|
|
|
|
for firm in self.company_agents:
|
|
|
|
for prod in firm.dct_prod_up_prod_stat.keys():
|
|
|
|
for up_prod in firm.dct_prod_up_prod_stat[prod]['s_stat'].keys():
|
|
|
|
if firm.dct_prod_up_prod_stat[prod]['s_stat'][up_prod]['set_disrupt_firm']:
|
|
|
|
firm.disrupt_cus_prod(prod, up_prod)
|
|
|
|
|
|
|
|
# 2. Trial Process
|
|
|
|
for n_trial in range(self.int_n_max_trial):
|
|
|
|
shuffle(self.company_agents) # 手动打乱代理顺序
|
|
|
|
|
|
|
|
is_stop_trial = True
|
|
|
|
for firm in self.company_agents:
|
|
|
|
lst_seek_prod = []
|
|
|
|
for prod in firm.dct_prod_up_prod_stat.keys():
|
|
|
|
status = firm.dct_prod_up_prod_stat[prod]['p_stat'][-1][0]
|
|
|
|
if status == 'D':
|
|
|
|
for supply in firm.dct_prod_up_prod_stat[prod]['s_stat'].keys():
|
|
|
|
if not firm.dct_prod_up_prod_stat[prod]['s_stat'][supply]['stat']:
|
|
|
|
lst_seek_prod.append(supply)
|
|
|
|
lst_seek_prod = list(set(lst_seek_prod))
|
|
|
|
if len(lst_seek_prod) > 0:
|
|
|
|
is_stop_trial = False
|
|
|
|
for supply in lst_seek_prod:
|
|
|
|
firm.seek_alt_supply(supply)
|
|
|
|
if is_stop_trial:
|
|
|
|
break
|
|
|
|
|
|
|
|
# Handle requests
|
|
|
|
shuffle(self.company_agents) # 手动打乱代理顺序
|
|
|
|
for firm in self.company_agents:
|
|
|
|
if len(firm.dct_request_prod_from_firm) > 0:
|
|
|
|
firm.handle_request()
|
|
|
|
|
|
|
|
# Reset dct_request_prod_from_firm
|
|
|
|
for firm in self.company_agents:
|
|
|
|
firm.clean_before_trial()
|
|
|
|
|
|
|
|
# Increment the time step
|
|
|
|
self.t += 1
|
|
|
|
self.schedule.step() # Activate all agents in the scheduler
|