Conta il numero di matrici Hankelable


12

sfondo

Un binario Matrice di Hankel è una matrice con costanti skew-diagonali (diagonali pendenza positiva) contenente solo 0s e 1s. Ad esempio una matrice binaria di Hankel 5x5 sembra

a b c d e
b c d e f
c d e f g
d e f g h
e f g h i

dove a, b, c, d, e, f, g, h, isono 0o 1.

Definiamo una matrice M come Hankelable se esiste una permutazione dell'ordine di righe e colonne di M in modo che M sia una matrice di Hankel. Ciò significa che è possibile applicare una permutazione all'ordine delle righe e una possibilmente diversa alle colonne.

La sfida

La sfida è quella di contare quante Hankelable n da nmatrici ci sono per tutti nfino a come valore di un grande possibile.

Produzione

Per ogni numero intero n da 1 in poi, genera il numero di Hankelablen per nmatrici con voci che sono 0o 1.

Per n = 1,2,3,4,5le risposte dovrebbero essere 2,12,230,12076,1446672. (Grazie a orlp per il codice per produrli.)

Limite di tempo

Eseguirò il tuo codice sulla mia macchina e lo fermerò dopo 1 minuto. Il codice che genera le risposte corrette fino al valore più grande di n vince. Il limite di tempo è per tutto, dal n = 1più grande valore nper il quale si dà una risposta.

Il vincitore sarà la migliore risposta entro la fine di sabato 18 aprile.

Tie Breaker

Nel caso di un pareggio per un po 'massimo n, cronometrerò quanto tempo ci vorrà per dare fino alle uscite n+1e quella più veloce vince. Nel caso in cui corrano nello stesso tempo entro un secondo fino a n+1, vince la prima presentazione.

Lingue e biblioteche

Puoi usare qualsiasi lingua che abbia un compilatore / interprete / ecc. Liberamente disponibile. per Linux e tutte le librerie che sono anche disponibili gratuitamente per Linux.

La mia macchina

I tempi verranno eseguiti sulla mia macchina. Questa è un'installazione ubuntu standard su un processore a otto core AMD FX-8350 su una scheda madre Asus M5A78L-M / USB3 (Socket AM3 +, DDR3 da 8 GB). Questo significa anche che devo essere in grado di eseguire il tuo codice. Di conseguenza, utilizzare solo software gratuito facilmente disponibile e includere istruzioni complete su come compilare ed eseguire il codice.

Appunti

Consiglio di non ripetere iterando tutte le matrici n di n e di provare a individuare se ognuna ha la proprietà che descrivo. Primo, ce ne sono troppi e secondo, non sembra esserci un modo rapido per fare questo rilevamento .

Le voci principali finora

  • n = 8 di Peter Taylor. Giava
  • n = 5 di orlp. Pitone

4
Mi piace molto la parola "Hankelable".
Alex A.

3
Per n=6il totale è 260357434. Penso che la pressione della memoria sia un problema maggiore rispetto al tempo della CPU.
Peter Taylor,

Questa è una domanda fantastica. Sono stato completamente secchiato.
alexander-brett,

Risposte:


7

Java (n = 8)

import java.util.*;
import java.util.concurrent.*;

public class HankelCombinatorics {
    public static final int NUM_THREADS = 8;

    private static final int[] FACT = new int[13];
    static {
        FACT[0] = 1;
        for (int i = 1; i < FACT.length; i++) FACT[i] = i * FACT[i-1];
    }

    public static void main(String[] args) {
        long prevElapsed = 0, start = System.nanoTime();
        for (int i = 1; i < 12; i++) {
            long count = count(i), elapsed = System.nanoTime() - start;
            System.out.format("%d in %dms, total elapsed %dms\n", count, (elapsed - prevElapsed) / 1000000, elapsed / 1000000);
            prevElapsed = elapsed;
        }
    }

    @SuppressWarnings("unchecked")
    private static long count(int n) {
        int[][] perms = new int[FACT[n]][];
        genPermsInner(0, 0, new int[n], perms, 0);

        // We partition by canonical representation of the row sum multiset, discarding any with a density > 50%.
        Map<CanonicalMatrix, Map<CanonicalMatrix, Integer>> part = new HashMap<CanonicalMatrix, Map<CanonicalMatrix, Integer>>();
        for (int m = 0; m < 1 << (2*n-1); m++) {
            int density = 0;
            int[] key = new int[n];
            for (int i = 0; i < n; i++) {
                key[i] = Integer.bitCount((m >> i) & ((1 << n) - 1));
                density += key[i];
            }
            if (2 * density <= n * n) {
                CanonicalMatrix _key = new CanonicalMatrix(key);
                Map<CanonicalMatrix, Integer> map = part.get(_key);
                if (map == null) part.put(_key, map = new HashMap<CanonicalMatrix, Integer>());
                map.put(new CanonicalMatrix(m, perms[0]), m);
            }
        }

        List<Job> jobs = new ArrayList<Job>();
        ExecutorService pool = Executors.newFixedThreadPool(NUM_THREADS);

        for (Map.Entry<CanonicalMatrix, Map<CanonicalMatrix, Integer>> e : part.entrySet()) {
            Job job = new Job(n, perms, e.getKey().sum() << 1 == n * n ? 0 : 1, e.getValue());
            jobs.add(job);
            pool.execute(job);
        }

        pool.shutdown();
        try {
            pool.awaitTermination(1, TimeUnit.DAYS); // i.e. until it's finished - inaccurate results are useless
        }
        catch (InterruptedException ie) {
            throw new IllegalStateException(ie);
        }

        long total = 0;
        for (Job job : jobs) total += job.subtotal;
        return total;
    }

