#!/usr/bin/env python
"""
A python module with logging to animate a reversible random walk.

Metadata:

  - Author:    W.S.Kendall
  - Copyright: opensource (c) W.S.Kendall 2008
  - Date:      2008-07-04
  - URL:       http://www.wilfridkendall.co.uk
  - Generator: '/home/wilfrid/lib/python/generate.py' version 1.18.


Command-line options::

  usage: %prog [options] arguments
    --logging=LOGGING:	   logging verbosity level.

Notes:
 - values for 'logging' option:
 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
 (decreasing level of verbosity), with 'WARNING' as default.

"""
__RCSID__ = '$Revision: 1.1 $ $Date: 2008/07/05 10:33:33 $'


# \section{Definitions}
# We need the logging module regardless of whether the #logging# option
# is deployed, since #logging.info# calls are scattered through the text.
import logging

from Tkinter import Checkbutton, DoubleVar, IntVar, LAST, Scale, Tk, \
    VERTICAL, X, Y

from randomwalk import RandomWalk

from display2 import ITALICS, BOLD, Display2

#\section{Classes}
class AnimateRandomWalk(Display2):
	"""
	Presents animation of reversible random walk.
	"""
	def __init__(self, root, x0=0, x1=100, y0=0, y1=10, p=0.5):
		Display2.__init__(self, root, xlo=x0, xhi=x1, ylo=y0, yhi=y1)

		# Buttons for actions.
		self.button(text='SIMULATE', font=BOLD, foreground='darkgreen',\
				    command=self.simulate)
		self.button(text='ANALYZE', font=BOLD, foreground='darkgreen', \
				    command=self.analyze)
		self.button(text='ITERATE', font=BOLD, foreground='darkgreen', \
				    command=self.iterate)
		
		# Scales for adjustments.
		self.prob = DoubleVar()
		self.prob.set(0.5)
		Scale(self.buttonframe, from_=1.0, to=0.0, \
			      resolution=0.1, \
			      orient=VERTICAL, \
			      label='P[+1]', font=BOLD, \
			      variable=self.prob, \
			      command=self.adjust_prob).pack(fill=Y)

		self.batch = IntVar()
		self.batch.set(100)
		Scale(self.buttonframe, from_=1000, to=100, \
			      resolution=100, \
			      orient=VERTICAL, \
			      label='batch', font=BOLD, \
			      variable=self.batch).pack(fill=Y)

		# Buttons for adjustments.
		self.toggle = IntVar()
		self.toggle.set(True)
		self.togglecb = Checkbutton(self.buttonframe, \
						    foreground='black', \
						    text='EQUILIBRIUM', \
						    width = 11,
						    font=BOLD, \
						    variable=self.toggle, \
						    command=self.togglewipe)
		self.togglecb.pack(fill=X)

		self.button(text='WIPE', font=BOLD, foreground='firebrick', \
				    command=self.wipe)


		for yinc in range(y0+1, y1+1):
			self.axis(x0, yinc, x1, fill='lightgray')
		for xinc in range(x0+1, x1+1):
			self.axis(xinc, y0, y1, fill='skyblue', xalign=False)
		
		self.p, self.x0, self.x1, self.y0, self.y1 = p, x0, x1, y0, y1
		
		self.chain, self.occupied, self.rev_trans = None, set(), []

		for i in range(self.y0, self.y1+1):
			self.rev_trans.append([0,0])

		self.iterations = 1
		
	def simulate(self):
		"""
		Run a single animation.
		"""
		if self.chain is not None:
			return
		
		p = self.p
		
		self.canvas.delete('active')
		self.chain = RandomWalk(p=p, x0=self.y0, x1=self.y1)
		if self.toggle.get():
			self.chain.equilibrium_draw()
		else:
			self.chain[:] = [self.chain.rng.choice([self.y0, self.y1])]
		loc = (self.x0, self.chain[-1])
		if loc not in self.occupied:
			self.dot(loc, fill='yellow', width=4, tags='objects')
			self.occupied.add(loc)
		self.dot(loc, fill='red', width=4, tags='active')
		for t in range(self.x0+1, self.x1+1):
			self.chain.update()
			loc = (t, self.chain[-1])
			if loc not in self.occupied:
				self.dot(loc, fill='peachpuff', tags='objects')
				self.occupied.add(loc)
			self.dot(loc, fill='midnightblue', \
					 tags='active')
		self.iterations += 1
	
	def analyze(self):
		"""
		Analyze last animation.
		"""
		if self.chain is None:
			return

		T = len(self.chain)
		self.canvas.delete('transient')
		for t, transition in enumerate(self.chain.reversed_statistics()):
			start, finis = transition[0], transition[1]
			if finis > start:
				self.rev_trans[start][1] += 1
			elif finis < start:
				self.rev_trans[start][0] += 1
			elif start == self.y1:
				self.rev_trans[start][1] += 1
			elif start == self.y0:
				self.rev_trans[start][0] += 1
			self.line(T - t - 1, start, T - t - 2, finis, \
					  fill='red', arrow=LAST, \
					  tag='transient')
		
		def prob_plus(x):
			if sum(x) == 0:
				return '????'
			else:
				return '%4.2f' % (float(x[1]) / sum(x))
		self.canvas.delete('prob')
		for i, txt in enumerate(prob_plus(x) for x in self.rev_trans):
			self.write(txt, -3, i, font=BOLD, tags='prob')
				
		self.chain = None

	def iterate(self):
		m, dm = 10, self.batch.get()
		for i in range(self.iterations, self.iterations + dm):
			self.simulate()
			self.analyze()
			if not i % m:
				self.canvas.delete('record')
				self.write('[%4d]' % i, -2, -0.5, \
						   font=ITALICS, tags='record')
				self.canvas.update_idletasks()
		self.iterations += dm
	
	def togglewipe(self):
		"""
		Wipe all objects and change checkbuttona ppearance.
		"""
		if self.toggle.get():
			self.togglecb['text'] = 'EQUILIBRIUM'
			self.togglecb['foreground'] = 'black'
		else:
			self.togglecb['text'] = '2 PT START '
			self.togglecb['foreground'] = 'red'
		self.wipe()

	def wipe(self):
		"""
		Wipe all objects.
		"""
		self.chain, self.iterations = None, 1
		self.occupied, self.rev_trans = set(), []
		for i in range(self.y0, self.y1+1):
			self.rev_trans.append([0,0])
		
		self.canvas.delete('objects', 'active', 'transient', 'prob', 'record')
		
	def adjust_prob(self, p):
		"""
		Callback if slider is adjusted.
		"""
		self.wipe()
		self.iterations, self.p = 1, float(p)


#\section{Exceptions}
class Error(Exception):
	"""
	Generic exception to be raised by this module.
	"""

# \section{Functions}
def main(opt, arguments):
	"""
	The action of the module as script should be concentrated here!
	"""
	root = Tk()
	root.title('Reversible simple reflecting random walk')
	animation = AnimateRandomWalk(root)
	
	root.mainloop()




# \section{Main script}
if __name__ == '__main__':
	import optionparse
	option, args = optionparse.parse(__doc__, version=__RCSID__)

	if option.logging is not None:
		logging.basicConfig(level=logging.__dict__[option.logging])
		logging.info('Logging level %s' % option.logging)


	# The action of the module as script should be concentrated
	# in the following function, to facilitate profiling.
	main(option, args)


	logging.info('Finishing')
	raise SystemExit


# \newpage \scriptsize
# \begin{multicols}{2}
# \section{History}
# \text{ }
#\\ $Log: animaterandomwalk.pyw,v $
#\\ Revision 1.1  2008/07/05 10:33:33  wskendall
#\\ Initial revision
#\\
#
# \end{multicols}

'$Id: animaterandomwalk.pyw,v 1.1 2008/07/05 10:33:33 wskendall Exp wskendall $'
