Документация Engee

Типизация и единицы измерения декларативного языка 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

Использование value_unit

Иногда нужно использовать числовую константу с единицей прямо в уравнении. Для этого применяется функция value_unit.

@equations begin
    i_lim ~ ModelingToolkit.value_unit(0.8, "A")
end

Здесь в уравнение подставляется 0.8 А, переведенное в СИ (Амперы → А).

Практические советы

  • Для физических параметров всегда задавайте unit, чтобы проверка размерностей ловила ошибки на этапе компиляции.

  • Используйте Bool для переключателей вместо чисел 0/1.

  • Для векторов и матриц указывайте начальные значения.

  • Избегайте default() для параметров, пришедших из портов — это может давать неверные результаты.

  • Если нужно явно задать единицу в подкомпоненте, используйте NamedTuple или value_unit.