def quantitative_plot(patterns, bias=None):
    FLIPPED = 30
    x = []
    y = []
    for n in xrange(1, len(patterns) + 1):
        x.append(n)
        considered_patterns = patterns[:n]
        if bias:
            weights = biasedLearn(considered_patterns)
        else:
            weights = utils.learn(considered_patterns)
        recovered = 0
        for p in considered_patterns:
            for trial in xrange(10):
                noisy = utils.flipper(p, FLIPPED)
                for _ in xrange(10 * len(p)):
                    if bias:
                        biasedUpdateOne(weights, noisy, bias)
                    else:
                        utils.updateOne(weights, noisy)
                    if utils.samePattern(noisy, p):
                        break

            if utils.samePattern(noisy, p):
                recovered += 1
        y.append(recovered)

    plt.plot(x, y)
    plt.title("Evolution of capacity with the number of patterns")
    plt.show()
def seq_converge(weights, pattern):
    last_energy = utils.energy(weights, pattern)
    while True:
        utils.updateOne(weights, pattern)
        energy = utils.energy(weights, pattern)
        if energy == last_energy:
            break
        last_energy = energy
def sequential_hopfield(weights,
                        noisy,
                        original=None,
                        num_iter=100,
                        display=100):
    for i in xrange(1, 1 + num_iter):
        utils.updateOne(weights, noisy)
        # Display
        if i % display == 0:
            show_pattern(
                noisy,
                title="Pattern recovered after {} iterations.".format(i))
        if utils.samePattern(noisy, original):
            print "Recovered the expected pattern."
            break
def small_patterns():
    patterns = [x1, x2, x3]
    weights = utils.learn(patterns)

    # Testing that the patterns are "fixpoints"
    for i, pattern in enumerate(patterns):
        updated_pattern = utils.update(weights, pattern)
        if utils.samePattern(pattern, updated_pattern):
            print "* Pattern #{} is a fixpoint, as expected.".format(i + 1)
    print

    # Test if the network will recall stored patterns from distorted versions
    NUM_TRIALS = 100
    for n in xrange(1, 4):
        print "# Recovering from {} flip(s):".format(n)
        for i, pattern in enumerate(patterns):
            success = 0
            for _ in xrange(NUM_TRIALS):
                distorted_pattern = utils.flipper(pattern, n)
                for j in xrange(500):
                    utils.updateOne(weights, distorted_pattern)

                if utils.samePattern(pattern, distorted_pattern):
                    success += 1
            print "  - Pattern #{}: {}/{} recoveries were succesful.".format(
                i + 1, success, NUM_TRIALS)
        print

    # Finding unexpected attractors
    attractors = set()
    for i in xrange(1000):
        pattern = utils.rndPattern(len(patterns[0]))
        for _ in xrange(100):
            utils.updateOne(weights, pattern)
        if not any(np.all(pattern == p) for p in patterns):
            attractors.add(tuple(pattern.tolist()))

    print "# Unexpected attractors:"
    print '\n'.join(map(str, attractors))
    print

    print "'Small patterns' experiment succesfull!"
