diff --git a/tests/test_generators.py b/tests/test_generators.py new file mode 100644 index 0000000..0d6f6c5 --- /dev/null +++ b/tests/test_generators.py @@ -0,0 +1,114 @@ +import numpy as np +import pytest + +from machine_design.designs.design import Design +from machine_design.optimization.generators import ( + HacklGenerator_3BrokenLines, + HacklGenerator_OneLambda, + HacklGenerator_SixLambdas, + check_barrier, + get_arc, + signed_distance, +) +from motors.synrm_3f_36s import Computation, Geometry + +r_stator_end = 0.7 +offset = 0.35 + +# feasible design +known_good_designs = { + "OneLambda": (HacklGenerator_OneLambda, np.array([7.1, 20.4, 33.7, 13.7, 27.1, 40.65, 0.35])), + "SixLambdas": ( + HacklGenerator_SixLambdas, + np.array([8.95052421, 23.01115583, 33.03271713, 14.34330278, 27.92171436, 37.3, 0.21260669, 0.31, 0.41, 0.3, 0.4, 0.46]), + ), + "3BrokenLines": ( + HacklGenerator_3BrokenLines, + np.array([6.0538801, 23.5, 35.74582285, 10.6, 28.5920024, 40.7191182, 0.36646249, 22.65142559, 30.7050295, 38.0, 9.49161503, 2.0, 1.5]), + ), +} + + +def test_get_arc_endpoints(): + arc = get_arc(R=2.0, start_deg=0, end_deg=90, n_points=3) + assert arc.shape == (3, 2) + assert arc[0] == pytest.approx([2.0, 0.0], abs=1e-10) + assert arc[-1] == pytest.approx([0.0, 2.0], abs=1e-10) + + +def test_signed_distance(): + P = np.array([[0.0, 0.0], [1.0, 1.0]]) + dist = signed_distance(P, a=1, b=1, c=-1) + assert dist == pytest.approx([-1.0, 1.0]) + + +def test_check_barrier_closed(): + # closed, should not raise + barrier = np.array([[0.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) + check_barrier(barrier) + + +def test_check_barrier_open(): + # open, should raise + barrier = np.array([[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]]) + with pytest.raises(ValueError): + check_barrier(barrier) + + +@pytest.fixture +def six_lambdas_generator(): + geometry = Geometry() + dummy_design = Design(m2d=None, geometry=geometry, computation=Computation(geometry)) + return HacklGenerator_SixLambdas(dummy_design, r_stator_end, offset=offset) + + +@pytest.fixture +def configured_generator(six_lambdas_generator): + x_best = known_good_designs["SixLambdas"][1] + params = six_lambdas_generator.X_to_params(x_best) + six_lambdas_generator.set_parameters(params) + return six_lambdas_generator + + +def test_x_to_params_splits_groups(six_lambdas_generator): + # 4 groups of 3 + x_best = known_good_designs["SixLambdas"][1] + phis_inner, phis_outer, lam_inner, lam_outer = six_lambdas_generator.X_to_params(x_best) + assert list(phis_inner) == pytest.approx([8.95052421, 23.01115583, 33.03271713]) + assert list(phis_outer) == pytest.approx([14.34330278, 27.92171436, 37.3]) + assert list(lam_inner) == pytest.approx([0.21260669, 0.31, 0.41]) + assert list(lam_outer) == pytest.approx([0.3, 0.4, 0.46]) + + +def test_set_parameters_stores_values(configured_generator): + assert configured_generator.phis_inner == pytest.approx([8.95052421, 23.01115583, 33.03271713]) + assert configured_generator.lam_outer == pytest.approx([0.3, 0.4, 0.46]) + + +@pytest.fixture(params=list(known_good_designs)) +def any_configured_generator(request): + generator_cls, x_best = known_good_designs[request.param] + geometry = Geometry() + generator = generator_cls(Design(m2d=None, geometry=geometry, computation=Computation(geometry)), r_stator_end, offset=offset) + generator.set_parameters(generator.X_to_params(x_best)) + return generator + + +def test_barrier_count(any_configured_generator): + # one barrier per pole pair + barriers = any_configured_generator.generate_barriers() + assert len(barriers) == 3 + + +def test_split_barriers_closed(any_configured_generator): + # each barrier should be closed + barriers = any_configured_generator.generate_barriers() + barriers = any_configured_generator.split_barriers(barriers) + for barrier in barriers: + assert barrier[0] == pytest.approx(barrier[-1]) + + +def test_known_good_design_is_feasible(any_configured_generator): + barriers = any_configured_generator.generate_barriers() + barriers = any_configured_generator.split_barriers(barriers) + assert any_configured_generator.feasible_barriers(barriers) is True diff --git a/tests/test_optimization.py b/tests/test_optimization.py new file mode 100644 index 0000000..33ed09b --- /dev/null +++ b/tests/test_optimization.py @@ -0,0 +1,48 @@ +import pickle + +import numpy as np +import pandas as pd +import pytest + +from machine_design.optimization.optimization import init_points, objective_transform + + +def test_objective_transform_normal_case(): + f1, f2 = objective_transform(TorAvg=4.5, TorRippleRms=8.0) + assert f1 == pytest.approx(4.5) + assert f2 == pytest.approx(-0.08) + + +def test_objective_transform_fallback(): + # torque null, uses fallback + f1, f2 = objective_transform(TorAvg=np.nan, TorRippleRms=np.nan, objective_fallback=(3.0, 5.0)) + assert f1 == pytest.approx(3.0) + assert f2 == pytest.approx(-0.05) + + +def test_objective_transform_no_fallback(): + # torque null, no fallback given, should raise + with pytest.raises(ValueError): + objective_transform(np.nan, np.nan) + + +def test_init_points_filters_and_flattens(tmp_path): + # filters by method and non-null T, flattens nested params into one X row + metadata = pd.DataFrame( + { + "method": ["A", "A", "B"], + "design": [0, 1, 0], + "T": [5.0, None, 6.0], + "ripple": [10.0, 20.0, 15.0], + } + ) + metadata.to_csv(tmp_path / "metadata.csv", index=False) + + with open(tmp_path / "design_A_0.pkl", "wb") as f: + pickle.dump((1.0, 2.0, [3.0, 4.0]), f) + + Xs, Ys = init_points(str(tmp_path), "A") + + assert Xs.shape == (1, 4) + assert Xs[0].tolist() == pytest.approx([1.0, 2.0, 3.0, 4.0]) + assert Ys[0].tolist() == pytest.approx([5.0, -0.1]) diff --git a/tests/test_optimization_geometry.py b/tests/test_optimization_geometry.py new file mode 100644 index 0000000..265b612 --- /dev/null +++ b/tests/test_optimization_geometry.py @@ -0,0 +1,34 @@ +import numpy as np +import pytest + +from machine_design.optimization.geometry import analyze_results, rotate, rotation_matrix + + +def test_rotation_matrix_zero_degrees(): + R = rotation_matrix(0) + assert R == pytest.approx(np.eye(2)) + + +def test_rotation_matrix_90_degrees(): + R = rotation_matrix(90) + assert R == pytest.approx(np.array([[0, -1], [1, 0]]), abs=1e-10) + + +def test_rotation_matrix_rad_matches_deg(): + R_deg = rotation_matrix(180) + R_rad = rotation_matrix(np.pi, rad=True) + assert R_deg == pytest.approx(R_rad) + + +def test_rotate_90_degrees(): + x, y = rotate(1, 0, 90) + assert x == pytest.approx(0, abs=1e-10) + assert y == pytest.approx(1) + + +def test_analyze_results_drops_last_point(): + Tor = np.array([9.0, 10.0, 11.0, 12.0, 999.0]) # last point excluded from the average + TorAvg, TorRmsAC, TorRippleRms = analyze_results(Tor) + assert TorAvg == pytest.approx(10.5) + assert TorRmsAC == pytest.approx(1.1180339887498949) + assert TorRippleRms == pytest.approx(10.647942749998999) diff --git a/tests/test_synrm_3f_36s.py b/tests/test_synrm_3f_36s.py index 9b2a205..490a020 100644 --- a/tests/test_synrm_3f_36s.py +++ b/tests/test_synrm_3f_36s.py @@ -1,4 +1,5 @@ import re +from types import SimpleNamespace import numpy as np import pytest @@ -149,3 +150,8 @@ def test_mm_to_str(geometry): def test_mm_to_str_raises_without_mm_suffix(geometry): with pytest.raises(Exception): geometry.mm_to_str("geom_params", "SlotNumber") + + +def test_extract_results_returns_data_magnitude(computation): + solutions = SimpleNamespace(data_magnitude=lambda: [1.0, 2.0, 3.0]) + assert computation.extract_results(solutions) == [1.0, 2.0, 3.0] diff --git a/tests/test_synrm_5f_40s.py b/tests/test_synrm_5f_40s.py index e0d82ba..9d077b1 100644 --- a/tests/test_synrm_5f_40s.py +++ b/tests/test_synrm_5f_40s.py @@ -1,3 +1,5 @@ +from types import SimpleNamespace + import numpy as np import pytest @@ -77,3 +79,11 @@ def test_post_params_plot_names_unique(computation2): def test_output_vars_known_values(computation2): assert computation2.output_vars["Rstat"] == "19" assert computation2.output_vars["Lew"] == "0" + + +def test_extract_results_converts_units_by_prefix(computation2): + solutions = SimpleNamespace(data_real=lambda expr: 100.0) + out = computation2.extract_results(solutions) + assert out["Ld1"] == pytest.approx(100.0 / 1e9) # Ld*/Lq* , nH + assert out["I_d1"] == pytest.approx(100.0 / 1e3) # I_* , A + assert out["V_d1"] == pytest.approx(100.0)