Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Диплом Voldem@r / Оно / ПЗ_release.doc
Скачиваний:
49
Добавлен:
16.04.2013
Размер:
1.6 Mб
Скачать

1.2.7. Переменные в выражениях и их использование в программе

Основной упор при разработке программы делался на возможность использования её в как можно большем числе предметных областей, пусть даже никак не связанных между собой. Незнание особенностей предметной области, в которой будет применяться данный комплекс, привело к мысли унифицировать описание критериев, по которым проводится оптимизация, задание функции приспособленности системы. Для этого было принято решение активно использовать в модели системы формульные зависимости, причём эти зависимости не имеют какой-то жёсткой наперёд заданной формы, а подчиняются правилам записи обычных математических выражений с соблюдением приоритетов операций. Некоторой особенностью является то, что эти выражение могут содержать в себе, помимо прочего, имена переменных, которые задаёт пользователь на этапе декомпозиции и описания модели системы.

Для обеспечения большей простоты обработки выражений, имена переменных содержат специальную метку вначале, которая указывает на тип элементарного блока модели, которому соответствует данная переменная. Так, имена переменных, соответствующих подсистемам модели, начинаются с символа ‘#’, а имена переменных, относящихся к компонентам (или критериям оптимизации), – с символа ‘$’. Кроме того, имена различных функций, которые могут использоваться в выражениях (например, sin, log, max), начинаются с символа ‘%’. Эти ограничения на имена переменных и функций не затрудняют работу пользователя с системой (скорее, наоборот, ведь человек сам по одному только символу понимает, о каком элементарном блоке модели идёт речь). Вместе с тем, эти ограничения существенно повышают производительность системы. Во-первых, при синтаксическом разборе выражения при встрече одного из вышеуказанных символов состояние автомата, осуществляющего разбор, становится детерминированным. Во-вторых, ускоряется поиск в таблице имён, ведь переменные каждого типа хранятся отдельно, т.е. поиск осуществляется в списке меньшего размера.

Правила записи имён переменных и функций совпадают с правилами, действующими в языке программирования C++: имя переменной или функции должно начинаться с литерала, которыми являются символы английского алфавита, или символа подчёркивания (‘_’) и может содержать любое число литералов, цифр и символов подчёркивания. При этом регистр букв имеет значение, т.е., например, имена $var1 и $vAr1 будут соответствовать разным компонентам системы. Правило записи имён в РБНФ [11] (расширенная форма Бэкуса-Наура) имеет следующий вид:

name ::= [$ | # | %][a-zA-Z_]+[a-zA-Z0-9_]*

1.2.8. Основные алгоритмы и особенности программной реализации

В следующих параграфах я хочу рассказать о некоторых алгоритмах, реализованных в программе. Они не являются основными, но важны и интересны.

Алгоритм прохода по дереву модели

Данный алгоритм осуществляет проверку дерева модели. Сюда входит поиск и удаление «лишних» переменных (т.е. таких переменных, от которых ничего не зависит), обнаружение использования в формулах ссылок на необъявленные переменные, поиск циклических зависимостей в значениях переменных, проверка корректности математических выражений.

В процессе работы алгоритма последовательно выбираются все подсистемы модели. Для каждой подсистемы проверка выполняется независимо от остальных подсистем. Однако областью видимости любой переменной является вся модель целиком, поэтому каждая переменная должна иметь уникальное имя. Такая проверка осуществляется в процессе обхода дерева. Уникальность имён необходима для упрощения организации сохранения и загрузки модели.

В зависимости от ошибки в модели, обнаруженной при её анализе, функция возвращает разные значения:

  • S_OK – ошибок нет, проверка прошла успешно;

  • S_EX – имя переменной, соответствующее просматриваемой в данный момент подсистеме или компоненте, уже существует в модели;

Рис 1.11 Схема алгоритма прохода по дереву модели

  • S_ERR – неправильная запись для функции приспособленности;

  • S_UN – в функции приспособленности текущей подсистемы есть имя переменной, которое не соответствует именам переменных для компонентов, непосредственно связанных с данной подсистемой;

  • S_CY – существует, как минимум, один цикл в зависимостях переменных для компонентов текущей подсистемы.

Если среди имён компонентов есть те, которые не входят в функцию приспособленности подсистемы, то они исключаются из рассмотрения. Данная ситуация не является ошибочной, и пользователь о ней не уведомляется.

Алгоритм разбора и проверки правильности выражения

В данном параграфе будет рассмотрен алгоритм, по которому работает блок проверки математических выражений, а точнее та его часть, которая отвечает за проверку корректности выражений.

В основе алгоритма лежит следующая таблица, в соответствии с которой просматривается строка выражения [11]:

Таблица 1.1

Текущий токен

Предыдущий токен

$component

Operator

#subsystem

(

%function

,

Number

Operator

$component

)

#subsystem

,

)

Number

