diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00f97d3..5dae1c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,17 +11,21 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] + python-version: [3.8, 3.9, '3.10'] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip + # Install importlib-metadata backport for Python < 3.10 + if [[ "${{ matrix.python-version }}" == "3.8" ]] || [[ "${{ matrix.python-version }}" == "3.9" ]]; then + pip install importlib-metadata + fi pip install tox tox-gh-actions - name: Test with tox run: tox -e py @@ -29,9 +33,9 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies @@ -44,9 +48,9 @@ jobs: type-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies @@ -59,9 +63,9 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies @@ -80,9 +84,9 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies diff --git a/pymars/_compat.py b/pymars/_compat.py new file mode 100644 index 0000000..bf7d3ec --- /dev/null +++ b/pymars/_compat.py @@ -0,0 +1,6 @@ +try: + import importlib.metadata as importlib_metadata # type: ignore +except Exception: + import importlib_metadata # type: ignore + +__all__ = ("importlib_metadata",) diff --git a/pymars/_forward.py b/pymars/_forward.py index 7ada1e0..763efc7 100644 --- a/pymars/_forward.py +++ b/pymars/_forward.py @@ -7,6 +7,7 @@ """ import logging +from typing import Union import numpy as np @@ -53,7 +54,7 @@ def __init__(self, earth_model: Earth): def _calculate_rss_and_coeffs( self, B_matrix: np.ndarray, y: np.ndarray, *, drop_nan_rows: bool = True - ) -> tuple[float, np.ndarray | None, int]: + ) -> tuple[float, Union[np.ndarray, None], int]: if B_matrix is None or B_matrix.shape[1] == 0: mean_y = np.mean(y) rss = np.sum((y - mean_y)**2) @@ -225,7 +226,7 @@ def run(self, X_fit_processed: np.ndarray, y_fit: np.ndarray, return self.current_basis_functions, self.current_coefficients - def _calculate_gcv_for_basis_set(self, basis_functions: list[BasisFunction]) -> tuple[float | None, np.ndarray | None]: + def _calculate_gcv_for_basis_set(self, basis_functions: list[BasisFunction]) -> tuple[Union[float, None], Union[np.ndarray, None]]: if not basis_functions: # This implies an intercept-only model for GCV calculation purposes rss_intercept_only = np.sum((self.y_train - np.mean(self.y_train))**2) @@ -348,8 +349,8 @@ def _get_allowable_knot_values(self, X_col_original_for_var: np.ndarray, parent_ minspan_countdown = max(0, minspan_abs - 1) return np.array(final_allowable_knots) - def _generate_candidates(self) -> list[tuple[BasisFunction, BasisFunction | None]]: - candidate_additions: list[tuple[BasisFunction, BasisFunction | None]] = [] + def _generate_candidates(self) -> list[tuple[BasisFunction, Union[BasisFunction, None]]]: + candidate_additions: list[tuple[BasisFunction, Union[BasisFunction, None]]] = [] for parent_bf in self.current_basis_functions: if parent_bf.degree() + 1 > self.model.max_degree: continue parent_involved_vars = parent_bf.get_involved_variables() diff --git a/pymars/_pruning.py b/pymars/_pruning.py index 07bb477..a54dfd3 100644 --- a/pymars/_pruning.py +++ b/pymars/_pruning.py @@ -8,6 +8,7 @@ """ import logging +from typing import Union import numpy as np @@ -38,7 +39,7 @@ def __init__(self, earth_model: Earth): self.best_basis_functions_so_far: list[BasisFunction] = [] self.best_coeffs_so_far: np.ndarray = None - def _calculate_rss_and_coeffs(self, B_matrix: np.ndarray, y_data: np.ndarray) -> tuple[float, np.ndarray | None, int]: + def _calculate_rss_and_coeffs(self, B_matrix: np.ndarray, y_data: np.ndarray) -> tuple[float, Union[np.ndarray, None], int]: """ Calculates RSS, coefficients, and num_valid_rows, considering NaNs in B_matrix. y_data is assumed finite. @@ -97,7 +98,7 @@ def _build_basis_matrix(self, X_data: np.ndarray, basis_functions: list[BasisFun def _compute_gcv_for_subset(self, X_fit_processed: np.ndarray, y_fit: np.ndarray, missing_mask: np.ndarray, X_fit_original: np.ndarray, - basis_subset: list[BasisFunction]) -> tuple[float | None, float | None, np.ndarray | None]: + basis_subset: list[BasisFunction]) -> tuple[Union[float, None], Union[float, None], Union[np.ndarray, None]]: """ Computes GCV, RSS, and coefficients for a given subset of basis functions. Returns (gcv, rss, coeffs).