Skip to content

Add the setting child_order and the branching rule LEAST_FRACTIONAL - #2

Draft
baggepinnen wants to merge 2 commits into
bnb-portfoliofrom
bnb-child-order
Draft

baggepinnen wants to merge 2 commits into
bnb-portfoliofrom
bnb-child-order

Conversation

@baggepinnen

Copy link
Copy Markdown
Collaborator

Adds the setting child_order and the branching rule DAQP_BRANCHING_LEAST_FRACTIONAL to branch and bound. Based on JuliaComputing/daqp#1 (bnb-portfolio) so that portfolios of daqp_solve_parallel can mix rules and child orders.

Design

  • DAQPSettings.child_order (appended at the end of the struct) selects the child of a node that is explored first: DAQP_CHILD_NEAREST (0, default, previous behavior), DAQP_CHILD_FARTHEST (1), DAQP_CHILD_UP (2, upper bound active), DAQP_CHILD_DOWN (3, lower bound active).
  • daqp_branch_on computes the lower bounds of the near and the far child as before and attaches each one to the child that activates the corresponding bound: the child spawned in node+1 (explored first) receives the bound of the bound it activates, so a flipped order does not exchange the bounds used for pruning.
  • DAQP_BRANCHING_LEAST_FRACTIONAL (4) selects, among the binary constraints that the relaxation does not satisfy, the one with the smallest distance to its nearest bound relative to half the distance between its bounds (ties: the first in the order of the constraints). It shares the loop of MOST_FRACTIONAL in daqp_get_branch_id.
  • Updated mirrors of DAQPSettings: daqp_default_settings, codegen/codegen.c (positional initializer), Julia DAQPSettings and constants, Python daqp.pxd/daqp.pyx (keyword, settings getter/setter), MATLAB daqpmex.c, and docs/docs/settings.md, docs/docs/miqp.md.

Tests

  • New interfaces/daqp-eigen/tests/31_bnb_child_order.cpp: 40 problems x {eq_reduction off, on} x 4 rules (FIRST, LAST, MOST_FRACTIONAL, LEAST_FRACTIONAL) x 4 child orders reach the optimum of the default (abs_subopt = 0); the default equals NEAREST node for node; the child order changes the node count of every rule; LEAST and MOST fractional differ; a 6-thread daqp_solve_parallel portfolio of mixed rules and child orders finds the same optimum. Exchanging the two lower bounds in daqp_branch_on (mutation check) makes this test fail.
  • Julia testset "BnB branching rules": every rule x child order reaches the optimum, node counts differ between child orders, the generated code holds child_order (compiled with gcc). Testset "Parallel BnB": a 4-model portfolio of mixed rules and child orders finds the optimum.
  • ctest -j32: 33/33 passed (including the Julia test suite).

Node counts over the 40 problems of the test (eq_reduction off):

rule nearest farthest up down
first 4525 9378 7477 6609
last 4355 9888 7432 6722
most fractional 5151 6679 4868 6271
least fractional 4358 16114 13309 8130

Benchmark

Release build on demeter2 (shared machine, other jobs running at low priority, so the times contain some noise). Every problem is solved with a fresh model per solve, wall time around solve (minimum of 3 repetitions if below 0.2 s), time_limit = 5 s.

Problem families (problems x variables / binaries):

  • pwa N8, pwa N12: LinearMPC example pwa_rotation (27 states each; 32/8, 48/12).
  • spring N6, spring N8: LinearMPC example hybrid_spring (35 each; 90/36, 120/48).
  • sat N20, sat N30: LinearMPC example satellite (binary thrusters; 35 each; 60/40, 90/60).
  • rand 30/20, rand 40/30: generate_test_MIQP (20 and 10 problems).
  • UC 5x8: unit commitment, 5 units x 8 steps, on/off binaries with minimum and maximum power and a demand constraint (12 problems; 80/40).
  • thermo N30: condensed MPC of a first-order thermal system with an on/off input over 30 steps (12 problems; 30/30).

The MIQPs of the LinearMPC examples were taken along the closed-loop trajectory of the example scenario and at random states. The example invpend_contact is infeasible already at the origin (also with LinearMPC and its own DAQP) and is excluded. No combination returned an objective different from the default (relative tolerance 1e-6).

Single rule x child order

Shifted geometric mean (shift 0.1 ms) of the time relative to the fastest combination on each problem (1.00 = fastest on every problem):

