Skip to content

boxstacks: skip unsized bins in Solution::feasible_axle_weights - #576

Open
HansBug wants to merge 3 commits into
fontanf:masterfrom
HansBug:fix/boxstacks-axle-weights-unsized-bins
Open

HansBug wants to merge 3 commits into
fontanf:masterfrom
HansBug:fix/boxstacks-axle-weights-unsized-bins

Conversation

@HansBug

@HansBug HansBug commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #575.

SolutionBuilder::build calls update_indicators once per bin, and each call recomputes the aggregate feasibility over every bin. The per-group weight vectors of a bin are only sized by its own pass, so while the first bins are processed the later ones still have empty weight / weight_weighted_sum vectors, and feasible_axle_weights indexed them anyway: undefined behaviour that _GLIBCXX_ASSERTIONS reports as an out-of-range std::vector::operator[] and that is an access violation with MSVC. feasible_total_weight already skips such bins; this does the same. The last pass sees every bin sized, so the final feasibility is unchanged.

Reached by every multi-bin solution rebuilt on another instance (Reduction::unreduce_solution, on by default since 24decc4, and InstanceFlipper::unflip_solution) and by reading a multi-bin certificate.

Two commits:

  • boxstacks: skip unsized bins in Solution::feasible_axle_weights — the guard, plus BoxStacks.SolutionBuilderTwoBinsBuiltAtOnce, the smallest solution that reaches it.
  • boxstacks: certificate-reading tests for multi-bin solutions — a new instance data/boxstacks/tests/bin_packing_postal_cartons_eur_pallets (three postal carton sizes on 1200 x 800 x 1800 EUR pallets, bin packing, optimal in two pallets so optimize reaches the certificate without a time limit), added to BoxStacksOptimizeTest; and BoxStacksSolutionBuilderReadTest, which reads a multi-bin certificate through SolutionBuilder::read + build and checks bins, fullness and feasibility, for this instance and for bin_packing_two_item_types_pallets_time_limit.

Validation (Ubuntu 24.04, GCC 13, RelWithDebInfo with -fsanitize=address -D_GLIBCXX_ASSERTIONS, CLP + HiGHS):


Update: added a third commit, boxstacks: accumulate axle weight feasibility per bin instead of rescanning, replacing the "skip unsized bins" rescan from the first commit. update_indicators(bin_pos) is only ever called once per bin_pos, in increasing order, and bins are never removed once added, so a bin's own axle weight feasibility, once computed, cannot change. feasible_axle_weights() is now split into a per-bin feasible_axle_weights(bin_pos) and an axle_weights_feasible_ flag ANDed with it right after bin_pos is sized in update_indicators, instead of rescanning every bin (sized or not) on every call. The no-arg feasible_axle_weights() is kept as a full loop over sized bins for existing external callers. Tests from the second commit are unchanged and still pass (21/21, PackingSolver_boxstacks_test, Ubuntu 24.04 GCC 13 RelWithDebInfo).

HansBug and others added 2 commits September 14, 2026 16:20
'SolutionBuilder::build' calls 'update_indicators' once per bin, and each
call recomputes the aggregate feasibility over every bin of the solution.
The per-group weight vectors of a bin are only sized by its own
'update_indicators' pass, so while the first bins are processed the later
ones still have empty 'weight' / 'weight_weighted_sum' vectors, and
'feasible_axle_weights' indexed them anyway: undefined behaviour that
'_GLIBCXX_ASSERTIONS' reports as an out-of-range 'std::vector::operator[]'
and that is an access violation with MSVC builds. 'feasible_total_weight'
already skips such bins; do the same here. The last pass sees every bin
sized, so the final feasibility is unchanged.

Every multi-bin solution rebuilt on another instance goes through
'SolutionBuilder::build': 'Reduction::unreduce_solution' (since instance
reduction runs by default, every bin packing / variable-sized bin packing
solve that uses at least two bins hits this) and
'InstanceFlipper::unflip_solution'. The 'BoxStacksOptimizeTest' bin packing
case aborts under '_GLIBCXX_ASSERTIONS' and crashes on Windows before this
change; the new 'BoxStacks.SolutionBuilderTwoBinsBuiltAtOnce' test is the
smallest solution that reaches it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A new instance, data/boxstacks/tests/bin_packing_postal_cartons_eur_pallets:
three postal carton sizes (530 x 290 x 370, 530 x 230 x 290, 430 x 210 x 270
mm, 14 / 16 / 20 copies, one stackability id each) packed on 1200 x 800 x
1800 EUR pallets, bin packing; the certificate uses two pallets, which is
optimal (volume bound 2), so optimize() reaches it without a time limit.

'BoxStacksSolutionBuilderReadTest' reads a multi-bin certificate through
'SolutionBuilder::read' + 'build' and checks bins, fullness and
feasibility, for this instance and for
bin_packing_two_item_types_pallets_time_limit: with the previous commit
reverted both abort under '_GLIBCXX_ASSERTIONS' in
'Solution::feasible_axle_weights', as does the new instance in
'BoxStacksOptimizeTest'.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…anning

SolutionBuilder::build calls update_indicators(bin_pos) once per bin, in
order, and each call recomputed feasible_axle_weights() over every bin,
including ones after bin_pos whose weight vectors update_indicators
hadn't sized yet - the out-of-range read the PR's first commit guarded
against.

update_indicators(bin_pos) is only ever called once per bin_pos, in
increasing order, and bins are never removed once added, so a bin's own
weight feasibility, once computed, cannot change. Accumulate
total_weight_feasible_ and axle_weights_feasible_ incrementally in
update_indicators instead of rescanning every bin (sized or not) on
every call.

feasible_total_weight() and feasible_axle_weights() are replaced by
plain total_weight_feasible()/axle_weights_feasible() getters, matching
the existing callback_feasible()/item_copies_feasible() style;
feasible_axle_weights(bin_pos), only ever called internally, is now
private; and the feasibility getters (callback_feasible,
total_weight_feasible, axle_weights_feasible, item_copies_feasible,
feasible) are grouped together in the 'Getters: feasibility' section.
@fontanf
fontanf force-pushed the fix/boxstacks-axle-weights-unsized-bins branch from b697fa5 to 13e559e Compare September 14, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants