This commit is contained in:
parent
7959fe85cc
commit
b9485e4097
32 changed files with 4692 additions and 65 deletions
2018
benchmarks/baseline.json
Normal file
2018
benchmarks/baseline.json
Normal file
File diff suppressed because it is too large
Load diff
61
benchmarks/cases.ts
Normal file
61
benchmarks/cases.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { defaultProject, fireplace, rectangle } from '../src/lib/defaults';
|
||||
import type { Project, Room } from '../src/lib/types';
|
||||
import angled from '../tests/fixtures/angled-room.json';
|
||||
import angledF8 from '../tests/fixtures/angled-room-f8.json';
|
||||
|
||||
export interface PlannerCase {
|
||||
name: string;
|
||||
project: Project;
|
||||
}
|
||||
|
||||
const room = (width: number, height: number, id = 'room-1'): Room => {
|
||||
const result = rectangle(id, id, width, height);
|
||||
result.walls.forEach((wall) => (wall.doors = []));
|
||||
return result;
|
||||
};
|
||||
|
||||
export function plannerCases(): PlannerCase[] {
|
||||
const cases: PlannerCase[] = [];
|
||||
const add = (name: string, rooms: Room[], changes: Partial<Project['flooring']> = {}) => {
|
||||
const project = defaultProject();
|
||||
project.rooms = rooms;
|
||||
project.flooring = { ...project.flooring, pattern: 'random', ...changes };
|
||||
cases.push({ name, project });
|
||||
};
|
||||
for (const pattern of ['brick', 'random'] as const) {
|
||||
add(`rectangle-${pattern}`, [rectangle()], { pattern });
|
||||
add(`fireplace-${pattern}`, [fireplace('room-2', 'Fireplace')], { pattern });
|
||||
add(`rotated-${pattern}`, [{ ...fireplace('room-2', 'Fireplace'), direction: 90 }], {
|
||||
pattern,
|
||||
});
|
||||
}
|
||||
// Fixed arithmetic sequences cover a range of remainders without runtime randomness.
|
||||
for (let i = 0; i < 45; i++) {
|
||||
add(`rectangle-${i + 1}`, [room(1620 + ((i * 733) % 3800), 620 + ((i * 379) % 1900))], {
|
||||
width: 200,
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const rooms = [
|
||||
room(1320 + ((i * 271) % 1900), 620 + ((i * 317) % 1200), 'r0'),
|
||||
room(1420 + ((i * 653) % 2100), 820 + ((i * 211) % 1200), 'r1'),
|
||||
];
|
||||
add(`two-rooms-${i + 1}`, rooms, { width: 200 });
|
||||
add(`two-rooms-reversed-${i + 1}`, [...rooms].reverse(), { width: 200 });
|
||||
}
|
||||
add('balanced-edges', [room(4020, 2050)], { width: 200 });
|
||||
add('tight-offsets', [room(4020, 2220)], { minOffset: 250, maxOffset: 450 });
|
||||
add('exact-offset', [room(4020, 2220)], { minOffset: 400, maxOffset: 400 });
|
||||
add('forced-alternation', [room(2420, 1620)], { width: 200, minLength: 600 });
|
||||
add('fractional-stock', [room(620, 420, 'r0'), room(617.2, 420, 'r1')], {
|
||||
length: 1200.2,
|
||||
width: 200,
|
||||
});
|
||||
add('large-rectangle', [room(14020, 8020)]);
|
||||
add('long-room', [room(30020, 1820)]);
|
||||
cases.push(
|
||||
{ name: 'angled', project: angled.project as Project },
|
||||
{ name: 'angled-f8', project: angledF8.project as Project },
|
||||
);
|
||||
return cases;
|
||||
}
|
||||
98
benchmarks/metrics.ts
Normal file
98
benchmarks/metrics.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import type { FlooringPlan, FlooringSettings, SkirtingPlan } from '../src/lib/types';
|
||||
|
||||
/** Evaluate public plan output. Keep these definitions fixed when comparing search changes. */
|
||||
export function flooringMetrics(plan: FlooringPlan, s: FlooringSettings) {
|
||||
let shapingCuts = 0;
|
||||
let ripCuts = 0;
|
||||
let patternPenalty = 0;
|
||||
let repeats = 0;
|
||||
let similarSteps = 0;
|
||||
let staircases = 0;
|
||||
for (const piece of plan.pieces) {
|
||||
ripCuts += piece.ripEdge === 'both' ? 2 : Number(!!piece.ripEdge);
|
||||
if (!piece.shaped) continue;
|
||||
const close = (a: number, b: number) => Math.abs(a - b) <= 0.01;
|
||||
shapingCuts += piece.template.filter((p, i, path) => {
|
||||
const q = path[(i + 1) % path.length];
|
||||
return !(
|
||||
(close(p.x, q.x) && (close(p.x, 0) || close(p.x, piece.length))) ||
|
||||
(close(p.y, q.y) && (close(p.y, 0) || close(p.y, piece.width)))
|
||||
);
|
||||
}).length;
|
||||
}
|
||||
const history = new Map<string, number[]>();
|
||||
const step = (from: number, to: number) => {
|
||||
const value = (((to - from + s.length / 2) % s.length) + s.length) % s.length;
|
||||
return ((Math.round(value * 1000) / 1000) % s.length) - s.length / 2;
|
||||
};
|
||||
const similar = Math.max(s.kerf * 2, s.length * 0.03);
|
||||
for (const row of s.pattern === 'random' ? plan.rows : []) {
|
||||
const recent = history.get(row.roomId) ?? [];
|
||||
const phase = row.offset;
|
||||
if (recent.length >= 2) {
|
||||
const movement = step(recent.at(-1)!, phase);
|
||||
const previous = step(recent.at(-2)!, recent.at(-1)!);
|
||||
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) {
|
||||
patternPenalty += 5 - distance;
|
||||
if (distance === 2) repeats++;
|
||||
}
|
||||
}
|
||||
if (movement * previous > 0) {
|
||||
patternPenalty++;
|
||||
if (Math.abs(movement - previous) <= similar) {
|
||||
patternPenalty += 3;
|
||||
similarSteps++;
|
||||
}
|
||||
if (recent.length >= 3 && previous * step(recent.at(-3)!, recent.at(-2)!) > 0) {
|
||||
patternPenalty += 2;
|
||||
staircases++;
|
||||
}
|
||||
}
|
||||
}
|
||||
history.set(row.roomId, [...recent, phase].slice(-4));
|
||||
}
|
||||
return {
|
||||
cuts: plan.cuts,
|
||||
shapingCuts,
|
||||
ripCuts,
|
||||
patternPenalty,
|
||||
repeats,
|
||||
similarSteps,
|
||||
staircases,
|
||||
boards: plan.boardCount,
|
||||
waste: plan.wasteArea,
|
||||
errors: plan.issues.filter((issue) => issue.severity === 'error').length,
|
||||
};
|
||||
}
|
||||
|
||||
export function skirtingMetrics(plan: SkirtingPlan) {
|
||||
return {
|
||||
cuts: plan.cuts,
|
||||
pieces: plan.pieces.length,
|
||||
boards: plan.stockCount,
|
||||
waste: Math.round(plan.waste * 1000) / 1000,
|
||||
errors: plan.issues.filter((issue) => issue.severity === 'error').length,
|
||||
};
|
||||
}
|
||||
|
||||
export function compareFlooring(
|
||||
a: ReturnType<typeof flooringMetrics>,
|
||||
b: typeof a,
|
||||
random = false,
|
||||
) {
|
||||
return (
|
||||
a.errors - b.errors ||
|
||||
(random ? a.patternPenalty - b.patternPenalty : 0) ||
|
||||
a.cuts - b.cuts ||
|
||||
a.shapingCuts - b.shapingCuts ||
|
||||
a.ripCuts - b.ripCuts ||
|
||||
a.patternPenalty - b.patternPenalty ||
|
||||
a.boards - b.boards
|
||||
);
|
||||
}
|
||||
|
||||
export function compareSkirting(a: ReturnType<typeof skirtingMetrics>, b: typeof a) {
|
||||
return a.errors - b.errors || a.cuts - b.cuts || a.waste - b.waste;
|
||||
}
|
||||
Loading…
Reference in a new issue