Filter the clique branching set by recolouring and infra-chromatic bounds - #76
Open
ciaranm wants to merge 1 commit into
Open
Filter the clique branching set by recolouring and infra-chromatic bounds#76ciaranm wants to merge 1 commit into
ciaranm wants to merge 1 commit into
Conversation
…unds
Adds --filter none|recolour|infra-chromatic to the clique solver. After
colouring, each vertex whose colour is at least k_min is a vertex we would
otherwise have to branch on; this tries to dispose of some of them without
branching.
Both tests hang off the same quantity, C_k1 & N(v), so they are fused as in
San Segundo et al's FILTER_RECOL_INFRACHROM. If v has no neighbour in some
class it belongs there (one-move recolouring); if it has exactly one
neighbour w, then either w can move to another class and v take its place
(Tomita's Re-NUMBER), or no vertex of a third class is adjacent to both v and
w, in which case ({v}, C_k1, C_k2) admits no triangle with one vertex in each
and contributes two rather than three to the bound.
Classes below k_min are held as intrusive linked lists over one int array, so
a recolouring is O(1) and nothing is allocated per node. Condition I is a walk
that bails at the second neighbour of v rather than a bitset intersection:
Prosser's Proposition 1 means the first neighbour is always there to be found,
and on a dense graph the second arrives after a couple of tests, so failure --
the common case -- is where it is cheapest.
Two things are easy to get wrong here, both caught by differential fuzzing:
- Filtering removes a vertex from the branching set, not from the candidate
set. All we have shown is that we need not branch on it here; a clique
through some other branching vertex may still need it. It stays in p and
the position is marked by negating its entry in p_bounds.
- Nogoods must not be posted for filtered vertices during a restart. A
filtered vertex has no completed subtree behind it; all we know is that
the classes below k_min and the filtered vertices cannot beat the
incumbent between them, which says nothing until the rest of the branching
set has been dealt with.
Proof logging follows the same shape. A filtered vertex is never individually
justified -- only the combined statement holds, that the classes below k_min,
enlarged by recolouring, together with one singleton per conflict, have clique
number k_min - 1. That is why the colours a conflict spends are marked
forbidden: the sum only works if the conflicts are over disjoint classes.
Proof::colour_bound therefore takes the conflicts as a second argument and is
handed the recoloured classes; since reverse polish composes, a conflict is a
subexpression and the bound is still one pol line.
Over fifteen DIMACS instances, against plain MCSa1: recolouring alone cuts the
search tree 1.80x and runs 32% slower, while the fused filter cuts it 3.38x
and runs 1.32x faster. Proofs get smaller too, since the stronger bound means
fewer nodes to log.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qVra2UG1rKEur4wa7w67w
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
--filter none|recolour|infra-chromaticto the clique solver, bringing inrecolouring (Tomita's Re-NUMBER, so MCSa → MCSb) and the infra-chromatic bound
(San Segundo, Nikolaev and Batsyn), fused into a single pass.
What it does
After colouring, every vertex whose colour is at least
k_minis one we wouldotherwise have to branch on. Both tests hang off the same quantity,
C_k1 & N(v), which is why they are fused as in San Segundo et al'sFILTER_RECOL_INFRACHROM:vin some class →vbelongs there (one-move recolouring);w→ eitherwmoves to another class andvtakesits place (double-move recolouring), or no vertex of a third class is
adjacent to both
vandw, in which case({v}, C_k1, C_k2)admits notriangle with one vertex in each, and contributes two rather than three to
the bound.
Recolouring alone does not pay for itself, which is Prosser's finding too
("MCSb1 is always slower than MCSa1, i.e. the colour repair is too expensive").
It pays once the second half of what condition I buys is also cashed in.
Results
Fifteen DIMACS instances, best of three, against plain MCSa1:
--filter recolour--filter infra-chromaticMANN_a27is the useful control: recolouring gives 4.2× fewer nodes and 1.61×faster, and the infra-chromatic test then adds nothing at all — 9,084 nodes
either way. It is near-perfect, so the colouring bound is already tight and
there are no conflicting triples to find.
Implementation notes
Classes below
k_minare held as intrusive singly linked lists over oneintarray, so a recolouring is O(1) and nothing is allocated per node. Condition I
is a walk over the class that bails at the second neighbour of
vrather thana bitset intersection: Prosser's Proposition 1 means the first neighbour is
always there to be found, and on a dense graph the second arrives after a
couple of tests — so failure, the common case, is where it is cheapest. A
bitset intersection has no such asymmetry and pays for every word every time.
Two things here are easy to get wrong, and both were caught by differential
fuzzing rather than by reading:
All we have shown is that we need not branch on it here; a clique through
some other branching vertex may still need it. It stays in
p, and theposition is marked by negating its entry in
p_bounds.filtered vertex has no completed subtree behind it; all we know is that the
classes below
k_minand the filtered vertices cannot beat the incumbentbetween them, which says nothing until the rest of the branching set has
been dealt with.
Proof logging
Works with
--prove. A filtered vertex is never individually justified — onlythe combined statement holds, that the classes below
k_min, enlarged byrecolouring, together with one singleton per conflict, have clique number
k_min - 1. That is exactly why the colours a conflict spends are markedforbidden: the sum only works if the conflicts are over disjoint classes.
Proof::colour_boundtherefore takes the conflicts as a second argument and ishanded the recoloured classes. An inconsistent triple becomes an at-most-two
over the union of the three classes, derived by summing the non-edges from
vto what it cannot see, the at-most-one over the independent set inside its
neighbourhood, and
nbar - 1copies of the plain two-class bound so that everycoefficient is
nbarand the division is legal; the right-hand side lands oneshort of
3 * nbarand the rounding does the rest. Since reverse polishcomposes, this is a subexpression and the bound remains a single
polline.Proofs come out smaller, since the stronger bound means fewer nodes to log:
brock200_2p_hat300-2keller4Testing
--filter none, checking both ω and that the reported clique really is one;decide mode across
k = ω ± 2; restarts enabled.--prove, all checked by VeriPB 3.0.2, exercising~2,800 conflict triples, no failures.
nbar - 1padding, both get rejected by VeriPB — so the check is load-bearing.
run-tests.bashpass. Node counts are unchanged with--filter none, which takes the same path as before.Unrelated issue found in passing
--decide k --proveemits a proof VeriPB rejects when the answer is true;the unsatisfiable direction is fine. This reproduces on a clean checkout of
masterwith no filtering, so it predates this branch — but it is worthknowing before trusting a satisfiable decision proof.
🤖 Generated with Claude Code
https://claude.ai/code/session_012qVra2UG1rKEur4wa7w67w