Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,17 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- name: Checkout source
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup Conda Environment
uses: conda-incubator/setup-miniconda@v2.2.0
with:
miniforge-variant: Mambaforge
miniforge-version: latest
use-mamba: true
python-version: ${{ matrix.python-version }}
activate-environment: dask-glm
- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install dask_glm
shell: bash -l {0}
run: pip install -e .
run: uv sync --python ${{ matrix.python-version }} --extra dev

- name: Run pytest
shell: bash -l {0}
run: |
pip install pytest pytest-xdist
pytest dask_glm -n auto
run: uv run pytest dask_glm
6 changes: 3 additions & 3 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ jobs:
name: "pre-commit hooks"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: pre-commit/action@v3.0.0
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: pre-commit/action@v3.0.1
83 changes: 25 additions & 58 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
@@ -1,79 +1,46 @@
name: Build and maybe upload to PyPI
name: Build and publish to PyPI

on:
push:
tags:
- '*'
pull_request:
release:
types:
- released
- prereleased
workflow_dispatch:

jobs:
artifacts:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]

build:
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Build wheels
run: pip wheel . -w dist
- name: Build Source Dist
run: python setup.py sdist
- uses: actions/upload-artifact@v3
with:
name: wheel
path: ./dist/dask_glm*
- uses: actions/upload-artifact@v3
with:
name: sdist
path: ./dist/dask-glm*

list_artifacts:
name: List build artifacts
needs: [artifacts]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: sdist
path: dist
- uses: actions/download-artifact@v3
- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Build wheel and sdist
run: uv build

- uses: actions/upload-artifact@v4
with:
name: wheel
path: dist
- name: test
run: |
ls
ls dist
name: dist
path: dist/

upload_pypi:
needs: [artifacts]
if: "startsWith(github.ref, 'refs/tags/')"
publish:
name: Publish to PyPI
needs: [build]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
environment:
name: releases
url: https://pypi.org/p/dask-glm
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: sdist
path: dist
- uses: actions/download-artifact@v3
with:
name: wheel
path: dist
name: dist
path: dist/

- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
skip-existing: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.swpi
*.cache
*.coverage
uv.lock

# Distribution / packaging
.Python
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
repos:
- repo: https://github.com/psf/black
rev: 22.10.0
rev: 25.1.0
hooks:
- id: black
language_version: python3

- repo: https://github.com/pycqa/flake8
rev: 5.0.4
rev: 7.2.0
hooks:
- id: flake8
language_version: python3

- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 6.0.1
hooks:
- id: isort
language_version: python3
args: ["--profile", "black"]

- repo: https://github.com/codespell-project/codespell
rev: v2.2.4
rev: v2.4.1
hooks:
- id: codespell
additional_dependencies:
Expand Down
6 changes: 3 additions & 3 deletions dask_glm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pkg_resources import DistributionNotFound, get_distribution
from importlib.metadata import PackageNotFoundError, version

try:
__version__ = get_distribution(__name__).version
except DistributionNotFound:
__version__ = version(__name__)
except PackageNotFoundError:
pass
11 changes: 4 additions & 7 deletions dask_glm/algorithms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"""Optimization algorithms for solving minimizaiton problems.
"""

from __future__ import absolute_import, division, print_function
"""Optimization algorithms for solving minimizaiton problems."""

import functools

Expand Down Expand Up @@ -233,7 +230,7 @@ def admm(
abstol=1e-4,
reltol=1e-2,
family=Logistic,
**kwargs
**kwargs,
):
"""
Alternating Direction Method of Multipliers
Expand Down Expand Up @@ -358,7 +355,7 @@ def lbfgs(
tol=1e-4,
family=Logistic,
verbose=False,
**kwargs
**kwargs,
):
"""L-BFGS solver using scipy.optimize implementation

Expand Down Expand Up @@ -428,7 +425,7 @@ def proximal_grad(
family=Logistic,
max_iter=100,
tol=1e-8,
**kwargs
**kwargs,
):
"""
Proximal Gradient Method
Expand Down
1 change: 1 addition & 0 deletions dask_glm/estimators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Models following scikit-learn's estimator API.
"""

from sklearn.base import BaseEstimator

from . import algorithms, families
Expand Down
47 changes: 47 additions & 0 deletions dask_glm/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pytest


def pytest_addoption(parser):
parser.addoption(
"--slow", action="store_true", default=False, help="run slow tests"
)


def pytest_configure(config):
config.addinivalue_line("markers", "slow: mark test as slow to run")


def pytest_collection_modifyitems(config, items):
if config.getoption("--slow"):
return
skip_slow = pytest.mark.skip(reason="need --slow option to run")
for item in items:
# Check for @pytest.mark.slow on the function
if item.get_closest_marker("slow"):
item.add_marker(skip_slow)
continue
# Check for pytest.param(..., marks=pytest.mark.slow) in parametrize
if not hasattr(item, "callspec"):
continue
for marker in item.iter_markers("parametrize"):
param_names = [n.strip() for n in marker.args[0].split(",")]
for param in marker.args[1]:
if not hasattr(param, "marks") or not param.marks:
continue
has_slow = any(
getattr(m, "name", None) == "slow"
or (hasattr(m, "mark") and m.mark.name == "slow")
for m in param.marks
)
if not has_slow:
continue
# Check if all values from this param match the callspec
match = True
for i, name in enumerate(param_names):
if i < len(param.values):
if item.callspec.params.get(name) != param.values[i]:
match = False
break
if match:
item.add_marker(skip_slow)
break
3 changes: 2 additions & 1 deletion dask_glm/tests/test_admm.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from dask_glm.utils import make_y, to_dask_cupy_array_xy


