Skip to content

Commit

Permalink
Moving scipy imports to happen at runtime.
Browse files Browse the repository at this point in the history
Follows along approach from previous commit and earlier work
in #194, #195. This supercedes #196.

This also fixes a "SympPy" -> "SymPy" typo from the previous commit.
  • Loading branch information
dhermes committed Jan 28, 2020
1 parent 9975c0f commit 38602d8
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 50 deletions.
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ def lint(session):
DEPS["matplotlib"],
DEPS["Pygments"],
DEPS["pylint"],
DEPS["scipy"],
DEPS["seaborn"],
DEPS["sympy"],
)
Expand Down
20 changes: 9 additions & 11 deletions src/python/bezier/_algebraic_intersection.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@
import numpy as np
from numpy.polynomial import polynomial

try:
import scipy.linalg.lapack as _scipy_lapack
except ImportError: # pragma: NO COVER
_scipy_lapack = None

from bezier import _curve_helpers
from bezier import _geometric_intersection
from bezier import _helpers
Expand Down Expand Up @@ -1043,16 +1038,19 @@ def _reciprocal_condition_number(lu_mat, one_norm):
float: The reciprocal condition number of :math:`A`.
Raises:
OSError: If SciPy is not installed.
RuntimeError: If the reciprocal 1-norm condition number could not
be computed.
"""
if _scipy_lapack is None:
raise OSError("This function requires SciPy for calling into LAPACK.")
# NOTE: We import SciPy at runtime to avoid the import-time cost for users
# that don't need algebraic intersection helpers (e.g. if only using
# the geometric intersection strategy). The ``scipy`` import is a
# tad expensive.
# pylint: disable=import-outside-toplevel,no-name-in-module
import scipy.linalg.lapack

# pylint: enable=import-outside-toplevel,no-name-in-module

# pylint: disable=no-member
rcond, info = _scipy_lapack.dgecon(lu_mat, one_norm)
# pylint: enable=no-member
rcond, info = scipy.linalg.lapack.dgecon(lu_mat, one_norm)
if info != 0:
raise RuntimeError(
"The reciprocal 1-norm condition number could not be computed."
Expand Down
14 changes: 5 additions & 9 deletions src/python/bezier/_py_curve_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@

import numpy as np

try:
import scipy.integrate as _scipy_int
except ImportError: # pragma: NO COVER
_scipy_int = None

from bezier import _py_helpers


Expand Down Expand Up @@ -316,7 +311,6 @@ def compute_length(nodes):
Raises:
ValueError: If ``nodes`` has zero columns.
OSError: If SciPy is not installed.
"""
_, num_nodes = np.shape(nodes)
# NOTE: We somewhat replicate code in ``evaluate_hodograph()``
Expand All @@ -333,11 +327,13 @@ def compute_length(nodes):
# NOTE: We convert to 1D to make sure NumPy uses vector norm.
return np.linalg.norm(first_deriv[:, 0], ord=2)

if _scipy_int is None:
raise OSError("This function requires SciPy for quadrature.")
# NOTE: We import SciPy at runtime to avoid the import-time cost for users
# that don't pure Python curve helpers (e.g. if the ``_speedup``
# module is available). The ``scipy`` import is a tad expensive.
import scipy.integrate # pylint: disable=import-outside-toplevel

size_func = functools.partial(vec_size, first_deriv)
length, _ = _scipy_int.quad(size_func, 0.0, 1.0)
length, _ = scipy.integrate.quad(size_func, 0.0, 1.0)
return length


