Đếm các ma trận tuần hoàn trực giao


8

Hai hàng của ma trận là trực giao nếu sản phẩm bên trong của chúng bằng không. Gọi một ma trận với tất cả các hàng theo cặp trực giao một ma trận trực giao . Một ma trận tuần hoàn là một trong đó mỗi vectơ hàng được xoay một phần tử sang bên phải so với vectơ hàng trước. Chúng tôi sẽ chỉ quan tâm đến ma trận trong đó các mục là -1hoặc 1.

Bài tập

Viết mã để đếm càng nhiều khác nhau n/2bằng nma trận trực giao, tuần hoàn càng tốt trong 2 phút (chẵn n).

Đầu vào

Mã này không có đầu vào. Nó có thể thử bất kỳ giá trị thậm chí của nnó thích. Ví dụ: mã có thể thử tất cả các nbội số 4bắt đầu từ nhỏ nhất và cũng thử n = 2.

Đầu ra

Số lượng ma trận tuần hoàn trực giao mà bạn đã tìm thấy. Cũng cần có một công tắc đơn giản trong mã của bạn để cho phép nó tự xuất ra ma trận.

Ghi bàn

Số lượng ma trận tuần hoàn bạn đã tìm thấy.

Gợi ý

Trực giao n/2bởi nma trận tuần hoàn chỉ tồn tại khi nbội số của 4hoặc nnhỏ hơn 4.

Một ví dụ ma trận tuần hoàn trực giao là:

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

Lời khuyên cho cách tiếp cận ngây thơ

Cách tiếp cận ngây thơ nhất chỉ là lặp đi lặp lại trên tất cả các ma trận có thể. Điều này có thể được tăng tốc bằng cách sử dụng hai quan sát sau đây.

  • Để kiểm tra tính trực giao của ma trận tuần hoàn, chúng ta chỉ cần so sánh từng hàng với hàng đầu tiên. Điều này được thực hiện trong mã mẫu.

  • Chúng ta có thể lặp lại các từ Lyndon và sau đó nếu chúng ta tìm thấy một ma trận trực giao nhân với số lần quay có thể. Đây là ý tưởng chưa được kiểm tra nên có thể có lỗi.

Mã mẫu

Đây là một câu trả lời trăn rất đơn giản và ngây thơ. Tôi chạy nó bằng cách sử dụng 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

Kiểm tra độ chính xác

Đối với n = 4,8,12,16,20,24,28, số lượng ma trận riêng biệt bạn nên nhận được 12,40,144,128,80,192,560, tương ứng.

Mức độ tuyệt vời

Đánh giá theo mã mẫu, tôi xin trình bày hai cấp độ tuyệt vời mà bất kỳ câu trả lời nào cũng có thể mong muốn đạt được.

  • Mức độ tuyệt vời của bạc đạt được bằng cách đạt được số điểm hoặc 1156 .

  • Mức vàng tuyệt vời là để có được cao hơn thế.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ hoặc thư viện nào bạn thích (không được thiết kế cho thử thách này). Tuy nhiên, với mục đích ghi điểm, tôi sẽ chạy mã của bạn trên máy của mình, vì vậy vui lòng cung cấp hướng dẫn rõ ràng về cách chạy mã trên Ubuntu.

Máy của tôi Thời gian sẽ được chạy trên máy của tôi. Đây là bản cài đặt Ubuntu tiêu chuẩn trên Bộ xử lý tám lõi AMD FX-8350. Điều này cũng có nghĩa là tôi cần để có thể chạy mã của bạn.

Câu trả lời hàng đầu

  • 332 bởi flawr trong Octave

  • 404 bởi RT bằng Python

  • 744 bằng giải pháp mẫu sử dụng pypy

  • 1156 bởi Thomas Kwa sử dụng Java . Mức độ bạc tuyệt vời!

  • 1588 bởi Reimer Behrends ở OCaml . Mức vàng tuyệt vời!


Bạn thực sự có thể tìm thấy một cho mỗi nđó là bội số của bốn?
flawr

@flawr Bây giờ đó là một câu hỏi tuyệt vời! Tôi không có ý tưởng nhưng tôi rất muốn biết.

Từ những gì tôi đã thấy bây giờ (nếu kịch bản của tôi là chính xác), chúng dường như khá hiếm. Theo như tôi biết thì ma trận hadamard tuần hoàn (ma trận vuông có các mục trong {-1,1}) được phỏng đoán chỉ tồn tại cho n = 1 và 4.
flawr

@flawr Có trên cả hai tính (Tôi muốn một cái gì đó hiếm có để làm cho thử thách thú vị). Nhưng tôi không biết bất cứ điều gì được biết về n / 2 bởi n ma trận tuần hoàn.

