直交循環行列のカウント


8

行列の2つの行は、それらの内積がゼロに等しい場合、直交します。すべての行が直交対毎ので行列を呼び出し、直交行列を巡回行列は、各行ベクトルは、前の行ベクトルに対して右に一つの要素を回転させるものです。我々は唯一のエントリはどちらかである行列に興味があるだろう-11

仕事

直交する循環行列n/2によってn2分間で(可能な限り)できるだけ多くの異なる数をカウントするコードを記述しますn

入力

コードには入力がありません。n好きな値を試してみることができます。たとえば、コードは最小値からのn乗算のすべてを試行して、を試行することもできます。 4n = 2

出力

あなたが見つけた直交循環行列の数。また、コード自体にマトリックスを出力できるようにするための簡単なスイッチがコード内にあるはずです。

スコア

あなたが見つけた循環行列の数。

ヒント

循環行列n/2による直交は、がの倍数または未満のn場合にのみ存在します。n4n4

直交循環行列の例は次のとおりです。

-1  1 -1 -1
-1 -1  1 -1

素朴なアプローチのヒント

最も単純なアプローチは、考えられるすべての行列を反復することです。これは、次の2つの観察を使用して高速化できます。

  • 循環行列の直交性をテストするには、各行を最初の行と比較するだけです。これはサンプルコードに実装されています。

  • リンドンの単語を反復処理し、直交行列が見つかれば、可能な回転数を掛けることができます。これはまだテストされていないアイデアなので、バグがあるかもしれません。

サンプルコード

これは非常にシンプルで素朴なpythonの答えです。を使用して実行しましたtimeout 120

import itertools
def check_orthogonal(row):
    for i in xrange(1,int(n/2)):
        if (sum(row[j]*row[(j+i) % n] for j in xrange(n)) != 0):
            return False
    return True

counter = 0
for n in xrange(4,33,4):
    for row in itertools.product([-1,1],repeat = n):
        if check_orthogonal(row):
            counter +=1
            print "Counter is ", counter, ". n = ", n

正当性テスト

の場合n = 4,8,12,16,20,24,28、取得する必要がある個別の行列の数は12,40,144,128,80,192,560それぞれです。

素晴らしいレベル

サンプルコードから判断して、ここでは、どのような答えでも達成したい2つのレベルの素晴らしさを紹介します。

  • シルバーレベルの素晴らしさは、スコアまたは1156を取得することによって達成されます。

  • ゴールドレベルの素晴らしさは、それよりも高くなることです。

言語とライブラリ

好きな言語やライブラリを使用できます(このチャレンジ用に設計されたものではありません)。ただし、スコアリングの目的で私のマシンでコードを実行するので、Ubuntuでコードを実行する方法について明確な指示を提供してください。

私のマシンタイミングは私のマシンで実行されます。これは、8GB AMD FX-8350 8コアプロセッサへの標準のUbuntuインストールです。これは、コードを実行できる必要があることも意味します。

主要な回答

  • オクターブの flawrによる332

  • Pythonの404 by RT

  • pypyを使用したSample Solutionによる744

  • Javaを使用したThomas Kwaによる1156。シルバーレベルのすごさ!

  • OCamlの Reimer Behrendsによる1588。ゴールドレベルの素晴らしさ!


実際nに4の倍数であるすべてを1つ見つけることができますか?
flawr

@flawrさて、それは素晴らしい質問です!わかりませんが、知りたいです。

私が今見たものから(私のスクリプトが正しい場合)、それらはかなり珍しいようです。私の知る限り巡回アダマール行列({-1,1}のエントリを有する正方行列が)のみN = 1及び4のために存在することが推測されている知っている
flawr

@flawrはい、どちらの点でもそうです(私は、チャレンジを面白くするために珍しいものを望んでいました)。しかし、私はn / 2 x nの循環行列について知られていることは何も知りません。

1
私はn = 32で機能すると思うビットマスキングを使用した解決策があります。
リルトシアスト2016年

回答:


5

OCaml、1588(n = 36)

