Skip to content

Commit

Permalink
Merge pull request #349 from robbievanleeuwen/optional-numba
Browse files Browse the repository at this point in the history
Make `numba` an optional dependency
  • Loading branch information
robbievanleeuwen committed Oct 20, 2023
2 parents 9030803 + 8cff33d commit d2720b7
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 21 deletions.
1 change: 1 addition & 0 deletions .readthedocs.yaml
Expand Up @@ -23,3 +23,4 @@ python:
extra_requirements:
- dxf
- rhino
- numba
12 changes: 12 additions & 0 deletions docs/installation.rst
Expand Up @@ -26,6 +26,18 @@ package index:
pip install sectionproperties
Installing ``Numba``
--------------------

``Numba`` translates a subset of Python and NumPy code into fast machine code, allowing
algorithms to approach the speeds of C. The speed of several ``sectionproperties``
analysis functions have been enhanced with `numba <https://github.com/numba/numba>`_.
To take advantage of this increase in performance you can install ``numba`` alongside
``sectionproperties`` with:

.. code-block:: shell
pip install sectionproperties[numba]
Installing ``PARDISO`` Solver
-----------------------------
Expand Down
16 changes: 14 additions & 2 deletions noxfile.py
Expand Up @@ -212,7 +212,13 @@ def docs_build(session: Session) -> None:
args.insert(0, "--color")

