Skip to content

rectangleguillotine: fix column_generation_strips distance-1-cuts handling and search gaps#426

Merged
fontanf merged 1 commit into
masterfrom
column-generation-strips-distance-1-cuts
Jul 3, 2026
Merged

rectangleguillotine: fix column_generation_strips distance-1-cuts handling and search gaps#426
fontanf merged 1 commit into
masterfrom
column-generation-strips-distance-1-cuts

Conversation

@fontanf

@fontanf fontanf commented Jul 2, 2026

Copy link
Copy Markdown
Owner

column_generation_strips previously ignored maximum_distance_1_cuts and minimum_distance_1_cuts entirely when pricing candidate first-stage (1-cut) strips, and had several related bugs uncovered while building out coverage for every (number_of_stages, cut_type, orientation) combination:

  • maximum_distance_1_cuts: cap the per-column search width (first_stage_available_width), so no generated strip can exceed it.
  • minimum_distance_1_cuts: pad a strip's boundary up to the minimum with waste (mirroring how tree_search pushes 1-cut positions outward instead of rejecting narrower placements), and stop searching narrower widths once they would only be padded back up anyway. The exact-width generators (generate_1e_patterns and the eager generate_all_columns_1r_patterns/ 2h_patterns) have no depth-3 narrowing cut, so a segment narrower than the minimum can't be padded without a dimension mismatch: skip those instead.
  • generate_all_columns_1r_patterns/2h_patterns were also missing a bound check on the (possibly rotated) item width against the bin width, crashing SolutionBuilder::add_node whenever a rotated item was wider than the bin.
  • generate_lower_stage_patterns charged the master LP the strip's unpadded width while building its reconstructed geometry at the padded width, letting the LP pack more strips than physically fit; both now agree.
  • generate_lower_stage_patterns's sub-instance previously inherited minimum_distance_1_cuts/maximum_distance_1_cuts/minimum_distance_2_cuts and maximum_number_2_cuts unchanged from the outer instance. Since the sub-instance's own 1-cuts are the outer's 2-cuts (and its 2-cuts are the outer's 3-cuts, which have no matching outer constraint), it now inherits minimum_distance_1_cuts from the outer's minimum_distance_2_cuts and resets the rest, fixing several cases where the recursion under- or over-constrained the sub-problem.
  • Every generated strip is now also considered directly as a candidate whole-bin solution (the rest of the bin auto-fills as waste), so the algorithm can find the optimum when it consists of a single strip even if the master's combinatorial search over multiple strips never isolates it.
  • The pricing solver now runs one extra pass per branch with all duals forced to zero (real profit instead of reduced profit), forcing the lower-stage recursion to run regardless of the "a good column was already found" shortcut. This guarantees the real-profit-optimal column for the remaining capacity is always generated, regardless of where the master's dual values happen to be at that point.

tree_search's minimum_distance_1_cuts handling had a matching bug: for 2-staged instances, insertion.x1 (checked against minimum_distance_1_cuts) is not a real 1-cut once the possibly-flipped first-stage decision is made, while the true width axis (insertion.y2) was only checked against minimum_distance_2_cuts, which defaults to unconstrained. minimum_distance_1_ cuts_/minimum_distance_2_cuts_ are now precomputed once per BranchingScheme (0/minimum_distance_1_cuts for 2-staged instances, unchanged otherwise) instead of re-derived on every node.

Added 32 knapsack_[23][ehnr]v[or][min1cut] test instances, wired into both RectangleGuillotineTreeSearchTest and RectangleGuillotineColumnGenerationStrips Test, each verified to genuinely require its stated stage count, cut type, and (for rotatable variants) actual rotation, with the min1cut variant always yielding a different optimum from its base. Also added knapsack_2nho_2, a captured reduced-cost sub-problem that pinned a since-fixed automatic_stop early-termination gap in the limited discrepancy search, and switched RectangleGuillotineColumnGenerationStripsTest to NotAnytimeSequential so it exercises that code path. Updated the knapsack_3rvo_min1cut/knapsack_3rvr min1cut reference certificates to the better solutions column_generation_ strips now finds, and dropped those two from RectangleGuillotineTreeSearchTest since tree_search's search cannot reach them (traced to overly permissive dominance-based pruning in BranchingScheme::dominates, not yet fixed).

