第一个可用的多目标进化策略
This commit is contained in:
parent
95f1467ed7
commit
92ef835422
@ -1,4 +1,5 @@
|
||||
import os
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# 项目根目录
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||
@ -16,10 +17,35 @@ DEFAULT_LLM_CONFIG = {
|
||||
"max_tokens": 1000
|
||||
}
|
||||
|
||||
# 默认问题配置模板
|
||||
DEFAULT_PROBLEM_CONFIG = {
|
||||
"description": "问题描述",
|
||||
"function_name": "solution",
|
||||
"input_format": "输入格式描述",
|
||||
"output_format": "输出格式描述",
|
||||
"evaluation_timeout": 30,
|
||||
"multi_objective": False, # 是否使用多目标优化
|
||||
"objective_names": ["f1", "f2"], # 多目标名称列表
|
||||
"llm_config": DEFAULT_LLM_CONFIG,
|
||||
"evolution_params": {
|
||||
"algorithm": "TU", # TU: Tournament, DE: Differential Evolution, MO: Multi-Objective
|
||||
"population_size": 8,
|
||||
"generations": 10,
|
||||
"mutation_rate": 0.3,
|
||||
"crossover_rate": 0.5,
|
||||
"tournament_size": 3,
|
||||
"F": 0.5, # DE参数
|
||||
"CR": 0.7 # DE参数
|
||||
}
|
||||
}
|
||||
|
||||
# 默认进化参数
|
||||
DEFAULT_EVOLUTION_PARAMS = {
|
||||
"population_size": 8,
|
||||
"generations": 10,
|
||||
"mutation_rate": 0.3,
|
||||
"crossover_rate": 0.5
|
||||
}
|
||||
"crossover_rate": 0.5,
|
||||
"tournament_size": 3,
|
||||
"F": 0.5, # DE参数
|
||||
"CR": 0.7 # DE参数
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
from .tournament import TournamentEvolution
|
||||
from .base import BaseEvolutionAlgorithm
|
||||
from .multi_objective import MultiObjectiveEvolution
|
||||
|
||||
__all__ = ['TournamentEvolution', 'BaseEvolutionAlgorithm']
|
||||
__all__ = ['TournamentEvolution', 'BaseEvolutionAlgorithm', 'MultiObjectiveEvolution']
|
134
src/lead/core/evolution_algorithms/multi_objective.py
Normal file
134
src/lead/core/evolution_algorithms/multi_objective.py
Normal file
@ -0,0 +1,134 @@
|
||||
from typing import List
|
||||
import random
|
||||
import numpy as np
|
||||
from .base import BaseEvolutionAlgorithm
|
||||
from ..multi_objective.individual import MultiObjectiveIndividual
|
||||
from ..llm_integration import LLMClient
|
||||
from ..operators.multi_objective_crossover import MultiObjectiveCrossoverOperator
|
||||
from ..operators.multi_objective_mutation import MultiObjectiveMutationOperator
|
||||
from ..operators.verify_operator import VerifyOperator
|
||||
|
||||
class MultiObjectiveEvolution(BaseEvolutionAlgorithm):
|
||||
"""多目标进化算法,基于NSGA-II框架"""
|
||||
|
||||
def __init__(self, config: dict, llm_client: LLMClient):
|
||||
super().__init__(config)
|
||||
self.llm_client = llm_client
|
||||
self.verify_operator = VerifyOperator(llm_client)
|
||||
self.crossover_operator = MultiObjectiveCrossoverOperator(
|
||||
llm_client, self.verify_operator)
|
||||
self.mutation_operator = MultiObjectiveMutationOperator(
|
||||
llm_client, self.verify_operator)
|
||||
self.tournament_size = config.get("tournament_size", 3)
|
||||
|
||||
def select(self, population: List[MultiObjectiveIndividual],
|
||||
num_parents: int) -> List[MultiObjectiveIndividual]:
|
||||
"""基于非支配排序和拥挤距离的锦标赛选择"""
|
||||
# 先进行非支配排序
|
||||
fronts = self._non_dominated_sort(population)
|
||||
|
||||
# 计算拥挤距离
|
||||
for front in fronts:
|
||||
for ind in front:
|
||||
ind.crowding_distance(front)
|
||||
|
||||
# 锦标赛选择
|
||||
parents = []
|
||||
for _ in range(num_parents):
|
||||
candidates = random.sample(population, min(self.tournament_size, len(population)))
|
||||
# 优先选择前沿等级高的,其次选择拥挤距离大的
|
||||
winner = min(candidates, key=lambda x: (
|
||||
x.rank,
|
||||
-x.crowding_distance(fronts[x.rank])
|
||||
))
|
||||
parents.append(winner)
|
||||
return parents
|
||||
|
||||
def crossover(self, parents: List[MultiObjectiveIndividual]) -> List[MultiObjectiveIndividual]:
|
||||
"""多目标交叉操作"""
|
||||
if len(parents) < 2:
|
||||
return parents
|
||||
|
||||
if random.random() < self.config.get("crossover_rate", 0.9):
|
||||
# 获取目标名称(从第一个父代中获取)
|
||||
objective_names = list(parents[0].fitnesses.keys())
|
||||
|
||||
# 执行多目标交叉
|
||||
child_code, _ = self.crossover_operator.crossover(
|
||||
parents[0], parents[1], objective_names)
|
||||
|
||||
if child_code:
|
||||
return [MultiObjectiveIndividual(
|
||||
child_code,
|
||||
generation=parents[0].generation + 1
|
||||
)]
|
||||
return [parents[0]]
|
||||
|
||||
def mutate(self, individual: MultiObjectiveIndividual) -> MultiObjectiveIndividual:
|
||||
"""多目标变异操作"""
|
||||
if random.random() < self.config.get("mutation_rate", 0.1):
|
||||
# 获取目标名称
|
||||
objective_names = list(individual.fitnesses.keys())
|
||||
|
||||
# 执行多目标变异
|
||||
mutated_code, _ = self.mutation_operator.mutate(
|
||||
individual, objective_names)
|
||||
|
||||
if mutated_code:
|
||||
return MultiObjectiveIndividual(
|
||||
mutated_code,
|
||||
generation=individual.generation
|
||||
)
|
||||
return individual
|
||||
|
||||
def survive(self, population: List[MultiObjectiveIndividual],
|
||||
offspring: List[MultiObjectiveIndividual],
|
||||
pop_size: int) -> List[MultiObjectiveIndividual]:
|
||||
"""基于非支配排序和拥挤距离的精英保留策略"""
|
||||
combined_pop = population + offspring
|
||||
|
||||
# 非支配排序
|
||||
fronts = self._non_dominated_sort(combined_pop)
|
||||
|
||||
# 构建新种群
|
||||
new_pop = []
|
||||
for front in fronts:
|
||||
if len(new_pop) + len(front) <= pop_size:
|
||||
new_pop.extend(front)
|
||||
else:
|
||||
# 计算拥挤距离并选择
|
||||
for ind in front:
|
||||
ind.crowding_distance(front)
|
||||
front = sorted(front, key=lambda x: x.crowding_distance(front), reverse=True)
|
||||
new_pop.extend(front[:pop_size - len(new_pop)])
|
||||
break
|
||||
return new_pop[:pop_size]
|
||||
|
||||
def _non_dominated_sort(self, population: List[MultiObjectiveIndividual]) -> List[List[MultiObjectiveIndividual]]:
|
||||
"""非支配排序"""
|
||||
fronts = [[]]
|
||||
for ind in population:
|
||||
ind.domination_count = 0
|
||||
ind.dominated_set = []
|
||||
for other in population:
|
||||
if ind.dominates(other):
|
||||
ind.dominated_set.append(other)
|
||||
elif other.dominates(ind):
|
||||
ind.domination_count += 1
|
||||
if ind.domination_count == 0:
|
||||
fronts[0].append(ind)
|
||||
ind.rank = 0
|
||||
|
||||
i = 0
|
||||
while fronts[i]:
|
||||
next_front = []
|
||||
for ind in fronts[i]:
|
||||
for dominated_ind in ind.dominated_set:
|
||||
dominated_ind.domination_count -= 1
|
||||
if dominated_ind.domination_count == 0:
|
||||
dominated_ind.rank = i + 1
|
||||
next_front.append(dominated_ind)
|
||||
i += 1
|
||||
fronts.append(next_front)
|
||||
|
||||
return fronts[:-1] # 最后一个是空的
|
@ -1,12 +1,13 @@
|
||||
import os
|
||||
import json
|
||||
import numpy as np
|
||||
from typing import List, Dict, Any
|
||||
from typing import List
|
||||
from ...utils.multi_objective.evaluator import MultiObjectiveEvaluator
|
||||
from ...utils.multi_objective.storage import MultiObjectiveGenerationStorage
|
||||
from .individual import MultiObjectiveIndividual
|
||||
from ..llm_integration import LLMClient
|
||||
from ...config.settings import DEFAULT_EVOLUTION_PARAMS
|
||||
from ..evolution_algorithms.multi_objective import MultiObjectiveEvolution
|
||||
from ..operators.initialize_operator import InitializeOperator
|
||||
|
||||
class MultiObjectiveEvolutionEngine:
|
||||
"""多目标进化引擎"""
|
||||
@ -16,6 +17,7 @@ class MultiObjectiveEvolutionEngine:
|
||||
self.storage = MultiObjectiveGenerationStorage(problem_path)
|
||||
self.evaluator = MultiObjectiveEvaluator(problem_path)
|
||||
self.llm_client = LLMClient.from_config(problem_path)
|
||||
self.initialize_operator = InitializeOperator(self.llm_client)
|
||||
|
||||
# 加载进化参数
|
||||
config = self._load_problem_config()
|
||||
@ -23,16 +25,30 @@ class MultiObjectiveEvolutionEngine:
|
||||
**DEFAULT_EVOLUTION_PARAMS,
|
||||
**config.get("evolution_params", {})
|
||||
}
|
||||
|
||||
# 初始化多目标进化算法
|
||||
self.evolution_algorithm = MultiObjectiveEvolution(
|
||||
self.evolution_params,
|
||||
self.llm_client
|
||||
)
|
||||
|
||||
print(f"多目标进化参数:{self.evolution_params}")
|
||||
|
||||
def initialize_population(self, size: int) -> List[MultiObjectiveIndividual]:
|
||||
"""初始化种群"""
|
||||
# 这里可以集成LLM生成初始代码
|
||||
"""使用LLM生成初始种群"""
|
||||
problem_config = self._load_problem_config()
|
||||
population = []
|
||||
for _ in range(size):
|
||||
# 示例代码,实际应使用LLM生成
|
||||
code = "def solution(input):\n return {'f1': 0.0, 'f2': 0.0}"
|
||||
population.append(MultiObjectiveIndividual(code, generation=0))
|
||||
|
||||
while len(population) < size:
|
||||
code = self.initialize_operator.generate_initial_code(
|
||||
problem_config["description"],
|
||||
problem_config["function_name"],
|
||||
problem_config["input_format"],
|
||||
problem_config["output_format"]
|
||||
)
|
||||
if code:
|
||||
population.append(MultiObjectiveIndividual(code, generation=0))
|
||||
|
||||
return population
|
||||
|
||||
def run_evolution(self, generations: int = None, population_size: int = None):
|
||||
@ -54,18 +70,19 @@ class MultiObjectiveEvolutionEngine:
|
||||
print(f"\n开始第 {gen+1}/{generations} 代进化")
|
||||
|
||||
# 生成子代
|
||||
offspring = self._generate_offspring(population)
|
||||
offspring = []
|
||||
while len(offspring) < population_size:
|
||||
parents = self.evolution_algorithm.select(population, 2)
|
||||
children = self.evolution_algorithm.crossover(parents)
|
||||
for child in children:
|
||||
mutated_child = self.evolution_algorithm.mutate(child)
|
||||
if mutated_child.code:
|
||||
mutated_child.fitnesses = self.evaluator.evaluate(mutated_child.code)
|
||||
print(f"新个体适应度:{mutated_child.fitnesses}")
|
||||
offspring.append(mutated_child)
|
||||
|
||||
# 评估子代
|
||||
for ind in offspring:
|
||||
ind.fitnesses = self.evaluator.evaluate(ind.code)
|
||||
print(f"新个体适应度:{ind.fitnesses}")
|
||||
|
||||
# 合并父代和子代
|
||||
combined_pop = population + offspring
|
||||
|
||||
# 非支配排序和选择
|
||||
population = self._select_new_population(combined_pop, population_size)
|
||||
# 生存选择
|
||||
population = self.evolution_algorithm.survive(population, offspring, population_size)
|
||||
|
||||
# 保存当前代信息
|
||||
self.storage.save_generation(gen, population)
|
||||
@ -77,81 +94,6 @@ class MultiObjectiveEvolutionEngine:
|
||||
print("\n多目标进化完成!")
|
||||
return population
|
||||
|
||||
def _generate_offspring(self, population: List[MultiObjectiveIndividual]) -> List[MultiObjectiveIndividual]:
|
||||
"""生成子代个体"""
|
||||
offspring = []
|
||||
for _ in range(len(population) // 2):
|
||||
# 选择父代
|
||||
parents = self._select_parents(population)
|
||||
|
||||
# 交叉 (这里可以集成LLM)
|
||||
child_code = self._crossover(parents[0].code, parents[1].code)
|
||||
|
||||
# 变异 (这里可以集成LLM)
|
||||
mutated_code = self._mutate(child_code)
|
||||
|
||||
offspring.append(MultiObjectiveIndividual(mutated_code, generation=parents[0].generation + 1))
|
||||
return offspring
|
||||
|
||||
def _select_parents(self, population: List[MultiObjectiveIndividual]) -> List[MultiObjectiveIndividual]:
|
||||
"""使用锦标赛选择父代"""
|
||||
tournament_size = min(5, len(population))
|
||||
parents = []
|
||||
for _ in range(2):
|
||||
candidates = np.random.choice(population, tournament_size, replace=False)
|
||||
# 基于非支配排序和拥挤距离选择
|
||||
candidates = sorted(candidates, key=lambda x: (
|
||||
-x.rank, # 优先选择前沿等级高的
|
||||
x.crowding_distance(population) # 其次选择拥挤距离大的
|
||||
))
|
||||
parents.append(candidates[0])
|
||||
return parents
|
||||
|
||||
def _select_new_population(self, combined_pop: List[MultiObjectiveIndividual], size: int) -> List[MultiObjectiveIndividual]:
|
||||
"""选择新一代种群"""
|
||||
fronts = self._non_dominated_sort(combined_pop)
|
||||
new_pop = []
|
||||
for front in fronts:
|
||||
if len(new_pop) + len(front) <= size:
|
||||
new_pop.extend(front)
|
||||
else:
|
||||
# 计算拥挤距离并选择
|
||||
for ind in front:
|
||||
ind.crowding_distance(front)
|
||||
front = sorted(front, key=lambda x: x.crowding_distance(front), reverse=True)
|
||||
new_pop.extend(front[:size - len(new_pop)])
|
||||
break
|
||||
return new_pop
|
||||
|
||||
def _non_dominated_sort(self, population: List[MultiObjectiveIndividual]) -> List[List[MultiObjectiveIndividual]]:
|
||||
"""非支配排序"""
|
||||
fronts = [[]]
|
||||
for ind in population:
|
||||
ind.domination_count = 0
|
||||
ind.dominated_set = []
|
||||
for other in population:
|
||||
if ind.dominates(other):
|
||||
ind.dominated_set.append(other)
|
||||
elif other.dominates(ind):
|
||||
ind.domination_count += 1
|
||||
if ind.domination_count == 0:
|
||||
fronts[0].append(ind)
|
||||
ind.rank = 0
|
||||
|
||||
i = 0
|
||||
while fronts[i]:
|
||||
next_front = []
|
||||
for ind in fronts[i]:
|
||||
for dominated_ind in ind.dominated_set:
|
||||
dominated_ind.domination_count -= 1
|
||||
if dominated_ind.domination_count == 0:
|
||||
dominated_ind.rank = i + 1
|
||||
next_front.append(dominated_ind)
|
||||
i += 1
|
||||
fronts.append(next_front)
|
||||
|
||||
return fronts[:-1] # 最后一个是空的
|
||||
|
||||
def _calculate_pareto_front(self, population: List[MultiObjectiveIndividual]) -> List[MultiObjectiveIndividual]:
|
||||
"""计算Pareto前沿"""
|
||||
front = []
|
||||
@ -165,16 +107,6 @@ class MultiObjectiveEvolutionEngine:
|
||||
front.append(ind)
|
||||
return front
|
||||
|
||||
def _crossover(self, code1: str, code2: str) -> str:
|
||||
"""交叉操作"""
|
||||
# 这里可以集成LLM的交叉操作
|
||||
return code1 # 简化示例
|
||||
|
||||
def _mutate(self, code: str) -> str:
|
||||
"""变异操作"""
|
||||
# 这里可以集成LLM的变异操作
|
||||
return code # 简化示例
|
||||
|
||||
def _load_problem_config(self) -> dict:
|
||||
"""加载问题配置"""
|
||||
config_path = os.path.join(self.problem_path, "problem_config.json")
|
||||
|
@ -4,7 +4,7 @@ from lead.core.multi_objective.individual import MultiObjectiveIndividual
|
||||
class CrossoverAnalysisOperator:
|
||||
"""交叉分析算子:分析两个父代个体的目标值和支配关系"""
|
||||
|
||||
def analyze(self, parent1: MultiObjectiveIndividual,
|
||||
def analyze(self, parent1: MultiObjectiveIndividual,
|
||||
parent2: MultiObjectiveIndividual,
|
||||
objective_names: List[str]) -> Dict:
|
||||
"""
|
||||
@ -14,14 +14,16 @@ class CrossoverAnalysisOperator:
|
||||
"dominance": "parent1"|"parent2"|"non_dominated",
|
||||
"target_objective": str, # 主要优化目标
|
||||
"parent1_strengths": List[str], # parent1优势目标
|
||||
"parent2_strengths": List[str] # parent2优势目标
|
||||
"parent2_strengths": List[str], # parent2优势目标
|
||||
"function_name": str # 函数名
|
||||
}
|
||||
"""
|
||||
result = {
|
||||
"dominance": self._check_dominance(parent1, parent2),
|
||||
"target_objective": None,
|
||||
"parent1_strengths": [],
|
||||
"parent2_strengths": []
|
||||
"parent2_strengths": [],
|
||||
"function_name": self._extract_function_name(parent1.code)
|
||||
}
|
||||
|
||||
# 比较各目标值,找出各自的优势目标
|
||||
@ -51,11 +53,18 @@ class CrossoverAnalysisOperator:
|
||||
|
||||
return result
|
||||
|
||||
def _check_dominance(self, ind1: MultiObjectiveIndividual,
|
||||
def _check_dominance(self, ind1: MultiObjectiveIndividual,
|
||||
ind2: MultiObjectiveIndividual) -> str:
|
||||
"""检查支配关系"""
|
||||
if ind1.dominates(ind2):
|
||||
return "parent1"
|
||||
elif ind2.dominates(ind1):
|
||||
return "parent2"
|
||||
return "non_dominated"
|
||||
return "non_dominated"
|
||||
|
||||
def _extract_function_name(self, code: str) -> str:
|
||||
"""从代码中提取函数名"""
|
||||
for line in code.split('\n'):
|
||||
if line.strip().startswith('def '):
|
||||
return line.split()[1].split('(')[0]
|
||||
return "solution" # 默认函数名
|
@ -32,11 +32,11 @@ class MultiObjectiveCrossoverOperator:
|
||||
new_code = self.llm_client._call_llm(prompt, operator="crossover")
|
||||
|
||||
# 4. 验证代码
|
||||
verified_code = self._verify_code(new_code)
|
||||
verified_code = self._verify_code(new_code, analysis_info["function_name"])
|
||||
|
||||
return verified_code, analysis_info
|
||||
|
||||
def _build_multi_obj_prompt(self,
|
||||
def _build_multi_obj_prompt(self,
|
||||
parent1: MultiObjectiveIndividual,
|
||||
parent2: MultiObjectiveIndividual,
|
||||
analysis_info: Dict) -> str:
|
||||
@ -49,7 +49,7 @@ class MultiObjectiveCrossoverOperator:
|
||||
父代1代码:
|
||||
{parent1.code}
|
||||
|
||||
父代2优势: {', '.join(analysis_info["parent2_strengths"])}
|
||||
父代2优势: {', '.join(analysis_info["parent2_strengths"])}
|
||||
父代2代码:
|
||||
{parent2.code}
|
||||
|
||||
@ -57,6 +57,7 @@ class MultiObjectiveCrossoverOperator:
|
||||
1. 保留两个父代在各自优势目标上的优点
|
||||
2. 特别优化目标 '{target_obj}' 的性能
|
||||
3. 确保代码完整且符合问题要求
|
||||
4. 函数名必须为: {analysis_info["function_name"]}
|
||||
|
||||
只返回最终的Python代码,不要包含任何解释。
|
||||
"""
|
||||
|
@ -24,6 +24,10 @@ class MultiObjectiveMutationOperator:
|
||||
analysis_info = self.analysis_operator.analyze(
|
||||
individual, objective_names, reference_front)
|
||||
|
||||
# 确保target_objective有效
|
||||
if not analysis_info["target_objective"]:
|
||||
analysis_info["target_objective"] = objective_names[0] if objective_names else "error"
|
||||
|
||||
# 2. 构建多目标变异提示词
|
||||
prompt = self._build_multi_obj_prompt(
|
||||
individual, analysis_info)
|
||||
@ -33,7 +37,7 @@ class MultiObjectiveMutationOperator:
|
||||
|
||||
# 4. 验证代码
|
||||
verified_code = self._verify_code(
|
||||
new_code, analysis_info["target_objective"])
|
||||
new_code, analysis_info["function_name"])
|
||||
|
||||
return verified_code, analysis_info
|
||||
|
||||
@ -42,7 +46,7 @@ class MultiObjectiveMutationOperator:
|
||||
analysis_info: Dict) -> str:
|
||||
"""构建多目标变异提示词"""
|
||||
target_obj = analysis_info["target_objective"]
|
||||
improvement = analysis_info["improvement_potential"][target_obj]
|
||||
improvement = analysis_info["improvement_potential"].get(target_obj, 1.0)
|
||||
|
||||
return f"""
|
||||
请对以下算法进行多目标优化变异,重点改进目标 '{target_obj}' (当前改进潜力: {improvement:.2f}):
|
||||
@ -55,6 +59,8 @@ class MultiObjectiveMutationOperator:
|
||||
2. 专门优化目标 '{target_obj}'
|
||||
3. 可以引入新的数学概念或优化方法
|
||||
4. 确保代码完整且可执行
|
||||
5. 避免使用递归或限制递归深度
|
||||
6. 函数名必须保持为: {analysis_info["function_name"]}
|
||||
|
||||
只返回最终的Python代码,不要包含任何解释。
|
||||
"""
|
||||
|
@ -13,41 +13,59 @@ class MutationAnalysisOperator:
|
||||
{
|
||||
"target_objective": str, # 主要优化目标
|
||||
"improvement_potential": Dict[str, float], # 各目标改进潜力
|
||||
"nearest_solution": Dict[str, float] # 参考前沿中最接近的解(可选)
|
||||
"nearest_solution": Dict[str, float], # 参考前沿中最接近的解(可选)
|
||||
"function_name": str # 函数名
|
||||
}
|
||||
"""
|
||||
result = {
|
||||
"target_objective": None,
|
||||
"improvement_potential": {},
|
||||
"nearest_solution": None
|
||||
"nearest_solution": None,
|
||||
"function_name": self._extract_function_name(individual.code)
|
||||
}
|
||||
|
||||
# 处理空fitnesses的情况
|
||||
if not individual.fitnesses:
|
||||
return result
|
||||
|
||||
# 计算各目标的改进潜力
|
||||
for obj in objective_names:
|
||||
if reference_front:
|
||||
# 如果有参考前沿,计算与前沿中最好解的差距
|
||||
best_val = min(sol[obj] for sol in reference_front)
|
||||
result["improvement_potential"][obj] = individual.fitnesses[obj] - best_val
|
||||
else:
|
||||
# 没有参考前沿,使用归一化值
|
||||
result["improvement_potential"][obj] = individual.fitnesses[obj]
|
||||
if obj in individual.fitnesses:
|
||||
if reference_front:
|
||||
# 如果有参考前沿,计算与前沿中最好解的差距
|
||||
best_val = min(sol[obj] for sol in reference_front)
|
||||
result["improvement_potential"][obj] = individual.fitnesses[obj] - best_val
|
||||
else:
|
||||
# 没有参考前沿,使用归一化值
|
||||
result["improvement_potential"][obj] = individual.fitnesses[obj]
|
||||
|
||||
# 选择改进潜力最大的目标作为变异方向
|
||||
result["target_objective"] = max(
|
||||
result["improvement_potential"].items(),
|
||||
key=lambda x: x[1]
|
||||
)[0]
|
||||
if result["improvement_potential"]:
|
||||
result["target_objective"] = max(
|
||||
result["improvement_potential"].items(),
|
||||
key=lambda x: x[1]
|
||||
)[0]
|
||||
else:
|
||||
# 如果没有有效的改进潜力数据,选择第一个目标
|
||||
result["target_objective"] = objective_names[0] if objective_names else None
|
||||
|
||||
# 如果有参考前沿,找到最接近的解
|
||||
if reference_front:
|
||||
min_distance = float('inf')
|
||||
for sol in reference_front:
|
||||
distance = sum(
|
||||
(individual.fitnesses[obj] - sol[obj]) ** 2
|
||||
for obj in objective_names
|
||||
(individual.fitnesses[obj] - sol[obj]) ** 2
|
||||
for obj in objective_names if obj in individual.fitnesses
|
||||
)
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
result["nearest_solution"] = sol
|
||||
|
||||
return result
|
||||
return result
|
||||
|
||||
def _extract_function_name(self, code: str) -> str:
|
||||
"""从代码中提取函数名"""
|
||||
for line in code.split('\n'):
|
||||
if line.strip().startswith('def '):
|
||||
return line.split()[1].split('(')[0]
|
||||
return "solution" # 默认函数名
|
@ -1,19 +1,27 @@
|
||||
from typing import Callable, List, Tuple
|
||||
from typing import Callable, List, Tuple, Dict
|
||||
import time
|
||||
import sys
|
||||
|
||||
def evaluate(sort_func: Callable[[List[int]], List[int]], dataset: List[Tuple[List[int], List[int]]]) -> float:
|
||||
"""评估排序算法的性能
|
||||
def evaluate(sort_func: Callable[[List[int]], List[int]], dataset: List[Tuple[List[int], List[int]]]) -> Dict[str, float]:
|
||||
"""多目标评估排序算法的性能
|
||||
|
||||
Args:
|
||||
sort_func: 排序函数
|
||||
dataset: [(输入数组, 正确排序结果), ...]
|
||||
|
||||
Returns:
|
||||
float: 适应度分数,分数越小表示性能越好
|
||||
dict: 包含两个评估指标的字典,所有指标都是越小越好
|
||||
{
|
||||
"time": 平均执行时间(秒),
|
||||
"error": 错误率(0-1)
|
||||
}
|
||||
"""
|
||||
total_time = 0
|
||||
success_count = 0
|
||||
|
||||
# 设置递归深度限制防止无限递归
|
||||
sys.setrecursionlimit(1000)
|
||||
|
||||
for input_arr, expected in dataset:
|
||||
try:
|
||||
# 复制输入数组,避免修改原数组
|
||||
@ -30,15 +38,24 @@ def evaluate(sort_func: Callable[[List[int]], List[int]], dataset: List[Tuple[Li
|
||||
total_time += execution_time
|
||||
success_count += 1
|
||||
|
||||
except RecursionError:
|
||||
print("递归深度超过限制,算法可能存在无限递归")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"测试用例执行失败: {str(e)}")
|
||||
continue
|
||||
|
||||
if success_count == 0:
|
||||
return float('inf') # 如果全部失败返回无穷大
|
||||
return {
|
||||
"time": float('inf'),
|
||||
"error": 1.0
|
||||
}
|
||||
|
||||
# 计算最终适应度:平均执行时间 * (1 + 错误率惩罚)
|
||||
# 计算各项指标
|
||||
avg_time = total_time / success_count
|
||||
error_rate = (len(dataset) - success_count) / len(dataset)
|
||||
|
||||
return avg_time * (1 + error_rate * 2) # 错误率会增加最终得分
|
||||
return {
|
||||
"time": avg_time,
|
||||
"error": error_rate
|
||||
}
|
2
src/lead/problems/sort/multi_objective_evolution.log
Normal file
2
src/lead/problems/sort/multi_objective_evolution.log
Normal file
@ -0,0 +1,2 @@
|
||||
Generation 0000 | Pareto Front Size: 2 | Timestamp: 2025-04-15T18:08:58.280070
|
||||
Generation 0001 | Pareto Front Size: 2 | Timestamp: 2025-04-15T18:10:29.667065
|
@ -0,0 +1,67 @@
|
||||
{
|
||||
"generation": 0,
|
||||
"population": [
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n \n def quicksort(arr, low, high):\n if low < high:\n pi = partition(arr, low, high)\n quicksort(arr, low, pi - 1)\n quicksort(arr, pi + 1, high)\n\n def partition(arr, low, high):\n pivot = arr[(low + high) // 2]\n i = low - 1\n j = high + 1\n\n while True:\n i += 1\n while arr[i] < pivot:\n i += 1\n j -= 1\n while arr[j] > pivot:\n j -= 1\n \n if i >= j:\n return j\n \n arr[i], arr[j] = arr[j], arr[i]\n\n def insertion_sort(arr):\n for i in range(1, len(arr)):\n key = arr[i]\n j = i - 1\n while j >= 0 and key < arr[j]:\n arr[j + 1] = arr[j]\n j -= 1\n arr[j + 1] = key\n\n if len(arr) <= 10:\n insertion_sort(arr)\n else:\n quicksort(arr, 0, len(arr) - 1)\n\n return arr",
|
||||
"generation": 0,
|
||||
"fitnesses": {
|
||||
"time": 0.07625889778137207,
|
||||
"error": 0.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(sub_arr):\n if len(sub_arr) <= 10:\n return insertion_sort(sub_arr)\n else:\n pivot = sub_arr[len(sub_arr) // 2]\n left = [x for x in sub_arr if x < pivot]\n middle = [x for x in sub_arr if x == pivot]\n right = [x for x in sub_arr if x > pivot]\n return quicksort(left) + middle + quicksort(right)\n\n return quicksort(arr)",
|
||||
"generation": 1,
|
||||
"fitnesses": {
|
||||
"time": 0.08427751064300537,
|
||||
"error": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(arr, low, high):\n if low < high:\n pi = partition(arr, low, high)\n quicksort(arr, low, pi)\n quicksort(arr, pi + 1, high)\n\n def partition(arr, low, high):\n pivot = arr[(low + high) // 2]\n i = low - 1\n j = high + 1\n\n while True:\n i += 1\n while arr[i] < pivot:\n i += 1\n j -= 1\n while arr[j] > pivot:\n j -= 1\n \n if i >= j:\n return j\n \n arr[i], arr[j] = arr[j], arr[i]\n\n if len(arr) <= 10:\n return insertion_sort(arr)\n else:\n quicksort(arr, 0, len(arr) - 1)\n\n return arr",
|
||||
"generation": 1,
|
||||
"fitnesses": {
|
||||
"time": 0.1091681718826294,
|
||||
"error": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(sub_arr):\n if len(sub_arr) <= 10:\n return insertion_sort(sub_arr)\n else:\n pivot = sub_arr[len(sub_arr) // 2]\n left = [x for x in sub_arr if x < pivot]\n middle = [x for x in sub_arr if x == pivot]\n right = [x for x in sub_arr if x > pivot]\n return quicksort(left) + middle + quicksort(right)\n\n return quicksort(arr)",
|
||||
"generation": 0,
|
||||
"fitnesses": {
|
||||
"time": 0.17589008808135986,
|
||||
"error": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def counting_sort(arr):\n max_val = max(arr)\n min_val = min(arr)\n range_of_elements = max_val - min_val + 1\n count = [0] * range_of_elements\n output = [0] * len(arr)\n\n for num in arr:\n count[num - min_val] += 1\n\n for i in range(1, len(count)):\n count[i] += count[i - 1]\n\n for num in reversed(arr):\n output[count[num - min_val] - 1] = num\n count[num - min_val] -= 1\n\n return output\n\n def quick_sort(arr):\n if len(arr) < 2:\n return arr\n\n pivot = arr[len(arr) // 2]\n left = [x for x in arr if x < pivot]\n middle = [x for x in arr if x == pivot]\n right = [x for x in arr if x > pivot]\n\n return quick_sort(left) + middle + quick_sort(right)\n\n unique_elements = set(arr)\n \n if len(unique_elements) * 2 < len(arr):\n return counting_sort(arr)\n else:\n return quick_sort(arr)",
|
||||
"generation": 0,
|
||||
"fitnesses": {
|
||||
"time": 0.2675272822380066,
|
||||
"error": 0.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"pareto_front": [
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n \n def quicksort(arr, low, high):\n if low < high:\n pi = partition(arr, low, high)\n quicksort(arr, low, pi - 1)\n quicksort(arr, pi + 1, high)\n\n def partition(arr, low, high):\n pivot = arr[(low + high) // 2]\n i = low - 1\n j = high + 1\n\n while True:\n i += 1\n while arr[i] < pivot:\n i += 1\n j -= 1\n while arr[j] > pivot:\n j -= 1\n \n if i >= j:\n return j\n \n arr[i], arr[j] = arr[j], arr[i]\n\n def insertion_sort(arr):\n for i in range(1, len(arr)):\n key = arr[i]\n j = i - 1\n while j >= 0 and key < arr[j]:\n arr[j + 1] = arr[j]\n j -= 1\n arr[j + 1] = key\n\n if len(arr) <= 10:\n insertion_sort(arr)\n else:\n quicksort(arr, 0, len(arr) - 1)\n\n return arr",
|
||||
"generation": 0,
|
||||
"fitnesses": {
|
||||
"time": 0.07625889778137207,
|
||||
"error": 0.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(sub_arr):\n if len(sub_arr) <= 10:\n return insertion_sort(sub_arr)\n else:\n pivot = sub_arr[len(sub_arr) // 2]\n left = [x for x in sub_arr if x < pivot]\n middle = [x for x in sub_arr if x == pivot]\n right = [x for x in sub_arr if x > pivot]\n return quicksort(left) + middle + quicksort(right)\n\n return quicksort(arr)",
|
||||
"generation": 1,
|
||||
"fitnesses": {
|
||||
"time": 0.08427751064300537,
|
||||
"error": 0.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"objective_names": [
|
||||
"time",
|
||||
"error"
|
||||
]
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
{
|
||||
"generation": 1,
|
||||
"population": [
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n \n def quicksort(arr, low, high):\n if low < high:\n pi = partition(arr, low, high)\n quicksort(arr, low, pi - 1)\n quicksort(arr, pi + 1, high)\n\n def partition(arr, low, high):\n pivot = arr[(low + high) // 2]\n i = low - 1\n j = high + 1\n\n while True:\n i += 1\n while arr[i] < pivot:\n i += 1\n j -= 1\n while arr[j] > pivot:\n j -= 1\n \n if i >= j:\n return j\n \n arr[i], arr[j] = arr[j], arr[i]\n\n def insertion_sort(arr):\n for i in range(1, len(arr)):\n key = arr[i]\n j = i - 1\n while j >= 0 and key < arr[j]:\n arr[j + 1] = arr[j]\n j -= 1\n arr[j + 1] = key\n\n if len(arr) <= 10:\n insertion_sort(arr)\n else:\n quicksort(arr, 0, len(arr) - 1)\n\n return arr",
|
||||
"generation": 0,
|
||||
"fitnesses": {
|
||||
"time": 0.07625889778137207,
|
||||
"error": 0.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(sub_arr):\n if len(sub_arr) <= 10:\n return insertion_sort(sub_arr)\n else:\n pivot = sub_arr[len(sub_arr) // 2]\n left = [x for x in sub_arr if x < pivot]\n middle = [x for x in sub_arr if x == pivot]\n right = [x for x in sub_arr if x > pivot]\n return quicksort(left) + middle + quicksort(right)\n\n return quicksort(arr)",
|
||||
"generation": 1,
|
||||
"fitnesses": {
|
||||
"time": 0.08427751064300537,
|
||||
"error": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(sub_arr):\n if len(sub_arr) <= 10:\n return insertion_sort(sub_arr)\n else:\n pivot = sub_arr[len(sub_arr) // 2]\n left = [x for x in sub_arr if x < pivot]\n middle = [x for x in sub_arr if x == pivot]\n right = [x for x in sub_arr if x > pivot]\n return quicksort(left) + middle + quicksort(right)\n\n return quicksort(arr)",
|
||||
"generation": 0,
|
||||
"fitnesses": {
|
||||
"time": 0.17589008808135986,
|
||||
"error": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(arr, low, high):\n if low < high:\n pi = partition(arr, low, high)\n quicksort(arr, low, pi)\n quicksort(arr, pi + 1, high)\n\n def partition(arr, low, high):\n pivot = arr[(low + high) // 2]\n i = low - 1\n j = high + 1\n\n while True:\n i += 1\n while arr[i] < pivot:\n i += 1\n j -= 1\n while arr[j] > pivot:\n j -= 1\n \n if i >= j:\n return j\n \n arr[i], arr[j] = arr[j], arr[i]\n\n if len(arr) <= 10:\n return insertion_sort(arr)\n else:\n quicksort(arr, 0, len(arr) - 1)\n\n return arr",
|
||||
"generation": 1,
|
||||
"fitnesses": {
|
||||
"time": 0.3470318913459778,
|
||||
"error": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n \n def quicksort(arr, low, high):\n if low < high:\n pi = partition(arr, low, high)\n quicksort(arr, low, pi - 1)\n quicksort(arr, pi + 1, high)\n\n def partition(arr, low, high):\n pivot = arr[(low + high) // 2]\n i = low - 1\n j = high + 1\n\n while True:\n i += 1\n while i < high and arr[i] < pivot:\n i += 1\n j -= 1\n while j > low and arr[j] > pivot:\n j -= 1\n \n if i >= j:\n return j\n \n arr[i], arr[j] = arr[j], arr[i]\n\n def insertion_sort(arr):\n for i in range(1, len(arr)):\n key = arr[i]\n j = i - 1\n while j >= 0 and key < arr[j]:\n arr[j + 1] = arr[j]\n j -= 1\n arr[j + 1] = key\n\n if len(arr) <= 10:\n insertion_sort(arr)\n else:\n quicksort(arr, 0, len(arr) - 1)\n\n return arr",
|
||||
"generation": 1,
|
||||
"fitnesses": {
|
||||
"time": 0.24364137649536133,
|
||||
"error": 0.75
|
||||
}
|
||||
}
|
||||
],
|
||||
"pareto_front": [
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n \n def quicksort(arr, low, high):\n if low < high:\n pi = partition(arr, low, high)\n quicksort(arr, low, pi - 1)\n quicksort(arr, pi + 1, high)\n\n def partition(arr, low, high):\n pivot = arr[(low + high) // 2]\n i = low - 1\n j = high + 1\n\n while True:\n i += 1\n while arr[i] < pivot:\n i += 1\n j -= 1\n while arr[j] > pivot:\n j -= 1\n \n if i >= j:\n return j\n \n arr[i], arr[j] = arr[j], arr[i]\n\n def insertion_sort(arr):\n for i in range(1, len(arr)):\n key = arr[i]\n j = i - 1\n while j >= 0 and key < arr[j]:\n arr[j + 1] = arr[j]\n j -= 1\n arr[j + 1] = key\n\n if len(arr) <= 10:\n insertion_sort(arr)\n else:\n quicksort(arr, 0, len(arr) - 1)\n\n return arr",
|
||||
"generation": 0,
|
||||
"fitnesses": {
|
||||
"time": 0.07625889778137207,
|
||||
"error": 0.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "def advanced_sort(arr):\n if len(arr) < 2:\n return arr\n\n def insertion_sort(sub_arr):\n for i in range(1, len(sub_arr)):\n key = sub_arr[i]\n j = i - 1\n while j >= 0 and sub_arr[j] > key:\n sub_arr[j + 1] = sub_arr[j]\n j -= 1\n sub_arr[j + 1] = key\n return sub_arr\n\n def quicksort(sub_arr):\n if len(sub_arr) <= 10:\n return insertion_sort(sub_arr)\n else:\n pivot = sub_arr[len(sub_arr) // 2]\n left = [x for x in sub_arr if x < pivot]\n middle = [x for x in sub_arr if x == pivot]\n right = [x for x in sub_arr if x > pivot]\n return quicksort(left) + middle + quicksort(right)\n\n return quicksort(arr)",
|
||||
"generation": 1,
|
||||
"fitnesses": {
|
||||
"time": 0.08427751064300537,
|
||||
"error": 0.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"objective_names": [
|
||||
"time",
|
||||
"error"
|
||||
]
|
||||
}
|
@ -1,23 +1,24 @@
|
||||
{
|
||||
"description": "实现一个高效的排序算法,要求时间复杂度尽可能低。算法需要处理各种情况:随机数组、接近有序的数组、逆序数组、包含大量重复元素的数组等。同时由于你拥有的评估时间仅有2秒,请确保你的算法在2秒内能够完成排序。而且你只能使用python语言。注意我们用于评估你的测试用例的数组大小为100000,请确保你的算法在100000个元素的数组上也能在2秒内完成排序。",
|
||||
"description": "实现一个高效的排序算法,需要同时优化执行时间和正确性。算法需要处理各种情况:随机数组、接近有序的数组、逆序数组、包含大量重复元素的数组等。",
|
||||
"function_name": "advanced_sort",
|
||||
"input_format": "一个整数列表arr,包含需要排序的数字",
|
||||
"output_format": "返回排序后的列表,按照从小到大的顺序排列",
|
||||
"evaluation_timeout": 2,
|
||||
"multi_objective": true,
|
||||
"objective_names": ["time", "error"],
|
||||
"llm_config": {
|
||||
"api_key": "sk-5wHuJRuC2KDMINeYERru0zCCMrRTzmkrOMwpvBQr4EULV0Tv",
|
||||
"base_url": "https://xiaohumini.site/v1/chat/completions",
|
||||
"model": "gemini-1.5-flash-latest",
|
||||
"model": "gpt-4o-mini",
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 4096
|
||||
},
|
||||
"evolution_params": {
|
||||
"algorithm": "TU",
|
||||
"algorithm": "MO",
|
||||
"population_size": 5,
|
||||
"generations": 2,
|
||||
"mutation_rate": 0.3,
|
||||
"crossover_rate": 0.5,
|
||||
"F": 0.2,
|
||||
"CR": 0.5
|
||||
"crossover_rate": 0.7,
|
||||
"tournament_size": 3
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
from lead.core.evolution import EvolutionEngine
|
||||
from lead.core.multi_objective.evolution import MultiObjectiveEvolutionEngine
|
||||
from lead.config.settings import PROBLEMS_DIR
|
||||
|
||||
def parse_args():
|
||||
@ -14,6 +16,19 @@ def parse_args():
|
||||
help="种群大小")
|
||||
return parser.parse_args()
|
||||
|
||||
def load_engine_config(problem_path):
|
||||
"""加载问题配置并决定使用哪种引擎"""
|
||||
config_path = os.path.join(problem_path, "problem_config.json")
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
if config.get("multi_objective", False):
|
||||
print("使用多目标进化引擎")
|
||||
return MultiObjectiveEvolutionEngine(problem_path)
|
||||
else:
|
||||
print("使用单目标进化引擎")
|
||||
return EvolutionEngine(problem_path)
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
problem_path = os.path.join(PROBLEMS_DIR, args.problem)
|
||||
@ -27,7 +42,7 @@ def main():
|
||||
print(f"开始进化求解问题: {args.problem}")
|
||||
print(f"配置: 代数={args.generations or '默认'},种群大小={args.population or '默认'}")
|
||||
|
||||
engine = EvolutionEngine(problem_path)
|
||||
engine = load_engine_config(problem_path)
|
||||
engine.run_evolution(
|
||||
generations=args.generations,
|
||||
population_size=args.population
|
||||
@ -36,4 +51,4 @@ def main():
|
||||
print("进化完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user