- physics/ac_resistance.py: R_AC обмотки по Доуэллу на частоте импульса 1/sqrt(LC) — для толстого провода во многих слоях потери в разы выше DC - трение о трубку (0.35·m·g) + аэродинамика (0.5·rho·Cd·A·v^2) во всей динамике: CPU ОДУ разряда, подлёт к датчику (с событием остановки), GPU numpy-путь, fused cupy-ядро, аналитический coast GPU-sweep'а - SwitchSpec.pulse_current_a: паспортные ITSM/IDM/ICM из даташитов вместо generic-множителей; отчёт теперь различает превышение продолжительного рейтинга (норма для импульса) и импульсного предела (отбраковка) — фикс вводившего в заблуждение флага switch_current_over_limit - КПД теперь может быть слегка отрицательным (трение съело больше, чем добавила слабая катушка) — это честно - MODEL_VERSION -> gausse-physics-v2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2.6 KiB
Python
63 lines
2.6 KiB
Python
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
|
||
# КПД <= 1 всегда; может быть СЛЕГКА отрицательным: трение о трубку
|
||
# за время разряда может съесть больше, чем слабая катушка добавила
|
||
assert -0.01 <= 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"])
|