305 lines
9.4 KiB
Python
305 lines
9.4 KiB
Python
import math
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
# Добавляем путь к родительской директории для корректных импортов
|
||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
||
from gp.chromosome import Chromosome
|
||
from gp.operation import Operation
|
||
from gp.ops import ADD, COS, DIV, EXP, MUL, NEG, SIN, SUB
|
||
from gp.terminal import Terminal
|
||
|
||
|
||
class TestChromosomeEval:
|
||
"""Тесты для метода eval класса Chromosome."""
|
||
|
||
def test_eval_single_terminal(self):
|
||
"""Тест вычисления хромосомы с одним терминалом."""
|
||
x = Terminal("x")
|
||
|
||
def init_single_terminal(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(x, [])
|
||
|
||
chromosome = Chromosome([ADD], [x], init_single_terminal)
|
||
|
||
result = chromosome.eval([5.0])
|
||
assert result == 5.0
|
||
|
||
result = chromosome.eval([-3.5])
|
||
assert result == -3.5
|
||
|
||
def test_eval_addition(self):
|
||
"""Тест вычисления простого сложения: x + y."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_addition(chr: Chromosome) -> Chromosome.Node:
|
||
# Создаём дерево: x + y
|
||
return Chromosome.Node(
|
||
ADD,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([ADD], [x, y], init_addition)
|
||
|
||
result = chromosome.eval([3.0, 4.0])
|
||
assert result == 7.0
|
||
|
||
result = chromosome.eval([10.0, -5.0])
|
||
assert result == 5.0
|
||
|
||
def test_eval_subtraction(self):
|
||
"""Тест вычисления вычитания: x - y."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_subtraction(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(
|
||
SUB,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([SUB], [x, y], init_subtraction)
|
||
|
||
result = chromosome.eval([10.0, 3.0])
|
||
assert result == 7.0
|
||
|
||
result = chromosome.eval([5.0, 8.0])
|
||
assert result == -3.0
|
||
|
||
def test_eval_multiplication(self):
|
||
"""Тест вычисления умножения: x * y."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_multiplication(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(
|
||
MUL,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([MUL], [x, y], init_multiplication)
|
||
|
||
result = chromosome.eval([3.0, 4.0])
|
||
assert result == 12.0
|
||
|
||
result = chromosome.eval([-2.0, 5.0])
|
||
assert result == -10.0
|
||
|
||
def test_eval_division(self):
|
||
"""Тест вычисления деления: x / y."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_division(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(
|
||
DIV,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([DIV], [x, y], init_division)
|
||
|
||
result = chromosome.eval([10.0, 2.0])
|
||
assert result == 5.0
|
||
|
||
result = chromosome.eval([7.0, 2.0])
|
||
assert result == 3.5
|
||
|
||
def test_eval_division_by_zero(self):
|
||
"""Тест деления на ноль (должно вернуть inf)."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_division(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(
|
||
DIV,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([DIV], [x, y], init_division)
|
||
|
||
result = chromosome.eval([10.0, 0.0])
|
||
assert result == float("inf")
|
||
|
||
def test_eval_unary_negation(self):
|
||
"""Тест вычисления унарного минуса: -x."""
|
||
x = Terminal("x")
|
||
|
||
def init_negation(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(NEG, [Chromosome.Node(x, [])])
|
||
|
||
chromosome = Chromosome([NEG], [x], init_negation)
|
||
|
||
result = chromosome.eval([5.0])
|
||
assert result == -5.0
|
||
|
||
result = chromosome.eval([-3.0])
|
||
assert result == 3.0
|
||
|
||
def test_eval_sin(self):
|
||
"""Тест вычисления синуса: sin(x)."""
|
||
x = Terminal("x")
|
||
|
||
def init_sin(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(SIN, [Chromosome.Node(x, [])])
|
||
|
||
chromosome = Chromosome([SIN], [x], init_sin)
|
||
|
||
result = chromosome.eval([0.0])
|
||
assert result == pytest.approx(0.0)
|
||
|
||
result = chromosome.eval([math.pi / 2])
|
||
assert result == pytest.approx(1.0)
|
||
|
||
result = chromosome.eval([math.pi])
|
||
assert result == pytest.approx(0.0, abs=1e-10)
|
||
|
||
def test_eval_cos(self):
|
||
"""Тест вычисления косинуса: cos(x)."""
|
||
x = Terminal("x")
|
||
|
||
def init_cos(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(COS, [Chromosome.Node(x, [])])
|
||
|
||
chromosome = Chromosome([COS], [x], init_cos)
|
||
|
||
result = chromosome.eval([0.0])
|
||
assert result == pytest.approx(1.0)
|
||
|
||
result = chromosome.eval([math.pi / 2])
|
||
assert result == pytest.approx(0.0, abs=1e-10)
|
||
|
||
result = chromosome.eval([math.pi])
|
||
assert result == pytest.approx(-1.0)
|
||
|
||
def test_eval_complex_expression(self):
|
||
"""Тест вычисления сложного выражения: (x + y) * (x - y)."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_complex(chr: Chromosome) -> Chromosome.Node:
|
||
# (x + y) * (x - y)
|
||
return Chromosome.Node(
|
||
MUL,
|
||
[
|
||
Chromosome.Node(
|
||
ADD,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
),
|
||
Chromosome.Node(
|
||
SUB,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
),
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([ADD, SUB, MUL], [x, y], init_complex)
|
||
|
||
# (5 + 3) * (5 - 3) = 8 * 2 = 16
|
||
result = chromosome.eval([5.0, 3.0])
|
||
assert result == 16.0
|
||
|
||
# (10 + 2) * (10 - 2) = 12 * 8 = 96
|
||
result = chromosome.eval([10.0, 2.0])
|
||
assert result == 96.0
|
||
|
||
def test_eval_nested_expression(self):
|
||
"""Тест вычисления вложенного выражения: sin(x + y)."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_nested(chr: Chromosome) -> Chromosome.Node:
|
||
# sin(x + y)
|
||
return Chromosome.Node(
|
||
SIN,
|
||
[
|
||
Chromosome.Node(
|
||
ADD,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
)
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([ADD, SIN], [x, y], init_nested)
|
||
|
||
result = chromosome.eval([math.pi / 4, math.pi / 4])
|
||
# sin(π/4 + π/4) = sin(π/2) = 1
|
||
assert result == pytest.approx(1.0)
|
||
|
||
def test_eval_exp(self):
|
||
"""Тест вычисления экспоненты: exp(x)."""
|
||
x = Terminal("x")
|
||
|
||
def init_exp(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(EXP, [Chromosome.Node(x, [])])
|
||
|
||
chromosome = Chromosome([EXP], [x], init_exp)
|
||
|
||
result = chromosome.eval([0.0])
|
||
assert result == pytest.approx(1.0)
|
||
|
||
result = chromosome.eval([1.0])
|
||
assert result == pytest.approx(math.e)
|
||
|
||
result = chromosome.eval([2.0])
|
||
assert result == pytest.approx(math.e**2)
|
||
|
||
def test_eval_multiple_calls(self):
|
||
"""Тест многократного вызова eval с разными значениями."""
|
||
x = Terminal("x")
|
||
y = Terminal("y")
|
||
|
||
def init_mul(chr: Chromosome) -> Chromosome.Node:
|
||
return Chromosome.Node(
|
||
MUL,
|
||
[
|
||
Chromosome.Node(x, []),
|
||
Chromosome.Node(y, []),
|
||
],
|
||
)
|
||
|
||
chromosome = Chromosome([MUL], [x, y], init_mul)
|
||
|
||
# Проверяем, что терминалы правильно обновляются
|
||
assert chromosome.eval([2.0, 3.0]) == 6.0
|
||
assert chromosome.eval([4.0, 5.0]) == 20.0
|
||
assert chromosome.eval([10.0, 0.5]) == 5.0
|
||
|
||
def test_eval_without_root_raises_error(self):
|
||
"""Тест, что eval вызывает ошибку, если root = None."""
|
||
|
||
def init_none(chr: Chromosome) -> Chromosome.Node:
|
||
return None # type: ignore
|
||
|
||
chromosome = Chromosome([ADD], [Terminal("x")], init_none)
|
||
|
||
with pytest.raises(ValueError, match="Chromosome is not initialized"):
|
||
chromosome.eval([1.0])
|