255 lines
9.0 KiB
Python
255 lines
9.0 KiB
Python
|
import math
|
||
|
import numpy as np
|
||
|
|
||
|
def decode(x, N):
|
||
|
index = 0
|
||
|
output = np.zeros((N))
|
||
|
while x > 0 and index < N:
|
||
|
output[index] = x & 0b1
|
||
|
x >>= 1
|
||
|
index += 1
|
||
|
return output
|
||
|
|
||
|
def hamming_distance(a, b):
|
||
|
return np.sum(np.logical_xor(a, b))
|
||
|
|
||
|
def xor(x, bits):
|
||
|
return np.sum(x[:bits]) % 2
|
||
|
|
||
|
def compute_pyramids(N):
|
||
|
num_orders = max(int(N / 2), 1)
|
||
|
pyramids = np.zeros((num_orders, N, N)).astype(np.int32)
|
||
|
for i in range(2, N):
|
||
|
for j in range(1, i):
|
||
|
pyramids[0][i][j] = j
|
||
|
for order in range(1, num_orders):
|
||
|
# build out the first column
|
||
|
acc = 0
|
||
|
for i in range(order * 2 + 2, N):
|
||
|
acc += pyramids[order - 1][i - 2][1]
|
||
|
pyramids[order][i][1] = acc
|
||
|
# accumulate the first column and place it on the diagonal(s)
|
||
|
for k in range(0, int(order / 2) + 1):
|
||
|
acc = 0
|
||
|
for i in range(order * 2 + 2, N):
|
||
|
acc += pyramids[order][i][1]
|
||
|
pyramids[order][i][i - 1 - 2 * k] = acc
|
||
|
# for odd, copy the first column to the first diagonal
|
||
|
if order % 2 == 1:
|
||
|
k += 1
|
||
|
for i in range(order * 2 + 2, N):
|
||
|
pyramids[order][i][i - 1 - 2 * k] = pyramids[order][i][1]
|
||
|
# integrate under the diagonal
|
||
|
inset = 1
|
||
|
for j in reversed(range(2, N - 2 * k - 2)):
|
||
|
acc = pyramids[order][N - inset - 1][j]
|
||
|
for i in range(N - inset, N):
|
||
|
acc += pyramids[order - 1][i - 2][j]
|
||
|
pyramids[order][i][j] = acc
|
||
|
if order * 2 + 2 < N - inset:
|
||
|
inset += 1
|
||
|
return pyramids
|
||
|
|
||
|
def compute_pyramids_full(N):
|
||
|
num_orders = max(int(N / 2), 1)
|
||
|
pyramids = np.zeros((num_orders, N, N)).astype(np.int32)
|
||
|
# 1st order can be filled in as multiplication and forms the base case
|
||
|
for i in range(0, N):
|
||
|
for j in range(0, i + 1):
|
||
|
pyramids[0][i][j] = (i - j + 1) * (j + 1)
|
||
|
for order in range(1, num_orders):
|
||
|
offset = order * 2
|
||
|
|
||
|
# fill in the LHS and diagonal
|
||
|
for i in range(0, N - offset):
|
||
|
value = math.comb(2 * (order + 1) + i - 1, i)
|
||
|
pyramids[order][i + offset][0] = value
|
||
|
# mirror
|
||
|
pyramids[order][i + offset][i + offset] = value
|
||
|
|
||
|
# accumulate along the diagonals
|
||
|
for i in range(1, N):
|
||
|
value = pyramids[order][i][0]
|
||
|
acc = value
|
||
|
for j in range(1, N - i):
|
||
|
value += acc
|
||
|
pyramids[order][i + j][j] = value
|
||
|
acc += pyramids[order - 1][i + j - 1][j - 1]
|
||
|
|
||
|
return pyramids
|
||
|
|
||
|
def get_total_band_count_2(distance, band_distance, N):
|
||
|
if band_distance % 2 == 1:
|
||
|
return 0
|
||
|
order = int(band_distance / 2) - 1
|
||
|
if order < 0:
|
||
|
return 0
|
||
|
if distance < order + 1:
|
||
|
return 0
|
||
|
if distance > N - order - 1:
|
||
|
return 0
|
||
|
order_root = math.factorial(2 * (order + 1)) / math.factorial(order + 1) ** 2
|
||
|
scale = math.comb(N - (order + 1) * 2, distance - order - 1)
|
||
|
value = math.comb(2 * (order + 1) + N - 2 * (order + 1), N - 2 * (order + 1))
|
||
|
return order_root * scale * value
|
||
|
|
||
|
def get_incoherent_band_count_2(pyramids, distance, band_distance, k, N):
|
||
|
if k == 0 or k == N or band_distance % 2 == 1:
|
||
|
return 0
|
||
|
order = int(band_distance / 2) - 1
|
||
|
if order < 0:
|
||
|
return 0
|
||
|
if distance < order + 1:
|
||
|
return 0
|
||
|
if distance > N - order - 1:
|
||
|
return 0
|
||
|
order_root = math.factorial(2 * (order + 1)) / math.factorial(order + 1) ** 2
|
||
|
scale = math.comb(N - (order + 1) * 2, distance - order - 1)
|
||
|
value = pyramids[order][N - 2][k - 1]
|
||
|
return order_root * scale * value
|
||
|
|
||
|
# pyramid = pyramids[order]
|
||
|
# offset = (N - 1 - order) - distance
|
||
|
# multiplier = pyramid[2 * order + offset][2 * order + 1 + offset]
|
||
|
# row = N - offset
|
||
|
# column = k
|
||
|
# value = pyramid[row][column]
|
||
|
# return multiplier * value
|
||
|
|
||
|
def get_incoherent_band_count(pyramids, distance, band_distance, k, N):
|
||
|
if k == 0 or k == N or band_distance % 2 == 1:
|
||
|
return 0
|
||
|
order = int(band_distance / 2) - 1
|
||
|
if order < 0:
|
||
|
return 0
|
||
|
if distance < order + 1:
|
||
|
return 0
|
||
|
if distance > N - order - 1:
|
||
|
return 0
|
||
|
if distance < k:
|
||
|
distance = N - distance
|
||
|
k = N - k
|
||
|
pyramid = pyramids[order]
|
||
|
offset = (N - 1 - order) - distance
|
||
|
multiplier = pyramid[2 * order + 2 + offset][2 * order + 1 + offset]
|
||
|
row = N - offset
|
||
|
column = k
|
||
|
value = pyramid[row][column]
|
||
|
return multiplier * value
|
||
|
|
||
|
def get_total_band_count(pyramids, distance, band_distance, N):
|
||
|
if band_distance % 2 == 1:
|
||
|
return 0
|
||
|
order = int(band_distance / 2) - 1
|
||
|
if order < 0:
|
||
|
return 0
|
||
|
if distance < order + 1:
|
||
|
return 0
|
||
|
if distance > N - order - 1:
|
||
|
return 0
|
||
|
pyramid = pyramids[order]
|
||
|
offset = (N - 1 - order) - distance
|
||
|
length = N + 1 - 2 * (order + 1)
|
||
|
a = pyramid[2 * order + 2 + offset][2 * order + 1 + offset]
|
||
|
b = pyramid[2 * order + 2 + (length - offset - 1)][2 * order + 1 + (length - offset - 1)]
|
||
|
return a * b
|
||
|
|
||
|
# def compute_band_distances(pyramids, N):
|
||
|
# num_orders = max(int(N / 2), 1)
|
||
|
# incoherent_bands = np.zeros((N + 1, N + 1, N + 1)).astype(np.int32)
|
||
|
# for order in range(0, num_orders):
|
||
|
# band_distance = (order + 1) * 2
|
||
|
# for k in range(1, N):
|
||
|
|
||
|
|
||
|
# for k in range(0, N + 1):
|
||
|
# for distance in range()
|
||
|
|
||
|
def main():
|
||
|
# N = 8
|
||
|
# print(compute_pyramids_full(N))
|
||
|
# total_distances = np.zeros((N + 1, N + 1)).astype(np.int32)
|
||
|
# for i in range(0, N + 1):
|
||
|
# for j in range(0, N + 1):
|
||
|
# total_distances[i][j] = get_total_band_count_2(i, j, N)
|
||
|
# print(total_distances)
|
||
|
# return
|
||
|
|
||
|
max_N = 8
|
||
|
orders = [np.zeros((max_N + 1, max_N + 1)).astype(np.int32) for _ in range(0, max_N)]
|
||
|
|
||
|
print('Attempting discrete solution...')
|
||
|
pyramids = compute_pyramids_full(max_N + 1)
|
||
|
|
||
|
for N in range(max_N, max_N + 1):
|
||
|
# for N in range(2, max_N + 1):
|
||
|
print('=============================')
|
||
|
print('N@', N)
|
||
|
print('Generating points...')
|
||
|
points = []
|
||
|
for i in range(0, 2 ** N):
|
||
|
points.append(decode(i, N))
|
||
|
|
||
|
print('Computing bands...')
|
||
|
bands = [[] for _ in range(0, N + 1)]
|
||
|
for i in range(1, len(points)):
|
||
|
distance = hamming_distance(points[0], points[i])
|
||
|
bands[distance].append(points[i])
|
||
|
|
||
|
print('Computing band distances...')
|
||
|
incoherent_distances = np.zeros((N + 1, N + 1))
|
||
|
for k in range(0, N + 1):
|
||
|
print('k@', k)
|
||
|
# print(k, '================================')
|
||
|
x_a = points[0]
|
||
|
y_a = xor(x_a, k)
|
||
|
incoherent_bands = np.zeros((N + 1, N + 1)).astype(np.int32)
|
||
|
precomputed_incoherent_bands = np.zeros((N + 1, N + 1)).astype(np.int32)
|
||
|
total_bands = np.zeros((N + 1, N + 1)).astype(np.int32)
|
||
|
precomputed_total_bands = np.zeros((N + 1, N + 1)).astype(np.int32)
|
||
|
for distance in range(0, N + 1):
|
||
|
band = bands[distance]
|
||
|
for x_b in band:
|
||
|
y_b = xor(x_b, k)
|
||
|
if y_a != y_b:
|
||
|
incoherent_distances[k][distance] += 1
|
||
|
|
||
|
if len(band) < 2:
|
||
|
continue
|
||
|
for band_origin in range(0, len(band)):
|
||
|
x_p = band[band_origin]
|
||
|
# print(x_p)
|
||
|
y_p = xor(x_p, k)
|
||
|
for i in range(0, len(band)):
|
||
|
if i == band_origin:
|
||
|
continue
|
||
|
x_q = band[i]
|
||
|
y_q = xor(x_q, k)
|
||
|
band_distance = hamming_distance(x_p, x_q)
|
||
|
total_bands[distance][band_distance] += 1
|
||
|
if y_p != y_q:
|
||
|
incoherent_bands[distance][band_distance] += 1
|
||
|
for band_distance in range(0, N + 1):
|
||
|
precomputed_incoherent_bands[distance][band_distance] = get_incoherent_band_count_2(pyramids, distance, band_distance, k, N)
|
||
|
precomputed_total_bands[distance][band_distance] = get_total_band_count_2(distance, band_distance, N)
|
||
|
# print(incoherent_bands)
|
||
|
for order in range(0, int(N / 2)):
|
||
|
root = math.factorial(2 * (order + 1)) / math.factorial(order + 1) ** 2
|
||
|
index = order * 2 + 2
|
||
|
orders[order][N][k] = incoherent_bands[-2 - order][index] / root
|
||
|
|
||
|
print(incoherent_bands)
|
||
|
print(precomputed_incoherent_bands)
|
||
|
print(total_bands)
|
||
|
print(precomputed_total_bands)
|
||
|
# print(total_bands)
|
||
|
# print(distance, hamming_distance(x_p, x_q), y_p, y_q)
|
||
|
# for i in range(0, len(orders)):
|
||
|
# print(orders[i])
|
||
|
# # print(pyramids[i])
|
||
|
# print('========================================')
|
||
|
# print(incoherent_distances)
|
||
|
# print(bands)
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|