To start to follow the suggestion at https://ask.sagemath.org/question/605..., consider this function:
def sum_list(length, total):
"""
INPUT:
- length -- how many terms
- total -- desired total
Return list of lists, each of which has ``length`` entries chosen
from {0, 1, -1} and adds up to ``total``.
"""
C = IntegerListsLex(length=length, n=length+total, min_part=0, max_part=2)
return [[a-1 for a in L] for L in C]
(It's only two lines long; everything between the """
lines is a comment.)
Sample use to get all lists of length 4 that add up to 1:
sage: sum_list(4, 1)
[[1, 1, 0, -1],
[1, 1, -1, 0],
[1, 0, 1, -1],
[1, 0, 0, 0],
[1, 0, -1, 1],
[1, -1, 1, 0],
[1, -1, 0, 1],
[0, 1, 1, -1],
[0, 1, 0, 0],
[0, 1, -1, 1],
[0, 0, 1, 0],
[0, 0, 0, 1],
[0, -1, 1, 1],
[-1, 1, 1, 0],
[-1, 1, 0, 1],
[-1, 0, 1, 1]]
Now try to build up matrices using sum_list(LENGTH, 1)
and sum_list(LENGTH, -1)
based on the entries of your given matrix.
The following is a draft, and it could probably be sped up, but it seems to work. It treats a solution (A,B)
as different from (B,A)
: the order matters.
def sum_list(length, total):
"""
INPUT:
- length -- how many terms
- total -- desired total
Return list of lists, each of which has ``length`` entries chosen
from {0, 1, -1} and adds up to ``total``.
"""
C = IntegerListsLex(length=length, n=length+total, min_part=0, max_part=2)
return [[a-1 for a in L] for L in C]
def mat_list(mat, length):
"""
INPUT:
- mat -- matrix, assumed to have entries in {1, -1}
- length -- how many terms
Return list of lists, each of which has ``length`` entries and
adds up to ``mat``, and each entry is either a `(0, 1)` or a `(0,
-1)` matrix.
"""
sum_plus = sum_list(length, 1)
sum_minus = sum_list(length, -1)
old_attempts = []
for x in mat.list():
if x == 1:
sums = sum_plus
elif x == -1:
sums = sum_minus
else:
raise ValueError('each entry of matrix should be 1 or -1')
if not old_attempts: # initial step
new_attempts = [[[s] for s in S] for S in sums]
else:
new_attempts = []
for mats in old_attempts:
NEW = []
for S in sums:
new_M = [M + [s] for (M, s) in zip(mats, S)]
# keep only entries that correspond to (0,1) or (0,-1) matrices
if all(max(entries) - min(entries) < 2 for entries in new_M):
NEW.append([M + [s] for (M, s) in zip(mats, S)])
new_attempts.extend(NEW)
old_attempts = new_attempts
matrices = [[matrix(mat.base_ring(), mat.nrows(), mat.ncols(), entries) for entries in L] for L in old_attempts]
return matrices
With this code:
sage: mat = matrix(ZZ, [[1, 1, 1, 1], [1, -1, 1, -1], [1, 1, -1, -1], [1, -1, -1, 1]])
sage: %time L = mat_list(mat, 3)
CPU times: user 8.04 s, sys: 141 ms, total: 8.18 s
Wall time: 8.18 s
sage: len(L)
179328
EDIT: here is a modification, in case you don't want to distinguish the same lists with a different ordering:
def mat_list_NEW(mat, length):
"""
INPUT:
- mat -- matrix, assumed to have entries in {1, -1}
- length -- how many terms
Return list of lists, each of which has ``length`` entries and
adds up to ``mat``, and each entry is either a `(0, 1)` or a `(0,
-1)` matrix.
"""
sum_plus = sum_list(length, 1)
sum_minus = sum_list(length, -1)
old_attempts = []
for x in mat.list():
if x == 1:
sums = sum_plus
elif x == -1:
sums = sum_minus
else:
raise ValueError('each entry of matrix should be 1 or -1')
if not old_attempts: # initial step
new_attempts = set(tuple([(s,) for s in S]) for S in sums)
else:
new_attempts = set()
for mats in old_attempts:
NEW = set()
for S in sums:
new_M = tuple(sorted(M + (s,) for (M, s) in zip(mats, S)))
# keep only entries that correspond to (0,1) or (0,-1) matrices
if all(max(entries) - min(entries) < 2 for entries in new_M):
NEW.add(new_M)
new_attempts.update(NEW)
old_attempts = new_attempts
matrices = [[matrix(mat.base_ring(), mat.nrows(), mat.ncols(), entries) for entries in L] for L in old_attempts]
return matrices
The differences: use the set
structure so that duplicates are automatically removed, and sort the lists before adding them to the set, so that it's easy to recognize duplicates. Elements of a set
have to be hashable so use tuples instead of lists. With this new version:
sage: mat = matrix(ZZ, [[1, 1, 1, 1], [1, -1, 1, -1], [1, 1, -1, -1], [1, -1, -1, 1]])
sage: %time L = mat_list_NEW(mat, 3)
CPU times: user 1.47 s, sys: 41 ms, total: 1.51 s
Wall time: 1.53 s
sage: len(L)
29889
But the lengths of the lists are still going to get large as the number of summands increases...
Is it homework ?
Previously asked as Ask Sage question 60560.