Цвета в табличках, остановка по лучшему решению
This commit is contained in:
@@ -12,12 +12,16 @@
|
|||||||
- "—" для отсутствующих данных
|
- "—" для отсутствующих данных
|
||||||
|
|
||||||
Выходной файл: tables.tex с готовым LaTeX кодом всех таблиц.
|
Выходной файл: tables.tex с готовым LaTeX кодом всех таблиц.
|
||||||
Лучший результат по времени выполнения в каждой таблице выделяется жирным.
|
Лучшие результаты по времени и фитнесу выделяются жирным (и цветом, если задан HIGHLIGHT_COLOR).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Настройка цвета для выделения лучших результатов
|
||||||
|
# None - только жирным, строка (например "magenta") - жирным и цветом
|
||||||
|
HIGHLIGHT_COLOR = "magenta"
|
||||||
|
|
||||||
|
|
||||||
def parse_csv_file(csv_path: str) -> tuple[str, list[list[str]]]:
|
def parse_csv_file(csv_path: str) -> tuple[str, list[list[str]]]:
|
||||||
"""
|
"""
|
||||||
@@ -53,7 +57,7 @@ def parse_csv_file(csv_path: str) -> tuple[str, list[list[str]]]:
|
|||||||
|
|
||||||
def extract_time_value(value: str) -> float | None:
|
def extract_time_value(value: str) -> float | None:
|
||||||
"""
|
"""
|
||||||
Извлекает значение времени из строки формата "X.Y (Z)".
|
Извлекает значение времени из строки формата "X.Y (Z)" или "X.Y (Z) W.V".
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value: Строка с результатом
|
value: Строка с результатом
|
||||||
@@ -73,6 +77,29 @@ def extract_time_value(value: str) -> float | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_fitness_value(value: str) -> float | None:
|
||||||
|
"""
|
||||||
|
Извлекает значение фитнеса из строки формата "X.Y (Z) W.V".
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Строка с результатом
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Значение фитнеса как float или None если значение пустое
|
||||||
|
"""
|
||||||
|
value = value.strip()
|
||||||
|
if value == "—" or value == "" or value == "–":
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ищем паттерн "число.число (число) число.число"
|
||||||
|
# Фитнес - это последнее число в строке
|
||||||
|
match = re.search(r"\)\s+(\d+\.?\d*)\s*$", value)
|
||||||
|
if match:
|
||||||
|
return float(match.group(1))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_best_time(data_rows: list[list[str]]) -> float | None:
|
def find_best_time(data_rows: list[list[str]]) -> float | None:
|
||||||
"""
|
"""
|
||||||
Находит минимальное время выполнения среди всех значений в таблице.
|
Находит минимальное время выполнения среди всех значений в таблице.
|
||||||
@@ -95,13 +122,38 @@ def find_best_time(data_rows: list[list[str]]) -> float | None:
|
|||||||
return min_time
|
return min_time
|
||||||
|
|
||||||
|
|
||||||
def format_value(value: str, best_time: float | None = None) -> str:
|
def find_best_fitness(data_rows: list[list[str]]) -> float | None:
|
||||||
"""
|
"""
|
||||||
Форматирует значение для LaTeX таблицы, выделяя лучший результат жирным.
|
Находит минимальное значение фитнеса среди всех значений в таблице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_rows: Строки данных таблицы
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Минимальное значение фитнеса или None если нет валидных значений
|
||||||
|
"""
|
||||||
|
min_fitness = None
|
||||||
|
|
||||||
|
for row in data_rows:
|
||||||
|
for i in range(1, min(6, len(row))): # Пропускаем первую колонку (Pc)
|
||||||
|
fitness_value = extract_fitness_value(row[i])
|
||||||
|
if fitness_value is not None:
|
||||||
|
if min_fitness is None or fitness_value < min_fitness:
|
||||||
|
min_fitness = fitness_value
|
||||||
|
|
||||||
|
return min_fitness
|
||||||
|
|
||||||
|
|
||||||
|
def format_value(
|
||||||
|
value: str, best_time: float | None = None, best_fitness: float | None = None
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Форматирует значение для LaTeX таблицы, выделяя лучшие результаты жирным.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value: Строковое значение из CSV
|
value: Строковое значение из CSV
|
||||||
best_time: Лучшее время в таблице для сравнения
|
best_time: Лучшее время в таблице для сравнения
|
||||||
|
best_fitness: Лучший фитнес в таблице для сравнения
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Отформатированное значение для LaTeX
|
Отформатированное значение для LaTeX
|
||||||
@@ -110,16 +162,52 @@ def format_value(value: str, best_time: float | None = None) -> str:
|
|||||||
if value == "—" or value == "" or value == "–":
|
if value == "—" or value == "" or value == "–":
|
||||||
return "—"
|
return "—"
|
||||||
|
|
||||||
# Проверяем, является ли это лучшим результатом
|
# Проверяем есть ли фитнес в строке
|
||||||
current_time = extract_time_value(value)
|
fitness_match = re.search(r"(\d+\.?\d*)\s*\((\d+)\)\s+(\d+\.?\d*)\s*$", value)
|
||||||
if (
|
|
||||||
current_time is not None
|
|
||||||
and best_time is not None
|
|
||||||
and abs(current_time - best_time) < 0.001
|
|
||||||
):
|
|
||||||
return f"\\textbf{{{value}}}"
|
|
||||||
|
|
||||||
return value
|
if fitness_match:
|
||||||
|
# Есть фитнес: "время (поколения) фитнес"
|
||||||
|
time_str = fitness_match.group(1)
|
||||||
|
generations_str = fitness_match.group(2)
|
||||||
|
fitness_str = fitness_match.group(3)
|
||||||
|
|
||||||
|
current_time = float(time_str)
|
||||||
|
current_fitness = float(fitness_str)
|
||||||
|
|
||||||
|
# Проверяем, является ли время лучшим
|
||||||
|
time_part = f"{time_str} ({generations_str})"
|
||||||
|
if best_time is not None and abs(current_time - best_time) < 0.001:
|
||||||
|
if HIGHLIGHT_COLOR is not None:
|
||||||
|
time_part = (
|
||||||
|
f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{time_part}}}}}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
time_part = f"\\textbf{{{time_part}}}"
|
||||||
|
|
||||||
|
# Проверяем, является ли фитнес лучшим
|
||||||
|
fitness_part = fitness_str
|
||||||
|
if best_fitness is not None and abs(current_fitness - best_fitness) < 0.00001:
|
||||||
|
if HIGHLIGHT_COLOR is not None:
|
||||||
|
fitness_part = (
|
||||||
|
f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{fitness_part}}}}}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
fitness_part = f"\\textbf{{{fitness_part}}}"
|
||||||
|
|
||||||
|
return f"{time_part} {fitness_part}"
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Нет фитнеса: только "время (поколения)"
|
||||||
|
time_match = re.match(r"(\d+\.?\d*)\s*\((\d+)\)", value)
|
||||||
|
if time_match:
|
||||||
|
current_time = float(time_match.group(1))
|
||||||
|
if best_time is not None and abs(current_time - best_time) < 0.001:
|
||||||
|
if HIGHLIGHT_COLOR is not None:
|
||||||
|
return f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{value}}}}}"
|
||||||
|
else:
|
||||||
|
return f"\\textbf{{{value}}}"
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def generate_latex_table(n: str, header: str, data_rows: list[list[str]]) -> str:
|
def generate_latex_table(n: str, header: str, data_rows: list[list[str]]) -> str:
|
||||||
@@ -134,8 +222,9 @@ def generate_latex_table(n: str, header: str, data_rows: list[list[str]]) -> str
|
|||||||
Returns:
|
Returns:
|
||||||
LaTeX код таблицы
|
LaTeX код таблицы
|
||||||
"""
|
"""
|
||||||
# Находим лучшее время в таблице
|
# Находим лучшее время и лучший фитнес в таблице
|
||||||
best_time = find_best_time(data_rows)
|
best_time = find_best_time(data_rows)
|
||||||
|
best_fitness = find_best_fitness(data_rows)
|
||||||
|
|
||||||
# Извлекаем заголовки колонок из header
|
# Извлекаем заголовки колонок из header
|
||||||
header_parts = header.split(",")
|
header_parts = header.split(",")
|
||||||
@@ -162,7 +251,7 @@ def generate_latex_table(n: str, header: str, data_rows: list[list[str]]) -> str
|
|||||||
|
|
||||||
# Добавляем значения для каждого Pm
|
# Добавляем значения для каждого Pm
|
||||||
for i in range(1, min(6, len(row))): # Максимум 5 колонок Pm
|
for i in range(1, min(6, len(row))): # Максимум 5 колонок Pm
|
||||||
value = format_value(row[i], best_time)
|
value = format_value(row[i], best_time, best_fitness)
|
||||||
latex_code += f" & {value}"
|
latex_code += f" & {value}"
|
||||||
|
|
||||||
# Заполняем недостающие колонки если их меньше 5
|
# Заполняем недостающие колонки если их меньше 5
|
||||||
@@ -207,9 +296,12 @@ def main():
|
|||||||
try:
|
try:
|
||||||
header, data_rows = parse_csv_file(str(csv_file))
|
header, data_rows = parse_csv_file(str(csv_file))
|
||||||
best_time = find_best_time(data_rows)
|
best_time = find_best_time(data_rows)
|
||||||
|
best_fitness = find_best_fitness(data_rows)
|
||||||
latex_table = generate_latex_table(n, header, data_rows)
|
latex_table = generate_latex_table(n, header, data_rows)
|
||||||
tables.append(latex_table)
|
tables.append(latex_table)
|
||||||
print(f"✓ Таблица для N={n} готова (лучшее время: {best_time})")
|
print(
|
||||||
|
f"✓ Таблица для N={n} готова (лучшее время: {best_time}, лучший фитнес: {best_fitness})"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ Ошибка при обработке {csv_file}: {e}")
|
print(f"✗ Ошибка при обработке {csv_file}: {e}")
|
||||||
@@ -221,9 +313,13 @@ def main():
|
|||||||
with open("tables.tex", "w", encoding="utf-8") as f:
|
with open("tables.tex", "w", encoding="utf-8") as f:
|
||||||
f.write("% Автоматически сгенерированные LaTeX таблицы\n")
|
f.write("% Автоматически сгенерированные LaTeX таблицы\n")
|
||||||
f.write(
|
f.write(
|
||||||
"% Лучший результат по времени выполнения в каждой таблице выделен жирным\n"
|
"% Лучший результат по времени и по фитнесу выделены жирным отдельно\n"
|
||||||
)
|
)
|
||||||
f.write("% Убедитесь, что подключен \\usepackage{tabularx}\n")
|
f.write("% Убедитесь, что подключен \\usepackage{tabularx}\n")
|
||||||
|
if HIGHLIGHT_COLOR is not None:
|
||||||
|
f.write(
|
||||||
|
"% ВНИМАНИЕ: Убедитесь, что подключен \\usepackage{xcolor} для цветового выделения\n"
|
||||||
|
)
|
||||||
f.write(
|
f.write(
|
||||||
"% Используйте \\newcolumntype{Y}{>{\\centering\\arraybackslash}X} перед таблицами\n\n"
|
"% Используйте \\newcolumntype{Y}{>{\\centering\\arraybackslash}X} перед таблицами\n\n"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ BASE_DIR = "experiments"
|
|||||||
POPULATION_SIZES = [10, 25, 50, 100]
|
POPULATION_SIZES = [10, 25, 50, 100]
|
||||||
PC_VALUES = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8] # вероятности кроссинговера
|
PC_VALUES = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8] # вероятности кроссинговера
|
||||||
PM_VALUES = [0.001, 0.01, 0.05, 0.1, 0.2] # вероятности мутации
|
PM_VALUES = [0.001, 0.01, 0.05, 0.1, 0.2] # вероятности мутации
|
||||||
|
SAVE_AVG_BEST_FITNESS = True
|
||||||
|
|
||||||
# Количество запусков для усреднения результатов
|
# Количество запусков для усреднения результатов
|
||||||
NUM_RUNS = 1
|
NUM_RUNS = 1
|
||||||
@@ -31,7 +32,9 @@ BASE_CONFIG = {
|
|||||||
"max_generations": 200,
|
"max_generations": 200,
|
||||||
"seed": None, # None для случайности, т. к. всё усредняем
|
"seed": None, # None для случайности, т. к. всё усредняем
|
||||||
"minimize": True,
|
"minimize": True,
|
||||||
"fitness_avg_threshold": 0.05, # критерий остановки
|
# "fitness_avg_threshold": 0.05, # критерий остановки
|
||||||
|
# "max_best_repetitions": 10,
|
||||||
|
"best_value_threshold": 0.005,
|
||||||
# при включенном сохранении графиков на время смотреть бессмысленно
|
# при включенном сохранении графиков на время смотреть бессмысленно
|
||||||
# "save_generations": [1, 50, 199],
|
# "save_generations": [1, 50, 199],
|
||||||
}
|
}
|
||||||
@@ -39,13 +42,15 @@ BASE_CONFIG = {
|
|||||||
|
|
||||||
def run_single_experiment(
|
def run_single_experiment(
|
||||||
pop_size: int, pc: float, pm: float
|
pop_size: int, pc: float, pm: float
|
||||||
) -> tuple[float, float, float, float]:
|
) -> tuple[float, float, float, float, float, float]:
|
||||||
"""
|
"""
|
||||||
Запускает несколько экспериментов с заданными параметрами и усредняет результаты.
|
Запускает несколько экспериментов с заданными параметрами и усредняет результаты.
|
||||||
Возвращает (среднее_время_в_мс, стд_отклонение_времени, среднее_поколений, стд_отклонение_поколений).
|
Возвращает (среднее_время_в_мс, стд_отклонение_времени, среднее_поколений,
|
||||||
|
стд_отклонение_поколений, среднее_лучшее_значение_фитнеса, стд_отклонение_лучшего_значения_фитнеса).
|
||||||
"""
|
"""
|
||||||
times = []
|
times = []
|
||||||
generations = []
|
generations = []
|
||||||
|
best_fitnesses = []
|
||||||
|
|
||||||
for run_num in range(NUM_RUNS):
|
for run_num in range(NUM_RUNS):
|
||||||
config = GARunConfig(
|
config = GARunConfig(
|
||||||
@@ -65,14 +70,26 @@ def run_single_experiment(
|
|||||||
result = genetic_algorithm(config)
|
result = genetic_algorithm(config)
|
||||||
times.append(result.time_ms)
|
times.append(result.time_ms)
|
||||||
generations.append(result.generations_count)
|
generations.append(result.generations_count)
|
||||||
|
best_fitnesses.append(result.best_generation.best_fitness)
|
||||||
|
|
||||||
# Вычисляем средние значения и стандартные отклонения
|
# Вычисляем средние значения и стандартные отклонения
|
||||||
avg_time = statistics.mean(times)
|
avg_time = statistics.mean(times)
|
||||||
std_time = statistics.stdev(times) if len(times) > 1 else 0.0
|
std_time = statistics.stdev(times) if len(times) > 1 else 0.0
|
||||||
avg_generations = statistics.mean(generations)
|
avg_generations = statistics.mean(generations)
|
||||||
std_generations = statistics.stdev(generations) if len(generations) > 1 else 0.0
|
std_generations = statistics.stdev(generations) if len(generations) > 1 else 0.0
|
||||||
|
avg_best_fitness = statistics.mean(best_fitnesses)
|
||||||
|
std_best_fitness = (
|
||||||
|
statistics.stdev(best_fitnesses) if len(best_fitnesses) > 1 else 0.0
|
||||||
|
)
|
||||||
|
|
||||||
return avg_time, std_time, avg_generations, std_generations
|
return (
|
||||||
|
avg_time,
|
||||||
|
std_time,
|
||||||
|
avg_generations,
|
||||||
|
std_generations,
|
||||||
|
avg_best_fitness,
|
||||||
|
std_best_fitness,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_experiments_for_population(pop_size: int) -> PrettyTable:
|
def run_experiments_for_population(pop_size: int) -> PrettyTable:
|
||||||
@@ -92,14 +109,22 @@ def run_experiments_for_population(pop_size: int) -> PrettyTable:
|
|||||||
row = [f"{pc:.1f}"]
|
row = [f"{pc:.1f}"]
|
||||||
for pm in PM_VALUES:
|
for pm in PM_VALUES:
|
||||||
print(f" Эксперимент: pop_size={pop_size}, Pc={pc:.1f}, Pm={pm:.3f}")
|
print(f" Эксперимент: pop_size={pop_size}, Pc={pc:.1f}, Pm={pm:.3f}")
|
||||||
avg_time, std_time, avg_generations, std_generations = (
|
(
|
||||||
run_single_experiment(pop_size, pc, pm)
|
avg_time,
|
||||||
)
|
std_time,
|
||||||
|
avg_generations,
|
||||||
|
std_generations,
|
||||||
|
avg_best_fitness,
|
||||||
|
std_best_fitness,
|
||||||
|
) = run_single_experiment(pop_size, pc, pm)
|
||||||
|
|
||||||
# Форматируем результат: среднее_время±стд_отклонение (среднее_поколения±стд_отклонение)
|
# Форматируем результат: среднее_время±стд_отклонение (среднее_поколения±стд_отклонение)
|
||||||
# cell_value = f"{avg_time:.1f}±{std_time:.1f} ({avg_generations:.1f}±{std_generations:.1f})"
|
# cell_value = f"{avg_time:.1f}±{std_time:.1f} ({avg_generations:.1f}±{std_generations:.1f})"
|
||||||
cell_value = f"{avg_time:.1f} ({avg_generations:.0f})"
|
cell_value = f"{avg_time:.1f} ({avg_generations:.0f})"
|
||||||
|
|
||||||
|
if SAVE_AVG_BEST_FITNESS:
|
||||||
|
cell_value += f" {avg_best_fitness:.5f}"
|
||||||
|
|
||||||
if avg_generations == BASE_CONFIG["max_generations"]:
|
if avg_generations == BASE_CONFIG["max_generations"]:
|
||||||
cell_value = "—"
|
cell_value = "—"
|
||||||
|
|
||||||
@@ -118,9 +143,6 @@ def main():
|
|||||||
print(f"Значения Pc: {PC_VALUES}")
|
print(f"Значения Pc: {PC_VALUES}")
|
||||||
print(f"Значения Pm: {PM_VALUES}")
|
print(f"Значения Pm: {PM_VALUES}")
|
||||||
print(f"Количество запусков для усреднения: {NUM_RUNS}")
|
print(f"Количество запусков для усреднения: {NUM_RUNS}")
|
||||||
print(
|
|
||||||
f"Критерий остановки: среднее значение > {BASE_CONFIG['fitness_avg_threshold']}"
|
|
||||||
)
|
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
# Создаем базовую папку
|
# Создаем базовую папку
|
||||||
|
|||||||
12
lab2/gen.py
12
lab2/gen.py
@@ -42,6 +42,9 @@ class GARunConfig:
|
|||||||
fitness_avg_threshold: float | None = (
|
fitness_avg_threshold: float | None = (
|
||||||
None # порог среднего значения фитнес функции для остановки
|
None # порог среднего значения фитнес функции для остановки
|
||||||
)
|
)
|
||||||
|
best_value_threshold: float | None = (
|
||||||
|
None # остановка при достижении значения фитнеса лучше заданного
|
||||||
|
)
|
||||||
log_every_generation: bool = False # логировать каждое поколение
|
log_every_generation: bool = False # логировать каждое поколение
|
||||||
|
|
||||||
|
|
||||||
@@ -396,6 +399,15 @@ def genetic_algorithm(config: GARunConfig) -> GARunResult:
|
|||||||
# if fitness_variance < config.variance_threshold:
|
# if fitness_variance < config.variance_threshold:
|
||||||
# stop_algorithm = True
|
# stop_algorithm = True
|
||||||
|
|
||||||
|
if config.best_value_threshold is not None:
|
||||||
|
if (
|
||||||
|
config.minimize and current.best_fitness < config.best_value_threshold
|
||||||
|
) or (
|
||||||
|
not config.minimize
|
||||||
|
and current.best_fitness > config.best_value_threshold
|
||||||
|
):
|
||||||
|
stop_algorithm = True
|
||||||
|
|
||||||
if config.fitness_avg_threshold is not None:
|
if config.fitness_avg_threshold is not None:
|
||||||
mean_fitness = np.mean(fitnesses)
|
mean_fitness = np.mean(fitnesses)
|
||||||
if (config.minimize and mean_fitness < config.fitness_avg_threshold) or (
|
if (config.minimize and mean_fitness < config.fitness_avg_threshold) or (
|
||||||
|
|||||||
Reference in New Issue
Block a user