From e6a0ab558113d7b8c35cc8f7ae3125248c3a8e49 Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Sat, 2 Nov 2024 12:52:20 -0300 Subject: [PATCH] Apply extra Ruff rules --- examples/1INFILTR/validation.py | 7 +- examples/HF135/validation.py | 5 +- fronts/D.py | 85 +++++++++++++-------- fronts/_boltzmann.py | 11 +-- fronts/_inverse.py | 13 ++-- fronts/_rootfinding.py | 29 ++++--- fronts/_semiinfinite.py | 81 +++++++++++--------- symbolic/generate.py | 11 +-- tests/test_examples.py | 9 +-- tests/test_rootfinding/__init__.py | 0 tests/test_rootfinding/test_bisect.py | 3 +- tests/test_rootfinding/test_bracket_root.py | 3 +- 12 files changed, 152 insertions(+), 105 deletions(-) create mode 100644 tests/test_rootfinding/__init__.py diff --git a/examples/1INFILTR/validation.py b/examples/1INFILTR/validation.py index 692b5ed..f6a92da 100755 --- a/examples/1INFILTR/validation.py +++ b/examples/1INFILTR/validation.py @@ -8,8 +8,7 @@ """ import itertools -import os -import sys +from pathlib import Path import matplotlib.pyplot as plt import numpy as np @@ -17,7 +16,7 @@ r_unit = "cm" t_unit = "h" -_filename = os.path.join(sys.path[0], "Nod_Inf.out") +_filename = Path(__file__).parent / "Nod_Inf.out" t = [] @@ -25,7 +24,7 @@ theta = [] velocity = [] -with open(_filename) as file: +with _filename.open() as file: for i, line in enumerate(file): if i <= 10: continue # Skip time 0 diff --git a/examples/HF135/validation.py b/examples/HF135/validation.py index 9beb3ff..a824358 100755 --- a/examples/HF135/validation.py +++ b/examples/HF135/validation.py @@ -9,13 +9,12 @@ """ -import os -import sys +from pathlib import Path import matplotlib.pyplot as plt import numpy as np -_filename = os.path.join(sys.path[0], "groundwaterFoam_results.csv") +_filename = Path(__file__).parent / "groundwaterFoam_results.csv" name = "porousMultiphaseFoam" diff --git a/fronts/D.py b/fronts/D.py index 5d4ce33..2f6bd28 100644 --- a/fronts/D.py +++ b/fronts/D.py @@ -41,7 +41,8 @@ def constant(D0): is the simplest supported function. """ if D0 <= 0: - raise ValueError("D0 must be positive") + msg = "D0 must be positive" + raise ValueError(msg) def D(_, derivatives=0): if derivatives == 0: @@ -108,10 +109,12 @@ def from_expr(expr, vectorized=True, max_derivatives=2): elif not free: return constant(float(expr)) else: - raise ValueError("expression cannot contain more than one variable") + msg = "expression cannot contain more than one variable" + raise ValueError(msg) if max_derivatives not in {0, 1, 2}: - raise ValueError("max_derivatives must be 0, 1 or 2") + msg = "max_derivatives must be 0, 1 or 2" + raise ValueError(msg) if max_derivatives == 0: func = sympy.lambdify(theta, expr, modules=np) @@ -152,9 +155,8 @@ def D(theta, derivatives=0): if derivatives == 2 and max_derivatives == 2: return funcs[0](theta), funcs[1](theta), funcs[2](theta) - raise ValueError( - f"derivatives must be one of {{{', '.join(str(n) for n in range(max_derivatives+1))}}}" - ) + msg = f"derivatives must be one of {{{', '.join(str(n) for n in range(max_derivatives+1))}}}" + raise ValueError(msg) else: f0v = sympy.lambdify(theta, exprs[0], modules=np) @@ -179,9 +181,8 @@ def D(theta, derivatives=0): if derivatives == 2 and max_derivatives == 2: return (*f01(theta), f2(theta)) - raise ValueError( - f"derivatives must be one of {{{', '.join(str(n) for n in range(max_derivatives+1))}}}" - ) + msg = f"derivatives must be one of {{{', '.join(str(n) for n in range(max_derivatives+1))}}}" + raise ValueError(msg) return D @@ -240,7 +241,8 @@ def D(theta, derivatives=0): if derivatives == 2: return D, dD_dtheta, d2D_dtheta2 - raise ValueError("derivatives must be 0, 1, or 2") + msg = "derivatives must be 0, 1, or 2" + raise ValueError(msg) return D @@ -278,18 +280,23 @@ def _as_Ks(Ks=None, k=None, nu=1e-6, g=9.81): """ if Ks is not None: if k is not None: - raise TypeError("cannot pass both Ks and k") + msg = "cannot pass both Ks and k" + raise TypeError(msg) if Ks <= 0: - raise ValueError("Ks must be positive") + msg = "Ks must be positive" + raise ValueError(msg) return Ks if k is not None: if k <= 0: - raise ValueError("k must be positive") + msg = "k must be positive" + raise ValueError(msg) if nu <= 0: - raise ValueError("nu must be positive") + msg = "nu must be positive" + raise ValueError(msg) if g <= 0: - raise ValueError("g must be positive") + msg = "g must be positive" + raise ValueError(msg) return g * k / nu return 1 @@ -373,12 +380,14 @@ def brooks_and_corey( Papers, Colorado State University, 1964, vol. 24, p. 37. """ if alpha <= 0: - raise ValueError("alpha must be positive") + msg = "alpha must be positive" + raise ValueError(msg) Ks = _as_Ks(Ks=Ks, k=k, nu=nu, g=g) if theta_range[1] <= theta_range[0]: - raise ValueError("theta_range[1] must be greater than theta_range[0]") + msg = "theta_range[1] must be greater than theta_range[0]" + raise ValueError(msg) # - Code generated with functionstr() from ../symbolic/generate.py - # # Source: ../symbolic/brooks_and_corey.py @@ -398,7 +407,8 @@ def D(theta, derivatives=0): d2D_dtheta2 = x4 * (-3 * x3 + 2 + x2**2 / n**2) / x0**3 if derivatives == 2: return D, dD_dtheta, d2D_dtheta2 - raise ValueError("derivatives must be 0, 1 or 2") + msg = "derivatives must be 0, 1 or 2" + raise ValueError(msg) # ----------------------- End generated code ----------------------- # @@ -498,24 +508,30 @@ def van_genuchten( """ if n is not None: if m is not None: - raise TypeError("cannot pass both n and m") + msg = "cannot pass both n and m" + raise TypeError(msg) if n <= 1: - raise ValueError("n must be greater than 1.0") + msg = "n must be greater than 1.0" + raise ValueError(msg) m = 1 - 1 / n elif m is None: - raise TypeError("either n or m must be given") + msg = "either n or m must be given" + raise TypeError(msg) if not (0 < m < 1): - raise ValueError("m must be strictly between 0.0 and 1.0") + msg = "m must be strictly between 0.0 and 1.0" + raise ValueError(msg) if alpha <= 0: - raise ValueError("alpha must be positive") + msg = "alpha must be positive" + raise ValueError(msg) Ks = _as_Ks(Ks=Ks, k=k, nu=nu, g=g) if theta_range[1] - theta_range[0] <= 0: - raise ValueError("theta_range[1] must be greater than theta_range[0]") + msg = "theta_range[1] must be greater than theta_range[0]" + raise ValueError(msg) # - Code generated with functionstr() from ../symbolic/generate.py - # # Source: ../symbolic/van_genuchten.py @@ -578,7 +594,8 @@ def D(theta, derivatives=0): ) if derivatives == 2: return D, dD_dtheta, d2D_dtheta2 - raise ValueError("derivatives must be 0, 1 or 2") + msg = "derivatives must be 0, 1 or 2" + raise ValueError(msg) # ----------------------- End generated code ----------------------- # @@ -825,7 +842,8 @@ def D(theta, derivatives=0): ) if derivatives == 2: return D, dD_dtheta, d2D_dtheta2 - raise ValueError("derivatives must be 0, 1 or 2") + msg = "derivatives must be 0, 1 or 2" + raise ValueError(msg) # ----------------------- End generated code ----------------------- # @@ -919,7 +937,8 @@ def D(theta, derivatives=0): ) if derivatives == 2: return D, dD_dtheta, d2D_dtheta2 - raise ValueError("derivatives must be 0, 1 or 2") + msg = "derivatives must be 0, 1 or 2" + raise ValueError(msg) # ----------------------- End generated code ----------------------- # @@ -1014,7 +1033,8 @@ def D(theta, derivatives=0): if derivatives == 2: return D, dD_dtheta, d2D_dtheta2 - raise ValueError("derivatives must be 0, 1, or 2") + msg = "derivatives must be 0, 1, or 2" + raise ValueError(msg) return D @@ -1046,15 +1066,18 @@ def _checked(D, theta=None): try: D_, dD_dtheta = D(theta, 1) except (ValueError, ArithmeticError) as e: - raise ValueError(f"D({theta}, 1) failed with {e.__class__.__name__}") from e + msg = f"D({theta}, 1) failed with {e.__class__.__name__}" + raise ValueError(msg) from e try: D_ = float(D_) dD_dtheta = float(dD_dtheta) except TypeError as e: - raise ValueError(f"D({theta}, 1) returned wrong type") from e + msg = f"D({theta}, 1) returned wrong type" + raise ValueError(msg) from e if not np.isfinite(D_) or D_ <= 0 or not np.isfinite(dD_dtheta): - raise ValueError(f"D({theta}, 1) returned invalid value") + msg = f"D({theta}, 1) returned invalid value" + raise ValueError(msg) return D_ diff --git a/fronts/_boltzmann.py b/fronts/_boltzmann.py index 3d5107b..a304feb 100644 --- a/fronts/_boltzmann.py +++ b/fronts/_boltzmann.py @@ -194,11 +194,13 @@ def as_o(r=None, t=None, o=None): """ if o is not None: if r is not None or t is not None: - raise TypeError("must pass either r and t, or just o") + msg = "must pass either r and t, or just o" + raise TypeError(msg) return o if r is None or t is None: - raise TypeError("must pass either r and t, or just o") + msg = "must pass either r and t, or just o" + raise TypeError(msg) return _o(r, t) @@ -312,9 +314,8 @@ def ode(D, radial=False, catch_errors=False): try: k = _k[radial] except KeyError: - raise ValueError( - f"radial must be one of {{{', '.join(repr(key) for key in _k)}}}" - ) from None + msg = f"radial must be one of {{{', '.join(repr(key) for key in _k)}}}" + raise ValueError(msg) from None def fun(o, y): theta, dtheta_do = y diff --git a/fronts/_inverse.py b/fronts/_inverse.py index 9d33cc3..f80ff07 100644 --- a/fronts/_inverse.py +++ b/fronts/_inverse.py @@ -43,7 +43,7 @@ def inverse(o, samples): first derivative at ``theta`` * ``D(theta, 2)`` returns the value of :math:`D`, its first derivative, and its second derivative at ``theta`` - + In all cases, the argument ``theta`` may be a single float or a NumPy array. @@ -66,20 +66,22 @@ def inverse(o, samples): [1] GERLERO, G. S.; BERLI, C. L. A.; KLER, P. A. Open-source high-performance software packages for direct and inverse solving of horizontal capillary flow. Capillarity, 2023, vol. 6, no. 2, pp. 31-40. - + [2] BRUCE, R. R.; KLUTE, A. The measurement of soil moisture diffusivity. Soil Science Society of America Journal, 1956, vol. 20, no. 4, pp. 458-462. """ o = np.asarray(o) if not np.all(np.diff(o) > 0): - raise ValueError("o must be strictly increasing") + msg = "o must be strictly increasing" + raise ValueError(msg) samples = np.asarray(samples) dsamples = np.diff(samples) if not (np.all(dsamples >= -1e-12) or np.all(dsamples <= 1e-12)): - raise ValueError("samples must be monotonic") + msg = "samples must be monotonic" + raise ValueError(msg) i = samples[-1] @@ -118,7 +120,8 @@ def D(theta, derivatives=0): if derivatives == 2: return D, dD_dtheta, d2D_dtheta2 - raise ValueError("derivatives must be 0, 1, or 2") + msg = "derivatives must be 0, 1, or 2" + raise ValueError(msg) return D diff --git a/fronts/_rootfinding.py b/fronts/_rootfinding.py index 6505057..5296238 100644 --- a/fronts/_rootfinding.py +++ b/fronts/_rootfinding.py @@ -118,18 +118,22 @@ def bracket_root( root. """ if growth_factor < 1: - raise ValueError("growth_factor cannot be less than 1") + msg = "growth_factor cannot be less than 1" + raise ValueError(msg) if ftol is not None and ftol < 0: - raise ValueError("ftol cannot be negative") + msg = "ftol cannot be negative" + raise ValueError(msg) if maxiter is not None and maxiter < 0: - raise ValueError("maxiter cannot be negative") + msg = "maxiter cannot be negative" + raise ValueError(msg) a, b = interval if a == b: - raise ValueError("interval must have different endpoints") + msg = "interval must have different endpoints" + raise ValueError(msg) f_a, f_b = f_interval @@ -185,8 +189,9 @@ def bracket_root( ) if maxiter is not None and iteration >= maxiter: + msg = f"failed to converge after {maxiter} iterations" raise IterationLimitReached( - f"failed to converge after {maxiter} iterations", + msg, interval=(a, b), f_interval=(f_a, f_b), function_calls=function_calls, @@ -195,6 +200,7 @@ def bracket_root( a, b = b, b + growth_factor * (b - a) f_a, f_b = f_b, f(b) function_calls += 1 + return None class NotABracketError(ValueError): @@ -269,10 +275,12 @@ def bisect(f, bracket, ftol=1e-12, maxiter=100, f_bracket=(None, None)): returned. """ if ftol < 0: - raise ValueError("ftol cannot be negative") + msg = "ftol cannot be negative" + raise ValueError(msg) if maxiter is not None and maxiter < 0: - raise ValueError("maxiter cannot be negative") + msg = "maxiter cannot be negative" + raise ValueError(msg) a, b = bracket f_a, f_b = f_bracket @@ -290,8 +298,9 @@ def bisect(f, bracket, ftol=1e-12, maxiter=100, f_bracket=(None, None)): # Check that the bracket is valid if f_a * f_b > 0: + msg = "f must have different signs at the bracket " "endpoints" raise NotABracketError( - "f must have different signs at the bracket " "endpoints", + msg, f_interval=(f_a, f_b), function_calls=function_calls, ) @@ -315,8 +324,9 @@ def bisect(f, bracket, ftol=1e-12, maxiter=100, f_bracket=(None, None)): # Perform the actual bisection for iteration in itertools.count(start=1): if maxiter is not None and iteration > maxiter: + msg = f"failed to converge after {maxiter} iterations" raise IterationLimitReached( - f"failed to converge after {maxiter} iterations", + msg, interval=(a, b), f_interval=(f_a, f_b), function_calls=function_calls, @@ -340,3 +350,4 @@ def bisect(f, bracket, ftol=1e-12, maxiter=100, f_bracket=(None, None)): iterations=iteration, function_calls=function_calls, ) + return None diff --git a/fronts/_semiinfinite.py b/fronts/_semiinfinite.py index 60683db..ba3796e 100644 --- a/fronts/_semiinfinite.py +++ b/fronts/_semiinfinite.py @@ -51,7 +51,8 @@ class Solution(BaseSolution): def __init__(self, sol, ob, oi, D): if ob > oi: - raise ValueError("ob cannot be greater than oi") + msg = "ob cannot be greater than oi" + raise ValueError(msg) def wrapped_sol(o): under = np.less(o, ob) @@ -491,7 +492,7 @@ def solve( first derivative at ``theta`` * ``D(theta, 2)`` returns the value of :math:`D`, its first derivative, and its second derivative at ``theta`` - + When called by this function, ``theta`` is always a single float. However, calls as ``D(theta)`` should also accept a NumPy array argument. @@ -611,10 +612,12 @@ def solve( start_time = process_time() if radial and ob <= 0: - raise ValueError("ob must be positive when using a radial coordinate") + msg = "ob must be positive when using a radial coordinate" + raise ValueError(msg) if maxiter < 0: - raise ValueError("maxiter must not be negative") + msg = "maxiter must not be negative" + raise ValueError(msg) if not callable(D): D = from_expr( @@ -623,7 +626,8 @@ def solve( if d_dob_bracket is not None: if d_dob_hint is not None: - raise TypeError("cannot pass both d_dob_hint and d_dob_bracket") + msg = "cannot pass both d_dob_hint and d_dob_bracket" + raise TypeError(msg) d_dob_bracket = tuple( x if np.sign(x) == np.sign(i - b) else 0 for x in d_dob_bracket @@ -633,9 +637,8 @@ def solve( d_dob_hint = (i - b) / (2 * _checked(D, b) ** 0.5) elif np.sign(d_dob_hint) != np.sign(i - b): - raise ValueError( - "sign of d_dob_hint does not match direction given " "by b and i" - ) + msg = "sign of d_dob_hint does not match direction given " "by b and i" + raise ValueError(msg) if verbose >= 2: print(f"{'Iteration':^15}{'Residual':^15}{'d/do|b':^15}{'Calls to D':^15}") @@ -710,17 +713,15 @@ def shot_callback(result): "d_dob_bracket does not contain target d/do|b. Try " "again with a correct interval." ) - raise ValueError( - "d_dob_bracket does not contain target d/do|b" - ) from None + msg = "d_dob_bracket does not contain target d/do|b" + raise ValueError(msg) from None except shooter.ShotLimitReached: if verbose: print(f"The solver did not converge after {maxiter} iterations.") print(f"Execution time: {process_time() - start_time:.3f} s") - raise RuntimeError( - f"The solver did not converge after {maxiter} iterations." - ) from None + msg = f"The solver did not converge after {maxiter} iterations." + raise RuntimeError(msg) from None if shooter.best_shot is not None and shooter.best_shot.d_dob == d_dob: result = shooter.best_shot @@ -858,7 +859,7 @@ def solve_flowrate( first derivative at ``theta`` * ``D(theta, 2)`` returns the value of :math:`D`, its first derivative, and its second derivative at ``theta`` - + where ``theta`` is always a float in the latter two cases, but it may be either a single float or a NumPy array when `D` is called as ``D(theta)``. @@ -982,30 +983,37 @@ def solve_flowrate( start_time = process_time() if ob <= 0: - raise ValueError("ob must be positive") + msg = "ob must be positive" + raise ValueError(msg) if not 0 < angle <= 2 * np.pi: - raise ValueError("angle must be positive and no greater than 2*pi") + msg = "angle must be positive and no greater than 2*pi" + raise ValueError(msg) if radial == "cylindrical": if height is None: - raise TypeError( + msg = ( "must pass a height if radial == 'cylindrical' " "(or use radial='polar')" ) + raise TypeError(msg) if height <= 0: - raise ValueError("height must be positive") + msg = "height must be positive" + raise ValueError(msg) elif radial == "polar": if height is not None: - raise TypeError( + msg = ( "height parameter not allowed if radial == " "'polar' (use radial='cylindrical' instead)" ) + raise TypeError(msg) else: - raise ValueError("radial must be one of {'cylindrical', 'polar'}") + msg = "radial must be one of {'cylindrical', 'polar'}" + raise ValueError(msg) if maxiter < 0: - raise ValueError("maxiter must not be negative") + msg = "maxiter must not be negative" + raise ValueError(msg) if not callable(D): D = from_expr( @@ -1014,7 +1022,8 @@ def solve_flowrate( if b_bracket is not None: if b_hint is not None: - raise TypeError("cannot pass both b_hint and b_bracket") + msg = "cannot pass both b_hint and b_bracket" + raise TypeError(msg) b_bracket = tuple(x if np.sign(i - x) == np.sign(-Qb) else i for x in b_bracket) @@ -1022,7 +1031,8 @@ def solve_flowrate( b_hint = i + np.sign(Qb) elif np.sign(i - b_hint) != np.sign(-Qb): - raise ValueError("value of b_hint disagrees with flowrate sign") + msg = "value of b_hint disagrees with flowrate sign" + raise ValueError(msg) if verbose >= 2: print( @@ -1103,17 +1113,15 @@ def shot_callback(result): "b_bracket does not contain target boundary value. " "Try again with a correct interval." ) - raise ValueError( - "b_bracket does not contain target bounday value" - ) from None + msg = "b_bracket does not contain target bounday value" + raise ValueError(msg) from None except shooter.ShotLimitReached: if verbose: print(f"The solver did not converge after {maxiter} iterations.") print(f"Execution time: {process_time() - start_time:.3f} s") - raise RuntimeError( - f"The solver did not converge after {maxiter} iterations." - ) from None + msg = f"The solver did not converge after {maxiter} iterations." + raise RuntimeError(msg) from None if shooter.best_shot is not None and shooter.best_shot.b == b: result = shooter.best_shot @@ -1184,7 +1192,7 @@ def solve_from_guess(D, i, b, o_guess, guess, radial=False, max_nodes=1000, verb first derivative at ``theta`` * ``D(theta, 2)`` returns the value of :math:`D`, its first derivative, and its second derivative at ``theta`` - + where ``theta`` may be a single float or a NumPy array. Alternatively, instead of a callable, the argument can be the @@ -1266,9 +1274,8 @@ def solve_from_guess(D, i, b, o_guess, guess, radial=False, max_nodes=1000, verb start_time = process_time() if radial and o_guess[0] <= 0: - raise ValueError( - "o_guess[0] must be positive when using a radial " "coordinate" - ) + msg = "o_guess[0] must be positive when using a radial " "coordinate" + raise ValueError(msg) if not callable(D): D = from_expr(D) @@ -1305,7 +1312,8 @@ def bc_jac(yb, yi): if not bvp_result.success: if verbose: print(f"Execution time: {process_time() - start_time:.3f} s") - raise RuntimeError(f"The solver did not converge: {bvp_result.message}") + msg = f"The solver did not converge: {bvp_result.message}" + raise RuntimeError(msg) if abs(bvp_result.y[1, -1]) > 1e-6: if verbose: @@ -1315,7 +1323,8 @@ def bc_jac(yb, yi): ) print(f"Execution time: {process_time() - start_time:.3f} s") - raise RuntimeError("o_guess cannot contain solution") + msg = "o_guess cannot contain solution" + raise RuntimeError(msg) solution = Solution( sol=bvp_result.sol, ob=bvp_result.x[0], oi=bvp_result.x[-1], D=D diff --git a/symbolic/generate.py b/symbolic/generate.py index 120003c..a967f6d 100644 --- a/symbolic/generate.py +++ b/symbolic/generate.py @@ -59,9 +59,7 @@ def functionstr(var, expr): lines = ["# - Code generated with functionstr() from ../symbolic/generate.py - #"] - for x in xs: - if x[0] not in variable: - lines.append("{} = {}".format(*x)) + lines.extend("{} = {}".format(*x) for x in xs if x[0] not in variable) lines.append(f"def D({var}, derivatives=0):") @@ -69,8 +67,11 @@ def functionstr(var, expr): for n, (name, expr) in enumerate(zip(deriv_names, exprs)): for x in xs: - if x[0] in variable and appearance[x] == n: - lines.append(" {} = {}".format(*x)) + lines.extend( + " {} = {}".format(*x) + for x in xs + if x[0] in variable and appearance[x] == n + ) lines.append(f" {name} = {expr}") lines.append( f" if derivatives == {n}: return {', '.join(deriv_names[:n+1])}" diff --git a/tests/test_examples.py b/tests/test_examples.py index 23c3ddb..de78075 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,14 +1,13 @@ import os +import subprocess +from pathlib import Path import pytest -examples = [] for root, _dirs, files in os.walk("examples"): - for file in files: - if file.endswith(".py"): - examples.append(os.path.join(root, file)) + examples = [Path(root, file) for file in files if file.endswith(".py")] @pytest.mark.parametrize("file", examples) def test_run_example(file): - assert os.system(file) == 0 + subprocess.run([file], check=True) diff --git a/tests/test_rootfinding/__init__.py b/tests/test_rootfinding/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_rootfinding/test_bisect.py b/tests/test_rootfinding/test_bisect.py index 328729b..748a0fb 100644 --- a/tests/test_rootfinding/test_bisect.py +++ b/tests/test_rootfinding/test_bisect.py @@ -2,7 +2,8 @@ import fronts._rootfinding as rootfinding import pytest -from checkobj import check_iterationlimitreached, check_notabracketerror, check_result + +from .checkobj import check_iterationlimitreached, check_notabracketerror, check_result def f(x): diff --git a/tests/test_rootfinding/test_bracket_root.py b/tests/test_rootfinding/test_bracket_root.py index 054a15b..4b89949 100644 --- a/tests/test_rootfinding/test_bracket_root.py +++ b/tests/test_rootfinding/test_bracket_root.py @@ -1,6 +1,7 @@ import fronts._rootfinding as rootfinding import pytest -from checkobj import check_iterationlimitreached, check_result + +from .checkobj import check_iterationlimitreached, check_result def f(x):