A mu-fold covering code is a code in which every word of the space
is within distance R of at least mu codewords. K_q^mu(n,R) denotes
the minimum size of such a code over Z_q^n; mu = 1 is the ordinary
covering-code problem K_q(n,R).
Multiple coverings are a record surface of their own — though, unlike
the classical case, no maintained online table exists (Keri's site
carries only mu = 1). The binary state of the art lives in papers:
the Hamalainen–Honkala–Kaikkonen–Litsyn tables (DCC 1993), Ostergard's
tabu-search improvements (Australas. J. Combin. 12, 1995, with explicit
codes), Seuranen's consolidated tables (PhD thesis, Aalto 2011, Table
2.3), and Krotov–Potapov's lower bounds (IEEE Trans. IT 67, 2021). That
lineage uses exactly coldcore's convention — a set of distinct
codewords, at-least-mu coverage, closed balls — so our bounds are
directly comparable cell for cell. The distinct multiset variant (MCR)
and the MCF/"farthest-off points" line are different quantities;
never compare against those columns. The classical
constructions are unions of shifted perfect codes, which are optimal
only in the divisible cases — the rest is search space.
In coldcore, mu is not a plugin. It is a change of predicate on
the cnt plane, which is why it works for every problem family at once
(hamming, lee, torus_linf, grid_linf, and anything added later).
Write cnt(x) for the number of codewords covering x and
deficit D(S) = sum_x max(0, mu - cnt(x)) (D = 0 <=> feasible)
D is the total number of (word, fold) requirements still unmet, and it
is the quantity the search core carries as uncov. Its two marginals
are exact and both are ball sums of an indicator field, so both are one
transform:
gain(x) = D(S) - D(S + x) = #{ w in B_R(x) : cnt(w) < mu }
loss(c) = D(S - c) - D(S) = #{ w in B_R(c) : 1 <= cnt(w) <= mu }
Gain: adding x raises cnt by one on every word of B_R(x), which
retires exactly one unit of deficit at each word that still had some —
i.e. at each word with cnt < mu. (Note this is a count of words, not
of folds: a word two folds short contributes 1, because only one of its
two missing folds is filled by this one codeword.)
Loss: removing a codeword c lowers cnt by one on every word of
B_R(c), which creates one new unit of deficit at each word that was at
level mu or below. Every word of B_R(c) has cnt >= 1 (c covers
it), so the clause 1 <= cnt excludes nothing that a removal can touch;
it is there so that the field is the legacy [cnt==1] indicator
verbatim when mu = 1.
Both indicators degenerate to the ordinary ones at mu = 1:
| quantity | general mu |
mu = 1 |
|---|---|---|
| objective | sum_x max(0, mu-cnt(x)) |
#{x : cnt(x) == 0} |
| gain input | [cnt < mu] |
[cnt == 0] |
| loss input | [1 <= cnt <= mu] |
[cnt == 1] |
peel: c removable |
no w in B(c) with cnt(w) <= mu |
no privately covered w |
so the mu = 1 code paths are untouched — literally: the plugin
dispatches to the legacy kernels when mu == 1.
Submodularity survives: D is a sum over words of a concave function of
cnt, so the coverage gain is still submodular and the lazy greedy is
still exact. The peel and the batched-removal independence argument are
unchanged too (removals whose balls are disjoint cannot interact).
Python — every backend takes mu:
from coldcore.gpu import GpuBackend
from coldcore.reference import RefHammingBackend
from coldcore.search import Searcher
b = GpuBackend([2, 2, 2], 1, problem="hamming", mu=2) # K_2^2(3,1)
s = Searcher(b, seed=1)
s.load([])
s.greedy_fill(10**6) # b.uncovered() == 0 => a 2-fold cover
s.descend(floor=1)RefHammingBackend(axes, R, mu=2) (and the lee / torus / grid
references) is the independent brute-force oracle for the same instance.
Backend surface, unchanged in shape:
uncovered()returns the total deficitD(not a word count) — atmu = 1these coincide, which is why the search core needs no change: it decrementsuncovby the exact placement gain.ball_gather(idx, target)takes a coverage role:0= deficient (cnt < mu, the exact placement gain),1= critical (1 <= cnt <= mu, the exact removal loss). Atmu = 1the roles are the literalcntlevels the ABI always used.count_eq(t)stays a literalcnt == tquery at everymu.gain_map()/loss_map()write the transformed role indicators.
Feature-tested by symbol presence, exactly like dct_map_hist; a plugin
without them supports mu = 1 only, and GpuBackend(..., mu>1) then
raises instead of computing something wrong.
int dct_set_mu(int mu); /* multiplicity for all later calls */
int dct_get_mu(void);
int dct_ball_gather_mu(const long long *words, int nwords, int role,
int32_t *out); /* role 0/1 as above */
long long dct_count_deficit(void); /* sum max(0, mu-cnt) */
dct_set_mu also re-points dct_transform's two indicator init modes
(1 -> [cnt<mu], 2 -> [1<=cnt<=mu]) and the owner-trick loss pass.
The singleton context resets mu to 1 on dct_free, and the host
binding re-states mu after every dct_init3, so multiplicity can
never leak into a later cell.
Kernel-side, the extension is src/plugins/covering/dctcore_mu.cuh:
predicate twins of the three cnt-reading core kernels
(k_init_from_cnt, k_ball_gather, k_loss_scan) plus a deficit
reduction. dctcore_core.cuh stays byte-identical to upstream.
tests/test_multi.py— CPU: role/field identities and the exact marginal property (Dafter an add/remove equalsD -/+ gain/loss, checked against from-scratch recounts) for hamming, lee and torus cells atmu = 2, 3; peel safety; and three exhaustively determined optima K_2^1(3,1) = 2, K_2^2(3,1) = 4, K_2^3(3,1) = 6 (exhaustive search over all subsets of the 8-word space; these attain the sphere boundmu*|X|/|B_1| = 2*muand are the unions of the four disjoint perfect codes).tests/test_gpu_parity_multi.py— GPU vs. brute force on all three families (mixed radix, even Lee axes) atmu = 2, 3:cnt, deficit, both maps over the whole space, both gather roles; plus end-to-end runs reachingK_2^2(3,1) = 4andK^2(6x6 king torus) = 8(sphere bound2*36/9, attained by two shifted perfect 3x3 tilings), and thedct_freemu-reset check.