@pytest.mark.parametrize("N", [1000, 10000])
@pytest.mark.parametrize("N", [1000, pytest.param(10000, marks=pytest.mark.slow)])
@pytest.mark.parametrize(
"beta",
[
Expand Down Expand Up @@ -47,6 +47,7 @@ def wrapped(beta, X, y, z, u, rho):
assert np.allclose(result, z, atol=2e-3)


@pytest.mark.slow
@pytest.mark.parametrize("N", [1000, 10000])
@pytest.mark.parametrize("nchunks", [5, 10])
@pytest.mark.parametrize("p", [1, 5, 10])
Expand Down
7 changes: 5 additions & 2 deletions dask_glm/tests/test_algos_families.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ def test_basic_unreg_descent(func, kwargs, N, nchunks, family, array_type):
@pytest.mark.parametrize(
"func,kwargs",
[
(admm, {"abstol": 1e-4}),
pytest.param(admm, {"abstol": 1e-4}, marks=pytest.mark.slow),
(proximal_grad, {"tol": 1e-7}),
],
)
@pytest.mark.parametrize("N", [1000])
@pytest.mark.parametrize("nchunks", [1, 10])
@pytest.mark.parametrize("nchunks", [1, pytest.param(10, marks=pytest.mark.slow)])
@pytest.mark.parametrize("family", [Logistic, Normal, Poisson])
@pytest.mark.parametrize("lam", [0.01, 1.2, 4.05])
@pytest.mark.parametrize("reg", [r() for r in Regularizer.__subclasses__()])
Expand Down Expand Up @@ -138,6 +138,7 @@ def test_basic_reg_descent(func, kwargs, N, nchunks, family, lam, reg, array_typ
assert opt < test_val


@pytest.mark.slow
@pytest.mark.parametrize(
"func,kwargs",
[
Expand Down Expand Up @@ -167,6 +168,7 @@ def test_determinism(func, kwargs, scheduler, array_type):
pass
else:

@pytest.mark.slow
@pytest.mark.parametrize(
"func,kwargs",
[
Expand All @@ -186,6 +188,7 @@ def test_determinism_distributed(func, kwargs, loop_in_thread): # noqa: F811

assert (a == b).all()

@pytest.mark.slow
def test_broadcast_lbfgs_weight(loop_in_thread): # noqa: F811
with cluster() as (s, [a, b]):
with Client(s["address"], loop=loop_in_thread) as c:
Expand Down
16 changes: 12 additions & 4 deletions dask_glm/tests/test_estimators.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ def _maybe_skip_sparse_error(fit_intercept, is_sparse, is_cupy):
pytest.xfail(f"TODO: {msg}")


@pytest.mark.parametrize("fit_intercept", [True, False])
@pytest.mark.parametrize(
"is_sparse,is_cupy", [(True, False), (False, False), (False, True)]
"fit_intercept", [pytest.param(True, marks=pytest.mark.slow), False]
)
@pytest.mark.parametrize(
"is_sparse,is_cupy",
[pytest.param(True, False, marks=pytest.mark.slow), (False, False), (False, True)],
)
def test_fit(fit_intercept, is_sparse, is_cupy):
_maybe_skip_sparse_error(fit_intercept, is_sparse, is_cupy)
Expand All @@ -75,9 +78,12 @@ def test_fit(fit_intercept, is_sparse, is_cupy):
lr.predict_proba(X)


@pytest.mark.parametrize("fit_intercept", [True, False])
@pytest.mark.parametrize(
"is_sparse,is_cupy", [(True, False), (False, False), (False, True)]
"fit_intercept", [pytest.param(True, marks=pytest.mark.slow), False]
)
@pytest.mark.parametrize(
"is_sparse,is_cupy",
[pytest.param(True, False, marks=pytest.mark.slow), (False, False), (False, True)],
)
def test_lm(fit_intercept, is_sparse, is_cupy):
_maybe_skip_sparse_error(fit_intercept, is_sparse, is_cupy)
Expand All @@ -95,6 +101,7 @@ def test_lm(fit_intercept, is_sparse, is_cupy):
assert lr.intercept_ is not None


@pytest.mark.slow
@pytest.mark.parametrize("fit_intercept", [True, False])
@pytest.mark.parametrize(
"is_sparse,is_cupy", [(True, False), (False, False), (False, True)]
Expand All @@ -115,6 +122,7 @@ def test_big(fit_intercept, is_sparse, is_cupy):
assert lr.intercept_ is not None


@pytest.mark.slow
@pytest.mark.parametrize("fit_intercept", [True, False])
@pytest.mark.parametrize(
"is_sparse,is_cupy", [(True, False), (False, False), (False, True)]
Expand Down
Loading
Loading