Files
gausse/tests/test_capacitor_bank.py
jze9 950be5fcf2 Model capacitor banks (series for voltage, parallel for current/capacitance)
Per user: each stage's energy source is now a bank of real capacitors,
n_series x n_parallel, not a single cap.

- components/schema.py CapacitorBank.from_spec: series multiplies voltage
  and ESR and divides capacitance; parallel multiplies capacitance and
  max current and divides ESR; price and count scale with total cap count.
  Duck-typed as a drop-in for CapacitorSpec in StageConfig.
- search_space: cap_series (1-6) and cap_parallel (1-8) per stage gene,
  sampled/mutated/repaired; decode builds the bank; charge voltage capped
  by min(bank voltage, switch rating)
- objective.build_detail records the full bank composition (base part,
  series/parallel, count, totals) and current-capability diagnostics:
  bank_current_over_limit / switch_current_over_limit vs peak discharge
  current (a warning, not a hard reject -- DB holds datasheet not pulse
  ratings; parallel caps already help for real via lower ESR)
- BOM shows "N шт (Sпосл × Pпар)" with per-unit and total price

Already supported and confirmed present (also user-requested): per-coil
independent wire gauge (wire_idx per stage) and computed wire length
(winding_geometry.total_wire_length_m, drives resistance/cost/DB detail).

83 tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 00:53:33 +05:00

81 lines
3.5 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
import pytest
from gausse.components.database import ComponentDatabase
from gausse.components.schema import CapacitorBank, CapacitorSpec
from gausse.optim.objective import build_detail
from gausse.optim.search_space import SearchBounds, decode, sample_genome
from gausse.sim.coilgun import run_coilgun
BASE = CapacitorSpec(
part_number="c-base", capacitance_uf=100.0, voltage_v=400.0, esr_ohm=0.2,
max_current_a=20.0, price=150.0, source="test",
)
def test_series_multiplies_voltage_and_divides_capacitance():
bank = CapacitorBank.from_spec(BASE, n_series=3, n_parallel=1)
assert bank.voltage_v == pytest.approx(1200.0) # 400 * 3
assert bank.capacitance_uf == pytest.approx(100.0 / 3) # C / 3
assert bank.esr_ohm == pytest.approx(0.6) # ESR * 3
assert bank.count == 3
assert bank.price == pytest.approx(450.0) # 150 * 3
def test_parallel_multiplies_capacitance_current_and_divides_esr():
bank = CapacitorBank.from_spec(BASE, n_series=1, n_parallel=4)
assert bank.capacitance_uf == pytest.approx(400.0) # C * 4
assert bank.voltage_v == pytest.approx(400.0) # без изменения
assert bank.esr_ohm == pytest.approx(0.05) # ESR / 4
assert bank.max_current_a == pytest.approx(80.0) # 20 * 4 -- вот "на силу тока"
assert bank.count == 4
def test_series_parallel_combination():
bank = CapacitorBank.from_spec(BASE, n_series=2, n_parallel=3)
assert bank.voltage_v == pytest.approx(800.0)
assert bank.capacitance_uf == pytest.approx(100.0 * 3 / 2)
assert bank.esr_ohm == pytest.approx(0.2 * 2 / 3)
assert bank.count == 6
assert bank.price == pytest.approx(150.0 * 6)
def test_energy_scales_with_bank():
single = CapacitorBank.from_spec(BASE, 1, 1)
bank = CapacitorBank.from_spec(BASE, 2, 2) # V×2, C×1 (2 series, 2 parallel => C*2/2=C)
e_single = 0.5 * (single.capacitance_uf * 1e-6) * single.voltage_v**2
e_bank = 0.5 * (bank.capacitance_uf * 1e-6) * bank.voltage_v**2
# тот же C, но вдвое большее напряжение -> вчетверо больше энергии
assert e_bank == pytest.approx(4 * e_single)
def test_decoded_record_contains_capacitor_bank_composition():
db = ComponentDatabase.load()
bounds = SearchBounds()
rng = random.Random(1)
genome = sample_genome(db, bounds, rng)
config, ix, iv = decode(genome, db, bounds)
result = run_coilgun(config)
detail = build_detail(config, result, db, ix, iv)
bank = detail["stages"][0]["capacitor_bank"]
assert set(bank) >= {"base_part", "n_series", "n_parallel", "count", "total_voltage_rating_v", "max_current_a"}
assert bank["count"] == bank["n_series"] * bank["n_parallel"]
# база должна быть реальной деталью
assert bank["base_part"] in {c.part_number for c in db.capacitors}
def test_genome_carries_series_parallel_and_they_vary():
db = ComponentDatabase.load()
bounds = SearchBounds()
rng = random.Random(5)
series_vals, parallel_vals = set(), set()
for _ in range(40):
g = sample_genome(db, bounds, rng)
for st in g.stages:
assert bounds.cap_series_min <= st.cap_series <= bounds.cap_series_max
assert bounds.cap_parallel_min <= st.cap_parallel <= bounds.cap_parallel_max
series_vals.add(st.cap_series)
parallel_vals.add(st.cap_parallel)
assert len(series_vals) > 1 and len(parallel_vals) > 1