Files
OpenNest/OpenNest.Core/AutomaticCutOffStripSearch.cs
T

138 lines
5.6 KiB
C#

using System;
using System.Collections.Generic;
namespace OpenNest;
/// <summary>Exact layered search over sorted positions; each transition uses a sliding minimum.</summary>
internal static class AutomaticCutOffStripSearch
{
internal static double[] FindPositions(double start, double end, int count, double spacing,
double minimum, double step, Func<double, bool> allowed,
Func<double, (double Length, int SegmentCount)> measure)
{
var rows = new List<double[]>(count);
var nominal = new double[count];
for (var line = 1; line <= count; line++)
{
var today = start + line * spacing;
nominal[line - 1] = today;
var low = System.Math.Max(start + line * minimum, end - (count - line + 1) * spacing);
var high = System.Math.Min(today, end - AutomaticCutOffPlanner.GeometryTolerance);
var positions = new List<double>();
// Integer indices keep candidates on the origin-based lattice without cumulative drift.
var first = (long)System.Math.Ceiling(low / step);
var last = (long)System.Math.Floor(high / step);
for (var index = first; index <= last; index++)
{
var position = index * step;
if (position >= low && position <= high && allowed(position))
positions.Add(position);
}
if (allowed(today))
positions.Add(today);
positions.Sort();
for (var index = positions.Count - 1; index > 0; index--)
if (positions[index] == positions[index - 1])
positions.RemoveAt(index);
if (positions.Count == 0)
return null;
rows.Add(positions.ToArray());
}
return Solve(start, rows, nominal, spacing, minimum, measure);
}
// Kept independent of geometry so tests can compare against exhaustive, non-greedy oracles.
internal static double[] Solve(double start, IReadOnlyList<double[]> rows,
IReadOnlyList<double> nominal, double spacing, double minimum,
Func<double, (double Length, int SegmentCount)> measure)
{
var previous = new List<State> { new() { Position = start } };
for (var line = 0; line < rows.Count; line++)
{
var current = new List<State>(rows[line].Length);
var deque = new int[previous.Count];
var head = 0;
var tail = 0;
var added = 0;
foreach (var position in rows[line])
{
var upper = position - minimum + AutomaticCutOffPlanner.GeometryTolerance;
var lower = position - spacing - AutomaticCutOffPlanner.GeometryTolerance;
while (added < previous.Count && previous[added].Position <= upper)
{
while (tail > head && Compare(previous[added], previous[deque[tail - 1]]) < 0)
tail--;
deque[tail++] = added++;
}
while (head < tail && previous[deque[head]].Position < lower)
head++;
if (head == tail)
continue;
var parent = previous[deque[head]];
var cost = measure(position);
current.Add(new State
{
Position = position,
Length = parent.Length + System.Math.Round(cost.Length, 6),
Pierces = parent.Pierces + cost.SegmentCount,
Deviation = parent.Deviation + System.Math.Abs(nominal[line] - position),
Parent = parent,
});
}
if (current.Count == 0)
return null;
AssignLexicographicRanks(current, previous.Count);
previous = current;
}
State best = null;
foreach (var state in previous)
if (best == null || Compare(state, best) < 0)
best = state;
var result = new double[rows.Count];
for (var line = result.Length - 1; line >= 0; line--)
{
result[line] = best.Position;
best = best.Parent;
}
return result;
}
private static int Compare(State left, State right)
{
var comparison = left.Length.CompareTo(right.Length);
if (comparison == 0)
comparison = left.Pierces.CompareTo(right.Pierces);
if (comparison == 0)
comparison = left.Deviation.CompareTo(right.Deviation);
return comparison != 0 ? comparison : left.LexicographicRank.CompareTo(right.LexicographicRank);
}
private static void AssignLexicographicRanks(List<State> states, int parentCount)
{
// Stable counting sort by prefix rank. Positions are already sorted, so within a
// prefix their order is lexicographic. Ranking and transitions are linear per row.
var offsets = new int[parentCount];
foreach (var state in states)
offsets[state.Parent.LexicographicRank]++;
var total = 0;
for (var index = 0; index < offsets.Length; index++)
{
var count = offsets[index];
offsets[index] = total;
total += count;
}
foreach (var state in states)
state.LexicographicRank = offsets[state.Parent.LexicographicRank]++;
}
private sealed class State
{
public double Position;
public double Length;
public int Pierces;
public double Deviation;
public State Parent;
public int LexicographicRank;
}
}