mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-14 18:21:55 -04:00
fix(engine): reuse clamp-removal probes across identical stock
Offering several identical sheets (same size, spacing and clamps) multiplied every feasibility probe, so eight full-sheet parts on four 48x120 options with the eight-clamp rail spent the 1000-probe budget and recovered nothing although each part fits with its clamps removed. A probe's answer is now reused for the same part, engine and physically identical stock. Delta review (s3b-delta-review.md) policy gate 3; PM 0e655fdb-a99a-40d8-968a-1863d8068c3e
This commit is contained in:
1 parent
64ebda2f6f
commit
a1914e147a
2 files changed
+50
-2
No files matched your search
@@ -333,4 +333,30 @@ public class ClampRemovalRepairTests
|
||||
Assert.All(result.Raw.Plates.Where(p => p.StockId.Contains("removed")), p => Assert.Single(p.Stock.ClampZones));
|
||||
Assert.Equal(2, result.Raw.StockUsage.Sum(u => u.Used));
|
||||
}
|
||||
// Delta review policy gate 3: identical offered stock (four 48x120 sheets with the same
|
||||
// eight-clamp rail) must not multiply the probe count, or a moderate job of full-sheet
|
||||
// parts runs out of budget although every part fits with its clamps removed.
|
||||
[Fact]
|
||||
public void IdenticalStockOptionsShareTheirProbes()
|
||||
{
|
||||
var rail = new ClampRail(1.5, 0.625, new[] { 6.0, 21.0, 36.0, 51.0, 66.0, 81.0, 96.0, 111.0 });
|
||||
var size = new Size(48, 120);
|
||||
var stocks = Enumerable.Range(0, 4)
|
||||
.Select(i => new NestPlateStock($"s{i}", size, null, 0, default, 1, null, rail.ResolveFor(size, 1)) { ClampRail = rail })
|
||||
.ToArray();
|
||||
var parts = Enumerable.Range(0, 8)
|
||||
.Select(i => JobBuilder.Rectangle($"full-{i}", w: 120, h: 48, count: 1, x: i * 1e-3))
|
||||
.ToArray();
|
||||
var job = new NestJob(parts, stocks);
|
||||
var engine = new OpenNest.Engine.NestingEngines.Default.DefaultNestingEngine();
|
||||
var raw = engine.Solve(job);
|
||||
Assert.Empty(raw.Plates);
|
||||
|
||||
var outcome = ClampRemovalRecovery.TryRecover(job, raw, engine, default);
|
||||
|
||||
Assert.NotNull(outcome);
|
||||
Assert.Equal(NestJobStatus.Complete, outcome!.Result.Status);
|
||||
Assert.Equal(8, outcome.Result.Plates.Count);
|
||||
Assert.All(outcome.Result.Plates, sheet => Assert.Empty(sheet.Stock.ClampZones));
|
||||
}
|
||||
}
|
||||
@@ -298,7 +298,9 @@ internal static class ClampRemovalRecovery
|
||||
) => Probe(part, stocks, options, engine, budget, token);
|
||||
|
||||
/// <summary>Whether this part, alone and at quantity one, places on an empty sheet of any
|
||||
/// of the given stock entries with the given engine. Spends one probe from the budget.</summary>
|
||||
/// of the given stock entries with the given engine. A question already answered for the
|
||||
/// same part, engine and physically identical stock is reused; otherwise it spends one
|
||||
/// probe from the budget.</summary>
|
||||
private static bool Probe(
|
||||
NestJobPart part,
|
||||
IReadOnlyList<NestPlateStock> stocks,
|
||||
@@ -308,6 +310,9 @@ internal static class ClampRemovalRecovery
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
var key = (engine, part.Id, string.Join(";", stocks.Select(Signature).Distinct(StringComparer.Ordinal)));
|
||||
if (budget.Answers.TryGetValue(key, out var known))
|
||||
return known;
|
||||
budget.Spend();
|
||||
var probePart = new NestJobPart(part.Id, part.Geometry, 1, part.Priority, part.Rotation);
|
||||
var probeStock = stocks.Select(s => Unlimited(s)).ToList();
|
||||
@@ -315,13 +320,30 @@ internal static class ClampRemovalRecovery
|
||||
var probeJob = new NestJob(new[] { probePart }, probeStock, probeOptions);
|
||||
token.ThrowIfCancellationRequested();
|
||||
var result = engine.Solve(probeJob, null, token);
|
||||
return result != null && result.Status == NestJobStatus.Complete;
|
||||
var fits = result != null && result.Status == NestJobStatus.Complete;
|
||||
budget.Answers[key] = fits;
|
||||
return fits;
|
||||
}
|
||||
|
||||
/// <summary>Everything about a stock entry that can change whether a lone part fits:
|
||||
/// sheet size, quadrant, spacings and the exact clamp zones (not its id, cost or
|
||||
/// quantity).</summary>
|
||||
private static string Signature(NestPlateStock stock)
|
||||
{
|
||||
var e = stock.EdgeSpacing;
|
||||
var zones = string.Join(",", stock.SheetClampZones.Select(z =>
|
||||
FormattableString.Invariant($"{z.Id}:{z.Bounds.X:R}/{z.Bounds.Y:R}/{z.Bounds.Length:R}/{z.Bounds.Width:R}")));
|
||||
return FormattableString.Invariant(
|
||||
$"{stock.Size.Width:R}x{stock.Size.Length:R}|q{stock.Quadrant}|{stock.PartSpacing:R}|{e.Left:R},{e.Bottom:R},{e.Right:R},{e.Top:R}|{zones}");
|
||||
}
|
||||
|
||||
private sealed class ProbeBudget(int solves)
|
||||
{
|
||||
private int left = solves;
|
||||
|
||||
/// <summary>Answers already paid for, by (engine, part, stock signatures).</summary>
|
||||
public Dictionary<(INestingEngine, string, string), bool> Answers { get; } = new();
|
||||
|
||||
/// <summary>Throws <see cref="ProbeBudgetExhaustedException"/> once spent; recovery
|
||||
/// then keeps the ordinary result.</summary>
|
||||
public void Spend()
|
||||
|
||||
Reference in new issue
Block a user