fix(planning): re-plan the last kept part when a learned rule changes what follows it

When a blocked approach teaches an order rule, the search backs up to the
earliest part that approach crossed, keeps the parts before it and
re-plans the rest. The last kept part was planned facing the part that
followed it in the old order. If the new order puts another part after
it, its entry and exit still face the old one, so the plan ends with a
stale exit that a kept-order re-plan of the same order would fix.

Now that part is planned again, in the same place, facing its new
successor; when its successor is unchanged nothing is re-planned.

The locked-among-regenerated fixture's plan changes: rapid 67.60 ->
60.22, expansions 528 -> 268 (digest updated). Learning "cut the
cutoff first" now takes 40 expansions rather than 22, because B is
planned again to face the cutoff; retrying every entry of B took 60.
This commit is contained in:
aj committed 2026-11-01 16:51:09 -05:00
1 parent d57678ecee
commit 277719a1d6
4 files changed
+14 -6

No files matched your search

@@ -155,9 +155,14 @@ internal static class JointCuttingPlanSearch
}
// Back up to just before the earliest part the blocked approach crossed, keep the
// parts cut before it, and re-plan the rest from where the tool is at that point.
// The last kept part was planned facing the part that followed it in the old
// order; when another part follows it now, it is planned again, in the same
// place, facing that one.
var back = attempt.Crossed.Min(part => Array.IndexOf(sequence, part));
resume = attempt.BoundaryAt(back);
sequence = [.. sequence.Take(back), .. CuttingNestedOrder.Plan(sequence[back..], resume.Position, nesting)];
var rest = CuttingNestedOrder.Plan(sequence[back..], attempt.BoundaryAt(back).Position, nesting);
var replan = back > 0 && rest.Length != 0 && rest[0] != sequence[back] ? back - 1 : back;
resume = attempt.BoundaryAt(replan);
sequence = [.. sequence.Take(back), .. rest];
}
}
catch (BudgetExceededException)
@@ -147,8 +147,9 @@ public class CuttingDependencyTests
Assert.True(result.Status == CuttingPlanStatus.Ready, Describe(result));
var order = result.ProposedOrder.Select(o => o.SourcePart).ToList();
Assert.True(order.IndexOf(cut) < order.IndexOf(a), string.Join(",", result.ProposedOrder.Select(o => o.SourceOrdinal)));
// Learning at once took 22 expansions; retrying every entry of B first took 60.
Assert.True(result.Expansions < 40, Describe(result));
// Learning at once takes 40 expansions (22, then B planned again to face the cutoff that
// now follows it); retrying every entry of B first took 60.
Assert.True(result.Expansions < 50, Describe(result));
}
// 100 x 100 sheet: squares at X 1-11 and 60-70; a cutoff at X 5 is trimmed around the left
@@ -45,7 +45,7 @@ public class PlanningCharacterizationTests
[InlineData("holes-grid-kept-order", "07B70B43549E95C65407B24944DA8F7262B784CFC4ABF86241C10DD8272B1780")]
[InlineData("rounded-circle-leads", "73677429BFC950669D238E834CAE48F54A483759BE4E0BB1437D096F0AF7F3DA")]
[InlineData("tabbed-perimeters", "6858C09A02D73E4531D0D59B6CD1CFFFA2C9C8BA5F48CF4943D9C4AEC17195BA")]
[InlineData("locked-among-regenerated", "D7914712F87513E38AA93776886AAFD5025EC89A0D4D7314F900A1163F830019")]
[InlineData("locked-among-regenerated", "42AFCF0DE942F501D8542C091C9D4407EAE41DE9442F75300348DCDB4FF0C733")]
public void FocusedFixture_PlansExactlyAsBefore(string name, string expected)
{
var nest = new Nest();
+3 -1
View File
@@ -218,7 +218,9 @@ from the start point. Its 2-opt and Or-opt passes would grow with the square of
part count. Replans after a learned rule use the plain tour. If a part on that order cannot be
reached without crossing parts already cut, the search learns "cut this part
before those", backs up to just before the earliest of them and re-plans the rest
from the tool position there; parts cut before that point are kept. When the approach
from the tool position there; parts cut before that point are kept. The last of them was
planned facing the part that followed it; when another part follows it now, it is
planned again, in the same place, facing that one. When the approach
crossed the part cut just before the one it left from, the tool went on past that part
and came back across it, so the search learns "cut those two the other way round"
instead: the tool then leaves from the nearer one, as with two stacked strips cut far