219 lines
7.5 KiB
Python
219 lines
7.5 KiB
Python
|
import hashlib
|
||
|
import math
|
||
|
import numpy as np
|
||
|
import random
|
||
|
|
||
|
def count_one_bits(n):
|
||
|
return bin(n).count("1")
|
||
|
|
||
|
def xor_n(n):
|
||
|
return count_one_bits(n) % 2
|
||
|
|
||
|
def sha_n(n):
|
||
|
m = hashlib.sha256()
|
||
|
m.update(str(n).encode("utf-8"))
|
||
|
result = m.digest()
|
||
|
return result[0] & 0b1
|
||
|
|
||
|
def xor_by_index(knowns, index, reverse=False):
|
||
|
mask = 1 << index
|
||
|
knowns = knowns[:]
|
||
|
for i in range(len(knowns)):
|
||
|
known = knowns[i]
|
||
|
if known[0] & mask or (not (known[0] & mask) and reverse):
|
||
|
knowns[i] = (known[0], known[1] ^ 1)
|
||
|
return knowns
|
||
|
|
||
|
def remove_bit(i, n):
|
||
|
return (i & ((1 << n) - 1)) | ((i & ~((1 << (n + 1)) - 1)) >> 1)
|
||
|
|
||
|
def split_at(knowns, N, i):
|
||
|
mask = 1 << i
|
||
|
left = [(remove_bit(j, i), value) for (j, value) in knowns if (j & mask) == 0]
|
||
|
right = [(remove_bit(j, i), value) for (j, value) in knowns if not (j & mask) == 0]
|
||
|
return (left, right)
|
||
|
|
||
|
def factor_at(knowns, N, i, identity_value=1):
|
||
|
mask = 1 << i
|
||
|
left = [(j, value) for (j, value) in knowns if value == identity_value or (j & mask) == 0]
|
||
|
right = [(j, value) for (j, value) in knowns if value == identity_value or not (j & mask) == 0]
|
||
|
return (left, right)
|
||
|
|
||
|
def compute_coherence(pair, N):
|
||
|
(left, right) = pair
|
||
|
left_coherence = compute_split_knowns_r(left, N)
|
||
|
right_coherence = compute_split_knowns_r(right, N)
|
||
|
ratio = min(len(left), len(right)) / max(len(left), len(right))
|
||
|
# evenness = min(left_coherence, right_coherence) / max(left_coherence, right_coherence) if left_coherence > 0 and right_coherence > 0 else 1.0
|
||
|
# evenness = left_coherence - right_coherence
|
||
|
evenness = (1.0 - ((1.0 - left_coherence) - (1.0 - right_coherence)) ** 2)
|
||
|
# return 0.75 * min(left_coherence, right_coherence) + 0.25 * evenness ** 2
|
||
|
# return 0.8 * min(left_coherence, right_coherence) + 0.2 * evenness ** 2
|
||
|
# coherence = left_coherence if left_depth > right_depth else right_coherence if right_depth > left_depth else (left_coherence + right_coherence) / 2.0
|
||
|
# depth = max(left_depth, right_depth)
|
||
|
# return (depth, 0.9 * coherence + 0.1 * (1.0 - (evenness ** 2)))
|
||
|
# return 0.8 * min(left_coherence, right_coherence) + 0.2 * (1.0 - (evenness ** 2))
|
||
|
# return 0.75 * min(left_coherence, right_coherence) + 0.25 * (evenness ** 2)
|
||
|
# return ((left_coherence * len(left) + right_coherence * len(right)) / (len(left) +len(right))) * min(left_coherence, right_coherence) * evenness
|
||
|
# return min(left_coherence, right_coherence) * (evenness ** 2)
|
||
|
coherence = ((len(left) / (len(left) + len(right))) * left_coherence + (len(right) / (len(left) + len(right))) * right_coherence)
|
||
|
return min(left_coherence, right_coherence) * (evenness ** 2)
|
||
|
|
||
|
def compute_split_knowns_r(knowns, N):
|
||
|
# if len(knowns) == 0:
|
||
|
# return 1.0
|
||
|
|
||
|
# hist = np.zeros(N)
|
||
|
# for i in range(0, N):
|
||
|
# mask = 1 << i
|
||
|
# for (j, value) in knowns:
|
||
|
# if j & mask == 0:
|
||
|
# hist[i] += 1
|
||
|
|
||
|
# constant_bits = [i for i in range(0, N) if hist[i] == 0 or hist[i] == len(knowns)]
|
||
|
# if len(constant_bits) > 0:
|
||
|
# constant_bits.reverse()
|
||
|
# for n in constant_bits:
|
||
|
# knowns = [(remove_bit(j, n), value) for (j, value) in knowns]
|
||
|
# return compute_split_knowns_r(knowns, N - len(constant_bits), depth)
|
||
|
|
||
|
if len(knowns) == 1:
|
||
|
return 1.0
|
||
|
if len(knowns) == 2:
|
||
|
if knowns[0][1] == knowns[1][1]:
|
||
|
return 1.0
|
||
|
else:
|
||
|
return 0.0
|
||
|
|
||
|
sum = 0
|
||
|
denominator = 0
|
||
|
for i in range(0, N):
|
||
|
(left, right) = split_at(knowns, N, i)
|
||
|
weight = min(len(left), len(right)) / max(len(left), len(right))
|
||
|
# weight = 1.0 - (abs(len(left) - len(right)) / (len(left) + len(right)))
|
||
|
if weight == 0:
|
||
|
continue
|
||
|
partial = compute_coherence((left, right), N - 1)
|
||
|
sum += weight * partial
|
||
|
denominator += weight
|
||
|
return sum / denominator
|
||
|
|
||
|
def invert(knowns):
|
||
|
inverted_knowns = []
|
||
|
for (i, value) in knowns:
|
||
|
inverted_knowns.append((i, 1 - value))
|
||
|
return inverted_knowns
|
||
|
|
||
|
def reduce(knowns, N):
|
||
|
flips = []
|
||
|
best_coherence = compute_split_knowns_r(knowns, N)
|
||
|
print(best_coherence)
|
||
|
print(knowns)
|
||
|
print()
|
||
|
while best_coherence < 1.0:
|
||
|
best_index = -1
|
||
|
best_reverse = False
|
||
|
# best_coherence = 0
|
||
|
for i in range(0, N):
|
||
|
for reverse in [False, True]:
|
||
|
mutated_knowns = xor_by_index(knowns, i, reverse)
|
||
|
# coherence = compute_coherence(mutated_knowns, N)
|
||
|
coherence = compute_split_knowns_r(mutated_knowns, N)
|
||
|
print(i, reverse, coherence)
|
||
|
if coherence > best_coherence:
|
||
|
best_coherence = coherence
|
||
|
best_index = i
|
||
|
best_reverse = reverse
|
||
|
if best_index < 0:
|
||
|
break
|
||
|
knowns = xor_by_index(knowns, best_index, best_reverse)
|
||
|
flips.append((best_index, best_reverse))
|
||
|
print()
|
||
|
print(best_index, best_reverse, best_coherence)
|
||
|
print(knowns)
|
||
|
print()
|
||
|
return (knowns, best_coherence, flips)
|
||
|
|
||
|
def solve(knowns, N):
|
||
|
(knowns, coherence, flips) = reduce(knowns, N)
|
||
|
if coherence == 1.0:
|
||
|
inverted = knowns[0][1]
|
||
|
return (inverted, flips, None)
|
||
|
|
||
|
raise Exception('Stop')
|
||
|
|
||
|
best_coherence = 0
|
||
|
best_index = -1
|
||
|
best_identity_value = False
|
||
|
print()
|
||
|
for i in range(0, N):
|
||
|
for identity_value in [0, 1]:
|
||
|
coherence = compute_coherence(factor_at(knowns, N, i, identity_value), N)
|
||
|
print(i, identity_value, coherence)
|
||
|
if coherence > best_coherence:
|
||
|
best_coherence = coherence
|
||
|
best_index = i
|
||
|
best_identity_value = identity_value
|
||
|
print()
|
||
|
(left, right) = factor_at(knowns, N, best_index, best_identity_value)
|
||
|
return (0, flips, (best_identity_value, solve(left, N), solve(right, N)))
|
||
|
|
||
|
def evaluate(model, n, value = 0):
|
||
|
(inverted, flips, child) = model
|
||
|
for (i, invert) in flips:
|
||
|
mask = (1 << i)
|
||
|
masked_n = n & mask
|
||
|
if (masked_n > 0 and not invert) or (masked_n == 0 and invert):
|
||
|
value = 1 - value
|
||
|
if not child is None:
|
||
|
(identity, left_child, right_child) = child
|
||
|
left = evaluate(left_child, n, 1 - identity)
|
||
|
right = evaluate(right_child, n, 1 - identity)
|
||
|
if left and right:
|
||
|
value = 1 - value
|
||
|
if identity == 0:
|
||
|
value = 1 - value
|
||
|
if inverted:
|
||
|
value = 1 - value
|
||
|
return value
|
||
|
|
||
|
def main():
|
||
|
N = 8
|
||
|
S = 2 ** N
|
||
|
train_size = 128
|
||
|
test_size = 100
|
||
|
f = xor_n
|
||
|
|
||
|
knowns = [(i, f(i)) for i in [
|
||
|
# 0, 1, 2, 3, 4, 5, 6, 7
|
||
|
# 0, 3, 4, 5, 7
|
||
|
# 3, 5, 6, 10, 12, 14
|
||
|
# 1, 3, 7, 10, 14, 15
|
||
|
# 0, 3, 5, 6, 10, 11, 12
|
||
|
# 0, 3, 5, 6, 10, 11, 12, 24, 30
|
||
|
# 0, 3, 5, 6, 10, 11, 12, 24, 30, 52, 63, 255, 243, 127
|
||
|
# 128, 131, 248, 0, 7, 13, 17, 19
|
||
|
23, 38, 46, 89, 108, 110, 114, 119, 137, 168, 177, 201, 206, 232, 247, 255
|
||
|
]]
|
||
|
|
||
|
# knowns = []
|
||
|
# train_samples = set()
|
||
|
# for i in range(0, train_size):
|
||
|
# k = random.randint(0, S)
|
||
|
# while k in train_samples:
|
||
|
# k = random.randint(0, S)
|
||
|
# knowns.append((k, f(i)))
|
||
|
# train_samples.add(k)
|
||
|
|
||
|
model = solve(knowns, N)
|
||
|
print(model)
|
||
|
# print(model)
|
||
|
correct = 0
|
||
|
for i in range(0, test_size):
|
||
|
k = random.randint(0, S - 1)
|
||
|
if f(k) == evaluate(model, k):
|
||
|
correct += 1
|
||
|
print(str(correct) + "/" + str(test_size))
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|