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>
81 lines
3.5 KiB
Python
81 lines
3.5 KiB
Python
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
|