better patterns
All checks were successful
Deploy to Pages / build (push) Successful in 26s

This commit is contained in:
Andras Schmelczer 2026-09-11 22:46:23 +01:00
commit 171fbf1927
9 changed files with 2589 additions and 37 deletions

View file

@ -7,6 +7,8 @@ remainders, fireplaces in both directions, room-order permutations, tight and ex
stagger limits, fractional stock, large floors, and both supplied angled rooms.
`--output=/tmp/results.json` saves a run; `--compare=/tmp/results.json` compares
against a previous iteration. Do not overwrite the baseline to hide regressions.
`h-pattern-baseline.json` separately freezes both options immediately before the
visual H-pattern correction, evaluated using the broader alignment metric below.
## Fixed metrics
@ -14,18 +16,23 @@ against a previous iteration. Do not overwrite the baseline to hide regressions.
and additional shaping edges. Skirting includes bevel and coping operations.
- **Cut difficulty:** shaping passes, then rip passes, used to break equal saw-count
ties. This is an explicit proxy, not an estimate of minutes or tool-specific effort.
- **Random appearance:** the existing repetition penalty, evaluated independently
- **Legacy repetition:** the original penalty, evaluated independently
from the output rows. It penalises nearby repeating joints, similar consecutive
steps and staircases, including steps that wrap across a board length. Raw repeat,
similar-step and staircase counts are also reported. A lower score is better.
similar-step and staircase counts are also reported. This stays unchanged for
historical comparisons, but its 3%-of-board-length alignment tolerance missed visible H patterns.
- **Near-H row pairs:** joints two rows apart separated by less than one board width,
capped at one sixth of the board length. This catches the 40–150 mm misalignments
that still look like an H. Current appearance cost is the legacy penalty plus
12 per near-H pair, so avoiding an H outweighs several same-direction steps.
- **Waste:** purchased stock minus installed material, including kerf. The report
gives board counts and waste percentages weighted by purchased stock.
- **Validity and runtime:** errors, elapsed time, p95 and maximum case time. Tests
separately verify coverage, non-overlapping row bands, minimum sizes, stagger,
factory ends, rounded stock consumption, cut accounting, references and input purity.
Minimise cuts orders `(passes, shaping, rips, pattern penalty, boards)`.
Maximise randomness orders `(pattern penalty, passes, shaping, rips, boards)`.
Minimise cuts orders `(passes, shaping, rips, appearance cost, boards)`.
Maximise randomness orders `(appearance cost, passes, shaping, rips, boards)`.
Validity precedes either objective. Skirting orders `(passes, waste)`.
## Measured results
@ -34,25 +41,52 @@ Totals across the same 84 cases, September 2026:
| Metric | Original | Minimise cuts | Maximise randomness |
| ------------------- | -------: | ------------: | ------------------: |
| Flooring saw passes | 2,177 | **1,741** | **1,994** |
| Shaping passes | 192 | 184 | 185 |
| Rip passes | 414 | 402 | 405 |
| Pattern penalty | 265 | 1,349 | **113** |
| Flooring boards | 3,561 | 3,537 | 3,579 |
| Flooring waste | 8.20% | 7.57% | 8.66% |
| Flooring saw passes | 2,177 | **1,741** | **2,113** |
| Shaping passes | 192 | 184 | 188 |
| Rip passes | 414 | 402 | 409 |
| Legacy penalty | 265 | 2,207 | 346 |
| Flooring boards | 3,561 | 3,548 | 3,551 |
| Flooring waste | 8.20% | 7.85% | 7.94% |
| Invalid cases | 0 | 0 | 0 |
Minimise cuts saves **20.0%** of saw passes. Maximise randomness reduces the pattern
penalty **57.4%**, while also saving **8.4%** of saw passes. For random flooring in the
default 4,800 × 3,600 mm room, 38 passes become **24** or **30**, respectively; both options use 76
Minimise cuts saves **20.0%** of saw passes. Maximise randomness saves **2.9%** of saw
passes against the original planner. For random flooring in the default
4,800 × 3,600 mm room, 38 passes become **24** or **31**, respectively; both options use 76
boards and have approximately 1.2% waste.
Skirting drops from **1,447 to 1,436 passes**. It uses 521 boards instead of 513:
cutting effort now takes precedence over waste, which rises from 9.28% to 10.68%.
Eight cases improve and 76 tie.
For flooring, 78 cut-priority cases improve and six tie. Appearance priority improves
72, ties 11, and uses one extra cut in `rectangle-38` despite the same zero pattern
penalty. The bounded search does not establish global optimality.
For flooring, 78 cut-priority cases improve and six tie against the original planner.
The bounded search does not establish global optimality.
## H-pattern correction
The first optimisation reported a 57% lower legacy penalty, but visual inspection
showed that the metric was too narrow. The default appearance-priority room still
had **11 near-H row pairs**, despite no repeats within the old 36 mm tolerance.
The candidate hash also generated almost identical offsets for adjacent candidate
numbers. A final bit-mixing step now spreads those candidates across the allowed range.
| Metric | Cuts before | Cuts now | Random before | Random now |
| ------------------------- | ----------: | -------: | ------------: | ---------: |
| Near-H row pairs | 515 | 247 | 557 | **6** |
| Saw passes | 1,741 | 1,741 | 1,994 | 2,113 |
| Boards | 3,537 | 3,548 | 3,579 | 3,551 |
| Waste | 7.57% | 7.85% | 8.66% | 7.94% |
| Default-room near-H pairs | 17 | 17 | 11 | **0** |
| Default-room saw passes | 24 | 24 | 30 | 31 |
Appearance priority removes **98.9%** of near-H pairs for **6.0%** more cuts than the
previous iteration. All six remaining pairs are in `forced-alternation`, whose
600 mm minimum pieces force alternating starts. It improves 79 cases and ties five
under the corrected objective. Ten additional room seeds also produce zero near-H pairs.
The tradeoff is explicit: the old direction-run/staircase score increases, and the
randomness option uses more cuts. Cuts-first retains its total saw count but three
individual cases use extra cuts (`two-rooms-reversed-6`, `angled`, `angled-f8`).
It can still show H patterns when avoiding them requires extra cuts; the UI explains
this beside the laying-pattern choice. Both frozen baselines remain unchanged.
## Iterations retained
@ -74,6 +108,9 @@ penalty. The bounded search does not establish global optimality.
the optimiser.
8. Compare exact three-piece skirting fills with the original packing. This improves
two cases for a small additional calculation cost.
9. Add the board-scale H penalty and mix the offset hash. Search size and geometry
constraints stay unchanged. Before/after drawings were checked for the default
rectangle, fireplace and large floor, alongside the numeric metrics.
## Experiments rejected
@ -90,5 +127,6 @@ These are indicative timings, not CI thresholds. Ordinary option changes reuse t
already calculated plans. A separate 500-row, 2,000-board boundary check completes
without an error; temporary stock is repacked before enforcing the board limit.
Tests enforce the retained aggregate improvements as well as physical invariants.
Metric definitions and input cases stay fixed while search strategies change.
Tests enforce aggregate cutting budgets, broad H avoidance and physical invariants.
The original metrics and cases remain fixed; the new H metric has its own frozen
baseline so the correction does not rewrite the earlier results.

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ export function flooringMetrics(plan: FlooringPlan, s: FlooringSettings) {
let ripCuts = 0;
let patternPenalty = 0;
let repeats = 0;
let nearH = 0;
let similarSteps = 0;
let staircases = 0;
for (const piece of plan.pieces) {
@ -32,6 +33,8 @@ export function flooringMetrics(plan: FlooringPlan, s: FlooringSettings) {
if (recent.length >= 2) {
const movement = step(recent.at(-1)!, phase);
const previous = step(recent.at(-2)!, recent.at(-1)!);
// Near-aligned joints with one intervening row still read as an H at the scale of the boards.
if (Math.abs(step(recent.at(-2)!, phase)) < Math.min(s.width, s.length / 6) - 0.01) nearH++;
patternPenalty += Number(Math.abs(Math.abs(movement) - s.length / 2) <= 0.01);
for (let distance = 2; distance <= recent.length; distance++) {
if (Math.abs(step(recent.at(-distance)!, phase)) <= similar) {
@ -59,6 +62,7 @@ export function flooringMetrics(plan: FlooringPlan, s: FlooringSettings) {
ripCuts,
patternPenalty,
repeats,
nearH,
similarSteps,
staircases,
boards: plan.boardCount,
@ -78,7 +82,7 @@ export function skirtingMetrics(plan: SkirtingPlan) {
}
export function compareFlooring(
a: ReturnType<typeof flooringMetrics>,
a: Omit<ReturnType<typeof flooringMetrics>, 'nearH'>,
b: typeof a,
random = false,
) {
@ -93,6 +97,19 @@ export function compareFlooring(
);
}
/** Current appearance score adds broad H alignments; the original metric stays comparable. */
export function compareCurrentFlooring(
a: ReturnType<typeof flooringMetrics>,
b: typeof a,
random = false,
) {
return compareFlooring(
{ ...a, patternPenalty: a.patternPenalty + 12 * a.nearH },
{ ...b, patternPenalty: b.patternPenalty + 12 * b.nearH },
random,
);
}
export function compareSkirting(a: ReturnType<typeof skirtingMetrics>, b: typeof a) {
return a.errors - b.errors || a.cuts - b.cuts || a.waste - b.waste;
}