From e57978d4ae6c53ef834ba19aaa07ddb92a649129 Mon Sep 17 00:00:00 2001 From: knQzx <75641500+knQzx@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:02:11 +0100 Subject: [PATCH 1/2] fix genetic algorithm crash when no feasible solution exists pymoo returns None for res.F/res.X when all candidates violate constraints, which crashes terminate() with AttributeError. Now catches that and raises a proper error message. Fixes #164 --- WeatherRoutingTool/algorithms/genetic/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/WeatherRoutingTool/algorithms/genetic/__init__.py b/WeatherRoutingTool/algorithms/genetic/__init__.py index 6887d40..a53a355 100644 --- a/WeatherRoutingTool/algorithms/genetic/__init__.py +++ b/WeatherRoutingTool/algorithms/genetic/__init__.py @@ -180,6 +180,17 @@ def terminate(self, res: Result, problem: RoutingProblem): """Genetic Algorithm termination procedures""" super().terminate() + + if res.F is None or res.X is None: + logger.error( + "No feasible solution found. All candidates violated constraints. " + "Try relaxing constraints or adjusting the start/end points." + ) + raise RuntimeError( + "Optimization failed: no feasible solution found. " + "All candidates violated the given constraints." + ) + self.consistency_check(res, problem) mcdm = MCDM.RMethod(self.objectives) From 1ec1b412e0f955398e93355b3502d132c7a50254 Mon Sep 17 00:00:00 2001 From: knQzx <75641500+knQzx@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:02:18 +0100 Subject: [PATCH 2/2] fix get_u/get_v tests that were never running MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit these had like 5 things wrong at once — no test_ prefix, wrong number of params, assertions comparing a variable to itself, wrong expected values, copy-pasted param names. rewrote them properly with correct values and added edge cases + a roundtrip test. Fixes #161 --- tests/test_weather.py | 61 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/tests/test_weather.py b/tests/test_weather.py index ed3dc34..2f9237b 100644 --- a/tests/test_weather.py +++ b/tests/test_weather.py @@ -10,15 +10,64 @@ def test_theta_from_uv(u, v, theta_res): assert theta_test == pytest.approx(theta_res) -@pytest.mark.parametrize("theta,windspeed,u_res", [(45, 1, -1), (315, 1, 1), (225, 1, 1), (135, 1, -1)]) -def get_u(theta, windspeed): +@pytest.mark.parametrize("theta,windspeed,u_res", [ + (45, 1, -0.7071067811865476), + (315, 1, 0.7071067811865476), + (225, 1, 0.7071067811865476), + (135, 1, -0.7071067811865476), + # cardinal directions + (0, 1, 0.0), + (90, 1, -1.0), + (180, 1, 0.0), + (270, 1, 1.0), + # windspeed scaling + (45, 5, -3.5355339059327378), + # zero wind + (45, 0, 0.0), + # full circle should equal 0 degrees + (360, 1, 0.0), +]) +def test_get_u(theta, windspeed, u_res): u_test = WeatherCond.get_u(theta, windspeed) - assert u_test == pytest.approx(u_test) + assert u_test == pytest.approx(u_res, abs=1e-10) -@pytest.mark.parametrize("theta,windspeed,u_res", [(45, 1, -1), (315, 1, -1), (225, 1, 1), (135, 1, 1)]) -def get_v(theta, windspeed): +@pytest.mark.parametrize("theta,windspeed,v_res", [ + (45, 1, -0.7071067811865476), + (315, 1, -0.7071067811865476), + (225, 1, 0.7071067811865476), + (135, 1, 0.7071067811865476), + # cardinal directions + (0, 1, -1.0), + (90, 1, 0.0), + (180, 1, 1.0), + (270, 1, 0.0), + # windspeed scaling + (45, 5, -3.5355339059327378), + # zero wind + (45, 0, 0.0), + # full circle + (360, 1, -1.0), +]) +def test_get_v(theta, windspeed, v_res): v_test = WeatherCond.get_v(theta, windspeed) - assert v_test == pytest.approx(v_test) + assert v_test == pytest.approx(v_res, abs=1e-10) + + +@pytest.mark.parametrize("theta,windspeed", [ + (45, 1), + (135, 1), + (225, 1), + (315, 1), + (45, 10), + (200, 3.5), +]) +def test_uv_roundtrip(theta, windspeed): + """get_u/get_v -> get_theta_from_uv should return the original angle""" + u = WeatherCond.get_u(theta, windspeed) + v = WeatherCond.get_v(theta, windspeed) + theta_back = WeatherCond.get_theta_from_uv(u, v) + + assert theta_back == pytest.approx(theta, abs=1e-6)