Типизация и единицы измерения декларативного языка Engee
Страница в процессе разработки. |
В декларативном языке Engee параметры и переменные могут быть снабжены типами данных и единицами измерения. Типы управляют формой данных (скаляр, вектор, булево значение) и видом элементов в интерфейсе, а единицы помогают проверять размерности и автоматически переводят все значения в систему СИ. Это делает модели более строгими и удобными для пользователя.
Типизация
По умолчанию все параметры и переменные считаются вещественными числами (Real). Это удобно для большинства физических моделей, но не всегда достаточно.
Типизация позволяет:
-
Явно указать, какой тип данных должен использоваться;
-
Контролировать ввод значений в интерфейсе (например, булево значение будет чекбоксом, а перечисление — выпадающим списком),
-
Задать структуру данных (скаляр, вектор, матрица),
-
Повысить строгость модели и исключить ошибки еще на этапе объявления.
Поддерживаются следующие типы:
-
Real
— вещественные числа (значение по умолчанию, если тип не указан); -
Int
— целые числа; -
Bool
— логические значения (true
/false
); -
Массивы (
[:]
для вектора,[:, :]
для матрицы); -
Перечисления (
Enum
), которые задаются отдельно.
Ниже рассмотрим примеры разных вариантов типизации.
Скалярные типы
Пример объявления простых параметров разных типов:
@parameters begin
flag::Bool = true
count::Int = 5
gain::Real = 1.25
end
где:
-
Bool
— логический тип, принимает только два значения:true
(истина) илиfalse
(ложь). В интерфейсе такой параметр отображается как чекбокс, который можно включить или выключить. -
Int
— целое число без дробной части. Удобно использовать для счетчиков, индексов, размеров массивов. -
Real
— число с дробной частью (вещественное). Подходит для любых физических величин: масса, скорость, напряжение и т.д.
Векторы и матрицы
При объявлении массивов важно учитывать правила:
-
a[:]
— всегда вектор (одномерный массив). -
b[:,:]
— всегда матрица (двумерный массив). -
j[:,:,:]
— трехмерный массив. -
Если параметр объявлен без
[:]
, то он всегда скаляр. -
Для параметров
::Int
допускаются только целые числа — символы и выражения не поддерживаются. -
Размерности массивов можно задавать через структурные параметры.
Пример:
@structural_parameters begin
n1::Int = 5
n2::Int = 3
end
@parameters begin
a[:] = [1, 2, 3, 4] # вектор
b[:] = zeros(n1) # вектор длиной n1
c[:,:] = ones(n1, n2) # матрица n1×n2
j[:,:,:] = zeros(3,3,3) # трехмерный массив
g[:]::Real = [0.1, 0.2, 0.3] # вектор вещественных чисел
end
@variables begin
x = 0
end
@equations begin
x ~ a[1] + b[2] + c[3,2] + j[1,1,1] + g[2]
end
Разберем, что делает этот код:
-
В секции
@structural_parameters
задаются размеры массивов:n1 = 5
,n2 = 3
. Эти параметры определяют форму векторов и матриц. -
В секции
@parameters
:-
a[:] = [1, 2, 3, 4]
— создается вектор длиной 4 с фиксированными значениями[1, 2, 3, 4]
. -
b[:] = zeros(n1)
— создается вектор длинойn1
(в данном случае 5), заполненный нулями. -
c[:,:] = ones(n1, n2)
— создается матрица размером5×3
, заполненная единицами. -
j[:,:,:] = zeros(3,3,3)
— создается трехмерный массив размером3×3×3
, заполненный нулями. -
g[:]::Real = [0.1, 0.2, 0.3]
— создается вектор из вещественных чисел, тип элементов указан явно (Real
).
-
-
В секции
@variables
вводится переменнаяx
, изначально равная 0. -
В секции
@equations
задается уравнение, в которомx
выражается через элементы всех массивов:-
a[1]
— первый элемент вектораa
(значение 1). -
b[2]
— второй элемент вектораb
(значение 0, так как вектор заполнен нулями). -
c[3,2]
— элемент матрицыc
в 3-й строке и 2-м столбце (значение 1, так как матрица заполнена единицами). -
j[1,1,1]
— элемент трехмерного массиваj
(значение 0). -
g[2]
— второй элемент вектораg
(значение 0.2).
-
Здесь видно, что массивы могут быть многомерными и типизированными. Индексация используется для обращения к отдельным элементам.
Массивы по умолчанию
Иногда нужно задать массив значений «по умолчанию» для параметра. Лучше всего вынести такой массив в отдельную константу, чтобы не перегружать объявление параметра и упростить работу решателя.
const a_default = [1:0.01:100...]
@engeemodel Component begin
@parameters begin
a = a_default
end
end
Разберем пример подробнее:
-
const a_default = [1:0.01:100…]
— создается константаa_default
, которая содержит массив чисел от 1 до 100 с шагом 0.01. -
В секции
@parameters
объявляется параметрa
, и ему сразу присваивается значение из этой константы. -
Теперь параметр
a
имеет готовый набор значений по умолчанию, который можно использовать в уравнениях или передавать в другие компоненты.
В правой части параметра не рекомендуется использовать операции с точкой (.+ , .* , ./ и т.д.) при инициализации. Это может приводить к ошибкам в анализе модели. Вместо этого массивы стоит готовить заранее, как в примере выше.
|
Типы результатов функций
Иногда параметры или переменные получают значения не напрямую, а через вызов пользовательской функции. Если такая функция возвращает массив, логическое значение (Bool
) или перечисление (Enum
), то для переменной или параметра обязательно нужно указать явный тип.
Это связано с тем, что без указания типа компилятор не сможет корректно интерпретировать результат, и может выдать ошибку на этапе сборки модели. Пример: функция, возвращающая массив и булево значение.
@functions begin
make_vector(n) = [i for i in 1:n]
check_positive(x) = x > 0
end
@parameters begin
a[:] = make_vector(3) # функция вернула вектор [1, 2, 3]
flag::Bool = check_positive(-5) # функция вернула false
end
Здесь:
-
В секции
@functions
объявлены две функции:-
make_vector(n)
создает массив чисел от 1 доn
. -
check_positive(x)
проверяет, больше ли число нуля, и возвращаетtrue
илиfalse
.
-
-
В секции
@parameters
:-
a[:] = make_vector(3)
— параметрa
получает массив[1, 2, 3]
. Тип массива задан квадратными скобками[:]
. -
flag::Bool = check_positive(-5)
— параметрflag
получает результат проверкиfalse
, и тип явно указан какBool
.
-
Без явного указания типов (например, [:]
для массива или ::Bool
для логической переменной) модель может не собраться или работать некорректно.
Перечисления
Перечисления используются, когда параметр должен принимать одно из заранее определенных значений. Они удобны для задания режимов работы, выбора моделей или конфигураций компонента.
@descriptive_enumeration CustomEnum begin
first = 1, "One"
second = 2, "Two"
third = 3, "Three"
end
В примере создается перечисление CustomEnum
с тремя возможными вариантами:
-
CustomEnum.first
— значение 1, отображается как"One"
, -
CustomEnum.second
— значение 2, отображается как"Two"
, -
CustomEnum.third
— значение 3, отображается как"Three"
.
Чтобы параметр мог принимать только значения из перечисления, нужно указать его тип явно:
@engeemodel CustomComponent begin
@parameters begin
math_model::CustomEnum = CustomEnum.first
end
end
Здесь параметр math_model
имеет тип CustomEnum
и по умолчанию равен CustomEnum.first
. В интерфейсе такой параметр будет представлен выпадающим списком, где доступны только значения из перечисления.
Единицы измерения
Единицы задаются через метаданные unit
. Они нужны для корректного отображения, проверки размерностей и автоматического перевода всех значений в систему СИ.
Если единицы не указаны, то модель все равно будет работать, но проверок согласованности не будет, и ошибки могут остаться незамеченными. |
Пример:
@parameters begin
R = 1.0, [unit = "Ohm"]
U = 5.0, [unit = "V"]
end
@variables begin
I = 0.0, [unit = "A"]
end
@equations begin
U ~ R * I
end
Здесь:
-
R
задан в омах; -
U
— в вольтах; -
I
— в амперах.
Решатель автоматически проверит согласованность уравнения U ~ R * I
: напряжение действительно равно произведению сопротивления на ток.
Если в уравнении встретятся несогласованные единицы, например вы попытаетесь приравнять вольты к ньютонам, система выдаст ошибку на этапе компиляции. Это помогает ловить ошибки еще до запуска моделирования. |
Передача значений в подкомпоненты
Если в подкомпонент передается число без единиц, то используется единица по умолчанию, указанная в его параметрах. Чтобы задать единицу явно, используется NamedTuple
. Пример:
@engeemodel A begin
@parameters begin
x = 1, [unit = "mV"]
end
end
@engeemodel B begin
@components begin
a1 = A(x = 2) # трактуется как 2 mV → 0.002 V
a2 = A(x = (value = 2, unit = "V", priority = "low"))
end
end
В этом примере видно, что:
-
a1
получает значение2
в милливольтах, так как это указано по умолчанию в компонентеA
. -
a2
получает значение 2 вольта, потому что мы явно задали единицу черезNamedTuple
.
Аналогично, если параметр объявлен с единицей "kA"
, то передача числа 0.8
будет интерпретироваться как 0.8 кА (800 А). Чтобы задать именно 0.8 ампера, нужно написать:
@components begin
a = A(h = (value = 0.8, unit = "A"))
end
Практические советы
-
Для физических параметров всегда задавайте
unit
, чтобы проверка размерностей ловила ошибки на этапе компиляции. -
Используйте
Bool
для переключателей вместо чисел 0/1. -
Для векторов и матриц указывайте начальные значения.
-
Избегайте
default()
для параметров, пришедших из портов — это может давать неверные результаты. -
Если нужно явно задать единицу в подкомпоненте, используйте
NamedTuple
илиvalue_unit
.