Note that it's hard to know how 'efficient' you need without supplying specific examples.
If you're only interested in a single instance, you can reduce this problem to the 0-1 knapsack problem (or equivalently 0-1 ILP) by calling KnapsackSolve
. Unfortunately I don't see an immediate (built in) way to find more than one solution with this approach.
(* Values from Danny's answer *)
A = {9, 16, 19, 35, 41, 42, 58, 59, 68, 74, 78};
b = 93;
If no solution exists, KnapsackSolve
will still return a result -- a nearby RHS that's satisfiable. We can first use Danny's answer to verify that there is a solution. If there's not, we stop.
SeriesCoefficient[Times @@ (1+x^A), {x, 0, b}]
5
A single solution is then:
ksol = KnapsackSolve[Thread[{A, A, 1}], b]
{0.004771, {0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0}}
If you don't want to call SeriesCoefficient
beforehand you can verify here:
A.ksol == b
True
If you need all solutions and your inputs are small enough, you can call FrobeniusSolve
and pick the results you want.
sol = FrobeniusSolve[A, b]; // AbsoluteTiming
Pick[sol, UnitStep[Max /@ sol - 2], 0]
{0.040838, Null}
{{0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0},
{0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0},
{1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0}}
We can also recurse brute force, pruning the search space along the way. Here's one such solution. I didn't spend time making a faster implementation as I don't know the how large the intended inputs are.
binaryFrobenius[{}, _] = {};
binaryFrobenius[list_, 0] := {ConstantArray[0, Length[list]]}
binaryFrobenius[{k_, rest___}, n_] /; n < k := binaryFrobenius[{rest}, n]
binaryFrobenius[{k_, rest___}, n_] := Join[
Prepend[0] /@ binaryFrobenius[{rest}, n],
Prepend[1] /@ binaryFrobenius[{rest}, n - k]
]
binaryFrobenius[A, b] // AbsoluteTiming
{0.000681, {{0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0},
{0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0},
{1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0}}}
Edit
Here's my attempt at leveraging a SAT solver.
I've managed to reduce subset sum to SAT, but not necessarily in polynomial space though. This method works, but is slow. I wonder how one can perform this reduction more efficiently.
For each ni
we'll have ai
boolean variables. Therefore this scales with the coefficients themselves, which is not ideal.
groupedvars = MapIndexed[Thread[x[First[#2], Range[#1]]]&, A];
vars = Flatten[groupedvars];
Assert that all variables corresponding to the same ni
are the same:
coeffs = Equivalent @@@ groupedvars;
Assert that exactly b
variables are true:
constraint = BooleanCountingFunction[{b}, Total[A]] @@ vars;
Verify that we only have 5 solutions:
SatisfiabilityCount[And @@ Prepend[coeffs, constraint]]
5
The explicit solutions:
insts = SatisfiabilityInstances[And @@ Prepend[coeffs, constraint], vars, All];
res = Boole[insts][[All, Prepend[Most[Accumulate[A] + 1], 1]]]
{{1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0},
{0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0},
{0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}}
res.A
{93, 93, 93, 93, 93}