From 964e5589815e41de6a28a6c17d92edaadb380616 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:46:00 +0000 Subject: [PATCH 1/3] Initial plan From 420673af25e723da7702a026601ca3da4fbdddb3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 07:01:23 +0000 Subject: [PATCH 2/3] Fix Python 3.8/3.9 compatibility: replace PEP 604 type hints and pin CI actions Co-authored-by: edithatogo <15080672+edithatogo@users.noreply.github.com> --- .github/workflows/ci.yml | 20 ++++++++++---------- pymars/_forward.py | 15 ++++++++------- pymars/_pruning.py | 13 +++++++------ pymars/_record.py | 13 +++++++------ pymars/earth.py | 3 ++- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00f97d3..365f441 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,9 @@ jobs: python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] 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 @@ -29,9 +29,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 +44,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 +59,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 +80,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/_forward.py b/pymars/_forward.py index 7ada1e0..cf9806a 100644 --- a/pymars/_forward.py +++ b/pymars/_forward.py @@ -7,6 +7,7 @@ """ import logging +from typing import List, Optional, Tuple import numpy as np @@ -39,7 +40,7 @@ def __init__(self, earth_model: Earth): self.y_train = None self.n_samples = 0 self.n_features = 0 - self.current_basis_functions: list[BasisFunction] = [] + self.current_basis_functions: List[BasisFunction] = [] self.current_B_matrix = None self.current_coefficients = None self.current_rss = np.inf @@ -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, Optional[np.ndarray], int]: if B_matrix is None or B_matrix.shape[1] == 0: mean_y = np.mean(y) rss = np.sum((y - mean_y)**2) @@ -93,7 +94,7 @@ def _calculate_rss_and_coeffs( except np.linalg.LinAlgError: return np.inf, None, num_valid_rows - def _build_basis_matrix(self, X_processed: np.ndarray, basis_functions: list[BasisFunction]) -> np.ndarray: + def _build_basis_matrix(self, X_processed: np.ndarray, basis_functions: List[BasisFunction]) -> np.ndarray: if not basis_functions: return np.empty((X_processed.shape[0], 0)) @@ -108,7 +109,7 @@ def _build_basis_matrix(self, X_processed: np.ndarray, basis_functions: list[Bas return B_matrix def run(self, X_fit_processed: np.ndarray, y_fit: np.ndarray, - missing_mask: np.ndarray, X_fit_original: np.ndarray) -> tuple[list[BasisFunction], np.ndarray]: + missing_mask: np.ndarray, X_fit_original: np.ndarray) -> Tuple[List[BasisFunction], np.ndarray]: self.X_train = X_fit_processed self.y_train = y_fit self.missing_mask = missing_mask @@ -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[Optional[float], Optional[np.ndarray]]: 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, Optional[BasisFunction]]]: + candidate_additions: List[Tuple[BasisFunction, Optional[BasisFunction]]] = [] 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..1ae4711 100644 --- a/pymars/_pruning.py +++ b/pymars/_pruning.py @@ -8,6 +8,7 @@ """ import logging +from typing import List, Optional, Tuple import numpy as np @@ -35,10 +36,10 @@ def __init__(self, earth_model: Earth): self.X_fit_original = None self.best_gcv_so_far = np.inf - self.best_basis_functions_so_far: list[BasisFunction] = [] + 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, Optional[np.ndarray], int]: """ Calculates RSS, coefficients, and num_valid_rows, considering NaNs in B_matrix. y_data is assumed finite. @@ -78,7 +79,7 @@ def _calculate_rss_and_coeffs(self, B_matrix: np.ndarray, y_data: np.ndarray) -> ) return np.inf, None, num_valid_rows - def _build_basis_matrix(self, X_data: np.ndarray, basis_functions: list[BasisFunction], + def _build_basis_matrix(self, X_data: np.ndarray, basis_functions: List[BasisFunction], missing_mask: np.ndarray) -> np.ndarray: """ Constructs the basis matrix B from X_data (which is X_processed) @@ -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[Optional[float], Optional[float], Optional[np.ndarray]]: """ Computes GCV, RSS, and coefficients for a given subset of basis functions. Returns (gcv, rss, coeffs). @@ -155,8 +156,8 @@ def _compute_gcv_for_subset(self, X_fit_processed: np.ndarray, y_fit: np.ndarray def run(self, X_fit_processed: np.ndarray, y_fit: np.ndarray, missing_mask: np.ndarray, X_fit_original: np.ndarray, - initial_basis_functions: list[BasisFunction], - initial_coefficients: np.ndarray) -> tuple[list[BasisFunction], np.ndarray, float]: + initial_basis_functions: List[BasisFunction], + initial_coefficients: np.ndarray) -> Tuple[List[BasisFunction], np.ndarray, float]: self.X_train = X_fit_processed self.y_train = y_fit.ravel() diff --git a/pymars/_record.py b/pymars/_record.py index eea572a..37809fd 100644 --- a/pymars/_record.py +++ b/pymars/_record.py @@ -7,6 +7,7 @@ """ import logging +from typing import List import numpy as np @@ -29,13 +30,13 @@ def __init__(self, X, y, earth_model_instance): # Pruning pass tracking # These will store the sequence of models considered during pruning. # Each element corresponds to a model of a certain size. - self.pruning_trace_basis_functions_: list[list[BasisFunction]] = [] - self.pruning_trace_coeffs_: list[np.ndarray] = [] - self.pruning_trace_gcv_: list[float] = [] - self.pruning_trace_rss_: list[float] = [] + self.pruning_trace_basis_functions_: List[List[BasisFunction]] = [] + self.pruning_trace_coeffs_: List[np.ndarray] = [] + self.pruning_trace_gcv_: List[float] = [] + self.pruning_trace_rss_: List[float] = [] # Final selected model details (can be set after pruning) - self.final_basis_: list[BasisFunction] = None + self.final_basis_: List[BasisFunction] = None self.final_coeffs_ = None self.final_gcv_ = None self.final_rss_ = None @@ -47,7 +48,7 @@ def log_forward_pass_step(self, basis_functions, coefficients, rss): self.fwd_coeffs_.append(np.copy(coefficients)) self.fwd_rss_.append(rss) - def log_pruning_step(self, basis_functions: list['BasisFunction'], + def log_pruning_step(self, basis_functions: List['BasisFunction'], coefficients: np.ndarray, gcv: float, rss: float): # Renamed """ Log a model state (basis functions, coefficients, GCV, RSS) encountered diff --git a/pymars/earth.py b/pymars/earth.py index 0fbb091..433f4fe 100644 --- a/pymars/earth.py +++ b/pymars/earth.py @@ -3,6 +3,7 @@ The main Earth class, coordinating the model fitting process. """ import logging +from typing import List, Optional import numpy as np from sklearn.base import BaseEstimator, RegressorMixin @@ -127,7 +128,7 @@ def __init__(self, max_degree: int = 1, penalty: float = 3.0, max_terms: int = N allow_linear: bool = True, allow_missing: bool = False, # New parameter feature_importance_type: str = None, - categorical_features: list[int] = None + categorical_features: Optional[List[int]] = None # TODO: Consider other py-earth params ): super().__init__() From a49b00979b927be1def5acbc043ed64544f8d0bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 07:02:41 +0000 Subject: [PATCH 3/3] Pin all GitHub Actions workflows to v4 for consistency --- .github/workflows/benchmarks.yml | 4 ++-- .github/workflows/code-quality.yml | 4 ++-- .github/workflows/docs.yml | 4 ++-- .github/workflows/release.yml | 6 +++--- .github/workflows/security.yml | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 697327b..3248455 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -10,9 +10,9 @@ jobs: benchmark: 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' diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index ee9934d..ad56236 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -10,9 +10,9 @@ jobs: code-quality: 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' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2428c5f..5938355 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,12 +12,12 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 with: ref: docs # Always check out the docs branch - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v4 with: python-version: '3.11' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f8b82c6..87660f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,9 +13,9 @@ jobs: contents: write 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' @@ -34,7 +34,7 @@ jobs: needs: release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for all tags and branches - name: Generate release notes diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index de9e1c6..9c37df4 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -12,9 +12,9 @@ jobs: security: 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'