このソリューションでは、通常のビットパターンアプローチを使用して、-1と1のベクトルを表します。通常、スカラー積は、2つのビットベクトルのxorを取り、n / 2を引くことによって計算されます。ベクトルは、それらのxorが正確にn / 2ビットセットを持っている場合に限り直交します。

リンドンの単語は、それ自体の回転であるパターンを除外するため、これ自体は正規化された表現としては役に立ちません。また、計算に比較的コストがかかります。したがって、このコードはやや単純な正規形を使用します。これは、ローテーション後の最長の連続するゼロのシーケンス(または複数ある場合はそのうちの1つ)が最上位ビットを占有する必要があることを要求します。したがって、最下位ビットは常に1になります。

また、すべての候補ベクトルには少なくともn / 4個(最大で3n / 4個)のベクトルが必要であることに注意してください。したがって、n / 4 ... n / 2ビットが設定されたベクトルのみを考慮します。これは、補数と回転を介して他を導出できるためです(実際には、そのようなベクトルはすべてn / 2-2とn / 2 + 2の間にあるようです。 、しかしそれも証明するのは難しいようです)。

これらの正規形を最下位ビットから構築し、残りのゼロの実行(コードでは「ギャップ」と呼ばれます)は、正規形の要件に従う必要があるという制約を守ります。特に、1つ以上の1ビットを配置する必要がある限り、現在のギャップのための余地がなければならず、それ以外のスペースは、現在のギャップまたはこれまでに観測された他のギャップと同じ大きさでなければなりません。

また、結果のリストが少ないこともわかります。したがって、ディスカバリプロセス中に重複を回避しようとはせず、単にワーカーごとのセットに結果を記録し、最後にこれらのセットの結合を計算します。

アルゴリズムの実行時のコストが依然として指数関数的に増加し、ブルートフォースバージョンに匹敵する速度で増加することは注目に値します。これが私たちにもたらすものは、本質的に一定の要因による削減であり、ブルートフォースバージョンよりも並列化が難しいアルゴリズムを犠牲にしています。

最大40のnの出力:

 4: 12
 8: 40
12: 144
16: 128
20: 80
24: 192
28: 560
32: 0
36: 432
40: 640

プログラムはOCamlで記述されており、以下でコンパイルされます。

ocamlopt -inline 100 -nodynlink -o orthcirc unix.cmxa bigarray.cmxa orthcirc.ml

実行./orthcirc -helpして、プログラムがサポートするオプションを確認します。

それをサポートするアーキテクチャで-fno-PICは、いくつかの小さな追加のパフォーマンス向上を提供する可能性があります。

これはOCaml 4.02.3用に書かれていますが、古いバージョンでも動作する可能性があります(古すぎない限り)。


更新:この新しいバージョンは、より良い並列化を提供します。p * (n/4 + 1)問題のインスタンスごとにワーカースレッドを使用することに注意してください。一部のスレッドは、他のスレッドよりもかなり短時間で実行されます。の値はp2の累乗である必要があります。4〜8コアでのスピードアップは最小限(おそらく10%程度)ですが、大規模なコアの場合、コア数が多いほどスケールアップしnます。

let max_n = ref 40
let min_n = ref 4
let seq_mode = ref false
let show_res = ref false
let fanout = ref 8

let bitcount16 n =
  let b2 n = match n land 3 with 0 -> 0 | 1 | 2 -> 1 | _ -> 2 in
  let b4 n = (b2 n) + (b2 (n lsr 2)) in
  let b8 n = (b4 n) + (b4 (n lsr 4)) in
  (b8 n) + (b8 (n lsr 8))

let bitcount_data =
  let open Bigarray in
  let tmp = Array1.create int8_signed c_layout 65536 in
    for i = 0 to 65535 do
      Array1.set tmp i (bitcount16 i)
    done;
    tmp

let bitcount n =
  let open Bigarray in
  let bc n = Array1.unsafe_get bitcount_data (n land 65535) in
  (bc n) + (bc (n lsr 16)) + (bc (n lsr 32)) + (bc (n lsr 48))

module IntSet = Set.Make (struct
  type t = int
  let compare = Pervasives.compare
end)

let worker_results = ref IntSet.empty

let test_row vec row mask n =
  bitcount ((vec lxor (vec lsr row) lxor (vec lsl (n-row))) land mask) * 2 = n

