#! /usr/bin/env python
#
#   >->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->
#    hopfield.py v0.01, Heiko Stamer <stamer@informatik.uni-leipzig.de>
#                       http://stinfwww.informatik.uni-leipzig.de/~mai97ixb
#
#   [1] Ben Kroese, Patrick van der Smagt: "An introduction to Neural Networks"
#
#   >->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->
#
# Copyright (C) 2001 - until_the_end_of_the_world  <Heiko Stamer>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

TEST_VECTORS = [\
[ -1 , -1 , -1 , -1 ] , [ -1 , -1 , -1 , 1 ] ,\
[ -1 , -1 , 1 , -1 ] , [ -1 , -1 , 1 , 1 ] ,\
[ -1 , 1 , -1 , -1 ] , [ -1 , 1 , -1 , 1 ] ,\
[ -1 , 1 , 1 , -1 ] , [ -1 , 1 , 1 , 1 ] ,\
[ 1 , -1 , -1 , -1 ] , [ 1 , -1 , -1 , 1 ] ,\
[ 1 , -1 , 1 , -1 ] , [ 1 , -1 , 1 , 1 ] ,\
[ 1 , 1 , -1 , -1 ] , [ 1 , 1 , -1 , 1 ] ,\
[ 1 , 1 , 1 , -1 ] , [ 1 , 1 , 1 , 1 ] ,\
]

MEMORY = [ [ -1 , 1 , 1 , 1 ] , [ 1 , -1 , 1 , 1 ] , [ 1 , 1 , -1, 1 ] ]

class HopfieldNeuron:
	"input/output Neuron"
	
	# class constructor
	def __init__(self, THRESHOLD, N, I):
		self.threshold = THRESHOLD
		
		# create connections to other neurons
		self.weights = []
		for j in range(0, N):
			self.weights.append(0.0)
		
		# initalize weights upon MEMORY
		for n in range(0, len(MEMORY)):
			for j in range(0, len(self.weights)):
				# Hebb Rule (5.6) from [1] for storing patterns in network
				if (j == I):
					self.weights[j] = 0.0
				else:
					self.weights[j] = self.weights[j] + MEMORY[n][I] * MEMORY[n][j]		

# another Hebb Learning Rule (6.13) from "VO Neuroinformatik" (Prof. R. Der)
# self.weights[j] = self.weights[j] + (1.0 / N) * MEMORY[n][I] * MEMORY[n][j]
			
	# class representation
	def __repr__(self):
		return "Hopfield Neuron: threshold = %s, weights = %s"\
		% (self.threshold, self.weights)
		
	def find(self, PATTERN, I):
		sum = 0
		for j in range(0, len(self.weights)):
			if (j != I):
				sum = sum + self.weights[j] * PATTERN[j]
		# update function (5.2) from [1]
		if (sum != self.threshold):
			if (sum < self.threshold):
				out = -1
			else:
				out = +1
			# check for stability
			if (out != PATTERN[I]):
				PATTERN[I] = out
				return 1
			else:
				return 0
		return 0
		
	def store(self, PATTERN, I):
		for j in range(0, len(self.weights)):
			# Hebb Rule (5.6) from [1] for storing patterns in network
			if (j == I):
				self.weights[j] = 0.0
			else:
				self.weights[j] = self.weights[j] + PATTERN[I] * PATTERN[j]		

# another Hebb Learning Rule (6.13) from "VO Neuroinformatik" (Prof. R. Der)
# self.weights[j] = self.weights[j] + (1.0 / N) * MEMORY[n][I] * MEMORY[n][j]
			
class HopfieldNetwork:
	"Hopfield Neuronal Network"
	
	# class constructor
	def __init__(self, N):
		self.NEURONS = []
		for i in range(0, N):
			# initalize i-th of N neurons with threshold = 0
			n = HopfieldNeuron(0, N, i)
			print "%sth %s" % (i + 1, n)
			self.NEURONS.append(n)
		# search for fixpoints
		fix_points = []
		for test_vector in TEST_VECTORS:
			tv = test_vector[:]
			self.find(tv)
			if (tv not in fix_points):
				fix_points.append(tv)
		print "FIX POINTS: %s" % fix_points

	def find(self, PATTERN):
		not_stable = 1
		while (not_stable > 0):
			not_stable = 0
			for i in range(0, len(self.NEURONS)):
				not_stable = not_stable + self.NEURONS[i].find(PATTERN, i)

	def store(self, PATTERN):
		for i in range(0, len(self.NEURONS)):
				self.NEURONS[i].store(PATTERN, i)

################################################################################

print "MEMORY: %s" % MEMORY

net = HopfieldNetwork(4)
pattern1 = [ 1 , 1 , 1 , -1 ]
pattern2 = [ 1 , 1 , 1 , 1 ]

print "INPUT: %s" % pattern1
net.find(pattern1)
print "OUTPUT: %s" % pattern1,
if (pattern1 in MEMORY):
	print "Pattern found in MEMORY"
else:
	print "Pattern not found in MEMORY"

print "INPUT: %s" % pattern2
net.find(pattern2)
print "OUTPUT: %s" % pattern2,
if (pattern2 in MEMORY):
	print "Pattern found in MEMORY"
else:
	print "Pattern not found in MEMORY"

pattern1 = [ 1 , 1 , 1 , -1 ]
pattern2 = [ 1 , 1 , 1 , 1 ]
print "STORING %s" % pattern1
MEMORY.append(pattern1)
net = HopfieldNetwork(4)

print "INPUT: %s" % pattern1
net.find(pattern1)
print "OUTPUT: %s" % pattern1,
if (pattern1 in MEMORY):
	print "Pattern found in MEMORY"
else:
	print "Pattern not found in MEMORY"

print "INPUT: %s" % pattern2
net.find(pattern2)
print "OUTPUT: %s" % pattern2,
if (pattern2 in MEMORY):
	print "Pattern found in MEMORY"
else:
	print "Pattern not found in MEMORY"

pattern1 = [ 1 , 1 , 1 , -1 ]
pattern2 = [ 1 , 1 , 1 , 1 ]
print "REMOVING %s" % pattern1
MEMORY.remove(MEMORY[-1])
print "STORING %s" % pattern2
MEMORY.append(pattern2)
net = HopfieldNetwork(4)

print "INPUT: %s" % pattern1
net.find(pattern1)
print "OUTPUT: %s" % pattern1,
if (pattern1 in MEMORY):
	print "Pattern found in MEMORY"
else:
	print "Pattern not found in MEMORY"

print "INPUT: %s" % pattern2
net.find(pattern2)
print "OUTPUT: %s" % pattern2,
if (pattern2 in MEMORY):
	print "Pattern found in MEMORY"
else:
	print "Pattern not found in MEMORY"
	
