Skip to content

Commit f296099

Browse files
committed
Merge branch 'develop'
Added --dry-run option to just display problem info Separated the plot part from the solution calculation: now we first solve and save to json, then free that memory and the plot methods read the json. This allows to plot in a second moment from a pre existing json.
2 parents 7fce8dc + e63f46b commit f296099

7 files changed

Lines changed: 4825 additions & 163 deletions

File tree

README.md

Lines changed: 114 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
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
5859
guillotine 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
6165
guillotine 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
6771
guillotine 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
7077
guillotine 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

118129
The 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

Comments
 (0)