let record vec len n =
  let m = (1 lsl n) - 1 in
  let rec test_orth_circ ?(row=2) vec m n =
    if 2 * row >= n then true
    else if not (test_row vec row m n) then false
    else test_orth_circ ~row:(row+1) vec m n
  in if test_row vec 1 m n &&
        test_orth_circ vec m n then
  begin
    for i = 0 to n - 1 do
      let v = ((vec lsr i) lor (vec lsl (n - i))) land m in
      worker_results := IntSet.add v !worker_results;
      worker_results := IntSet.add (v lxor m) !worker_results
    done
  end

let show vec n =
  for i = 0 to n / 2 - 1 do
    let vec' = (vec lsr i) lor (vec lsl (n - i)) in
    for j = 0 to n-1 do
      match (vec' lsr (n-j)) land 1 with
      | 0 -> Printf.printf "  1"
      | _ -> Printf.printf " -1"
    done; Printf.printf "\n"
  done; Printf.printf "\n"; flush stdout

let rec build_normalized ~prefix ~plen ~gap ~maxgap ~maxlen ~bits ~fn =
  if bits = 0 then
    fn prefix plen maxlen
  else begin
    let room = maxlen - gap - plen - bits in
    if room >= gap && room >= maxgap then begin
      build_normalized
        ~prefix:(prefix lor (1 lsl (plen + gap)))
        ~plen:(plen + gap + 1)
        ~gap:0
        ~maxgap:(if gap > maxgap then gap else maxgap)
        ~maxlen
        ~bits:(bits - 1)
        ~fn;
      if room > gap + 1 && room > maxgap then
        build_normalized ~prefix ~plen ~gap:(gap + 1) ~maxgap ~maxlen ~bits ~fn
    end
  end

let rec log2 = function
| 0 -> -1
| n -> 1 + (log2 (n lsr 1))

let rec test_gap n pat =
  if n land pat = 0 then true
  else if pat land 1 = 0 then test_gap n (pat lsr 1)
  else false

let rec test_gaps n maxlen len =
  let fill k = (1 lsl k) -1 in
  if len = 0 then []
  else if test_gap n ((fill maxlen) lxor (fill (maxlen-len))) then
    len :: (test_gaps n maxlen (len-1))
  else test_gaps n maxlen (len-1)

let rec longest_gap n len =
  List.fold_left max 0 (test_gaps n len len)

let start_search low lowbits maxlen bits fn =
  let bits = bits - (bitcount low) in
  let plen = log2 low + 1 in
  let gap = lowbits - plen in
  let maxgap = longest_gap low lowbits in
  worker_results := IntSet.empty;
  if bits >= 0 then
    build_normalized ~prefix:low ~plen ~gap ~maxgap ~maxlen ~bits ~fn;
  !worker_results

let spawn f x =
  let open Unix in
  let safe_fork () = try fork() with _ -> -1 in
  let input, output = pipe () in
  let pid = if !seq_mode then -1 else safe_fork() in
  match pid with
  | -1 -> (* seq_mode selected or fork() failed *)
     close input; close output; (fun () -> f x)
  | 0 -> (* child process *)
    close input;
    let to_parent = out_channel_of_descr output in
    Marshal.to_channel to_parent (f x) [];
    close_out to_parent; exit 0
  | pid -> (* parent process *)
    close output;
    let from_child = in_channel_of_descr input in
    (fun () -> 
      ignore (waitpid [] pid);
      let result = Marshal.from_channel from_child in
      close_in from_child; result)

let worker1 (n, k) =
  start_search 1 1 n k record

let worker2 (n, k, p) =
  start_search (p * 2 + 1) (log2 !fanout + 1) n k record

let spawn_workers n =
  let queue = Queue.create () in
  if n = 4 || n = 8 then begin
    for i = n / 4 to n / 2 do
      Queue.add (spawn worker1 (n, i)) queue
    done
  end else begin
    for i = n / 2 downto n / 4 do
      for p = 0 to !fanout - 1 do
        Queue.add (spawn worker2 (n, i, p)) queue
      done
    done
  end;
  Queue.fold (fun acc w -> IntSet.union acc (w())) IntSet.empty queue

let main () =
  if !max_n > 60 then begin
    print_endline "error: cannot handle n > 60";
    exit 1
  end;
  min_n := max !min_n 4;
  if bitcount !fanout <> 1 then begin
    print_endline "error: number of threads must be a power of 2";
    exit 1;
  end;
  for n = !min_n to !max_n do
    if n mod 4 = 0 then
    let result = spawn_workers n in
    Printf.printf "%2d: %d\n" n (IntSet.cardinal result);
    if !show_res then
      IntSet.iter (fun v -> show v n) result;
    flush stdout
  done

let () =
  let args =[("-m", Arg.Set_int min_n, "min size of the n by n/2 matrix");
             ("-n", Arg.Set_int max_n, "max size of the n by n/2 matrix");
             ("-p", Arg.Set_int fanout, "parallel fanout");
             ("-seq", Arg.Set seq_mode, "run in single-threaded mode");
             ("-show", Arg.Set show_res, "display list of results") ] in
  let usage = ("Usage: " ^
               (Filename.basename Sys.argv.(0)) ^
               " [-n size] [-seq] [-show]") in
  let error _ = Arg.usage args usage; exit 1 in
  Arg.parse args error usage;
  main ()

これはすばらしいことです。とはいえ、Nimの回答もどうですか?:)

