fix(benchmark): rank by sheet cost so engines can't game the score
The benchmark is about to be used as the objective for LLM-designed engines, and several gaps would have rewarded the wrong behavior: - Ranking was utilization-first, so dropping awkward parts raised the score. Rank valid > fully placed > cost > plates, where cost is salvage-credited sheet area plus a largest-sheet penalty per unplaced part; placing a part is never scored worse than omitting it. - Salvage rate was ignored in scoring; cost now uses EstimateNetArea, recomputed from job geometry rather than trusted from the engine. - Rotation constraints were never validated. Add RotationPolicy.Allows (shared with NestJobPlacementValidator) and check every placement. - Returned sheets were trusted, so an engine could loosen spacing or invent a size. Sheets must now match offered stock. - Part-in-part placements were flagged as overlaps; spacing now accounts for cutouts, with an X-sorted sweep to prune distant pairs. - Summary averaged per-job percentages; it now sums areas and cost. - --spacing and sheet sizes parsed with the current culture. - Warn when .nest jobs offer only their original sheet sizes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,7 @@ internal static class NestJobPlacementValidator
|
||||
counts.TryGetValue(placement.PartId, out var count);
|
||||
if (count >= available)
|
||||
throw new InvalidOperationException("Candidate overproduces a requirement.");
|
||||
if (!RotationIsAllowed(part.Rotation, placement.Rotation))
|
||||
if (!part.Rotation.Allows(placement.Rotation))
|
||||
throw new InvalidOperationException(
|
||||
"Candidate rotation is not allowed for the requirement."
|
||||
);
|
||||
@@ -78,25 +78,6 @@ internal static class NestJobPlacementValidator
|
||||
_ = CreateShape(geometry);
|
||||
}
|
||||
|
||||
private static bool RotationIsAllowed(RotationPolicy policy, double rotation)
|
||||
{
|
||||
if (policy.Kind == RotationPolicyKind.Automatic)
|
||||
return true;
|
||||
if (policy.Kind == RotationPolicyKind.Fixed)
|
||||
return AnglesEqual(rotation, policy.Start);
|
||||
if (rotation < policy.Start - Epsilon || rotation > policy.End + Epsilon)
|
||||
return false;
|
||||
var steps = (rotation - policy.Start) / policy.Step;
|
||||
return System.Math.Abs(steps - System.Math.Round(steps)) <= Epsilon;
|
||||
}
|
||||
|
||||
private static bool AnglesEqual(double left, double right)
|
||||
{
|
||||
var delta = (left - right) % (System.Math.PI * 2);
|
||||
return System.Math.Abs(delta) <= Epsilon
|
||||
|| System.Math.Abs(System.Math.Abs(delta) - System.Math.PI * 2) <= Epsilon;
|
||||
}
|
||||
|
||||
private static ShapeTopology CreateShape(PartGeometrySnapshot geometry)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(DrawingJobMapper.ToProgram(geometry));
|
||||
|
||||
Reference in New Issue
Block a user