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

Подкомпоненты и наследование в декларативном языке Engee

Страница в процессе разработки.

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

Наследование компонентов

Иногда новый компонент очень похож на уже существующий, и нет смысла переписывать все заново. Для этого в декларативном языке Engee используется конструкция @extend, которая позволяет наследовать переменные и уравнения из другого физического компонента.

@extend v, i = branch = Branch(i = 0, v = 0)   # 1
@extend v, i = branch = Branch()               # 2
@extend i = branch = Branch()                  # 3
@extend v = branch = Branch(i = 0.0)           # 4
@extend v = branch = Branch(i = 0.0)           # 5 - только если Branch написан через @engeemodel
@extend Branch()                               # 6 - только если Branch написан через @engeemodel
@extend Branch(i = 0)                          # 7 - только если Branch написан через @engeemodel

Здесь показаны разные варианты наследования. Общий формат:

  • Слева (v, i) указываются имена переменных, которые мы хотим извлечь из родительского компонента;

  • branch = Branch(…​) — это создание экземпляра родительского компонента, из которого берутся переменные;

  • Если переменные не извлечь, они останутся скрытыми и использоваться не смогут.

Применять @extend можно только один раз внутри компонента.

Особенности наследования:

  • Все переменные/параметры, которые должны отображаться в интерфейсе, нужно обязательно извлечь через @extend. Иначе они будут скрыты.

  • В коде расширяющего компонента нужно обязательно передать все параметры, которые есть у родителя, если они имеют gui = Modify.

Ошибки при наследовании:

Если не извлечь нужные переменные, то в коде можно случайно получить неверные значения без ошибки. Например:

@engeemodel BV begin
    @extend v = branch = Branch(i = 0.0, v = 0.0)  # переменная i не извлечена
    @variables begin
        a = i + 5
    end
end

Ошибки компиляции не будет, но i в выражении окажется равной 0 (значение по умолчанию), что приведет к некорректной работе модели.

Подкомпоненты

Подкомпоненты — это компоненты, которые мы встраиваем внутрь других физических компонентов. Они создаются с помощью секции @components.

@components begin
    diode     = Diode(v_forward = 0.6, R_on = 0.3, G_off = 1e-8)
    capacitor = Capacitor(C = 1e-4)
end

Здесь в новый компонент добавлены два подкомпонента: диод и конденсатор.

  • В аргументах конструктора подкомпонента можно сразу задать параметры (v_forward, R_on, C и т. д.).

  • Если подкомпонент объявлен через @engeemodel, то единицы измерения учитываются автоматически.

Если подкомпонент написан на императивном языке (через ModelingToolkit.jl), то передавать параметры и выражения нужно через функцию default(), чтобы значения автоматически переводились в систему СИ.

Пример с передачей значений

@engeemodel A begin
    @variables begin
        a = 1
        b[:] = [1, 2]
    end
    @parameters begin
        c = 3
        d[:] = [3, 4]
    end
    @equations begin
        a^2 ~ 2
        b[1] * b[2] ~ 5
        b[1]^2 ~ 12
    end
end

@engeemodel B begin
    @components begin
        a1 = A(a = 2, b = [3, 4.0], c = 1, d = [5, 6])
        a2 = A(a = e, b = f*2, c = sin(e), d = f .+ 2)
        # нельзя передавать в качестве value символические массивы или выражения на их основе
        a3 = A(d = (value = f, priority = "high"))
    end
    @parameters begin
        e = 3
        f[:] = [3,4]
    end
end

В этом примере видно:

  • В компонент A передаются параметры и переменные разными способами (числа, выражения, массивы).

  • Символические массивы нельзя передавать напрямую через NamedTuple.

  • Для таких случаев можно использовать default(), чтобы правильно подставить значение.

Массивы подкомпонентов

Подкомпоненты можно задавать не только по одному, но и массивами.

@components begin
    diodes    = [Diode() for i in 1:5]
    capacitors = [Capacitor() for i in 1:5]
end

Особенности:

  • Обращение к подкомпонентам выполняется как к обычным массивам Julia. Пример: connect(diode[1].p, capacitor[3].p).

  • Имена элементов для внешнего использования будут вида diode_1, diode_2, … и capacitor_1, capacitor_2, …

Это удобно, если нужно подключить сразу группу одинаковых элементов (например, несколько диодов или конденсаторов).

Выбор между наследованием и подкомпонентами

Наследование и подкомпоненты — это разные подходы для организации моделей. Чтобы было проще выбрать, сравним их в таблице:

Подход Когда использовать Особенности и ограничения

Наследование (@extend)

Когда новый компонент является «расширением» или «вариацией» существующего, и нужно добавить немного нового функционала.

  • Извлекаются переменные и уравнения из родителя.

  • Можно применить только один раз.

  • Нужно явно извлекать переменные, иначе они будут скрыты.

  • Возможны сложности при наследовании от компонентов на ModelingToolkit.jl.

Подкомпоненты (@components)

Когда новый компонент строится как комбинация нескольких других компонентов.

  • Поддерживает любое количество вложенных компонентов.

  • Можно использовать массивы подкомпонентов.

  • Параметры передаются прямо в конструкторы.

  • Для императивных компонентов нужно использовать default().

Краткие рекомендации

  • Используйте @extend, когда нужно слегка расширить готовый компонент.

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

  • Для подкомпонентов, написанных на ModelingToolkit.jl, используйте default().

  • Для однотипных элементов лучше задавать массивы подкомпонентов, чем копировать код.