Skip to main content

pyutils27 (cgshop2027-pyutils)

Utilities for working with CG:SHOP 2027 instances & solutions. This is a pure Python package containing a parser/verifier for our instance format, solution format and a verifier for solutions.


Installation

pip install cgshop2027-pyutils

The current state of main, ahead of the latest release:

pip install git+https://github.com/CG-SHOP/pyutils27

From a local clone (editable, for development):

git clone https://github.com/CG-SHOP/pyutils27
cd pyutils27
pip install -e ".[dev]"

Layout

src/cgshop2027_pyutils/
├── schemas/            # Pydantic models for instances & solutions
├── io/                 # reading instances & solutions from paths or file objects
├── zip/                # safe reading/writing of instance & solution archives
├── instance_database/  # query instances from a folder or a zip
├── grid.py             # cell-set algebra on the integer grid
├── verify.py           # checking a solution against its instance
└── visualize.py        # matplotlib plots and animations
tests/                  # pytest test suite

Usage

Verifying a solution against its instance:

from cgshop2027_pyutils.io import read_instance, read_solution
from cgshop2027_pyutils.verify import check_for_errors

errors = check_for_errors(
    read_instance("demo.instance.json"),
    read_solution("demo.solution.json"),
)

check_for_errors returns a list of human-readable messages; an empty list means the solution is feasible. When checking many solutions for the same instance, use SolutionValidator(instance) directly so that the instance-derived precomputation is only paid once.

A solution consists of exactly number_of_cutters tours, one per cutter:

solution = CGSHOP2027Solution(
    instance_uid="demo",
    tours=[{"x": [0, 3, 3, 0], "y": [0, 0, 1, 1]}, {"x": [7], "y": [7]}],
)

Each tour lists the points the cutter's center passes through and is closed implicitly, so the cutter travels from the last point back to the first one. Consecutive points must be axis-parallel; a point may be visited again later, but not immediately again. A tour of a single point is a cutter that stays where it is. Cutters do not interfere with each other and may leave the region; all that is required is that together they sweep the whole region.

The objective is solution.max_tour_length, the length of the longest tour, and it is to be minimized.

Bundling instances or solutions into an archive:

from cgshop2027_pyutils.zip import ZipWriter

with ZipWriter("solutions.zip") as writer:
    for solution in solutions:
        writer.add_solution(solution)

Reading a submitted archive, with size, CRC and path-traversal checks:

from cgshop2027_pyutils.zip import BadSolutionFile, ZipSolutionIterator, ZipReaderError

try:
    for solution in ZipSolutionIterator("solutions.zip"):
        print(solution.instance_uid)
except (ZipReaderError, BadSolutionFile) as e:
    print(f"Rejected: {e}")

example_zip.ipynb walks the whole submission path end to end: reading the instances, checking solutions against them, packing an archive, and what a rejection looks like. It runs on the two archives next to it: example_instances.zip, a copy of the published example instances, and example_solutions.zip, one feasible solution for each of them to check against and to beat.

Looking instances up by name, from either a folder or a zip:

from cgshop2027_pyutils.instance_database import InstanceDatabase

db = InstanceDatabase("instances.zip")
instance = db["demo"]
for instance in db:
    ...

Plotting an instance, a solution, or an animation of the cutters at work:

from cgshop2027_pyutils.visualize import (
    create_instance_plot,
    create_solution_animation,
    create_solution_plot,
)

create_instance_plot(instance).savefig("instance.png")
create_solution_plot(instance, solution).savefig("solution.png")

animation = create_solution_animation(instance, solution)
animation.save("solution.gif", writer="pillow")

The solution plot draws one tour per cutter, shades the swept area and marks any cells left uncovered in red. The animation strides through the tours to fit max_frames (300 by default), filling in coverage as the cutters move; pass step to control the stride yourself.

Both draw the swept area in one flat colour by default. Pass coverage="shaded" to have each cutter shade the area it sweeps in its own colour instead, graded by how often it passed over each cell: washed out towards white where it went by once, deepened towards black over the cell it worked hardest. All cutters share one scale, so the same shade means the same number of passes whichever cutter drew it, and where two of them sweep the same cell their fills blend. This needs a count grid per cutter, so on large instances it is noticeably slower and uses more memory, increasingly so with more cutters.

create_solution_plot(instance, solution, coverage="shaded").savefig("shaded.png")

All three take bare=True, which drops the axes, the grid and every title and trims the figure to the drawing, for a picture meant for a paper or a slide rather than for debugging:

create_solution_plot(instance, solution, bare=True).savefig("solution.pdf")

Instance files must be named NAME.instance.json. A lookup key may be the bare name, the file name, or a path to it: db["demo"], db["demo.instance.json"] and db["batch1/demo.instance.json"] all resolve to the same instance. Note that every .json file in the folder or archive is treated as an instance, so keep solutions in a separate location.


Development

Set up the tooling (linting & formatting run through pre-commit):

pip install -e ".[dev]"
pre-commit install

Run the checks manually:

ruff check .
ruff format .
pytest

Linting, formatting and import sorting are all handled by ruff; its configuration lives in pyproject.toml.


Contributing

Issues & PRs welcome. Please:

  1. Add/adjust unit tests for new behavior
  2. Keep the public API documented here
  3. Run the test suite before submitting

Changelog

  • 0.2.0 (2026-10-16): Incorporated a faster and more memory-efficient verification routine, with a fallback to the old routine in case of unusually large coordinates, in addition to stricter enforced limits on the coordinate sizes in tours. The solution plot and the animation now take coverage="shaded" as optional argument, which draws coverage per cutter and shaded by how often each cell was passed over; the default, "flat", keeps the single flat area (it's a bit faster and uses less memory, looking less nice as a trade-off). Adds CellCounts and dilate_counts to grid.py and makes SolutionValidator.reachable_anchors public.
  • 0.1.0 (2026-08-25): First release. Instance and solution schemas, the solution verifier, archive reading and writing, the instance database, and plotting and animation.

License

MIT, see LICENSE.

Metadata

Release files for cgshop2027-pyutils 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cgshop2027-pyutils 0.2.0
File Size Uploaded
cgshop2027_pyutils-0.2.0.tar.gz 57.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cgshop2027-pyutils 0.2.0
File Interpreter ABI Platform
cgshop2027_pyutils-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 94.5 kB

Release files / cgshop2027_pyutils-0.2.0.tar.gz

Download URL cgshop2027_pyutils-0.2.0.tar.gz
Size 57.0 kB
Tags Source
SHA-256 checksum
How to use checksums
95c2603eb359e69e00f22edd2b648555b47a29ab9de232f036ad80d4c6f165de
BLAKE2b-256 checksum
How to use checksums
04f926952688f147372eee955b8dc7b1910175195bab107f1a24c0422f8173fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / cgshop2027_pyutils-0.2.0-py3-none-any.whl

Download URL cgshop2027_pyutils-0.2.0-py3-none-any.whl
Size 37.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fc7c9bbcf6a5361fd390f875afb182adf594c3c0cd7f9efe632cb6c5309911cd
BLAKE2b-256 checksum
How to use checksums
a6b0bad7e636bf6daafc26af432749f22b9d038ea1b49bdffc39934d53e7903b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page