1
Tôi có một giải pháp sử dụng bitmasking mà tôi nghĩ sẽ hoạt động với n = 32.
lirtosiast

Câu trả lời:


5

Tháng 10, 1588 (n = 36)

Giải pháp này sử dụng cách tiếp cận mẫu bit thông thường để biểu diễn các vectơ -1 và 1. Sản phẩm vô hướng được tính như bình thường bằng cách lấy xor của hai vectơ bit và trừ đi n / 2. Các vectơ là trực giao nếu xor của chúng có chính xác n / 2 bit được đặt.

Các từ Lyndon không hữu ích như một đại diện được chuẩn hóa cho điều này, vì chúng loại trừ bất kỳ mẫu nào là một vòng quay của chính nó. Chúng cũng tương đối đắt tiền để tính toán. Do đó, mã này sử dụng một hình thức bình thường có phần đơn giản hơn, đòi hỏi chuỗi số 0 liên tiếp dài nhất sau khi quay (hoặc một trong số chúng, nếu có bội số) phải chiếm các bit quan trọng nhất. Theo sau đó, bit có ý nghĩa nhỏ nhất luôn là 1.

Cũng lưu ý rằng bất kỳ vectơ ứng cử viên nào cũng phải có ít nhất n / 4 cái (và nhiều nhất là 3n / 4). Do đó, chúng tôi chỉ xem xét các vectơ có n / 4 ... n / 2 bit được đặt, vì chúng tôi có thể lấy được các vectơ khác thông qua bổ sung và xoay (trong thực tế, tất cả các vectơ như vậy dường như có từ n / 2-2 đến n / 2 + 2 , nhưng điều đó dường như cũng khó chứng minh).

Chúng tôi xây dựng các hình thức bình thường này từ bit có ý nghĩa nhỏ nhất, quan sát ràng buộc rằng mọi lần chạy số 0 còn lại (được gọi là "khoảng trống" trong mã) phải tuân theo yêu cầu biểu mẫu thông thường của chúng tôi. Cụ thể, miễn là phải đặt ít nhất 1 bit nữa, thì phải có khoảng trống cho khoảng cách hiện tại và khoảng trống khác ít nhất là khoảng cách hiện tại hoặc bất kỳ khoảng cách nào khác được quan sát cho đến nay.

Chúng tôi cũng quan sát rằng danh sách kết quả là nhỏ. Do đó, chúng tôi không cố gắng tránh trùng lặp trong quá trình khám phá mà chỉ ghi lại kết quả trong các bộ công nhân và tính toán kết hợp của các bộ này ở cuối.

Điều đáng chú ý là chi phí thời gian chạy của thuật toán vẫn tăng theo cấp số nhân và với tốc độ tương đương với phiên bản brute-force; những gì điều này mua cho chúng ta về cơ bản là giảm theo một yếu tố không đổi, và phải trả giá bằng một thuật toán khó song song hơn so với phiên bản brute-force.

Đầu ra cho n lên tới 40:

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

Chương trình được viết bằng OCaml, được biên dịch với:

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

Chạy ./orthcirc -helpđể xem những tùy chọn mà chương trình hỗ trợ.

Trên các kiến ​​trúc hỗ trợ nó, -fno-PICcó thể cung cấp một số tăng hiệu suất bổ sung nhỏ.

Điều này được viết cho OCaml 4.02.3, nhưng cũng có thể hoạt động với các phiên bản cũ hơn (miễn là chúng không quá cũ).


CẬP NHẬT: Phiên bản mới này cung cấp song song tốt hơn. Lưu ý rằng nó sử dụng các p * (n/4 + 1)luồng công nhân cho mỗi trường hợp của vấn đề và một số trong số chúng vẫn sẽ chạy ngắn hơn đáng kể so với các luồng khác. Giá trị pphải là một công suất bằng 2. Tốc độ tăng tốc trên 4-8 lõi là tối thiểu (có thể khoảng 10%), nhưng nó có tỷ lệ tốt hơn với số lượng lớn các lõi lớn 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 ()

Điều này là tuyệt vời và chào mừng trở lại! Có nói rằng ... làm thế nào về một câu trả lời Nim quá? :)

Mã của bạn chỉ kịp 36: 432 trên máy của tôi.