Expand Down
14 changes: 7 additions & 7 deletions src/python/bezier/_symbolic.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def to_symbolic(nodes):
Raises:
ValueError: If ``nodes`` is not 2D.
"""
# NOTE: We import SympPy at runtime to avoid the import-time cost for users
# NOTE: We import SymPy at runtime to avoid the import-time cost for users
# that don't want to do symbolic computation. The ``sympy`` import is
# a tad expensive.
import sympy # pylint: disable=import-outside-toplevel
Expand Down Expand Up @@ -64,7 +64,7 @@ def curve_weights(degree, s):
sympy.Matrix: The de Casteljau weights for the curve as a
``(degree + 1) x 1`` matrix.
"""
# NOTE: We import SympPy at runtime to avoid the import-time cost for users
# NOTE: We import SymPy at runtime to avoid the import-time cost for users
# that don't want to do symbolic computation. The ``sympy`` import is
# a tad expensive.
import sympy # pylint: disable=import-outside-toplevel
Expand All @@ -90,7 +90,7 @@ def curve_as_polynomial(nodes, degree):
* The symbol ``s`` used in the polynomial
* The curve :math:`B(s)`.
"""
# NOTE: We import SympPy at runtime to avoid the import-time cost for users
# NOTE: We import SymPy at runtime to avoid the import-time cost for users
# that don't want to do symbolic computation. The ``sympy`` import is
# a tad expensive.
import sympy # pylint: disable=import-outside-toplevel
Expand All @@ -117,7 +117,7 @@ def implicitize_2d(x_fn, y_fn, s):
sympy.Expr: The implicitized function :math:`f(x, y)` such that the
curve satisfies :math:`f(x(s), y(s)) = 0`.
"""
# NOTE: We import SympPy at runtime to avoid the import-time cost for users
# NOTE: We import SymPy at runtime to avoid the import-time cost for users
# that don't want to do symbolic computation. The ``sympy`` import is
# a tad expensive.
import sympy # pylint: disable=import-outside-toplevel
Expand Down Expand Up @@ -166,7 +166,7 @@ def triangle_weights(degree, s, t):
sympy.Matrix: The de Casteljau weights for the triangle as an ``N x 1``
matrix, where ``N == (degree + 1)(degree + 2) / 2``.
"""
# NOTE: We import SympPy at runtime to avoid the import-time cost for users
# NOTE: We import SymPy at runtime to avoid the import-time cost for users
# that don't want to do symbolic computation. The ``sympy`` import is
# a tad expensive.
import sympy # pylint: disable=import-outside-toplevel
Expand Down Expand Up @@ -201,7 +201,7 @@ def triangle_as_polynomial(nodes, degree):
* The symbol ``t`` used in the polynomial
* The triangle :math:`B(s, t)`.
"""
# NOTE: We import SympPy at runtime to avoid the import-time cost for users
# NOTE: We import SymPy at runtime to avoid the import-time cost for users
# that don't want to do symbolic computation. The ``sympy`` import is
# a tad expensive.
import sympy # pylint: disable=import-outside-toplevel
Expand Down Expand Up @@ -232,7 +232,7 @@ def implicitize_3d(x_fn, y_fn, z_fn, s, t):
sympy.Expr: The implicitized function :math:`f(x, y, z)` such that the
triangle satisfies :math:`f(x(s, t), y(s, t), z(s, t)) = 0`.
"""
# NOTE: We import SympPy at runtime to avoid the import-time cost for users
# NOTE: We import SymPy at runtime to avoid the import-time cost for users
# that don't want to do symbolic computation. The ``sympy`` import is
# a tad expensive.
import sympy # pylint: disable=import-outside-toplevel
Expand Down
19 changes: 4 additions & 15 deletions tests/unit/test__algebraic_intersection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1066,28 +1066,17 @@ def _call_function_under_test(lu_mat, one_norm):
lu_mat, one_norm
)

@unittest.mock.patch(
"bezier._algebraic_intersection._scipy_lapack", new=None
)
def test_without_scipy(self):
lu_mat = np.zeros((2, 2), order="F")
one_norm = 0.0
with self.assertRaises(OSError):
self._call_function_under_test(lu_mat, one_norm)

@unittest.mock.patch("bezier._algebraic_intersection._scipy_lapack")
def test_dgecon_failure(self, _scipy_lapack):
@unittest.mock.patch("scipy.linalg.lapack.dgecon")
def test_dgecon_failure(self, dgecon):
rcond = 0.5
info = -1
_scipy_lapack.dgecon.return_value = rcond, info
dgecon.return_value = rcond, info
one_norm = 1.0
with self.assertRaises(RuntimeError):
self._call_function_under_test(
unittest.mock.sentinel.lu_mat, one_norm
)
_scipy_lapack.dgecon.assert_called_once_with(
unittest.mock.sentinel.lu_mat, one_norm
)
dgecon.assert_called_once_with(unittest.mock.sentinel.lu_mat, one_norm)

@unittest.skipIf(SCIPY_LAPACK is None, "SciPy not installed")
def test_singular(self):
Expand Down
8 changes: 0 additions & 8 deletions tests/unit/test__py_curve_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,6 @@ def test_cubic(self):
local_eps = abs(SPACING(expected))
self.assertAlmostEqual(length, expected, delta=local_eps)

def test_without_scipy(self):
nodes = np.zeros((2, 5), order="F")
with unittest.mock.patch(
"bezier._py_curve_helpers._scipy_int", new=None
):
with self.assertRaises(OSError):
self._call_function_under_test(nodes)


class Test_elevate_nodes(utils.NumPyTestCase):
@staticmethod
Expand Down

0 comments on commit 38602d8

Please sign in to comment.