Engee 文档
Notebook

使用 Genie 将线性算子的作用可视化

本文主要介绍使用 GenieFramework 开发交互式网络应用程序,演示线性算子的作用。以使用 2×2 矩阵对代表圆的点集进行变换为例进行说明。实现过程中使用了 Stipple 和 PlotlyBase 软件包。文中详细描述了代码的关键要素,并在相关片段后附有解释。其中特别关注了使用反应宏@in@out 控制矩阵元素的原理 (m11,m12,m21,m22),并讨论了根据 Stipple 文档使用自定义类型(如mutable struct )的问题。


应用概念

circle.png

该应用程序展示了 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 &amp;&amp; x[2] >= 0, circle)
const y_axis_points = findall(x -> x[2] == 0 &amp;&amp; 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_pointsy_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 可提供必要的连接,具体方法的选择取决于数据结构和界面要求。


要运行应用程序,让我们使用 以前的文章 中已经熟悉的结构:

In [ ]:
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 在开发教育工具方面的潜力。