Skip to content
This repository has been archived by the owner on Sep 6, 2024. It is now read-only.

Commit

Permalink
feat(lab-01): monotonic iterative added
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey N. Alexandrov authored and Alexey N. Alexandrov committed Mar 11, 2024
1 parent 8861927 commit 414b7fd
Show file tree
Hide file tree
Showing 9 changed files with 866 additions and 215 deletions.
Binary file added assets/Metodichka_add_V3.pdf
Binary file not shown.
Empty file.
580 changes: 580 additions & 0 deletions game_theory/01-rk-monotonous_algorithm/monotonic_algorithm.ipynb

Large diffs are not rendered by default.

307 changes: 92 additions & 215 deletions game_theory/introduction_rgz_01/rgz.ipynb

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions game_theory/utils/matrix_games/game_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import numpy as np
from prettytable import PrettyTable

from game_theory.utils.simplex.dual_problem import DualProblem
from game_theory.utils.simplex.simplex_problem import SimplexProblem

from .exceptions import MatrixGameException
from .types import ComparisonOperator, IndexType, LabelType, SizeType, ValueType

Expand Down Expand Up @@ -129,6 +132,49 @@ def reduce_dimension(self, method="dominant_absorption") -> "GameMatrix":
reduced_matrix: GameMatrix = self._base_game_reduce(method=method)
return reduced_matrix

def solve(self, player="A") -> tuple[ValueType, tuple[float, ...]]:
"""
Решает матричную игру относительно выбранного игрока.
(поддерживается симплекс-метод, как вполне универсальное решение)
:param player: Метка первого или второго игрока:
- "A" (default) - метка первого игрока A;
- "B" - метка второго игрока B.
:return: Возвращает значение цены игры и вектор смешанных стратегий игрока.
"""
# Проверка седловой точки.
i, lgp_value = self.lowest_game_price
j, hgp_value = self.highest_game_price
if lgp_value == hgp_value:
strategy_count, idx = (self.shape[0], i) if player == "A" else (self.shape[1], j)
clear_strategy = [0] * strategy_count
clear_strategy[idx] = 1
_logger.info(f"Седловая точка найдена: {lgp_value, clear_strategy}")
return lgp_value, tuple(clear_strategy)

match player:
case "A":
problem_cls = DualProblem
player_strategy_labels = self.player_a_strategy_labels
case "B":
problem_cls = SimplexProblem
player_strategy_labels = self.player_b_strategy_labels
case _:
exc_msg = f"Invalid player label: {player}"
raise MatrixGameException(exc_msg)

problem: SimplexProblem | DualProblem = problem_cls.from_constraints(
obj_func_coffs=[1] * self.shape[1],
constraint_system_lhs=self.matrix.tolist(),
constraint_system_rhs=[1] * self.shape[0],
func_direction="max",
)
var_values, target_function_value = problem.solve()

game_price_value: ValueType = 1 / target_function_value
mixed_strategies = [var_value * game_price_value for var_value in var_values]
mixed_strategies = mixed_strategies[: len(player_strategy_labels)]
return game_price_value, tuple(mixed_strategies)

def _base_game_reduce(self, method="dominant_absorption") -> "GameMatrix":
"""
Сводит к минимуму размерность матричной игры поглощением стратегий.
Expand Down
Empty file.
113 changes: 113 additions & 0 deletions game_theory/utils/matrix_games/monotonic/monotonic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Монотонный итеративный алгоритм решения матричной игры (n x m)-игры с нулевой суммой."""
import logging
import random

import numpy as np
from numpy import ndarray

from game_theory.utils.matrix_games.game_matrix import GameMatrix
from game_theory.utils.matrix_games.types import IndexType, ValueType

_logger = logging.getLogger(__name__)


class Monotonic:
"""Класс инкапсулирует решение матричной игры монотонным итеративным методом."""

def __init__(self, game_matrix: GameMatrix):
self.game: GameMatrix = game_matrix

self.iteration_number: int = 0
self.strategy_index: IndexType
self.strategy_x: np.ndarray[float]
self.scores_c: np.ndarray[ValueType]
self.price_v: ValueType
self.indicator_mask_j: np.ndarray[bool]

def solve(self):
# Решение за игрока A.
_logger.info("Решение игры относительно игрока A")
(
price_a,
strategy_a,
) = self._base_solve(self.game.matrix.copy())
# Решения за игрока B.
_logger.info("Решение игры относительно игрока B")
price_b, strategy_b = self._base_solve(self.game.matrix.T.copy())
return (price_a, strategy_a), (price_b, strategy_b)

def _base_solve(self, matrix: np.ndarray[ValueType]):
m, n = matrix.shape
self.iteration_number = 0
_logger.info("Итерация 0:")
# Выбираем произвольную (x^0) чистую стратегию (выставляя 1 только в одну позицию).
self.strategy_index = random.randint(0, m - 1)
self.strategy_x = np.array([0] * n)
self.strategy_x[self.strategy_index] = 1
# Выбираем вектор (c^0), соответствующий выбранной стратегии.
self.scores_c: np.ndarray = matrix[self.strategy_index].copy()
# Текущая цена игры.
self.price_v = np.min(self.scores_c)
# Вектор-индикатор, который показывает принадлежность к множеству.
self.indicator_mask_j: np.ndarray[bool] = self.scores_c == self.price_v
self.__log_calculated_parameters()

