- components/schema.py + database.py: typed loader for real retail parts (wire, capacitors, switches, sensors, projectile materials) - physics/inductance.py: multilayer solenoid inductance (Wheeler) with ferromagnetic-slug coupling via smooth overlap model and analytic dL/dx - physics/force.py: F = 0.5*I^2*dL/dx with a saturation clamp - physics/circuit.py: coupled [Q,I,x,v] discharge ODE - Validated against textbook RLC analytical solutions (under/over/critically damped) and energy conservation, not just spot-checked by eye - PLAN.md updated with animation (per-run field visualization) and GPU-accelerated batch sweep as explicit later stages Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
import math
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from gausse.physics.inductance import (
|
|
CoilInductanceModel,
|
|
air_core_inductance_wheeler,
|
|
demagnetizing_factor_prolate,
|
|
effective_permeability,
|
|
winding_geometry,
|
|
)
|
|
|
|
|
|
def test_winding_geometry_scales_with_turns_and_layers():
|
|
geo = winding_geometry(tube_od_m=0.02, wire_od_m=0.001, turns_per_layer=50, layers=3)
|
|
assert geo.total_turns == 150
|
|
assert geo.coil_length_m == pytest.approx(0.05)
|
|
assert geo.radial_depth_m == pytest.approx(0.003)
|
|
assert geo.total_wire_length_m > 0
|
|
# больше слоёв -> больше провода при тех же витках на слой
|
|
geo_more_layers = winding_geometry(0.02, 0.001, 50, 5)
|
|
assert geo_more_layers.total_wire_length_m > geo.total_wire_length_m
|
|
|
|
|
|
def test_wheeler_inductance_is_positive_and_grows_with_turns():
|
|
l_small = air_core_inductance_wheeler(mean_radius_m=0.015, length_m=0.05, depth_m=0.003, turns=50)
|
|
l_large = air_core_inductance_wheeler(mean_radius_m=0.015, length_m=0.05, depth_m=0.003, turns=200)
|
|
assert l_small > 0
|
|
assert l_large > l_small
|
|
|
|
|
|
def test_demagnetizing_factor_bounds():
|
|
# почти сфера (aspect_ratio -> 1): классическое значение 1/3
|
|
assert demagnetizing_factor_prolate(1.001) == pytest.approx(1 / 3, abs=0.02)
|
|
# очень вытянутый стержень: коэффициент размагничивания стремится к 0
|
|
assert demagnetizing_factor_prolate(200.0) < 0.01
|
|
|
|
|
|
def test_effective_permeability_limits():
|
|
# без размагничивания (Nd=0) эффективная проницаемость равна мю материала
|
|
assert effective_permeability(mu_r=500.0, demagnetizing_factor=0.0) == pytest.approx(500.0)
|
|
# полное размагничивание (Nd=1) гасит эффект проницаемости до 1
|
|
assert effective_permeability(mu_r=500.0, demagnetizing_factor=1.0) == pytest.approx(1.0)
|
|
|
|
|
|
@pytest.fixture
|
|
def model() -> CoilInductanceModel:
|
|
return CoilInductanceModel(
|
|
l_air_h=100e-6,
|
|
coil_length_m=0.05,
|
|
slug_length_m=0.03,
|
|
mu_eff=50.0,
|
|
smoothing_width_m=0.001,
|
|
)
|
|
|
|
|
|
def test_inductance_peaks_when_slug_centered(model):
|
|
l_center = model.l_of_x(np.array(0.0))
|
|
l_far = model.l_of_x(np.array(0.2))
|
|
assert l_center > l_far
|
|
assert l_far == pytest.approx(model.l_air_h, rel=1e-3)
|
|
|
|
|
|
def test_dl_dx_vanishes_at_symmetric_center(model):
|
|
assert model.dl_dx(np.array(0.0)) == pytest.approx(0.0, abs=1e-9)
|
|
|
|
|
|
def test_dl_dx_sign_flips_across_center(model):
|
|
# слева от центра индуктивность растёт (dL/dx > 0), справа убывает (< 0)
|
|
assert model.dl_dx(np.array(-0.01)) > 0
|
|
assert model.dl_dx(np.array(0.01)) < 0
|
|
|
|
|
|
def test_analytic_dl_dx_matches_finite_difference(model):
|
|
xs = np.linspace(-0.06, 0.06, 25)
|
|
h = 1e-7
|
|
analytic = np.array([model.dl_dx(x) for x in xs])
|
|
finite_diff = np.array(
|
|
[(model.l_of_x(x + h) - model.l_of_x(x - h)) / (2 * h) for x in xs]
|
|
)
|
|
assert np.allclose(analytic, finite_diff, rtol=1e-3, atol=1e-9)
|