Code refactoring

This commit is contained in:
anwinged 2012-04-24 09:05:52 +00:00
parent d859399249
commit 20e9beaf69
6 changed files with 313 additions and 117 deletions

View File

@ -2,18 +2,40 @@
import wx
import wx.gizmos
import wx.grid
import wx.propgrid as wxpg
import wx.lib.plot as wxplot
ID_TEST = wx.NewId()
ID_ADD_MODEL_ROOT = wx.NewId()
ID_ADD_MODEL_SELECTED = wx.NewId()
ID_DUPLICATE_MODEL = wx.NewId()
ID_DUPLICATE_TREE = wx.NewId()
ID_DELETE_MODEL = wx.NewId()
ID_PROCESS_MODEL = wx.NewId()
ID_SHOW_RESULT = wx.NewId()
ID_SHOW_PLOT = wx.NewId()
class TreeListCtrl(wx.gizmos.TreeListCtrl):
def __iter__(self):
return TreeListCtrlIterator(self)
class TreeListCtrlIterator:
def __init__(self, owner):
self.owner = owner
self.item = self.owner.GetRootItem()
def __iter__(self):
return self
def next(self):
if not self.item.IsOk():
raise StopIteration
item = self.item
self.item = self.owner.GetNext(self.item)
return item
class MyTreeListCtrl(wx.gizmos.TreeListCtrl):
def Refresh(self, erase, rect):
wx.gizmos.TreeListCtrl.Refresh(False, rect)
class MainFrame (wx.Frame):
@ -31,9 +53,7 @@ class MainFrame (wx.Frame):
bSizer4 = wx.BoxSizer(wx.VERTICAL)
self.m_user_models = wx.gizmos.TreeListCtrl(self,
#self.m_user_models = MyTreeListCtrl(self,
#self.m_user_models = wx.TreeCtrl(self,
self.m_user_models = TreeListCtrl(self,
style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT
| wx.TR_EDIT_LABELS | wx.TR_ROW_LINES | wx.TR_MULTIPLE)
self.m_user_models.SetMinSize(wx.Size(-1, 200))
@ -45,8 +65,7 @@ class MainFrame (wx.Frame):
bSizer4.Add(self.m_user_models, 0, wx.ALL|wx.EXPAND, 1)
# WARNING: wxPython code generation isn't supported for this widget yet.
self.m_params = wxpg.PropertyGridManager(self,
style = wxpg.PG_TOOLBAR)
self.m_params = wxpg.PropertyGridManager(self)
self.m_params.AddPage('fp')
bSizer4.Add(self.m_params, 1, wx.EXPAND |wx.ALL, 1)
@ -80,14 +99,18 @@ class MainFrame (wx.Frame):
menubar.Append(menu, '&File')
menu = wx.Menu()
menu.Append(ID_TEST, "&Test\tCtrl+T")
menu.Append(ID_PROCESS_MODEL, 'Process\tCtrl+R')
menu.Append(ID_SHOW_RESULT, 'Show result\tCtrl+S')
menu.Append(ID_SHOW_PLOT, 'Show plot\tCtrl+G')
menu.AppendSeparator()
menu.Append(ID_ADD_MODEL_ROOT, 'Add model to root')
menu.Append(ID_ADD_MODEL_SELECTED, 'Append model to selected')
#menu.AppendSeparator()
menu.Append(ID_DUPLICATE_MODEL, "&Duplicate\tCtrl+D")
menu.Append(ID_DELETE_MODEL, 'Delete')
menu.AppendSeparator()
menu.Append(ID_PROCESS_MODEL, 'Process\tCtrl+R')
menu.Append(ID_DUPLICATE_MODEL, "&Duplicate\tCtrl+D")
menu.Append(ID_DUPLICATE_TREE, "&Duplicate with subitems\tCtrl+Shift+D")
menu.Append(ID_DELETE_MODEL, 'Delete\tCtrl+E')
menu.AppendSeparator()
menu.Append(ID_TEST, "&Test\tCtrl+T")
menubar.Append(menu, '&Model')
menu = wx.Menu()
@ -104,3 +127,28 @@ class MainFrame (wx.Frame):
menu.Append(ID_ADD_MODEL_SELECTED, 'Add model to selected')
self.m_specs.Bind(wx.EVT_CONTEXT_MENU,
lambda x: self.m_specs.PopupMenu(menu))
class ResultFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(600, 400))
sizer = wx.BoxSizer(wx.VERTICAL)
self.scalar = wxpg.PropertyGridManager(self)
self.scalar.AddPage('fp')
self.table = wx.grid.Grid(self)
self.table.SetDefaultCellAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)
sizer.Add(self.scalar, 0, wx.EXPAND |wx.ALL, 1)
sizer.Add(self.table, 1, wx.EXPAND |wx.ALL, 1)
self.SetSizer(sizer)
self.Layout()
self.Centre(wx.BOTH)
class PlotFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(600, 400))
self.plot = wxplot.PlotCanvas(self)

