使用 Genie 将线性算子的作用可视化¶
本文主要介绍使用 GenieFramework 开发交互式网络应用程序,演示线性算子的作用。以使用 2×2 矩阵对代表圆的点集进行变换为例进行说明。实现过程中使用了 Stipple 和 PlotlyBase 软件包。文中详细描述了代码的关键要素,并在相关片段后附有解释。其中特别关注了使用反应宏@in
和@out
控制矩阵元素的原理 (m11
,m12
,m21
,m22
),并讨论了根据 Stipple 文档使用自定义类型(如mutable struct
)的问题。
应用概念¶
该应用程序展示了 2×2 矩阵作为线性运算符对二维点集的作用。起初,点构成一个半径为 1 的圆,用户可以使用滑块修改矩阵元素,观察拉伸、压缩或旋转等变换的效果。这种方法为学习线性代数提供了一种互动工具,具有教育潜力。
从一组点创建一个圆¶
使用 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)
此片段生成用于可视化的原始数据。它为 x 和 y 坐标定义了从 -1 到 1 的范围circ_range
,步长为 0.05。列表包含用于生成坐标对[i, j]
,并通过圆方程i^2 + j^2 <= 1
进行过滤,得到一组近似半径为 1 的圆的点。变量x_axis_points
和y_axis_points
分别包含位于 Y 轴和 X 轴正部分的点的索引,用于选择基向量。函数Base.stack
将坐标列表转换为矩阵,其中第一行为 x 坐标,第二行为 y 坐标。常量 (const
) 用于不变数据。离散步进会对圆轮廓产生一些步进,但该方法的实现非常简单。
开发图形转换功能¶
函数 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="Circle Points")、
scatter(x=transformed[1, x_axis_points], y=transformed[2, x_axis_points], name="Y-axis")、
scatter(x=transformed[1, y_axis_points], y=transformed[2, y_axis_points], name="X-axis", aspect_ratio=:equal)
]
结束
const initial_plot_data = create_plot_data(1.0, 0.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)
结束
结束
程序块@app
定义了应用程序的反应模型。宏@in
将矩阵元素声明为输入变量,其初始值与单位矩阵相对应。宏@out
定义了输出:plot_data
表示图形,plot_layout
表示布局。@onchange
宏跟踪m11
,m12
,m21
,m22
的值变化,并调用create_plot_data
更新plot_data
。
自定义用户界面¶
函数 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 实现了一种反应模型,使数据和界面保持同步。宏@in
和@out
将元素整合到这一模型中。@in
将数据与界面元素(滑块)相连,允许用户更改它们。当状态发生变化时,@out
会更新输出数据(图形)。在使用标准声明(如m11 = 1.0
)时,界面元素会失去与数值的关联,从而无法更改;由于 Stipple 不会跟踪此类数据,因此@onchange
没有响应;由于没有将数值传递给 JavaScript,因此交互性被破坏。
自定义类型的替代方案¶
根据 Stipple 文档(Stipple 中的变量类型),可以将mutable struct
用作反应式变量:
可变结构 MatrixState
m11::Float64
m12::Float64
m21::Float64
m22::Float64
结束
@app begin
@in state = MatrixState(1.0, 0.0, 0.0, 0.0, 1.0)
@out plot_data = initial_plot_data
@out plot_layout = plot_layout
@onchange 状态开始
plot_data = create_plot_data(state.m11, state.m12, state.m21, state.m22)
结束
结束
MatrixState
结构是通过@in
声明的,这使得 Stipple 可以跟踪其字段的变化。不过,本应用程序更倾向于使用单个变量(@in m11
等),因为这样可以更方便地通过单个滑块控制矩阵中的元素。
如果没有@in
和@out
,矩阵元素就会在 Julia 中处于孤立状态,无法与界面互动。使用@in
/@out
的反应式宏或mutable struct
可提供必要的连接,具体方法的选择取决于数据结构和界面要求。
要运行应用程序,让我们使用 以前的文章 中已经熟悉的结构:
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 在开发教育工具方面的潜力。