あなたのコードは、私のマシンでは時間内に36:432にしか到達しません。

ええと。クアッドコアプロセッサ(2.5GHz Intel Core i7)を搭載した私のラップトップでは2分弱で40に達するので、あなたのコンピューターでも40になるとかなり確信していました。それに応じて更新します。あなたの他の質問について、私はブルートフォースNim実装を持っていますが、それは(ブルートフォース部分のため)2倍の速度で実行され、Thomas Kwaのコードとそれほど違いはありません(検索を少し減らします)スペース)、エキサイティングなものを見逃しているわけではありません。
Reimer Behrends、2016年

あなたのコードには64ビットシステムが必要だと思いますか?私はそれを常に0を出力する32ビットのものでテストしました

1
正解です。これは、ベクターをintとして格納するためです。64ビット表現(または大きな整数)を強制することもできますが、32ビットシステムではそれが非常に遅くなります。
Reimer Behrends 2016年

3

Java、1156マトリックス

これはかなり単純なビットマスキングを使用し、私のマシンではn = 28の場合15秒未満かかります。

循環行列は、最初の行によって決定されます。したがって、私は行列の最初の行をビットベクトルとして表します。1と0は-1と1を表します。2つの行は、それらをxorしたときのセットビット数がn / 2の場合、直交します。

import java.util.Arrays;

class Main {

    static void dispmatrix(long y,int N)
    {
        int[][] arr = new int[N/2][N];
        for(int row=0; row < N/2; row++)
        {
            for(int col=0; col < N; col++)
            {
                arr[row][col] = (int) ((y >>> (N+row-col)) & 1L);
            }
        }
        System.out.println(Arrays.deepToString(arr));
    }

  public static void main(String[] args) {

    int count = 0;
    boolean good;
    boolean display = false;
    long y;
    for(int N=4; N <= 28 ;N += 4)
    {
    long mask = (1L << N) - 1;
    for(long x=0; x < (1L<<N); x++)
    {
        good = true;
        y = x + (x << N);

        for(int row = 1; row < N/2; row++)
        {
            good &= N/2 == Long.bitCount((y ^ (y >>> row)) & mask);
        }

        if(good)
        {
            if(display) dispmatrix(y,N);
            count++;
        }

    }
    System.out.println(count);
    }
  }
}

現在、Eclipseを機能させることができないため、これはrepl.itでテストされました。

次に、n = 28の場合の最初のr行に直交する最初の行の数を示します。

[268435456, 80233200, 23557248, 7060320, 2083424, 640304, 177408, 53088, 14896, 4144, 2128, 1008, 1008, 560]

最適化:

  • 直交性は両方の行の循環回転で変化しないので、最初の行が他の行と直交していることを確認するだけで済みます。
  • Nビットの巡回シフトを手動でN / 2回行うのではなく、シフトするビットをの上部に格納し、下位Nビットのlongシングルandを使用して必要なビットを抽出します。

