From 682e552dd46326b7db81388a1de674a1ea046674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amund=20Faller=20R=C3=A5heim?= Date: Tue, 2 Apr 2024 12:36:09 +0200 Subject: [PATCH 1/7] feat: remove invalid_value functionality in bisect BREAKING CHANGE: remove invalid_value parameter and functionality in bisect method, instead raise ValueError. By extension, changes behaviour of compute_conductor_ampacity which had invaluid_value set to 0 when bisect failed. --- linerate/solver.py | 16 +++++----------- tests/test_solver.py | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/linerate/solver.py b/linerate/solver.py index aa0561f..d2914a6 100644 --- a/linerate/solver.py +++ b/linerate/solver.py @@ -1,5 +1,5 @@ from functools import partial -from typing import Callable, Optional +from typing import Callable import numpy as np @@ -13,7 +13,6 @@ def bisect( xmin: FloatOrFloatArray, xmax: FloatOrFloatArray, tolerance: float, - invalid_value: Optional[float] = None, ) -> FloatOrFloatArray: r"""Compute the roots of a function using a vectorized bisection method. @@ -32,9 +31,6 @@ def bisect( bounded within an interval of size :math:`\Delta x` or less. The bisection method will run for :math:`\left\lceil\frac{x_\max - x_\min}{\Delta x}\right\rceil` iterations. - invalid_value: - If provided, then the this value is used whenever - :math:`\text{sign}(f(\mathbf{x}_\min)) = \text{sign}(f(\mathbf{x}_\max))`. Returns ------- @@ -51,12 +47,10 @@ def bisect( f_right = f(xmax) invalid_mask = np.sign(f_left) == np.sign(f_right) - if np.any(invalid_mask) and invalid_value is None: + if np.any(invalid_mask): raise ValueError( "f(xmin) and f(xmax) have the same sign. Consider increasing the search interval." ) - elif isinstance(invalid_mask, bool) and invalid_mask: - return invalid_value # type: ignore while interval > tolerance: xmid = 0.5 * (xmax + xmin) @@ -69,7 +63,7 @@ def bisect( f_left = np.where(mask, f_mid, f_left) f_right = np.where(mask, f_right, f_mid) - out = np.where(invalid_mask, invalid_value, 0.5 * (xmax + xmin)) # type: ignore + out = 0.5 * (xmax + xmin) return out @@ -139,7 +133,7 @@ def compute_conductor_ampacity( :math:`\Delta I~\left[\text{A}\right]`. The numerical accuracy of the ampacity. The bisection iterations will stop once the numerical ampacity uncertainty is below :math:`\Delta I`. The bisection method will run for - :math:`\left\lceil\frac{I_\text{min} - I_\text{min}}{\Delta I}\right\rceil` iterations. + :math:`\left\lceil\frac{I_\text{max} - I_\text{min}}{\Delta I}\right\rceil` iterations. Returns ------- @@ -148,4 +142,4 @@ def compute_conductor_ampacity( """ f = partial(heat_balance, max_conductor_temperature) - return bisect(f, min_ampacity, max_ampacity, tolerance, invalid_value=0) + return bisect(f, min_ampacity, max_ampacity, tolerance) diff --git a/tests/test_solver.py b/tests/test_solver.py index 37e20fd..f0c97e9 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -1,5 +1,4 @@ import pytest - import linerate.solver as solver @@ -31,17 +30,19 @@ def heat_balance(conductor_temperature, current): assert conductor_temperature == pytest.approx(9000, rel=1e-7) -def test_compute_conductor_temperature_caps_ampacity_at_zero(): +def test_compute_conductor_temperature_raises_value_error(): def heat_balance(conductor_temperature, current): A = current T = conductor_temperature return (A + 100 * T) * (current + 200 * T) - conductor_temperature = solver.compute_conductor_ampacity( - heat_balance, - max_conductor_temperature=90, - min_ampacity=0, - max_ampacity=10_000, - tolerance=1e-8, - ) - assert conductor_temperature == pytest.approx(0, rel=1e-7) + try: + solver.compute_conductor_ampacity( + heat_balance, + max_conductor_temperature=90, + min_ampacity=0, + max_ampacity=10_000, + tolerance=1e-8, + ) + except Exception as e: + assert isinstance(e, ValueError) From c20e9987dac7bce3a6b5ac71fabc9b6ffec14a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amund=20Faller=20R=C3=A5heim?= Date: Tue, 2 Apr 2024 14:57:26 +0200 Subject: [PATCH 2/7] isort --- tests/test_solver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_solver.py b/tests/test_solver.py index f0c97e9..a94d595 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -1,4 +1,5 @@ import pytest + import linerate.solver as solver From 1f6fbe071fe8726d670e13a82c17b38cded7018b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amund=20Faller=20R=C3=A5heim?= Date: Wed, 3 Apr 2024 15:18:03 +0200 Subject: [PATCH 3/7] test that bisect raises ValueError --- tests/test_solver.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/test_solver.py b/tests/test_solver.py index a94d595..7e4ecdb 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -31,19 +31,16 @@ def heat_balance(conductor_temperature, current): assert conductor_temperature == pytest.approx(9000, rel=1e-7) -def test_compute_conductor_temperature_raises_value_error(): - def heat_balance(conductor_temperature, current): +def test_bisect_raises_value_error(): + def heat_balance(current): A = current - T = conductor_temperature - return (A + 100 * T) * (current + 200 * T) + T = 90 + return (A + 100 * T) * (A + 100 * T) - try: - solver.compute_conductor_ampacity( + with pytest.raises(ValueError): + solver.bisect( heat_balance, - max_conductor_temperature=90, - min_ampacity=0, - max_ampacity=10_000, + xmin=0, + xmax=10_000, tolerance=1e-8, ) - except Exception as e: - assert isinstance(e, ValueError) From d1a7212854b5cf5cae723d4fdeb882b1cb18df50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amund=20Faller=20R=C3=A5heim?= Date: Thu, 4 Apr 2024 14:29:42 +0200 Subject: [PATCH 4/7] fix bisect function for array inputs, more tests --- linerate/solver.py | 2 +- tests/test_solver.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/linerate/solver.py b/linerate/solver.py index d2914a6..1581c35 100644 --- a/linerate/solver.py +++ b/linerate/solver.py @@ -39,7 +39,7 @@ def bisect( there is a root :math:`x_i \in [\tilde{x}_i - 0.5 \Delta x, \tilde{x}_i + 0.5 \Delta x]` so :math:`f_i(x_i) = 0`. """ - if not np.isfinite(xmin) or not np.isfinite(xmax): + if not np.all(np.isfinite(xmin)) or not np.all(np.isfinite(xmax)): raise ValueError("xmin and xmax must be finite.") interval = np.max(np.abs(xmax - xmin)) diff --git a/tests/test_solver.py b/tests/test_solver.py index 7e4ecdb..50dba00 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -1,3 +1,4 @@ +import numpy as np import pytest import linerate.solver as solver @@ -44,3 +45,45 @@ def heat_balance(current): xmax=10_000, tolerance=1e-8, ) + + +def test_bisect_handles_function_returning_array_happy_path(): + def heat_balance(currents: np.array): + A = currents + T = 90 + res = (A - 100 * T) * (currents + 100 * T) + return res + + solution = solver.bisect( + heat_balance, + xmin=np.array([0, 0]), + xmax=np.array([10_000, 10_000]), + tolerance=1e-8, + ) + np.testing.assert_array_almost_equal(solution, [9000, 9000], decimal=8) + + +def test_bisect_raises_valueerror_when_same_sign_for_array_input(): + def heat_balance(currents: np.array): + A = currents + T = 90 + res = (A - 100 * T) * (currents + 100 * T) + return res + + with pytest.raises(ValueError): + solver.bisect( + heat_balance, + xmin=np.array([0, 0]), + xmax=np.array([10_000, 8000]), + tolerance=1e-8, + ) + + +def test_bisect_raises_valueerror_when_infinite_in_array_input(): + with pytest.raises(ValueError): + solver.bisect( + lambda x: x, + xmin=np.array([-np.inf, 0]), + xmax=np.array([10_000, 10_000]), + tolerance=1e-8, + ) From 3d7c02404f9047a6849c58668360da88ddacc4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amund=20Faller=20R=C3=A5heim?= Date: Thu, 4 Apr 2024 14:36:12 +0200 Subject: [PATCH 5/7] updated version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ef877ad..54e4e73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "linerate" -version = "0.0.7-post.3+62231f4" +version = "1.0.0" description = "Library for computing line ampacity ratings for overhead lines" authors = ["Statnett Datascience ", "Yngve Mardal Moe "] repository = "https://github.com/statnett/linerate.git" From 35f8d02f749de936a03e35327d36b4124d55c118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amund=20Faller=20R=C3=A5heim?= Date: Fri, 5 Apr 2024 09:49:34 +0200 Subject: [PATCH 6/7] Update tests/test_solver.py Co-authored-by: Gunnhild Svandal Presthus <36099159+gunnhildsp@users.noreply.github.com> --- tests/test_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_solver.py b/tests/test_solver.py index 50dba00..2211b50 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -74,7 +74,7 @@ def heat_balance(currents: np.array): solver.bisect( heat_balance, xmin=np.array([0, 0]), - xmax=np.array([10_000, 8000]), + xmax=np.array([10_000, 8_000]), tolerance=1e-8, ) From 8c185df41a8a2fbafc4001f6d9c76a7b10f258eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amund=20Faller=20R=C3=A5heim?= Date: Fri, 5 Apr 2024 09:49:44 +0200 Subject: [PATCH 7/7] Update tests/test_solver.py Co-authored-by: Gunnhild Svandal Presthus <36099159+gunnhildsp@users.noreply.github.com> --- tests/test_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_solver.py b/tests/test_solver.py index 2211b50..1720ef3 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -60,7 +60,7 @@ def heat_balance(currents: np.array): xmax=np.array([10_000, 10_000]), tolerance=1e-8, ) - np.testing.assert_array_almost_equal(solution, [9000, 9000], decimal=8) + np.testing.assert_array_almost_equal(solution, [9_000, 9_000], decimal=8) def test_bisect_raises_valueerror_when_same_sign_for_array_input():