probabilities/space_analysis2.py

255 lines
9.0 KiB
Python
Raw Normal View History

2023-01-01 23:45:51 +00:00
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()