可能なさらなる最適化:

  • リンドン語を生成します。私の計算によると、これはリンドンの単語がそれぞれ〜1000サイクル未満で生成できる場合にのみ意味があります。
  • リンドンの単語に時間がかかりすぎる場合でも、で始まるビットベクトルのみをチェックし00、直交行列を見つけるときにその回転(およびNOTの回転)を使用できます。最初の行は不可能であり、他のすべての行にはa またはa が含まれているため0101...01、これを行うことができます。1010...100011
  • 行列がおそらく直交している場合、分岐はおそらく途中まで可能です。ただし、分岐にかかるコストがわからないので、テストする必要があります。
  • 上記が機能する場合、行1以外の行から始めますか?
  • たぶん、いくつかの可能性を排除するいくつかの数学的な方法があるでしょう。どうなるかわかりません。
  • もちろんC ++で書いてください。

これは素晴らしい。ありがとうございました!数値n=36も確認できるよう、いくつかの最適化をお待ちしています。

ああ、あなたはシルバーレベルの素晴らしさに達しました!:)

2

Python(i5-5300Uでは404マトリックス)

主にこれを他の人が改善するための出発点として投稿します。これは、たくさんクリーンアップしたり、並列化したりできます。

import numpy
import itertools
import time

def findCirculantOrthogonalRows(n, t1, timeLimit):
  validRows = []
  testMatrix = numpy.zeros((n//2, n))
  identityMatrixScaled = n*numpy.identity(n//2)
  outOfTime = False
  for startingRowTuple in itertools.product([1, -1], repeat=n):
     for offset in range(n//2):
       for index in range(n):
         testMatrix[offset][index] = startingRowTuple[(index-offset) % n]

     if(numpy.array_equal(identityMatrixScaled, testMatrix.dot(testMatrix.transpose()))):
      validRows.append(startingRowTuple)

    if(time.clock() - t1 >= timeLimit):
      outOfTime = True
      break

  return (validRows, outOfTime)

n = 4
validFirstRows = []
t1 = time.clock()
timeLimit = 120
fullOutput = True

while(True):
  print('calling with', n)
  (moreRows, outOfTime) = findCirculantOrthogonalRows(n, t1, timeLimit)

  if(len(moreRows) > 0):
    validFirstRows.extend(moreRows)

  if(outOfTime == True):
    break

  n += 4

print('Found', len(validFirstRows), 'circulant orthogonal matrices in', timeLimit, 'seconds')

if(fullOutput):
  counter = 1
  for r in validFirstRows:
    n = len(r)
    matrix = numpy.zeros((n//2, n))
    for offset in range(n//2):
      for index in range(n):
        matrix[offset][index] = r[(index-offset) % n]
    print('matrix #', counter, ':\n', matrix)
    counter += 1
    input()

サンプルコードをいくつか追加しました。最初の明らかな改善点は、リンドンの単語を反復処理することですが、それをどのようにコーディングするかはわかりません。

2

Matlab / Octave、381/328マトリックス

また、考えられるあらゆる組み合わせを試してみただけの単純なアプローチです。

counter = 0;
%ok: 2,4,8
%none: 
tic
for n=[2,4,8,12,16,20];
    m=n/2;
    N=2^n-1;
    for k=1:N

        k/N; %remove ; in order to see progress
        v=(dec2bin(k,n)-'0')*2-1;               %first row

        z=toeplitz([v(1) fliplr(v(m+2:n))], v); %create circulante matrix
        w = z * z.';
        if norm(w - diag(diag(w))) < eps
            counter = counter+1;
            %n    %remove % in order to see the matrices
            %z
        end
        if toc>120;
            break;
        end
    end
end
counter

オクターブでは、コードが画面に大量に出力し、遅くなる可能性があります。"ans = ...."

そうそう、私はそれを追加するのを忘れていました:最もインデントされた行にはnとaがありz、これら2つは単一のでコメント化できます%。そして、あなたは追加することができます;counter = counter+1k/N 出力を抑制するであろう。
flawr
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.