cgshop2027-pyutils 0.1.0__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.
- cgshop2027_pyutils-0.1.0/LICENSE +21 -0
- cgshop2027_pyutils-0.1.0/MANIFEST.in +4 -0
- cgshop2027_pyutils-0.1.0/PKG-INFO +232 -0
- cgshop2027_pyutils-0.1.0/README.md +205 -0
- cgshop2027_pyutils-0.1.0/pyproject.toml +90 -0
- cgshop2027_pyutils-0.1.0/setup.cfg +4 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/__init__.py +3 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/grid.py +252 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/instance_database/__init__.py +7 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/instance_database/instance_base_database.py +86 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/instance_database/instance_database.py +73 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/instance_database/instance_file_database.py +81 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/instance_database/instance_zip_database.py +77 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/io/__init__.py +54 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/schemas/__init__.py +9 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/schemas/instance.py +199 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/schemas/solution.py +171 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/verify.py +153 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/visualize.py +504 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/zip/__init__.py +9 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/zip/zip_processor.py +121 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/zip/zip_reader_errors.py +96 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils/zip/zip_writer.py +52 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils.egg-info/PKG-INFO +232 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils.egg-info/SOURCES.txt +35 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils.egg-info/dependency_links.txt +1 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils.egg-info/requires.txt +12 -0
- cgshop2027_pyutils-0.1.0/src/cgshop2027_pyutils.egg-info/top_level.txt +1 -0
- cgshop2027_pyutils-0.1.0/tests/conftest.py +79 -0
- cgshop2027_pyutils-0.1.0/tests/test_grid.py +150 -0
- cgshop2027_pyutils-0.1.0/tests/test_imports.py +64 -0
- cgshop2027_pyutils-0.1.0/tests/test_instance_database.py +159 -0
- cgshop2027_pyutils-0.1.0/tests/test_io.py +32 -0
- cgshop2027_pyutils-0.1.0/tests/test_schemas.py +655 -0
- cgshop2027_pyutils-0.1.0/tests/test_verify.py +341 -0
- cgshop2027_pyutils-0.1.0/tests/test_visualize.py +389 -0
- cgshop2027_pyutils-0.1.0/tests/test_zip.py +218 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Technische Universität Braunschweig, Algorithms Group, Phillip Keldenich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cgshop2027-pyutils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utilities for verifying solutions of the CG:SHOP 2027 Competition.
|
|
5
|
+
Author: Phillip Keldenich
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/CG-SHOP/pyutils27
|
|
8
|
+
Project-URL: Issues, https://github.com/CG-SHOP/pyutils27/issues
|
|
9
|
+
Keywords: cgshop,computational geometry,optimization challenge
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: matplotlib>=3.6
|
|
18
|
+
Requires-Dist: numpy>=1.24
|
|
19
|
+
Requires-Dist: pydantic>=2.0.0
|
|
20
|
+
Requires-Dist: shapely>=2.1.2
|
|
21
|
+
Requires-Dist: typing_extensions>=4.0.0; python_version < "3.12"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# pyutils27 (cgshop2027-pyutils)
|
|
29
|
+
|
|
30
|
+
Utilities for working with CG:SHOP 2027 instances & solutions. This is a pure
|
|
31
|
+
Python package containing a parser/verifier for our instance format, solution
|
|
32
|
+
format and a verifier for solutions.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install cgshop2027-pyutils
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The current state of `main`, ahead of the latest release:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install git+https://github.com/CG-SHOP/pyutils27
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
From a local clone (editable, for development):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/CG-SHOP/pyutils27
|
|
52
|
+
cd pyutils27
|
|
53
|
+
pip install -e ".[dev]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Layout
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
src/cgshop2027_pyutils/
|
|
62
|
+
├── schemas/ # Pydantic models for instances & solutions
|
|
63
|
+
├── io/ # reading instances & solutions from paths or file objects
|
|
64
|
+
├── zip/ # safe reading/writing of instance & solution archives
|
|
65
|
+
├── instance_database/ # query instances from a folder or a zip
|
|
66
|
+
├── grid.py # cell-set algebra on the integer grid
|
|
67
|
+
├── verify.py # checking a solution against its instance
|
|
68
|
+
└── visualize.py # matplotlib plots and animations
|
|
69
|
+
tests/ # pytest test suite
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
Verifying a solution against its instance:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from cgshop2027_pyutils.io import read_instance, read_solution
|
|
80
|
+
from cgshop2027_pyutils.verify import check_for_errors
|
|
81
|
+
|
|
82
|
+
errors = check_for_errors(
|
|
83
|
+
read_instance("demo.instance.json"),
|
|
84
|
+
read_solution("demo.solution.json"),
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`check_for_errors` returns a list of human-readable messages; an empty list
|
|
89
|
+
means the solution is feasible. When checking many solutions for the same
|
|
90
|
+
instance, use `SolutionValidator(instance)` directly so that the
|
|
91
|
+
instance-derived precomputation is only paid once.
|
|
92
|
+
|
|
93
|
+
A solution consists of exactly `number_of_cutters` tours, one per cutter:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
solution = CGSHOP2027Solution(
|
|
97
|
+
instance_uid="demo",
|
|
98
|
+
tours=[{"x": [0, 3, 3, 0], "y": [0, 0, 1, 1]}, {"x": [7], "y": [7]}],
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Each tour lists the points the cutter's _center_ passes through and is closed
|
|
103
|
+
implicitly, so the cutter travels from the last point back to the first one.
|
|
104
|
+
Consecutive points must be axis-parallel; a point may be visited again later,
|
|
105
|
+
but not immediately again. A tour of a single point is a cutter that stays where
|
|
106
|
+
it is. Cutters do not interfere with each other and may leave the region; all
|
|
107
|
+
that is required is that together they sweep the whole region.
|
|
108
|
+
|
|
109
|
+
The objective is `solution.max_tour_length`, the length of the longest tour, and
|
|
110
|
+
it is to be minimized.
|
|
111
|
+
|
|
112
|
+
Bundling instances or solutions into an archive:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from cgshop2027_pyutils.zip import ZipWriter
|
|
116
|
+
|
|
117
|
+
with ZipWriter("solutions.zip") as writer:
|
|
118
|
+
for solution in solutions:
|
|
119
|
+
writer.add_solution(solution)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Reading a submitted archive, with size, CRC and path-traversal checks:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from cgshop2027_pyutils.zip import BadSolutionFile, ZipSolutionIterator, ZipReaderError
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
for solution in ZipSolutionIterator("solutions.zip"):
|
|
129
|
+
print(solution.instance_uid)
|
|
130
|
+
except (ZipReaderError, BadSolutionFile) as e:
|
|
131
|
+
print(f"Rejected: {e}")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`example_zip.ipynb` walks the whole submission path end to end: reading the
|
|
135
|
+
instances, checking solutions against them, packing an archive, and what a
|
|
136
|
+
rejection looks like. It runs on the two archives next to it:
|
|
137
|
+
`example_instances.zip`, a copy of the published example instances, and
|
|
138
|
+
`example_solutions.zip`, one feasible solution for each of them to check against
|
|
139
|
+
and to beat.
|
|
140
|
+
|
|
141
|
+
Looking instances up by name, from either a folder or a zip:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from cgshop2027_pyutils.instance_database import InstanceDatabase
|
|
145
|
+
|
|
146
|
+
db = InstanceDatabase("instances.zip")
|
|
147
|
+
instance = db["demo"]
|
|
148
|
+
for instance in db:
|
|
149
|
+
...
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Plotting an instance, a solution, or an animation of the cutters at work:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from cgshop2027_pyutils.visualize import (
|
|
156
|
+
create_instance_plot,
|
|
157
|
+
create_solution_animation,
|
|
158
|
+
create_solution_plot,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
create_instance_plot(instance).savefig("instance.png")
|
|
162
|
+
create_solution_plot(instance, solution).savefig("solution.png")
|
|
163
|
+
|
|
164
|
+
animation = create_solution_animation(instance, solution)
|
|
165
|
+
animation.save("solution.gif", writer="pillow")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The solution plot shades the swept area, marks any cells left uncovered in red
|
|
169
|
+
and draws one tour per cutter. The animation strides through the tours to fit
|
|
170
|
+
`max_frames` (300 by default), filling in coverage as the cutters move; pass
|
|
171
|
+
`step` to control the stride yourself.
|
|
172
|
+
|
|
173
|
+
All three take `bare=True`, which drops the axes, the grid and every title and
|
|
174
|
+
trims the figure to the drawing, for a picture meant for a paper or a slide
|
|
175
|
+
rather than for debugging:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
create_solution_plot(instance, solution, bare=True).savefig("solution.pdf")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Instance files must be named `NAME.instance.json`. A lookup key may be the bare
|
|
182
|
+
name, the file name, or a path to it: `db["demo"]`, `db["demo.instance.json"]`
|
|
183
|
+
and `db["batch1/demo.instance.json"]` all resolve to the same instance. Note
|
|
184
|
+
that every `.json` file in the folder or archive is treated as an instance, so
|
|
185
|
+
keep solutions in a separate location.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
Set up the tooling (linting & formatting run through pre-commit):
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
pip install -e ".[dev]"
|
|
195
|
+
pre-commit install
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Run the checks manually:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
ruff check .
|
|
202
|
+
ruff format .
|
|
203
|
+
pytest
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Linting, formatting and import sorting are all handled by
|
|
207
|
+
[ruff](https://docs.astral.sh/ruff/); its configuration lives in
|
|
208
|
+
`pyproject.toml`.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Contributing
|
|
213
|
+
|
|
214
|
+
Issues & PRs welcome. Please:
|
|
215
|
+
|
|
216
|
+
1. Add/adjust unit tests for new behavior
|
|
217
|
+
2. Keep the public API documented here
|
|
218
|
+
3. Run the test suite before submitting
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Changelog
|
|
223
|
+
|
|
224
|
+
- **0.1.0** (2026-08-25): First release. Instance and solution schemas, the
|
|
225
|
+
solution verifier, archive reading and writing, the instance database, and
|
|
226
|
+
plotting and animation.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT, see `LICENSE`.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# pyutils27 (cgshop2027-pyutils)
|
|
2
|
+
|
|
3
|
+
Utilities for working with CG:SHOP 2027 instances & solutions. This is a pure
|
|
4
|
+
Python package containing a parser/verifier for our instance format, solution
|
|
5
|
+
format and a verifier for solutions.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install cgshop2027-pyutils
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The current state of `main`, ahead of the latest release:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install git+https://github.com/CG-SHOP/pyutils27
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
From a local clone (editable, for development):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/CG-SHOP/pyutils27
|
|
25
|
+
cd pyutils27
|
|
26
|
+
pip install -e ".[dev]"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Layout
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
src/cgshop2027_pyutils/
|
|
35
|
+
├── schemas/ # Pydantic models for instances & solutions
|
|
36
|
+
├── io/ # reading instances & solutions from paths or file objects
|
|
37
|
+
├── zip/ # safe reading/writing of instance & solution archives
|
|
38
|
+
├── instance_database/ # query instances from a folder or a zip
|
|
39
|
+
├── grid.py # cell-set algebra on the integer grid
|
|
40
|
+
├── verify.py # checking a solution against its instance
|
|
41
|
+
└── visualize.py # matplotlib plots and animations
|
|
42
|
+
tests/ # pytest test suite
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Verifying a solution against its instance:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from cgshop2027_pyutils.io import read_instance, read_solution
|
|
53
|
+
from cgshop2027_pyutils.verify import check_for_errors
|
|
54
|
+
|
|
55
|
+
errors = check_for_errors(
|
|
56
|
+
read_instance("demo.instance.json"),
|
|
57
|
+
read_solution("demo.solution.json"),
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`check_for_errors` returns a list of human-readable messages; an empty list
|
|
62
|
+
means the solution is feasible. When checking many solutions for the same
|
|
63
|
+
instance, use `SolutionValidator(instance)` directly so that the
|
|
64
|
+
instance-derived precomputation is only paid once.
|
|
65
|
+
|
|
66
|
+
A solution consists of exactly `number_of_cutters` tours, one per cutter:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
solution = CGSHOP2027Solution(
|
|
70
|
+
instance_uid="demo",
|
|
71
|
+
tours=[{"x": [0, 3, 3, 0], "y": [0, 0, 1, 1]}, {"x": [7], "y": [7]}],
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Each tour lists the points the cutter's _center_ passes through and is closed
|
|
76
|
+
implicitly, so the cutter travels from the last point back to the first one.
|
|
77
|
+
Consecutive points must be axis-parallel; a point may be visited again later,
|
|
78
|
+
but not immediately again. A tour of a single point is a cutter that stays where
|
|
79
|
+
it is. Cutters do not interfere with each other and may leave the region; all
|
|
80
|
+
that is required is that together they sweep the whole region.
|
|
81
|
+
|
|
82
|
+
The objective is `solution.max_tour_length`, the length of the longest tour, and
|
|
83
|
+
it is to be minimized.
|
|
84
|
+
|
|
85
|
+
Bundling instances or solutions into an archive:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from cgshop2027_pyutils.zip import ZipWriter
|
|
89
|
+
|
|
90
|
+
with ZipWriter("solutions.zip") as writer:
|
|
91
|
+
for solution in solutions:
|
|
92
|
+
writer.add_solution(solution)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Reading a submitted archive, with size, CRC and path-traversal checks:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from cgshop2027_pyutils.zip import BadSolutionFile, ZipSolutionIterator, ZipReaderError
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
for solution in ZipSolutionIterator("solutions.zip"):
|
|
102
|
+
print(solution.instance_uid)
|
|
103
|
+
except (ZipReaderError, BadSolutionFile) as e:
|
|
104
|
+
print(f"Rejected: {e}")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`example_zip.ipynb` walks the whole submission path end to end: reading the
|
|
108
|
+
instances, checking solutions against them, packing an archive, and what a
|
|
109
|
+
rejection looks like. It runs on the two archives next to it:
|
|
110
|
+
`example_instances.zip`, a copy of the published example instances, and
|
|
111
|
+
`example_solutions.zip`, one feasible solution for each of them to check against
|
|
112
|
+
and to beat.
|
|
113
|
+
|
|
114
|
+
Looking instances up by name, from either a folder or a zip:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from cgshop2027_pyutils.instance_database import InstanceDatabase
|
|
118
|
+
|
|
119
|
+
db = InstanceDatabase("instances.zip")
|
|
120
|
+
instance = db["demo"]
|
|
121
|
+
for instance in db:
|
|
122
|
+
...
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Plotting an instance, a solution, or an animation of the cutters at work:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from cgshop2027_pyutils.visualize import (
|
|
129
|
+
create_instance_plot,
|
|
130
|
+
create_solution_animation,
|
|
131
|
+
create_solution_plot,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
create_instance_plot(instance).savefig("instance.png")
|
|
135
|
+
create_solution_plot(instance, solution).savefig("solution.png")
|
|
136
|
+
|
|
137
|
+
animation = create_solution_animation(instance, solution)
|
|
138
|
+
animation.save("solution.gif", writer="pillow")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The solution plot shades the swept area, marks any cells left uncovered in red
|
|
142
|
+
and draws one tour per cutter. The animation strides through the tours to fit
|
|
143
|
+
`max_frames` (300 by default), filling in coverage as the cutters move; pass
|
|
144
|
+
`step` to control the stride yourself.
|
|
145
|
+
|
|
146
|
+
All three take `bare=True`, which drops the axes, the grid and every title and
|
|
147
|
+
trims the figure to the drawing, for a picture meant for a paper or a slide
|
|
148
|
+
rather than for debugging:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
create_solution_plot(instance, solution, bare=True).savefig("solution.pdf")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Instance files must be named `NAME.instance.json`. A lookup key may be the bare
|
|
155
|
+
name, the file name, or a path to it: `db["demo"]`, `db["demo.instance.json"]`
|
|
156
|
+
and `db["batch1/demo.instance.json"]` all resolve to the same instance. Note
|
|
157
|
+
that every `.json` file in the folder or archive is treated as an instance, so
|
|
158
|
+
keep solutions in a separate location.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
Set up the tooling (linting & formatting run through pre-commit):
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pip install -e ".[dev]"
|
|
168
|
+
pre-commit install
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Run the checks manually:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
ruff check .
|
|
175
|
+
ruff format .
|
|
176
|
+
pytest
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Linting, formatting and import sorting are all handled by
|
|
180
|
+
[ruff](https://docs.astral.sh/ruff/); its configuration lives in
|
|
181
|
+
`pyproject.toml`.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Contributing
|
|
186
|
+
|
|
187
|
+
Issues & PRs welcome. Please:
|
|
188
|
+
|
|
189
|
+
1. Add/adjust unit tests for new behavior
|
|
190
|
+
2. Keep the public API documented here
|
|
191
|
+
3. Run the test suite before submitting
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Changelog
|
|
196
|
+
|
|
197
|
+
- **0.1.0** (2026-08-25): First release. Instance and solution schemas, the
|
|
198
|
+
solution verifier, archive reading and writing, the instance database, and
|
|
199
|
+
plotting and animation.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT, see `LICENSE`.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cgshop2027-pyutils"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Utilities for verifying solutions of the CG:SHOP 2027 Competition."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Phillip Keldenich" }]
|
|
14
|
+
keywords = ["cgshop", "computational geometry", "optimization challenge"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"matplotlib>=3.6", # `width_ratios` on `plt.subplots`
|
|
23
|
+
"numpy>=1.24",
|
|
24
|
+
"pydantic>=2.0.0",
|
|
25
|
+
"shapely>=2.1.2",
|
|
26
|
+
# For `override` (3.12+) and `Self` (3.11+).
|
|
27
|
+
"typing_extensions>=4.0.0; python_version<'3.12'",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = ["pre-commit", "pytest", "ruff"]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/CG-SHOP/pyutils27"
|
|
35
|
+
Issues = "https://github.com/CG-SHOP/pyutils27/issues"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
filterwarnings = [
|
|
43
|
+
# The animation tests inspect frames without ever rendering the animation.
|
|
44
|
+
"ignore:Animation was deleted without rendering anything:UserWarning",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
target-version = "py310"
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = [
|
|
52
|
+
"A", # builtin shadowing
|
|
53
|
+
"ARG", # unused arguments
|
|
54
|
+
"B", # bugbear
|
|
55
|
+
"C4", # comprehensions
|
|
56
|
+
"E", # pycodestyle
|
|
57
|
+
"EM", # error messages (no string literals in raise)
|
|
58
|
+
"F", # pyflakes
|
|
59
|
+
"FA", # future annotations
|
|
60
|
+
"I", # import sorting
|
|
61
|
+
"N", # naming
|
|
62
|
+
"PERF", # performance
|
|
63
|
+
"PIE",
|
|
64
|
+
"PL", # pylint
|
|
65
|
+
"PT", # pytest style
|
|
66
|
+
"PTH", # use pathlib
|
|
67
|
+
"RET",
|
|
68
|
+
"RSE",
|
|
69
|
+
"RUF",
|
|
70
|
+
"SIM",
|
|
71
|
+
"UP", # pyupgrade
|
|
72
|
+
"W",
|
|
73
|
+
]
|
|
74
|
+
ignore = [
|
|
75
|
+
"E501", # line length is handled by the formatter
|
|
76
|
+
"PLR0913", # too many arguments
|
|
77
|
+
"PLR2004", # magic value comparison
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.per-file-ignores]
|
|
81
|
+
# The import tests import inside the test functions on purpose.
|
|
82
|
+
"tests/*" = ["ARG", "PLC0415", "PLR2004"]
|
|
83
|
+
# These exception names are part of the public API carried over from previous
|
|
84
|
+
# years; renaming them to an `Error` suffix would break existing consumers.
|
|
85
|
+
"src/cgshop2027_pyutils/zip/*" = ["N818"]
|
|
86
|
+
|
|
87
|
+
[tool.pyright]
|
|
88
|
+
reportUnusedCallResult = false
|
|
89
|
+
reportPrivateUsage = false
|
|
90
|
+
reportImplicitStringConcatenation = false
|