fastrectpack 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.
@@ -0,0 +1,5 @@
1
+ BasedOnStyle: Google
2
+ IndentWidth: 4
3
+ AccessModifierOffset: -4
4
+ ColumnLimit: 120
5
+ AllowShortIfStatementsOnASingleLine: WithoutElse
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{yml,yaml,toml,json}]
12
+ indent_size = 2
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
@@ -0,0 +1,2 @@
1
+ * text=auto eol=lf
2
+ *.png binary
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+ .hypothesis/
8
+ .pytest_cache/
9
+ .venv*/
@@ -0,0 +1,92 @@
1
+ # Benchmarks
2
+
3
+ ## Reproduce
4
+
5
+ ```
6
+ pip install .
7
+ python bench/run_bench.py # writes results/bench.json and results/bench.md
8
+ python tools/summarize.py # writes results/summary.md from every results/bench*.json
9
+ ```
10
+
11
+ `--quick` runs five small cases in about a minute. On Windows the script restricts itself to logical CPUs 0-13 by default (`--cpus all` turns that off); see [Noise](#noise-on-this-machine).
12
+
13
+ ## Environment
14
+
15
+ | | |
16
+ |---|---|
17
+ | Machine | Laptop, Intel Core i7-14700HX (20 cores, 28 threads; hybrid: fast and slow cores), 31.7 GB RAM |
18
+ | OS | Windows 11 Home, build 10.0.26100, "Balanced" power plan |
19
+ | Python | CPython 3.13.5 (Anaconda build, MSC v.1929, 64-bit) |
20
+ | Compiler | MSVC 19.44 (Visual Studio 2022), Release configuration, default optimisation flags |
21
+ | Original library | rectpack 0.2.2 |
22
+ | Build tools | pybind11 3.1.0, CMake 4.4.4, scikit-build-core (exact versions in `results/pip_freeze.txt`) |
23
+ | Date | 2026-11-10 |
24
+
25
+ ## What is measured
26
+
27
+ - **Operation:** build a packer with `newPacker()` defaults (offline mode, best-bin-fit, `MaxRectsBssf`, sort by area, rotation on), add all rectangles and bins, call `pack()`, read `rect_list()`. The whole call is timed, including Python-side setup.
28
+ - **Three variants, same inputs:**
29
+ 1. `original`: rectpack 0.2.2, untouched.
30
+ 2. `python_fixes`: the original with two pure-Python changes and no C++ (`bench/cheap_wins.py`).
31
+ 3. `native`: fastrectpack.
32
+ - **Workloads** (`bench/workloads.py`, fixed seeds, integer sizes):
33
+ - `random`: sides uniform in 10-100, bins 1000x1000.
34
+ - `cutting`: 20 typical sheet-part sizes with heavy repetition, bins 2440x1220. Uses many bins.
35
+ - `sprites`: sides drawn from 16 to 256 in the usual texture steps, bins 2048x2048.
36
+ - `skewed`: 5% large pieces (200-600), the rest small (5-60), bins 1500x1500.
37
+ - **Procedure:** the variants are run interleaved in rotating order, so background load affects them alike. One warm-up round for cases up to 1,000 rectangles. Rounds: 7 up to 1,000 rectangles, 5 at 2,000, 3 at 5,000. Garbage is collected before each timed call.
38
+ - **Statistic:** best (minimum) time per variant, and speedup = best original time / best variant time. Background load only ever adds time, so the minimum is the closest estimate of the code's own cost. The table also shows the range of per-round speedups from the main run.
39
+ - **Parity:** each run hashes the full output of all three variants. A mismatch fails the script.
40
+
41
+ ## Results
42
+
43
+ Generated by `tools/summarize.py` into `results/summary.md`. "Best" is the minimum over every run recorded on this machine, which for the original is the least flattering choice for the speedup.
44
+
45
+ | Workload | Rectangles | Bins used | Original (best) | Python fixes (best) | C++ (best) | Speedup, Python fixes | Speedup, C++ | C++ speedup range across rounds of the main run | Output identical |
46
+ |---|---|---|---|---|---|---|---|---|---|
47
+ | random | 200 | 1 | 0.344 s | 0.114 s | 0.0032 s | 3.0x | 106x | 99x to 108x | yes |
48
+ | random | 500 | 2 | 0.944 s | 0.168 s | 0.0057 s | 5.6x | 164x | 69x to 165x | yes |
49
+ | random | 1,000 | 4 | 3.525 s | 0.473 s | 0.0144 s | 7.5x | 246x | 198x to 264x | yes |
50
+ | random | 2,000 | 7 | 7.703 s | 1.071 s | 0.0792 s | 7.2x | 97x | 115x to 219x | yes |
51
+ | random | 5,000 | 16 | 21.123 s | 3.738 s | 0.1364 s | 5.7x | 155x | 121x to 265x | yes |
52
+ | cutting | 500 | 27 | 0.036 s | 0.022 s | 0.0068 s | 1.7x | 5x | 5x to 5x | yes |
53
+ | cutting | 1,000 | 50 | 0.119 s | 0.066 s | 0.0221 s | 1.8x | 5x | 5x to 6x | yes |
54
+ | cutting | 2,000 | 100 | 0.415 s | 0.214 s | 0.0780 s | 1.9x | 5x | 5x to 6x | yes |
55
+ | sprites | 500 | 2 | 0.069 s | 0.032 s | 0.0029 s | 2.1x | 23x | 24x to 26x | yes |
56
+ | sprites | 1,000 | 3 | 0.487 s | 0.131 s | 0.0075 s | 3.7x | 65x | 67x to 70x | yes |
57
+ | sprites | 2,000 | 5 | 1.500 s | 0.322 s | 0.0177 s | 4.7x | 85x | 85x to 88x | yes |
58
+ | skewed | 500 | 3 | 0.684 s | 0.120 s | 0.0048 s | 5.7x | 141x | 135x to 144x | yes |
59
+ | skewed | 1,000 | 4 | 1.360 s | 0.243 s | 0.0102 s | 5.6x | 133x | 126x to 300x | yes |
60
+ | skewed | 2,000 | 11 | 2.197 s | 0.483 s | 0.0270 s | 4.5x | 82x | 78x to 187x | yes |
61
+
62
+ - C++ speedup: minimum 5.3x (cutting), maximum 246x (random, 1,000 rectangles), over 14 cases.
63
+ - Pure-Python fixes alone: 1.7x to 7.5x.
64
+ - All 14 outputs are identical across the three variants.
65
+
66
+ Where the two speedup columns disagree (for example random 2,000: 97x from best times, 115x to 219x within the main run), the lower figure is the safer one to quote. The disagreement comes from the noise described below.
67
+
68
+ ## Where the time went in the original
69
+
70
+ `bench/profile_original.py` (cProfile, so read the shares, not the seconds):
71
+
72
+ | Function | random, 1,000 | skewed, 1,000 |
73
+ |---|---|---|
74
+ | `_remove_duplicates` | 93.7% | 88.2% |
75
+ | `_select_position` | 3.6% | 7.6% |
76
+ | `_split` | 2.4% | 3.7% |
77
+
78
+ After every placement, `_remove_duplicates` compares every pair of free rectangles: 30.8 million `contains()` calls to place 1,000 rectangles in the random case, with a mean of 146.5 free rectangles per search.
79
+
80
+ ## Where the speedup comes from
81
+
82
+ 1. **Incremental duplicate removal (pure Python).** The free list never holds a rectangle that contains another once the step has run, so only pairs involving newly created pieces need checking. Measured alone: 1.0x to 7.1x (`results/phase3.json`).
83
+ 2. **Single fitness evaluation (pure Python).** The original computes each candidate's fitness twice. Together with step 1: 1.7x to 7.5x.
84
+ 3. **C++ port.** The same algorithm on plain structs instead of Python objects.
85
+
86
+ ## Noise on this machine
87
+
88
+ - **Hybrid CPU.** The same fixed loop takes 378-414 ms on logical CPUs 0-13 and 577-1,501 ms on CPUs 14-27 (`bench/probe_cores.py`). An unrestricted process drifts between them: the first baseline showed identical runs taking 21 s and 57 s. The benchmark therefore restricts itself to CPUs 0-13.
89
+ - **Slow phases.** Even on the fast cores, the machine drops into stretches where everything runs about 2.3x slower. Other background software was running during the first two full runs, but the third run, with it closed and the laptop on mains power, showed the same slow phases, so it is not the main cause. Thermal or power throttling is the likely explanation; I did not confirm it. Because variants are interleaved, a slow phase moves all three together: best-time speedup ratios agreed to roughly 10% between full runs, while absolute seconds did not.
90
+ - **Consequence.** Treat the speedup ratios as the result. Treat the absolute seconds as approximate. A re-run on a desktop, or a laptop that does not throttle, would firm them up.
91
+
92
+ Raw files: `results/bench.json` (main run, the third, with other software closed), `results/bench_run2.json` (second run), `results/bench_run1_single_core_pin.json` (first run, pinned to one core), `results/baseline.json` (original only), `results/phase3.json` (pure-Python steps). `results/baseline_unpinned_UNRELIABLE.json` is kept as evidence of the core-drift problem; its best times are included in the "best" columns only because lower original times make the speedup claim more conservative.
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses [Semantic Versioning](https://semver.org/).
4
+
5
+ ## [0.1.0] - not yet released
6
+
7
+ First version.
8
+
9
+ ### Added
10
+ - C++17 (pybind11) implementations of `MaxRectsBl`, `MaxRectsBssf`, `MaxRectsBaf` and `MaxRectsBlsf`, exposed as subclasses of the originals. Output is identical to `rectpack` 0.2.2.
11
+ - Fast path for plain Python `int` sizes up to 2^30. Any other input (floats, `Decimal`, numpy integers, `bool`, zero or negative sizes, larger integers) hands the object over to the original Python code for the rest of its life.
12
+ - `fastrectpack.newPacker`, same signature as `rectpack.newPacker`, defaulting to the accelerated algorithm. Everything else `rectpack` exports is re-exported unchanged.
13
+ - Test suite of 474 tests: step-by-step parity, edge cases, property-based tests, all 200 packer configurations that use a MaxRects algorithm.
14
+ - Benchmarks (`bench/`), raw results (`results/`) and a chart (`docs/`).
15
+ - CI: wheels for CPython 3.9 to 3.13 on Linux x86_64, Windows x86_64 and macOS arm64; AddressSanitizer and UndefinedBehaviorSanitizer on Linux.
@@ -0,0 +1,23 @@
1
+ cmake_minimum_required(VERSION 3.15...3.31)
2
+ project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_CXX_EXTENSIONS OFF)
7
+
8
+ find_package(pybind11 CONFIG REQUIRED)
9
+
10
+ pybind11_add_module(_native MODULE src/fastrectpack/_native.cpp)
11
+
12
+ # Optional sanitizer build for the correctness phase: pip install . -C cmake.define.FASTRECTPACK_SANITIZE=address
13
+ set(FASTRECTPACK_SANITIZE "" CACHE STRING "Sanitizers to enable (address, undefined, thread)")
14
+ if(FASTRECTPACK_SANITIZE)
15
+ if(MSVC)
16
+ target_compile_options(_native PRIVATE /fsanitize=${FASTRECTPACK_SANITIZE} /Zi)
17
+ else()
18
+ target_compile_options(_native PRIVATE -fsanitize=${FASTRECTPACK_SANITIZE} -fno-omit-frame-pointer -g)
19
+ target_link_options(_native PRIVATE -fsanitize=${FASTRECTPACK_SANITIZE})
20
+ endif()
21
+ endif()
22
+
23
+ install(TARGETS _native LIBRARY DESTINATION fastrectpack)
@@ -0,0 +1,47 @@
1
+ # Contributing
2
+
3
+ Thanks for looking. This project has one rule that matters more than the others: **fastrectpack must give exactly the same answers as the original `rectpack`.** Speed is the point, but only if the output is identical.
4
+
5
+ ## Set up
6
+
7
+ You need Python 3.9+, CMake 3.15+ and a C++17 compiler (MSVC 2019+, GCC 9+ or Clang 10+).
8
+
9
+ ```
10
+ python -m venv .venv
11
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
12
+ pip install ".[test]" numpy
13
+ pytest -q
14
+ ```
15
+
16
+ Re-run `pip install .` after changing `src/fastrectpack/_native.cpp`; the extension is rebuilt on install.
17
+
18
+ On Windows from Git Bash, set `CMAKE_GENERATOR="Visual Studio 17 2022"` first. A "Developer PowerShell for VS" prompt should not need it, but that has not been tested.
19
+
20
+ ## Before you open a pull request
21
+
22
+ 1. **Parity first.** The tests compare every placement and the full free-rectangle list against the original, step by step. If you change behaviour on purpose, say why in the PR; if a change alters output, the tests are meant to fail.
23
+ 2. **Add a test** for what you changed. For anything touching the C++, extend `tests/test_parity.py` or `tests/test_edge_cases.py`.
24
+ 3. **Claiming a speedup?** Include numbers from `python bench/run_bench.py`, with the hardware, and say how many runs they came from. Please quote the best-of ratio and show the spread. See `BENCHMARKS.md` for why.
25
+ 4. **Style.** `ruff check .` for Python and `clang-format -i src/fastrectpack/_native.cpp` for C++. CI checks both.
26
+ 5. **Changelog.** Add a line to `CHANGELOG.md`.
27
+
28
+ If a substantial part of a change was written with an AI assistant, please say so in the PR description.
29
+
30
+ ## Things worth knowing
31
+
32
+ - Quirks inherited from rectpack are **reproduced on purpose** so that output matches (see "Behaviour inherited from rectpack" in the README). Please do not fix them here; they belong upstream.
33
+ - The fast path only handles plain Python `int` sizes up to 2^30. Anything else falls back to the original code. Adding a float fast path is welcome, but must keep value types identical (`0` versus `0.0`), which is harder than it looks. See `docs/DESIGN.md`.
34
+ - Moving the bin-selection loop (`PackerBBF` and friends) into C++ is the most valuable open improvement. It would lift workloads that spread rectangles over many bins above the current ~5x.
35
+
36
+ ## Checks you can run
37
+
38
+ | What | Command |
39
+ |---|---|
40
+ | Tests | `pytest -q` |
41
+ | Quick benchmark (about a minute) | `python bench/run_bench.py --quick` |
42
+ | Mutation check (breaks the C++ on purpose, expects the tests to notice) | `python tools/mutation_check.py` |
43
+ | AddressSanitizer + UBSan (Linux) | `bash tools/sanitize_linux.sh` |
44
+
45
+ ## Reporting problems
46
+
47
+ Use the issue templates. For a performance report, include the workload shape (rectangle count and size range, bin size and count) and the packer settings. For anything that looks like a memory-safety problem, see [SECURITY.md](SECURITY.md) instead of opening a public issue.
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,13 @@
1
+ fastrectpack
2
+ Copyright 2026 Ramcharan
3
+
4
+ This product is a derivative work of rectpack (https://github.com/secnot/rectpack),
5
+ licensed under the Apache License, Version 2.0, by its author SecNot.
6
+
7
+ Changes made: the MaxRects packing algorithms (MaxRectsBl, MaxRectsBssf, MaxRectsBaf,
8
+ MaxRectsBlsf) were re-implemented in C++17 for plain Python int sizes and are exposed
9
+ through classes that subclass the originals. All other behaviour and all other algorithms
10
+ are the unmodified rectpack code, which is installed as a dependency and re-exported.
11
+
12
+ The MaxRects family itself is described in: Jukka Jylanki, "A Thousand Ways to Pack the
13
+ Bin - A Practical Approach to Two-Dimensional Rectangle Bin Packing" (2010).
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastrectpack
3
+ Version: 0.1.0
4
+ Summary: Drop-in C++ accelerator for rectpack's MaxRects 2D packing algorithms, with identical output
5
+ Keywords: rectpack,bin packing,rectangle packing,2d packing,nesting,pybind11,performance
6
+ Author: Ramcharan
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ License-File: NOTICE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: C++
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Project-URL: Homepage, https://github.com/p-ramcharan-dev/fastrectpack
22
+ Project-URL: Source, https://github.com/p-ramcharan-dev/fastrectpack
23
+ Project-URL: Issues, https://github.com/p-ramcharan-dev/fastrectpack/issues
24
+ Project-URL: Benchmarks, https://github.com/p-ramcharan-dev/fastrectpack/blob/main/BENCHMARKS.md
25
+ Project-URL: Original library, https://github.com/secnot/rectpack
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: rectpack==0.2.2
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest; extra == "test"
30
+ Requires-Dist: hypothesis; extra == "test"
31
+ Description-Content-Type: text/markdown
32
+
33
+ # fastrectpack
34
+
35
+ [![ci](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/ci.yml/badge.svg)](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/ci.yml)
36
+ [![wheels](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/wheels.yml/badge.svg)](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/wheels.yml)
37
+ ![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue)
38
+ ![Python 3.9 to 3.13](https://img.shields.io/badge/python-3.9%20to%203.13-blue)
39
+
40
+ A drop-in accelerator for [rectpack](https://github.com/secnot/rectpack), the pure-Python 2D rectangle packing library. The MaxRects packing algorithms are re-implemented in C++17 and produce **exactly the same packings** as the original, 5x to 246x faster depending on the workload.
41
+
42
+ ```python
43
+ # before
44
+ from rectpack import newPacker
45
+ # after: nothing else changes
46
+ from fastrectpack import newPacker
47
+ ```
48
+
49
+ ## Headline numbers
50
+
51
+ Default `newPacker()` settings. Times are the best recorded run; full method and all 14 cases are in [BENCHMARKS.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/BENCHMARKS.md).
52
+
53
+ | Workload | Rectangles | Original rectpack | Pure-Python fixes only | fastrectpack (C++) | Speedup |
54
+ |---|---|---|---|---|---|
55
+ | random sizes | 1,000 | 3.525 s | 0.473 s | 0.0144 s | 246x |
56
+ | random sizes | 5,000 | 21.123 s | 3.738 s | 0.1364 s | 155x |
57
+ | few large, many small | 1,000 | 1.360 s | 0.243 s | 0.0102 s | 133x |
58
+ | sprite sizes | 2,000 | 1.500 s | 0.322 s | 0.0177 s | 85x |
59
+ | sheet cutting, 100 bins | 2,000 | 0.415 s | 0.214 s | 0.0780 s | 5x |
60
+
61
+ Two things to read from this table:
62
+
63
+ - **Part of the win needs no C++.** A better algorithm in plain Python gives 1.7x to 7.5x on its own (the "Pure-Python fixes only" column). The C++ port supplies the rest.
64
+ - **The gain depends on the workload.** Jobs that spread rectangles over many bins gain about 5x, because the bin-selection loop is still Python. See "What is not faster" below.
65
+
66
+ The benchmark machine is a laptop that drops into slow phases (probably thermal throttling), so absolute seconds vary by up to about 2.3x. Best-time speedup ratios agreed to roughly 10% between full runs.
67
+
68
+ <p align="center">
69
+ <picture>
70
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/p-ramcharan-dev/fastrectpack/main/docs/benchmarks-dark.png">
71
+ <img alt="Dumbbell chart of the speedup over the original rectpack for 14 workloads, log scale. Pure-Python fixes alone give 1.7x to 7.5x; the C++ version gives 5x to 246x, with output identical in every case. Jobs that spread rectangles over many bins gain about 5x. The same numbers are in the tables in this README and in BENCHMARKS.md." src="https://raw.githubusercontent.com/p-ramcharan-dev/fastrectpack/main/docs/benchmarks-light.png" width="760">
72
+ </picture>
73
+ </p>
74
+
75
+ ## Install
76
+
77
+ ```
78
+ pip install fastrectpack
79
+ ```
80
+
81
+ Prebuilt wheels cover CPython 3.9 to 3.13 on Linux x86_64 (manylinux), Windows x86_64 and macOS arm64 (Apple silicon). Other platforms (Linux ARM, Intel Macs, Alpine/musl) and Python 3.14 are untested and build from the source distribution, which needs CMake 3.15+ and a C++17 compiler (MSVC 2019+, GCC 9+ or Clang 10+). From a checkout: `pip install .`. `rectpack==0.2.2` is installed as a dependency.
82
+
83
+ ## Usage
84
+
85
+ Identical to rectpack:
86
+
87
+ ```python
88
+ from fastrectpack import newPacker
89
+
90
+ packer = newPacker()
91
+ for width, height in [(100, 30), (40, 60), (30, 30), (70, 70), (100, 50), (30, 30)]:
92
+ packer.add_rect(width, height)
93
+ packer.add_bin(300, 450)
94
+ packer.pack()
95
+
96
+ for bin_index, x, y, w, h, rid in packer.rect_list():
97
+ print(bin_index, x, y, w, h)
98
+ ```
99
+
100
+ `fastrectpack` re-exports everything rectpack exports. `MaxRectsBl`, `MaxRectsBssf`, `MaxRectsBaf` and `MaxRectsBlsf` are replaced by accelerated subclasses of the originals; everything else is the original object.
101
+
102
+ ## How it works
103
+
104
+ The four MaxRects classes subclass the originals and run the algorithm in a small C++17 core while every size is a plain Python `int`. Anything else falls back, for good, to the original Python code for that object.
105
+
106
+ Profiling the original showed about 90% of its time in one step that compares every pair of free rectangles after each placement. The port compares only the pairs that involve newly created rectangles, which is valid because the list left by the previous step contains no rectangle that contains another. [docs/DESIGN.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/docs/DESIGN.md) covers the design, exactly what "identical" means, and the known gaps.
107
+
108
+ ## What is accelerated
109
+
110
+ | | Accelerated |
111
+ |---|---|
112
+ | MaxRects algorithms (Bl, Bssf, Baf, Blsf), with or without rotation | Yes |
113
+ | All bin heuristics (BNF, BFF, BBF, Global), online and offline modes, all sort orders | Yes, when used with a MaxRects algorithm |
114
+ | Sizes given as plain Python `int`, up to 2^30 | Yes |
115
+ | Sizes given as `float`, `Decimal`, numpy integers or `bool` | No: handled by the original code, same result, original speed |
116
+ | Skyline and Guillotine algorithms | No: unchanged original code |
117
+
118
+ When an accelerated object meets an input outside the fast path, it hands its state to the original Python implementation and continues there, so results and error messages stay the same.
119
+
120
+ ## What is not faster
121
+
122
+ - **Many-bin workloads.** With 100 open bins, most of the time goes to the Python loop that asks every bin how well a rectangle fits. That loop was left in Python, so the gain there is about 5x.
123
+ - **Non-integer sizes.** Floats and Decimals run at the original speed.
124
+ - **Skyline and Guillotine.** Not ported.
125
+
126
+ ## How correctness was checked
127
+
128
+ - **Exact parity.** After every single placement, the tests compare the fitness value, the placement and the complete list of free rectangles (including its order) against the original. 474 tests, including two Hypothesis property tests (300 and 150 generated cases) and all 200 packer configurations that use a MaxRects algorithm.
129
+ - **Edge cases.** Floats, Decimals, NaN, infinity, zero and negative sizes, booleans, strings, `None`, numpy integers, integers beyond the native range, exact fits, pieces larger than the bin. Results and exceptions match the original.
130
+ - **Benchmark outputs.** Every benchmark run hashes the full result of all three variants; all 14 cases are identical.
131
+ - **Sanitizers.** The suite passes under AddressSanitizer and UndefinedBehaviorSanitizer, both locally (GCC 15.2 in WSL) and on a GitHub Actions Ubuntu runner.
132
+ - **Mutation check.** Six deliberate bugs were injected into the C++. The tests caught five; the sixth (treating edge-touching rectangles as overlapping) only adds free rectangles that the next step deletes, so the output cannot change. I reasoned that through rather than proving it formally.
133
+ - **Platforms and Python versions.** In GitHub Actions the test suite ran inside every built wheel: Python 3.9, 3.10, 3.11, 3.12 and 3.13 on Linux x86_64, Windows x86_64 and macOS arm64. Locally it also passes on 3.13 and 3.11 on Windows.
134
+
135
+ Run them yourself:
136
+
137
+ ```
138
+ pip install ".[test]"
139
+ pytest
140
+ python bench/run_bench.py --quick # about a minute
141
+ python bench/run_bench.py # full benchmark, several minutes
142
+ ```
143
+
144
+ ## Behaviour inherited from rectpack
145
+
146
+ These were found while porting. They are reproduced on purpose, not fixed, so that results match ([`research/original_bugs.py`](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/research/original_bugs.py) demonstrates each):
147
+
148
+ - `validate_packing()` never compares the last rectangle with the others, so an overlap involving it goes undetected.
149
+ - `MaxRectsBl` documents a smallest-x tie-break but takes the first candidate.
150
+ - With the BBF bin heuristic, online `add_rect` returns `True`/`False` instead of the rectangle.
151
+
152
+ ## Limitations
153
+
154
+ - Speed was measured on one Windows laptop only. Correctness is checked in CI on Linux, Windows and macOS (arm64), but speed on those systems has not been measured.
155
+ - The package subclasses rectpack internals and pins `rectpack==0.2.2`. A new rectpack release would need re-checking.
156
+ - An unpickled or deep-copied packer continues on the original Python code path: correct, but not accelerated.
157
+
158
+ ## Contributing and security
159
+
160
+ Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/CONTRIBUTING.md). The most useful report is a real workload where it is still slow. Please report security problems privately, as described in [SECURITY.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/SECURITY.md).
161
+
162
+ ## Credits and licence
163
+
164
+ Apache-2.0, the same licence as rectpack, of which this is a derivative work. rectpack is by SecNot. The MaxRects algorithms come from Jukka Jylänki, "A Thousand Ways to Pack the Bin" (2010). See [NOTICE](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/NOTICE).