From b56fd455cc105039d48a22115da4f6a7b2358d2b Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 7 Sep 2026 14:43:11 +0200 Subject: [PATCH 1/7] Add unit tests for objective_transform --- tests/test_optimization.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/test_optimization.py diff --git a/tests/test_optimization.py b/tests/test_optimization.py new file mode 100644 index 0000000..d5e0841 --- /dev/null +++ b/tests/test_optimization.py @@ -0,0 +1,21 @@ +import numpy as np +import pytest + +from machine_design.optimization.optimization import 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_uses_fallback_when_torque_is_null(): + 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_raises_without_fallback(): + with pytest.raises(ValueError): + objective_transform(np.nan, np.nan) From b39242fe4ab425dd52800ccd65545955692e82b3 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 7 Sep 2026 15:04:10 +0200 Subject: [PATCH 2/7] Add unit tests for get_arc, signed_distance, and check_barrier --- tests/test_generators.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_generators.py diff --git a/tests/test_generators.py b/tests/test_generators.py new file mode 100644 index 0000000..dda4aa0 --- /dev/null +++ b/tests/test_generators.py @@ -0,0 +1,28 @@ +import numpy as np +import pytest + +from machine_design.optimization.generators import check_barrier, get_arc, signed_distance + + +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_does_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_raises(): + barrier = np.array([[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]]) + with pytest.raises(ValueError): + check_barrier(barrier) From a9749b6f542f6e65c555d5c824b917e908c9394b Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 7 Sep 2026 15:14:07 +0200 Subject: [PATCH 3/7] Add unit tests for rotation_matrix, rotate, and analyze_results --- tests/test_optimization_geometry.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/test_optimization_geometry.py diff --git a/tests/test_optimization_geometry.py b/tests/test_optimization_geometry.py new file mode 100644 index 0000000..b0468f6 --- /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_flag_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) From 1ceff9923c7e2114cb04779cacf7928769cf18a9 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 7 Sep 2026 15:46:14 +0200 Subject: [PATCH 4/7] Add unit tests for barrier generation --- tests/test_generators.py | 57 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/test_generators.py b/tests/test_generators.py index dda4aa0..352861f 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -1,7 +1,15 @@ import numpy as np import pytest -from machine_design.optimization.generators import check_barrier, get_arc, signed_distance +from machine_design.designs.design import Design +from machine_design.optimization.generators import HacklGenerator_SixLambdas, check_barrier, get_arc, signed_distance +from motors.synrm_3f_36s import Computation, Geometry + +r_stator_end = 0.7 +offset = 0.35 + +# design at Pareto front +x_best_six_lambdas = 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]) def test_get_arc_endpoints(): @@ -26,3 +34,50 @@ def test_check_barrier_open_raises(): 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): + params = six_lambdas_generator.X_to_params(x_best_six_lambdas) + six_lambdas_generator.set_parameters(params) + return six_lambdas_generator + + +@pytest.fixture +def split_barriers(configured_generator): + barriers = configured_generator.generate_barriers() + return configured_generator.split_barriers(barriers) + + +def test_X_to_params_splits_into_four_groups_of_three(six_lambdas_generator): + phis_inner, phis_outer, lam_inner, lam_outer = six_lambdas_generator.X_to_params(x_best_six_lambdas) + 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]) + + +def test_generate_barriers_returns_one_barrier_per_pole(configured_generator): + barriers = configured_generator.generate_barriers() + assert len(barriers) == 3 + + +def test_split_barriers_closes_each_barrier(split_barriers): + for barrier in split_barriers: + assert barrier[0] == pytest.approx(barrier[-1]) + + +def test_feasible_barriers_true_for_known_good_design(configured_generator, split_barriers): + assert configured_generator.feasible_barriers(split_barriers) is True \ No newline at end of file From b13483b0b6f64f5c6c53e3b5ea8a49f0c45ad409 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 7 Sep 2026 16:11:04 +0200 Subject: [PATCH 5/7] Add unit tests for extract_results --- tests/test_synrm_3f_36s.py | 6 ++++++ tests/test_synrm_5f_40s.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/test_synrm_3f_36s.py b/tests/test_synrm_3f_36s.py index 9b2a205..17bce52 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] \ No newline at end of file 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) From fa99ca96fb15ee6c5db331d2870cd9b84a1c14d9 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 9 Sep 2026 09:24:00 +0200 Subject: [PATCH 6/7] Add coverage for other generators and init_points; simplify test names --- tests/test_generators.py | 71 +++++++++++++++++++++-------- tests/test_optimization.py | 33 ++++++++++++-- tests/test_optimization_geometry.py | 2 +- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/tests/test_generators.py b/tests/test_generators.py index 352861f..0d6f6c5 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -2,14 +2,31 @@ import pytest from machine_design.designs.design import Design -from machine_design.optimization.generators import HacklGenerator_SixLambdas, check_barrier, get_arc, signed_distance +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 -# design at Pareto front -x_best_six_lambdas = 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]) +# 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(): @@ -25,12 +42,14 @@ def test_signed_distance(): assert dist == pytest.approx([-1.0, 1.0]) -def test_check_barrier_closed_does_not_raise(): +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_raises(): +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) @@ -45,19 +64,16 @@ def six_lambdas_generator(): @pytest.fixture def configured_generator(six_lambdas_generator): - params = six_lambdas_generator.X_to_params(x_best_six_lambdas) + 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 -@pytest.fixture -def split_barriers(configured_generator): - barriers = configured_generator.generate_barriers() - return configured_generator.split_barriers(barriers) - - -def test_X_to_params_splits_into_four_groups_of_three(six_lambdas_generator): - phis_inner, phis_outer, lam_inner, lam_outer = six_lambdas_generator.X_to_params(x_best_six_lambdas) +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]) @@ -69,15 +85,30 @@ def test_set_parameters_stores_values(configured_generator): assert configured_generator.lam_outer == pytest.approx([0.3, 0.4, 0.46]) -def test_generate_barriers_returns_one_barrier_per_pole(configured_generator): - barriers = configured_generator.generate_barriers() +@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_closes_each_barrier(split_barriers): - for barrier in split_barriers: +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_feasible_barriers_true_for_known_good_design(configured_generator, split_barriers): - assert configured_generator.feasible_barriers(split_barriers) is True \ No newline at end of file +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 index d5e0841..33ed09b 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -1,7 +1,10 @@ +import pickle + import numpy as np +import pandas as pd import pytest -from machine_design.optimization.optimization import objective_transform +from machine_design.optimization.optimization import init_points, objective_transform def test_objective_transform_normal_case(): @@ -10,12 +13,36 @@ def test_objective_transform_normal_case(): assert f2 == pytest.approx(-0.08) -def test_objective_transform_uses_fallback_when_torque_is_null(): +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_raises_without_fallback(): +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 index b0468f6..265b612 100644 --- a/tests/test_optimization_geometry.py +++ b/tests/test_optimization_geometry.py @@ -14,7 +14,7 @@ def test_rotation_matrix_90_degrees(): assert R == pytest.approx(np.array([[0, -1], [1, 0]]), abs=1e-10) -def test_rotation_matrix_rad_flag_matches_deg(): +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) From c5ec8cbbecdba081f835cf25177a33817757b552 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 9 Sep 2026 09:32:04 +0200 Subject: [PATCH 7/7] Fix missing trailing newline --- tests/test_synrm_3f_36s.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_synrm_3f_36s.py b/tests/test_synrm_3f_36s.py index 17bce52..490a020 100644 --- a/tests/test_synrm_3f_36s.py +++ b/tests/test_synrm_3f_36s.py @@ -154,4 +154,4 @@ def test_mm_to_str_raises_without_mm_suffix(geometry): 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] \ No newline at end of file + assert computation.extract_results(solutions) == [1.0, 2.0, 3.0]