session.run_always(
"poetry", "install", "--only", "main", "--extras", "dxf rhino", external=True
"poetry",
"install",
"--only",
"main",
"--extras",
"dxf rhino numba",
external=True,
)
session.install(
"furo",
Expand Down Expand Up @@ -243,7 +249,13 @@ def docs(session: Session) -> None:
"""
args = session.posargs or ["--open-browser", "docs", "docs/_build"]
session.run_always(
"poetry", "install", "--only", "main", "--extras", "dxf rhino", external=True
"poetry",
"install",
"--only",
"main",
"--extras",
"dxf rhino numba",
external=True,
)
session.install(
"furo",
Expand Down
7 changes: 4 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pyproject.toml
Expand Up @@ -48,15 +48,15 @@ Changelog = "https://github.com/robbievanleeuwen/section-properties/releases"

[tool.poetry.dependencies]
python = ">=3.9.0,<3.12"
numpy = "^1.25.2" # numba requires numpy <1.26
numpy = "^1.25.2"
scipy = "^1.11.3"
matplotlib = "^3.8.0"
shapely = "^2.0.1"
triangle = "^20230923"
rich = "^13.6.0"
click = "^8.1.7"
more-itertools = "^10.1.0"
numba = "^0.58.0"
numba = { version = "^0.58.0", optional = true }
cad-to-shapely = { version = "^0.3.1", optional = true }
rhino-shapley-interop = { version = "^0.0.4", optional = true }
rhino3dm = { version = "==8.0.0b3", optional = true }
Expand Down Expand Up @@ -97,6 +97,7 @@ sphinxext-opengraph = "^0.8.2"
[tool.poetry.extras]
dxf = ["cad-to-shapely"]
rhino = ["rhino-shapley-interop", "rhino3dm"]
numba = ["numba"]
pardiso = ["pypardiso"]

[tool.poetry.scripts]
Expand Down
78 changes: 64 additions & 14 deletions src/sectionproperties/analysis/fea.py
Expand Up @@ -10,19 +10,67 @@
import warnings
from dataclasses import dataclass, field
from functools import lru_cache
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Callable

import numpy as np
import numpy.typing as npt
from numba import njit
from numba.core.errors import NumbaPerformanceWarning


if TYPE_CHECKING:
from sectionproperties.pre.pre import Material


@njit(cache=True, nogil=True) # type: ignore
# numba is an optional dependency
try:
from numba import njit
from numba.core.errors import NumbaPerformanceWarning

USE_NUMBA = True
except ImportError:

def njit() -> None:
"""Assigns empty function to njit if numba isn't installed.
Returns:
None
"""
return None

USE_NUMBA = False


def conditional_decorator(
dec: Callable[[Any], Any],
condition: bool,
) -> Callable[[Any], Any]:
"""A decorator that applies a decorator only if a condition is True.
Args:
dec: Decorator to apply
condition: Apply decorator if this is true
Returns:
Decorator wrapper
"""

def decorator(func: Callable[[Any], Any]) -> Callable[[Any], Any]:
"""Decorator wrapper.
Args:
func: Function decorator operates on.
Returns:
Original or decorated function.
"""
if not condition:
return func

return dec(func) # type: ignore

return decorator


@conditional_decorator(njit, USE_NUMBA)
def _assemble_torsion(
k_el: npt.NDArray[np.float64],
f_el: npt.NDArray[np.float64],
Expand Down Expand Up @@ -55,7 +103,7 @@ def _assemble_torsion(
return k_el, f_el, c_el


@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def _shear_parameter(
nx: float, ny: float, ixx: float, iyy: float, ixy: float
) -> tuple[float, float, float, float, float, float]:
Expand All @@ -81,7 +129,7 @@ def _shear_parameter(
return r, q, d1, d2, h1, h2


@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def _assemble_shear_load(
f_psi: npt.NDArray[np.float64],
f_phi: npt.NDArray[np.float64],
Expand Down Expand Up @@ -126,7 +174,7 @@ def _assemble_shear_load(
return f_psi, f_phi


@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def _assemble_shear_coefficients(
kappa_x: float,
kappa_y: float,
Expand Down Expand Up @@ -648,7 +696,9 @@ def element_stress(

# extrapolate results to nodes, ignore numba warnings about performance
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=NumbaPerformanceWarning)
if USE_NUMBA:
warnings.simplefilter("ignore", category=NumbaPerformanceWarning)

sig_zz_mxx = extrapolate_to_nodes(w=sig_zz_mxx_gp)
sig_zz_myy = extrapolate_to_nodes(w=sig_zz_myy_gp)
sig_zz_m11 = extrapolate_to_nodes(w=sig_zz_m11_gp)
Expand Down Expand Up @@ -951,7 +1001,7 @@ def gauss_points(*, n: int) -> npt.NDArray[np.float64]:


@lru_cache(maxsize=None)
@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def __shape_function_cached(
coords: tuple[float, ...],
gauss_point: tuple[float, float, float],
Expand Down Expand Up @@ -1039,7 +1089,7 @@ def shape_function(


@lru_cache(maxsize=None)
@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def shape_function_only(p: tuple[float, float, float]) -> npt.NDArray[np.float64]:
"""The values of the ``Tri6`` shape function at a point ``p``.
Expand Down Expand Up @@ -1117,7 +1167,7 @@ def shape_function_only(p: tuple[float, float, float]) -> npt.NDArray[np.float64
)


@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def extrapolate_to_nodes(w: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
"""Extrapolates results at six Gauss points to the six nodes of a ``Tri6`` element.
Expand All @@ -1130,7 +1180,7 @@ def extrapolate_to_nodes(w: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
return h_inv @ w


@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def principal_coordinate(
phi: float,
x: float,
Expand All @@ -1153,7 +1203,7 @@ def principal_coordinate(
return x * cos_phi + y * sin_phi, y * cos_phi - x * sin_phi


@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def global_coordinate(
phi: float,
x11: float,
Expand All @@ -1176,7 +1226,7 @@ def global_coordinate(
return x11 * cos_phi - y22 * sin_phi, x11 * sin_phi + y22 * cos_phi


@njit(cache=True, nogil=True) # type: ignore
@conditional_decorator(njit, USE_NUMBA)
def point_above_line(
u: npt.NDArray[np.float64],
px: float,
Expand Down

0 comments on commit d2720b7

Please sign in to comment.