#(c) Rory Clune 05/16/2011

from numpy import *
from matplotlib import*

class NelderMeadConstrained:
	'''Implementation of Constrained Nelder Mead Algorithm'''			
	def __init__(self, ndim, structure, start_pt, UB = None, LB = None, tol = 1e-15, maxsteps = 1000):
		self.ndim = ndim
		self.function = structure.Objective
		self.constraint = structure.Constraint
		self.tol = tol
		self.maxsteps = maxsteps
		# Apply the bounds, if any are given
		self.UB = UB
		self.LB = LB
		if (UB != None) or (LB != None): 
			self.bounded = True
			start_pt = maximum(minimum(start_pt, self.UB), self.LB)
		else: self.bounded = False
		self.x0 = start_pt		
		self.scale = ones_like(start_pt)
		self.penalty = 0*ones(self.constraint(start_pt).shape[0])
		self.s = 0.001	
		
	def Run(self):				
		if self.bounded: print 'Bounded Nelder-Mead Algorithm is running.. Please wait'  
		else: print 'Unbounded Nelder-Mead Algorithm is running.. Please wait'
		
		#----------------------
		# 0. Data Setup
		#----------------------		
		function = self.function
		constraint = self.constraint
		penalty = self.penalty; s = self.s		
		scale = self.scale
		ndim = self.ndim
		npts = ndim + 1 # a simplex
		nsteps = 0
		
		#----------------------
		# 1. Define the starting simplex and evaluate the function at its vertices
		#----------------------				
		x = zeros((npts, ndim))		
		if self.x0 == None:
			x[0,:] = 0 # Arbitrary starting point
		else:
			x[0,:] = self.x0
		for i in range(1,npts,1):
			x[i,:] = x[i-1,:] + arange(1.,ndim+1,1)*i/100
			
		print x.shape[1]		
		# Make sure all the new points stay within the allowed bounds
		if self.bounded:
			for i in range(1,npts,1):
				x[i,:] = maximum(minimum(x[i,:], self.UB), self.LB)
		f = apply_along_axis(function,1,x*scale)	 
		
		#----------------------
		# 1. (b) Add penalties for violated constraints and increase multipliers
		#----------------------					
		c = apply_along_axis(constraint,1,x*scale)
		f += (c*penalty).sum(axis = 1)		
		penalty += s*(c.sum(axis = 0))		
		
		# Sort the simplex and the vector of function values
		index = argsort(f, axis = 0) # best - middle - worst
		x = x[index,:] 
		f = f[index]
		nsteps += npts - 1
		# set up storage for simplex path plotting
		self.path = copy(x[0,:])
		
		self.obj_history = f[0]
		self.con_history = c[0]
		
		converged = False
		while (converged==False): #Do the following until convergence
			#----------------------			
			# 2. Find the center of the face of the simplex (except the worst point)
			#----------------------		
			x_mean = mean(x[0:(npts-1),:], axis = 0)    
			
			#----------------------			
			# 3. Reflect the worst point across the face
			#----------------------			
			xr = 2*x_mean-x[(npts-1),:]
			if self.bounded:
				xr = maximum(minimum(xr, self.UB), self.LB)
			nsteps += 1
			fxr = function(xr*scale)
			c = constraint(xr*scale)			
			fxr += sum(penalty*c)
			penalty += s*c
			
			#----------------------			
			# 4. Check if the reflected point is the new best, and go further if so 
			#----------------------			     
			if fxr <= f[0]: # is the new point the new best?
				x2r = 3*x_mean-2*x[(npts-1),:] # grow further in that direction
				if self.bounded:
					x2r = maximum(minimum(xr, self.UB), self.LB)				
				fx2r = function(x2r*scale)				
				c = constraint(x2r*scale)			
				fx2r += sum(penalty*c)
				penalty += s*c						
				nsteps += 1
				if fx2r < fxr: # did the growing improve?
					x[(npts-1),:] = x2r # accept the grown point and go to the next iteration
					f[(npts-1)] = fx2r	
					print 'accepted grown point'
				else: # the growing step did not help
					x[(npts-1),:] = xr # accept the original reflected point and go to the next iteration
					f[(npts-1)] = fxr            
					print 'accepted reflected point (it was the best)'
			
			#----------------------			
			# 4. Check if the reflected point is still the worst, and try reflecting & shrinking, just shrinking, and then shrinking everything 
			#----------------------			
			elif fxr >= f[(npts-2)]: 
				xs = 1.5*x_mean - 0.5*x[(npts-1),:] # reflect + shrink
				if self.bounded:
					xs = maximum(minimum(xs, self.UB), self.LB)
				fxs = function(xs*scale)				
				c = constraint(xs*scale)			
				fxs += sum(penalty*c)
				penalty += s*c
				nsteps += 1
				if fxs < f[(npts-2)]: # better than the second worst?
					x[(npts-1),:] = xs # accept and move on
					f[(npts-1)] = fxs
					print 'accepted reflected & shrunk point'
				else: # not better than the second worst
					xs = 0.5*x_mean+0.5*x[(npts-1),:] # just shrink (no reflection)
					if self.bounded:
						xs = maximum(minimum(xs, self.UB), self.LB)						
					fxs = function(xs*scale)				
					c = constraint(xs*scale)			
					fxs += sum(penalty*c)
					penalty += s*c
					nsteps += 1
					if fxs > f[(npts-2)]:
						x[1:npts,:] = 0.5*(x[1:npts,:] + x[0,:]) # shrink everything (except the best, 0) towards the best
						if self.bounded:
							for i in range(1,npts,1):
								x[i,:] = maximum(minimum(x[i,:], self.UB), self.LB)
						f = apply_along_axis(function, 1, x*scale)
						c = apply_along_axis(constraint, 1, x*scale)
						f += (c*penalty).sum(axis = 1)		
						penalty += s*(c.sum(axis = 0))
						print 'shrank everything towards the best'												
					else:						
						x[(npts-1),:] = xs # accept and move on
						f[(npts-1)] = function(xs)
						print 'accepted shrunk only point'                          
			else: # the new reflected point is neither the best nor the worst. keep it
				x[(npts-1),:] = xr
				f[(npts-1)] = fxr
				print 'accepted reflected point (though it was not a new best)'   
			# Sort the simplex and the vector of function values
			index = argsort(f, axis = 0) # best - middle - worst
			x = x[index,:]
			f = f[index]			
			# Record the history of the simplex:			
			self.obj_history = append(self.obj_history, f[0])
			# Check for convergence or maximum iterations
			if (fabs(f[0]- f[(npts-1)]) < self.tol) or (nsteps > self.maxsteps):
				converged = True						
		print '\nconverged at: ' + str(scale*x[0,:]) + ', with a function value of ' + str(f[0]) + '\n' + str(nsteps) + ' points generated' + '\n'
		self.optimum = x[0,:]	