    private static int genPermsInner(int idx, int usedMask, int[] a, int[][] perms, int off) {
        if (idx == a.length) perms[off++] = a.clone();
        else for (int i = 0; i < a.length; i++) {
            int m = 1 << (a[idx] = i);
            if ((usedMask & m) == 0) off = genPermsInner(idx+1, usedMask | m, a, perms, off);
        }
        return off;
    }

    static class Job implements Runnable {
        private volatile long subtotal = 0;
        private final int n;
        private final int[][] perms;
        private final int shift;
        private final Map<CanonicalMatrix, Integer> unseen;

        public Job(int n, int[][] perms, int shift, Map<CanonicalMatrix, Integer> unseen) {
            this.n = n;
            this.perms = perms;
            this.shift = shift;
            this.unseen = unseen;
        }

        public void run() {
            long result = 0;
            int[][] perms = this.perms;
            Map<CanonicalMatrix, Integer> unseen = this.unseen;
            while (!unseen.isEmpty()) {
                int m = unseen.values().iterator().next();
                Set<CanonicalMatrix> equiv = new HashSet<CanonicalMatrix>();
                for (int[] perm : perms) {
                    CanonicalMatrix canonical = new CanonicalMatrix(m, perm);
                    if (equiv.add(canonical)) {
                        result += canonical.weight() << shift;
                        unseen.remove(canonical);
                    }
                }
            }

            subtotal = result;
        }
    }

    static class CanonicalMatrix {
        private final int[] a;
        private final int hash;

        public CanonicalMatrix(int m, int[] r) {
            this(permuteRows(m, r));
        }

        public CanonicalMatrix(int[] a) {
            this.a = a;
            Arrays.sort(a);

            int h = 0;
            for (int i : a) h = h * 37 + i;
            hash = h;
        }

        private static int[] permuteRows(int m, int[] perm) {
            int[] cols = new int[perm.length];
            for (int i = 0; i < perm.length; i++) {
                for (int j = 0; j < cols.length; j++) cols[j] |= ((m >> (perm[i] + j)) & 1L) << i;
            }
            return cols;
        }

        public int sum() {
            int sum = 0;
            for (int i : a) sum += i;
            return sum;
        }

        public int weight() {
            int prev = -1, count = 0, weight = FACT[a.length];
            for (int col : a) {
                if (col == prev) weight /= ++count;
                else {
                    prev = col;
                    count = 1;
                }
            }
            return weight;
        }

        @Override public boolean equals(Object obj) {
            // Deliberately unsuitable for general-purpose use, but helps catch bugs faster.
            CanonicalMatrix that = (CanonicalMatrix)obj;
            for (int i = 0; i < a.length; i++) {
                if (a[i] != that.a[i]) return false;
            }
            return true;
        }

        @Override public int hashCode() {
            return hash;
        }
    }
}

Salva come HankelCombinatorics.java, compila come javac HankelCombinatorics.java, esegui come java -Xmx2G HankelCombinatorics.

Con NUM_THREADS = 4sulla mia macchina quad-core si ottiene 20420819767436per n=8a 50 a 55 secondi trascorsi, con una buona dose di variabilità tra le esecuzioni; Mi aspetto che dovrebbe gestire facilmente lo stesso sulla tua macchina octa-core ma ci vorranno un'ora o più per ottenerlo n=9.

Come funziona

Dato n, ci sono matrici 2^(2n-1)binarie nx di nHankel. Le righe possono essere permutate in n!modi e le colonne in n!modi. Tutto quello che dobbiamo fare è evitare il doppio conteggio ...

Se si calcola la somma di ogni riga, quindi né permutando le righe né permutando le colonne si cambia il multiset di somme. Per esempio

0 1 1 0 1
1 1 0 1 0
1 0 1 0 0
0 1 0 0 1
1 0 0 1 0

ha un multiset di somma delle righe {3, 3, 2, 2, 2}, così come tutte le matrici di Hankelable derivate da esso. Ciò significa che possiamo raggruppare le matrici di Hankel in base a questi multiset di somma delle righe e quindi gestire ogni gruppo in modo indipendente, sfruttando più core del processore.

