Skip to content

Commit

Permalink
Python 3.11 compatible versions of numba JIT-compiled functions (#20)
Browse files Browse the repository at this point in the history
* update mean_angle for jit-compilation in 3.11

* add haversine dist test

* haversine dist is weird

* update test class names

* update changelog for 0.2.15

* update pyproject file

* add 3.11 to CI workflow

* use 3.8.1 as minimal Py version in CI

* drop 3.8 support !

* bump to 0.3.0 since dropped Python 3.8

* update lock
  • Loading branch information
FObersteiner authored Jun 29, 2023
1 parent adcd2fa commit 5fd98c2
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 441 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10"]
python-version: ["3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@main
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v0.3.0 (2023-06-29)
- Python 3.11 compatible versions of numba JIT-compiled functions
- drop Python 3.8 support

## v0.2.14 (2023-04-17)
- avgbinmap / mean_day_frac: bugfix corner case mean day fraction is almost zero but < 0
- avgbinmap / add scipy based version of mean angle function
Expand Down
777 changes: 378 additions & 399 deletions poetry.lock

Large diffs are not rendered by default.

31 changes: 19 additions & 12 deletions pyfuppes/avgbinmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from cmath import phase, rect
from copy import deepcopy
from math import degrees, radians
from math import cos, sin, atan2, pi

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -47,23 +48,29 @@ def mean_angle(deg):


@njit
def mean_angle_numba(deg):
def mean_angle_numba(angles):
"""
Numba-compatible version of mean_angle().
mean_angle(), numba-JIT compiled.
- input must be numpy array of type float!
C version: https://rosettacode.org/wiki/Averages/Mean_angle
"""
deg = deg[np.isfinite(deg)]
if len(deg) == 0:
angles = angles[np.isfinite(angles)]
if len(angles) == 0:
return np.nan
elif len(deg) == 1:
return deg[0]
elif len(angles) == 1:
return angles[0]

size = len(angles)
y_part = 0.0
x_part = 0.0

result = 0
for d in deg:
result += rect(1, radians(d))
for i in range(size):
x_part += cos(angles[i] * pi / 180)
y_part += sin(angles[i] * pi / 180)

return degrees(phase(result / len(deg)))
return atan2(y_part / size, x_part / size) * 180 / pi


###############################################################################
Expand Down Expand Up @@ -194,7 +201,7 @@ def bin_t_10s(t, force_t_range=True, drop_empty=True):

@njit
def get_npnanmean(v):
"""Njit'ed nan-mean."""
"""nan-mean, numba-JIT compiled."""
return np.nanmean(v)


Expand Down Expand Up @@ -579,7 +586,7 @@ def calc_shift(
_tol: float = 1e-9,
) -> np.ndarray:
"""
Calculate shift values that, when added to arr, put the values of arr on a regular grid.
Calculate shift-values that, when added to arr, put the values of arr on a regular grid. Code gets numba-JIT compiled.
Parameters
----------
Expand Down
36 changes: 17 additions & 19 deletions pyfuppes/geo.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
"""Geospatial helpers, such as Haversine distance or solar zenith angle."""

import math
from datetime import datetime
from math import cos, sin, acos, asin, radians, sqrt, degrees, floor

from geopy import distance
from numba import njit
Expand All @@ -13,25 +13,23 @@

@njit
def haversine_dist(lat, lon):
"""Calculate Haversine distance along lat/lon coordinates."""
"""Calculate Haversine distance along lat/lon coordinates in km. Code gets numba-JIT compiled."""
assert lat.shape[0] == lon.shape[0], "lat/lon must be of same length."
R = 6373 # approximate radius of earth in km
R = 6372.8 # approximate radius of earth in km
dist = 0

lat1 = math.radians(lat[0])
lon1 = math.radians(lon[0])
lat1, lon1 = lat[0], lon[0]
for j in range(1, lat.shape[0]):
lat0, lat1 = lat1, math.radians(lat[j])
lon0, lon1 = lon1, math.radians(lon[j])
lat0, lat1 = lat1, lat[j]
lon0, lon1 = lon1, lon[j]

dlon = lon1 - lon0
dlat = lat1 - lat0
dLat = radians(lat1 - lat0)
dLon = radians(lon1 - lon0)
lat0 = radians(lat0)
lat1 = radians(lat1)

a = (
math.sin(dlat / 2) ** 2
+ math.cos(lat0) * math.cos(lat1) * math.sin(dlon / 2) ** 2
)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
a = sin(dLat / 2) ** 2 + cos(lat0) * cos(lat1) * sin(dLon / 2) ** 2
c = 2 * asin(sqrt(a))

dist += R * c

Expand Down Expand Up @@ -79,13 +77,13 @@ def sza(UTC=datetime.utcnow(), latitude=52.37, longitude=9.72):

# define trigonometry with degrees
def cos2(x):
return math.cos(math.radians(x))
return cos(radians(x))

def sin2(x):
return math.sin(math.radians(x))
return sin(radians(x))

def acos2(x):
return math.degrees(math.acos(x))
return degrees(acos(x))

# parameter
day_of_year = UTC.timetuple().tm_yday
Expand Down Expand Up @@ -132,7 +130,7 @@ def get_EoT(date_ts):
use for: calculation of local solar time
"""
B = (360 / 365) * (date_ts.timetuple().tm_yday - 81)
return 9.87 * math.sin(2 * B) - 7.53 * math.cos(B) - 1.5 * math.sin(B)
return 9.87 * sin(2 * B) - 7.53 * cos(B) - 1.5 * sin(B)


###############################################################################
Expand All @@ -155,5 +153,5 @@ def get_LSTdayFrac(longitude, tz_offset, EoT, days_delta, time_delta):
t_corr = (4 * (longitude - LSTM) + EoT) / 60 / 24 # [d]
LST_frac = (time_delta + tz_offset / 24 - days_delta) + t_corr
if LST_frac > 1:
LST_frac -= math.floor(LST_frac)
LST_frac -= floor(LST_frac)
return LST_frac
9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[tool.poetry]
name = "pyfuppes"
version = "0.2.14"
version = "0.3.0"
description = "A collection of tools in Python"
authors = ["Florian Obersteiner <f.obersteiner@kit.edu>"]
license = "MIT"
readme = "README.md"

[tool.poetry.dependencies]
python = ">= 3.8.1, < 3.11" # indirect; numba ...
python = ">= 3.9, < 3.12"
geopy = ">= 2.0"
matplotlib = ">= 3.0"
pandas = ">= 1.0"
Expand All @@ -18,9 +18,8 @@ scipy = ">= 1.1"
netcdf4 = ">= 1.6"
xarray = ">= 2022, >=2023"
tomli = ">= 2.0.1"
numba = "^0.56.4" # no python 3.11 support as of 2023-02-10
numpy = "<1.24, >=1.18" # required by numba 0.56.4
llvmlite = "<0.40, >=0.39.0dev0" # required by numba 0.56.4
numba = ">= 0.56.4"
numpy = ">= 1.18"

[tool.poetry.dev-dependencies]
pytest = ">=7.0"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_avgbin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pyfuppes import avgbinmap


class TestTimeconv(unittest.TestCase):
class TestAvgbinmap(unittest.TestCase):
@classmethod
def setUpClass(cls):
# to run before all tests
Expand Down
2 changes: 1 addition & 1 deletion tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pyfuppes import filters


class TestTimeconv(unittest.TestCase):
class TestFilters(unittest.TestCase):
@classmethod
def setUpClass(cls):
# to run before all tests
Expand Down
40 changes: 40 additions & 0 deletions tests/test_geo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-

import unittest

import numpy as np

from pyfuppes import geo


class TestGeo(unittest.TestCase):
@classmethod
def setUpClass(cls):
# to run before all tests
print("\ntesting pyfuppes.avgbin...")

@classmethod
def tearDownClass(cls):
# to run after all tests
pass

def setUp(self):
# to run before each test
pass

def tearDown(self):
# to run after each test
pass

def test_haversine_dist(self):
dist = 2887
tol_decimalplaces = 0
self.assertAlmostEqual(
geo.haversine_dist(np.array((36.12, 33.94)), np.array((-86.67, -118.40))),
dist,
tol_decimalplaces,
)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion tests/test_interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pyfuppes import interpolate


class TestCfg(unittest.TestCase):
class TestInterpolate(unittest.TestCase):
@classmethod
def setUpClass(cls):
pass
Expand Down
2 changes: 1 addition & 1 deletion tests/test_timecorr.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def _make_df(dt_list):
)


class TestTimeconv(unittest.TestCase):
class TestTimecorr(unittest.TestCase):
@classmethod
def setUpClass(cls):
# to run before all tests
Expand Down
2 changes: 1 addition & 1 deletion tests/test_v25.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
src, dst = wd / "test_input", wd / "test_output"


class TestTimeconv(unittest.TestCase):
class TestV25tools(unittest.TestCase):
@classmethod
def setUpClass(cls):
# to run before all tests
Expand Down

0 comments on commit 5fd98c2

Please sign in to comment.