alpha_values = np.array((np.inf, np.inf))
# Выполняем итерации без заданной точности, то есть пока α_N не станет 0.
while not np.allclose(alpha_values, [0, 1]):
optimal_strategy_x_, optimal_scores_c_, alpha_values = self.perform_iteration(matrix)

alpha, _ = alpha_values
self.strategy_x = (1 - alpha) * self.strategy_x + alpha * optimal_strategy_x_
self.scores_c = (1 - alpha) * self.scores_c + alpha * optimal_scores_c_
self.price_v = np.min(self.scores_c)
self.indicator_mask_j = self.scores_c == self.price_v
self.__log_calculated_parameters()

return self.price_v, self.strategy_x.copy()

def perform_iteration(self, matrix: np.ndarray[ValueType]) -> tuple[ndarray, ndarray, ndarray]:
self.iteration_number += 1
i = self.iteration_number
_logger.info(f"Итерация {self.iteration_number}:")
# Выбираем только столбцы, удовлетворяющие нашему индикатору.
sub_game_matrix_a = GameMatrix(matrix[:, self.indicator_mask_j].copy())
_logger.info(f"Рассмотрим подыгру Г^{i}: " f"\n{np.around(sub_game_matrix_a.matrix, 3)}")
# Решаем подыгру и находим оптимальную стратегию x_.
_, optimal_strategy_x_ = sub_game_matrix_a.solve()
optimal_scores_c_: np.ndarray = self.__reduce_sum(matrix, np.array(optimal_strategy_x_))
_logger.info(
f"Оптимальная стратегия игрока: "
f"\n\t‾x_{i} = {optimal_strategy_x_}"
f"\n\t‾c_{i} = {tuple(optimal_scores_c_)}"
)
# Находим оптимальную стратегию игрока в подыгре из двух строк.
sub_game_gamma = GameMatrix(np.stack((self.scores_c, optimal_scores_c_)))
_logger.info(
f"Находим оптимальную стратегию игрока в подыгре из двух строк: " f"\n{np.around(sub_game_gamma.matrix, 3)}"
)
sub_game_gamma = sub_game_gamma.reduce_dimension(method="nbr_drop")
_logger.info(f"Матрица после уменьшения размерности: " f"\n{np.around(sub_game_gamma.matrix, 3)}")
_, alpha_values = sub_game_gamma.solve()
alpha_values = (alpha_values[1], alpha_values[0])
_logger.info(
f"В результате получена оптимальная стратегия (α_{i}, 1 - α_{i}) = " f"{np.around(alpha_values, 3)}"
)
return np.array(optimal_strategy_x_), optimal_scores_c_, np.array(alpha_values)

@staticmethod
def __reduce_sum(lhs: np.ndarray, rhs: np.ndarray) -> np.ndarray:
return np.sum((rhs * lhs.T).T, 0)

def __log_calculated_parameters(self, accuracy=3):
j_indexes = list(*np.where(self.indicator_mask_j))
_logger.info(
"\n".join(
[
f"x^{self.iteration_number} = {np.around(self.strategy_x, accuracy)}",
f"c^{self.iteration_number} = {np.around(self.scores_c, accuracy)}",
f"v^{self.iteration_number} = {round(self.price_v, accuracy)}",
f"J^{self.iteration_number} = {j_indexes}",
]
)
)
33 changes: 33 additions & 0 deletions game_theory/utils/simplex/simplex_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import logging
from pathlib import Path
from tempfile import NamedTemporaryFile

import numpy as np

Expand Down Expand Up @@ -49,13 +50,45 @@ def __init__(self, input_path: Path):
if self.func_direction_ == "max":
self.obj_func_coffs_ *= -1

_logger.info(str(self))

# Инициализация симплекс-таблицы.
self.simplex_table_ = SimplexTable(
obj_func_coffs=self.obj_func_coffs_,
constraint_system_lhs=self.constraint_system_lhs_,
constraint_system_rhs=self.constraint_system_rhs_,
)

@classmethod
def from_constraints(
cls,
obj_func_coffs: list,
constraint_system_lhs: list,
constraint_system_rhs: list,
func_direction="max",
) -> "SimplexProblem":
"""
Альтернативный конструктор, использующий входные значения напрямую.
:param obj_func_coffs: Вектор-строка с - коэффициенты ЦФ.
:param constraint_system_lhs: Матрица ограничений А.
:param constraint_system_rhs: Вектор-столбец ограничений b.
:param func_direction: Направление задачи ("min" (default) или "max").
:return: Экземпляр класса SimplexProblem.
"""
with NamedTemporaryFile(mode="w") as input_file:
input_path = Path(input_file.name)
input_path.write_text(
json.dumps(
{
"obj_func_coffs": obj_func_coffs,
"constraint_system_lhs": constraint_system_lhs,
"constraint_system_rhs": constraint_system_rhs,
"func_direction": func_direction,
}
)
)
return cls(input_path)

def __str__(self):
"""Вывод условия прямой задачи ЛП."""
return "\n".join(
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@ ignore = [
"PLR09", # Too many <...>
"PLR2004", # Magic value used in comparison
"ISC001", # Conflicts with formatter
"G004", # f-strings in logging
]
# A list of allowed "confusable" Unicode chars to ignore
# when enforcing RUF001, RUF002, and RUF003.
allowed-confusables = [
"а", "А", "в", "В", "е", "Е", "к", "К",
"н", "Н", "о", "О", "р", "Р", "с", "С",
"т", "Т", "г", "Г", "у", "У", "х", "Х",
"α",
]

[build-system]
Expand Down

0 comments on commit 414b7fd

Please sign in to comment.