Because you want to minimize the number of elements removed, a natural way to do this is with constrained optimization functions like Minimize
and LinearProgramming
.
In another answer I used the the easy-to-implement, and slow, method with Minimize
; here the faster method of LinearProgramming
.
LinearProgramming
require that the objective function and constraints are linear in the decision variables. So we need to rewrite the objective function in some linear form. This is possible adding some more (many more!) decision variables and constraints.
Given:
- the matrix $A$, with dimensions $(m,n)$ and elements $a_{i,j}$
you want to determine the value of variables ($i=1,\ldots,m$, $j=1,\ldots,n$):
- $e_{i,j} \in \{0,1\}$ (it's $1$ if we want/need to remove the element $(i,j)$, and $0$ otherwise)
- $r_i \in \{0,1\}$ (it's $1$ if we remove row $i$, and $0$ otherwise)
- $c_j \in \{0,1\}$ (it's $1$ if we remove column $j$, and $o$ otherwise)
subject to:
- $e_{i,j}=1$ for every $(i,j)$ such that $a_{i,j}=0$
- $r_i+c_j \ge e_{i,j}$ i.e. $e_{i,j}=1 \Rightarrow (r_i=1 \vee c_j=1)$
- $e_{i,j} \ge r_i$ i.e. $r_i = 1 \Rightarrow e_{i,j}=1$
- $e_{i,j} \ge c_j$ i.e. $c_j = 1 \Rightarrow e_{i,j}=1$
minimizing the objective function
- $\sum_{i=1}^m \sum_{j=1}^n e_{i,j}$.
Note you can also enforce $e_{i,j} \ge 1$ instead of $e_{i,j} = 1$ because of the objective function. Indeed it's this we do in the following code. But we need to build the arguments of LinearProgramming
:
- the cost vector
- the constraints matrix
- the right-hand side of the constraints
- the range of the decision variables
- the domain of the decision variables
I packaged the process in the following function:
zeroFreeSubmatrix[A_?MatrixQ] :=
Module[{m, n, e, r, c, vars, constraints, bm, solution},
{m, n} = Dimensions[A];
vars = Flatten@{Array[e, {m, n}], Array[r, m], Array[c, n]};
constraints = Flatten@{
Thread[e @@@ Position[A, 0, {2}] == 1],
Table[{r[i] + c[j] >= e[i, j], e[i, j] >= r[i],
e[i, j] >= c[j]}, {i, m}, {j, n}]
};
bm = CoefficientArrays[Equal @@@ constraints, vars];
solution = Thread[vars -> LinearProgramming[
vars /. {_e -> 1, (_r | _c) -> 0},
bm[[2]], -bm[[1]],
Table[{0, 1}, Length@vars], Integers
]];
Sort@Cases[solution, (#[i_] -> 0) :> i] & /@ {r, c}
]
The usage:
SeedRandom[0];
A = RandomChoice[{8, 1} -> {1, 0}, {15, 20}];
{rows, cols} =
zeroFreeSubmatrix[A] // Timing // EchoFunction["Timing:", First] //
Last
A[[rows, cols]] // MatrixForm
During evaluation of LinearProgramming::lpip: Warning: integer linear programming will use a machine-precision approximation of the inputs. >>
Timing: 0.234375
{{1, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14}, {2, 3, 4, 7, 9, 12,
14, 15, 17, 18, 19, 20}}
Faster than the simpler approach with Minimize
.
There is also a way to directly build the arguments of LinearProgramming
with SparseArray
but I don't think in this case it deserve the effort.
Update
If you are sure that some rows or columns will be removed in any optimal solution you can use the following generalization of the previous routine.
zeroFreeSubmatrix[A_?MatrixQ, rl : _?VectorQ : {},
cl : _?VectorQ : {}] :=
Module[{m, n, e, r, c, vars, constraints, bm, solution},
{m, n} = Dimensions[A];
vars = Flatten@{Array[e, {m, n}], Array[r, m], Array[c, n]};
constraints = Flatten@{
Thread[e @@@ Position[A, 0, {2}] == 1],
Table[{r[i] + c[j] >= e[i, j], e[i, j] >= r[i],
e[i, j] >= c[j]}, {i, m}, {j, n}],
Thread[r /@ rl == 1],
Thread[c /@ cl == 1]
};
bm = CoefficientArrays[Equal @@@ constraints, vars];
solution = Thread[vars -> Quiet[LinearProgramming[
vars /. {_e -> 1, (_r | _c) -> 0},
bm[[2]], -bm[[1]],
Table[{0, 1}, Length@vars], Integers
], LinearProgramming::lpip]];
Sort@Cases[solution, (#[i_] -> 0) :> i] & /@ {r, c}
]
For example if you are sure that the rows $2,5$ and colum $1,5$ are to be removed you can use:
{rows, cols} = zeroFreeSubmatrix[A, {2, 5}, {1, 5}]
A[[rows, cols]] // Dimensions // Apply[Times]
{{1, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14}, {2, 3, 4, 7, 9, 12,
14, 15, 17, 18, 19, 20}}
132
and you get the (same) optimal solution.
But if you are wrong you can get a different, maybe sub-optimal, solution (the optimal solution where you enforce the removal of that rows/columns). For example:
{rows, cols} = zeroFreeSubmatrix[A, {1, 3}, {2, 3}]
A[[rows, cols]] // Dimensions // Apply[Times]
{{4, 5, 6, 9, 10, 11, 13, 15}, {1, 4, 5, 6, 7, 9, 12, 13, 14,
15, 16, 17, 18, 19}}
112
initmat
(RandomChoice[]
will of course give different results on different machines), and show what your "optimal solution" looks like for that particular input. $\endgroup$initmat
is provided, as the code is preceded bySeedRandom[0];
nowRandomChoice
will always give the same output first time is called. So far so good. What we do need now is the output, preferable step by step to understand unambiguously what is the procedure you mean. $\endgroup$initmat
, I can see that the solution is a 7x10 matrix of 1's (rows 1,2,4 deleted; columns 10 and 12 deleted). What I need is a formal approach that gets me to that solution. $\endgroup$