Дипломная (вкр): Программный комплекс моделирования релейно-контактных схем

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

4. СТРУКТУРА ДАННЫХ ПРОГРАММНОГО КОМПЛЕКСА

Язык Lua, который используется для написания данной программы, имеет относительно долгую историю. Lua (от португальского “луна”) - облегченный скриптовый язык c расширяемой семантикой.был создан и поддерживается представителями Pontifical Catholic University Рио-де-Жанейро. У него нет официального стандарта, и стандартом считается описание в руководстве пользователя.

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

синтаксис структур управления логикой программы - из Modul;

семантику более поздних версий - из Scheme;

концепцию локальных переменных - из C++;

концепцию наличия единственной встроенной структуры данных, используемой несколькими способами - из Lisp;

использование ассоциативных массивов - из SNOBOL;

множественные присвоения и возвраты из функций - из CLU etc.

Основополагающим принципом Lua является расширяемость семантики, т.е. предоставление мета-механизмов для реализации переменного набора инструментов вместо предоставления фиксированного набора инструментов. Это позволяет языку быть небольшим и простым, в то же время сохраняя мощность. Таким образом, Lua можно считать мультипарадигменным языком, поскольку он позволяет вести разработку в различных стилях. Lua поддерживает логические, числовые (по умолчанию - числа с плавающей точкой двойной точности) и строковые атомарные типы данных. Единственным “родным” сложным типом данных является таблица - гетерогенный ассоциативный массив, позволяющий использовать разные типы данных для разных пар ключей и значений. Функции являются объектами первого класса, то есть ими можно манипулировать точно так же, как переменными, передавать и получать как аргументы и так далее [8].

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

Например, пустая таблица a={}. До того, как в нее что-либо не будет добавлено, это будет просто пустая таблица, то есть будет зарезервирована переменная с именем a. Если в нее добавить какие-нибудь значения, командой table.insert(a, [pos], data), то, соответственно, в таблице будут храниться уже эти значения на своих местах. В этой функции (table.insert()) a имя таблицы, в которую будет добавлено значение. Необязательный параметр [pos] - позиция в таблице, на которую будет добавлено значение. По умолчанию это значение начинается с первого и каждая последующая позиция увеличивается на единицу.

Допустим, в этой таблице хранятся числа: 1, 3, 5, 7, тогда она будет выглядеть следующим образом - a={1, 3, 5, 7}. Уже сейчас сформировались пары индекс-значение, как в таблице 4.1.

Таблица 4.1 - Графическое представление таблицы a={}

Индекс

Значение

1

1

2

3

3

5

4

7


Только в данном случае индекс не отображается в самой таблице.

Можно внести сразу переменные со значениями, даже если значение будет nil, то есть пустое. Тогда, a1={x=1, y=2, z=3}, пары индекс-значение теперь будут такими, как в таблице 4.2.

Таблица 4.2 - Графическое представление таблицы a1={}

Индекс (поле)

Значение

x

1

y

2

z

3


Можно хранить таблицы в таблице вместе с другими типами данных: a2={{2, 4, 6, 8}, {1, 3, 5, 7}, number=12, name=”name”, 5}.

В данном случае пары индекс-значение будут, как в таблице 4.3.

Таблица 4.3 - Графическое представление таблицы a2={}

Индекс (поле)Значение


1

{2, 4, 6, 8}

2

{1, 3, 5, 7}

number

12

name

“name”

3

5


При этом таблицы по первому и второму индексам также имеют свои пары индекс-значение, как в таблицах 4.4 и 4.5. Для данного случая есть свое обозначение в коде: первая таблица, находящаяся в таблице a2, называется по первому индексу и носит название a2[1]={}, вторая, соответственно - a2[2]={}.

Таблица 4.4 - Графическое представление таблицы a2[1]={}

Индекс

Значение

1

2

2

4

3

6

4

8

Таблица 4.5 - Графическое представление таблицы a2[2]={}

Индекс

Значение

1

2

3

3

5

4

7


Все данные в таких таблицах доступны в любом месте кода, где происходит обращение к ним. Можно получить длину таблицы командой #a (a - имя таблицы) и использовать в цикле for i=1, #a do … end, только нужно учитывать, что длинной в данном случае будет являться номер индекса, поэтому длину таблицы, например b={val=1, val1=3, val2=”string”, val3=function()} получить не удастся, так как при таких обозначениях индексом будет являться имя переменной. То есть длину таблицы можно получить лишь в том случае, если есть значения без указанного индекса, например a={1, 3, 5, 7} - длина таблицы будет равна четырем.

