gsimplex 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. gsimplex-0.0.2/PKG-INFO +25 -0
  2. gsimplex-0.0.2/README.md +7 -0
  3. gsimplex-0.0.2/pyproject.toml +44 -0
  4. gsimplex-0.0.2/setup.cfg +4 -0
  5. gsimplex-0.0.2/src/gsimplex/__init__.py +1 -0
  6. gsimplex-0.0.2/src/gsimplex/benchmarks/downloader.py +76 -0
  7. gsimplex-0.0.2/src/gsimplex/benchmarks/netlib.py +78 -0
  8. gsimplex-0.0.2/src/gsimplex/benchmarks/netlib_emps.py +805 -0
  9. gsimplex-0.0.2/src/gsimplex/benchmarks/plato.py +55 -0
  10. gsimplex-0.0.2/src/gsimplex/demo.py +54 -0
  11. gsimplex-0.0.2/src/gsimplex/main.py +37 -0
  12. gsimplex-0.0.2/src/gsimplex/problem.py +46 -0
  13. gsimplex-0.0.2/src/gsimplex/solution.py +25 -0
  14. gsimplex-0.0.2/src/gsimplex/solvers/__init__.py +1 -0
  15. gsimplex-0.0.2/src/gsimplex/solvers/criss_cross.py +13 -0
  16. gsimplex-0.0.2/src/gsimplex/solvers/dual_simplex.py +104 -0
  17. gsimplex-0.0.2/src/gsimplex/solvers/gap_simplex.py +74 -0
  18. gsimplex-0.0.2/src/gsimplex/solvers/iterative_solver.py +20 -0
  19. gsimplex-0.0.2/src/gsimplex/solvers/primal_simplex.py +136 -0
  20. gsimplex-0.0.2/src/gsimplex/solvers/simplex_interface.py +15 -0
  21. gsimplex-0.0.2/src/gsimplex/solvers/solver_interface.py +27 -0
  22. gsimplex-0.0.2/src/gsimplex/tools/extractor.py +37 -0
  23. gsimplex-0.0.2/src/gsimplex/tools/parser.py +45 -0
  24. gsimplex-0.0.2/src/gsimplex/vertex.py +96 -0
  25. gsimplex-0.0.2/src/gsimplex.egg-info/PKG-INFO +25 -0
  26. gsimplex-0.0.2/src/gsimplex.egg-info/SOURCES.txt +30 -0
  27. gsimplex-0.0.2/src/gsimplex.egg-info/dependency_links.txt +1 -0
  28. gsimplex-0.0.2/src/gsimplex.egg-info/entry_points.txt +6 -0
  29. gsimplex-0.0.2/src/gsimplex.egg-info/requires.txt +6 -0
  30. gsimplex-0.0.2/src/gsimplex.egg-info/top_level.txt +1 -0
  31. gsimplex-0.0.2/tests/test_linear_programming.py +46 -0
  32. gsimplex-0.0.2/tests/test_simple.py +88 -0
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: gsimplex
3
+ Version: 0.0.2
4
+ Summary: Implementation of simplex algorithm contrelled by the primal-dual gap
5
+ Author-email: Riccardo Ciucci <riccardo@ciucci.dev>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Richie314/GapControlledSimplex
8
+ Project-URL: Issues, https://github.com/Richie314/GapControlledSimplex/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: numpy>=2.2.0
14
+ Requires-Dist: aiohttp>=3.9.0
15
+ Requires-Dist: pulp>=3.1.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest; extra == "dev"
18
+
19
+ # Gap controlled Simplex
20
+
21
+ [![Test and publish package](https://github.com/Richie314/GapControlledSimplex/actions/workflows/pypi.yml/badge.svg)](https://github.com/Richie314/GapControlledSimplex/actions/workflows/pypi.yml)
22
+
23
+ ![Algorithm diagram](./assets/AlgorithmDiagram.drawio.svg)
24
+
25
+ ## Download test problems
@@ -0,0 +1,7 @@
1
+ # Gap controlled Simplex
2
+
3
+ [![Test and publish package](https://github.com/Richie314/GapControlledSimplex/actions/workflows/pypi.yml/badge.svg)](https://github.com/Richie314/GapControlledSimplex/actions/workflows/pypi.yml)
4
+
5
+ ![Algorithm diagram](./assets/AlgorithmDiagram.drawio.svg)
6
+
7
+ ## Download test problems
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = [ "setuptools >= 80.0.1", "wheel" ]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gsimplex"
7
+ version = "0.0.2"
8
+ authors = [
9
+ { name="Riccardo Ciucci", email="riccardo@ciucci.dev" },
10
+ ]
11
+ description = "Implementation of simplex algorithm contrelled by the primal-dual gap"
12
+ readme = "README.md"
13
+ requires-python = ">=3.12"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ license = "MIT"
19
+ license-files = ["LICEN[CS]E*"]
20
+ dependencies = [
21
+ "numpy >= 2.2.0",
22
+ "aiohttp >= 3.9.0",
23
+ "pulp >= 3.1.0",
24
+ ]
25
+
26
+ [project.scripts]
27
+ gsimplex = "gsimplex.main:__main"
28
+ gsimplex-demo = "gsimplex.demo:demo"
29
+ gsimplex-download-netlib = "gsimplex.benchmarks.netlib:main"
30
+ gsimplex-emps = "gsimplex.benchmarks.netlib_emps:__main"
31
+ gsimplex-download-plato = "gsimplex.benchmarks.plato:main"
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/Richie314/GapControlledSimplex"
35
+ Issues = "https://github.com/Richie314/GapControlledSimplex/issues"
36
+
37
+ [project.optional-dependencies]
38
+ dev = ["pytest"]
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.setuptools]
44
+ include-package-data = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ # Simplex library
@@ -0,0 +1,76 @@
1
+ import asyncio
2
+ import aiohttp
3
+ from pathlib import Path
4
+ from typing import Dict, List, Tuple, Optional
5
+
6
+ class Downloader:
7
+ def __init__(self, benchmark_dir: Optional[str] = None, quiet: bool = False):
8
+ self._quiet = quiet
9
+ if benchmark_dir is None:
10
+ self._benchmark_dir = Path.cwd() / "benchmark"
11
+ else:
12
+ self._benchmark_dir = Path(benchmark_dir)
13
+
14
+ async def download_async(self,
15
+ url: str,
16
+ filename: str,
17
+ cached_filename: Optional[str] = None,
18
+ ) -> Optional[str]:
19
+
20
+ # Make sure the benchmark directory exists
21
+ self._benchmark_dir.mkdir(parents=True, exist_ok=True)
22
+
23
+ if not cached_filename:
24
+ cached_filename = filename
25
+
26
+ filepath = self._benchmark_dir / filename
27
+ cached_filepath = self._benchmark_dir / cached_filename
28
+
29
+ # If already downloaded, return it
30
+ if cached_filepath.exists():
31
+ if not self._quiet:
32
+ print(f"Using cached: {cached_filename}")
33
+ return str(cached_filepath)
34
+
35
+ if not self._quiet:
36
+ print(f"Downloading: {url}...")
37
+ filepath.parent.mkdir(parents=True, exist_ok=True)
38
+ try:
39
+ async with aiohttp.ClientSession() as session:
40
+ async with session.get(url) as response:
41
+ if not response.ok:
42
+ if not self._quiet:
43
+ print(f"Failed to download {filename}: HTTP {response.status}")
44
+ return None
45
+
46
+ content = await response.read()
47
+ with open(filepath, 'wb') as f:
48
+ f.write(content)
49
+
50
+ return str(filepath)
51
+ except Exception as e:
52
+ if not self._quiet:
53
+ print(f"Failed to download {url}: {e}")
54
+ if filepath.exists():
55
+ try:
56
+ filepath.unlink()
57
+ except:
58
+ pass
59
+ return None
60
+
61
+ async def download_many_async(self,
62
+ files: List[Tuple[str, str, str, Optional[str]]],
63
+ post_process=None,
64
+ ) -> Dict[str, str]:
65
+ tasks = [self.download_async(url, filename, cached_filename) for url, problem_name, filename, cached_filename in files]
66
+ results = await asyncio.gather(*tasks)
67
+
68
+ problem_files = {}
69
+ for (url, problem_name, filename, cached_filename), path in zip(files, results):
70
+ if path:
71
+ if post_process is not None:
72
+ path = post_process(path)
73
+ problem_files[problem_name] = path
74
+
75
+ return problem_files
76
+
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import asyncio
4
+ import sys
5
+ import argparse
6
+ from typing import Dict, List, Optional, Tuple
7
+ from pathlib import Path
8
+
9
+ from gsimplex.benchmarks.downloader import Downloader
10
+ from gsimplex.benchmarks.netlib_emps import expand_mps
11
+
12
+ class NetLibDownloader(Downloader):
13
+ BASE_URL = "https://www.netlib.org/lp/data/"
14
+
15
+ async def download_netlib_benchmarks_async(self, problem_names: List[str]) -> Dict[str, str]:
16
+ files: List[Tuple[str, str, str, Optional[str]]] = [
17
+ (f"{self.BASE_URL}{name}", name, f"netlib/{name}.mps.netlib", f"netlib/{name}.mps")
18
+ for name in problem_names
19
+ if name.strip()
20
+ ]
21
+ return await self.download_many_async(files, post_process=NetLibDownloader.post_process_download)
22
+
23
+ @staticmethod
24
+ def post_process_download(netlib_mps_file: str|Path) -> str:
25
+ downloaded_file = Path(netlib_mps_file)
26
+ if not downloaded_file.exists():
27
+ raise FileNotFoundError(f"Downloaded file path mismatch: {downloaded_file} not found!")
28
+
29
+ download_dir = downloaded_file.parent
30
+ target_file = download_dir / downloaded_file.name.removesuffix('.netlib')
31
+
32
+ expand_mps(str(downloaded_file), str(target_file))
33
+ downloaded_file.unlink()
34
+
35
+ assert target_file.exists()
36
+ return str(target_file)
37
+
38
+ async def download_netlib_benchmarks(dir: Optional[str] = None, quiet: bool = False) -> bool:
39
+ downloader = NetLibDownloader(benchmark_dir=dir, quiet=quiet)
40
+
41
+ # All Netlib problems
42
+ problem_names = [
43
+ "25fv47", "80bau3b", "adlittle", "afiro", "agg", "agg2", "agg3",
44
+ "bandm", "beaconfd", "blend", "bnl1", "bnl2", "boeing1", "boeing2",
45
+ "bore3d", "brandy", "capri", "cycle", "czprob", "d2q06c", "d6cube",
46
+ "degen2", "degen3", "dfl001", "e226", "etamacro", "fffff800",
47
+ "finnis", "fit1d", "fit1p", "fit2d", "fit2p", "forplan", "ganges",
48
+ "gfrd-pnc", "greenbea", "greenbeb", "grow7", "grow15", "grow22",
49
+ "israel", "kb2", "lotfi", "maros", "maros-r7", "modszk1", "nesm",
50
+ "perold", "pilot", "pilot.ja", "pilot.we", "pilot4", "pilot87",
51
+ "pilotnov", "recipe", "sc105", "sc205", "sc50a", "sc50b",
52
+ "scagr25", "scagr7", "scfxm1", "scfxm2", "scfxm3", "scorpion",
53
+ "scrs8", "scsd1", "scsd6", "scsd8", "sctap1", "sctap2", "sctap3",
54
+ "seba", "share1b", "share2b", "shell", "ship04l", "ship04s",
55
+ "ship08l", "ship08s", "ship12l", "ship12s", "sierra", "stair",
56
+ "standata", "standgub", "standmps", "stocfor1", "stocfor2",
57
+ "tuff", "vtp.base", "wood1p", "woodw"
58
+ ]
59
+
60
+ if not quiet:
61
+ print(f"Downloading {len(problem_names)} Netlib problems...")
62
+ results = await downloader.download_netlib_benchmarks_async(problem_names)
63
+ if not quiet:
64
+ print(f"Downloaded {len(results)} problems successfully")
65
+
66
+ return len(results) == len(problem_names)
67
+
68
+ def main():
69
+ parser = argparse.ArgumentParser(description="Download Netlib benchmarks")
70
+ parser.add_argument('--quiet', action='store_true', help='Run in quiet mode')
71
+ parser.add_argument('--dir', type=str, default=None, help='Directory to save benchmarks')
72
+ args = parser.parse_args()
73
+
74
+ esit = asyncio.run(download_netlib_benchmarks(quiet=args.quiet, dir=args.dir))
75
+ return 0 if esit else 1
76
+
77
+ if __name__ == "__main__":
78
+ sys.exit(main())