Работа с переменными в Engee
Чтобы создать новую переменную, введите имя (название переменной) в командную строку/редактор скриптов и оператором присваивания = (знак равно) присвойте нужное значение.
Переменные в Engee могут быть заданы разными способами. Рассмотрим основные способы присваивания на основе следующего кода и трех переменных — a, b, c:
a = 1 # Простое присваивание
a::Float64 = 1.0 # Присваивание с явным указанием типа данных
b = 1 + 2 # Присваивание через вычисление выражения
c = b = 5 # Множественное присваивание
a,b,c = (10,20,30) # Распаковка кортежа
В результате выполнения кода можно получим следующее:
-
Переменной a присваивается значение
1
. Это самое простое присваивание значения переменной; -
Переменной a присваивается значение
1
, но с явным указанием типа данных Float64 (с плавающей запятой); Теперь ее действительное значение —1.0
; -
Переменной b присваивается результат выражения
1 + 2
, то есть3
; -
Значение
5
присваивается переменным b и c; -
Значения из кортежа
(10, 20, 30)
распаковываются и присваиваются переменным a, b и c соответственно;
Отметим, что переменные в Engee могут быть скалярными, векторными и матричными:
-
Скаляр — имеет единичное значение данных. Задается с помощью переменных в виде целых и вещественных чисел, логических значений и символьных строк. Например:
v = 1 # Целое число, тип данных Int64 v = 1.0 # Вещественное число, тип данных Float64 v = true # Логическое значение, тип данных Bool v = "a" # Символьная строка, тип данных String
-
Вектор — это упорядоченная коллекция элементов, которая может быть представлена в виде одномерного массива. Задается с использованием переменных, которые содержат целые или вещественные числа, логические значения или символьные строки. Например:
v = [1, 2, 3] # Вектор целых чисел, тип данных Vector{Int64} v = [1.0, 2.0, 3.0] # Вектор вещественных чисел, тип данных Vector{Float64} v = [true, false] # Вектор логических значений, тип данных Vector{Bool} v = ["a", "b", "с"] # Вектор символьных строк, тип данных Vector{String}
-
Матрица — двумерный массив данных, состоящий из строк и столбцов. Матрица может быть использована для представления множества переменных одновременно. Каждый элемент матрицы может быть скалярным значением, таким как целое число, вещественное число, логическое значение или символьная строка. Например:
v = [1 2 3] # Матрица целых чисел, тип данных Matrix{Int64} v = [1.0 2.0 3.0] # Матрица вещественных чисел, тип данных Matrix{Float64} v = [true false] # Матрица логических значений, тип данных Matrix{Bool} v = ["a" "b" "с"] # Матрица символьных строк, тип данных Matrix{String} v = ([1 2 3; 4 5 6; 7 8 9]) # Многострочная матрица, тип данных Matrix{Int64}
Для создания массивов с размерностью больше трех можно использовать значение undef
, которое указывает, что массив не инициализирован. Элементы такого массива могут содержать произвольные данные. Выберем одну из двух структур для создания массива:
Array{T}(undef, dims)
Array{T,N}(undef, dims)
где:
-
T
— тип данных массива. -
N
— размерность массива. Может быть задана явно или определяться длиной и количествомdims
. Если указана явно, то должна соответствовать длине и количествуdims
. -
dims
— кортеж или ряд целочисленных аргументов, соответствующих длине в каждом измерении.
Например:
A = Array{Float64, 3}(undef, 2, 3, 4)
# или
A = Array{Float64}(undef, 2, 3, 4) # Получим трехмерный массив с размерностью 2*3*4, тип данных Float64, размерность задается автоматически
Вывод результата создания массива
2×3×4 Array{Float64, 3}:
[:, :, 1] =
6.94241e-310 6.94267e-310 6.94267e-310
6.94272e-310 6.94267e-310 6.94267e-310
[:, :, 2] =
6.94267e-310 6.94267e-310 6.94267e-310
6.94267e-310 6.94267e-310 6.94267e-310
[:, :, 3] =
6.94267e-310 6.94267e-310 6.94267e-310
6.94267e-310 6.94267e-310 6.94267e-310
[:, :, 4] =
6.94267e-310 6.94267e-310 6.94267e-310
6.94267e-310 6.94267e-310 6.94267e-310
Подробнее о создании массивов читайте здесь.
Подробнее о типах данных читайте в статье.
Типизация переменных
Engee поддерживает явную и неявную типизации переменных — это значит, что тип данных переменной может быть явно указан (принудительно присвоен) или определен на основе содержимого в процессе выполнения программы. Среди основных типов данных можно выделить:
-
Integer — представляет целые числа, например
1
,100
,−1
. -
Float — представляет вещественные числа (с плавающей запятой), например
1.0
,−3.14
. -
String — представляет текст, заключенный в кавычки, например "Hello, world!".
-
Array — представляет упорядоченный набор элементов, например,
[1, 2, 3]
.
Рассмотрим явное указание типа данных переменных на следующем примере:
Здесь для переменной z был явно задан тип данных Int64, который содержит только целые числа. Попытка присвоить переменной z вещественное число 3.14
(соответствующее типу данных Float64) вызвало ошибку (InexactError: Int64).
Попытка присвоить переменной несоответствующий тип данных приводит к ошибке и созданию переменной err в окне переменных. Переменная err хранит информацию о последней ошибке в столбце значений в окне переменных. При необходимости скройте переменную err по аналогии с другим переменными или вручную с помощью объекта nothing:
|
Рассмотрим неявную типизацию, в которой программа определяет тип данных переменной на основе содержимого:
При неявной типизации присвоение другого типа данных переменной не вызывает ошибку. Присвоение типа данных происходит на основе содержимого (значения) переменной.
Подробнее ознакомиться с типизацией можно в статье Типы в Engee.
Кортежи
Кортежи (Tuple) в Engee — это упорядоченная и неизменяемая структура данных. Кортежи создаются с помощью круглых скобок ( )
и запятых.
Кортежи описываются с помощью параметризованного типа данных MyType{ParamType1, ParamType2, ParamType3, …}
и представляют собой тип данных элементов в прямой последовательности. При этом элементы кортежа могут иметь разные типы данных. Например, кортеж может содержать целое число, вещественное число и текст в кавычках одновременно. Рассмотрим такой кортеж:
tuple = (1, 3.14, "Hello, world!")
В данном случае кортеж имеет тип данных Tuple(Int64, Float64, String).
Кортежи бывают обычными и именованными:
-
Обычный кортеж — это упорядоченная и неизменяемая структура данных. Каждый элемент обычного кортежа может быть любого типа данных. Его элементы располагаются в определенном порядке. К элементам обращаются через их индексы. Рассмотрим такой кортеж на примере:
x = (1, 3.14, "Hello, world!") x[3]
В примере переменная x содержит три значения —
1
,3.14
и"Hello, world!"
. Этим значениям соответствуют порядковые индексы [1], [2] и [3] соответственно. С помощью переменной x, содержащей значения обычного кортежа и индекса [3], получили конкретное значение переменной — "Hello, World!", а не весь кортеж целиком. -
Именованный кортеж — это особый тип кортежа, похожий на обычный кортеж, но в котором каждый элемент имеет свое имя. Вместо обращения к элементам по индексу как в обычных кортежах, в именованных кортежах также можно обращаться к элементам по их именам. Пример именованного кортежа:
x = (a=1, b=3.14, c="Hello, world!") println(x.a) println(x.b) println(x.c) println(x[1]) println(x[2]) println(x[3])
В этом примере поддерживается обращение к элементам кортежа не только через индексы (как в обычном кортеже [1],…,[n]), но и через имена переменных — x.a, x.b, x.c и т.д.
Поскольку кортеж является неизменяемой структурой — порядок его элементов имеет значение и сохраняется. Неизменность кортежа позволяет работать с множественным присваиванием. |
Множественное присваивание — это особенность, позволяющая присваивать значения нескольким переменным одновременно с помощью кортежа. Это средство позволяет избежать временных переменных и улучшить читаемость кода. Например:
tuple = (1, 3.14, "Hello, world!")
a, b, c = tuple
Здесь создается кортеж tuple с тремя элементами:
-
Целое число
1
. -
Вещественное число
3.14
. -
Строка "Hello, world!".
Далее значения кортежа параллельно присваиваются трем переменным a, b и c соответственно. После выполнения кода переменная a будет содержать значение 1
, переменная b 3.14
, а переменная c — строку "Hello, world!". Так можно одновременно присвоить несколько переменных без явного указания значений кортежа с помощью интерполяции строк:
info = ("Ilya", 25, "Engineer")
name, age, occupation = info
println("Name: $name, Age: $age, Occupation: $occupation")
В этом коде кортеж используется для хранения информации, а параллельное присваивание – для распаковки значений кортежа в отдельные переменные. Затем для вывода информации о человеке применяется интерполяция строк с использованием литерала $:
-
info = ("Ilya", 25, "Engineer")
— создается кортеж info с тремя элементами: строкой"Ilya"
(имя), целым числом25
(возраст) и строкой"Engineer"
(род занятий). -
name, age, occupation = info
— с помощью параллельного присваивания значения кортежа распаковываются в три переменные:name
,age
иoccupation
. Теперь переменная name содержит значение"Ilya"
, age содержит целое число25
, а occupation содержит род занятий"Engineer"
. -
println("Name: $name, Age: $age, Occupation: $occupation")
— выводится строка с использованием интерполяции строк (используются$name
,$age
, и$occupation
для вставки значений переменных в строку). В результате выполнения этой строки кода будет выведено:
Name: Ilya, Age: 25, Occupation: Engineer
Таким образом, код создает кортеж с информацией о человеке, а затем распаковывает этот кортеж в отдельные переменные для более удобного доступа к данным и выводит информацию в редактор скриптов или командную строку.
Подробнее о работе с кортежами в Engee описано в статье Функции в Julia. Про интерполяцию в Julia можно узнать из статьи Строки в Julia.
Область видимости
Под блоком кода будем понимать фрагмент кода, заключенный в синтаксическую конструкцию function (функция) или |
Область видимости определяет, где в коде вы можете использовать переменную. Так, переменные бывают локальные и глобальные:
-
Локальная (внутренняя) переменная — переменная считается локальной, если она объявлена и определена внутри своего родительского элемента, например функции, объекта, блока кода (в котором определена) и т.д. Локальные переменные видны только внутри той области видимости, в которой определены и недоступны за ее пределами. Рассмотрим локальную переменную на примере:
function example_function() x = 10 println(x) end
Здесь function example_function() определяет функцию с именем example_function, содержащую локальную переменную x в локальной области видимости.
Дальнейший вызов функции командой example_function() выведет значение переменной x, равному 10
:
example_function()
Выполнение кода также создало переменную example-function с типом данных Function в окне переменных Engee. |
После выполнения функции, локальная переменная x выходит из области видимости, и становится недоступной за пределами функции. Это означает, что переменная существует только в пределах блока, в котором была определена.
Попробуем вызвать переменную вне локальной области с помощью следующего кода:
println(x)
В результате выполнения кода мы получили ошибку, что переменная x
не определена. Делаем вывод, что локальная переменная ограничивается областью видимости внутри блока кода (в котором определена) и не определяется извне.
Внешний код не имеет доступа к локальной переменной и выдает ошибку, что локальная переменная не определена. |
-
Глобальная (внешняя) переменная — переменная считается глобальной, если определена вне функций, циклов и блоков кода с локальной областью видимости. Глобальная переменная доступна из любой части кода и находится в глобальной области видимости, которая по умолчанию является модулем Main. Внутри модуля Main можно определить подмодули, для которых будут свои глобальные переменные. Рассмотрим глобальную переменную на примере:
global_var = 20 function example_function() println(global_var) end
Этот код устанавливает глобальную переменную global_var и определяет функцию example_function(). example_function() выводит значение глобальной переменной. Для вывода результата функции введите следующий код:
example_function()
println(global_var)# можно использовать глобальную переменную за пределами функции
После вызова example_function() выводится значение глобальной переменной global_var равное 20
. Функция println одновременно выведет два значения переменной, один раз — из функции, другой — за ее пределами.
Использование такой переменной может быть затруднено из-за проблемы затенения, когда имеется переменная с тем же именем в локальной области видимости. |
Затенение переменных — это ситуация, когда внутри определенной области видимости (например, внутри функции или блока кода) используется переменная с тем же именем, что и в глобальной области. В результате переменная внутри области видимости затеняет (перезаписывает) переменную с тем же именем из внешней области.
Будьте внимательны при задании переменных с одинаковыми именами — из-за затенения переменная внутри ограниченной области может иметь иное значение или тип, чем переменная с тем же именем в других частях программы. |
Для примера рассмотрим код с созданием двух переменных с одинаковыми именами и значениями, и без затенения:
global_var = 10 # создание глобальной переменной
function example_function()
global_var = 20 # создание локальной переменной (внутри функции) с тем же именем, что и глобальная
println(global_var) # будет использована локальная переменная внутри функции
end
example_function() # вызов функции
println(global_var) # глобальная переменная не была изменена внутри функции, поэтому выводится ее значение
Внутри функции создается локальная переменная с тем же именем global_var, что и глобальная. Это не изменяет глобальную переменную, а создает новую локальную, видимую только внутри функции. В результате выводятся значения обеих переменных, равные 20
(глобальная) и 10
(локальная) соответственно. Таким образом, создание переменных в разных областях видимости помогает избежать проблем с перезаписью переменных с одинаковыми именами.
Переменные в Среде моделирования
Переменные как параметры моделей
В Engee переменные могут быть заданы как параметры модели. Рассмотрим следующий пример:
engee.create("model")
С помощью метода create создана модель в Engee с именем model. Далее присвоим переменную:
m = engee.gcm()
Методом gcm обращаемся к созданной модели и возвращаем объект, представляющий эту модель (в нашем случае GCM — Get Current Model или получение текущей модели). Полученный объект присваивается переменной m.
Далее заменим параметры моделирования с помощью set_param!:
engee.set_param!(m, "name" => "Tsit5", "type" => "variable-step")
В коде изменяются параметры решателя и его шаг с постоянного на переменный.
Посмотрите, изменились ли параметры модели с помощью метода gcm:
engee.get_param("model")
Параметры модели поменялись на новые благодаря переменной m:
Далее с помощью метода run запустим симуляцию модели через переменную:
engee.run("m")
После успешной симуляции сохраните модель Engee со свойствами объекта (переменная m) по заданному пути в файловый браузер. В нашем случае:
engee.save(m,"/user/start/examples/base_simulation/command_control/model.engee"; force=true)
Если файл с таким именем уже существует (в нашем случае model.engee) — опция force=true перезапишет модель данными из переменной m без запроса подтверждения. Это полезно, если нужно перезаписать существующие файлы при сохранении модели.
Модель Engee появится в файловом браузере только после ее сохранения. Сохранение модели позволит работать с ней в будущем и применять весь функционал файлового браузера. |
Еще больше возможностей для работы с переменными в среде моделирования представлено в статье Программное управление моделированием.
Переменные как параметры блоков
Вы можете управлять параметрами блоков без программных методов, просто устанавливая отдельные значения параметров как переменные. Рассмотрим пример с моделью, представленной в статье. Модель состоит из блоков Sine Wave и Terminator, для сигнала между ними включена запись:
Блок Sine Wave – единственный блок в модели, чьи параметры можно настроить. Нажмите левой кнопкой мыши по блоку и задайте переменные для каждого параметра в соответствии с рисунком:
После установки имен переменных для значений блока присвоим значения для каждой из этих переменных:
a_var = 1 # Amplitude становится равным 1
b_var = 2 # Bias становится равным 2
f_var = 3 # Frequency становится равным 3
p_var = 1 # Phase становится равным 1
s_var = 1 # Sample time становится равным 1
Запустим симуляцию модели, задав отдельной переменной имя модели (ранее в статье m) и используя метод engee.run, или вручную, через кнопку Запустить модель . На графике будет видно, что блок Sine Wave изменил свои параметры и симуляция прошла корректно:
Устанавливать переменные в качестве параметров блоков полезно в случае, если блок имеет зависимости, влияющие на тот или иной параметр:
a_var = (2*f_var) # Amplitude становится равным 6
b_var = (6/f_var) # Bias становится равным 2
f_var = 3 # Frequency равен 3
p_var = (1 + f_var) # Phase становится равным 4
s_var = 1 # Sample time равен 1
В качестве параметров блока могут использоваться алгебраические выражения, в том числе с переменными:
Вывод
Переменная a_var
равна 6
, тогда амплитуда после умножения на 3
будет равна 18
, убедимся в этом на графике результатов симуляции:
Использование разных наборов значений переменных (сохраненных в разных скриптах) позволяет моделировать объекты с одинаковой структурой, но разными параметрами. |
Сохранение результатов симуляции
Можно сохранить результаты симуляции модели в оперативную память Engee с помощью переменной simout. Переменная создается автоматически после завершения симуляции модели при включенной записи сигналов модели. Рассмотрим пример с моделью из предыдущего пункта, но используем созданную ранее переменную m.
После завершения симуляции модели в файловом браузере появится переменная simout, хранящая результаты симуляции. Выгрузим данные из simout в переменную m:
m = collect(simout["newmodel_1/Sine Wave.1"])
Визуализируем результаты симуляции с помощью библиотеки Plots и нашей переменной:
using Plots # подключаем модуль Plots
plot(m.time, m.value) # вывод time (время) и value (значение) из переменной m
После завершения работы кода мы увидим следующий график:
Информация о процессе сохранения результатов симуляции описана в статье о Программная обработка результатов симуляции в Engee.