Структурные элементы и управление логикой декларативного языка Engee
Страница в процессе разработки. |
В статье представлено несколько ключевых механизмов декларативного языка Engee, которые делают описание физических компонентов более гибким и удобным: перечисления, ветвления, циклы, а также организацию кода через модульную структуру и настройку внешнего вида блоков с помощью иконок.
Перечисления
Перечисления позволяют задать конечный набор допустимых значений параметра и удобно комбинируются с ветвлениями. Благодаря этому компонент может работать в разных режимах, которые выбираются пользователем через выпадающий список в интерфейсе.
Подробный разбор перечислений, синтаксиса и примеров см. в статье Типизация и единицы измерения.
Ветвления
Ветвление — это способ описать разные варианты поведения компонента в зависимости от условий. Следовательно, задается правило: «если выполняется условие — используется один блок кода, иначе — другой».
В декларативном языке Engee ветвления бывают двух видов:
-
Компиляционные ветвления (
if … elseif … else … end
) — выполняются один раз при объявлении компонента. Они зависят только от параметров и перечислений. Такой код разворачивается на этапе сборки модели и не меняется во время моделирования. Применяются для выбора структуры, формул или числа элементов еще до запуска расчетов. -
Динамические ветвления (
ifelse(…)
) — действуют уже во время моделирования. Ветка выбирается в зависимости от текущих значений переменных, и результат может меняться на каждом шаге интеграции. Такие ветки позволяют описывать режимы работы, которые переключаются «на лету».
В итоге компиляционные ветвления нужны для настройки модели перед запуском, а динамические — для описания поведения системы во времени.
Компиляционные ветвления
Компиляционные ветвления применяются, когда условие зависит от параметров или перечислений. Ветка выбирается один раз при создании компонента и дальше не меняется.
@descriptive_enumeration Model begin
simple = 1, "Simple"
adv = 2, "Advanced"
end
@engeemodel Example begin
@parameters begin
has_option::Bool = true
model::Model = Model.simple
end
@variables begin
x = 0; y = 0
end
if model == Model.simple
@equations begin
x ~ 1 * t
end
else
@equations begin
x ~ 2 * t
end
end
@equations begin
y ~ if default(has_option) 2 * x else 5 * x end
end
end
-
Условие
model == Model.simple
отрабатывает на этапе объявления. -
Внутри
@equations
используетсяdefault(has_option)
, чтобы корректно подставить параметр в условие.
Использовать if … elseif … else по переменным нельзя — такие ветки фиксируются по начальному значению и не меняются во время моделирования. Для переменных применяйте ifelse(…) .
|
Динамические ветвления
Динамические ветвления работают во время моделирования и зависят от значений переменных. Для этого используется функция ifelse(condition, expr1, expr2)
. Она позволяет описывать переключение уравнений «на лету». Пример:
@variables begin
x = 0; y = 0
end
@equations begin
y ~ ifelse(x < 10, x, -x)
end
Здесь:
-
Если
x < 10
, тоy = x
; -
Если
x >= 10
, тоy = -x
.
В отличие от компиляционного if
, такое условие пересчитывается на каждом шаге моделирования.
При использовании обычных сравнений (x < 10 ) такие ветки порождают события (момент переключения фиксируется отдельно). Чтобы задать ветвления без событий, применяйте специальные функции gt , lt , ge , le , eq , neq .
|
Ветвления в уравнениях
Для выбора, зависящего от переменных во времени, используют функцию ifelse(condition, a, b)
. Она позволяет корректно переключать выражения во время моделирования.
@equations begin
# Переключение по условию с событием
y ~ ifelse(x < 10, x, -x)
# Сравнение без события
if gt(x, 0.5)
u ~ 1
elseif x < 0 # это условие создаст событие
if lt(y, 1) # это условие события не создаст
u ~ 3
else
u ~ 4
end
v ~ 5
else
u ~ 6
v ~ 7
end
end
-
ifelse(x < 10, x, -x)
переключает выражение при измененииx
. -
Функции
gt
,lt
,ge
,le
,eq
,neq
позволяют писать условия без событий. -
Количество уравнений в каждой ветке должно совпадать.
Циклы
Циклы позволяют объявлять порты, подкомпоненты и уравнения повторяющимся образом, что удобно для массивов однотипных элементов. Пример:
@structural_parameters begin
n::Int = 5
end
@components begin
pins = [AcausalFoundation.Electrical.Pin() for i in 1:n]
end
@equations begin
[v[i] ~ pins[i].v for i in 1:n]
end
Здесь:
-
В секции
@structural_parameters
объявляется параметрn = 5
. Он определяет размерность массива. -
В секции
@components
создается массивpins
из 5 электрических портов (Pin
). Каждый элемент массива соответствует отдельному порту. -
В секции
@equations
с помощью цикла формируется массив уравнений: напряжениеv[i]
связывается с напряжением соответствующего портаpins[i].v
.
Так, одним блоком кода описано сразу 5 одинаковых соединений. Если изменить значение n
, то количество портов и уравнений автоматически изменится.
Массивы подкомпонентов
Тот же прием можно использовать и для подкомпонентов. Как мы только что сделали массив портов и уравнений, так же можно создавать массивы из однотипных компонентов. Это позволяет, например, быстро собрать схему из нескольких резисторов, конденсаторов или диодов. Например:
@engeemodel ArrayExample begin
@components begin
diode[1:5] = Diode()
cap[1:3] = Capacitor()
end
end
Здесь создается:
-
Массив из 5 диодов (
diode[1:5]
); -
Массив из 3 конденсаторов (
cap[1:3]
).
Снаружи элементы будут иметь имена diode_1
, diode_2
, … и cap_1
, cap_2
, … . К каждому элементу можно обращаться по индексу, а также использовать их в соединениях и уравнениях.
Так, циклы в @components
работают по тому же принципу, что и для портов и уравнений: один шаблон — несколько экземпляров.
Модульная структура
Компоненты, коннекторы и перечисления можно объединять в модули. Модули позволяют группировать связанные элементы, а также экспортировать их и использовать в других частях проекта по полному квалифицированному пути. Пример:
module Electrical
@engeeconnector Pin begin
...
end
module Enumerations
@descriptive_enumeration Type begin
first = 1, "One"
second = 2, "Two"
end
end
end
@engeemodel begin
@components begin
pin = Electrical.Pin()
end
@parameters begin
type::Type = Type.first
end
end
Здесь:
-
Объявлен модуль
Electrical
с коннекторомPin
и перечислениемType
; -
Далее используется компонент, в котором подключается
Electrical.Pin()
и параметр с типомType
.
Иконки
Внешний вид блока можно настраивать с помощью конструкции @icon
. Она задает описание иконки через словарь Dict
, что позволяет управлять формой, текстом и другими графическими элементами. Пример:
@icon begin
rectangle("Resistor")
end
Краткие рекомендации
-
Используйте
if … elseif … else
для параметров и структурных решений. -
Для переменных во времени применяйте
ifelse(…)
. -
Внутри секций пишите условия по параметрам через
default(…)
. -
Чтобы избежать лишних событий, применяйте
gt/lt/ge/le/eq/neq
вместо обычных сравнений. -
Масштабируйте код массивами подкомпонентов и векторной записью уравнений.