class NelderMead:
	'''Implementation of Nelder Mead Algorithm'''			
	def __init__(self, ndim, structure, start_pt, UB = None, LB = None, tol = 1e-15, maxsteps = 1000):
		self.ndim = ndim
		self.function = structure.Objective
		self.tol = tol
		self.maxsteps = maxsteps
		# Apply the bounds, if any are given
		self.UB = UB
		self.LB = LB
		if (UB != None) or (LB != None): 
			self.bounded = True
			start_pt = maximum(minimum(start_pt, self.UB), self.LB)
		else: self.bounded = False
		self.x0 = start_pt
		self.scale = ones_like(start_pt)
		
	def Run(self):				
		if self.bounded: print 'Bounded Nelder-Mead Algorithm is running.. Please wait'  
		else: print 'Unbounded Nelder-Mead Algorithm is running.. Please wait'
		
		#----------------------
		# 0. Data Setup
		#----------------------		
		function = self.function		
		ndim = self.ndim
		npts = ndim + 1 # a simplex
		nsteps = 0
		scale = self.scale
		#----------------------
		# 1. Define the starting simplex and evaluate the function at its vertices
		#----------------------				
		x = zeros((npts, ndim))		
		if self.x0 == None:
			x[0,:] = 0 # Arbitrary starting point
		else:
			x[0,:] = self.x0
		for i in range(1,npts,1):			
			x[i,:] = x[i-1,:] + arange(1.,ndim+1,1)*i/100
		# Make sure all the new points stay within the allowed bounds
		if self.bounded:
			for i in range(1,npts,1):
				x[i,:] = maximum(minimum(x[i,:], self.UB), self.LB)
		f = apply_along_axis(function,1,x*scale)
		# Sort the simplex and the vector of function values
		index = argsort(f, axis = 0) # best - middle - worst
		x = x[index,:] 
		f = f[index]
		nsteps += npts - 1
		# set up storage for simplex path plotting
		self.path = copy(x[0,:])
		self.obj_history = f[0]
		
		converged = False
		while (converged==False): #Do the following until convergence
			#----------------------			
			# 2. Find the center of the face of the simplex (except the worst point)
			#----------------------		
			x_mean = mean(x[0:(npts-1),:], axis = 0)    
			
			#----------------------			
			# 3. Reflect the worst point across the face
			#----------------------			
			xr = 2*x_mean-x[(npts-1),:]
			if self.bounded:
				xr = maximum(minimum(xr, self.UB), self.LB)
			nsteps += 1
			fxr = function(xr*scale)
			#----------------------			
			# 4. Check if the reflected point is the new best, and go further if so 
			#----------------------			     
			if fxr <= f[0]: # is the new point the new best?
				x2r = 3*x_mean-2*x[(npts-1),:] # grow further in that direction
				if self.bounded:
					x2r = maximum(minimum(xr, self.UB), self.LB)			
				nsteps += 1
				fx2r = function(x2r*scale)
				if fx2r < fxr: # did the growing improve?
					x[(npts-1),:] = x2r # accept the grown point and go to the next iteration
					f[(npts-1)] = fx2r	
