Merge branch 'feature-gui' into develop

This commit is contained in:
LSaldyt
2017-10-26 12:55:17 -07:00
15 changed files with 522 additions and 28 deletions

View File

@ -22,7 +22,6 @@ def codelet(name):
return f
return wrap
# some methods common to the codelets
def __showWhichStringObjectIsFrom(structure):
if not structure:

View File

@ -3,6 +3,7 @@ from .randomness import Randomness
from .slipnet import Slipnet
from .temperature import Temperature
from .workspace import Workspace
from .gui import GUI
class Reporter(object):
"""Do-nothing base class for defining new reporter types"""
@ -23,30 +24,56 @@ class Reporter(object):
class Copycat(object):
def __init__(self, rng_seed=None, reporter=None):
def __init__(self, rng_seed=None, reporter=None, showgui=True):
self.coderack = Coderack(self)
self.random = Randomness(rng_seed)
self.slipnet = Slipnet()
self.temperature = Temperature() # TODO: use entropy
self.workspace = Workspace(self)
self.reporter = reporter or Reporter()
self.showgui = showgui
self.gui = GUI('Copycat')
self.lastUpdate = float('-inf')
def mainLoop(self, lastUpdate):
def step(self):
if (not self.showgui) or (self.showgui and (not self.gui.app.primary.control.paused or self.gui.app.primary.control.has_step())):
self.coderack.chooseAndRunCodelet()
self.reporter.report_coderack(self.coderack)
self.reporter.report_temperature(self.temperature)
self.reporter.report_workspace(self.workspace)
if (self.showgui):
self.gui.update(self)
def update_workspace(self, currentTime):
self.workspace.updateEverything()
self.coderack.updateCodelets()
self.slipnet.update(self.random)
self.temperature.update(self.workspace.getUpdatedTemperature())
self.lastUpdate = currentTime
self.reporter.report_slipnet(self.slipnet)
def check_reset(self):
if self.gui.app.primary.control.go:
initial, modified, target = self.gui.app.primary.control.get_vars()
self.reset_with_strings(initial, modified, target)
return True
else:
return False
def reset_with_strings(self, initial, modified, target):
self.workspace.resetWithStrings(initial, modified, target)
self.gui.app.reset_with_strings(initial, modified, target)
def mainLoop(self):
currentTime = self.coderack.codeletsRun
self.temperature.tryUnclamp(currentTime) # TODO: use entropy
# Every 15 codelets, we update the workspace.
if currentTime >= lastUpdate + 15:
self.workspace.updateEverything()
self.coderack.updateCodelets()
self.slipnet.update(self.random)
self.temperature.update(self.workspace.getUpdatedTemperature()) # TODO: use entropy
lastUpdate = currentTime
self.reporter.report_slipnet(self.slipnet)
self.coderack.chooseAndRunCodelet()
self.reporter.report_coderack(self.coderack)
self.reporter.report_temperature(self.temperature)
self.reporter.report_workspace(self.workspace)
return lastUpdate
if currentTime >= self.lastUpdate + 15:
self.update_workspace(currentTime)
self.step()
if self.showgui:
self.gui.refresh()
def runTrial(self):
"""Run a trial of the copycat algorithm"""
@ -54,9 +81,8 @@ class Copycat(object):
self.slipnet.reset()
self.temperature.reset() # TODO: use entropy
self.workspace.reset()
lastUpdate = float('-inf')
while self.workspace.finalAnswer is None:
lastUpdate = self.mainLoop(lastUpdate)
self.mainLoop()
answer = {
'answer': self.workspace.finalAnswer,
'temp': self.temperature.last_unclamped_value, # TODO: use entropy
@ -65,22 +91,43 @@ class Copycat(object):
self.reporter.report_answer(answer)
return answer
def run(self, initial, modified, target, iterations):
self.workspace.resetWithStrings(initial, modified, target)
def runGUI(self):
while not self.check_reset():
self.gui.update(self)
self.gui.refresh()
answers = {}
while True:
if self.check_reset():
answers = {}
answer = self.runTrial()
if self.showgui:
self.gui.app.log('Answered: {}'.format(answer['answer']))
d = answers.setdefault(answer['answer'], {
'count': 0,
'sumtemp': 0,
'sumtime': 0
})
d['count'] += 1
d['sumtemp'] += answer['temp']
d['sumtime'] += answer['time']
if self.showgui:
self.gui.add_answers(answers)
self.temperature.useAdj('original')
#self.temperature.useAdj('entropy')
#self.temperature.useAdj('inverse') # 100 weight
#self.temperature.useAdj('fifty_converge')
#self.temperature.useAdj('soft')
#self.temperature.useAdj('weighted_soft')
#self.temperature.useAdj('alt_fifty')
#self.temperature.useAdj('average_alt')
for answer, d in answers.items():
d['avgtemp'] = d.pop('sumtemp') / d['count']
d['avgtime'] = d.pop('sumtime') / d['count']
def run(self, initial, modified, target, iterations):
self.reset_with_strings(initial, modified, target)
self.temperature.useAdj('best')
answers = {}
for i in range(iterations):
if self.check_reset():
answers = {}
answer = self.runTrial()
if self.showgui:
self.gui.app.log('Answered: {}'.format(answer['answer']))
d = answers.setdefault(answer['answer'], {
'count': 0,
'sumtemp': 0, # TODO: use entropy
@ -89,6 +136,8 @@ class Copycat(object):
d['count'] += 1
d['sumtemp'] += answer['temp'] # TODO: use entropy
d['sumtime'] += answer['time']
if self.showgui:
self.gui.add_answers(answers)
for answer, d in answers.items():
d['avgtemp'] = d.pop('sumtemp') / d['count']
@ -96,6 +145,7 @@ class Copycat(object):
return answers
def run_forever(self, initial, modified, target):
self.workspace.resetWithStrings(initial, modified, target)
self.reset_with_strings(initial, modified, target)
while True:
self.check_reset()
self.runTrial()

1
copycat/gui/__init__.py Normal file
View File

@ -0,0 +1 @@
from .gui import GUI

56
copycat/gui/control.py Normal file
View File

@ -0,0 +1,56 @@
import tkinter as tk
import tkinter.ttk as ttk
from .gridframe import GridFrame
from .entry import Entry
class Control(GridFrame):
def __init__(self, parent, *args, **kwargs):
GridFrame.__init__(self, parent, *args, **kwargs)
self.paused = True
self.steps = 0
self.go = False
self.playbutton = ttk.Button(self, text='Play', command=lambda : self.toggle())
self.add(self.playbutton, 0, 0)
self.stepbutton = ttk.Button(self, text='Step', command=lambda : self.step())
self.add(self.stepbutton, 1, 0)
self.entry = Entry(self)
self.add(self.entry, 0, 1, xspan=2)
self.gobutton = ttk.Button(self, text='Go', command=lambda : self.set_go())
self.add(self.gobutton, 0, 2, xspan=2)
def play(self):
self.paused = False
self.playbutton['text'] = 'Pause'
def pause(self):
self.paused = True
self.playbutton['text'] = 'Play'
def toggle(self):
if self.paused:
self.play()
else:
self.pause()
def step(self):
self.steps += 1
def has_step(self):
if self.steps > 0:
self.steps -= 1
return True
else:
return False
def set_go(self):
self.go = True
self.play()
def get_vars(self):
return self.entry.a.get(), self.entry.b.get(), self.entry.c.get()

27
copycat/gui/entry.py Normal file
View File

@ -0,0 +1,27 @@
import tkinter as tk
import tkinter.ttk as ttk
from .gridframe import GridFrame
class Entry(GridFrame):
def __init__(self, parent, *args, **kwargs):
GridFrame.__init__(self, parent, *args, **kwargs)
self.aLabel = ttk.Label(self, text='Initial:')
self.a = ttk.Entry(self, style='EntryStyle.TEntry')
self.add(self.aLabel, 0, 0)
self.add(self.a, 0, 1)
self.bLabel = ttk.Label(self, text='Final:')
self.b = ttk.Entry(self, style='EntryStyle.TEntry')
self.add(self.bLabel, 1, 0)
self.add(self.b, 1, 1)
self.cLabel = ttk.Label(self, text='Next:')
self.c = ttk.Entry(self, style='EntryStyle.TEntry')
self.add(self.cLabel, 2, 0)
self.add(self.c, 2, 1)
GridFrame.configure(self)

11
copycat/gui/gridframe.py Normal file
View File

@ -0,0 +1,11 @@
import tkinter as tk
import tkinter.ttk as ttk
class GridFrame(tk.Frame):
def __init__(self, parent, *args, **kwargs):
ttk.Frame.__init__(self, parent, *args, **kwargs)
def add(self, element, x, y, xspan=1, yspan=1):
element.grid(column=x, row=y, columnspan=xspan, rowspan=yspan, sticky=tk.N+tk.E+tk.S+tk.W)
tk.Grid.rowconfigure(self, x, weight=1)
tk.Grid.columnconfigure(self, y, weight=1)

98
copycat/gui/gui.py Executable file
View File

@ -0,0 +1,98 @@
import sys
import time
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import scrolledtext
from tkinter import filedialog
import matplotlib.pyplot as plt
from .status import Status, StatusFrame
from .status import Plot
from .gridframe import GridFrame
from .primary import Primary
from .list import List
from .style import configure_style
from .plot import plot_imbedded
plt.style.use('dark_background')
class MainApplication(GridFrame):
def __init__(self, parent, *args, **kwargs):
GridFrame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.primary = Primary(self, *args, **kwargs)
self.add(self.primary, 0, 0, xspan=2)
self.create_widgets()
GridFrame.configure(self)
#self.messages = []
#def log(self, message):
# self.messages.append(message)
def create_widgets(self):
self.slipList = List(self, 10)
self.add(self.slipList, 0, 1)
self.codeletList = List(self, 10)
self.add(self.codeletList, 1, 1)
self.objectList = List(self, 10)
self.add(self.objectList, 2, 1)
#self.logBox = List(self, 10)
#self.add(self.logBox, 1, 0)
self.graph2 = Plot(self, 'Answer Distribution')
self.add(self.graph2, 2, 0)
def update(self, copycat):
self.primary.update(copycat)
slipnodes = copycat.slipnet.slipnodes
codelets = copycat.coderack.codelets
objects = copycat.workspace.objects
self.slipList.update(slipnodes, key=lambda s:s.activation,
formatter=lambda s : '{}: {}'.format(s.name, round(s.activation, 2)))
self.codeletList.update(codelets, key=lambda c:c.urgency, formatter= lambda s : '{}: {}'.format(s.name, round(s.urgency, 2)))
self.objectList.update(objects, formatter=lambda s : '{}'.format(str(s.descriptions)))
#self.logBox.update(list(reversed(self.messages))[:10])
def reset_with_strings(self, initial, modified, target):
self.primary.reset_with_strings(initial, modified, target)
class GUI(object):
def __init__(self, title, updateInterval=.1):
self.root = tk.Tk()
self.root.title(title)
tk.Grid.rowconfigure(self.root, 0, weight=1)
tk.Grid.columnconfigure(self.root, 0, weight=1)
self.app = MainApplication(self.root)
self.app.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
configure_style(ttk.Style())
self.lastUpdated = time.time()
self.updateInterval = updateInterval
def add_answers(self, answers):
def modifier(status):
with plt.style.context(('dark_background')):
plot_imbedded(answers, status)
self.app.graph2.status.modifier = modifier
def refresh(self):
self.root.update_idletasks()
self.root.update()
def update(self, copycat):
current = time.time()
self.app.update(copycat)
self.lastUpdated = current

26
copycat/gui/list.py Normal file
View File

@ -0,0 +1,26 @@
import tkinter as tk
import tkinter.ttk as ttk
import time
from .gridframe import GridFrame
class List(GridFrame):
def __init__(self, parent, columns, updateInterval=.1):
GridFrame.__init__(self, parent)
self.text = ttk.Label(self, anchor='w', justify=tk.LEFT, width=30)
self.add(self.text, 0, 0)
self.columns = columns
self.lastUpdated = time.time()
self.updateInterval = updateInterval
def update(self, l, key=None, reverse=False, formatter=lambda s : str(s)):
current = time.time()
if current - self.lastUpdated > self.updateInterval:
l = l[:self.columns]
if key is not None:
l = sorted(l, key=key, reverse=False)
self.text['text'] = '\n'.join(map(formatter, l))

17
copycat/gui/plot.py Normal file
View File

@ -0,0 +1,17 @@
import matplotlib.pyplot as plt; plt.rcdefaults()
import numpy as np
import matplotlib.pyplot as plt
def plot_imbedded(answers, status):
answers = sorted(answers.items(), key=lambda kv : kv[1]['count'])
objects = [t[0] for t in answers]
yvalues = [t[1]['count'] for t in answers]
y_pos = np.arange(len(objects))
status.subplot.clear()
status.subplot.bar(y_pos, yvalues, align='center', alpha=0.5)
status.subplot.set_xticks(y_pos)
status.subplot.set_xticklabels(tuple(objects))
status.subplot.set_ylabel('Count')
status.subplot.set_title('Answers')

72
copycat/gui/primary.py Normal file
View File

@ -0,0 +1,72 @@
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import scrolledtext
from tkinter import filedialog
from .control import Control
from .gridframe import GridFrame
font1Size = 32
font2Size = 16
font1 = ('Helvetica', str(font1Size))
font2 = ('Helvetica', str(font2Size))
style = dict(background='black',
foreground='white',
font=font2)
def create_main_canvas(root, initial, final, new, guess):
padding = 100
canvas = tk.Canvas(root, background='black')
def add_sequences(sequences, x, y):
for sequence in sequences:
x += padding
if sequence is None:
sequence = ''
for char in sequence:
canvas.create_text(x, y, text=char, anchor=tk.NW, font=font1, fill='white')
x += font1Size
return x, y
x = 0
y = padding
add_sequences([initial, final], x, y)
x = 0
y += padding
add_sequences([new, guess], x, y)
#canvas['height'] = str(int(canvas['height']) + padding)
#canvas['width'] = str(int(canvas['width']) + padding)
return canvas
class Primary(GridFrame):
def __init__(self, parent, *args, **kwargs):
GridFrame.__init__(self, parent, *args, **kwargs)
self.initial = ''
self.modified = ''
self.target = ''
self.canvas = create_main_canvas(self, self.initial, self.modified, self.target, '')
self.add(self.canvas, 0, 0, xspan=2)
self.control = Control(self)
self.add(self.control, 0, 2)
GridFrame.configure(self)
def update(self, copycat):
answer = '' if copycat.workspace.rule is None else copycat.workspace.rule.buildTranslatedRule()
#self.canvas = create_main_canvas(self, self.initial, self.modified, self.target, answer)
#self.add(self.canvas, 0, 0, xspan=2)
def reset_with_strings(self, initial, modified, target):
self.initial = initial
self.modified = modified
self.target = target

66
copycat/gui/status.py Normal file
View File

@ -0,0 +1,66 @@
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import tkinter as tk
import tkinter.ttk as ttk
import time
import matplotlib.animation as animation
import matplotlib.pyplot as plt
plt.style.use('dark_background')
from .gridframe import GridFrame
class Plot(GridFrame):
def __init__(self, parent, title):
GridFrame.__init__(self, parent)
self.status = Status()
self.sframe = StatusFrame(self, self.status, title)
self.add(self.sframe, 0, 0, xspan=2)
self.savebutton = ttk.Button(self, text='Save to path:', command=lambda : self.save())
self.add(self.savebutton, 0, 1)
self.pathentry = ttk.Entry(self, style='EntryStyle.TEntry', textvariable='output/dist.png')
self.add(self.pathentry, 1, 1)
def save(self):
path = self.pathentry.get()
if len(path) > 0:
try:
self.status.figure.savefig(path)
except Exception as e:
print(e)
class StatusFrame(ttk.Frame):
def __init__(self, parent, status, title):
ttk.Frame.__init__(self, parent)
self.status = status
self.canvas = FigureCanvasTkAgg(status.figure, self)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
self.animation = animation.FuncAnimation(status.figure, lambda i : status.update_plots(i), interval=1000)
class Status(object):
def __init__(self):
self.figure = Figure(figsize=(5,5), dpi=100)
self.subplot = self.figure.add_subplot(111)
self.x = []
self.y = []
def modifier(status):
with plt.style.context(('dark_background')):
status.subplot.plot(status.x, status.y)
self.modifier = modifier
self.update_plots(0)
def update_plots(self, i):
self.subplot.clear()
self.modifier(self)

33
copycat/gui/style.py Normal file
View File

@ -0,0 +1,33 @@
style_dict = dict(foreground='white',
background='black')
map_options = dict(
foreground=[('disabled', 'black'),
('pressed', 'white'),
('active', 'white')],
background=[('disabled', 'black'),
('pressed', '!focus', 'black'),
('active', 'black')],
highlightcolor=[('focus', 'black'),
('!focus', 'black')])
def configure_style(style):
style.configure('TButton', **style_dict)
style.map('TButton', **map_options)
style.configure('TLabel', **style_dict)
#style.configure('TEntry', **style_dict)
#style.map('TEntry', **map_options)
# A hack to change entry style
style.element_create("plain.field", "from", "clam")
style.layout("EntryStyle.TEntry",
[('Entry.plain.field', {'children': [(
'Entry.background', {'children': [(
'Entry.padding', {'children': [(
'Entry.textarea', {'sticky': 'nswe'})],
'sticky': 'nswe'})], 'sticky': 'nswe'})],
'border':'2', 'sticky': 'nswe'})])
style.configure("EntryStyle.TEntry",
background="black",
foreground="white",
fieldbackground="black")

4
copycat/sampleText.txt Normal file
View File

@ -0,0 +1,4 @@
1,2
3,4
7,7
100,100

View File

@ -23,6 +23,16 @@ class Workspace(object):
self.intraStringUnhappiness = 0.0
self.interStringUnhappiness = 0.0
# LSaldyt: default initializations for GUI entry
self.targetString = ''
self.initialString = ''
self.modifiedString = ''
self.finalAnswer = None
self.changedObject = None
self.objects = []
self.structures = []
self.rule = None
def __repr__(self):
return '<Workspace trying %s:%s::%s:?>' % (
self.initialString, self.modifiedString, self.targetString,

24
gui.py Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
import argparse
import logging
from copycat import Copycat, Reporter
class SimpleReporter(Reporter):
def report_answer(self, answer):
print('Answered %s (time %d, final temperature %.1f)' % (
answer['answer'], answer['time'], answer['temp'],
))
def main():
logging.basicConfig(level=logging.INFO, format='%(message)s', filename='./output/copycat.log', filemode='w')
parser = argparse.ArgumentParser()
parser.add_argument('--seed', type=int, default=None, help='Provide a deterministic seed for the RNG.')
options = parser.parse_args()
copycat = Copycat(reporter=SimpleReporter(), rng_seed=options.seed)
copycat.runGUI()
if __name__ == '__main__':
main()