C'è anche una simmetria sfruttabile: le matrici con più zero di quelle sono in biiezione con le matrici con più di zero.

Doppio conteggio si verifica quando Matrice di Hankel M_1con la fila permutazione r_1e colonna permutazione c_1corrisponde Matrice di Hankel M_2con la fila permutazione r_2e colonna permutazione c_2(con un massimo di due, ma non tutti e tre M_1 = M_2, r_1 = r_2, c_1 = c_2). Le permutazioni di riga e colonna sono indipendenti, quindi se applichiamo fila permutazione r_1di M_1riga e permutazione r_2per M_2le colonne come multinsiemi devono essere uguali. Quindi per ogni gruppo, calcolo tutti i multiset di colonna ottenuti applicando una permutazione di riga a una matrice nel gruppo. Il modo più semplice per ottenere una rappresentazione canonica dei multiset è ordinare le colonne, e ciò è utile anche nel passaggio successivo.

Avendo ottenuto i distinti set di colonne, dobbiamo scoprire quante delle n!permutazioni di ciascuna sono uniche. A questo punto, il doppio conteggio può avvenire solo se un determinato multiset di colonna ha colonne duplicate: ciò che dobbiamo fare è contare il numero di occorrenze di ciascuna colonna distinta nel multiset e quindi calcolare il corrispondente coefficiente multinomiale. Poiché le colonne sono ordinate, è facile fare il conteggio.

Alla fine li sommiamo tutti.

La complessità asintotica non è banale da calcolare con la massima precisione, perché dobbiamo fare alcune ipotesi sugli insiemi. Valutiamo l'ordine dei 2^(2n-2) n!multiset di colonna, prendendo n^2 ln ntempo per ciascuno (incluso l'ordinamento); se il raggruppamento non richiede più di un ln nfattore, abbiamo una complessità temporale Theta(4^n n! n^2 ln n). Ma poiché i fattori esponenziali dominano completamente quelli polinomiali, lo è Theta(4^n n!) = Theta((4n/e)^n).


Questo è davvero impressionante. Potresti dire qualcosa sull'algoritmo che hai usato?

3

Python2 / 3

Approccio piuttosto ingenuo, in un linguaggio lento:

import itertools

def permute_rows(m):
    for perm in itertools.permutations(m):
        yield perm

def permute_columns(m):
    T = zip(*m)
    for perm in itertools.permutations(T):
        yield zip(*perm)

N = 1
while True:
    base_template = ["abcdefghijklmnopqrstuvwxyz"[i:i+N] for i in range(N)]

    templates = set()
    for c in permute_rows(base_template):
        for m in permute_columns(c):
            templates.add("".join("".join(row) for row in m))

    def possibs(free, templates):
        if free == 2*N - 1:
            return set(int(t, 2) for t in templates)

        s = set()
        for b in "01":
            new_templates = set(t.replace("abcdefghijklmnopqrstuvwxyz"[free], b) for t in templates)
            s |= possibs(free + 1, new_templates)

        return s

    print(len(possibs(0, templates)))
    N += 1

Esegui digitando python script.py.


Hai la lingua elencata come Python 2/3, ma perché funzioni in Python 2, non ti serve from __future__ import print_function(o qualcosa del genere)?
Alex A.

2
@AlexA. Normalmente sì, ma non in questo caso. Considera il comportamento di Python2 durante la digitazione return(1). Ora sostituisci returncon print:)
orlp

Freddo! Imparo qualcosa di nuovo ogni giorno. :)
Alex A.

2

Haskell

import Data.List
import Data.Hashable
import Control.Parallel.Strategies
import Control.Parallel
import qualified Data.HashSet as S

main = mapM putStrLn $ map (show.countHankellable) [1..]

a§b=[a!!i|i<-b]

hashNub :: (Hashable a, Eq a) => [a] -> [a]
hashNub l = go S.empty l
    where
      go _ []     = []
      go s (x:xs) = if x `S.member` s then go s xs
                                    else x : go (S.insert x s) xs

pmap = parMap rseq

makeMatrix :: Int->[Bool]->[[Bool]]
makeMatrix n vars = [vars§[i..i+n-1]|i<-[0..n-1]]

countHankellable :: Int -> Int
countHankellable n = let
    s = permutations [0..n-1]
    conjugates m = concat[permutations[r§q|r<-m]|q<-s]
    variableSets = sequence [[True,False]|x<-[0..2*(n-1)]]
 in
    length.hashNub.concat.pmap (conjugates.makeMatrix n ) $ variableSets

Non è mai stato così veloce come quello di Peter: è una configurazione davvero impressionante che è arrivato lì! Ora con molto più codice copiato da Internet. Uso:

$ ghc -threaded hankell.hs
$ ./hankell

Una risposta Haskell è sempre benvenuta. Grazie.

@Lembik - come sta la mia sulla tua macchina?
alexander-brett,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.