rule / child order pwa N8 pwa N12 spring N6 spring N8 sat N20 sat N30 rand 30/20 rand 40/30 UC 5x8 thermo N30
first/nearest 1.08 1.08 1.00 1.00 1.04 1.17 1.08 1.13 2.07 1.00
first/farthest 1.15 1.14 6.54 7.55 26.25 94.96 1.98 5.57 3.27 3.61
first/up 1.13 1.14 6.81 7.64 8.74 21.96 1.32 2.30 3.01 2.94
first/down 1.07 1.08 1.00 1.01 7.99 17.08 1.37 2.25 2.40 2.36
last/nearest 2.09 3.47 9.54 15.07 1.11 1.23 1.09 1.16 2.39 3.26
last/farthest 2.29 3.62 21.76 31.89 59.06 197.87 1.99 5.00 3.33 9.26
last/up 2.30 3.68 22.52 31.97 11.71 38.71 1.22 2.16 2.71 8.54
last/down 2.06 3.40 9.44 14.83 13.13 29.25 1.42 2.03 2.90 6.58
mostfrac/nearest 1.37 1.63 7.75 13.46 1.08 1.17 1.05 1.09 2.45 7.51
mostfrac/farthest 1.47 1.72 7.82 13.28 21.91 51.51 1.46 2.77 2.19 9.92
mostfrac/up 1.47 1.74 7.98 13.16 5.11 9.70 1.22 1.68 1.13 9.23
mostfrac/down 1.36 1.61 7.76 13.49 2.84 4.15 1.19 1.77 3.55 8.48
leastfrac/nearest 1.06 1.08 3.72 5.44 1.21 1.52 1.08 1.18 3.80 4.85
leastfrac/farthest 1.14 1.14 8.75 14.08 106.18 645.47 2.47 8.60 3.92 14.75
leastfrac/up 1.13 1.14 8.90 14.04 13.45 56.79 1.44 3.07 3.92 12.27
leastfrac/down 1.06 1.08 3.85 5.54 20.71 95.18 1.47 3.02 3.81 7.10

Absolute times (shifted geometric mean) of the default first/nearest: pwa N8 0.22 ms, pwa N12 3.96 ms, spring N6 10.8 ms, spring N8 90.8 ms, sat N20 0.095 ms, sat N30 0.55 ms, rand 0.05 / 0.15 ms, UC 2636 ms, thermo 23.4 ms.

Number of problems on which a combination is the fastest (ties within 0.01 % counted for all), selected families:

family most wins
spring N6 / N8 first/down 21 / 18, first/nearest 15 / 17
sat N20 / N30 mostfrac/nearest 15 / 14, last/nearest 13 / 14, first/nearest 6 / 8
rand 30/20 mostfrac/nearest 10
UC 5x8 mostfrac/up 10 of 12
thermo N30 first/nearest 12 of 12
pwa N8 leastfrac/down 9, leastfrac/nearest 7

UC 5x8 within the time limit of 5 s: mostfrac/up solves 9 of 12, first/nearest 5, last/nearest 8, leastfrac (any order) at most 1. The times of the UC family are bounded by the limit, so its ratios understate the differences.

Observations:

  • FIRST is the best rule on the MPC problems with a mode or input binary per time step (spring, thermostat, pwa), where the order of the constraints is the order of the time steps. LAST and MOST_FRACTIONAL are best on the satellite problems and the random problems. On UC only MOST_FRACTIONAL with UP is competitive.
  • FARTHEST is never competitive (up to two orders of magnitude slower). UP and DOWN coincide with NEAREST or FARTHEST when the relaxation of the binaries is mostly on one side of the midpoint (first/down = first/nearest on spring; first/up = first/farthest on pwa). The only case where a child order other than NEAREST is the best by a substantive margin is mostfrac/up on UC (unit commitment: switching a unit on first finds feasible commitments early).
  • LEAST_FRACTIONAL is the fastest on pwa (by about 2 %, it explores the same nodes as FIRST there, since each node has few fractional binaries) and the second best rule on spring (3.7x the time of FIRST, versus 7.8x for MOST_FRACTIONAL and 9.5x for LAST). It is not the fastest by a substantive margin on any family of this set, and it is the slowest rule on UC.
  • Complementary pairs: first/nearest (time-step-structured MPC) with mostfrac/nearest or last/nearest (satellite, random), and mostfrac/up (UC).

Parallel portfolios (daqp_solve_parallel)

248 problems (all families above). Greedy portfolios: members added in order of the largest reduction of the shifted geometric mean of the per-problem best member time, starting from first/nearest (selected on the same problems, so in-sample). "> 1 ms": the 135 problems on which first/nearest takes more than 1 ms.

