Une version d'optimisation du problème Hadamard

11

Tout d'abord, quelques définitions.

Une matrice de Hadamard est une matrice carrée dont les entrées sont +1 ou -1 et dont les lignes sont mutuellement orthogonales. La conjecture de Hadamard propose qu'une matrice de Hadamard d'ordre 4k existe pour chaque entier positif k.

Une matrice circulante est un type spécial de matrice dans laquelle chaque vecteur ligne est tourné d'un élément vers la droite par rapport au vecteur ligne précédent. C'est-à-dire que la matrice est définie par sa première ligne.

On sait qu'à l'exception des matrices 4 x 4, il n'y a pas de matrices Hadamard circulantes .

Une matrice avec m lignes et n> = m colonnes est partiellement circulante , s'il s'agit des m premières lignes d'une matrice circulante.

La tâche

Pour chaque entier pair n commençant à 2, affichez la taille de la plus grande matrice de circulation partielle avec + -1 entrées et n colonnes qui a la propriété que toutes ses lignes sont mutuellement orthogonales.

But

Votre score est le plus élevé de nsorte que pour tous k <= n, personne d'autre n'a posté une réponse correcte plus élevée que vous. De toute évidence, si vous avez toutes les réponses optimales, vous obtiendrez le score le plus élevé que nvous publiez. Cependant, même si votre réponse n'est pas optimale, vous pouvez toujours obtenir le score si personne d'autre ne peut le battre.

Langues et bibliothèques

Vous pouvez utiliser toutes les langues et bibliothèques disponibles que vous aimez. Dans la mesure du possible, il serait bon de pouvoir exécuter votre code, veuillez donc inclure une explication complète sur la façon d'exécuter / compiler votre code sous Linux si possible.

Entrées principales

  • 64 par Mitch Schwartz en Python
flawr
la source

Réponses:

7

Python 2

Table jusqu'à n = 64, vérifié optimal avec force brute jusqu'à n = 32:

 4  4 0001
 8  4 00010001
12  6 000001010011
16  8 0000010011101011
20 10 00010001011110011010
24 12 000101001000111110110111
28 14 0001011000010011101011111011
32 14 00001101000111011101101011110010
36 18 001000101001000111110011010110111000
40 20 0010101110001101101111110100011100100100
44 18 00010000011100100011110110110101011101101111
48 24 001011011001010111111001110000100110101000000110
52 26 0011010111000100111011011111001010001110100001001000
56 28 00100111111101010110001100001101100000001010100111001011
60 30 000001101101100011100101011101111110010010111100011010100010
64 32 0001100011110101111111010010011011100111000010101000001011011001

0représente -1. Si nn'est pas divisible par 4, alors m = 1est optimal. Généré à l'aide de ce code (ou de petites variantes de celui-ci) mais avec plus d'essais pour plus n:

from random import *

seed(10)

trials=10000

def calcm(x,n):
    m=1
    y=x
    while 1:
        y=((y&1)<<(n-1))|(y>>1)
        if bin(x^y).count('1')!=n/2:
            return m
        m+=1

def hillclimb(x,n,ns):
    bestm=calcm(x,n)

    while 1:
        cands=[]

        for pos in ns:
            xx=x^(1<<pos)
            m=calcm(xx,n)

            if m>bestm:
                bestm=m
                cands=[xx]
            elif cands and m==bestm:
                cands+=[xx]

        if not cands:
            break

        x=choice(cands)

    return x,bestm

def approx(n):
    if n<10: return brute(n)

    bestm=1
    bestx=0

    for trial in xrange(1,trials+1):
        if not trial&16383:
            print bestm,bin((1<<n)|bestx)[3:]

        if not trial&1:
            x=randint(0,(1<<(n/2-2))-1)
            x=(x<<(n/2)) | (((1<<(n/2))-1)^x)
            ns=range(n/2-2)

            if not trial&7:
                adj=randint(1,5)
                x^=((1<<adj)-1)<<randint(0,n/2-adj)
        else:
            x=randint(0,(1<<(n-2))-1)
            ns=range(n-2)

        x,m=hillclimb(x,n,ns)

        if m>bestm:
            bestm=m
            bestx=x

    return bestm,bestx

def brute(n):
    bestm=1
    bestx=0

    for x in xrange(1<<(n-2)):
        m=calcm(x,n)
        if m>bestm:
            bestm=m
            bestx=x

    return bestm,bestx

for n in xrange(4,101,4):
    m,x=approx(n)
    print n,m,bin((1<<n)|x)[3:]

L'approche est une simple recherche aléatoire avec escalade, profitant d'un schéma remarqué pour les petits n. Le motif est que pour une optimisation m, la seconde moitié de la première ligne a souvent une petite distance d'édition par rapport à la négation (au niveau du bit) de la première moitié. Les résultats pour le code ci-dessus sont bons pour les petits nmais commencent à se détériorer peu de temps après que la force brute soit devenue impossible; Je serais heureux de voir une meilleure approche.

Quelques observations:

  • Quand nest impair, m = 1est optimal car un nombre impair de uns et de négatifs ne peut pas s'additionner à zéro. (Orthogonal signifie que le produit scalaire est nul.)
  • Quand n = 4k + 2, m = 1est optimal car pour que m >= 2nous ayons besoin d'avoir exactement des n/2inversions de signe parmi {(a1,a2), (a2,a3), ... (a{n-1},an), (an,a1)}, et un nombre impair d'inversion de signe impliquerait a1 = -a1.
  • Le produit scalaire de deux rangées iet jdans une matrice circulante est déterminé par abs(i-j). Par exemple, si row1 . row2 = 0alors row4 . row5 = 0. C'est parce que les paires d'éléments pour le produit scalaire sont les mêmes, juste tournées.
  • Par conséquent, pour vérifier l'orthogonalité mutuelle, il suffit de comparer les lignes successives à la première ligne.
  • Si nous représentons une ligne comme une chaîne binaire avec 0à la place de -1, nous pouvons vérifier l'orthogonalité de deux lignes en prenant xor au niveau du bit et en comparant le popcount avec n/2.
  • Nous pouvons fixer arbitrairement les deux premiers éléments de la première ligne, car (1) la négation d'une matrice n'affecte pas si les produits scalaires sont égaux à zéro, et (2) nous savons qu'il doit y avoir au moins deux éléments adjacents avec le même signe et deux éléments adjacents avec signe différent, afin que nous puissions tourner pour placer la paire souhaitée au début.
  • Une solution (n0, m0)donnera automatiquement des solutions (k * n0, m0)arbitraires k > 1, en concaténant (à plusieurs reprises) la première ligne à elle-même. Une conséquence est que l'on peut facilement obtenir m = 4pour tout ndivisible par 4.

Il est naturel de conjecturer que n/2c'est une limite supérieure stricte pour mquand n > 4, mais je ne sais pas comment cela serait prouvé.

Mitch Schwartz
la source
Il est très intéressant de noter qu'il n'y a pas de solution avec 16 lignes et 32 ​​colonnes. Tu sais pourquoi?
@Lembik Si j'avais eu une idée, je l'aurais écrite dans la réponse.
Mitch Schwartz