Huh. Nó chạy tới 40 chỉ trong chưa đầy hai phút trên máy tính xách tay của tôi với bộ xử lý lõi tứ (Intel Core i7 2,5 GHz), vì vậy tôi khá tự tin rằng nó cũng sẽ làm cho nó lên 40 trên máy tính của bạn. Tôi sẽ cập nhật tương ứng. Về câu hỏi khác của bạn, trong khi tôi có triển khai Nim vũ phu, thì câu hỏi đó vẫn chạy chậm gấp đôi (vì phần vũ phu) và không quá khác biệt so với mã của Thomas Kwa (chỉ giảm một chút tìm kiếm không gian), vì vậy nó không giống như bạn đang thiếu bất cứ điều gì thú vị.
Reimer Behrends

Tôi có đúng rằng mã của bạn cần một hệ thống 64 bit? Tôi vừa thử nó trên 32 bit, trong đó nó luôn xuất ra 0.

1
Đúng, vì nó lưu trữ các vectơ như ints. Tôi cũng có thể buộc đại diện 64 bit (hoặc thậm chí là số nguyên lớn), nhưng điều đó sẽ chậm hơn rất nhiều trên hệ thống 32 bit.
Reimer Behrends

3

Java, 1156 ma trận

Điều này sử dụng bitmasking khá ngây thơ và mất dưới 15 giây cho n = 28 trên máy của tôi.

Ma trận tuần hoàn được xác định bởi các hàng đầu tiên của chúng. Do đó, tôi biểu diễn các hàng đầu tiên của ma trận dưới dạng vectơ bit: 1 và 0 đại diện cho -1 và 1. Hai hàng là trực giao khi số bit được đặt khi chúng xor'd với nhau là 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);
    }
  }
}

Tôi không thể làm cho Eclipse hoạt động ngay bây giờ, vì vậy điều này đã được thử nghiệm trên repl.it.

Dưới đây là số hàng đầu tiên trực giao với hàng r đầu tiên sau đó cho n = 28:

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

Tối ưu hóa:

  • Vì tính trực giao không thay đổi theo vòng quay của cả hai hàng, nên chúng ta chỉ cần kiểm tra xem hàng đầu tiên có trực giao với phần còn lại không.
  • Thay vì tự làm một ca tuần hoàn N-bit N / 2 lần, tôi lưu trữ các bit được dịch chuyển ở đầu long, và sau đó sử dụng một andbit có N bit thấp hơn để trích xuất các bit cần thiết.

Tối ưu hóa có thể hơn nữa:

  • Tạo từ Lyndon. Theo tính toán của tôi, điều này chỉ có ý nghĩa nếu các từ Lyndon có thể được tạo ra trong ít hơn ~ 1000 chu kỳ mỗi từ.
  • Nếu các từ Lyndon mất quá nhiều thời gian, chúng ta vẫn chỉ có thể kiểm tra các vectơ bit bắt đầu 00và sử dụng các phép quay của chúng (và các phép quay của KHÔNG) khi chúng ta tìm thấy một ma trận trực giao. Chúng tôi có thể làm điều này bởi vì 0101...011010...10không thể có hàng đầu tiên và tất cả các hàng khác chứa a 00hoặc a 11.
  • Nhánh có thể đi được nửa đường khi ma trận có thể là trực giao. Tuy nhiên, tôi không biết phân nhánh sẽ có giá bao nhiêu, vì vậy tôi sẽ cần kiểm tra.
  • Nếu các công việc trên, bắt đầu với một hàng khác ngoài hàng 1?
  • Có lẽ có một số cách toán học để loại bỏ một số khả năng. Tôi không biết đó là gì.
  • Viết bằng C ++, tất nhiên.

Điều đó thật tuyệt. Cảm ơn bạn! Tôi mong đợi một số tối ưu hóa của bạn để chúng ta cũng có thể thấy các con số n=36.

Oh và bạn đã đạt đến cấp độ bạc tuyệt vời! :)

2

Python (ma trận 404 trên i5-5300U)

Chủ yếu đăng bài này như một điểm khởi đầu để người khác cải thiện, điều này có thể được làm sạch rất nhiều, song song, v.v.

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()

Tôi đã thêm một số mã mẫu. Sự cải thiện rõ ràng đầu tiên là lặp đi lặp lại các từ Lyndon nhưng tôi không chắc cách mã hóa nó.

2

Matlab / Octave, ma trận 381/328

Cũng chỉ là cách tiếp cận ngây thơ, cố gắng mọi sự kết hợp có thể.

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

Trong quãng tám, mã in một lượng lớn lên màn hình có thể làm chậm nó. "ans = ...."

Ồ đúng rồi, tôi quên thêm rằng: Các dòng thụt vào nhiều nhất có một nzhai, hai dòng này có thể được nhận xét bằng một %. Và sau đó bạn có thể thêm một cái ;sau counter = counter+1k/N cái sẽ triệt tiêu đầu ra.
flawr
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.