from gausse.components.database import ComponentDatabase from gausse.report.animate import animate_run from gausse.report.bom import build_bom, render_bom_text, total_cost_rub from gausse.report.plots import ( plot_stage_currents, plot_stage_fields, plot_velocity_vs_position, save_all, ) from gausse.report.summary import build_summary, render_summary_json, render_summary_text from gausse.sim.coilgun import CoilgunConfig, run_coilgun from gausse.sim.stage import ProjectileConfig, StageConfig DB = ComponentDatabase.load() def _feasible_config() -> CoilgunConfig: wire = next(w for w in DB.wires if w.part_id == "cu-petv2-1.0mm") capacitor = next(c for c in DB.capacitors if c.part_number == "cap-470uf-400v") # мощный ключ (180А, surge ~720А) — конфигурация с реальным пиковым током switch = next(s for s in DB.switches if s.part_number == "IRFP4468PBF") sensor = next(s for s in DB.sensors if s.part_number == "TCST2103") steel = next(m for m in DB.projectile_materials if m.name == "Ст3 (конструкционная сталь)") projectile = ProjectileConfig(material=steel, diameter_m=0.008, length_m=0.02) stage = StageConfig( wire=wire, capacitor=capacitor, switch=switch, sensor=sensor, tube_od_m=0.01, turns_per_layer=20, layers=4, sensor_to_coil_distance_m=0.02, charge_voltage_v=200.0, ) return CoilgunConfig( stages=[stage, stage], inter_stage_gaps_m=[0.05], projectile=projectile, initial_x_m=-0.05, initial_v_mps=5.0, ) def test_feasible_fixture_is_actually_feasible(): result = run_coilgun(_feasible_config()) assert result.feasible, result.reason def test_plots_render_without_error(): config = _feasible_config() result = run_coilgun(config) assert result.feasible fig1 = plot_stage_currents(result) fig2 = plot_stage_fields(result) fig3 = plot_velocity_vs_position(result) for fig in (fig1, fig2, fig3): assert fig is not None def test_save_all_writes_nonempty_png_files(tmp_path): config = _feasible_config() result = run_coilgun(config) paths = save_all(result, tmp_path) assert len(paths) == 3 for p in paths: from pathlib import Path assert Path(p).stat().st_size > 0 def test_bom_totals_are_positive_and_match_sum(): config = _feasible_config() lines = build_bom(config, DB) assert len(lines) > 0 total = total_cost_rub(lines) assert total == sum(line.total_price_rub for line in lines) assert total > 0 text = render_bom_text(lines) assert "ИТОГО" in text def test_summary_reports_feasible_run_honestly(): config = _feasible_config() result = run_coilgun(config) summary = build_summary(result, config, DB) assert summary["feasible"] is True assert summary["efficiency"] == result.efficiency assert "model_limitations" in summary assert len(summary["model_limitations"]) > 0 text = render_summary_text(summary) assert "РЕАЛИЗУЕМО" in text json_text = render_summary_json(summary) assert "efficiency" in json_text def test_summary_reports_infeasible_run_honestly(): import dataclasses config = _feasible_config() # индукционный датчик при скорости заведомо ниже порога -> провал ступени 0 weak_sensor = next(s for s in DB.sensors if s.kind == "inductive") broken_stage = dataclasses.replace(config.stages[0], sensor=weak_sensor) config = dataclasses.replace( config, stages=[broken_stage, config.stages[1]], initial_v_mps=2.0 ) result = run_coilgun(config) summary = build_summary(result, config, DB) if not result.feasible: text = render_summary_text(summary) assert "НЕ РЕАЛИЗУЕМО" in text assert summary["reason"] is not None def test_animate_run_produces_nonempty_gif(tmp_path): config = _feasible_config() result = run_coilgun(config) assert result.feasible out_path = tmp_path / "run.gif" animate_run(result, config, str(out_path), fps=10) assert out_path.stat().st_size > 0