fix: cover Local G2 window targets before splits

This commit is contained in:
梁薄云
2026-08-01 00:20:15 +08:00
parent 144a0883b2
commit bd08a9bac6
2 changed files with 69 additions and 11 deletions
@@ -106,12 +106,25 @@ internal sealed class LocalG2WindowPlanner
double firstEvent = transitions[0].LocalArcLengthMeters; double firstEvent = transitions[0].LocalArcLengthMeters;
double lastEvent = transitions[transitions.Count - 1].LocalArcLengthMeters; double lastEvent = transitions[transitions.Count - 1].LocalArcLengthMeters;
double anchor = (firstEvent + lastEvent) / 2d; double anchor = (firstEvent + lastEvent) / 2d;
foreach (double target in BuildTargets(options, segmentLength)) IReadOnlyList<double> targets = BuildTargets(options, segmentLength);
double[] ratios = { 0.5d, 0.4d, 0.6d };
for (int ratioIndex = 0; ratioIndex < ratios.Length; ratioIndex++)
{ {
if (variants.Count >= options.MaximumCandidatesPerRegion) break; for (int targetIndex = 0; targetIndex < targets.Count; targetIndex++)
AddIfLegal(variants, target, 0.5d, anchor, firstEvent, lastEvent, segmentLength, true, options); {
AddIfLegal(variants, target, 0.4d, anchor, firstEvent, lastEvent, segmentLength, false, options); if (variants.Count >= options.MaximumCandidatesPerRegion)
AddIfLegal(variants, target, 0.6d, anchor, firstEvent, lastEvent, segmentLength, false, options); return new ReadOnlyCollection<LocalG2WindowVariant>(variants);
AddIfLegal(
variants,
targets[targetIndex],
ratios[ratioIndex],
anchor,
firstEvent,
lastEvent,
segmentLength,
ratioIndex == 0,
options);
}
} }
return new ReadOnlyCollection<LocalG2WindowVariant>(variants); return new ReadOnlyCollection<LocalG2WindowVariant>(variants);
} }
@@ -160,10 +173,10 @@ internal sealed class LocalG2WindowPlanner
double[] requested = double[] requested =
{ {
options.PreferredWindowLengthMeters, options.PreferredWindowLengthMeters,
0.75d * options.PreferredWindowLengthMeters,
1.25d * options.PreferredWindowLengthMeters,
options.MinimumWindowLengthMeters, options.MinimumWindowLengthMeters,
options.MaximumWindowLengthMeters, options.MaximumWindowLengthMeters,
0.75d * options.PreferredWindowLengthMeters,
1.25d * options.PreferredWindowLengthMeters,
}; };
var targets = new List<double>(requested.Length); var targets = new List<double>(requested.Length);
for (int index = 0; index < requested.Length; index++) for (int index = 0; index < requested.Length; index++)
@@ -262,6 +275,10 @@ internal sealed class LocalG2WindowPlanner
transitions = new[] { Transition(0.1d, 0) }; transitions = new[] { Transition(0.1d, 0) };
segmentLength = 1d; segmentLength = 1d;
break; break;
case "InteriorCoverage":
transitions = new[] { Transition(1d, 0) };
segmentLength = 2d;
break;
default: default:
throw new ArgumentOutOfRangeException(nameof(scenario)); throw new ArgumentOutOfRangeException(nameof(scenario));
} }
@@ -305,7 +322,25 @@ internal sealed class LocalG2WindowPlanner
exactEnvelope &= Math.Abs(region.MaximumEndArcLengthMeters - maximumEnd) <= 1e-9d; exactEnvelope &= Math.Abs(region.MaximumEndArcLengthMeters - maximumEnd) <= 1e-9d;
} }
LocalG2WindowVariant first = regions[0].WindowVariants[0]; IReadOnlyList<LocalG2WindowVariant> firstVariants = regions[0].WindowVariants;
bool representativeTargetsFirst =
firstVariants.Count >= 3 &&
Math.Abs(WindowLength(firstVariants[0]) - 0.50d) <= 1e-9d &&
Math.Abs(WindowLength(firstVariants[1]) - 0.20d) <= 1e-9d &&
Math.Abs(WindowLength(firstVariants[2]) - 0.80d) <= 1e-9d;
bool hasAsymmetricVariant = false;
for (int index = 0; index < firstVariants.Count; index++)
{
if (Math.Abs(
firstVariants[index].LeftWindowLengthMeters -
firstVariants[index].RightWindowLengthMeters) > 1e-9d)
{
hasAsymmetricVariant = true;
break;
}
}
LocalG2WindowVariant first = firstVariants[0];
return new WindowPlanningTestSnapshot( return new WindowPlanningTestSnapshot(
regions.Count, regions.Count,
string.Join(",", counts), string.Join(",", counts),
@@ -313,7 +348,10 @@ internal sealed class LocalG2WindowPlanner
exactEnvelope, exactEnvelope,
first.LeftWindowLengthMeters, first.LeftWindowLengthMeters,
first.RightWindowLengthMeters, first.RightWindowLengthMeters,
string.Join("|", signature)); string.Join("|", signature),
firstVariants.Count,
representativeTargetsFirst,
hasAsymmetricVariant);
} }
public sealed class WindowPlanningTestSnapshot public sealed class WindowPlanningTestSnapshot
@@ -325,7 +363,10 @@ internal sealed class LocalG2WindowPlanner
bool exactEnvelope, bool exactEnvelope,
double firstLeftLength, double firstLeftLength,
double firstRightLength, double firstRightLength,
string signature) string signature,
int firstRegionVariantCount,
bool representativeTargetsFirst,
bool hasAsymmetricVariant)
{ {
RegionCount = regionCount; RegionCount = regionCount;
TransitionCounts = transitionCounts; TransitionCounts = transitionCounts;
@@ -334,6 +375,9 @@ internal sealed class LocalG2WindowPlanner
FirstLeftLength = firstLeftLength; FirstLeftLength = firstLeftLength;
FirstRightLength = firstRightLength; FirstRightLength = firstRightLength;
Signature = signature; Signature = signature;
FirstRegionVariantCount = firstRegionVariantCount;
RepresentativeTargetsFirst = representativeTargetsFirst;
HasAsymmetricVariant = hasAsymmetricVariant;
} }
public int RegionCount { get; } public int RegionCount { get; }
@@ -343,8 +387,14 @@ internal sealed class LocalG2WindowPlanner
public double FirstLeftLength { get; } public double FirstLeftLength { get; }
public double FirstRightLength { get; } public double FirstRightLength { get; }
public string Signature { get; } public string Signature { get; }
public int FirstRegionVariantCount { get; }
public bool RepresentativeTargetsFirst { get; }
public bool HasAsymmetricVariant { get; }
} }
private static double WindowLength(LocalG2WindowVariant variant) =>
variant.EndArcLengthMeters - variant.StartArcLengthMeters;
private static CurvatureTransition Transition(double arcLength, int index) private static CurvatureTransition Transition(double arcLength, int index)
{ {
return new CurvatureTransition( return new CurvatureTransition(
@@ -116,7 +116,15 @@ Assert-True ($boundary.FirstRightLength -gt $boundary.FirstLeftLength) `
Assert-True ($boundary.MaximumWindowLength -le 0.80 + 1e-9) ` Assert-True ($boundary.MaximumWindowLength -le 0.80 + 1e-9) `
'No candidate window may exceed 0.80 m total length.' 'No candidate window may exceed 0.80 m total length.'
foreach ($snapshot in @($separated, $mergeable, $partition, $boundary)) { $coverage = $planScenario.Invoke($null, @('InteriorCoverage'))
Assert-True $coverage.RepresentativeTargetsFirst `
'Preferred, minimum, and maximum balanced targets must precede asymmetric variants.'
Assert-True $coverage.HasAsymmetricVariant `
'Default window planning must retain a legal asymmetric variant after balanced coverage.'
Assert-True ($coverage.FirstRegionVariantCount -le 12) `
'Window planning must obey the configured default cap.'
foreach ($snapshot in @($separated, $mergeable, $partition, $boundary, $coverage)) {
Assert-True $snapshot.ExactEnvelope 'Region envelope must equal the extrema of actual legal variants.' Assert-True $snapshot.ExactEnvelope 'Region envelope must equal the extrema of actual legal variants.'
Assert-True ($snapshot.MaximumWindowLength -le 0.80 + 1e-9) ` Assert-True ($snapshot.MaximumWindowLength -le 0.80 + 1e-9) `
'MaximumWindowLengthMeters is a total, not a per-side length.' 'MaximumWindowLengthMeters is a total, not a per-side length.'