# Search for all interesting configurations for a three-input neuron.
# Neil Fraser, 2009

# All permutations of three binary digits.
abc = ((False, False, False),
       (False, False, True),
       (False, True,  False),
       (False, True,  True),
       (True,  False, False),
       (True,  False, True),
       (True,  True,  False),
       (True,  True,  True))

# (1, 1, 1, 0.5) is equivalent to but simpler than (2, 2, 2, 1.5)
def compute_score(weight_a, weight_b, weight_c, threshold):
  return abs(weight_a) + abs(weight_b) + abs(weight_c) + abs(threshold)

# Compute all eight outputs for the given neuron configuration.
def compute_table(weight_a, weight_b, weight_c, threshold):
  table = []
  for (digit_a, digit_b, digit_c) in abc:
    sum = 0
    if digit_a:
      sum += weight_a
    if digit_b:
      sum += weight_b
    if digit_c:
      sum += weight_c
    table.append(sum > threshold)
  return table

# Limit the search space to these weights and thresholds.
weights = (3, 2, 1, 0, -1, -2, -3)
thresholds = (4.5, 3.5, 2.5, 1.5, 0.5, -0.5, -1.5, -2.5, -3.5, -4.5)

# Record some statistics.
total_combinations = 0
total_unique = 0

# Hash table of outputs to inputs.
known_outputs = {}

for weight_a in weights:
  for weight_b in weights:
    for weight_c in weights:
      for threshold in thresholds:
        total_combinations += 1
        table1 = compute_table(weight_a, weight_b, weight_c, threshold)
        table2 = compute_table(weight_a, weight_c, weight_b, threshold)
        table3 = compute_table(weight_b, weight_a, weight_c, threshold)
        table4 = compute_table(weight_b, weight_c, weight_a, threshold)
        table5 = compute_table(weight_c, weight_b, weight_a, threshold)
        table6 = compute_table(weight_c, weight_a, weight_b, threshold)
        score = compute_score(weight_a, weight_b, weight_c, threshold)
        for table in (table1, table2, table3, table4, table5, table6):
          if known_outputs.has_key(str(table)):
            (known_a, known_b, known_c, known_threshold) = known_outputs[str(table)]
            if compute_score(known_a, known_b, known_c, known_threshold) > score:
              # The existing entry is the same, but more complex than the
              # current configuration.  Replace it.
              known_outputs[str(table)] = (weight_a, weight_b, weight_c, threshold)
            break
        else:
          # Never seen this output before.  Add it.
          known_outputs[str(table1)] = (weight_a, weight_b, weight_c, threshold)
          total_unique += 1

# Print the unique neuron configurations and their outputs.
# Not in any particular order.
for (known_a, known_b, known_c, known_threshold) in known_outputs.values():
  print "%d, %d, %d, %.1f = %s" % (known_a, known_b, known_c, known_threshold,
      compute_table(known_a, known_b, known_c, known_threshold))

print "Total combinations searched: %d" % total_combinations
print "Total unique outputs: %d" % total_unique