#					print 'accepted grown point'
				else: # the growing step did not help
					x[(npts-1),:] = xr # accept the original reflected point and go to the next iteration
					f[(npts-1)] = fxr            
#					print 'accepted reflected point (it was the best)'
			
			#----------------------			
			# 4. Check if the reflected point is still the worst, and try reflecting & shrinking, just shrinking, and then shrinking everything 
			#----------------------			
			elif fxr >= f[(npts-2)]: 
				xs = 1.5*x_mean - 0.5*x[(npts-1),:] # reflect + shrink
				if self.bounded:
					xs = maximum(minimum(xs, self.UB), self.LB)
#					print 'shrunk and reflected point only'
				nsteps += 1
				fxs = function(xs*scale)
				if fxs < f[(npts-2)]: # better than the second worst?
					x[(npts-1),:] = xs # accept and move on
					f[(npts-1)] = fxs
#					print 'accepted reflected & shrunk point'
				else: # not better than the second worst
					xs = 0.5*x_mean+0.5*x[(npts-1),:] # just shrink (no reflection)
					if self.bounded:
						xs = maximum(minimum(xs, self.UB), self.LB)
#						print 'shrunk point'
					nsteps += 1
					fxs = function(xs*scale)				
					if fxs > f[(npts-2)]:
						x[1:npts,:] = 0.5*(x[1:npts,:] + x[0,:]) # shrink everything (except the best, 0) towards the best
						if self.bounded:
							for i in range(1,npts,1):
								x[i,:] = maximum(minimum(x[i,:], self.UB), self.LB)
						f = apply_along_axis(function, 1, x*scale)
#						print 'shrank everything towards the best'
					else:
						x[(npts-1),:] = xs # accept and move on
						f[(npts-1)] = fxs
#						print 'accepted shrunk only point'                          
			else: # the new reflected point is neither the best nor the worst. keep it
				x[(npts-1),:] = xr
				f[(npts-1)] = fxr
#				print 'accepted reflected point (though it was not a new best)'   
			# Sort the simplex and the vector of function values
			index = argsort(f, axis = 0) # best - middle - worst
			x = x[index,:]
			f = f[index]			
			
			# Record the history of the simplex:			
			self.obj_history = append(self.obj_history, f[0])
						
			if (fabs(f[0]- f[(npts-1)]) < self.tol) or (nsteps > self.maxsteps):
				converged = True
		print '\nconverged at: ' + str(scale*x[0,:]) + ', with a function value of ' + str(f[0]) + '\n' + str(nsteps) + ' points generated' + '\n'
		self.optimum = x[0,:]	
