Compare commits

..

No commits in common. "wiki" and "master" have entirely different histories.
wiki ... master

48 changed files with 3358 additions and 208 deletions

View File

@ -1 +0,0 @@
# Встроенный сервер #

View File

View File

@ -1,25 +0,0 @@
# Введение #
В окружающем мире большинство процессов протекают непрерывно с течением времени. Вода не льется из под крана маленькими кубиками, она течет постоянной непрерывной струей, машина не едет "рывками", она плавно едет по извилистой змейке дороги.
Но, тем не менее, почти все непрерывные процессы мы можем представить в качестве дискретных. Струю воды можно считать по каплям, а путь автомобиля по пройденным сантиметрам. Такой подход несколько неудобен для человека, который привык к плавности движений и форм. Однако, для вычислительных машин нет ничего лучше! Компьютеры все меряют отдельными значениями. Но если маленьких отдельных значений очень много, то они становятся похожи на непрерывный набор данных, поэтому, в сущности, какая в конечном счете разница что использовать: непрерывные функции или их дискретные аналоги?
# Дискретная задача #
Дискретную задачу оптимального управления можно рассматривать и описывать как многошаговый процесс. На каждом шагу такой процесс характеризуется набором переменных состояния (фазовых переменных) _X_ = (x<sup>k</sup><sub>1</sub> ... x<sup>k</sup><sub>n</sub>) и набором переменных управления _U_ = (u<sup>k</sup><sub>1</sub> ... u<sup>k</sup><sub>n</sub>). Если подробнее, то на каждом шаге процесс описывается некоторым набором значений, которых характеризуют его состояние, а также переменными, которых характеризуют воздействие на процесс.
## Начальные условия ##
Изначально нам известны только стартовые параметры системы, а, так как процесс изменяется со временем, вычисляются с помощью заданной функции, которая определяет динамику процесса. В общем случае
f = f(x<sup>k</sup>, u<sup>k</sup>, k),
но может быть и так, что на каждом выбранном промежутке функция будет разной.
## Условие оптимальности ##
В общем случае задача управления дискретным процессом состоит в нахождении такого допустимого процесса, при котором функция
_I_(_U_) = _F_(x<sup>q</sup>} + Sum( f<sub>i</sub>(x<sup>k</sup>, u<sup>k</sup>, k), i = 0 .. q-1 )
стремится к минимуму (максимуму). В этой функции _F_() - заданная функция.

View File

@ -1,15 +0,0 @@
Легкое и удобное управление моделями поиска оптимального управления.
Суть разработки в том, чтобы отделить сам алгоритм от всех "красивостей", которые уже будут реализованы в платформе. Это позволит программистам, занимающимся разработкой алгоритмов для решения задач оптимизации, сосредоточиться на самой задаче и не отвлекаться на создание графического интерфейса, графиков, таблиц отчетов. Среди запланированных возможностей платформы:
1. Готовый универсальный графический интерфейс. Чтобы алгоритм заработал на этом интерфейсе нужно всего лишь написать несколько строк кода, которые будут работать по необходимому протоколу и обеспечат связь между графическим интерфейсом и алгоритмом. Сейчас я как раз проектирую данный протокол с целью сделать его максимально простым и удобным в использовании.
1. Возможность управлять моделями и "методами" их решения. Под "методом" я понимаю алгоритм, который будет решать ту или иную модель: генетический алгоритм, метод проекции градиента, метод Беллмана и др. Можно будет создать одну модель, создать для нее несколько методов решения, чтобы узнать какие параметры будут более точны. Или создать несколько моделей (например, с разными коэффициентами в уравнениях), связать их с одним методом. И пока программа в автоматическом режиме производит расчеты, собирает данные и строит графики, не спеша выпить кофе.
1. Возможность построения графиков одной или нескольких моделей на одном листе, для того, чтобы быстро и в наглядной форме можно было сравнить (например) как от изменения одного параметра изменяется поведение результата. Несколько нажатий клавиш, и подробный отчет будет сделан!
1. Встроенные возможности экспорта таблиц в различные форматы: TeX, Excell, plain text и другие.
Еще несколько особенностей, которые я не буду включать в диплом из-за их громоздкости, но тем не менее, если проект вызовет интерес, их можно будет реализовать.
* Сетевой вариант приложения. Управлять с одного компьютера, например, ноутбука, в то время как все вычисления могут производиться на отдельном сервере. Зачем загромождать свой рабочий компьютер, ведь его назначение - управлять! Расширяя эту возможность, можно разработать web-интерфейс, который позволит управлять всеми задачами из любого места в сети.
* Возможность запуска задач не в виде написанного алгоритма, а в виде математической нотации, где для решения будут применяться уже готовые проверенные алгоритмы. Это несколько снизит скорость работы алгоритмов (универсализация всегда приводит к снижению скорости), но позволит проектировать и запускать задачи не имея больших навыков в программировании.
**It's brilliant! No. It's Opal.**

View File

@ -1,167 +0,0 @@
---
# Общие сведения #
Протокол Opal специально разрабатывался с такой целью, чтобы быть максимально простым в использовании как человеку, так и машине. Не смотря на простоту, протокол обладает большой гибкостью и лаконичностью.
Описание протокола можно разделить на две части. Такое разделение связано с архитектурой проекта. Тем не менее, формат сообщений универсален.
## Формат сообщения ##
Все сообщения строятся по одному шаблону, очень похожему на протокол JSON. В основе выбранного стиля лежит набор пар ключ-значение, которое в большинстве языков программирования представляется в виде ассоциативного массива.
В общем случае сообщение выглядит следующим образом - это набор пар ключ-значение, заключенный в фигурные скобки.
```
{ тело сообщения }
```
Пример простейшего сообщения:
```
{ request = info }
```
## Ключевые слова ##
Некоторые из ключей являются специальными словами, жестко установленными, для возможности адекватного общения меду сервером и клиентом.
| **Слово** | Краткое описание |
|:----------|:-----------------|
| request | запрос какой-либо информации |
| answer | ответ на запрос |
| status | статус опрашиваемого процесса |
| main | секция с описанием параметров модели |
| algo | секция с описанием алгоритмов-методов модели |
| header | секция с описанием заголовка таблицы результатов |
| table | секция с описанием таблицы результатов |
| comment | комментарий |
Теперь более подробно о каждом из ключевых слов.
### request ###
### answer ###
## Типы данных ##
# Соединение сервер - задача #
## Запрос информации сервером ##
Перед запуском задачи для того, чтобы узнать какие параметры нужно передавать для корректного выполнения алгоритма, сервер запускает задачу с ключом -i или --info:
`task (-i|--info)`
Результатом работы будет набор строк, который описывает все доступные значения для данной задачи. Строки описываются в следующем формате:
_name_ = _type_ (choice _list_) (default _value_)
<a href='Hidden comment:
в будущем
_name_ = _type_ (choice _list_) (default _value_) (check _expr_)
'></a>
В ответе обязательно должна присутствовать секция Main, в которой описываются все параметры модели. Другие секции описывают параметры доступных алгоритмов в задаче и их количество не ограничено.
## Передача списка параметров ##
## Получение результата ##
# Соединение сервер - ГИП #
# Пример сессии #
```
// запрос сервера на получение информации о задаче
{
request = info
}
// ответ клиента-задачи
{
answer = ok
meta = {
detailed = true
}
main = {
size = int default 100
x = float default 20.0
y = float default 0.0
time = period default 00:00 to 24:00 comment twenty-four hours
}
algo = {
genetic = {
method = string choice [std, last] default std
poplulation = int default 1000
}
bellman = {
xpart = float partition 0 to 100 by 1
ypart = float partition 0 to 50 by 0.5
}
}
}
// запрос сервера на вычисления по указанным параметрам
{
request = calculate
main = {
size = 100
x = 20.0
y = 5.0
time = 00:00 to 48:00
}
algo = {
genetic = {
method = last
population = 1200
}
}
}
// ответ клиента задачи о том, что вычисления начались
{
answer = ok
status = inprogress
}
// запрос сервера о статусе вычислений
{
request = status
}
// ответ клиента-задачи о том, что прошла уже половина вычислений
{
answer = ok
status = inprogress
percent = 0.50
comment = 10 минут, полет нормальный
}
// снова запрос сервера о статусе
{
request = status
}
// клиент сообщает, что вычисления завершены, и предоставляет таблицу результатов
{
answer = ok
status = ready
header = {
time = datetime
x = float
y = float
u = float comment management
}
table = {
00:00 20.0 5.0 0.0
01:00 18.0 6.0 1.0
02:00 16.0 7.0 2.0
//...
}
}
```

508
forms.py Normal file
View File