(

Operator

(

,

%function

Здесь:

  • operator – знак операции, т.е. ‘+’, ‘-‘, ‘*’, ‘/’;

  • $component – переменная, соответствующая компоненте;

  • #subsystem – переменная, соответствующая подсистеме;

  • %function – имя функции;

  • number – число.

Работа с таблицей проста: выражение считается правильным, если при просматривании его слева направо каждому текущему токену предшествовал какой-нибудь соответствующий ему токен из таблицы. Так, например, если текущий токен – number, то предшествовать ему могли только operator, (, ‘,’. В случае нарушения последовательности токенов выражение считается неверным и функция проверки возвращает соответствующий код ошибки.

Ниже приведён общий вид программной реализация данной функции. Остановлюсь на некоторых местах подробнее.

#define MAX_F_COUNT 11

int Check(CString & sExpression)

{

//1

CString sTmp = sExpression;

if (sTmp[0] == '+') sTmp[0] = ‘ ‘;

sTmp.Delete(" "); sTmp.Replace("(+", "("); sTmp.Replace("(-", "(0-");

if (sTmp[0] == '-') sTmp.Insert(0, "0");

//2

int sz = sizeof(inf) / sizeof(SPInfo);

CLexer lxr(LX_VAR_COMP, sz, inf, consts);

//3

int iFCount = -1; //число вложенных функций - 1

int iFArgCount[MAX_F_COUNT]; //число аргументов у функции

int iFBrCount[MAX_F_COUNT]; //число открывающих скобок внутри функции

//(если есть вложенная ф-ия, переходим к ней)

int iExprBrCount = 0;

char * p1 = (LPCTSTR)sTmp, * p2 = p1;

char sToken[1024];

int t;

int iLastToken = LX_VAR_COMP - 1;

//4

while (p1[0])

{

t = lxr.Lex(&p1);

if (t < LX_VAR_COMP) return -1;

for(int l = p1 - p2; p1 != p2; sToken[l - p1 + p2] = p2[0], p2++);

switch (t)

{

case LX_VAR_COMP: case LX_VAR_SYS: case LX_NUM: case LX_FUNC:

if (iLastToken == LX_VAR_COMP - 1)

{ iLastToken = t; break; }

if (t == LX_VAR_COMP || t == LX_VAR_SYS)

if (!VariableExist(sToken)) return ERR_VAR_NOT_SPEC;

else if (t == LX_FUNC)

if (++iFCount == MAX_F_COUNT) return ERR_FUN_COUNT;

if ((iLastToken != LX_OPS) &&

(iLastToken != LX_OPENBR) &&

(iLastToken != LX_SEP)) return ERR_SEQ;

iLastToken = t;

break;

case LX_OPS:

// здесь и далее реализация аналогична предыдущему случаю в // соответствии с таблицей

//…..

break;

case LX_OPENBR:

// …..

break;

case LX_CLOSEBR:

// ……

break;

case LX_SEP:

// ……

break;

default: return ERR_UNKNOWN_TOKEN;

}

}

//5

if (iFCount != -1) return ERR_FUNC_POS;

if (iExprBrCount) return ERR_BRS;

//6

sExpression = sTmp;

return 0;

}

В самом начале работы функции (1) происходит некоторая предварительная обработка выражения – удаляются все пробелы, выражения вида “(+” приводятся к виду “(“, выражения вида “(-” – к виду “(0-” и т.д. Затем (2) создаётся и инициализируется объект класса CLexer. Его задача – выбирать следующий токен из строки. В (4) задаются некоторые начальные значения. Дальнейшая работа заключается в получении очередного токена и сравнении его с предыдущим в соответствии с описанной выше таблицей. При исчерпании всех токенов производится ещё две проверки (5): не должно остаться лишних вложенных вызовов функций и количество открывающих скобок должно равняться количеству открываемых. Если проверка выражения прошла успешно, то нормализованная строка с выражением переписывается во входной параметр sExpression.

Алгоритм обнаружения циклов в зависимостях переменных

Данный алгоритм позволяет обнаружить наличие циклов в зависимостях переменных, например, что переменная $a зависит от переменной $b, а переменная $b – от переменной $a. Такая ситуация является ошибочной, т.к. невозможно вычислить значения переменных $a и $b.

В основе алгоритма лежит представление зависимостей переменных в виде ориентированного графа [3].

На рис. 1.12 показан пример такого графа. Направление стрелки показывает, какая переменная от какой зависит. На рисунке переменная $a зависит от переменных $b и $c. Легко видеть, что в зависимостях переменных $a, $d и $c есть цикл, а в зависимостях переменных $a, $b и $c цикла нет.

Граф кодируется с помощью модифицированной матрицы связности. Размер матрицы NxN, где N – количество переменных. Для графа на рис. 1.12 матрица связности будет иметь следующий вид:

$a

$b

$c

$d

$a

0

1

1

0

$b

0

0

0

0

$c

0

1

0

1

$d

1

0

0

0

Модификация матрицы связности заключается в том, что в ней содержатся сведения только о прямых путях в графе, а обратное направление кодируется 0. Это облегчает поиск циклов.

Как по такой матрице определить наличие циклов? Для этого нужно для каждого сочетания переменных составить подматрицу. Если такая подматрица содержит ровно по одной ‘1’в каждой строке и в каждом столбце, то переменные, которым соответствует эта подматрица, образуют циклические зависимости. В случае из примера получаем:

$a

$c

$d

$a

0

1

0

$c

0

0

1

$d

1

0

0

При программной реализации составлять подматрицы нет необходимости, нужно на каждом шаге итерации формировать вектор индексов и просматривать столбцы и строки в соответствии с данным вектором.

Рис 1.13 Алгоритм поиска циклов в зависимостях переменных

Соседние файлы в папке Оно