portfolio threads members virtual best sgm [ms] parallel sgm [ms] parallel sgm, > 1 ms [ms] solved within limit
default (single) 1 first/nearest 2.719 2.719 23.73 241/248
rules3_nearest 3 first, last, mostfrac (all nearest) 2.574 2.760 20.64 245/248
greedy3 3 first/nearest, mostfrac/nearest, mostfrac/up 2.524 2.817 21.11 246/248
first_all_orders4 4 first with all four child orders 2.673 3.115 23.24 241/248
rules4_nearest 4 first, last, mostfrac, leastfrac (all nearest) 2.567 2.715 20.39 246/248
greedy4 4 greedy3 + first/up 2.495 2.852 20.78 246/248
greedy6 6 greedy4 + last/nearest, leastfrac/nearest 2.473 3.024 20.62 246/248
mix6 6 rules4_nearest + mostfrac/up, leastfrac/down 2.501 2.927 20.60 246/248
greedy8 8 greedy6 + first/down, mostfrac/down 2.471 3.233 20.89 246/248
rules4_x_near_far8 8 4 rules x {nearest, farthest} 2.526 3.349 21.21 245/248

Per family (parallel sgm [ms], default in parentheses), rules4_nearest: spring N8 83.4 (90.8), UC 1284 (2636), sat N30 0.53 (0.55), pwa N12 3.41 (3.96), sat N20 0.137 (0.095), rand 30/20 0.098 (0.051). On UC the parallel time is below the best single member (1996 ms) since the shared incumbent bound prunes the trees of all threads.

  • On problems below about 0.2 ms the thread creation and the setup of additional workspaces make the parallel solve slower than the default; the cost increases with the number of threads (sat N20: 0.14 ms with 4 threads, 0.26 ms with 8).
  • Portfolios with more than 4 threads do not reduce the time of the hard problems further on this set; child orders other than NEAREST contribute only through mostfrac/up on UC, which rules4_nearest also solves within the limit (shared bound).
  • A portfolio of child orders of a single rule (first_all_orders4) is not useful.
  • Recommended: the 4-thread portfolio first/nearest, last/nearest, mostfrac/nearest, leastfrac/nearest, or greedy4 (first/nearest, mostfrac/nearest, mostfrac/up, first/up) if problems of unit-commitment type are expected. For problems that take less than about 0.2 ms, the single default.

Open points

  • Inconsistency of the parallel solve (base branch JuliaComputing/daqp#1) on the invpend_contact problems: daqp_solve returns infeasible (-1, 1 node), while daqp_solve_parallel with presolve = 1 returns exit flag 5 with fval about 1.8e44 after the time limit (with presolve = 0 it returns -1). The problems have 36 soft constraints that can only be satisfied with very large slacks; the single solve prunes the root since its objective exceeds fval_bound = DAQP_INF, while daqp_bnb_lb adds the constant c to fval_bound when work->bnb->shared != NULL, which presumably raises the bound above the root objective. Not addressed here.
  • The greedy portfolios are selected on the benchmark problems themselves; a validation on other problem families (e.g. the DyadLinearMPC fork examples) has not been done.
  • The benchmark scripts are not part of this PR (local to the working directory).

Host: demeter2, session session_01JGy8Ya4ZvmiQUA33aivGGJ (sub-agent), working directory /tmp/daqp-childorder

🤖 Generated with Claude Code

DAQPSettings.child_order selects which child of a branch-and-bound node is
explored first: the one whose bound of the binary constraint is nearest to
the relaxation (DAQP_CHILD_NEAREST, the default and the previous behavior),
the farthest (DAQP_CHILD_FARTHEST), the one with the upper bound active
(DAQP_CHILD_UP) or the one with the lower bound active (DAQP_CHILD_DOWN).
The lower bounds of the children, which are computed from the distance of
the relaxation to the nearest and the farthest bound, remain attached to the
child that activates the corresponding bound.

DAQP_BRANCHING_LEAST_FRACTIONAL selects, among the binary constraints that
the relaxation does not satisfy, the one nearest to its nearest bound
relative to the distance between its bounds.

The field is appended to DAQPSettings; the code generation, the Julia,
Python and MATLAB interfaces and the documentation are updated. The test
31_bnb_child_order and the Julia test sets check that every rule with every
child order finds the optimal solution, that the child order changes the
explored tree, that a parallel portfolio of rules and child orders finds the
same solution and that generated code holds the setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
child_order is not compared by daqp_parallel_compatible, so the threads of a
parallel solve may use different child orders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@baggepinnen

Copy link
Copy Markdown
Collaborator Author

Merged the updated bnb-portfolio into this branch. It contains:

  • the fixes from the review of JuliaComputing/daqp#1: the settings of a parallel solve may only differ in branching, time_limit and iter_limit (and child_order, which is not compared);
  • the three defects from the review of this PR that predate it, fixed on dyad in JuliaComputing/daqp#4.

The review found no defect in the changes of this PR. Results after the merge: ctest 34/34, Julia 2035/2035.

The benchmark in the description was run on the code before these fixes. Only the parallel solves with large objective offsets (invpend_contact, excluded) were affected, so the tables are expected to hold, but they have not been rerun.

Host: demeter2, session session_01JGy8Ya4ZvmiQUA33aivGGJ, working directory /tmp/daqp-childorder

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