It would be great if someone could point me towards an algorithm that would allow me to :
- create a random square matrix, with entries 0 and 1, such that
- every row and every column contain exactly two non-zero entries,
- two non-zero entries cannot be adjacent,
- all possible matrices are equiprobable.
Right now I manage to achieve points 1 and 2 doing the following : such a matrix can be transformed, using suitable permutations of rows and columns, into a diagonal block matrix with blocks of the form
1 1 0 0 ... 0
0 1 1 0 ... 0
0 0 1 1 ... 0
.............
1 0 0 0 ... 1
So I start from such a matrix using a partition of [0, ..., n-1] and scramble it by permuting rows and columns randomly. Unfortunately, I can't find a way to integrate the adjacency condition, and I am quite sure that my algorithm won't treat all the matrices equally.
Update
I have managed to achieve point 3. The answer was actually straight under my nose : the block matrix I am creating contains all the information needed to take into account the adjacency condition. First some properties and definitions:
- a suitable matrix defines permutations of
[1, ..., n]
that can be build like so: select a 1 in row1
. The column containing this entry contains exactly one other entry equal to 1 on a rowa
different from 1. Again, rowa
contains another entry 1 in a column which contains a second entry 1 on a rowb
, and so on. This starts a permutation1 -> a -> b ...
.
For instance, with the following matrix, starting with the marked entry
v
1 0 1 0 0 0 | 1
0 1 0 0 0 1 | 2
1 0 0 1 0 0 | 3
0 0 1 0 1 0 | 4
0 0 0 1 0 1 | 5
0 1 0 0 1 0 | 6
------------+--
1 2 3 4 5 6 |
we get permutation 1 -> 3 -> 5 -> 2 -> 6 -> 4 -> 1
.
- the cycles of such a permutation lead to the block matrix I mentioned earlier. I also mentioned scrambling the block matrix using arbitrary permutations on the rows and columns to rebuild a matrix compatible with the requirements.
But I was using any permutation, which led to some adjacent non-zero entries. To avoid that, I have to choose permutations that separate rows (and columns) that are adjacent in the block matrix. Actually, to be more precise, if two rows belong to a same block and are cyclically consecutive (the first and last rows of a block are considered consecutive too), then the permutation I want to apply has to move these rows into non-consecutive rows of the final matrix (I will call two rows incompatible in that case).
So the question becomes : How to build all such permutations ?
The simplest idea is to build a permutation progressively by randomly adding rows that are compatible with the previous one. As an example, consider the case n = 6
using partition 6 = 3 + 3
and the corresponding block matrix
1 1 0 0 0 0 | 1
0 1 1 0 0 0 | 2
1 0 1 0 0 0 | 3
0 0 0 1 1 0 | 4
0 0 0 0 1 1 | 5
0 0 0 1 0 1 | 6
------------+--
1 2 3 4 5 6 |
Here rows 1
, 2
and 3
are mutually incompatible, as are 4
, 5
and 6
. Choose a random row, say 3
.
We will write a permutation as an array: [2, 5, 6, 4, 3, 1]
meaning 1 -> 2
, 2 -> 5
, 3 -> 6
, ... This means that row 2
of the block matrix will become the first row of the final matrix, row 5
will become the second row, and so on.
Now let's build a suitable permutation by choosing randomly a row, say 3
:
p = [3, ...]
The next row will then be chosen randomly among the remaining rows that are compatible with 3
: 4
, 5
and 6
. Say we choose 4
:
p = [3, 4, ...]
Next choice has to be made among 1
and 2
, for instance 1
:
p = [3, 4, 1, ...]
And so on: p = [3, 4, 1, 5, 2, 6]
.
Applying this permutation to the block matrix, we get:
1 0 1 0 0 0 | 3
0 0 0 1 1 0 | 4
1 1 0 0 0 0 | 1
0 0 0 0 1 1 | 5
0 1 1 0 0 0 | 2
0 0 0 1 0 1 | 6
------------+--
1 2 3 4 5 6 |
Doing so, we manage to vertically isolate all non-zero entries. Same has to be done with the columns, for instance by using permutation p' = [6, 3, 5, 1, 4, 2]
to finally get
0 1 0 1 0 0 | 3
0 0 1 0 1 0 | 4
0 0 0 1 0 1 | 1
1 0 1 0 0 0 | 5
0 1 0 0 0 1 | 2
1 0 0 0 1 0 | 6
------------+--
6 3 5 1 4 2 |
So this seems to work quite efficiently, but building these permutations needs to be done with caution, because one can easily be stuck: for instance, with n=6
and partition 6 = 2 + 2 + 2
, following the construction rules set up earlier can lead to p = [1, 3, 2, 4, ...]
. Unfortunately, 5
and 6
are incompatible, so choosing one or the other makes the last choice impossible. I think I've found all situations that lead to a dead end. I will denote by r
the set of remaining choices:
p = [..., x, ?]
,r = {y}
withx
andy
incompatiblep = [..., x, ?, ?]
,r = {y, z}
withy
andz
being both incompatible withx
(no choice can be made)p = [..., ?, ?]
,r = {x, y}
withx
andy
incompatible (any choice would lead to situation 1)p = [..., ?, ?, ?]
,r = {x, y, z}
withx
,y
andz
being cyclically consecutive (choosingx
orz
would lead to situation 2, choosingy
to situation 3)p = [..., w, ?, ?, ?]
,r = {x, y, z}
withxwy
being a 3-cycle (neitherx
nory
can be chosen, choosingz
would lead to situation 3)p = [..., ?, ?, ?, ?]
,r = {w, x, y, z}
withwxyz
being a 4-cycle (any choice would lead to situation 4)p = [..., ?, ?, ?, ?]
,r = {w, x, y, z}
withxyz
being a 3-cycle (choosingw
would lead to situation 4, choosing any other would lead to situation 4)
Now it seems that the following algorithm gives all suitable permutations:
- As long as there are strictly more than 5 numbers to choose, choose randomly among the compatible ones.
- If there are 5 numbers left to choose: if the remaining numbers contain a 3-cycle or a 4-cycle, break that cycle (i.e. choose a number belonging to that cycle).
- If there are 4 numbers left to choose: if the remaining numbers contain three cyclically consecutive numbers, choose one of them.
- If there are 3 numbers left to choose: if the remaining numbers contain two cyclically consecutive numbers, choose one of them.
I am quite sure that this allows me to generate all suitable permutations and, hence, all suitable matrices.
Unfortunately, every matrix will be obtained several times, depending on the partition that was chosen.