def restoring_images():
    patterns = [figs.p1, figs.p2, figs.p3]
    weights = utils.learn(patterns)

    print "! Pattern recovery ( 1 & 2 )"
    for i, (original, noisy) in enumerate([(figs.p1, figs.p11),
                                           (figs.p2, figs.p22)]):
        noisy = np.array(noisy)
        show_pattern(noisy, title="Noisy pattern #{}".format(i + 1))
        for _ in xrange(10000):
            utils.updateOne(weights, noisy)
        show_pattern(noisy, title="Recovered pattern #{}".format(i + 1))
        if utils.samePattern(noisy, original):
            print "  . Correctly recovered pattern {}".format(i + 1)
        else:
            print "  . Couldn't recover pattern {}".format(i + 1)
    print
    #sequential_hopfield(weights, figs.p22, figs.p2, num_iter=3000, display=300)

    # Testing recovering distorted patterns
    pattern = figs.p1
    attractors = set()
    print "! Pattern recovery with varying distortion:"
    for n in xrange(1, len(pattern) - 1, 10):
        print "  * n = {}/{}".format(n, len(pattern))
        for trial in xrange(10):
            noisy = utils.flipper(pattern, n)
            for l in xrange(20000):
                utils.updateOne(weights, noisy)
                if l % 1000 == 0 and utils.samePattern(pattern, noisy):
                    break
            attractors.add(tuple(noisy.tolist()))
            if utils.samePattern(pattern, noisy):
                break

        if utils.samePattern(pattern, noisy):
            print "   . Correctly recovered the pattern (on at least one of the trials)"
        else:
            print "   * Couldn't recover the pattern, stopping."
            break

    # Energy at the different attractors
    x = Counter()
    for attr in attractors:
        energy = utils.energy(weights, np.array(attr))
        x[energy] += 1
    plt.plot(x.keys(), x.values(), 'b.')
    plt.title("Energy at different attractors")
    plt.show()

    # Studying the change of energy at each iteration
    noisy = utils.flipper(figs.p1, 40)
    iterations = 5000
    iterations = range(iterations)
    energies = []
    for iteration in iterations:
        energies.append(utils.energy(weights, noisy))
        utils.updateOne(weights, noisy)
    plt.plot(iterations, energies, '-b')
    plt.title("Evolution of the energy at each iteration")
    plt.show()
def capacity_benchmarks(patterns,
                        force_recovery=False,
                        updates=200,
                        ntrials=10,
                        bias=[0],
                        plot=False):
    if force_recovery:
        print "! Capacity benchmarks: pattern_length={} updates={}, attempts={}".format(
            len(patterns[0]),
            len(patterns[0]) * 10, ntrials)
    else:
        print "! Capacity benchmarks: pattern_length={}".format(
            len(patterns[0]))

    for b in bias:
        if b != 0:
            print "=> BENCHMARKS: bias={}".format(b)
        # Increasing pattern memory
        for i in range(1, len(patterns) + 1):
            recovery_failure = [0] * i
            if b == 0:
                weights = utils.learn(patterns[:i])
            else:
                weights = biasedLearn(patterns[:i])
            nmin = len(patterns[0]) + 1
            pmin = None

            # Applying benchmark on each pattern stored
            for p in xrange(i):
                pattern = patterns[p]
                recovered = False

                # Increasing pattern noise
                for n in range(1, len(patterns[0]) + 1):
                    if not force_recovery:
                        # Random noise
                        noisy_pattern = utils.flipper(pattern, n)
                        # Pattern recovery
                        noisy_pattern = utils.update(weights, noisy_pattern)

                        if not utils.samePattern(pattern, noisy_pattern):
                            recovery_failure[p] = n
                            break

                    else:
                        recovered = False

                        # Multiple attemps if failure
                        for t in xrange(ntrials):
                            # Random noise
                            noisy_pattern = utils.flipper(pattern, n)

                            # Pattern recovery
                            for j in xrange(len(patterns[0]) * 10):
                                if b == 0:
                                    utils.updateOne(weights, noisy_pattern)
                                else:
                                    biasedUpdateOne(weights, noisy_pattern, b)

                            if utils.samePattern(pattern, noisy_pattern):
                                recovered = True
                                break
                            else:
                                if n < nmin:
                                    nmin = n
                                    pmin = p + 1
                                recovery_failure[p] = n

                        if not recovered:
                            break

            if force_recovery:
                print(
                    "{} stored - All patterns recovered until {} (p{} failed) - Last failure at {} by p{}\n"
                    + "First attempt failed by p{} at {}\nDetails: {}").format(
                        i, min(recovery_failure),
                        recovery_failure.index(min(recovery_failure)),
                        max(recovery_failure),
                        recovery_failure.index(max(recovery_failure)), nmin,
                        pmin, recovery_failure)
            else:
                print "{} stored - All patterns recovered until {} (p{} failed) - Last failure at {} by p{}\nDetails: {}".format(
                    i, min(recovery_failure),
                    recovery_failure.index(min(recovery_failure)),
                    max(recovery_failure),
                    recovery_failure.index(max(recovery_failure)),
                    recovery_failure)