"""The experiments behind principles.md: what changes when you edit the code.

Each experiment is one of the code edits a teacher can try on the page.
Run it:  python3 experiments.py
"""
import itertools

from perceptron import LINES_ANYWHERE, LINES_IN_THE_MIDDLE, SEVEN_OR_ONE


def perceptron(examples, threshold=0, change_size=1, learn_threshold=False, only_mistakes=True, max_rounds=200):
    """Train from 0 and return (rounds or None, right, points, threshold)."""
    points = [0] * len(examples[0][0])

    def guess(picture):
        return sum(p for p, square in zip(points, picture) if square) > threshold

    for round_number in range(1, max_rounds + 1):
        mistakes = 0
        for picture, answer in examples:
            wrong = guess(picture) != answer
            mistakes += wrong
            if wrong or not only_mistakes:
                change = change_size if answer else -change_size
                points = [p + change if square else p for p, square in zip(points, picture)]
                if learn_threshold:
                    threshold -= change   # a lower bar after a missed YES, a higher bar after a false YES
        if mistakes == 0:
            return round_number, len(examples), points, threshold
    right = sum(guess(p) == a for p, a in examples)
    return None, right, points, threshold


def report(title, result, total):
    rounds, right, points, threshold = result
    status = f'all right after {rounds} rounds' if rounds else f'never all right ({right}/{total})'
    print(f'  {title}: {status}; threshold {threshold}; points {points}')


ALL_3X3 = [list(bits) for bits in itertools.product([0, 1], repeat=9)]
THREE_OR_MORE = [(p, sum(p) >= 3) for p in ALL_3X3]     # "3 or more black squares?"
EMPTY = [(p, sum(p) == 0) for p in ALL_3X3]             # "is the picture empty?"

print('1. The threshold (total > 0)')
report('3 or more black squares, threshold 0', perceptron(THREE_OR_MORE), 512)
report('3 or more black squares, threshold 2.5', perceptron(THREE_OR_MORE, threshold=2.5), 512)
report('3 or more black squares, it learns its threshold', perceptron(THREE_OR_MORE, learn_threshold=True), 512)
report('empty picture?, threshold 0', perceptron(EMPTY), 512)
report('empty picture?, threshold -0.5', perceptron(EMPTY, threshold=-0.5), 512)
for t in (0, 1, -1, 2.5):
    report(f'lines anywhere, threshold {t}', perceptron(LINES_ANYWHERE, threshold=t), 6)
report('lines anywhere, it learns its threshold', perceptron(LINES_ANYWHERE, learn_threshold=True), 6)

print('2. The size of a change (change = 1)')
for size in (1, 2, 0.5):
    report(f'7 or 1, change {size}', perceptron(SEVEN_OR_ONE, change_size=size), 8)

print('3. Learn only from mistakes (delete "if guess == answer: return")')
for name, examples in [('lines in the middle', LINES_IN_THE_MIDDLE), ('7 or 1', SEVEN_OR_ONE), ('lines anywhere', LINES_ANYWHERE)]:
    report(f'{name}, learn from every example', perceptron(examples, only_mistakes=False, max_rounds=30), len(examples))
    report(f'{name}, learn only from mistakes', perceptron(examples, max_rounds=30), len(examples))
