From c06fe08a3121f39148beb6f04d63a9b84c6979be Mon Sep 17 00:00:00 2001 From: Frederick Robinson Date: Fri, 1 Nov 2024 11:06:27 -0400 Subject: [PATCH] type `sparse.py`, enable mypy in CI (#773) * typing configs * remove fallback implemenations of combination/permutation functions * refactor "test" to a test * some types * stash * register test_sparse in `run_tests.py` * isort * types * revert * unused * remove type restriction * python 3.7 * does claude know travis? * add comprehensive blacklist * travis * omit one more * black * add mypy to github actions * remove travis * better type ignore * remove whitespace cruft from merge * turns out black wanted that whitespace... * took out versions from requirements-dev.txt and simplified exclude rule for mypy --------- Co-authored-by: Franco Peschiera --- .github/workflows/pythonpackage.yml | 6 +++- .travis.yml | 42 -------------------------- pulp/sparse.py | 46 ++++++++++++++++++++--------- pulp/tests/test_pulp.py | 2 -- pulp/tests/test_sparse.py | 40 +++++++++++++++++++++++-- pyproject.toml | 17 +++++++++++ requirements-dev.txt | 3 +- 7 files changed, 94 insertions(+), 62 deletions(-) delete mode 100644 .travis.yml create mode 100644 pyproject.toml diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 6d6999cc..f85c22f6 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -20,13 +20,17 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Update pip + - name: Update pip, install dev deps run: | python -m pip install --upgrade pip + pip install -r requirements-dev.txt - name: Code Quality run: | python -m pip install black black pulp/ --check + - name: Type Check + run: | + mypy ./ - name: Install dependencies run: | python -m pip install . diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bf27742a..00000000 --- a/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ -language: python -matrix: - include: - # Ubuntu 18.04 LTS (Bionic Beaver) - - dist: bionic - python: pypy3 - - dist: bionic - python: 3.6 - - dist: bionic - python: 3.7 - - dist: bionic - python: 3.8 - - dist: bionic - python: 3.9 - - dist: bionic - python: 3.9-dev - - dist: bionic - python: nightly - # Ubuntu 20.04 LTS (Focal Fossa) - - dist: focal - python: pypy3 - - dist: focal - python: 3.6 - - dist: focal - python: 3.7 - - dist: focal - python: 3.8 - - dist: focal - python: 3.9 - - dist: focal - python: 3.9-dev - - dist: focal - python: nightly -before_install: -- sudo apt-get update -qq -- sudo apt-get install -qq glpk-utils - -install: -- pip install . - -script: pulptest - diff --git a/pulp/sparse.py b/pulp/sparse.py index 09d7231f..545ffefd 100644 --- a/pulp/sparse.py +++ b/pulp/sparse.py @@ -1,3 +1,5 @@ +from typing import Dict, Generic, List, Tuple, TypeVar, cast + # Sparse : Python basic dictionary sparse matrix # Copyright (c) 2007, Stuart Mitchell (s.mitchell@auckland.ac.nz) @@ -27,22 +29,31 @@ notably this allows the sparse matrix to be output in various formats """ +T = TypeVar("T") + -class Matrix(dict): +class Matrix(Generic[T], Dict[Tuple[int, int], T]): """This is a dictionary based sparse matrix class""" - def __init__(self, rows, cols): + def __init__(self, rows: List[int], cols: List[int]): """initialises the class by creating a matrix that will have the given rows and columns """ - self.rows = rows - self.cols = cols - self.rowdict = {row: {} for row in rows} - self.coldict = {col: {} for col in cols} + self.rows: List[int] = rows + self.cols: List[int] = cols + self.rowdict: Dict[int, Dict[int, T]] = {row: {} for row in rows} + self.coldict: Dict[int, Dict[int, T]] = {col: {} for col in cols} - def add(self, row, col, item, colcheck=False, rowcheck=False): - if not (rowcheck and row not in self.rows): - if not (colcheck and col not in self.cols): + def add( + self, + row: int, + col: int, + item: T, + colcheck: bool = False, + rowcheck: bool = False, + ) -> None: + if not rowcheck or row in self.rows: + if not colcheck or col in self.cols: dict.__setitem__(self, (row, col), item) self.rowdict[row][col] = item self.coldict[col][row] = item @@ -52,7 +63,7 @@ def add(self, row, col, item, colcheck=False, rowcheck=False): else: raise RuntimeError(f"row {row} is not in the matrix rows") - def addcol(self, col, rowitems): + def addcol(self, col: int, rowitems: Dict[int, T]) -> None: """adds a column""" if col in self.cols: for row, item in rowitems.items(): @@ -60,12 +71,18 @@ def addcol(self, col, rowitems): else: raise RuntimeError("col is not in the matrix columns") - def get(self, k, d=0): - return dict.get(self, k, d) + def get( # type: ignore[override] + self, + coords: Tuple[int, int], + default: T = 0, # type: ignore[assignment] + ) -> T: + return dict.get(self, coords, default) - def col_based_arrays(self): + def col_based_arrays( + self, + ) -> Tuple[int, List[int], List[int], List[int], List[T]]: numEls = len(self) - elemBase = [] + elemBase: List[T] = [] startsBase = [] indBase = [] lenBase = [] @@ -74,5 +91,6 @@ def col_based_arrays(self): elemBase.extend(list(self.coldict[col].values())) indBase.extend(list(self.coldict[col].keys())) lenBase.append(len(elemBase) - startsBase[-1]) + startsBase.append(len(elemBase)) return numEls, startsBase, lenBase, indBase, elemBase diff --git a/pulp/tests/test_pulp.py b/pulp/tests/test_pulp.py index 77666b5c..6a9b8b38 100644 --- a/pulp/tests/test_pulp.py +++ b/pulp/tests/test_pulp.py @@ -1218,7 +1218,6 @@ def add_const(prob): @gurobi_test def test_measuring_solving_time(self): - time_limit = 10 solver_settings = dict( PULP_CBC_CMD=30, @@ -1256,7 +1255,6 @@ def test_measuring_solving_time(self): @gurobi_test def test_time_limit_no_solution(self): - time_limit = 1 solver_settings = dict(HiGHS_CMD=60, HiGHS=60, PULP_CBC_CMD=60, COIN_CMD=60) bins = solver_settings.get(self.solver.name) diff --git a/pulp/tests/test_sparse.py b/pulp/tests/test_sparse.py index 4ea921aa..7bf917c5 100644 --- a/pulp/tests/test_sparse.py +++ b/pulp/tests/test_sparse.py @@ -4,10 +4,10 @@ class SparseTest(unittest.TestCase): - def test_sparse(self): + def test_sparse(self) -> None: rows = list(range(10)) cols = list(range(50, 60)) - mat = Matrix(rows, cols) + mat = Matrix[str](rows, cols) mat.add(1, 52, "item") mat.add(2, 54, "stuff") assert mat.col_based_arrays() == ( @@ -17,3 +17,39 @@ def test_sparse(self): [1, 2], ["item", "stuff"], ) + + assert mat.get((1, 52)) == "item" + + mat.addcol(51, {2: "hello"}) + assert mat.col_based_arrays() == ( + 3, + [0, 0, 1, 2, 2, 3, 3, 3, 3, 3, 3], + [0, 1, 1, 0, 1, 0, 0, 0, 0, 0], + [2, 1, 2], + ["hello", "item", "stuff"], + ) + + def test_sparse_floats(self) -> None: + rows = list(range(10)) + cols = list(range(50, 60)) + mat = Matrix[float](rows, cols) + mat.add(1, 52, 1.234) + mat.add(2, 54, 5.678) + assert mat.col_based_arrays() == ( + 2, + [0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2], + [0, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [1, 2], + [1.234, 5.678], + ) + + assert mat.get((1, 52)) == 1.234 + + mat.addcol(51, {2: 9.876}) + assert mat.col_based_arrays() == ( + 3, + [0, 0, 1, 2, 2, 3, 3, 3, 3, 3, 3], + [0, 1, 1, 0, 1, 0, 0, 0, 0, 0], + [2, 1, 2], + [9.876, 1.234, 5.678], + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..85e4991b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[tool.mypy] +exclude = [ + "doc/", + "examples/", + "pulp/__init__.py", + "pulp/apis/", + "pulp/mps_lp.py", + "pulp/pulp.py", + "pulp/solverdir/", + "pulp/tests/", + "pulp/utilities.py", + "setup.py", + "venv/.*" +] +follow_imports = "silent" +strict = true +check_untyped_defs = true diff --git a/requirements-dev.txt b/requirements-dev.txt index e22bf08e..b3d8bf75 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,5 @@ +black +mypy pre-commit==2.12.0 sphinx sphinx_rtd_theme -black \ No newline at end of file