AnyMath 文档
Notebook

利用Genie可视化线性算子的作用

本文重点介绍如何利用 GenieFramework 开发一个交互式 Web 应用程序,以演示线性算子的作用。作为示例,我们将探讨使用 2×2 矩阵对表示圆的一组点进行变换。实现过程中使用了 Stipple 和 PlotlyBase 包。 本文详细描述了代码的关键要素,并在相关代码片段旁提供了说明。特别着重阐述了使用响应式宏@in@out 来控制矩阵元素(m11m12m21m22 )的理由,以及根据Stipple文档使用自定义类型(如mutable struct )的可能性。

: 应用概念

circle.png

附录演示了2x2矩阵作为线性算子对二维点集的作用效果。 最初,这些点构成一个半径为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)

此代码片段提供了可视化的初始数据。 x 和 y 坐标的取值范围定义为circ_range –1,步长为 0.05。坐标对通过列表包含函数 [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```). Performs matrix multiplication bycircle_matrix, obtaining the new coordinates of the points in transformed. Returns an array of three objects scatter: all points on the circle, points on the Y-axis and points on the X-axis. The parameter aspect_ratio=:equalprovides a uniform scale for the axes. The constant initial_plot_data ` sets the initial state of a graph using a unit matrix that does not alter the circle.

---### : Configuring the graph layout

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
)

The fragment defines the graph layout using PlotlyBase.Layout``. The heading, axis labels, grid and value range from -2 to 2 are set. The graph dimensions are fixed: width 600 pixels, height 550 pixels. The layout is declared as a constant, as it does not change whilst the application is running.

Ensuring reactivity

@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

The block @app defines the reactive application model. The macros @in declare the matrix elements as input variables with initial values corresponding to the identity matrix. The macro @out sets the output data: plot_data for the schedule and plot_layout for the layout. The macro @onchange tracks changes in the values m11, m12, m21 and m22, and causes create_plot_data to update plot_data.

Customising the user interface

function ui()
    sliders = [row([column([h6("m$(i)$(j)={{m$(i)$(j)}}"), slider(-2:0.1:2, Symbol("m (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)

The function ui forms the interface. The variable sliders creates an array of two lines, each of which contains two sliders with captions (m11, m12, m21, m22). The sliders range from -2 to 2 in increments of 0.1. The interface consists of a row (row) with two columns: sliders on the left and a graph on the right, displayed via plot. The macro @page binds the interface to the root route "/".

The need for application- @in and @out#### Reactivity in Stipple

Stipple implements a reactive model, ensuring the synchronisation of data and the interface. Macros @in and @out integrate the elements into this model. @in binds data to interface elements (sliders), allowing the user to change them. @out updates the output data (graph) when the state changes. When using standard ads, for example m11 = 1.0, the interface elements lose their connection with the values, preventing them from being modified; @onchange it does not respond because Stipple does not track such data; interactivity is disrupted because values are not passed to JavaScript.#### An alternative with custom types

According to the Stipple documentation (Types of variables in Stipple), it is possible to use mutable struct as reactive variables:

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 可提供必要的通信功能,具体采用哪种方法取决于数据结构和界面要求。


要启动该应用程序,我们将采用前文中已介绍的、经过精心设计的方案:

In [ ]:
using Markdown
cd(@__DIR__)

app_url = string(engee.genie.start(string(@__DIR__,"/app.jl")))

Markdown.parse(match(r"'(https?://[^']+)'",app_url)[1])
Out[0]:

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在开发教育工具方面的能力。---