pipstools 0.2.1__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.
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.4
2
+ Name: pipstools
3
+ Version: 0.2.1
4
+ Summary: Add your description here
5
+ Author: Manuel Wetzel
6
+ Author-email: Manuel Wetzel <manuel.wetzel@dlr.de>
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Operating System :: Unix
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Dist: gamsapi
19
+ Requires-Dist: gamspy-base
20
+ Requires-Dist: highspy
21
+ Requires-Dist: matplotlib
22
+ Requires-Dist: mtkahypar ; sys_platform != 'win32'
23
+ Requires-Dist: networkx
24
+ Requires-Dist: numpy
25
+ Requires-Dist: polars
26
+ Requires-Dist: pyarrow
27
+ Requires-Dist: scipy
28
+ Requires-Dist: tqdm
29
+ Requires-Dist: typer
30
+ Requires-Dist: typing-extensions
31
+ Requires-Dist: gurobipy ; extra == 'gurobi'
32
+ Requires-Dist: mosek ; extra == 'mosek'
33
+ Requires-Dist: coverage ; extra == 'test'
34
+ Requires-Dist: pytest ; extra == 'test'
35
+ Requires-Dist: pytest-cov ; extra == 'test'
36
+ Requires-Python: >=3.11
37
+ Provides-Extra: gurobi
38
+ Provides-Extra: mosek
39
+ Provides-Extra: test
40
+ Description-Content-Type: text/markdown
41
+
42
+ # PIPS-IPM++ tools
43
+
44
+ `pipstools` is a collection of software tools that aims to give modellers easy access to the [massively parallel solver PIPS-IPM++](https://gitlab.com/pips-ipmpp).
45
+ The tools can be used to identify suitable block structures for linear optimisation problems, either based on domain information or
46
+ in a fully automated manner using hypergraph partitioning. Furthermore, the identified block structures can be scored to estimate
47
+ their expected performance with PIPS-IPM++, and can be visually inspected to help modellers improve the problem formulation.
48
+
49
+
50
+ ## Getting started
51
+
52
+ If you are using [uv](https://docs.astral.sh/uv) as your python package manager, annotation a problem file can be done with a single call:
53
+
54
+ ```
55
+ uvx git+https://git@gitlab.com/pips-ipmpp/pipstools annotate problem.mps
56
+ ```
57
+
58
+ For solving the problem with PIPS-IPM++ using e.g. a Docker imange please refer to the [solver documentation](https://gitlab.com/pips-ipmpp/pips-ipmpp#docker-image).
59
+
60
+ ## Annotation
61
+
62
+ Problem annotation describes the process of identifying underlying block strucutres which the massivly parallel solver PIPS-IPM++ can
63
+ exploit. Problem files can be provided either as .lp, .mps, or .gdx. If gurobipy is used as a file reader, also compressed
64
+ files (.mps.gz, .lp.gz) can be provided. `pipstools` currently support two differnt modes of identifying block strucutres, user-defined
65
+ annotations using domain knowledge and fully automated annotation using hypergraph partitioning.
66
+
67
+ ### Using variables names and regular expressions
68
+
69
+ Typcially, users are aware of the strucutre and interdependencies between variables and equation of their optimization problem. This can
70
+ provide a good starting point by specifying variables naming patterns to be used for the decomposition. In the [sample file](/../main/samples/remix.mps.gz)
71
+ of an energy system optimization model generated with the [REMix framework](https://gitlab.com/dlr-ve/esy/remix/framework) the temporal
72
+ dimension uses the naming pattern of `tm` followed by the integer of the specific timestep, e.g. `tm1` to `tm8760` for each timestep of a year.
73
+ This specific pattern can be encoded as the regular expression "tm([0-9]+)" which captures the integer representing the timestep to maintain
74
+ chronological ordering. Note, that using regular expressions for the annotation requires the problem file to include the actual variables
75
+ as generated with the specific mathematical programming tool used.
76
+
77
+ ```
78
+ pipstools annotate samples/remix.mps --regex-pattern=tm([0-9]+)
79
+ ```
80
+
81
+ ### Using hypergraph partitioning
82
+ For problems without variable names or prior knowledge about the underlying problem strucutre, a fully automated annotation can be used.
83
+ The algorithm first splits any dense variables (automatically detected by heuristics, can be changed using the `--densecol` argument), which
84
+ in the case of energy system optimization problems typically corrosponds to investment decisions. The remaining problem strucute is then
85
+ converted to a hypergraph using variables as nodes and constraints as hyperedges. Using the [Karlsruhe Hypergraph Partitioner](https://github.com/kahypar/mt-kahypar),
86
+ a balanced partitioning into `n` partitions for the variables is generated (corresponding to the `--blocks n` argument). Afterwards, the
87
+ constraints of the optimization problem are similarly paritioned taking into account the variable partions included in each constraint.
88
+
89
+ ```
90
+ pipstools annotate samples/remix.mps --method hypergraph --blocks 30
91
+ ```
92
+
93
+ ## Visualization
94
+
95
+ After the identification of block structures using the annotation functionality, the identified block structure can be explored visually
96
+ using the included visulisation tool. By default it will try to plot the matrix using a matplot display and include both the annotation
97
+ as well as variable and equation names. The matrix can be zoomed into, which further help refining the block structure and model formulation.
98
+ If no display is available, the matrix plot can also be redirected to an image using the `--output` argument.
99
+
100
+ ```
101
+ pipstools visualize samples/remix.gdx
102
+ ```
103
+
104
+ ## Scoring of block strucutres
105
+
106
+ Scoring of the annotation is currently a work-in-progress feature.
107
+
108
+
109
+ ## Authors
110
+ - Manuel Wetzel (German Aerospace Center, DLR)
111
+ - Stephen Maher (GAMS Software GmbH)
112
+
113
+ ## Acknoledgements
114
+ `pipstools` was developed as a deliverable of the PEREGRINE project, which was funded by
115
+ the German Federal Ministry for Economic Affairs and Energy under grant number 03EI1082A-B.
@@ -0,0 +1,74 @@
1
+ # PIPS-IPM++ tools
2
+
3
+ `pipstools` is a collection of software tools that aims to give modellers easy access to the [massively parallel solver PIPS-IPM++](https://gitlab.com/pips-ipmpp).
4
+ The tools can be used to identify suitable block structures for linear optimisation problems, either based on domain information or
5
+ in a fully automated manner using hypergraph partitioning. Furthermore, the identified block structures can be scored to estimate
6
+ their expected performance with PIPS-IPM++, and can be visually inspected to help modellers improve the problem formulation.
7
+
8
+
9
+ ## Getting started
10
+
11
+ If you are using [uv](https://docs.astral.sh/uv) as your python package manager, annotation a problem file can be done with a single call:
12
+
13
+ ```
14
+ uvx git+https://git@gitlab.com/pips-ipmpp/pipstools annotate problem.mps
15
+ ```
16
+
17
+ For solving the problem with PIPS-IPM++ using e.g. a Docker imange please refer to the [solver documentation](https://gitlab.com/pips-ipmpp/pips-ipmpp#docker-image).
18
+
19
+ ## Annotation
20
+
21
+ Problem annotation describes the process of identifying underlying block strucutres which the massivly parallel solver PIPS-IPM++ can
22
+ exploit. Problem files can be provided either as .lp, .mps, or .gdx. If gurobipy is used as a file reader, also compressed
23
+ files (.mps.gz, .lp.gz) can be provided. `pipstools` currently support two differnt modes of identifying block strucutres, user-defined
24
+ annotations using domain knowledge and fully automated annotation using hypergraph partitioning.
25
+
26
+ ### Using variables names and regular expressions
27
+
28
+ Typcially, users are aware of the strucutre and interdependencies between variables and equation of their optimization problem. This can
29
+ provide a good starting point by specifying variables naming patterns to be used for the decomposition. In the [sample file](/../main/samples/remix.mps.gz)
30
+ of an energy system optimization model generated with the [REMix framework](https://gitlab.com/dlr-ve/esy/remix/framework) the temporal
31
+ dimension uses the naming pattern of `tm` followed by the integer of the specific timestep, e.g. `tm1` to `tm8760` for each timestep of a year.
32
+ This specific pattern can be encoded as the regular expression "tm([0-9]+)" which captures the integer representing the timestep to maintain
33
+ chronological ordering. Note, that using regular expressions for the annotation requires the problem file to include the actual variables
34
+ as generated with the specific mathematical programming tool used.
35
+
36
+ ```
37
+ pipstools annotate samples/remix.mps --regex-pattern=tm([0-9]+)
38
+ ```
39
+
40
+ ### Using hypergraph partitioning
41
+ For problems without variable names or prior knowledge about the underlying problem strucutre, a fully automated annotation can be used.
42
+ The algorithm first splits any dense variables (automatically detected by heuristics, can be changed using the `--densecol` argument), which
43
+ in the case of energy system optimization problems typically corrosponds to investment decisions. The remaining problem strucute is then
44
+ converted to a hypergraph using variables as nodes and constraints as hyperedges. Using the [Karlsruhe Hypergraph Partitioner](https://github.com/kahypar/mt-kahypar),
45
+ a balanced partitioning into `n` partitions for the variables is generated (corresponding to the `--blocks n` argument). Afterwards, the
46
+ constraints of the optimization problem are similarly paritioned taking into account the variable partions included in each constraint.
47
+
48
+ ```
49
+ pipstools annotate samples/remix.mps --method hypergraph --blocks 30
50
+ ```
51
+
52
+ ## Visualization
53
+
54
+ After the identification of block structures using the annotation functionality, the identified block structure can be explored visually
55
+ using the included visulisation tool. By default it will try to plot the matrix using a matplot display and include both the annotation
56
+ as well as variable and equation names. The matrix can be zoomed into, which further help refining the block structure and model formulation.
57
+ If no display is available, the matrix plot can also be redirected to an image using the `--output` argument.
58
+
59
+ ```
60
+ pipstools visualize samples/remix.gdx
61
+ ```
62
+
63
+ ## Scoring of block strucutres
64
+
65
+ Scoring of the annotation is currently a work-in-progress feature.
66
+
67
+
68
+ ## Authors
69
+ - Manuel Wetzel (German Aerospace Center, DLR)
70
+ - Stephen Maher (GAMS Software GmbH)
71
+
72
+ ## Acknoledgements
73
+ `pipstools` was developed as a deliverable of the PEREGRINE project, which was funded by
74
+ the German Federal Ministry for Economic Affairs and Energy under grant number 03EI1082A-B.
@@ -0,0 +1,74 @@
1
+ [project]
2
+ name = "pipstools"
3
+ version = "0.2.1"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ classifiers = [
8
+ "Development Status :: 4 - Beta",
9
+ "Operating System :: Unix",
10
+ "Intended Audience :: Science/Research",
11
+ "Programming Language :: Python",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3 :: Only",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Topic :: Scientific/Engineering",
18
+ ]
19
+ requires-python = ">=3.11"
20
+ dependencies = [
21
+ "gamsapi",
22
+ "gamspy_base",
23
+ "highspy",
24
+ "matplotlib",
25
+ "mtkahypar ; platform_system != 'Windows'",
26
+ "networkx",
27
+ "numpy",
28
+ "polars",
29
+ "pyarrow",
30
+ "scipy",
31
+ "tqdm",
32
+ "typer",
33
+ "typing-extensions",
34
+ ]
35
+
36
+ [[project.authors]]
37
+ name = "Manuel Wetzel"
38
+ email = "manuel.wetzel@dlr.de"
39
+
40
+ [project.optional-dependencies]
41
+ test = [
42
+ "coverage",
43
+ "pytest",
44
+ "pytest-cov",
45
+ ]
46
+ gurobi = ["gurobipy"]
47
+ mosek = ["mosek"]
48
+
49
+ [project.scripts]
50
+ pipstools = "pipstools.cli:app"
51
+
52
+ [build-system]
53
+ requires = ["uv_build>=0.12.7,<0.13.0"]
54
+ build-backend = "uv_build"
55
+
56
+ [tool.uv]
57
+ package = true
58
+
59
+ [tool.ruff]
60
+ line-length = 100
61
+ fix = true
62
+
63
+ [tool.ruff.lint]
64
+ select = [
65
+ "E",
66
+ "F",
67
+ "I",
68
+ ]
69
+ ignore = ["E501"]
70
+
71
+ [tool.ruff.format]
72
+ quote-style = "double"
73
+ indent-style = "space"
74
+ docstring-code-format = true
@@ -0,0 +1,65 @@
1
+ [project]
2
+ name = "pipstools"
3
+ version = "0.2.1"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [{ name = "Manuel Wetzel", email = "manuel.wetzel@dlr.de" }]
8
+ classifiers = [
9
+ "Development Status :: 4 - Beta",
10
+ "Operating System :: Unix",
11
+ "Intended Audience :: Science/Research",
12
+ "Programming Language :: Python",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Scientific/Engineering",
19
+ ]
20
+
21
+ requires-python = ">=3.11"
22
+ dependencies = [
23
+ "gamsapi",
24
+ "gamspy_base",
25
+ "highspy",
26
+ "matplotlib",
27
+ "mtkahypar ; platform_system != 'Windows'",
28
+ "networkx",
29
+ "numpy",
30
+ "polars",
31
+ "pyarrow",
32
+ "scipy",
33
+ "tqdm",
34
+ "typer",
35
+ "typing-extensions",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ test = ["coverage", "pytest", "pytest-cov"]
40
+
41
+ gurobi = ["gurobipy"]
42
+ mosek = ["mosek"]
43
+
44
+ [project.scripts]
45
+ pipstools = "pipstools.cli:app"
46
+
47
+ [build-system]
48
+ requires = ["uv_build>=0.12.7,<0.13.0"]
49
+ build-backend = "uv_build"
50
+
51
+ [tool.uv]
52
+ package = true
53
+
54
+ [tool.ruff]
55
+ line-length = 100
56
+ fix = true
57
+
58
+ [tool.ruff.lint]
59
+ select = ["E", "F", "I"]
60
+ ignore = ["E501"]
61
+
62
+ [tool.ruff.format]
63
+ quote-style = "double"
64
+ indent-style = "space"
65
+ docstring-code-format = true
@@ -0,0 +1,5 @@
1
+ from .annotation import annotate_problem as annotate_problem
2
+ from .conversion import convert_problem as convert_problem
3
+ from .scoring import score_problem as score_problem
4
+ from .validation import validate_problem as validate_problem
5
+ from .visualisation import visualize_problem as visualize_problem
@@ -0,0 +1,204 @@
1
+ import gzip
2
+ import importlib.util
3
+ import shutil
4
+ from pathlib import Path
5
+ from time import perf_counter
6
+
7
+ from pipstools.io import read_gdx, read_mps, read_parquet, write_gdx, write_parquet
8
+ from pipstools.partitioning import get_partitions
9
+ from pipstools.scoring import get_score
10
+ from pipstools.utils import get_stem
11
+
12
+
13
+ def annotate_problem(
14
+ f_input,
15
+ k,
16
+ f_output=None,
17
+ fileformat="gdx",
18
+ var_dense=None,
19
+ equ_dense=None,
20
+ presolve=False,
21
+ mpsreader=None,
22
+ method="hypergraph",
23
+ hypergraph="col",
24
+ hg_objective="soed",
25
+ regex_pattern=None,
26
+ regex_order=None,
27
+ regex_mapfile=None,
28
+ dec=None,
29
+ write_names=True,
30
+ write_uels=True,
31
+ distributed=False,
32
+ vcycles=0,
33
+ ):
34
+ start = perf_counter()
35
+ if isinstance(f_input, str):
36
+ f_input = Path(f_input)
37
+
38
+ if var_dense is not None and var_dense < 0:
39
+ var_dense = None
40
+
41
+ if equ_dense is not None and equ_dense < 0:
42
+ var_dense = None
43
+
44
+ if mpsreader is None:
45
+ lib_gurobi = importlib.util.find_spec("gurobipy")
46
+ if lib_gurobi is not None:
47
+ mpsreader = "gurobi"
48
+ else:
49
+ mpsreader = "highs"
50
+ print(f"Selected {mpsreader} as default mps reader.")
51
+
52
+ if k == 1 and method.lower() != "oneblock":
53
+ print('Specificed partitioning into a single block, switching method to "oneblock"')
54
+ method = "oneblock"
55
+
56
+ if regex_pattern is not None:
57
+ print('Specificed regex pattern, switching method to "regex"')
58
+ method = "regex"
59
+
60
+ if dec is not None:
61
+ print('Specificed .dec file, switching method to "dec"')
62
+ method = "dec"
63
+
64
+ # Check if valid hypergraph arguments are provided
65
+ if method == "hypergraph":
66
+ if hypergraph.lower() not in ["col", "row", "colrow", "rowcol"]:
67
+ raise IOError(
68
+ f"Hypergraph type {hypergraph} is not available. "
69
+ "Use either 'col', 'row', 'colrow', or 'rowcol'"
70
+ )
71
+ if hg_objective.lower() not in ["cut", "km1", "soed"]:
72
+ raise IOError(
73
+ f"Hypergraph objective {hg_objective} is not available. "
74
+ "Use either 'cut', 'km1', or 'soed'"
75
+ )
76
+
77
+ # Read in file and extract A, vars, cols
78
+ match f_input.suffix.lower():
79
+ case ".gdx":
80
+ A, cols, rows, objcol, objrow, objcoef, objjacval = read_gdx(f_input=f_input)
81
+ case ".parquet":
82
+ A, cols, rows, objcol, objrow, objcoef, objjacval = read_parquet(f_input=f_input)
83
+ case ".lp" | ".mps" | ".gz" | ".bz2":
84
+ compressed_input = f_input.suffix.lower() in [".gz", ".bz2"]
85
+
86
+ # File needs to be compresses lp or mps
87
+ if compressed_input:
88
+ suffix_uncomp = f_input.stem.lower()
89
+ if not suffix_uncomp.endswith(".lp") and not suffix_uncomp.endswith(".mps"):
90
+ raise (
91
+ IOError(
92
+ "Can only read compressed files of type .lp and .mps, "
93
+ f"not {f_input.suffix}"
94
+ )
95
+ )
96
+
97
+ # For highspy manually decompress file and delete later
98
+ remove_decompressed = False
99
+ if compressed_input and mpsreader == "highs":
100
+ print(f"Automatically decompressing problem file {f_input.as_posix()} for highs")
101
+ if f_input.with_suffix("").exists():
102
+ raise (
103
+ IOError(
104
+ f"Cannot automatically decompress file {f_input.as_posix()}, since "
105
+ "a file with the target name already exists!"
106
+ )
107
+ )
108
+ with gzip.open(f_input, "rb") as f_in:
109
+ with open(f_input.with_suffix(""), "wb") as f_out:
110
+ shutil.copyfileobj(f_in, f_out)
111
+ f_input = f_input.with_suffix("")
112
+ remove_decompressed = True
113
+
114
+ # Read in mps content
115
+ A, cols, rows, objcol, objrow, objcoef = read_mps(
116
+ f_input=f_input,
117
+ presolve=presolve,
118
+ read_names=write_names or method == "regex",
119
+ mpsreader=mpsreader,
120
+ )
121
+ objjacval = 1.0
122
+
123
+ # Remove temporary decompressed file again
124
+ if remove_decompressed:
125
+ print(f"Automatically removing decompressed file {f_input.as_posix()}")
126
+ f_input.unlink()
127
+ case _:
128
+ raise (IOError(f"No reader implemented for filetype {f_input.suffix}"))
129
+
130
+ # Check if kahypar is available
131
+ if method == "hypergraph":
132
+ lib_mkkahypar = importlib.util.find_spec("mtkahypar")
133
+ if lib_mkkahypar is None:
134
+ raise ImportError(
135
+ "Hypergraph partitioning specified, but library mtkahypar not available."
136
+ )
137
+
138
+ # Partition by regex, hypergraph or dense elements
139
+ cols, rows = get_partitions(
140
+ A=A,
141
+ cols=cols,
142
+ rows=rows,
143
+ method=method,
144
+ hypergraph=hypergraph,
145
+ hg_objective=hg_objective,
146
+ k=k,
147
+ var_dense=var_dense,
148
+ equ_dense=equ_dense,
149
+ regex_pattern=regex_pattern,
150
+ regex_order=regex_order,
151
+ regex_mapfile=regex_mapfile,
152
+ dec=dec,
153
+ vcycles=vcycles,
154
+ objcol=objcol,
155
+ )
156
+
157
+ # Derive stem for output
158
+ if f_output is None:
159
+ stem, suffix = get_stem(f_input)
160
+ else:
161
+ stem, suffix = get_stem(f_output)
162
+ if suffix is not None:
163
+ fileformat = suffix.lstrip(".")
164
+
165
+ f_output = Path(f"{stem}_{len(set(cols['partition'].unique()) - {1})}b")
166
+ f_output.parent.mkdir(parents=True, exist_ok=True)
167
+
168
+ # Get scoring for annotation
169
+ get_score(A, cols, rows)
170
+
171
+ # Write out annotated gdx file
172
+ match fileformat.lower():
173
+ case "gdx":
174
+ write_gdx(
175
+ f_input=f_input,
176
+ f_output=f_output,
177
+ A=A,
178
+ cols=cols,
179
+ rows=rows,
180
+ objcol=objcol,
181
+ objrow=objrow,
182
+ objcoef=objcoef,
183
+ objjacval=objjacval,
184
+ write_names=write_names,
185
+ write_uels=write_uels,
186
+ distributed=distributed,
187
+ )
188
+ case "parquet":
189
+ write_parquet(
190
+ f_input=f_input,
191
+ f_output=f_output,
192
+ A=A,
193
+ cols=cols,
194
+ rows=rows,
195
+ objcol=objcol,
196
+ objrow=objrow,
197
+ objcoef=objcoef,
198
+ objjacval=objjacval,
199
+ )
200
+ case _:
201
+ raise (IOError(f"No method implemented for output format {fileformat}"))
202
+
203
+ stop = perf_counter()
204
+ print(f"Annotated problem in {(stop - start):.2f} seconds")