Cуществует возможность получения значения прямо по индексу таблицы. То есть, если использовать такой способ, то не нужно использовать циклы или подобные конструкции, а можно просто использовать команду вывода (print()) по нужному индексу. Например, в таблице a2 по индексу number хранится число 12, чтобы вывести это число на экран, можно использовать функцию print() следующим образом: print(a2.number) - на экран выведется число 12. Чтобы вывести значение по неименованному индексу, нужно использовать конструкцию: print(a2[3]) - выведется 5. Могут быть последовательности индексов, например, чтобы получить значение из вложенной таблицы: print(a2[1][4]) - на экран выведется 8.

Все эти способы удобны для манипулирования данными в программе еще и потому, что можно изменять значения при вычислениях, при этом нужно просто переназначить нужное значение, обращаясь к нему одним из перечисленных выше способов. a2[1][4]=88 - тогда таблица a2 теперь будет выглядеть так: a2={{2, 4, 6, 88}, {1, 3, 5, 7}, number=12, name=”name”, 5}.

Данные способы удобны до того момента, когда нужно работать с многомерными таблицами, то есть когда в одну таблицу вложено еще несколько таблиц с именованными индексами (или полями). В моей программе как раз используются именно такие таблицы. Например, таблица contacts={} - изначально она пустая, но когда происходит чтение из файла, то в нее заносится еще несколько таблиц. В итоге получается примерно так: contacts={{x=20, y=20, h=15, state=”open”, id=1, name=”РТО”, active=false, line=1}, {x=30, y=30, h=15, h1=17, state=”close”, id=2, name=”ПМВ1”, active=true, line=2}, {x=40, y=40, h=15, state=”open”, id=2, name=”ПМВ2”, active=false, line=2}}. x, y - координаты контакта, h - значение, необходимое для отрисовки контакта, h1 - дополнительное значение для отрисовки нормально закрытого контакта, id - значение, необходимое для ассоциации контакта с реле, на котором он находится, name - название контакта, state - состояние контакта (нормально открытый или закрытый), active - имитация проводимости контакта, line - параметр, необходимый для просчета срабатывания выбранной цепи.

Перечисленными способами работать с такой многомерной таблицей очень неудобно. Поэтому существует еще один способ - работа с парами значений. Описывается следующим образом:

for k, v in pairs(contacts) do … end. Здесь k, v - переменные, в которые циклически записываются пары индекс-значение.

Следующий код выведет значения первой таблицы в таблице contacts:

for k, v in pairs(contacts[1]) do print(k, v) end

На экране отобразится следующее:

x 20

y 2015“open”1“РТО”false1.

То есть все, что есть в таблице, только без знаков «равно». Можно вывести отдельно значения k или v, то есть просто в теле цикла написать print(k) или print(v).

При таком способе также можно назначать и переназначать значения полей (индексов) таблицы и сравнивать поля одной таблицы с полями другой. В моей программе есть еще несколько подобных таблиц, например relays={} - таблица со всеми реле схемы. После чтения из файла она становится примерно такой: relays={{x=110, y=110, idrelay=2, active=false, name=”ПМВ”, line=1}, {x=120, y=120, idrelay=3, active=false, name=”ПМО”, line=2}, {x=130, y=130, idrelay=4, active=false, name=”РПЗ”, line=3}}. x, y - координаты реле, idrelay - значение, необходимое для ассоциации реле с контактами, active - подано ли на реле напряжение или нет, name - название реле, line - значение, для выбора цепи, в которой находится реле.

Для того, чтобы сравнить, например, id и idrelay в этих двух таблицах (contacts и relays), необходимо использовать такой код:

for k,v in pairs(contacts) dor,f in pairs(relays) dov.id == f.idrelay then … end

end

Переменные k, v и r, f могут иметь любые имена, важно лишь, чтобы в этих циклах они были разными, иначе они переназначатся и возникнет путаница, в итоге схема не будет работать.

Таблица в языке Lua самый удобный способ для манипуляции большими количествами переменных, которые должны иметь одинаковые поля (индексы), поэтому в своей программе именно их я и использую, язык выбран по причине наличия такого мощного инструмента для работы с данными.

5. РАЗРАБОТКА АЛГОРИТМОВ И ПРОГРАММНОГО КОМПЛЕКСА

Так как программа имеет графический интерфейс, то в этой главе будут продемонстрированы код программы и связанные с ним функции программы.