239
opal.py
View File

@ -22,6 +22,25 @@ import os
import threading
import re
class ModelData:
def __init__(self, server, model, parent_data = None):
# если мы создаем новый набор данных из описания модели
if isinstance(model, task.DataDescription):
self.mdef = task.DataDefinition(model, parent_data)
self.jid = server.CreateJob() if model.IsExecutable() else None
# если мы создаем набор данных из другого набора данных
elif isinstance(model, ModelData):
self.mdef = model.mdef.Copy()
self.jid = server.CreateJob() if model.jid != None else None
else:
self.mdef = None
self.jid = None
self.res = None
class ItemError(Exception):
pass
#-----------------------------------------------------------------------------
# Главная форма
#-----------------------------------------------------------------------------
@ -47,21 +66,28 @@ class MainFrame(forms.MainFrame):
self.m_params.Bind(wxpg.EVT_PG_CHANGED,
self.OnParamChanged)
self.Bind(wx.EVT_MENU, self.OnTest, id = forms.ID_TEST)
self.Bind(wx.EVT_MENU, self.OnTest,
id = forms.ID_TEST)
self.Bind(wx.EVT_MENU, self.OnAddModelToRoot,
id = forms.ID_ADD_MODEL_ROOT)
self.Bind(wx.EVT_MENU, self.OnAddModelToSelected,
id = forms.ID_ADD_MODEL_SELECTED)
self.Bind(wx.EVT_MENU, self.OnDuplicate,
id = forms.ID_DUPLICATE_MODEL)
self.Bind(wx.EVT_MENU, self.OnDuplicateTree,
id = forms.ID_DUPLICATE_TREE)
self.Bind(wx.EVT_MENU, self.OnDeleteModel,
id = forms.ID_DELETE_MODEL)
self.Bind(wx.EVT_MENU, self.OnModelProcess,
id = forms.ID_PROCESS_MODEL)
self.Bind(wx.EVT_MENU, self.OnShowResult,
id = forms.ID_SHOW_RESULT)
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.Bind(wx.EVT_IDLE, self.OnIdle)
ov = threading.Thread(target = self.Overseer)
ov.daemon = 1
ov.daemon = True
ov.start()
self.NewProject(model)
@ -72,6 +98,12 @@ class MainFrame(forms.MainFrame):
self.Destroy()
def Overseer(self):
"""
Функция-надсмотрщик, которая периодически проверяет состояние
всех пользовательских моделей, в зависимости от этого изменяет
состояние окружения, выводит информацию, подгружает результаты
выполнения работ и др.
"""
def StateToStr(state):
if state == server.JOB_READY:
@ -90,25 +122,26 @@ class MainFrame(forms.MainFrame):
cycle_count = 0
while True:
wx.MutexGuiEnter()
print 'cycle{:-8}'.format(cycle_count)
#print 'cycle{:-8}'.format(cycle_count)
cycle_count += 1
item = um.GetRootItem()
while item.IsOk():
# просматриваем всю иерархию моделей
for item in um:
data = um.GetPyData(item)
if data:
jid = data[1]
if not data:
continue
jid = data.jid
if jid != None and self.server.IsJobChanged(jid):
state = self.server.GetJobState(jid)
um.SetItemText(item, StateToStr(state[0]), 1)
p = state[1]
p = 'Unknown' if p < 0 else '{:%}'.format(p)
state, percent, comment = self.server.GetJobState(jid)
um.SetItemText(item, StateToStr(state), 1)
p = 'Unknown' if percent < 0 else '{:%}'.format(percent)
um.SetItemText(item, p, 2)
um.SetItemText(item, state[2], 3)
print jid, state
um.SetItemText(item, comment, 3)
print jid, (state, percent, comment)
if state == server.JOB_COMPLETED:
data.res = self.server.GetJobResult(jid)
item = um.GetNext(item)
wx.MutexGuiLeave()
time.sleep(0.2)
time.sleep(0.1)
except Exception, e:
print 'Error in overseer: ', e
@ -118,12 +151,10 @@ class MainFrame(forms.MainFrame):
Возвращает True, если имя уникально, иначе False.
"""
um = self.m_user_models
item = um.GetRootItem()
while item.IsOk():
for item in um:
item_name = um.GetItemText(item)
if item_name == name:
return False
item = um.GetNext(item)
return True
def GenerateName(self, name):
@ -131,7 +162,7 @@ class MainFrame(forms.MainFrame):
На основе переданного имени генерирует новое имя модели таким образом,
чтобы оно осталось уникальным в рамках существующей иерархии моделей.
"""
m = re.match(r'(.+)\s+\d*', name)
m = re.match(r'^(.+)\s+\d*$', name, re.UNICODE)
basename = m.group(1) if m else name
while True:
name = basename + ' ' + str(self.name_id)
@ -172,13 +203,17 @@ class MainFrame(forms.MainFrame):
um = self.m_user_models
item = um.GetRootItem()
defparent = None
for m in ms:
root = None
for i, m in enumerate(ms):
name = self.GenerateName(m.GetTitle())
item = um.AppendItem(item, name)
data = task.DataDefinition(m, defparent)
defparent = data
jid = self.server.CreateJob() if m.IsExecutable() else None
um.SetPyData(item, [data, jid])
if not i:
root = item
data = ModelData(self.server, m, defparent)
defparent = data.mdef
um.SetPyData(item, data)
if root:
um.Expand(root)
def NewProject(self, model):
# 1. загрузить спецификации модели
@ -194,7 +229,7 @@ class MainFrame(forms.MainFrame):
return True # Project(model)
def SelectUserModel(self, model_def, jid):
def SelectUserModel(self, model_def):
def SelectProperty(param_type):
"""
@ -220,23 +255,42 @@ class MainFrame(forms.MainFrame):
msg = model_def.PackParams()
pg = self.m_params
pg.ClearPage(0)
#pg.Append(wxpg.PropertyCategory('Model properties'))
for k, v in model_def.params.iteritems():
p = model_def.DD[k]
title = p.GetTitle() or k
prop = SelectProperty(p.GetType())
pid = pg.Append(prop(title, value = v))
pg.SetPropertyClientData(pid, k)
pg.SetPropertyHelpString(pid, p.GetComment())
for label, value in model_def.params.iteritems():
param = model_def.DD[label]
title = param.GetTitle() or label
prop = SelectProperty(param.GetType())
pid = pg.Append(prop(title, value = value))
pg.SetPropertyClientData(pid, label)
pg.SetPropertyHelpString(pid, param.GetComment())
self.SetStatusText(model_def.PackParams(), 0)
def GetSelectedItem(self, source):
item = source.GetSelection()
if not item.IsOk():
raise ItemError('Invalid item')
return item
def GetSelectedData(self, source):
item = self.GetSelectedItem(source)
data = source.GetPyData(item)
if not data:
raise ItemError('Empty data')
return data
def GetSelectedItemData(self, source):
item = self.GetSelectedItem(source)
data = source.GetPyData(item)
if not data:
raise ItemError('Empty data')
return (item, data)
pd = model_def.PackParams()
self.SetStatusText(pd, 0)
def OnModelActivated(self, event):
item = event.GetItem()
data = self.m_user_models.GetPyData(item)
if data:
self.SelectUserModel(data[0], data[1])
self.SelectUserModel(data.mdef)
def OnParamChanging(self, event):
#value = event.GetValue()
@ -251,69 +305,112 @@ class MainFrame(forms.MainFrame):
return
value = prop.GetValue()
param = prop.GetClientData()
um = self.m_user_models
id = um.GetSelection()
data, jid = um.GetItemPyData(id)
data[param] = value
data = self.GetSelectedData(self.m_user_models)
data.mdef[param] = value
def OnTest(self, event):
um = self.m_user_models
def OnAddModelToRoot(self, event):
item = self.m_specs.GetSelection()
if not item.IsOk():
return
print self.m_specs.GetItemText(item)
model = self.m_specs.GetPyData(item)
model = self.GetSelectedData(self.m_specs)
self.AddModelToRoot(model)
def OnAddModelToSelected(self, event):
"""
Добавляет пользовательскую спецификацию к указанной модели или в уже
существующую иерархию спецификаций.
Добавляет пользовательскую спецификацию к указанной модели
"""
item = self.m_specs.GetSelection()
if not item.IsOk():
return
model = self.m_specs.GetPyData(item)
# получаем модель, которая будет добавлена к пользовательским
model = self.GetSelectedData(self.m_specs)
# получаем пользовательскую модель, к которой хотим присоединить новую
item, data = self.GetSelectedItemData(self.m_user_models)
pmdef = data.mdef
um = self.m_user_models
item = um.GetSelection()
if not item.IsOk():
return
pmdef, _ = um.GetPyData(item)
# если новая модель может быть присоединена...
if pmdef.DD == model.parent:
modeldef = task.DataDefinition(model, pmdef)
name = self.GenerateName(model.GetTitle())
item = um.AppendItem(item, name)
jid = self.server.CreateJob() if model.IsExecutable() else None
um.SetPyData(item, [modeldef, jid])
child = um.AppendItem(item, name)
new_data = ModelData(self.server, model, pmdef)
um.SetPyData(child, new_data)
um.Expand(item)
else:
wx.MessageBox('It\'s impossible to append model', 'Error')
def OnDuplicate(self, event):
um = self.m_user_models
id = um.GetSelection()
title = um.GetItemText(id)
parent = um.GetItemParent(id)
md, jid = um.GetItemPyData(id)
"""
Обработчик события "дублирование модели"
Когда модель дублируется, ее параметры копируются в новую модель,
при неоходимости выделяется слот для работ на сервере.
Результаты модели-оригинала не копируются.
"""
um = self.m_user_models
item, data = self.GetSelectedItemData(self.m_user_models)
title = um.GetItemText(item)
parent = um.GetItemParent(item)
child = um.AppendItem(parent, self.GenerateName(title))
jid = self.server.CreateJob()
um.SetPyData(child, [md.Copy(), jid])
new_data = ModelData(self.server, data)
um.SetPyData(child, new_data)
self.SetStatusText('Copy for "{}" created'.format(title), 0)
def OnDuplicateTree(self, event):
pass
def OnDeleteModel(self, event):
item, data = self.GetSelectedItemData(self.m_user_models)
self.server.DeleteJob(data.jid)
self.m_user_models.Delete(item)
def OnModelProcess(self, event):
um = self.m_user_models
for i in um.GetSelections():
data, jid = um.GetItemPyData(i)
self.server.LaunchJob(jid, data)
data = um.GetItemPyData(i)
self.server.LaunchJob(data.jid, data.mdef)
def OnShowResult(self, event):
item, data = self.GetSelectedItemData(self.m_user_models)
title = self.m_user_models.GetItemText(item)
title = 'Result for model "{}"'.format(title)
rframe = ResultFrame(self, title, data.res)
rframe.Show()
def OnIdle(self, event):
pass
#-----------------------------------------------------------------------------
# Форма с результатами выполнения работы
#-----------------------------------------------------------------------------
class ResultFrame(forms.ResultFrame):
def __init__(self, parent, title, result):
forms.ResultFrame.__init__(self, parent, title)
self.result = result
self.UpdateResults()
def UpdateResults(self):
self.scalar.ClearPage(0)
self.table.ClearGrid()
if self.result:
table = self.result.get('table', [])
if table and len(table):
cols = len(table[0])
rows = len(table) - 1
self.table.CreateGrid(rows, cols)
#
for i, col in enumerate(table[0]):
label = "{} ({})".format(col[0], col[1])
self.table.SetColLabelValue(i, label)
#
for ri, row in enumerate(table[1:]):
for ci, value in enumerate(row):
self.table.SetCellValue(ri, ci, str(value))
self.table.AutoSize()
data = self.result.get('data', {})
pg = self.scalar
for label, value in data.iteritems():
pid = pg.Append(wxpg.StringProperty(label, value = str(value)))
#-----------------------------------------------------------------------------
# Приложение
#-----------------------------------------------------------------------------

View File

@ -42,7 +42,7 @@ class LocalServer:
self.workers = workers # количество потоков выполнения
self.tasks_meta = {} # идентификаор задачи
self.models = [] # список моделей
self.next_job_id = 0 # очередной идентификатор работы
self.next_job_id = 1 # очередной идентификатор работы
self.jobs = {} # очередб работ
self.log = None #
self.running = False #
@ -168,6 +168,12 @@ class LocalServer:
if job != None:
job.Stop()
def DeleteJob(self, jid):
job = self.jobs.get(jid)
if job != None:
job.Stop()
del self.jobs[jid]
#--------------------------------------------------------------------------
def Start(self):
@ -343,13 +349,13 @@ def main():
md['d'] = 10
md['r'] = 3.14
slots = [ s.CreateJob() for i in xrange(5) ]
slots = [ s.CreateJob() for i in xrange(1) ]
for jid in slots:
md['n'] = random.randint(20, 30)
print jid, md['n']
s.LaunchJob(jid, md)
time.sleep(30)
time.sleep(5)
for jid in slots:
pprint(s.GetJobResult(jid))

21
task.py
View File

@ -32,17 +32,12 @@ class Parameter:
def GetDefault(self):
return self.data.get('default')
def GetTestExpresion(self):
def GetTestExpression(self):
return self.data.get('test')
def Test(self, value):
return True
#def __repr__(self):
# return "'{}'".format(
# self.GetType()
# )
#-------------------------------------------------------------------------------
class DataDescription:
@ -88,6 +83,9 @@ class DataDescription:
def __getitem__(self, label):
return self.pdata.get(label)
def type(self):
return 'data-def'
#-------------------------------------------------------------------------------
class DataDefinition:
@ -124,3 +122,14 @@ class DataDefinition:
package.reverse()
return json.dumps(package)
def type(self):
return 'data-def'
#-------------------------------------------------------------------------------
class ResultData:
def __init__(self, data):
self.data = data
def GetColumns(self):
pass

View File

@ -7,7 +7,7 @@
"sintaylor": {
"title": "Simple model",
"title": "Sin Taylor",
"author": "Anton Vakhrushev",
"date": "2012-03-08",
@ -40,11 +40,11 @@
"spec": {
"left": {
"title": "Left"
"title": "Left rectangles"
},
"right": {
"title": "Right"
"title": "Right rectangles"
},
"trapezium": {

View File

@ -39,13 +39,20 @@ def error(msg):
"comment": msg
})
def result(r):
def result(s, t):
return json.dumps({
"answer": "result",
"result": {
"table": [[ {"x": "double"}, {"y": "double"} ]] + r
}
})
"data": s,
"table": t
}})
def serie(n, d, h, l = 0):
for i in xrange(n):
y = sin_taylor(l, d)
yield (l, y)
l += h
time.sleep(0.01)
def main():
@ -62,26 +69,55 @@ def main():
textdata = raw_input()
data = json.loads(textdata)
if not len(data) or data[-1]['label'] != 'sintaylor':
write(error('Unknown model'))
sys.exit(1)
params = data[0]['params']
l = 0 # левая граница
r = params['r'] # правая граница
n = params['n'] # количество шагов
d = params['d'] # количество членов в разложении Тейлора
h = float(r - l) / n # шаг сетки по х
h = r / n
res = [] # таблица резултатов
while l <= r:
y = sin_taylor(l, d)
res.append([l, y])
write(answer(l / r, data[-1]['label']))
l += h
time.sleep(0.1)
label = data[-1]['label']
sum = 0
if label == 'sintaylor':
for x, y in serie(n, d, h):
res.append([x, y])
write(answer(x / r, label))
write(result({},
[[ ['x', 'double'], [ 'y', 'double' ] ]] + res))
elif label == 'left':
for x, y in serie(n-1, d, h):
s = y * h
res.append([x, y, s])
write(answer(x / r, label))
sum += s
write(result(
{ 'sum': sum },
[[ ['x', 'double'], [ 'y', 'double' ], [ 's', 'double' ] ]] + res))
elif label == 'right':
for x, y in serie(n-1, d, h, h):
s = y * h
res.append([x, y, s])
write(answer(x / r, label))
sum += s
write(result(
{ 'sum': sum },
[[ ['x', 'double'], [ 'y', 'double' ], [ 's', 'double' ] ]] + res))
elif label == 'trapezium':
prev = 0
for x, y in serie(n + 1, d, h):
s = 0.5 * (y + prev) * h
res.append([x, y, s])
write(answer(x / r, label))
sum += s
prev = y
write(result(
{ 'sum': sum },
[[ ['x', 'double'], [ 'y', 'double' ], [ 's', 'double' ] ]] + res))
write(result(res))
except Exception, e:
write(error('Fatal error: ' + str(e)))