…dling and search gaps

column_generation_strips previously ignored maximum_distance_1_cuts and
minimum_distance_1_cuts entirely when pricing candidate first-stage (1-cut)
strips, and had several related bugs uncovered while building out coverage
for every (number_of_stages, cut_type, orientation) combination:

- maximum_distance_1_cuts: cap the per-column search width
  (first_stage_available_width), so no generated strip can exceed it.
- minimum_distance_1_cuts: pad a strip's boundary up to the minimum with
  waste (mirroring how tree_search pushes 1-cut positions outward instead of
  rejecting narrower placements), and stop searching narrower widths once
  they would only be padded back up anyway. The exact-width generators
  (generate_1e_patterns and the eager generate_all_columns_1r_patterns/
  2h_patterns) have no depth-3 narrowing cut, so a segment narrower than the
  minimum can't be padded without a dimension mismatch: skip those instead.
- generate_all_columns_1r_patterns/2h_patterns were also missing a bound
  check on the (possibly rotated) item width against the bin width, crashing
  SolutionBuilder::add_node whenever a rotated item was wider than the bin.
- generate_lower_stage_patterns charged the master LP the strip's unpadded
  width while building its reconstructed geometry at the padded width,
  letting the LP pack more strips than physically fit; both now agree.
- generate_lower_stage_patterns's sub-instance previously inherited
  minimum_distance_1_cuts/maximum_distance_1_cuts/minimum_distance_2_cuts
  and maximum_number_2_cuts unchanged from the outer instance. Since the
  sub-instance's own 1-cuts are the outer's 2-cuts (and its 2-cuts are the
  outer's 3-cuts, which have no matching outer constraint), it now inherits
  minimum_distance_1_cuts from the outer's minimum_distance_2_cuts and
  resets the rest, fixing several cases where the recursion under- or
  over-constrained the sub-problem.
- Every generated strip is now also considered directly as a candidate
  whole-bin solution (the rest of the bin auto-fills as waste), so the
  algorithm can find the optimum when it consists of a single strip even if
  the master's combinatorial search over multiple strips never isolates it.
- The pricing solver now runs one extra pass per branch with all duals
  forced to zero (real profit instead of reduced profit), forcing the
  lower-stage recursion to run regardless of the "a good column was already
  found" shortcut. This guarantees the real-profit-optimal column for the
  remaining capacity is always generated, regardless of where the master's
  dual values happen to be at that point.

tree_search's minimum_distance_1_cuts handling had a matching bug: for
2-staged instances, insertion.x1 (checked against minimum_distance_1_cuts)
is not a real 1-cut once the possibly-flipped first-stage decision is made,
while the true width axis (insertion.y2) was only checked against
minimum_distance_2_cuts, which defaults to unconstrained. minimum_distance_1_
cuts_/minimum_distance_2_cuts_ are now precomputed once per BranchingScheme
(0/minimum_distance_1_cuts for 2-staged instances, unchanged otherwise)
instead of re-derived on every node.

Added 32 knapsack_[23][ehnr]v[or][_min1cut] test instances, wired into both
RectangleGuillotineTreeSearchTest and RectangleGuillotineColumnGenerationStrips
Test, each verified to genuinely require its stated stage count, cut type,
and (for rotatable variants) actual rotation, with the min1cut variant always
yielding a different optimum from its base. Also added knapsack_2nho_2, a
captured reduced-cost sub-problem that pinned a since-fixed automatic_stop
early-termination gap in the limited discrepancy search, and switched
RectangleGuillotineColumnGenerationStripsTest to NotAnytimeSequential so it
exercises that code path. Updated the knapsack_3rvo_min1cut/knapsack_3rvr_
min1cut reference certificates to the better solutions column_generation_
strips now finds, and dropped those two from RectangleGuillotineTreeSearchTest
since tree_search's search cannot reach them (traced to overly permissive
dominance-based pruning in BranchingScheme::dominates, not yet fixed).
@fontanf fontanf force-pushed the column-generation-strips-distance-1-cuts branch from eda5f9e to 25337cd Compare July 2, 2026 22:39
@fontanf fontanf merged commit 9e09f1b into master Jul 3, 2026
3 checks passed
@fontanf fontanf deleted the column-generation-strips-distance-1-cuts branch July 3, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant