Использование Genie для визуализации действия линейного оператора¶
Статья посвящена применению GenieFramework для разработки интерактивного веб-приложения, демонстрирующего действие линейного оператора. В качестве примера рассмотрено преобразование набора точек, представляющих круг, с использованием матрицы 2×2. Для реализации задействованы пакеты Stipple и PlotlyBase. В тексте подробно описаны ключевые элементы кода с пояснениями, следующими за соответствующими фрагментами. Особое внимание уделено обоснованию применения реактивных макросов @in
и @out
для управления элементами матрицы (m11
, m12
, m21
, m22
), а также рассмотрены возможности использования пользовательских типов, таких как mutable struct
, на основе документации Stipple.
Концепция приложения¶
Приложение иллюстрирует действие матрицы 2×2 как линейного оператора на двумерный набор точек. Исходно точки формируют круг радиуса 1, а пользователь может изменять элементы матрицы с помощью ползунков, наблюдая эффекты преобразований, такие как растяжение, сжатие или поворот. Данный подход обладает образовательным потенциалом, предоставляя интерактивный инструмент для изучения линейной алгебры.
Создание круга из набора точек¶
using GenieFramework, Stipple, Stipple.ReactiveTools, StippleUI, PlotlyBase
const circ_range = -1:0.05:1
const circle = [[i, j] for i in circ_range for j in circ_range if i^2 + j^2 <= 1]
const x_axis_points = findall(x -> x[1] == 0 && x[2] >= 0, circle)
const y_axis_points = findall(x -> x[2] == 0 && x[1] >= 0, circle)
const circle_matrix = Base.stack(circle)
Этот фрагмент формирует исходные данные для визуализации. Определён диапазон circ_range
от -1 до 1 с шагом 0.05 для координат x и y. С помощью спискового включения сгенерированы пары координат [i, j]
, отфильтрованные по уравнению круга i^2 + j^2 <= 1
, что даёт набор точек, аппроксимирующий круг радиуса 1. Переменные x_axis_points
и y_axis_points
содержат индексы точек, лежащих на положительных частях осей Y и X соответственно, для выделения базисных векторов. Функция Base.stack
преобразует список координат в матрицу, где первая строка — x-координаты, а вторая — y-координаты. Константы (const
) используются для неизменяемых данных. Дискретный шаг приводит к некоторой ступенчатости контура круга, но метод прост в реализации.
Разработка функции преобразования графика¶
function create_plot_data(m11::Float64, m12::Float64, m21::Float64, m22::Float64)
transformed = [m11 m12; m21 m22] * circle_matrix
[
scatter(x=transformed[1, :], y=transformed[2, :], mode="markers", name="Точки круга"),
scatter(x=transformed[1, x_axis_points], y=transformed[2, x_axis_points], name="Ось Y"),
scatter(x=transformed[1, y_axis_points], y=transformed[2, y_axis_points], name="Ось X", aspect_ratio=:equal)
]
end
const initial_plot_data = create_plot_data(1.0, 0.0, 0.0, 1.0)
Функция create_plot_data
отвечает за преобразование точек и подготовку данных для графика. Принимает четыре аргумента — элементы матрицы 2×2 (m11
, m12
, m21
, m22
). Выполняет умножение матрицы на circle_matrix
, получая новые координаты точек в transformed
. Возвращает массив из трёх объектов scatter
: все точки круга, точки на оси Y и точки на оси X. Параметр aspect_ratio=:equal
обеспечивает равномерный масштаб осей. Константа initial_plot_data
задаёт начальное состояние графика с единичной матрицей, не изменяющей круг.
Настройка макета графика¶
const plot_layout = PlotlyBase.Layout(
title="Линейное преобразование круга",
xaxis=attr(title="Ось X", showgrid=true, range=[-2, 2]),
yaxis=attr(title="Ось Y", showgrid=true, range=[-2, 2]),
width=600, height=550
)
Фрагмент определяет макет графика с помощью PlotlyBase.Layout
. Устанавливаются заголовок, подписи осей, сетка и диапазон значений от -2 до 2. Размеры графика фиксированы: ширина 600 пикселей, высота 550 пикселей. Макет объявлен как константа, так как не изменяется в процессе работы приложения.
Обеспечение реактивности¶
@app begin
@in m11 = 1.0
@in m12 = 0.0
@in m21 = 0.0
@in m22 = 1.0
@out plot_data = initial_plot_data
@out plot_layout = plot_layout
@onchange m11, m12, m21, m22 begin
plot_data = create_plot_data(m11, m12, m21, m22)
end
end
Блок @app
определяет реактивную модель приложения. Макросы @in
объявляют элементы матрицы как входные переменные с начальными значениями, соответствующими единичной матрице. Макрос @out
задаёт выходные данные: plot_data
для графика и plot_layout
для макета. Макрос @onchange
отслеживает изменения значений m11
, m12
, m21
, m22
и вызывает create_plot_data
для обновления plot_data
.
Настройка пользовательского интерфейса¶
function ui()
sliders = [row([column([h6("m$(i)$(j)={{m$(i)$(j)}}"), slider(-2:0.1:2, Symbol("m$(i)$(j)"), color="purple")], size=3) for j in 1:2]) for i in 1:2]
[
row([
column(sliders, size=4),
column(plot(:plot_data, layout=:plot_layout))
], size=3)
]
end
@page("/", ui)
Функция ui
формирует интерфейс. Переменная sliders
создаёт массив из двух строк, каждая из которых содержит два ползунка с подписями (m11
, m12
, m21
, m22
). Диапазон ползунков — от -2 до 2 с шагом 0.1. Интерфейс состоит из строки (row
) с двумя столбцами: слева ползунки, справа график, отображаемый через plot
. Макрос @page
привязывает интерфейс к корневому маршруту "/".
Необходимость применения @in
и @out
¶
Реактивность в Stipple¶
Stipple реализует реактивную модель, обеспечивая синхронизацию данных и интерфейса. Макросы @in
и @out
интегрируют элементы в эту модель. @in
связывает данные с элементами интерфейса (ползунками), позволяя пользователю их изменять. @out
обновляет выходные данные (график) при изменении состояния. При использовании стандартных объявлений, например m11 = 1.0
, элементы интерфейса теряют связь с значениями, исключая их изменение; @onchange
не реагирует, так как Stipple не отслеживает такие данные; интерактивность нарушается, поскольку значения не передаются в JavaScript.
Альтернатива с пользовательскими типами¶
Согласно документации Stipple (Типы переменных в Stipple), возможно использование mutable struct
как реактивных переменных:
mutable struct MatrixState
m11::Float64
m12::Float64
m21::Float64
m22::Float64
end
@app begin
@in state = MatrixState(1.0, 0.0, 0.0, 1.0)
@out plot_data = initial_plot_data
@out plot_layout = plot_layout
@onchange state begin
plot_data = create_plot_data(state.m11, state.m12, state.m21, state.m22)
end
end
Структура MatrixState
объявлена через @in
, что позволяет Stipple отслеживать изменения её полей. Однако в данном приложении предпочтение отдано индивидуальным переменным (@in m11
и т.д.), так как это упрощает управление элементами матрицы через отдельные ползунки.
Без @in
и @out
элементы матрицы остаются как бы изолированными в Julia, не взаимодействуя с интерфейсом. Реактивные макросы или mutable struct
с @in
/@out
обеспечивают необходимую связь, при этом выбор подхода зависит от структуры данных и требований интерфейса.
Для запуска приложения, воспользуемся уже знакомой по предыдущим статьям конструкцией:
using Markdown
cd(@__DIR__)
app_url = string(engee.genie.start(string(@__DIR__,"/app.jl")))
Markdown.parse(match(r"'(https?://[^']+)'",app_url)[1])
Заключение¶
В статье продемонстрировано применение GenieFramework для создания интерактивного приложения, визуализирующего действие линейного оператора. Реактивные макросы @in
и @out
обеспечили связь между интерфейсом и логикой, а возможность использования mutable struct
предоставляет гибкость для сложных сценариев. Приложение подчёркивает возможности Genie для разработки образовательных инструментов.