77- C extension with OpenMP parallelization for fast solving
88- Handles defects and irregular regions
99- JSON and CSV input support
10- - Command-line interface with ` benchmark ` and ` solve ` subcommands
10+ - Command-line interface with ` benchmark ` , ` solve ` , and ` plot ` subcommands
1111- Inline problem definition via ` --sheet ` , ` --items ` , and ` --defects ` flags
12+ - ` --dry-run ` flag to preview problem stats and memory estimates before solving
1213
1314## Installation
1415
@@ -48,7 +49,7 @@ print(f"Optimal value: {value}")
4849
4950### CLI Usage
5051
51- The CLI uses two subcommands: ` benchmark ` and ` solve ` .
52+ The CLI uses three subcommands: ` benchmark ` , ` solve ` , and ` plot ` .
5253
5354``` bash
5455# Run the built-in 27x27 benchmark
@@ -57,6 +58,9 @@ guillotine benchmark
5758# Run benchmark with visualization and profiling
5859guillotine benchmark --plot --profile
5960
61+ # Preview problem stats and memory estimate without solving
62+ guillotine solve problem.json --dry-run
63+
6064# Solve from a JSON file
6165guillotine solve problem.json
6266
@@ -66,22 +70,29 @@ guillotine solve --sheet 27x27 --items 5x5 10x10 12x12 --defects 9,9,2x2
6670# Save output to a custom file with a plot
6771guillotine solve problem.json -o my_solution.json --plot result.png
6872
73+ # Plot from a previously saved solution (no re-solving needed)
74+ guillotine plot output/solution.json
75+
6976# With profiling
7077guillotine solve --sheet 27x27 --items 5x5 10x10 --profile
7178```
7279
73- All outputs (JSON, PNG, profile) are saved to ` ./output/ ` by default unless a path with a directory is specified.
80+ All outputs (JSON, PNG, SVG, profile) are saved to ` ./output/ ` by default unless a path with a directory is specified.
7481
7582#### Subcommand reference
7683
77- | Flag | ` benchmark ` | ` solve ` | Description |
78- | ---| ---| ---| ---|
79- | ` --plot [FILE] ` | ✓ | ✓ | Save visualization (default: ` plot.png ` ) |
80- | ` --profile [FILE] ` | ✓ | ✓ | Save profiling data (default: ` profiling.txt ` ) |
81- | ` -o / --output FILE ` | ✓ | ✓ | Output JSON file (default: ` solution.json ` ) |
82- | ` --sheet WxH ` | | ✓ | Sheet dimensions for inline mode |
83- | ` --items WxH ... ` | | ✓ | Item sizes for inline mode |
84- | ` --defects X,Y,WxH ... ` | | ✓ | Defect positions/sizes for inline mode |
84+ | Flag | ` benchmark ` | ` solve ` | ` plot ` | Description |
85+ | ---| ---| ---| ---| ---|
86+ | ` --plot [FILE] ` | ✓ | ✓ | | Save visualization (default: ` plot.png ` ) |
87+ | ` --profile [FILE] ` | ✓ | ✓ | | Save profiling data (default: ` profiling.txt ` ) |
88+ | ` -o / --output FILE ` | ✓ | ✓ | ✓ | Output file (default: ` solution.json ` or ` plot.png ` ) |
89+ | ` --dry-run ` | ✓ | ✓ | | Print problem summary and memory estimate, then exit |
90+ | ` --sheet WxH ` | | ✓ | | Sheet dimensions for inline mode |
91+ | ` --items WxH ... ` | | ✓ | | Item sizes for inline mode |
92+ | ` --defects X,Y,WxH ... ` | | ✓ | | Defect positions/sizes for inline mode |
93+
94+ The ` plot ` subcommand takes a single solution JSON file (which embeds the
95+ problem definition) and generates a visualization without re-solving.
8596
8697### Input Formats
8798
@@ -116,8 +127,9 @@ x,y,width,height
116127## Output
117128
118129The solver generates:
119- - ** JSON solution** with metrics (utilization, efficiency, cut sequence) saved to ` ./output/ `
120- - ** PNG visualization** (optional) showing the cutting pattern
130+ - ** JSON solution** with metrics (utilization, efficiency, cut sequence) and
131+ the embedded problem definition, saved to ` ./output/ `
132+ - ** PNG + SVG visualization** (optional) showing the cutting pattern
121133- ** Profile data** (optional) for performance analysis
122134
123135## Performance
@@ -136,32 +148,85 @@ Benchmark results on a Ryzen 5 9600X (6 cores, OMP_NUM_THREADS=6):
136148
137149| Sheet | Items | Defects | Time | Memory |
138150| ---------| -------| ---------| --------| --------|
139- | 27×27 | 4 | 1 | 0.003s | 34 MB |
140- | 40×40 | 4 | 6 | 0.013s | 58 MB |
141- | 60×60 | 4 | 10 | 0.058s | 182 MB |
142- | 80×80 | 6 | 15 | 0.238s | 514 MB |
143- | 100×100 | 10 | 20 | 0.703s | 1.2 GB |
151+ | 27×27 | 4 | 1 | 0.002s | 30 MB |
152+ | 40×40 | 4 | 6 | 0.013s | 37 MB |
153+ | 60×60 | 4 | 10 | 0.058s | 79 MB |
154+ | 80×80 | 6 | 15 | 0.238s | 190 MB |
155+ | 100×100 | 10 | 20 | 0.703s | 422 MB |
156+
157+ Example output for a 100x100 sheet with 10 item types and 20 defects (in black):
158+
159+ <img src =" images/plot.svg " width =" 800 " alt =" 100x100 solution " >
160+
161+ ### Memory model
162+
163+ The algorithm stores three categories of data:
144164
145- Example output for the 40×40 problem (4 item types, 6 defects):
165+ - ** g and F tables** (2D, always allocated): ` g_values ` , ` g_indices ` ,
166+ ` F_values ` , ` F_type ` , ` F_param ` — shape ` (W+1) × (H+1) ` each. These are
167+ small (a few KB to a few hundred KB).
146168
147- <img src =" images/solution_40x40.png " width =" 600 " alt =" 40×40 solution " >
169+ - ** Prefix sum** (2D): ` prefix ` — shape ` (W+1) × (H+1) ` , used for O(1)
170+ defect overlap queries. Also small.
171+
172+ - ** Fd table** (4D, dominates memory): ` Fd_values ` — shape
173+ ` (W+1) × (H+1) × (W+1) × (H+1) ` in ` int32 ` . This is where nearly all
174+ memory goes. Only allocated when the sheet contains defects.
175+
176+ Previously, three 4D arrays were stored (` Fd_values ` , ` Fd_type ` , ` Fd_param ` ,
177+ each ` int32 ` ), totaling 12 bytes per cell. The current implementation stores
178+ only ` Fd_values ` (4 bytes per cell) — a ** 3× reduction** . Decision types and
179+ parameters are no longer stored; instead, they are re-derived during solution
180+ reconstruction by checking which candidate cut reproduces the optimal value.
181+
182+ The ` --dry-run ` flag shows the estimated memory breakdown before solving:
183+
184+ ```
185+ $ guillotine solve examples/problem_100x100.json --dry-run
186+ ============================================================
187+ DRY RUN — Problem Summary
188+ ============================================================
189+
190+ Sheet size: 100 x 100 (area: 10,000)
191+
192+ Item types: 10
193+ [0] 5 x 5 (area: 25)
194+ ...
195+
196+ Defects: 20
197+ [0] at (12, 45) size 8 x 6 (area: 48)
198+ ...
199+ Total defect area: 1,234 (12.3% of sheet)
200+
201+ Estimated memory usage:
202+ g tables (values + indices): 79.2 KB
203+ F tables (values + type + param): 89.2 KB
204+ Prefix sum array: 39.6 KB
205+ Fd dense 4D array: 397.0 MB (104,060,401 cells)
206+ ----------------------------------------
207+ Total estimated: 397.2 MB
208+
209+ ============================================================
210+ ```
148211
149- ### Memory requirements
212+ The estimated and actual memory usage for square sheets:
150213
151- The algorithm stores three 4D arrays of shape ` (W+1, H+1, W+1, H+1) ` in
152- ` int32 ` , giving a memory footprint of approximately ` 3 × (N+1)^4 × 4 ` bytes for
153- an ` N×N ` sheet. This grows as the fourth power of sheet size:
214+ | Sheet | Fd cells | Estimated total | Actual RSS |
215+ | -----------| ---------------| -----------------| ------------|
216+ | 27 × 27 | 614,656 | 2.4 MB | 30 MB |
217+ | 40 × 40 | 2,825,761 | 10.8 MB | 37 MB |
218+ | 60 × 60 | 13,845,841 | 52.9 MB | 79 MB |
219+ | 80 × 80 | 42,998,721 | 164.3 MB | 190 MB |
220+ | 100 × 100| 104,060,401 | 397.2 MB | 422 MB |
154221
155- | Sheet | Memory |
156- | ---------| --------|
157- | 60×60 | 182 MB |
158- | 80×80 | 514 MB |
159- | 100×100 | 1.2 GB |
160- | 120×120 | 2.6 GB |
161- | 150×150 | 7.9 GB |
222+ The ~ 25 MB gap between estimated and actual is the constant overhead of
223+ Python, numpy, and the C extension runtime. For large sheets the Fd table
224+ dominates and the overhead becomes negligible.
162225
163- The full table is allocated upfront regardless of how many states are actually needed.
164- In the current implementation, memory is therefore the binding constraint for large problems.
226+ Memory grows as ` O((W+1)² × (H+1)²) ` — roughly the fourth power of sheet
227+ size for square sheets. The full table is allocated upfront regardless of how
228+ many states are actually needed. Memory is therefore the binding constraint
229+ for large problems.
165230
166231## Development
167232
@@ -188,7 +253,7 @@ guillotine-cutter/
188253│ │ ├── geometry.py # SheetGeometry: defect prefix sums, O(1) purity queries
189254│ │ ├── patterns.py # CutPatternGenerator: normal pattern cut positions
190255│ │ ├── dp_solver.py # GuillotineDP: DP solver, reconstruction
191- │ │ ├── _solver.c # C extension: fill_Fd hot loop with OpenMP
256+ │ │ ├── _solver.c # C extension: fill_g, fill_F, fill_Fd with OpenMP
192257│ │ └── constants.py # DECISION_* constants shared by Python and C
193258│ ├── io.py # JSON/CSV I/O, input validation
194259│ ├── visualize.py # Matplotlib visualization
@@ -200,20 +265,24 @@ guillotine-cutter/
200265
201266### Algorithm overview
202267
203- The solver implements exact guillotine DP in two phases:
268+ The solver implements exact guillotine DP in three phases:
204269
205- ** Phase 1 — pure rectangles ( ` _precompute_F ` ):** For each rectangle size
206- ` (w,h) ` , compute the best tiling assuming no defects. This is a standard 2D
207- knapsack DP and only requires a ` (W+1)×(H+1) ` table .
270+ ** Phase 1 — g-table ( ` fill_g ` ):** For each rectangle size ` (w,h) ` , compute the
271+ best value achievable by tiling with copies of a single item type. Implemented
272+ in C with OpenMP parallelization across widths .
208273
209- ** Phase 2 — defected rectangles ( ` _fill_Fd ` ):** For each rectangle size ` (w,h) `
210- and position ` (x,y) ` , compute the best tiling accounting for defects. Pure
211- rectangles reuse Phase 1 results directly. Defected rectangles try all normal
212- pattern cuts and all defect boundary cuts, taking the best. This requires a
213- ` (W+1)×(H+1)×(W+1)×(H+1) ` table indexed as ` [w,h,x,y] ` for cache locality .
274+ ** Phase 2 — F-table ( ` fill_F ` ):** For each rectangle size ` (w,h) ` , compute the
275+ optimal value assuming no defects (pure rectangle). Tries single-item tiling
276+ from Phase 1, vertical cuts, and horizontal cuts at normal pattern positions.
277+ Uses symmetry (only tries cuts up to half the dimension). Implemented in C,
278+ sequential (bottom-up dependency) .
214279
215- The hot loop of Phase 2 is implemented in ` _solver.c ` and parallelized with
216- OpenMP across ` (x,y) ` positions at each fixed ` (w,h) ` .
280+ ** Phase 3 — Fd-table (` fill_Fd ` ):** For each rectangle size ` (w,h) ` and
281+ position ` (x,y) ` , compute the optimal value accounting for defects. Pure
282+ positions (detected via O(1) prefix-sum query) reuse Phase 2 results directly.
283+ Defected positions try all normal pattern cuts and defect-aligned cuts, taking
284+ the best. Implemented in C with OpenMP parallelization across ` (x,y) ` positions
285+ at each fixed ` (w,h) ` .
217286
218287## Citation
219288
0 commit comments