Код интерфейса строится на использовании библиотеки Wx.lua, которая является портом wxWidgets. Все классы и методы даны на сайте, посвященном этим виджетам [10].

.1 Программа-имитатор

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

Рисунок 5.1 - Создание фрейма

Фрейм, в данном случае, как на рисунке 5.1, пустое окно без кнопок и остальных элементов. Так как сейчас демонстрируется программа-имитатор работы схем, то лишних кнопок здесь не установлено.

Необходима для отображения схемы и изменений в ней при работе функция painter(event). Рисуются главные питающие шины и отображается строка срабатывания защиты. Код показан на рисунке 5.2, окно программы - рисунок 5.3.

Рисунок 5.2 - Код изначального окна программы

Рисунок 5.3 - Изначальное состояние окна программы

Так как таблицы контактов, реле и проводов не заполнены, то отображается только такое окно. Для этого в коде осуществляются проверки, как на рисунке 5.4.

Рисунок 5.4 - Проверки таблиц контактов и реле

Данная функция, onDown(event), необходима для обработки события нажатия правой кнопки мыши на одном из элементов. Код показан на рисунке 5.5, действие - рисунок 5.7.

Рисунок 5.5 - Код функции onDown(event)

В программе-имитаторе это выглядит так, как на рисунке 5.6.

Рисунок 5.6 - Изначальное состояние схемы

При нажатии на контакт «РТО» подаем на реле «РПЗ» напряжение. Контакт «РПЗ2» этого реле замыкается. Выводится сообщение о сработавшей защите и такое состояние поддерживается до тех пор, пока первый контакт не вернется в изначальное положение, то есть до тех пор, пока не будет отпущена правая кнопка мыши. За перерисовку схемы отвечает функция repaint().

Рисунок 5.7 - Сработавшая схема

Она необходима для перерисовки схемы. Вызывается из функции onDown (event). В ней просчитываются все изменения в таблицах контактов и реле, как показано на рисунке 5.8.

Функция onUp(event) возвращает схему в исходное состояние. Аналогична функциям onDown(event) и repaint() вместе, то есть перерисовка схемы происходит внутри функции onUp(event) без вызова дополнительных функций, как на рисунке 5.9. Также, нужно пояснить, что слово event в функции обозначает событие, например нажатие кнопки мыши, нажатие клавиши, потеря фокуса указателя, фокус, при наведении указателя мыши и так далее. В программном коде всегда ставится ссылка на функцию, соответствующую необходимому событию. Это делает отдельно, что позволяет удобно и без путаницы в коде подключать события к функциям, а затем вызывать эти функции по какому-либо из указанных в программе событий. Выше говорилось, что для сохранения таблиц используется их сериализация. Для десериализации необходима функция, как показано на рисунке 5.10. Для того, чтобы занести данные в таблицы контактов, реле, проводов и линий используется функция setValues(newTable). Она необходима потому, что в файле-сохранении в одной таблице хранится четыре. Поэтому нужно из одной сохраненной сделать четыре рабочие и код функции показан на рисунке 5.11.

Рисунок 5.8 - Функция repaint()

Рисунок 5.9 - Функция onUp(event)

На фрейм устанавливается панель меню и в нее вносятся файл-меню и меню помощи, как показано на рисунке 5.12.

Рисунок 5.10 - Функция десериализации таблиц

Рисунок 5.11 - Функция setValues(newTable)

5.2 Редактор схем

Программа для редактирования схем строится таким же образом. То есть изначально точно таким же способом, что и в программе-имитаторе работы схем создается фрейм, определяются некоторые необходимые переменные, а также функция painter(event) содержит точно такой же код, как и в первом случае, так как принципы рисования схемы одинаковы в обоих случаях. Таблицы те же самые, чтобы потом можно было легко их ассоциировать в программе-имитаторе. Редактор схем может и сохранять схемы, для последующего их открытия в программе-имитаторе, и открывать сохраненные схемы для их редактирования.

Рисунок 5.12 - Установка элементов интерфейса на фрейм

Так как на панели инструментов находятся незарезервированные инструменты, то для них нужны собственные id. Для этих целей используется соответствующая функция, соответствующие переменные для этих id и конструктор панели инструментов. Хотя в конструкторе нестандартны только названия id. Эта функция позволяет назначать новые инструменты не сбивая порядок между уже существующими и установленными на панель. Код функции и инструментов, а так же интерфейс показаны на рисунке 5.13 и рисунке 5.14.

Источник: https://www.bibliofond.ru/detail.aspx?id=896854