├── README.md ├── initialize_part_path.py ├── create_path_ground.py ├── create_path_sat.py ├── add_ground_column.py ├── add_sat_column.py ├── column_generation.py ├── Main.py ├── create_constellation.py ├── master_solver.py └── master_solver_sema.py /README.md: -------------------------------------------------------------------------------- 1 | # CG_satellite 2 | This is a Python version for "https://github.com/Yuanliaoo/CG_satellite" 3 | 4 | # Solver 5 | This code changes the original solver to Gurobi 6 | 7 | # Gurobi 8 | Thie Groubi needs a license. About the license, please see "https://blog.csdn.net/zzrh2018/article/details/134071999". 9 | 10 | # Paper 11 | Code for the paper 'Towards Routing and Edge Computing in Satellite-Terrestrial Networks: A Column Generation Approach' 12 | please see "https://arxiv.org/abs/2502.14422" 13 | -------------------------------------------------------------------------------- /initialize_part_path.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | 3 | def initialize_part_path(adj_matrix_sat, adj_matrix_ground, max_hop_sat, max_hop_ground): 4 | num_sat = len(adj_matrix_sat) 5 | 6 | # Create satellite graph from adjacency matrix 7 | sat_graph = nx.from_numpy_array(adj_matrix_sat) 8 | 9 | # 一个卫星到另一个卫星的所有路径,使用三维数组表示 10 | sat_path = [[None for _ in range(num_sat)] for _ in range(num_sat)] 11 | 12 | # 卫星星座中所以卫星的数量 13 | num_sat_path = 0 14 | 15 | # Find all paths between satellite pairs 16 | for source_sat in range(num_sat): 17 | for terminal_sat in range(num_sat): 18 | if source_sat != terminal_sat: 19 | # Find all paths between source_sat and terminal_sat with max hops = max_hop_sat 20 | paths_ij = list(nx.all_simple_paths(sat_graph, source_sat, terminal_sat, cutoff=max_hop_sat)) 21 | sat_path[source_sat][terminal_sat] = paths_ij 22 | 23 | num_sat_path += len(paths_ij) 24 | 25 | # Create ground graph from adjacency matrix 26 | ground_graph = nx.from_numpy_array(adj_matrix_ground) 27 | 28 | ground_path = [None] * (num_sat + 1) 29 | num_ground_path = 0 30 | 31 | # Find paths from ground stations (2 to num_sat + 1) to satellite 1 (index 0) 32 | for source_ground in range(1, num_sat + 1): 33 | # Find all paths from source_ground to satellite 1 (index 0) with max hops = max_hop_ground 34 | paths_ij = list(nx.all_simple_paths(ground_graph, source_ground, 0, cutoff=max_hop_ground)) 35 | ground_path[source_ground] = paths_ij 36 | 37 | num_ground_path += len(paths_ij) 38 | 39 | return sat_path, num_sat_path, ground_path, num_ground_path 40 | -------------------------------------------------------------------------------- /create_path_ground.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def create_path_ground(ground_path, num_ground_path, adj_matrix_ground): 5 | num_sat_ground = adj_matrix_ground.shape[0] 6 | num_sat = num_sat_ground - 1 7 | 8 | # Initialize the output matrices 9 | groundpath_edges = np.zeros((num_sat, num_sat, num_ground_path)) 10 | groundpath_lastsecond = np.zeros((num_sat, num_ground_path)) 11 | groundpath_source = np.zeros((num_sat, num_ground_path)) 12 | 13 | path_id = 0 14 | 15 | # Loop over each source satellite (starting from 2 as in MATLAB) 16 | for source_sat in range(1, num_sat + 1): 17 | path_ij = ground_path[source_sat] 18 | num_path_ij = len(path_ij) 19 | 20 | if num_path_ij > 0: 21 | for s in range(num_path_ij): 22 | # Get one path 23 | path1 = path_ij[s] 24 | path_length = len(path1) 25 | 26 | # Update the groundpath_edges matrix 27 | if path_length > 2: 28 | for q in range(path_length - 2): 29 | first_node = path1[q] 30 | second_node = path1[q + 1] 31 | groundpath_edges[first_node - 1, second_node - 1, path_id] = 1 32 | 33 | # Update the groundpath_source matrix 34 | groundpath_source[source_sat - 1, path_id] = 1 35 | 36 | # Update the groundpath_lastsecond matrix 37 | lastsecond_sat_id = path1[path_length - 1] 38 | groundpath_lastsecond[lastsecond_sat_id - 1, path_id] = 1 39 | 40 | # Increment the path_id 41 | path_id += 1 42 | 43 | return groundpath_edges, groundpath_lastsecond, groundpath_source 44 | -------------------------------------------------------------------------------- /create_path_sat.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def create_path_sat(sat_path, num_sat_path, adj_matrix_sat): 5 | 6 | # 卫星的数量 7 | num_sat = adj_matrix_sat.shape[0] 8 | 9 | # Initialize the output matrices 10 | # 给路径编号,一共有num_sat_path个路径,1表示第num_sat_path个路径存在 11 | satpath_edges = np.zeros((num_sat, num_sat, num_sat_path)) 12 | satpath_source = np.zeros((num_sat, num_sat_path)) 13 | satpath_terminal = np.zeros((num_sat, num_sat_path)) 14 | 15 | path_id = 0 16 | 17 | # Loop over each pair of source and terminal satellites 18 | for source_sat in range(num_sat): 19 | for terminal_sat in range(num_sat): 20 | 21 | path_ij = sat_path[source_sat][terminal_sat] 22 | 23 | if path_ij is not None: 24 | num_path_ij = len(path_ij) 25 | else: 26 | num_path_ij = 0 # Or some other value indicating no path 27 | 28 | if num_path_ij > 0: 29 | for s in range(num_path_ij): 30 | # Get one path 31 | path1 = path_ij[s] 32 | path_length = len(path1) 33 | 34 | # Update the satpath_edges matrix 35 | for q in range(path_length - 1): 36 | first_node = path1[q] 37 | second_node = path1[q + 1] 38 | satpath_edges[first_node, second_node, path_id] = 1 39 | 40 | # Update the satpath_source and satpath_terminal matrices 41 | satpath_source[source_sat, path_id] = 1 42 | satpath_terminal[terminal_sat, path_id] = 1 43 | 44 | # Update the path_id 45 | path_id += 1 46 | 47 | return satpath_edges, satpath_source, satpath_terminal 48 | -------------------------------------------------------------------------------- /add_ground_column.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import numpy as np 3 | 4 | 5 | def add_ground_column(adj_matrix_ground, max_hop_ground, part_ground_path, part_num_ground_path, 6 | SatCapCon_dual, GroundCapCon_dual, DemandCon_dual, obj_weight): 7 | num_sat_ground = adj_matrix_ground.shape[0] 8 | num_sat = num_sat_ground - 1 9 | 10 | # Create the graph from adjacency matrix 11 | graph_map = nx.from_numpy_array(adj_matrix_ground) 12 | 13 | is_groundopt = 1 14 | 15 | # Iterate over each source satellite 16 | for source_sat in range(1, num_sat + 1): 17 | # Get all paths from source_sat with max_hop_ground limit 18 | all_paths_i = list(nx.all_simple_paths(graph_map, source_sat, 0, cutoff=max_hop_ground)) 19 | num_path_i = len(all_paths_i) 20 | 21 | if num_path_i >= 1: 22 | shortest_length = 10 ** 9 23 | shortest_path = [] 24 | 25 | for path_id in range(num_path_i): 26 | path__i_j = all_paths_i[path_id] 27 | sum_dual = 0 28 | path_length = len(path__i_j) 29 | 30 | if path_length > 2: 31 | for q in range(path_length - 2): 32 | first_node = path__i_j[q] - 1 33 | second_node = path__i_j[q + 1] - 1 34 | sum_dual += SatCapCon_dual[first_node, second_node] 35 | 36 | sum_dual += DemandCon_dual[path__i_j[0] - 1] 37 | sum_dual += GroundCapCon_dual[path__i_j[path_length - 1] - 1] 38 | 39 | # Update the shortest path 40 | if sum_dual <= shortest_length: 41 | shortest_length = sum_dual 42 | shortest_path = path__i_j 43 | 44 | # Check if this path violates the dual constraints 45 | if shortest_length < obj_weight[2]: 46 | part_ground_path[source_sat - 1].append(shortest_path) 47 | is_groundopt = 0 48 | part_num_ground_path += 1 49 | 50 | return is_groundopt, part_ground_path, part_num_ground_path 51 | -------------------------------------------------------------------------------- /add_sat_column.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import numpy as np 3 | 4 | 5 | def add_sat_column(adj_matrix_sat, max_hop_sat, part_sat_path, part_num_sat_path, 6 | SatCapCon_dual, DemandCon_dual, ComputeCon_dual, obj_weight): 7 | num_sat = adj_matrix_sat.shape[0] 8 | 9 | # Create the graph from adjacency matrix 10 | graph_map = nx.from_numpy_array(adj_matrix_sat) 11 | 12 | is_satopt = 1 13 | 14 | # Iterate over each pair of source and terminal satellites 15 | for source_sat in range(num_sat): 16 | for terminal_sat in range(num_sat): 17 | if source_sat != terminal_sat: 18 | # Get all paths from source_sat to terminal_sat with max_hop_sat limit 19 | all_paths_i = list( 20 | nx.all_simple_paths(graph_map, source=source_sat, target=terminal_sat, cutoff=max_hop_sat)) 21 | num_path_i = len(all_paths_i) 22 | 23 | if num_path_i >= 1: 24 | shortest_length = 10 ** 9 25 | shortest_path = [] 26 | 27 | for path_id in range(num_path_i): 28 | path__i_j = all_paths_i[path_id] 29 | sum_dual = 0 30 | path_length = len(path__i_j) 31 | 32 | # Sum the dual values of the edges in the path 33 | for q in range(path_length - 1): 34 | first_node = path__i_j[q] 35 | second_node = path__i_j[q + 1] 36 | sum_dual += SatCapCon_dual[first_node, second_node] 37 | 38 | # Add the dual values for the source and terminal nodes 39 | sum_dual += DemandCon_dual[path__i_j[0]] 40 | sum_dual += ComputeCon_dual[path__i_j[path_length - 1]] 41 | 42 | # Update the shortest path if the current path has a smaller dual sum 43 | if sum_dual <= shortest_length: 44 | shortest_length = sum_dual 45 | shortest_path = path__i_j 46 | 47 | # Check if this path violates the dual constraints 48 | if shortest_length < obj_weight[1]: 49 | part_sat_path[source_sat][terminal_sat].append(shortest_path) 50 | is_satopt = 0 51 | part_num_sat_path += 1 52 | 53 | return is_satopt, part_sat_path, part_num_sat_path 54 | -------------------------------------------------------------------------------- /column_generation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from path.add_ground_column import add_ground_column 4 | from path.add_sat_column import add_sat_column 5 | from path.create_path_ground import create_path_ground 6 | from path.create_path_sat import create_path_sat 7 | from path.initialize_part_path import initialize_part_path 8 | from path.master_solver import master_solver 9 | 10 | 11 | def column_generation(adj_matrix_sat, adj_matrix_ground, capacity_matrix, demand_matrix, compute_matrix, 12 | max_hop_sat, max_hop_ground, obj_weight): 13 | # Initialize the part of feasible paths 14 | part_sat_path, part_num_sat_path, part_ground_path, part_num_ground_path = initialize_part_path( 15 | adj_matrix_sat, adj_matrix_ground, max_hop_sat, max_hop_ground 16 | ) 17 | 18 | # While loop for column generation 19 | is_opt = False 20 | 21 | while not is_opt: 22 | print(part_num_ground_path, part_num_sat_path) 23 | 24 | # Create path related matrices 25 | satpath_edges, satpath_source, satpath_terminal = create_path_sat(part_sat_path, part_num_sat_path, adj_matrix_sat) 26 | groundpath_edges, groundpath_lastsecond, groundpath_source = create_path_ground(part_ground_path, part_num_ground_path, adj_matrix_ground) 27 | 28 | # Solve the restricted master problem 29 | compute_vol, SatCapCon_dual, GroundCapCon_dual, DemandCon_dual, ComputeCon_dual = master_solver( 30 | capacity_matrix, demand_matrix, compute_matrix, obj_weight, 31 | satpath_edges, satpath_source, satpath_terminal, 32 | groundpath_edges, groundpath_lastsecond, groundpath_source 33 | ) 34 | 35 | # Add column for satellite paths 36 | is_satopt, part_sat_path, part_num_sat_path = add_sat_column( 37 | adj_matrix_sat, max_hop_sat, part_sat_path, part_num_sat_path, 38 | SatCapCon_dual, DemandCon_dual, ComputeCon_dual, obj_weight 39 | ) 40 | 41 | # Add column for ground paths 42 | is_groundopt, part_ground_path, part_num_ground_path = add_ground_column( 43 | adj_matrix_ground, max_hop_ground, part_ground_path, part_num_ground_path, 44 | SatCapCon_dual, GroundCapCon_dual, DemandCon_dual, obj_weight 45 | ) 46 | 47 | # Check if optimal solution is reached 48 | is_opt = is_satopt and is_groundopt 49 | 50 | # Calculate the number of active paths 51 | active_num_satpath = part_num_sat_path 52 | active_num_groundpath = part_num_ground_path 53 | 54 | return compute_vol, active_num_satpath, active_num_groundpath 55 | -------------------------------------------------------------------------------- /Main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from path.add_ground_column import add_ground_column 4 | from path.add_sat_column import add_sat_column 5 | from path.create_constellation import create_constellation 6 | from path.create_path_ground import create_path_ground 7 | from path.create_path_sat import create_path_sat 8 | from path.initialize_part_path import initialize_part_path 9 | from path.master_solver import master_solver 10 | 11 | # Assuming functions like create_constellation, initialize_part_path, etc., are already defined as in your earlier code. 12 | 13 | # Define the parameters for the constellation 14 | num_orbit = 6 15 | num_sat_orbit = 5 16 | 17 | # 和地面站有连接的卫星数量 18 | num_ground_sat = 6 19 | capacity_sat = 5 20 | capacity_ground = 1 21 | computer_capacity = 10 22 | 23 | # Create the satellite constellation 24 | sat_position, adj_matrix_sat, adj_matrix_ground, capacity_matrix, demand_matrix, compute_matrix = create_constellation( 25 | num_orbit, num_sat_orbit, num_ground_sat, capacity_sat, capacity_ground, computer_capacity) 26 | 27 | # Define max hops for satellites and ground 28 | max_hop_sat = 4 29 | max_hop_ground = 4 30 | 31 | # Initialize part paths 32 | # 根据初始化的卫星星座的邻接矩阵,找到从一个卫星到另一个卫星的所有路径part_sat_path 和卫星到地面站的所有路径part_ground_path 33 | part_sat_path, part_num_sat_path, part_ground_path, part_num_ground_path = initialize_part_path( 34 | adj_matrix_sat, adj_matrix_ground, max_hop_sat, max_hop_ground) 35 | 36 | 37 | print(part_sat_path) 38 | 39 | # Create satellite paths 40 | satpath_edges, satpath_source, satpath_terminal = create_path_sat(part_sat_path, part_num_sat_path, adj_matrix_sat) 41 | 42 | # Create ground paths 43 | groundpath_edges, groundpath_lastsecond, groundpath_source = create_path_ground(part_ground_path, part_num_ground_path, adj_matrix_ground) 44 | 45 | # Define the objective weight vector 46 | obj_weight = np.array([0.5, 0.3, 0.2]) 47 | 48 | # 使用求解器求出分配给本地、其他卫星、地面站的任务量,并返回约束的对偶 49 | # Call the master solver to get the optimization results 50 | compute_vol, SatCapCon_dual, GroundCapCon_dual, DemandCon_dual, ComputeCon_dual = master_solver( 51 | capacity_matrix, demand_matrix, compute_matrix, obj_weight, 52 | satpath_edges, satpath_source, satpath_terminal, 53 | groundpath_edges, groundpath_lastsecond, groundpath_source) 54 | 55 | print(compute_vol) 56 | 57 | 58 | 59 | # Add satellite column to the solution 60 | is_satopt, part_sat_path, part_num_sat_path = add_sat_column( 61 | adj_matrix_sat, max_hop_sat, part_sat_path, part_num_sat_path, 62 | SatCapCon_dual, DemandCon_dual, ComputeCon_dual, obj_weight) 63 | 64 | 65 | 66 | # Add ground column to the solution 67 | is_groundopt, part_ground_path, part_num_ground_path = add_ground_column( 68 | adj_matrix_ground, max_hop_ground, part_ground_path, part_num_ground_path, 69 | SatCapCon_dual, GroundCapCon_dual, DemandCon_dual, obj_weight) 70 | 71 | 72 | print(is_groundopt) 73 | print(part_ground_path) 74 | print(part_num_ground_path) -------------------------------------------------------------------------------- /create_constellation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def create_constellation(num_orbit, num_sat_orbit, num_ground_sat, capacity_sat, capacity_ground, computer_capacity): 4 | num_sat = num_orbit * num_sat_orbit 5 | 6 | # Initialize satellite positions 维数:卫星数*3 第0列是卫星编号、第1列是轨道id、第2列是卫星轨道id 7 | sat_position = np.zeros((num_sat, 3)) 8 | sat_position[:, 0] = np.arange(1, num_sat + 1) 9 | 10 | sat_id = 0 11 | for orbit_id in range(1, num_orbit + 1): 12 | for sat_orbit_id in range(1, num_sat_orbit + 1): 13 | sat_position[sat_id, 1] = orbit_id 14 | sat_position[sat_id, 2] = sat_orbit_id 15 | sat_id += 1 16 | 17 | # Initialize adjacency matrices 18 | # 卫星之间的临界矩阵 19 | adj_matrix_sat = np.zeros((num_sat, num_sat)) 20 | # 卫星、地面站之间的临界矩阵,第0列是地面站 21 | adj_matrix_ground = np.zeros((num_sat + 1, num_sat + 1)) 22 | 23 | # Create adjacency matrix for satellites and ground stations 24 | for sat_id_1 in range(num_sat): 25 | for sat_id_2 in range(num_sat): 26 | orbit_id_1 = sat_position[sat_id_1, 1] 27 | sat_orbit_id_1 = sat_position[sat_id_1, 2] 28 | orbit_id_2 = sat_position[sat_id_2, 1] 29 | sat_orbit_id_2 = sat_position[sat_id_2, 2] 30 | 31 | # Check if satellites are in the same orbit 32 | if orbit_id_1 == orbit_id_2: 33 | if (abs(sat_orbit_id_1 - sat_orbit_id_2) == 1) or \ 34 | (sat_orbit_id_1 == 1 and sat_orbit_id_2 == num_sat_orbit) or \ 35 | (sat_orbit_id_1 == num_sat_orbit and sat_orbit_id_2 == 1): 36 | adj_matrix_sat[sat_id_1, sat_id_2] = 1 37 | adj_matrix_sat[sat_id_2, sat_id_1] = 1 38 | adj_matrix_ground[sat_id_1 + 1, sat_id_2 + 1] = 1 39 | adj_matrix_ground[sat_id_2 + 1, sat_id_1 + 1] = 1 40 | 41 | # Check if satellites are in the same position in different orbits 42 | if sat_orbit_id_1 == sat_orbit_id_2: 43 | if (abs(orbit_id_1 - orbit_id_2) == 1) or \ 44 | (orbit_id_1 == 1 and orbit_id_2 == num_orbit) or \ 45 | (orbit_id_1 == num_orbit and orbit_id_2 == 1): 46 | adj_matrix_sat[sat_id_1, sat_id_2] = 1 47 | adj_matrix_sat[sat_id_2, sat_id_1] = 1 48 | adj_matrix_ground[sat_id_1 + 1, sat_id_2 + 1] = 1 49 | adj_matrix_ground[sat_id_2 + 1, sat_id_1 + 1] = 1 50 | 51 | # Ground station adjacency (connected to satellite 1) 52 | # 地面站与前面num_ground_sat个卫星有连接 53 | adj_matrix_ground[0, 1:num_ground_sat + 1] = 1 54 | adj_matrix_ground[1:num_ground_sat + 1, 0] = 1 55 | 56 | # Initialize the capacity matrix 57 | capacity_matrix = np.zeros((num_sat + 1, num_sat + 1)) 58 | 59 | # Ground station capacity 60 | capacity_matrix[0, 1:num_ground_sat + 1] = capacity_ground 61 | capacity_matrix[1:num_ground_sat + 1, 0] = capacity_ground 62 | 63 | # Satellite-to-satellite capacity (based on adjacency matrix) 64 | for sat_id_1 in range(1, num_sat + 1): 65 | for sat_id_2 in range(1, num_sat + 1): 66 | if adj_matrix_ground[sat_id_1, sat_id_2] == 1: 67 | capacity_matrix[sat_id_1, sat_id_2] = capacity_sat 68 | 69 | # Communication demand for each satellite (log-normal distribution) 70 | com_avg = 20 # Example average communication demand 71 | com_dev = 1.3 # Standard deviation of the communication demand 72 | com_mu = np.log(com_avg) - 0.5 * com_dev ** 2 73 | demand_matrix = np.random.lognormal(mean=com_mu, sigma=com_dev, size=(num_sat, 1)) 74 | 75 | # Computational capacity for each satellite 76 | compute_matrix = computer_capacity * np.ones((num_sat, 1)) 77 | 78 | 79 | return sat_position, adj_matrix_sat, adj_matrix_ground, capacity_matrix, demand_matrix, compute_matrix 80 | -------------------------------------------------------------------------------- /master_solver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import gurobipy as gp 3 | from gurobipy import GRB 4 | 5 | 6 | def master_solver(capacity_matrix, demand_matrix, compute_matrix, obj_weight, 7 | satpath_edges, satpath_source, satpath_terminal, 8 | groundpath_edges, groundpath_lastsecond, groundpath_source): 9 | num_sat = satpath_edges.shape[0] 10 | num_sat_path = satpath_edges.shape[2] 11 | num_ground_path = groundpath_edges.shape[2] 12 | 13 | # 创建模型 14 | model = gp.Model("SatelliteNetworkOptimization") 15 | model.setParam('OutputFlag', 0) # 关闭求解日志 16 | 17 | # 添加变量 18 | x_i = model.addVars(num_sat, name="x_i", lb=0.0) # 卫星本地计算量 19 | sat_flow = model.addVars(num_sat_path, name="sat_flow", lb=0.0) # 卫星路径流量 20 | ground_flow = model.addVars(num_ground_path, name="ground_flow", lb=0.0) # 地面路径流量 21 | 22 | # 存储约束对象以便获取对偶变量 23 | SatCapCon = {} 24 | GroundCapCon = {} 25 | DemandCon = None 26 | ComputeCon = None 27 | 28 | # 1. 卫星间链路容量约束 29 | for i in range(num_sat): 30 | for j in range(num_sat): 31 | if capacity_matrix[i + 1, j + 1] > 0: 32 | # 获取该链路上的所有路径 33 | sat_paths = [p for p in range(num_sat_path) if satpath_edges[i, j, p] > 0] 34 | ground_paths = [p for p in range(num_ground_path) if groundpath_edges[i, j, p] > 0] 35 | 36 | if sat_paths or ground_paths: 37 | # 创建一个表达式 38 | lhs = gp.LinExpr() 39 | # 卫星路径贡献 40 | for p in sat_paths: 41 | lhs += sat_flow[p] 42 | # 地面路径贡献 43 | for p in ground_paths: 44 | lhs += ground_flow[p] 45 | # 添加约束 46 | SatCapCon[(i, j)] = model.addConstr( 47 | lhs <= capacity_matrix[i + 1, j + 1], 48 | name=f"SatCapCon_{i}_{j}" 49 | ) 50 | 51 | # 2. 地面链路容量约束 52 | for i in range(num_sat): 53 | if capacity_matrix[i, 0] > 0: 54 | # 获取所有使用该地面链路的路径 55 | ground_paths = [p for p in range(num_ground_path) if groundpath_lastsecond[i, p] > 0] 56 | if ground_paths: 57 | lhs = gp.LinExpr() 58 | for p in ground_paths: 59 | lhs += ground_flow[p] 60 | GroundCapCon[i] = model.addConstr( 61 | lhs <= capacity_matrix[i, 0], 62 | name=f"GroundCapCon_{i}" 63 | ) 64 | 65 | # 3. 需求约束(每个卫星节点一个) 66 | demand_constrs = [] 67 | for i in range(num_sat): 68 | expr = x_i[i] 69 | # 卫星路径源贡献 70 | for p in range(num_sat_path): 71 | expr += satpath_source[i, p] * sat_flow[p] 72 | # 地面路径源贡献 73 | for p in range(num_ground_path): 74 | expr += groundpath_source[i, p] * ground_flow[p] 75 | demand_constrs.append( 76 | model.addConstr(expr <= demand_matrix[i], name=f"DemandCon_{i}") 77 | ) 78 | DemandCon = demand_constrs # 保存为列表 79 | 80 | # 4. 计算能力约束(每个卫星节点一个) 81 | compute_constrs = [] 82 | for i in range(num_sat): 83 | expr = x_i[i] 84 | # 卫星路径终点贡献 85 | for p in range(num_sat_path): 86 | expr += satpath_terminal[i, p] * sat_flow[p] 87 | compute_constrs.append( 88 | model.addConstr(expr <= compute_matrix[i], name=f"ComputeCon_{i}") 89 | ) 90 | ComputeCon = compute_constrs 91 | 92 | # 设置目标函数 93 | obj = gp.LinExpr() 94 | # 本地计算项 95 | obj += obj_weight[0] * sum(x_i[i] for i in range(num_sat)) 96 | # 卫星流量项 97 | obj += obj_weight[1] * sum(sat_flow[p] for p in range(num_sat_path)) 98 | # 地面流量项 99 | obj += obj_weight[2] * sum(ground_flow[p] for p in range(num_ground_path)) 100 | model.setObjective(obj, GRB.MAXIMIZE) # 原MATLAB代码是最大化目标 101 | 102 | # 求解模型 103 | model.optimize() 104 | 105 | # 检查求解状态 106 | if model.status != GRB.OPTIMAL: 107 | raise Exception(f"Optimization failed with status {model.status}") 108 | 109 | # 提取结果 110 | compute_vol = np.zeros(3) 111 | compute_vol[0] = sum(x_i[i].X for i in range(num_sat)) 112 | compute_vol[1] = sum(sat_flow[p].X for p in range(num_sat_path)) 113 | compute_vol[2] = sum(ground_flow[p].X for p in range(num_ground_path)) 114 | 115 | # 提取对偶变量 116 | SatCapCon_dual = np.zeros((num_sat, num_sat)) 117 | for i in range(num_sat): 118 | for j in range(num_sat): 119 | if (i, j) in SatCapCon: 120 | # SatCapCon卫星容量约束, 121 | SatCapCon_dual[i, j] = SatCapCon[(i, j)].Pi 122 | 123 | GroundCapCon_dual = np.zeros(num_sat) 124 | for i in range(num_sat): 125 | if i in GroundCapCon: 126 | # GroundCapCon地面容量约束 127 | GroundCapCon_dual[i] = GroundCapCon[i].Pi 128 | 129 | # 需求约束对偶变量 130 | DemandCon_dual = np.array([c.Pi for c in DemandCon]) 131 | # 计算约束对偶变量 132 | ComputeCon_dual = np.array([c.Pi for c in ComputeCon]) 133 | 134 | return compute_vol, SatCapCon_dual, GroundCapCon_dual, DemandCon_dual, ComputeCon_dual -------------------------------------------------------------------------------- /master_solver_sema.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import gurobipy as gp 3 | from gurobipy import GRB 4 | 5 | 6 | def master_solver(capacity_matrix, demand_matrix, compute_matrix, obj_weight, 7 | satpath_edges, satpath_source, satpath_terminal, 8 | groundpath_edges, groundpath_lastsecond, groundpath_source): 9 | num_sat = satpath_edges.shape[0] 10 | num_sat_path = satpath_edges.shape[2] 11 | num_ground_path = groundpath_edges.shape[2] 12 | 13 | # 创建模型 14 | model = gp.Model("SatelliteNetworkOptimization") 15 | model.setParam('OutputFlag', 0) # 关闭求解日志 16 | 17 | # 定义遥感数据的总数据量大小 18 | D = np.random.uniform(200, 500) 19 | 20 | # 添加变量 21 | x_i = model.addVars(num_sat, lb=0, ub=D, name="x_i") 22 | 23 | 24 | 25 | 26 | x_i = model.addVars(num_sat, name="x_i", lb=0.0) # 卫星本地计算量 27 | sat_flow = model.addVars(num_sat_path, name="sat_flow", lb=0.0) # 卫星路径流量 28 | ground_flow = model.addVars(num_ground_path, name="ground_flow", lb=0.0) # 地面路径流量 29 | 30 | # 存储约束对象以便获取对偶变量 31 | SatCapCon = {} 32 | GroundCapCon = {} 33 | DemandCon = None 34 | ComputeCon = None 35 | 36 | # 1. 卫星间链路容量约束 37 | for i in range(num_sat): 38 | for j in range(num_sat): 39 | if capacity_matrix[i + 1, j + 1] > 0: 40 | # 获取该链路上的所有路径 41 | sat_paths = [p for p in range(num_sat_path) if satpath_edges[i, j, p] > 0] 42 | ground_paths = [p for p in range(num_ground_path) if groundpath_edges[i, j, p] > 0] 43 | 44 | if sat_paths or ground_paths: 45 | # 创建一个表达式 46 | lhs = gp.LinExpr() 47 | # 卫星路径贡献 48 | for p in sat_paths: 49 | lhs += sat_flow[p] 50 | # 地面路径贡献 51 | for p in ground_paths: 52 | lhs += ground_flow[p] 53 | # 添加约束 54 | SatCapCon[(i, j)] = model.addConstr( 55 | lhs <= capacity_matrix[i + 1, j + 1], 56 | name=f"SatCapCon_{i}_{j}" 57 | ) 58 | 59 | # 2. 地面链路容量约束 60 | for i in range(num_sat): 61 | if capacity_matrix[i, 0] > 0: 62 | # 获取所有使用该地面链路的路径 63 | ground_paths = [p for p in range(num_ground_path) if groundpath_lastsecond[i, p] > 0] 64 | if ground_paths: 65 | lhs = gp.LinExpr() 66 | for p in ground_paths: 67 | lhs += ground_flow[p] 68 | GroundCapCon[i] = model.addConstr( 69 | lhs <= capacity_matrix[i, 0], 70 | name=f"GroundCapCon_{i}" 71 | ) 72 | 73 | # 3. 需求约束(每个卫星节点一个) 74 | demand_constrs = [] 75 | for i in range(num_sat): 76 | expr = x_i[i] 77 | # 卫星路径源贡献 78 | for p in range(num_sat_path): 79 | expr += satpath_source[i, p] * sat_flow[p] 80 | # 地面路径源贡献 81 | for p in range(num_ground_path): 82 | expr += groundpath_source[i, p] * ground_flow[p] 83 | demand_constrs.append( 84 | model.addConstr(expr <= demand_matrix[i], name=f"DemandCon_{i}") 85 | ) 86 | DemandCon = demand_constrs # 保存为列表 87 | 88 | # 4. 计算能力约束(每个卫星节点一个) 89 | compute_constrs = [] 90 | for i in range(num_sat): 91 | expr = x_i[i] 92 | # 卫星路径终点贡献 93 | for p in range(num_sat_path): 94 | expr += satpath_terminal[i, p] * sat_flow[p] 95 | compute_constrs.append( 96 | model.addConstr(expr <= compute_matrix[i], name=f"ComputeCon_{i}") 97 | ) 98 | ComputeCon = compute_constrs 99 | 100 | # 设置目标函数 101 | obj = gp.LinExpr() 102 | # 本地计算项 103 | obj += obj_weight[0] * sum(x_i[i] for i in range(num_sat)) 104 | # 卫星流量项 105 | obj += obj_weight[1] * sum(sat_flow[p] for p in range(num_sat_path)) 106 | # 地面流量项 107 | obj += obj_weight[2] * sum(ground_flow[p] for p in range(num_ground_path)) 108 | model.setObjective(obj, GRB.MAXIMIZE) # 原MATLAB代码是最大化目标 109 | 110 | # 求解模型 111 | model.optimize() 112 | 113 | # 检查求解状态 114 | if model.status != GRB.OPTIMAL: 115 | raise Exception(f"Optimization failed with status {model.status}") 116 | 117 | # 提取结果 118 | compute_vol = np.zeros(3) 119 | compute_vol[0] = sum(x_i[i].X for i in range(num_sat)) 120 | compute_vol[1] = sum(sat_flow[p].X for p in range(num_sat_path)) 121 | compute_vol[2] = sum(ground_flow[p].X for p in range(num_ground_path)) 122 | 123 | # 提取对偶变量 124 | SatCapCon_dual = np.zeros((num_sat, num_sat)) 125 | for i in range(num_sat): 126 | for j in range(num_sat): 127 | if (i, j) in SatCapCon: 128 | # SatCapCon卫星容量约束, 129 | SatCapCon_dual[i, j] = SatCapCon[(i, j)].Pi 130 | 131 | GroundCapCon_dual = np.zeros(num_sat) 132 | for i in range(num_sat): 133 | if i in GroundCapCon: 134 | # GroundCapCon地面容量约束 135 | GroundCapCon_dual[i] = GroundCapCon[i].Pi 136 | 137 | # 需求约束对偶变量 138 | DemandCon_dual = np.array([c.Pi for c in DemandCon]) 139 | # 计算约束对偶变量 140 | ComputeCon_dual = np.array([c.Pi for c in ComputeCon]) 141 | 142 | return compute_vol, SatCapCon_dual, GroundCapCon_dual, DemandCon_dual, ComputeCon_dual --------------------------------------------------------------------------------