Files
gausse/tests/test_objective.py
jze9 56c7ab9c1d Add variable-length genome search space and objective function (Stage 6 pt.1)
- optim/search_space.py: Genome encodes only indices into the real
  component database plus continuous/discrete geometry parameters; number
  of stages is itself mutable via add/remove/duplicate-stage operators.
  repair() clips everything back into bounds after mutation/crossover so
  decode() never sees an invalid genome.
- optim/objective.py: evaluate() decodes a genome, computes real BOM cost
  from wire length/price and component prices, runs the full chain, and
  scores fitness = efficiency for feasible runs or a soft penalty scaled
  by how many stages it got through for infeasible ones (so the GA gets
  gradient instead of a wall).

Tested against the real component database (not synthetic fixtures) —
including an explicit infeasible case using the real inductive sensor's
actual sensitivity/threshold values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:26:16 +05:00

61 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import random
from gausse.components.database import ComponentDatabase
from gausse.optim.objective import compute_cost_rub, decoded_summary, evaluate
from gausse.optim.search_space import SearchBounds, decode, sample_genome
DB = ComponentDatabase.load()
BOUNDS = SearchBounds()
def test_evaluate_feasible_genome_has_fitness_equal_efficiency():
rng = random.Random(2)
found_feasible = False
for _ in range(50):
genome = sample_genome(DB, BOUNDS, rng)
result = evaluate(genome, DB, BOUNDS)
if result.feasible:
found_feasible = True
assert result.fitness == result.efficiency
assert 0.0 <= result.efficiency <= 1.0
assert result.cost_rub > 0
break
assert found_feasible, "ни один из 50 случайных геномов не оказался реализуемым"
def test_evaluate_infeasible_genome_has_negative_fitness_and_reason():
rng = random.Random(9)
genome = sample_genome(DB, BOUNDS, rng)
# индукционный датчик требует скорости >= порог/чувствительность = 5 м/с,
# а старт по умолчанию 3 м/с -- первая ступень обязана провалиться
inductive_idx = next(i for i, s in enumerate(DB.sensors) if s.kind == "inductive")
genome.stages[0].sensor_idx = inductive_idx
result = evaluate(genome, DB, BOUNDS)
assert not result.feasible
assert result.fitness < 0
assert result.reason is not None
assert result.failed_stage_index == 0
def test_compute_cost_rub_is_positive_and_scales_with_stage_count():
rng = random.Random(4)
genome = sample_genome(DB, BOUNDS, rng)
config, _, _ = decode(genome, DB, BOUNDS)
cost = compute_cost_rub(config, DB)
assert cost > 0
genome.stages.append(genome.stages[0])
genome.inter_stage_gaps_m.append(0.05)
config2, _, _ = decode(genome, DB, BOUNDS)
cost2 = compute_cost_rub(config2, DB)
assert cost2 > cost
def test_decoded_summary_uses_real_part_numbers():
rng = random.Random(6)
genome = sample_genome(DB, BOUNDS, rng)
summary = decoded_summary(genome, DB)
assert len(summary["stages"]) == len(genome.stages)
real_wire_ids = {w.part_id for w in DB.wires}
assert all(s["wire"] in real_wire_ids for s in summary["stages"])