@ -0,0 +1,508 @@
# -*- coding: utf-8 -*-
import wx
import wx.gizmos
import wx.grid
import wx.propgrid as wxpg
import wx.lib.plot as wxplot
import wx.lib.agw.aui as aui
import gettext
import json
import os
# import wx.aui as aui
_ = gettext.gettext
ID_NEW = wx.NewId()
ID_SAVE = wx.NewId()
ID_OPEN = wx.NewId()
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_STOP_MODEL = wx.NewId()
ID_SHOW_RESULT = wx.NewId()
ID_SHOW_PLOT = wx.NewId()
ID_ADD_PLOT = wx.NewId()
ID_ADD_CURVES = wx.NewId()
ID_ADD_MARKERS = wx.NewId()
ID_ENGLISH_LANG = wx.NewId()
ID_RUSSIAN_LANG = wx.NewId()
ID_ABOUT = wx.NewId()
ID_EXPORT_CSV = wx.NewId()
ID_SAVE_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 Icons:
"""
Пустой класс для хранения идентификаторов иконок, чтобы к ним можно было
удобно обращаться:
icons = Icons()
icons.open = wxIcon(...)
"""
pass
class PropertyCtrl(wxpg.PropertyGrid):
def GetPosition(self):
return self.GetPanel().GetPosition()
def Clear(self):
wxpg.PropertyGrid.Clear(self)
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__ (self, parent, title = 'Opal', size = wx.Size(873,594))
self.settings = {}
self.LoadSettings()
lang = self.settings['locale']
if not lang:
locale = wx.Locale(wx.LANGUAGE_DEFAULT)
lang = locale.GetCanonicalName()
self.settings['locale'] = lang
Lang = gettext.translation('opal', './locale', languages=[lang], fallback=True)
Lang.install(unicode=True)
global _
_ = Lang.ugettext
self.lang = Lang
self.auimgr = aui.AuiManager()
self.auimgr.SetManagedWindow(self)
self.auimgr.GetArtProvider().SetMetric(aui.AUI_DOCKART_SASH_SIZE, 3)
self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
self.ilist, self.icons = self.LoadIcons()
# Спецификации
self.m_specs = wx.TreeCtrl(self, size = (200, -1), style = wx.TR_DEFAULT_STYLE)
# self.m_specs.SetMinSize(wx.Size(200,-1))
self.auimgr.AddPane(self.m_specs,
aui.AuiPaneInfo().Name("m_specs").Left().Layer(1).CloseButton(False))
# Пользовательские модели
self.m_user_models = TreeListCtrl(self, size = (200, -1),
style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT
| wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_ROW_LINES | wx.TR_MULTIPLE)
self.m_user_models.AddColumn(_("Model name"))
self.m_user_models.AddColumn(_("Status"))
self.m_user_models.AddColumn(_("Progress"))
self.m_user_models.AddColumn(_("Comment"))
self.m_user_models.SetMainColumn(0)
self.m_user_models.SetImageList(self.ilist)
self.auimgr.AddPane(self.m_user_models,
aui.AuiPaneInfo().Name("m_user_models").CenterPane().Position(1))
# Параметры модели
self.m_params = PropertyCtrl(self, size = (-1, 300))
self.auimgr.AddPane(self.m_params,
aui.AuiPaneInfo().Name("m_params").CloseButton(False).
CenterPane().Bottom().Position(2))
# Быстрые результаты
self.m_quick_result = PropertyCtrl(self, size = (200, -1))
self.auimgr.AddPane(self.m_quick_result,
aui.AuiPaneInfo().Name("m_quick_result").CloseButton(False).
Right().Position(1).Layer(1))
# Графики
self.m_plots = wx.TreeCtrl(self, size = (200, -1),
style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS)
self.m_plots.SetImageList(self.ilist)
self.auimgr.AddPane(self.m_plots,
aui.AuiPaneInfo().Name("m_plots").CloseButton(False).
Right().Position(2).Layer(1))
# Меню, панель инструментов и панель статуса
sbar = wx.StatusBar(self)
self.SetStatusBar(sbar)
self.SetMenuBar(self.BuildMenu())
self.BuildContextMenu()
self.BuildToolBar()
layout = self.settings['layout']
if layout:
self.auimgr.LoadPerspective(layout, False)
# print 'layout loaded'
self.auimgr.GetPane('m_specs').Caption(_("Templates"))
self.auimgr.GetPane('m_user_models').Caption(_("Models"))
self.auimgr.GetPane("m_params").Caption(_("Parameters"))
self.auimgr.GetPane("m_quick_result").Caption(_("Quick results"))
self.auimgr.GetPane("m_plots").Caption(_("Plots"))
self.auimgr.Update()
def LoadIcons(self):
icons = Icons()
ilist = wx.ImageList(16, 16)
icons.mready = ilist.Add(wx.Bitmap('share/model-ready.png'))
icons.mrun = ilist.Add(wx.Bitmap('share/model-run.png'))
icons.mcomplete = ilist.Add(wx.Bitmap('share/model-complete.png'))
icons.mstopped = ilist.Add(wx.Bitmap('share/model-stop.png'))
icons.mnoexec = ilist.Add(wx.Bitmap('share/model-no-exec.png'))
icons.porg = ilist.Add(wx.Bitmap('share/plot-org.png'))
icons.pline = ilist.Add(wx.Bitmap('share/plot-line.png'))
icons.pmarker = ilist.Add(wx.Bitmap('share/plot-marker.png'))
icons.phist = ilist.Add(wx.Bitmap('share/plot-histogram.png'))
return ilist, icons
def BuildMenu(self):
menubar = wx.MenuBar()
menu = wx.Menu()
menu.Append(ID_NEW, _("&New\tCtrl+N"))
menu.Append(ID_OPEN, _("&Open\tCtrl+O"))
menu.Append(ID_SAVE, _("&Save\tCtrl+S"))
menubar.Append(menu, _('&Project'))
menu = wx.Menu()
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_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()
menu.Append(ID_PROCESS_MODEL, _('Process\tF5'))
menu.Append(ID_STOP_MODEL, _('Stop\tF6'))
#menu.AppendSeparator()
menubar.Append(menu, _('&Run'))
menu = wx.Menu()
menu.Append(ID_SHOW_RESULT, _('Show report\tF7'))
menu.AppendSeparator()
menu.Append(ID_SHOW_PLOT, _('Show plot\tF8'))
menu.Append(ID_ADD_PLOT, _('Add plot'))
#menu.Append(ID_ADD_LINE, _('Add line'))
menubar.Append(menu, _('&Result'))
menu = wx.Menu()
submenu = wx.Menu()
submenu.Append(ID_ENGLISH_LANG, _('English'))
submenu.Append(ID_RUSSIAN_LANG, _('Russian'))
menu.AppendSubMenu(submenu, _('Language'))
# menu.Append(ID_SHOW_PLOT, _('Layout'))
# menu.Append(ID_ADD_PLOT, _('Options'))
#menu.Append(ID_ADD_LINE, _('Add line'))
menubar.Append(menu, _('&Settings'))
menu = wx.Menu()
menu.Append(ID_ABOUT, _("&About\tF1"))
menubar.Append(menu, _('&Help'))
return menubar
def BuildContextMenu(self):
menu = wx.Menu()
menu.Append(ID_ADD_MODEL_ROOT, _('Add model to root'))
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))
menu1 = wx.Menu()
menu1.Append(ID_ADD_PLOT, _('Add plot'))
menu1.AppendSeparator()
menu1.Append(ID_ADD_CURVES, _('Add curves'))
menu1.Append(ID_ADD_MARKERS, _('Add markers'))
self.m_plots.Bind(wx.EVT_CONTEXT_MENU,
lambda x: self.m_plots.PopupMenu(menu1))
menu2 = wx.Menu()
menu2.Append(ID_SHOW_RESULT, _('Show report'))
menu2.AppendSeparator()
menu2.Append(ID_ADD_CURVES, _('Add curves'))
menu2.Append(ID_ADD_MARKERS, _('Add markers'))
self.m_user_models.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK,
lambda x: self.m_user_models.PopupMenu(menu2))
def BuildToolBar(self):
tb1 = aui.AuiToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
agwStyle = aui.AUI_TB_DEFAULT_STYLE | aui.AUI_TB_VERTICAL)
tb1.SetToolBitmapSize(wx.Size(16, 16))
tb1.AddSimpleTool(ID_ADD_MODEL_SELECTED, "model-new", wx.Bitmap('share/model-add.png'),
_('Add specification to selected model'))
tb1.AddSimpleTool(ID_DUPLICATE_MODEL, "model-dup", wx.Bitmap('share/model-dup.png'),
_('Duplicate selected model'))
tb1.AddSimpleTool(ID_DUPLICATE_TREE, "model-dup-tree", wx.Bitmap('share/model-dup-tree.png'),
_('Duplicate selected model and all submodels'))
tb1.AddSimpleTool(ID_DELETE_MODEL, "model-del", wx.Bitmap('share/model-delete.png'),
_('Delete selected model'))
tb1.AddSeparator()
tb1.AddSimpleTool(ID_PROCESS_MODEL, "model-go", wx.Bitmap('share/model-go.png'),
_('Start processing of selected models'))
tb1.AddSimpleTool(ID_STOP_MODEL, "model-stop", wx.Bitmap('share/model-cancel.png'),
_('Stop processing of selected models'))
tb1.AddSeparator()
tb1.AddSimpleTool(ID_SHOW_PLOT, "plot-quick", wx.Bitmap('share/plot-line.png'),
_('Show quick plot for selected model'))
tb1.AddSimpleTool(ID_SHOW_RESULT, "report-show", wx.Bitmap('share/report-show.png'),
_('Show result data and table for selected model'))
tb1.AddSeparator()
tb1.AddSimpleTool(ID_ABOUT, "app-about", wx.Bitmap('share/app-about.png'),
_('Show infomation about application'))
tb1.Realize()
self.auimgr.AddPane(tb1, aui.AuiPaneInfo().Name("tb1").Caption(_("Toolbar")).
ToolbarPane().Left().Floatable(False).Movable(False).Gripper(False))
def SaveSettings(self):
self.settings['layout'] = self.auimgr.SavePerspective()
with open('settings.conf', 'w') as f:
json.dump(self.settings, f, indent = 2)
def LoadSettings(self):
default = {
'workers': 2,
'conf': 'tasks.conf',
'locale': None,
'layout': None,
}
self.settings = default
if os.path.exists('settings.conf'):
with open('settings.conf', 'r') as f:
self.settings.update(json.load(f))
class SelectModelDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1, _('Select model'))
sizer = wx.BoxSizer(wx.VERTICAL)
self.mlist = wx.ListCtrl(self, style = wx.LC_ICON | wx.LC_SINGLE_SEL)
sizer.Add(self.mlist, 1, wx.EXPAND | wx.ALL, 0)
buttonsSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
sizer.Add(buttonsSizer, 0, wx.EXPAND | wx.ALL, 5)
self.SetSizer(sizer)
self.Layout()
self.Centre(wx.BOTH)
class ResultFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(500, 500),
style = wx.DEFAULT_FRAME_STYLE)
sizer = wx.BoxSizer(wx.VERTICAL)
self.scalar = PropertyCtrl(self)
self.scalar.SetMinSize((-1, 200))
self.table = wx.grid.Grid(self)
self.table.SetDefaultCellAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)
self.table.DisableCellEditControl()
sizer.Add(self.scalar, 0, wx.EXPAND | wx.ALL, 1)
sizer.Add(self.table, 1, wx.EXPAND | wx.ALL, 1)
self.SetMenuBar(self.BuildMenu())
self.SetSizer(sizer)
self.Layout()
self.Centre(wx.BOTH)
def BuildMenu(self):
menubar = wx.MenuBar()
menu = wx.Menu()
menu.Append(ID_EXPORT_CSV, _('CSV\tCtrl+E'))
#menu.Append(wx.NewId(), 'TeX')
menubar.Append(menu, _('Export to'))
return menubar
class LineSelectDialog(wx.Dialog):
def __init__(self, parent, title):
wx.Dialog.__init__ (self, parent, -1, title, size = wx.Size(400, 300))
bSizer = wx.BoxSizer(wx.HORIZONTAL)
self.left = wx.ListBox(self)
self.right = wx.ListBox(self, style = wx.LB_EXTENDED)
bSizer.Add(self.left, 1, wx.EXPAND | wx.ALL, 2)
bSizer.Add(self.right, 1, wx.EXPAND | wx.ALL, 2)
buttonsSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(bSizer, 1, wx.EXPAND | wx.ALL, 0)
sizer.Add(buttonsSizer, 0, wx.EXPAND | wx.ALL, 5)
self.SetSizer(sizer)
self.Layout()
self.Centre(wx.BOTH)
class SizeSelector(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1, _('Image size'), size = (200, 100))
bSizer = wx.BoxSizer(wx.HORIZONTAL)
self.width = wx.SpinCtrl(self)
self.width.SetRange(1, 5000)
self.width.SetValue(800)
bSizer.Add(self.width, 1, wx.EXPAND | wx.LEFT, 5)
self.height = wx.SpinCtrl(self)
self.height.SetRange(1, 5000)
self.height.SetValue(600)
bSizer.Add(self.height, 1, wx.EXPAND | wx.RIGHT, 5)
buttonsSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.AddStretchSpacer(1)
sizer.Add(bSizer, 0, wx.EXPAND | wx.ALL, 0)
sizer.AddStretchSpacer(1)
sizer.Add(buttonsSizer, 0, wx.EXPAND | wx.ALL, 5)
self.SetSizer(sizer)
self.Layout()
self.Centre(wx.BOTH)
def GetValues(self):
return self.width.GetValue(), self.height.GetValue()
HandCursorImage = wx.Image('share/cursor-openhand.png')
GrabHandCursorImage = wx.Image('share/cursor-closedhand.png')
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)
# стандартные курсоры компонента настолько монстроуозные,
# что их просто необходимо заменить на что-нибудь приличное
self.plot.canvas.SetCursor(wx.STANDARD_CURSOR)
self.plot.HandCursor = wx.CursorFromImage(HandCursorImage)
self.plot.GrabHandCursor = wx.CursorFromImage(GrabHandCursorImage)
self.plot.MagCursor = wx.StockCursor(wx.CURSOR_MAGNIFIER)
self.plot.SetGridColour(wx.Color(200, 200, 200))
self.plot.SetEnableGrid(True)
self.plot.SetEnableAntiAliasing(True)
self.plot.SetEnableHiRes(True)
self.plot.SetEnableLegend(True)
self.plot.SetEnableDrag(True)
self.Centre(wx.BOTH)
menubar = wx.MenuBar()
menu = wx.Menu()
menu.Append(ID_SAVE_PLOT, _('Save to file\tCtrl+S'))
menubar.Append(menu, _('Plot'))
self.SetMenuBar(menubar)
self.plot.canvas.Bind(wx.EVT_MOUSEWHEEL, self.OnZoom)
self.plot.canvas.Bind(wx.EVT_MIDDLE_DOWN, self.OnZoomReset)
self.plot.canvas.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.plot.canvas.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
def OnZoom(self, event):
x = event.GetX()
y = event.GetY()
r = event.GetWheelRotation()
x, y = self.plot.PositionScreenToUser((x, y))
delta = 0.8/1.0 if r > 0 else 1.0/0.8
self.plot.Zoom((x, y), (delta, delta))
def OnZoomReset(self, event):
self.plot.Reset()
def OnKeyDown(self, event):
if event.GetKeyCode() == wx.WXK_SHIFT:
self.plot.SetEnableDrag(False)
self.plot.SetEnableZoom(True)
def OnKeyUp(self, event):
if event.GetKeyCode() == wx.WXK_SHIFT:
self.plot.SetEnableZoom(False)
self.plot.SetEnableDrag(True)
class AboutDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, title = _('About Opal'), size = (300, 330))
title = 'Opal System'
version = 'Aurora version'
copyr = '(c) 2012 Anton Vakhrushev'
self.SetBackgroundColour(wx.Colour(42, 42, 40))
img = wx.StaticBitmap(self)
img.SetBitmap(wx.Bitmap('share/opal_logo.png'))
st = wx.StaticText(self, -1, title,
pos = (15, 170), size = (270, 100))
st.SetForegroundColour(wx.Colour(245, 245, 0))
st.SetFont(wx.Font(24, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana"));
st = wx.StaticText(self, -1, version,
pos = (25, 215), size = (250, 20))
st.SetForegroundColour(wx.Colour(240, 240, 240))
st.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana"));
st = wx.StaticText(self, -1, copyr,
pos = (25, 255), size = (250, 30))
st.SetForegroundColour(wx.Colour(240, 240, 240))
st.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana"));
self.Centre(wx.BOTH)

Binary file not shown.

View File

@ -0,0 +1,334 @@
msgid ""
msgstr ""
"Project-Id-Version: Opal Aurora\n"
"POT-Creation-Date: 2012-05-12 13:57+0400\n"
"PO-Revision-Date: 2012-05-12 13:58+0400\n"
"Last-Translator: anwinged <anwinged@ya.ru>\n"
"Language-Team: Anton \"anwinged\" Vakhrushev <anwinged@ya.ru>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-KeywordsList: _;gettext;gettext_noop\n"
"X-Poedit-Basepath: C:\\Projects\n"
"X-Poedit-Language: Russian\n"
"X-Poedit-SourceCharset: utf-8\n"
"X-Poedit-SearchPath-0: Opal\n"
#: Opal/forms.py:114
msgid "Templates"
msgstr "Шаблоны"
#: Opal/forms.py:122
msgid "Model name"
msgstr "Имя модели"
#: Opal/forms.py:123
msgid "Status"
msgstr "Статус"
#: Opal/forms.py:124
msgid "Progress"
msgstr "Прогресс"
#: Opal/forms.py:125
msgid "Comment"
msgstr "Комментарий"
#: Opal/forms.py:130
msgid "Models"
msgstr "Модели"
#: Opal/forms.py:138
msgid "Parameters"
msgstr "Параметры"
#: Opal/forms.py:146
msgid "Quick results"
msgstr "Быстрый просмотр"
#: Opal/forms.py:156
msgid "Plots"
msgstr "Графики"
#: Opal/forms.py:198
msgid "&New\tCtrl+N"
msgstr "&Новый\tCtrl+N"
#: Opal/forms.py:199
msgid "&Open\tCtrl+O"
msgstr "&Открыть\tCtrl+O"
#: Opal/forms.py:200
msgid "&Save\tCtrl+S"
msgstr "&Сохранить\tCtrl+S"
#: Opal/forms.py:201
msgid "&Project"
msgstr "&Проект"
#: Opal/forms.py:204
#: Opal/forms.py:248
msgid "Add model to root"
msgstr "Добавить модель в корень"
#: Opal/forms.py:205
msgid "Append model to selected"
msgstr "Добавить модель к выбранной"
#: Opal/forms.py:207
msgid "&Duplicate\tCtrl+D"
msgstr "&Дублировать\tCtrl+D"
#: Opal/forms.py:208
msgid "&Duplicate with subitems\tCtrl+Shift+D"
msgstr "&Дублировать с поддеревом\tCtrl+Shift+D"
#: Opal/forms.py:209
msgid "Delete\tCtrl+E"
msgstr "Удалить\tCtrl+E"
#: Opal/forms.py:211
msgid "&Test\tCtrl+T"
msgstr ""
#: Opal/forms.py:212
msgid "&Model"
msgstr "&Модель"
#: Opal/forms.py:215
msgid "Process\tF5"
msgstr "Запустить\tF5"
#: Opal/forms.py:216
msgid "Stop\tF6"
msgstr "Остановить\tF6"
#: Opal/forms.py:218
msgid "&Run"
msgstr "&Выполнить"
#: Opal/forms.py:221
msgid "Show report\tF7"
msgstr "Показать отчет\tF7"
#: Opal/forms.py:223
msgid "Show plot\tF8"
msgstr "Показать график\tF8"
#: Opal/forms.py:224
#: Opal/forms.py:254
msgid "Add plot"
msgstr "Добавить график"
#: Opal/forms.py:226
msgid "&Result"
msgstr "&Результат"
#: Opal/forms.py:230
msgid "English"
msgstr "Английский"
#: Opal/forms.py:231
msgid "Russian"
msgstr "Русский"
#: Opal/forms.py:232
msgid "Language"
msgstr "Язык"
#: Opal/forms.py:237
msgid "&Settings"
msgstr "Настройки"
#: Opal/forms.py:241
msgid "&About\tF1"
msgstr "&О программе\tF1"
#: Opal/forms.py:242
msgid "&Help"
msgstr "&Помощь"
#: Opal/forms.py:249
msgid "Add model to selected"
msgstr "Добавить модель к выбранной"
#: Opal/forms.py:256
#: Opal/forms.py:264
msgid "Add curves"
msgstr "Добавить линии"
#: Opal/forms.py:257
#: Opal/forms.py:265
msgid "Add markers"
msgstr "Добавить маркеры"
#: Opal/forms.py:262
msgid "Show report"
msgstr "Показать отчет"
#: Opal/forms.py:275
msgid "Add specification to selected model"
msgstr "Добавить спецификацию к выбранной модели"
#: Opal/forms.py:277
msgid "Duplicate selected model"
msgstr "Дублировать выделенную модель"
#: Opal/forms.py:279
msgid "Duplicate selected model and all submodels"
msgstr "Дублировать выбранную модель со всеми спецификациями"
#: Opal/forms.py:281
msgid "Delete selected model"
msgstr "Удалить выбранную модель"
#: Opal/forms.py:284
msgid "Start processing of selected models"
msgstr "Начать обработку выбранных моделей"
#: Opal/forms.py:286
msgid "Stop processing of selected models"
msgstr "Остановить обработку выбранных моделей"
#: Opal/forms.py:289
msgid "Show quick plot for selected model"
msgstr "Построить график для выбранных моделей"
#: Opal/forms.py:291
msgid "Show result data and table for selected model"
msgstr "Покаразть результаты вычислений для выбранной модели"
#: Opal/forms.py:294
msgid "Show infomation about application"
msgstr "Показать информацию о приложении"
#: Opal/forms.py:297
msgid "Toolbar"
msgstr ""
#: Opal/forms.py:320
msgid "Select model"
msgstr "Выбрать модель"
#: Opal/forms.py:361
msgid "CSV\tCtrl+E"
msgstr "CSV\tCtrl+E"
#: Opal/forms.py:363
msgid "Export to"
msgstr "Экспортировать как"
#: Opal/forms.py:391
msgid "Image size"
msgstr "Размер изображения"
#: Opal/forms.py:447
msgid "Save to file\tCtrl+S"
msgstr "Сохранить\tCtrl+S"
#: Opal/forms.py:448
#: Opal/opal.py:984
msgid "Plot"
msgstr "График"
#: Opal/forms.py:479
msgid "About Opal"
msgstr "О программе Opal"
#: Opal/opal.py:242
msgid "Information"
msgstr "Информация"
#: Opal/opal.py:242
msgid "Locale changed. Restart application to apply settings"
msgstr "Локаль изменена. Перезапустите приложение, чтобы настройки вступили в силу"
#: Opal/opal.py:293
msgid "Unknown"
msgstr "Неизвестно"
#: Opal/opal.py:376
msgid "Model \"{}\" selected"
msgstr "Модель \"{}\" выбрана"
#: Opal/opal.py:433
msgid "Select file to load project"
msgstr "Выбрать файл с проектом"
#: Opal/opal.py:472
msgid "Can't load saved file"
msgstr "Невозможно загрузить сохраненный файл"
#: Opal/opal.py:472
#: Opal/opal.py:569
#: Opal/opal.py:694
msgid "Error"
msgstr "Ошибка"
#: Opal/opal.py:534
msgid "Select file to save project"
msgstr "Выбрать файл для проекта"
#: Opal/opal.py:569
msgid "Can't save the project"
msgstr "Невозможно сохранить прокт"
#: Opal/opal.py:610
msgid "Ready"
msgstr "Ожидает"
#: Opal/opal.py:614
msgid "Running"
msgstr "Выполняется"
#: Opal/opal.py:618
msgid "Completed"
msgstr "Завершено"
#: Opal/opal.py:622
msgid "Stopped"
msgstr "Остановлено"
#: Opal/opal.py:626
msgid "No executable"
msgstr "Не выполнимая"
#: Opal/opal.py:694
msgid "It's impossible to append model"
msgstr "Невозможно добавить модель"
#: Opal/opal.py:789
msgid "Invalid item"
msgstr "Неверный элемент"
#: Opal/opal.py:796
#: Opal/opal.py:803
msgid "Empty data"
msgstr "Нет данных"
#: Opal/opal.py:883
msgid "Result for model \"{}\""
msgstr "Результат для моделиl \"{}\""
#: Opal/opal.py:891
msgid "New plot"
msgstr "Новый график"
#: Opal/opal.py:937
msgid "Line(s) for \"{}\" ({}/{})"
msgstr "Линии для \"{}\" ({}/{})"
#: Opal/opal.py:941
msgid "There is no any result data for model!"
msgstr "Для модели нет результата!"
#: Opal/opal.py:1098
msgid "Save table to CSV"
msgstr "Сохранить таблицу как CSV"
#: Opal/opal.py:1200
msgid "Save plot"
msgstr "Сохранить график"
#~ msgid "Locale"
#~ msgstr "Язык"

39
manual/brief.txt Normal file
View File

@ -0,0 +1,39 @@
* Введение
Про актуальность решения задач оптимизации,
какие классы проблем могут они решать, почему решение порой
требует очень больших вычислительных ресурсов.
* Про Opal
В этой главе будет рассказано о причинах разработки,
почему решение конкретных задач лучше и быстрее решения общих,
какие задачи можно решать и какие модели обрабатывать.
Как можно развить идею платформы до облачного сервиса,
плюсы и минусы выбранного подхода
* Opal для пользователя
Глава о том как пользоваться программой. Описание
интерфейса, элементов управления. Работа с моделями,
графиками, отчетами. Сохранение и загрузка. Подключение
новых моделей.
* Opal для программиста
Глава о том, как написать и подключить собственную модель.
Как Opal опрашивает модели, как запускает. Описание протокола
и типов данных. Входящие данные и результирующие.
Все описание идет на примере разложения синус в ряд тейлора.
* Тонкая настройка Opal
Еще раз об архитектуре приложния. Как работает сервер Opal.
API сервера и почему оно позволяет произвести быстрое масштабирование
до сетевой структуры. Идентификаторы задач и процессов: что, для чего
и почему. Безопасность при работе с исполняемыми файлами задач
сторонних разработчиков. Контроль ресурсов машины.
* Заключение
О достигнутых целях и перспективах использования разработки.

8
manual/for_adv.tex Normal file
View File

@ -0,0 +1,8 @@
\part{“Opal” для продвинутых}
Или что-скрывает-ложь как произвести тонкую настройку и извлечь максимум выгоды.
Очередь выполнения задач
Приоритеты задач
Идентификация и безопасность
Приложение A
Список предопределенных полей протокола “Opal”

267
manual/for_prog.tex Normal file
View File

@ -0,0 +1,267 @@
\chapter{Opal для программиста}
Или как написать собственную модель для использования с Opal.
\section{Краткий обзор архитектуры Opal}
Система Opal представлена в двух редакциях: локальная (local) и сетевая (net). В локальной редакции приложение сервера совмещено с клиентским приложением графического интерфейса. Оба варианта могут работать на одной машине, но только сетевой вариант способен работать раздельно с графическим интерфейсом. При таком подходе появляется больше свободы в управлении для сервера и пропадает нагрузка на клиентский компьютер (если, конечно, части разнесены по разным машинам в сети).
Локальная редакция, серверная часть совмещена с графической:
Сетевая редакция (сплошными линиями показаны фактические связи между компонентами, пунктирными — логические):
Если при использовании локальной версии нужно очень аккуратно использовать системные ресурсы и процессорное время, дабы избежать дискомфорта при работе с другими приложениями, то для сетевой редакции такой недостаток почти не имеет значения. Сервер может находиться на выделенной мощной машине, а при должной настройке задачи могут даже запускаться на кластере из многих компьютеров. Все это скрыто от конечного пользователя, предоставляя ему только управление задачами и моделями.
\section{Подключение своего приложения}
Вне зависимости от того какую редакцию использовать, все задачи находятся на той же машине, что и сервер. Сервер связывается с задачами через стандартные потоки ввода-вывода (stdin и stdout). Это сделано для того, чтобы упростить разработку пользовательских задач. Можно было бы, конечно, использовать подключения по протоколу TCP\slash{}IP, но это бы только вызвало лишние сложности, так как работа с этим сетевым протоколом зависит от операционной системы, и в многих языках программирования не содержится нужных библиотек в стандартной поставке.
Рассмотрим все этапы подготовки своего приложения и подключения его к системе Опал на основе данного в первой части условия задачи (стр. \pageref{testtask}). Исполняемый файл задачи будет называться testt.exe.
При старте сервер опрашивает задачи, путь к которым указан в специальном файле
\begin{verbatim}
tasks.conf
\end{verbatim}
Путь может быть как абсолютным, так и относительным. При использовании относительного пути следует помнить, что он вычисляется относительно исполняемого файла ссервера Opal.
Таким образом, задача может располагаться где угодно в системе. Это полезно, потому что при работе задачи могут создавать или использовать дополнительные файлы, и чтобы не было путаницы каждую задачу лучше поместить в отдельную директорию.
Чтобы добавить свой проект в систему, достаточно добавить в конец файла tasks.conf путь к исполняемому файлу задачи.
Во время опроса система запускает задачу с ключом \emph{-i}:
\begin{verbatim}
reltask.exe -i
\end{verbatim}
а при работе будет запускать с ключом \emph{-r}:
\begin{verbatim}
reltask.exe -r
\end{verbatim}
так что исполняемый файл должен адекватно реагировать как минимум на эти два ключа. В то же время не рекомендуется использование каких либо других ключей запуска, а все настройки передавать через сообщения по протоколу Opal. В то же время такой подход удобен тем, что при старте без ключей (к примеру) может запускаться обычное отдельное приложение с оконным интерфейсом, а значит задача будет вести себя двояко: и как приложение для исползования с системой Opal, так и для использования как независимая программа.
\section{Ключи запуска}
Ключ \emph{-i} нужен для того, чтобы сервер получил полные сведения о задаче. Эти сведения спрашиваются один раз и используются в дальнейшем при построении таблицы параметров на графическом клиенте, проверке целостности данных, в качестве рекомендаций при управлении очередью задач.
После того как параметры опрошены, задача будет готова к запуску сервером. Когда клиент сделает запрос на запуск задачи, сервер запустит ее с ключом -r и через стандартный поток ввода передаст полученные от клиента параметры старта задачи (параметры задачи, модели и метода) и будет ожидать результата вычислений в виде таблицы данных.
\section{Протокол Opal}
Чтобы понять как происходит обмен командами между клиентскими задачами и серверным процессом, нужно сперва описать соответствующий этому протокол.
Протокол обмена данными между всеми частями системы Opal базируется на формате обмена данными JSON (JavaScript Object Notation). Формат очень простой и построен на основе словарей и списков. Эти структуры данных если в большинстве современных языков программирования, а в некоторых JSON даже поддерживается стандартной библиотекой (к примеру, Python или JavaScript). Кроме того формат удобен для чтения и изменения человеком, что явилось немаловажным фактором при его выборе. Список всех полей можно прочитать в приложении №.
\section{Коммуникация сежду сервером и задачей}
Запустив задачу и передав ей параметры, сервер переходит в режим прослушивания. В этом режиме сервер больше не передает никаких сообщений задаче, а только принимает сообщения от нее и обрабатывает их. Сообщения содержат в себе информацию о ходе выполнения задачи, каких-либо комментариях, предупреждениях или ошибках, которые могли возникнуть во время выполнения, а также о результатах выполнения.
Как и в любом клиент-серверном приложении клиент посылает запросы серверу, а сервер на них отвечает. Система Opal использует тот же принцип. Графический клиент посылает запросы серверу, а сервер, в свою очередь, посылает запросы задачам. В любом случае каждый запрос должен иметь поле запроса, по котороу и будет определено, что же требуется выполнить. Текст запроса в общем случае выглядит так:
\begin{verbatim}
{ "request": %request-text% }
\end{verbatim}
где \%request-text\% — одна из препопределенных команд. Список таких команд представлен ниже:
todo заполнить полностью
get-task-list
Получить список доступных для запуска задач.
run-task
Запустить новую задачу.
stop-task
Остановить задачу.
get-status
Узнать статус выполнения задачи.
В ответ на запрос сервер высылает сообщение, в котором должно присутствовать поле answer:
\begin{verbatim}
{ "answer": %answer-text% }
\end{verbatim}
где \%answer-text\% --- один из предопределенных ответов. Ответы характеризуют результат выполнения запроса и они должны быть сигналами для дальнейшей обработки.
ok
Все прошло хорошо, можно анализировать возвращенные значения.
warning
Запрос выполнен, но при его выполнении возникли некоторые проблемы, подробнее о которых можно узнать, проанализировав полученные данные.
error
Запрос не выполнен, произошла критическая ошибка. Следует выяснить причину ошибки, если нужно обратиться к справочному руководству или в техническую поддержку.
Кроме поля answer в ответе могут присутствовать поля code, value и comment. Эти поля используются для получения дополнительной информации. Так поле code может содержать числовой код ошибки, которая вызвала отказ или ошибку, поле value используется для возвращения некоторого значения (прогресс выполнения, список доступных задач и др.), comment сожержит произвольную текстовую строку, которая должна просто прояснить ситуацию где необходимо.
Пример запроса и ответа. В качестве простого запроса-ответа можно привести запрос статуса выполнения задачи. Графический клиент посылает серверу запрос
\begin{verbatim}
{
"request": "get-status",
"uid": %user-id%,
"tid": %task-id%
}
\end{verbatim}
где \%user-id\% и \%task-id\% обозначают уникальные идентификаторы клиента и задачи соответственно. О том, как они формируются и какую роль играют можно прочитать в последней части данного сочинения.
Сервер на основе uid и tid перенаправляет запрос нужной задаче, если, конечно, в данный момент она способна его обработать. После чего высылает клиенту результат разпроса (к примеру):
\begin{verbatim}
{
"answer": "ok",
"code": 0,
"value": 0.6321,
"comment": "in progress, all right"
}
\end{verbatim}
Если, например, клиент ошибся в tid или пытается получить доступ к чужой задаче, сервер даст ответ
\begin{verbatim}
{
"answer": "error",
"comment": "access denied"
}
\end{verbatim}
\section{Описание данных}
Перед тем как перейти к описанию структуры, описывающей зачаду и ее модели, следует рассказать о типах данных, которые применяются в Opal. Далее будем говорить только о тех полях, которые встречаются в задачах, моделях и методах.
Тип --- очень важная часть описания данных. Хотя без него можно было бы обойтись, предоставив пользователям полный контроль над передаваемыми данными, этого лучше избежать. Все дело в том, что тип является мощным инструментом контроля целостности передаваемых данных, не позволяя использовать, скажем, строку там, где должно быть целое число.
В описании поля используется следующий синтаксис:
"name": "type [choice list] [default value] [title title]
[// comment-text]"
Подробнее о том как происходит разбор данного выражения можно прочитать в приложении №, подробные примеры есть в последнем пункте этой главы.
Из описания видно, что описания данных представляет собой словарь (dictionary, map, ассоциативный массив), где ключом является имя поля, а значением — его описание.
Таблица типов данных:
Тип
Название
Пример
bool
Логический
true, false
int
Целочисленный
0, 1, 100, -2, 897462
float
Действительный
1.231e22, -34.4332
string
Строковый
hello, world!, foo bar
time
Момент времени
2012-01-01 12:01:01, 1:2:3
list
Списочный
[1, 2, 3], [a, b, c]
range
Разбиение
[0,10,20], [5,25], [1.2,1.5,0.3]
period
Период
[0:0:0, 12:0:0]
\subsection{Логический тип \emph{bool}}
Значения: true, false
Логический тип один из основополагающих типов данных. Он может принимать всего два значения: истина и ложь. Отлично подходит для создания выключателей (или переключателей) дополнительных опций модели.
Присутствует в JSON.
\subsection{Целочисленный тип \emph{int}}
Значения: от -231 до 231-1 (4 байта)
Тип данных для хранения целых чисел со знаком. Пожалуй, еще более основной тип, чем логический. Если вы не собираетесь работать с большими целыми числами (к примеру, рассчитывать госдолг США) то представленного диапазона хватит для большинства задач.
Присутствует в JSON.
\subsection{Действительный тип}
float
Значения: от -1.7*10+308 до 1.7*10+308 (двойная точность, 8 байт)
Тип данных с плавающей точкой предназначен для хранения и обработки действительных чисел.
Присутствует в JSON.
\subsection{Строковый тип \emph{string}}
Строковый тип служит для хранения символов. Отсутствует разделение на строки и символы как во многих языках программирования. Это сделано для того, чтобы упростить работу с данными. Строки можно использовать в качестве комментариев. В качестве нетривиального примера можно привести использование строк вместе с оператором выбора в качестве перечисляемого типа (аналог enum):
string choice [a, b, c]
При записи строк в определении поля данных нужно быть внимательным с использованием кавычек. Кавычки внутри описания поля нужно экранировать символом обратного слеша \\. Лучше это будет видно на примере:
%"field": "string default \"foobar\""
Чтобы не запутаться надо просто помнить, что вы описываете строку в строке.
Присутствует в JSON.
\subsection{Тип момента времени \emph{time}}
Значения: от "1000-01-01 00:00:00" до "9999-12-31 23:59:59"
Является аналогом типа данных timestamp. При описании значений этого типа можно описывать как все поле целиком, так и использовать его части отдельно (время и дату). Отсутствующая часть будет заменена значением по умолчанию. Примеры:
"12:10:00"
"2012-01-01"
"2012-02-03 13:23:43"
Не поддерживается JSON, представляется как строка.
\subsection{Списочный тип \emph{list}}
Списочный тип является единственным составным типом (все остальные типы — скалярные). Это значит, что при описании поля этого типа нужно указать элементы какого скалярного типа содержатся в списке. Синтаксис объявления:
list (scalar)
Сразу хочется отметить, что вложенные списки не поддерживаются.
Списочный тип был введен для возможности передачи переменного числа аргументов и только. Если возникнет необходимость передать многомерный список, это можно сделать, использовав два списка: первый с размерностями и второй со значениями. Но такое на практике встречается крайне редко, а если вы встретитесь с таким случаем, возможно его можно преобразовать, чтобы он стал более простым.
Не рекомендуется употреблять оператор choice со списками. Это вызовет лишь лишнее нагромождение определения, а принесет минимум пользы. Как и в случае многомерных списков, наверняка, есть способ сделать все проще.
Примеры:
"field": "list(int) // простой список целых чисел"
"field": "list(int) default [1, 2, 3] // список целых чисел"
"field": "list(list(int)) // нельзя!"
"field": "list(int) choice [[1, 2], [3, 4]] // нагромождение!"
Присутствует в JSON.
\subsection{Тип разбиения \emph{range}}
Разбиение (диапазон) — нестандартный тип данных. Он основан на идее разбиения отрезка на одинаковые части. Это бывает очень полезно когда надо перебрать значения из некоторого диапазона с некоторым шагом.
Синтаксис записи значения типа выглядит следующим образом:
[[begin,] end [, step]]
Если указан только параметр end, то считается, что диапазон начинается с 0. Шаг по умолчанию равен 1. Если вы используете этот тип данных в описании своих полей, следует определить соответствующую его обработку. Опущенные значения будут подставлены графическим приложением (если это поддерживается).
Не поддерживается JSON, представляется как список из трех значений.
\subsection{Выбор из нескольких вариантовsection}
Оператор сhoice предназначен для выбора одного варианта из предложенного списка. В записи определения поля после него должен идти список возможных значений:
choice [val1, val2, … ]
Примеры:
"field": "int choice [1, 2, 3, 4]"
"field": "string choice [a, b, c]"
Каждое значение в списке должно соответствовать указанному типу поля. Не рекомендуется использовать оператор выбора вместе со списковым типом, хотя это и не возброняется.
Если в списке присутствует только одно значение или список пуст, то фактически выбор становится фиксированным. Не лишайте пользователей выбора.
\subsection{Значение по умолчанию}
Оператор default осуществляет подстановку значения по умолчанию в графическом клиенте. Нельзя переоценить важность этого оператора, так как значение по умолчанию помогает пользователям сориентироваться для выбора нужного им значения. Параметр по умолчанию — это некий средний параметр, который подходит в большинстве случаев и (или) рекомендуется к использованию. Золотое правило работы с такими параметрами: "Если не знаешь что делает этот параметр, используй значение по умолчанию".
Если вместе с оператором default используется оператор choice, то значение по умолчанию должно входить в список вариантов.
Синтаксис:
default value
Примеры:
"field": "int default 10"
"field": "int choice [1, 2, 3] default 2"
"field": "string choice [foo, bar, foobar]
default foobar"
\subsection{Заголовок поля}
Оператор title отвечает за заголовок поля. Текст, указанный как заголовок будет использоваться в графическом интерфейсе в качестве названия поля. Если заголовок не указан, будет использоваться имя поля.
Имя поля не всегда позволяет описать поле так, как хочется. Имя хочется сделать простым, коротким и понятным программисту, в то время как заголовок должен быть полезен пользователю.
\subsection{Комментарий}
Комментарии используются для того, чтобы пояснить для чего нужно описываемое поле. В графическом интерфейсе они проявляются как всплывающие подсказки. Чем яснее будет сформулированы комментарии, тем проще будет пользователю разобраться какие параметры и как нужно описывать для данной задачи.
\section{Задачи, модели и методы}
Покажем возможную структуру описания информационного сообщения и сообщения с параметрами, которое будет передано задаче, на примере, который был описан в первой главе про младшего сотрудника Василия.
Составление информационного сообщения — очень важный процесс при проектировании задачи. От того как описана задача напрямую зависит то, насколько удобно ей будет пользоваться и насколько гибко ей можно управлять.
Обратимся еще раз к тексту условия (страница №). Сперва нужно выделить те параметры, которые напрямую относятся к модели. В нашем случае это будут: энергия, температура в доме, количество работы, которую нужно выполнить. Можно заметить, что кроме этих граничных условий есть еще различные параметры, которые отвечают за изменение главных параметров с течением времени: изменение температуры в час, затраты энергии на выполнение работы, восполнение энергии за счет сна и питания.
Эти параметры можно оставить постоянными, указав их как константы в коде задачи, но более грамотно будет вынести их в секцию метода, чтобы потом можно было сравнить поведение модели в зависимости от их изменения.
Следует заметить, что в модели нет метода по умолчанию. Хотя, если добавить Василию возможность ничего не делать, то это формально можно назвать методом без управления, но проблема в том, что если ничего не делать, задание не будет выполнено, а запас сил Васи вскоре кончится от холода.
Беря во внимание все вышеизложенное, такой результат может вернуть приложение-задача серверу на запрос информации (reltask -i):
Описание задачи состоит из списка моделей, которые включают в себя описание полей данных и методов. Методы тоже, как и модели, могут (и должны) включать в себя описание данных, которые будут использоваться управлением алгоритма.
В описании задачи, каждой модели, каждого метода могут присутствовать мета-секции title, description, data, results. Первые две отвечают за описание названия и описания соответственно. Данные могут быть описаны только в секции data и нигде более. results используется для описание результатов вычислений. Подробнее об этом будет рассказано в следующем параграфе.
\section{Результат вычислений}
\section{Обслуживающие запросы}

61
manual/for_user.tex Normal file
View File

@ -0,0 +1,61 @@
\chapter{Opal для пользователя}
Или как управлять моделями и алгоритмами.
\section{Задачи, модели и методы}
\section{Используемая терминология}
Перед тем как будет подробно описана работа с моделями в программе Opal, необходимо установить используемую терминологию, чтобы случайно не ввести в заблуждение.
Для полного описания задачи оптимизации используется несколько основных понятий.
Задача — отдельное приложение, которое может быть подключено к Opal. Приложение может быть как отдельным исполняемым файлом, так и некоторым скриптом на интерпретируемом языке. Для работы важно, чтобы приложение могло взаимодействовать на уровне стандартных потоков ввода-вывода для того, чтобы ему можно было передавать запросы. Термин <<задача>> используется в силу того, что этот объект очень схож в теми задачами (процессами, tasks), которые используются в операционных системах.
Модель — набор параметров, которые описывают условия для задачи нахождения оптимального управления. Фактически, моделью является набор значений, как то период времени, на котором происходит моделирование ситуации, начальные условия, параметры для функции перехода, ограничения на управление и др.
Метод — функция, которая по заданным начальным параметрам и алгоритму просчитывает решение для данной модели. Для одной модели может быть создано несколько методов ее решения.
Решение модели — как процесс, это работа выбранного метода, т.е. просчитывание по алгоритму модели с целью найти те параметры, которые будут удовлетворять заданным условиям; как результат, это результат работы алгоритма. Терминология будет яснее, если представить ее со стороны решения уравнения, потому как с какой-то стороны модель и является некоторым уравнением или системой уравнений.
В одной задаче может содержаться несколько моделей. Это удобно, когда модели по структуре схожи и можно эффективно использовать исходный код, не разбивая задачу на множество почти одинаковых. Частным случаем решения модели является решение без управления. В самом деле, если никак не управлять процессом, процесс все равно будет протекать, но в какую сторону - это уже другой вопрос.
Организацию всех структур можно увидеть на рисунке:
\section{Работа с моделями}
Модель — основная сущность для Opal. Создав единожды, модель можно отредактировать позже, присоединить или отсоединить от нее методы, создать копию (как поверхностную — только сама модель, — так и полную, включая все методы).
По сравнению с подходом, когда одно приложение решает одну задачу, концепция моделей имеет большое преимущество. Можно настроить несколько моделей на выполнение или выбрать несколько вариантов одной модели, после чего запустить их, а потом, дождавшись результата, проанализировать. Не приходится отвлекаться для того, чтобы запустить новую задачу, после того, как прошлая закончила свою работу.
Для того, чтобы лучше всего разобраться в том, что представляет из себя модель, можно рассмотреть простой пример.
Ответственное задание. Младшего научного сотрудника Василия холодной зимой отправляют в одинокий домик где-то в тайге для того, чтобы он собрал важные научные сведения, а после чего привез результаты обратно в НИИ (гидрологии, экологии, биологии, геологии, в, общем, не важно чего). Избушка находится в столь глухой местности, что связь очень плохая, еду всю нужно везти с собой, отопление дровами, которые еще предстоит нарубить. Благо ходит за ними далеко не нужно. Работу надо сделать быстро, потому что домой хочется вернуться скорее. Главное условие задачи: выполнить установленный объем работ и выжить.
Теперь можно перейти к формальному описанию задачи. Основные ресурсы здесь: энергия (когда Вася спит или ест, она восстанавливается, когда работает или заготавливает дрова — тратится), количество еды, количество выполненных заданий, температура в доме. Все ресурсы представлены целыми числами.
Будем считать единицей измерения времени один час. Вася может делать следующие действия каждый час:
1. поспать (+4 к энергии), можно только с 21:00 и до 9:00;
2. покушать (+6 к энергии);
3. сделать задание (-10 к энергии);
4. затопить печь (-6 к энергии, +5 градусов в доме).
Каждый час температура в доме падает на один градус. Если температура опустится ниже отметки в 15 градусов, то дополнительно будет тратится 2 ед. энергии, потому как Вася будет пытаться согреться. Энергии не может быть больше 100 пунктов.
По ходу дальнейшего описания работы с моделями в системе Opal, будем обращаться к этому примеру для наглядного представления обсуждаемой темы.
\section{Создание новой модели}
\section{Редактирование параметров модели}
\section{Присоединение метода к модели}
\section{Копирование модели}
\section{Удаление моделей и методов}
\section{Запуск решения модели}
\section{Построение графиков}
\section{Составление отчетов}
\section{Гибкое планирование}

9
manual/intro.tex Normal file
View File

@ -0,0 +1,9 @@
\chapter*{Введение}
% Про актуальность решения задач оптимизации,
% какие классы проблем могут они решать, почему решение порой
% требует очень больших вычислительных ресурсов.
Человечество не стоит не месте. Каждый год появляются новые технологии, проводится ножество исследований, появляются новые технологии и материалы. До своего появления на свет новый продукт проходит множество стадий: идея, исследование, опытный образец, многочисленные проверки и, наконец, выпуск в свет.
С появлением компьютеров стадия исследования кардинально изменилась. Теперь не нужно создавать опытный образец, чтобы провести на нем тесты, не нужно вести огромное количество рассчетов на бумаге --- все это заменили компьютеры.

View File

@ -0,0 +1,4 @@
{
"title": "test",
"author": "Anton Vakhrushev"
}

16
manual/opal.sty Normal file
View File

@ -0,0 +1,16 @@
% Подключаемые пакеты
\RequirePackage{amsmath}
\RequirePackage{amsfonts}
\RequirePackage{amssymb}
\RequirePackage{textcomp}
\RequirePackage[T2A]{fontenc}
%\RequirePackage{pscyr}
\RequirePackage[utf8]{inputenc}
\RequirePackage[russian]{babel}
\RequirePackage{indentfirst}
\RequirePackage{array}
\RequirePackage{longtable}
\RequirePackage{geometry}
\usepackage[pdftex]{graphicx}
\RequirePackage[colorlinks=true, linkcolor=blue, citecolor=blue, urlcolor=blue
]{hyperref}

14
manual/opal.tex Normal file
View File

@ -0,0 +1,14 @@
\documentclass[12pt, draft]{extreport}
\usepackage{opal}
\begin{document}
\include{intro}
\include{overview}
\include{for_user}
\include{for_prog}
\include{for_adv}
\include{outro}
\end{document}

3
manual/outro.tex Normal file
View File

@ -0,0 +1,3 @@
\chapter*{Заключение}
Спасибо за внимание!

27
manual/overview.tex Normal file
View File

@ -0,0 +1,27 @@
\chapter{Обзор}
\section{Задача оптимального управления}
В окружающем мире большинство процессов протекают непрерывно с течением времени. Вода не льется из под крана маленькими кубиками, она течет постоянной непрерывной струей, машина не едет <<рывками>>, она плавно едет по извилистой змейке дороги.
Но, тем не менее, почти все непрерывные процессы мы можем представить в качестве дискретных. Струю воды можно считать по каплям, а путь автомобиля по пройденным сантиметрам. Такой подход несколько неудобен для человека, который привык к плавности движений и форм. Однако, для вычислительных машин нет ничего лучше! Компьютеры все меряют отдельными значениями. Но если маленьких отдельных значений очень много, то они становятся похожи на непрерывный набор данных, поэтому, в сущности, какая в конечном счете разница что использовать: непрерывные функции или их дискретные аналоги?
\section{Обзор Opal}
Для рассмотренных ранее задач методов оптимизации в большинстве случаев используются алгоритмы, которые путем полного перебора всех возможных значений на заданной области определения находят нужное оптимальное решение.
Такие алгоритмы отличаются высоким потреблением системных ресурсов: процессорного времени и оперативной памяти компьютера. Яркий пример~--- метод Беллмана. Время расчета с его использованием может достигать от нескольких минут до нескольких дней, а объем памяти для хранения данных доходит до гигабайт.
Традиционным подходом при компьютерном решении является отдельное приложение, нацеленное на решение конкретной задачи. Приложение содержит в себе сам алгоритм, строит графики, выводит отчеты о работе. Даже с использованием уже написанных библиотек, каждое такое приложение невольно становится <<изобретением велосипеда>>, потому как программисту приходится заново выстраивать все компоненты (ввод-вывод параметров, проверка значений, вычислительное ядро, построение графиков и вывод отчетов) в единое целое.
Безусловно такой подход к решению нельзя назвать оптимальным. Время, затраченное на те части, которые не являются кодом самого алгоритма и которые в разных вариациях повторяются из программы в программу, довольно значительно, и иногда составляет больше времени кодирования самого алгоритма.
Назревает вопрос: если в большинстве программ разными являются только сами вычислительные алгоритмы, а все остальное присутствует в почти в неизменном виде, может эти две сущности следует разделить? Разделив, получим универсальный интерфейс, уже готовые подсистемы ввода-вывода, графиков, отчетов, таблиц. Также появится возможность запускать алгоритмы не только локально, но и где-то на другой машине, имея к ним доступ по сети или даже из web.
Выглядит многообещающе, не так ли? Запустить <<прожорливый>> алгоритм на удаленной машине, а потом только получить результаты работы и проанализировать их.
Именно этих целей и придерживается в своей философии проект Opal:
\begin{enumerate}
\item разделение вычислительного ядра и управляющих интерфейсов, когда <<мухи отдельно, котлеты отдельно>>;
\item удобство использования, когда программисту достаточно только реализовать алгоритм, а математику с легкостью им воспользоваться;
\item универсальность представлений, когда управление задачей не зависит от задачи;
\end{enumerate}

1243
opal.py Normal file

File diff suppressed because it is too large Load Diff

2
opal.pyw Normal file
View File

@ -0,0 +1,2 @@
from opal import main
main()

375
server.py Normal file
View File

@ -0,0 +1,375 @@
#-------------------------------------------------------------------------------
# Name: server.py
# Purpose:
#
# Author: Anton Vakhrushev
#
# Created: 14.03.2012
# Copyright: (c) Anton Vakhrushev 2012
# Licence: LGPL
#-------------------------------------------------------------------------------
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import json
import time
import datetime
import threading
import subprocess
import hashlib
import task
globallock = threading.Lock()
def WriteToLog(msg):
with globallock:
tm = str(datetime.datetime.now())
msg = tm + ' ' + str(msg)
#self.log.write(msg + '\n')
print msg
class JIDError(Exception):
def __str__(self):
return 'Invalid jid'
class LocalServer:
def __init__(self, conf = 'tasks.conf', workers = 2):
"""
"""
self.conf = conf # файл с конфигурацией задач
self.workers = workers # количество потоков выполнения
self.tasks_meta = {} # идентификаор задачи
self.models = [] # список моделей
self.next_job_id = 1 # очередной идентификатор работы
self.jobs = {} # очередб работ
self.log = None #
self.running = False #
self.queue_lock = threading.Lock()
# init actions
self.WriteToLog('local server initialized')
def Close(self):
self.Stop()
self.WriteToLog('local server closed\n')
def __del__(self):
self.Close()
def WriteToLog(self, msg):
tm = str(datetime.datetime.now())
msg = tm + ' ' + msg
# self.log.write(msg + '\n')
print msg
def TestTaskData(self, data):
pass
def LoadModels(self):
self.tasks_meta = {}
self.models = []
self.WriteToLog('tasks interrogation starts')
for line in open(self.conf, 'r'):
try:
# нормализуем указанный путь
line = line.strip()
if not line:
continue
line = os.path.normpath(line)
line = os.path.abspath(line)
# считываем данные через shell (важно для скриптовых языков)
textdata = subprocess.check_output([line, '-i'], shell = True,
cwd = os.path.dirname(line))
# загружаем данные описания задачи
data = json.loads(textdata)
# провряем их на корректность
self.TestTaskData(data)
# вычисляем псевдоуникальный идентификатор модели
tid = hashlib.md5(data['meta']).hexdigest()
# сохраняем описание задачи
self.tasks_meta[tid] = {
'title': data.get('title', ''),
'author': data.get('author', ''),
'meta': data['meta'],
'exec': line,
'models': []
}
# выделяем описания моделей
ms = data.get('models', {})
for label, data in ms.iteritems():
model_descr = task.DataDescription(None, label, data, tid)
# добавляем в список описаний
self.models.append(model_descr)
self.tasks_meta[tid]['models'].append(model_descr)
self.WriteToLog('Task from "{}" asked'.format(line))
except IOError, e:
self.WriteToLog('file "{}" not found'.format(line))
except subprocess.CalledProcessError, e:
self.WriteToLog('file "{}" not opened, error {} (msg: {})'.format(line, e, e.output))
except ValueError, e:
self.WriteToLog('file "{}" not opened, error "{}")'.format(line, e))
def GetModels(self):
return self.models
def GetTaskMeta(self, tid):
return self.tasks_meta.get(tid)
def CheckModel(self, tid, model_label):
models = self.tasks_meta[tid]['models']
for model in models:
if model_label == model.GetLabel():
return model
return None
#--------------------------------------------------------------------------
def CreateJob(self):
jid = self.next_job_id
self.next_job_id += 1
with self.queue_lock:
self.jobs[jid] = Job()
return jid
def GetJobsCount(self):
return len(self.jobs)
def GetJobState(self, jid):
job = self.jobs.get(jid)
if job:
return job.GetState()
def IsJobChanged(self, jid):
job = self.jobs.get(jid)
return job.IsChanged() if job else False
def GetJobResult(self, jid):
job = self.jobs.get(jid)
return job.GetResult() if job else None
def GetJobTID(self, jid):
job = self.jobs.get(jid)
return job.tid if job else None
def LaunchJob(self, jid, data_def):
job = self.jobs.get(jid)
if job:
tid = data_def.DD.tid
datadump = data_def.PackParams()
job.Launch(tid, datadump)
return True
def StopJob(self, jid):
job = self.jobs.get(jid)
if job:
job.Stop()
def DeleteJob(self, jid):
job = self.jobs.get(jid)
if job:
job.Stop()
del self.jobs[jid]
#--------------------------------------------------------------------------
def Start(self):
self.running = True
for i in xrange(self.workers):
worker = Worker(self)
worker.start()
def Stop(self):
self.running = False
#-------------------------------------------------------------------------------
class Worker(threading.Thread):
number = 0
def __init__(self, server):
threading.Thread.__init__(self)
self.server = server
self.daemon = True
self.id = Worker.number
Worker.number += 1
WriteToLog('worker started')
def FindNextJob(self):
with self.server.queue_lock:
for jid, job in self.server.jobs.iteritems():
# если нашли ожидающую вызова работу
if job.state == JOB_READY:
job.state = JOB_RUNNING # пометим, как запущенную
WriteToLog('Job ({}) found'.format(jid))
return job
return None
def ProcessJob(self, job):
try:
execpath = self.server.GetTaskMeta(job.tid)['exec']
# запускаем процесс на выполнение
proc = subprocess.Popen([execpath, '-r'], shell = True,
stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.STDOUT, cwd = os.path.dirname(execpath))
job.proc = proc
# передаем стартовые параметры
proc.stdin.write(job.datadump + '\n')
proc.stdin.flush()
# пока процесс не завершится (или его не прибьют)
while proc.poll() == None:
# читаем и обрабатываем сообщение
msg = proc.stdout.readline()
self.ProcessMessage(job, msg)
# сервер был остановлен, завершаем выполнение всех работ
if not self.server.running:
proc.kill()
except Exception, e:
# любая нестандартная исключительная ситуация
# приводит к немедленному завершанию работы
WriteToLog('Job loop failed: ' + str(e))
job.Finish(JOB_STOPPED)
else:
# только если работа уже не была остановлена
if job.state != JOB_STOPPED:
job.Finish(JOB_COMPLETED, 1.0)
def ProcessMessage(self, job, msg):
try:
# разбираем полученный ответ
data = json.loads(msg)
# извлекаем оттуда ответ
ans = data['answer']
# ответ получен ок или предупреждение
# записываем значение прогресса, если имеется
if ans == 'ok' or ans == 'warning':
job.percent = data.get('value', 0.0)
# в ответе пришел результат вычислений
# помещаем в секцию результата
elif ans == 'result':
job.result = task.ResultData(data['result'])
# произошла ошибка
elif ans == 'error':
# произошла серьезная ошибка
# завршаем выполнение работы
WriteToLog('Error! ' + msg)
raise RuntimeError, msg
# недокументированный ответ приложения
else:
pass
# возможно, комментарий прольет свет на проблему
job.comment = data.get('comment', '')
# почему изменяем флаг состояния здесь в конце?
# потому как только после правильной обработки сообщения
# мы можем быть уверены, что состояние действительно изменилось
job.ChangeState()
except KeyError as e:
pass
except ValueError as e:
pass
def Cycle(self):
# найти следующее готовое к выполнению задание
job = self.FindNextJob()
# и, если нашли, приступаем к выполнению
if job:
WriteToLog("{} started!".format(self.id))
self.ProcessJob(job)
WriteToLog("{} finished!".format(self.id))
else:
time.sleep(1)
def run(self):
while True:
if not self.server.running:
return
self.Cycle()
#-------------------------------------------------------------------------------
JOB_READY = 0
JOB_RUNNING = 1
JOB_STOPPED = 2
JOB_COMPLETED = 3
class Job:
def __init__(self):
self.tid = None
self.datadump = None
self.state = JOB_STOPPED # состояние выполнения работы
self.percent = -1.0 # прогресс (от 0.0 до 1.0 или -1.0)
self.comment = '' # комментарий к ходу выполнения
self.result = None # результат вычислений
self.proc = None # ссылка на субпроцесс
self.state_id = 0
self.last_state_id = 0
def ChangeState(self):
self.state_id += 1
def GetState(self):
self.last_state_id = self.state_id
return (self.state, self.percent, self.comment)
def IsChanged(self):
return self.state_id != self.last_state_id
def Launch(self, tid, datadump):
self.tid = tid
self.datadump = datadump
self.state = JOB_READY
self.percent = -1.0
self.ChangeState()
def Stop(self):
if self.proc and self.proc.poll() == None:
WriteToLog('Try to kill')
self.proc.kill()
self.Finish(JOB_STOPPED)
WriteToLog('Job killed')
def Finish(self, state, percent = None):
self.proc = None
self.state = state
if percent:
self.percent = percent
self.ChangeState()
def GetResult(self):
return self.result
#-------------------------------------------------------------------------------
import random
from pprint import pprint
def main():
s = LocalServer(workers = 2)
s.LoadModels()
s.Start()
models = s.GetModels()
model = models[0]
md = task.DataDefinition(model)
md['d'] = 10
md['r'] = 3.14
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(5)
for jid in slots:
pprint(s.GetJobResult(jid))
print ''
if __name__ == '__main__':
main()

18
setup.py Normal file
View File

@ -0,0 +1,18 @@
from distutils.core import setup
import py2exe
excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
'Tkconstants', 'Tkinter']
setup(
name = 'Opal',
windows = ['opal.pyw'],
options = { "py2exe": {
"compressed": 2,
"optimize": 2,
"bundle_files": 1,
"excludes": excludes,
} },
zipfile = None,
)

BIN
share/app-about.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

BIN
share/cursor-closedhand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

BIN
share/cursor-openhand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

BIN
share/model-add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

BIN
share/model-cancel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

BIN
share/model-complete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

BIN
share/model-delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

BIN
share/model-dup-tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

BIN
share/model-dup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

BIN
share/model-go.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

BIN
share/model-no-exec.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

BIN
share/model-ready.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

BIN
share/model-run.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

BIN
share/model-stop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

BIN
share/opal_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
share/plot-histogram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

BIN
share/plot-line.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

BIN
share/plot-marker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

BIN
share/plot-org.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

BIN
share/report-show.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

BIN
share/show-plot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

228
task.py Normal file
View File

@ -0,0 +1,228 @@
#-------------------------------------------------------------------------------
# Name: task.py
# Purpose:
#
# Author: Anton Vakhrushev
#
# Created: 14.03.2012
# Copyright: (c) Anton Vakhrushev 2012
# Licence: LGPL
#-------------------------------------------------------------------------------
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import copy
import json
#-------------------------------------------------------------------------------
class Parameter:
def __init__(self, label, data):
self.data = data
self.data['label'] = label
def GetLabel(self):
return self.data['label']
def GetType(self):
return self.data['type']
def GetTitle(self):
return self.data.get('title', self.GetLabel())
def GetComment(self):
return self.data.get('comment', '')
def GetDefault(self):
return self.data.get('default')
def GetTestExpression(self):
return self.data.get('test')
def Test(self, value):
return True
def DumpData(self):
"""
Возвращает данные в стандартных контейнерах
READ ONLY!!!
"""
return self.data
def LoadData(self, data):
self.data = data
class Value(Parameter):
def __init__(self, label, value):
if isinstance(value, dict):
self.data = value
else:
self.data = {
'value': value,
'type': value.__class__.__name__
}
self.data['label'] = label
def GetType(self):
return self.data.get('type', 'unknown')
def GetValue(self):
return self.data['value']
class Column(Parameter):
def __init__(self, colvalues):
self.data = {}
# следующие два поля должны обязательно присутствовать
self.data['label'] = colvalues[0]
self.data['type'] = colvalues[1]
try:
self.data['title'] = colvalues[2]
except:
pass
def DumpData(self):
return [
self.GetLabel(),
self.GetType(),
self.GetTitle(),
]
#-------------------------------------------------------------------------------
class DataDescription:
def __init__(self, parent, label, data, tid):
self.parent = parent
self.label = label
self.data = data
self.tid = tid
# создание описаний параметров
self.pdata = self.data.get('params', {})
# заменяем текстовое описание на объект-параметр
for label in self.pdata:
par = Parameter(label, self.pdata[label])
self.pdata[label] = par
# рекурсивное создание описаний спецификаций
self.specs = { label: DataDescription(self, label, data, self.tid)
for label, data in self.data.get('spec', {}).iteritems() }
# for label, data in self.data.get('spec', {}).iteritems():
# self.specs.append(DataDescription(self, label, data, self.tid))
def GetLabel(self):
return self.label
def GetTitle(self):
return self.data.get('title', self.label)
def GetAuthor(self):
return self.data.get('author', 'Unknown')
def GetId(self):
return None
def GetParent(self):
return self.parent
def GetSpecs(self):
return self.specs
def IsExecutable(self):
return self.data.get('exec', True)
def GetImage(self):
return self.data.get('img')
def GetTaskId(self):
return self.tid
def __getitem__(self, label):
return self.pdata.get(label)
#-------------------------------------------------------------------------------
class DataDefinition:
def __init__(self, datadescr, parent = None):
self.DD = datadescr
self.parent = parent
self.params = {}
for param in self.DD.pdata:
self.params[param] = self.DD[param].GetDefault()
self.job = None
def __getitem__(self, label):
return self.params[label]
def __setitem__(self, label, value):
if self.DD[label].Test(value):
self.params[label] = value
else:
raise ValueError
def Copy(self):
res = copy.copy(self)
res.params = copy.copy(self.params)
res.job = None
return res
def PackParams(self):
package = []
owner = self
while owner:
data = {'label': owner.DD.GetLabel(), 'params': owner.params}
package.append(data)
owner = owner.parent
package.reverse()
return json.dumps(package)
#-------------------------------------------------------------------------------
class ResultData:
def __init__(self, data):
self.LoadData(data)
def GetColumns(self):
return self.head
columns = property(GetColumns)
def GetRows(self):
return self.table
rows = property(GetRows)
def GetCell(self, row, col):
return self.table[row][col]
def GetColumn(self, index):
return [ row[index] for row in self.rows ]
def Zip(self, col1, col2):
return [ (row[col1], row[col2]) for row in self.rows ]
def DumpData(self):
data = {}
if self.data:
data['data'] = { key: self.data[key].DumpData()
for key in self.data }
if self.head:
head = [ col.DumpData() for col in self.columns ]
body = self.table
data['table'] = [head] + body
return data
def LoadData(self, data):
self.data = {}
for key, value in data.get('data', {}).iteritems():
self.data[key] = Value(key, value)
table = data.get('table', [])
self.head = []
self.table = []
if table:
self.head = [ Column(item) for item in table[0] ]
self.table = table[1:]

2
tasks.conf Normal file
View File

@ -0,0 +1,2 @@
tasks/testt.exe
tasks/sandbox.exe

13
tasks/setup.py Normal file
View File

@ -0,0 +1,13 @@
from distutils.core import setup
import py2exe
setup(
name = 'testt',
console = ['testt.py'],
options = { "py2exe": {
"compressed": 2,
"optimize": 2,
"bundle_files": 1,
} },
zipfile = None,
)

59
tasks/testt.json Normal file
View File

@ -0,0 +1,59 @@
{
"title": "Example task",
"author": "Anton Vakhrushev",
"meta": "av-example-task-version-00-10",
"models": {
"sintaylor": {
"title": "Sin Taylor",
"author": "Anton Vakhrushev",
"date": "2012-03-08",
"exec": true,
"img": "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABuBJREFUeNqsV2tsFFUU/mZ2Zh9tdy2vUldpKe83thVbIColGGiB6A9BAgn+Ef2hIn8MCIn+0QDRGE2Mj2jUaFDKI4A8WnlIQZFCpShQKS2lwPbdAtvuY967njs7C6UPtlWnOb13Z+6c851zvnPuHUSjUcTlf7jsc7fmM0X2HvNeV9wmj//3Ek+tLx81Z3OeQvNUy6nkB4HhunvOcVwi7xQy4KC52uM35mzJUwK+4OKkh5O2J7uT3F3+ANPnD1ztenHY1GH7GDBadockFI8Au/hBe7fF9I55ktzNQ0fHpVvPzyyYfFBRVPfK1UvwymsrULDgidThZLytsn0ZrdEsGVQE4l46c9/KXsHb+XWcgMfib7DVET1aqdyW3x/zWOZnhUufSm263Y5dX5fgpdeX4+tPd6Fo6dM4fvRMp2gXHmL64xGM200EgHk3JO/dWb6hwzwYOy4D6d4RiEZi1nla39TUitrq65g2YwKEVBGGEUGq2w3BZkNIlpCZ7sUXn2zHH29X5tJbfpJWloZEKbhLmrz3ZvmmzZyAvDkzATeP4u8Pod7fgGt3fNixrQSa08Aseub2pCAohXG4+FcEwkH8+OV+7P3uCCUbMQGCFge0gaTgrufMeOXZKuQumgaBt0G0ieBtPI7vPI3Zz2YjLMtQNBXVx+uwZFUB6lsb4LI7KUAcREHE9NHj8c3nu0N2l5j8+4YzcQL3S8K45/z0V6cWDR3qwaMZI5G9cArO7v8LyQ6XKW5HEgpXPg2n4IBTdKD2RD0ycrzwpqbBOyQNFQcvwOV0YnJGFn47eQ7tFR3vkPEZjE89S7FnBEzPiek+Nayfz3tyRrbuisCgv5SkZDLuRBJ59/3n+7BizRKElTBCYQlBVUJADkH1a5j8SBaoDKFICqouXpXP7760wVfaUEp6JcbZeCn2FwGWH5XQTudFZD/sTSPlIdMjG8djz7dHIFBYX37jBTMdB7eVEWjeDPeNXxvQdLEFnlQ39uw8gs35nxT+tKZ00SNPeT8inW0W+XpxQOgBQLWYmhTDF0XV0Ro8vngmdF3Hc6sXmPnWNR6arqFoZQGC4RBVRQSZc0ehSwqYYATRVFtPEi7feDaTRp1EseS+qy8OKJSCess+Rj+ZQcZUqIYOWVNQ/NUBhCjku7/9mcAo0AwNOpWeQc9ZCTIa8zyH2Zvzqml6m+RWX4b7A8C63VhKQT5ntZqIYUAlb8v3VyKsKih4fjaVWQjzluVDVhR6puNKWR3UiE6GbWYlsXdPv3VmhqUzFGf+QACw/ARnb80v1ySjqqWlDalOD2rL6jFx/lgqOckkXUgO4/e95yARIFmVkZ6fho6KDqS7h6CpuRX07t+k48JA+nuvRpT/QX7r6fXl0/31geLaOh8yRngx/InhCCoSqn6pRUBjjJcwZl4Wqk/UUToUSMQLT84QZI3MAHuH3t3OdJCuYH/b8QPL0AKWMundnAM5j0/KSn90OCpvXjbv2uhflIvv55R7IiDP2ZCbMQWtDR04d+5yffWmyiVW54t03wF7ngf6AsDQujY+M/EHLhIpIoYhSsSCeXDg2QJw9xbfr5Axlj0X6L5oI8vcofcOV6+06l/tD0BfZeiIctGitZvehCZTyekGETFCQGgkb1nJsd/35oY5GnrsublTkfI9Bw8UWRFQH5QCoY97TvZWoO0m2mv+NBXrzLgJhHqipptj/Lf5TGNzjcqQzTWMGJ9tponpYqoGC4DjeWo0/maIDjuVlgGeKebIOJtT5zMBcASE1tFicpOEj21mEYpsqPmauc46MmCwACJhzSj7bs9v85h3UXLF3CUtMQdwlurY73t8MPdA2EI6VY1eZqUAg6kCNrhJxqybP27HgsKFE5S2G1BCXZTfaCznkTgfoiQxkpr3KDU2lxueUeNwpqKi5sOjNctJz7X+UtAfCWGxtjUg66dcojAha/I43Gmoi5GMuh4bDTayvJNRnbokdWEYXBSe9DSEKQYBWTtlbT7SoBuRtXEEGjvlkrraGvCiy+zxBnncS8gLlvOYRCE4XWhs8qHBL5dYnuv/BoBZjqVVLRU3rjdQ2DU6AQl3z1XoO57gBQERVUW7vxMll5orEpVfIgAMuSTJcm1LYzMEuyMhoQU6Kd3u9CMsybVW6PX/AoC5qwYU4yTbXDhqLt0KwaqEbsL+ETFvkfddsnbS8j76XwCYaWjqlI81ttyCyEVgF+hAyoQOpEwEU+jwwceEMxT4JQ2NfunYQMOf6LtAJPGunT9+W4pDmHt3mbWGs/pAvB8wTxj7Pz52ZRVNm/r6ChrIZnQfOBIPCfumG9ZPyfbkDTv9+Ei6EqVgoF9GzKjL6umJ2ipTJA+UgHG7/wgwAGCxyeIw5AiUAAAAAElFTkSuQmCC",
"params": {
"r": {
"type": "float",
"default": 6.283185307,
"title": "Right edge",
"comment": "Right edge"
},
"d": {
"type": "int",
"default": 5,
"title": "Serie deep",
"comment": "Number of members in taylor serie"
},
"n": {
"type": "int",
"default": 20,
"title": "Steps",
"comment": "Number of steps for algorithm"
}
},
"spec": {
"left": {
"title": "Left rectangles"
},
"right": {
"title": "Right rectangles"
},
"trapezium": {
"title": "Trapezium"
}
}
}
}
}

128
tasks/testt.py Normal file
View File

@ -0,0 +1,128 @@
#! coding: utf-8
# Тестовое приложение для проекта Opal
# Вычисление значений синуса по формулам Тейлора
# Вычисляет значения для указанного диапазона
# с заданной точностью и нужным количеством шагов
import sys
import json
import time
def write(msg):
sys.stdout.write(str(msg) + '\n')
sys.stdout.flush()
def sin_taylor(x, n):
f = 1
s = 0.0
e = 1.0
x0 = x
for i in xrange(n + 1):
#print e, f, x
f *= (2 * i) * (2 * i + 1) if i else 1
s += e * x / f
x *= x0 * x0
e *= -1
return s
def answer(p, c = ''):
return json.dumps({
"answer": "ok",
"value": p,
"comment": c
})
def error(msg):
return json.dumps({
"answer": "error",
"comment": msg
})
def result(s, t):
return json.dumps({
"answer": "result",
"result": {
"data": s,
"table": t
}})
def serie(n, d, h, l = 0):
for i in xrange(n + 1):
y = sin_taylor(l, d)
yield (l, y)
l += h
# assert 0
time.sleep(0.002)
def main():
try:
if sys.argv[1] == '-i':
with open('testt.json') as f:
d = json.load(f)
write(json.dumps(d, indent = 2))
elif sys.argv[1] == '-r':
textdata = raw_input()
data = json.loads(textdata)
params = data[0]['params']
r = params['r'] # правая граница
n = params['n'] # количество шагов
d = params['d'] # количество членов в разложении Тейлора
h = r / n
res = [] # таблица резултатов
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', 'Delta sum' ] ]] + 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, 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))
except Exception, e:
write(error('Fatal error: ' + str(e)))
sys.exit(1)
if __name__ == '__main__':
main()