File size: 5,818 Bytes
6a43f79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import tempfile
import gradio as gr

def solve_and_animate_2d(Lx: float,

                         Ly: float,

                         t_max: float,

                         Gamma: float = 0.1,

                         Nx: int = 50,

                         Ny: int = 50,

                         initial: str = "gaussian",

                         bc: str = "dirichlet",

                         frame_skip: int = 1):
    """

    Solve the 2D heat equation u_t = Gamma*(u_xx + u_yy) and return a GIF animation.

    Initial conditions: {"gaussian", "random", "sinusoidal", "step"}

    Boundary conditions: {"dirichlet", "neumann", "periodic"}

    """
    # Spatial grid
    x = np.linspace(0, Lx, Nx)
    y = np.linspace(0, Ly, Ny)
    dx, dy = x[1] - x[0], y[1] - y[0]
    if dx == 0 or dy == 0:
        raise ValueError("Nx and Ny must be > 1.")

    # Time stepping for stability
    dt = 1.0 / (2 * Gamma * (1/dx**2 + 1/dy**2))
    Nt = int(np.ceil(t_max / dt)) + 1
    rx, ry = Gamma * dt / dx**2, Gamma * dt / dy**2

    # Initial condition
    X, Y = np.meshgrid(x, y, indexing='ij')
    if initial == "gaussian":
        u = np.exp(-(((X - Lx/2)**2 + (Y - Ly/2)**2) / (2*(Lx/10)**2)))
    elif initial == "random":
        u = np.random.rand(Nx, Ny)
    elif initial == "sinusoidal":
        kx, ky = 2 * np.pi / Lx, 2 * np.pi / Ly
        u = np.sin(kx * X) * np.sin(ky * Y)
    elif initial == "step":
        u = np.where((X < Lx/2) & (Y < Ly/2), 1.0, 0.0)
    else:
        raise ValueError(f"Unknown initial condition: {initial}")

    # Storage for solution
    U = np.zeros((Nt, Nx, Ny))
    U[0] = u.copy()

    # Time-stepping loop
    for n in range(1, Nt):
        un = u.copy()
        # Interior update
        u[1:-1, 1:-1] = (
            un[1:-1, 1:-1]
            + rx * (un[2:, 1:-1] - 2 * un[1:-1, 1:-1] + un[:-2, 1:-1])
            + ry * (un[1:-1, 2:] - 2 * un[1:-1, 1:-1] + un[1:-1, :-2])
        )
        # Boundary conditions
        if bc == "dirichlet":
            u[0, :] = u[-1, :] = u[:, 0] = u[:, -1] = 0.0
        elif bc == "neumann":
            u[0, :] = u[1, :]
            u[-1, :] = u[-2, :]
            u[:, 0] = u[:, 1]
            u[:, -1] = u[:, -2]
        elif bc == "periodic":
            u[0, :] = un[-2, :]
            u[-1, :] = un[1, :]
            u[:, 0] = un[:, -2]
            u[:, -1] = un[:, 1]
        else:
            raise ValueError(f"Unknown bc: {bc}")
        U[n] = u.copy()

    # Create animation with Matplotlib
    fig, ax = plt.subplots()
    im = ax.imshow(U[0].T, cmap='viridis', origin='lower', extent=[0, Lx, 0, Ly], vmin=U.min(), vmax=U.max())
    ax.set_title(f"2D Heat Eq — init={initial}, bc={bc}, Gamma={Gamma:.2f}")
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    plt.colorbar(im, ax=ax, label="u")

    # Animation update function
    def update(frame):
        im.set_data(U[frame].T)
        return [im]

    # Subsample frames
    idx = list(range(0, Nt, frame_skip))
    if idx[-1] != Nt - 1:
        idx.append(Nt - 1)

    # Generate animation
    ani = FuncAnimation(fig, update, frames=idx, blit=True)

    # Save as GIF
    with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as tmpfile:
        ani.save(tmpfile.name, writer='pillow', fps=30)
        gif_path = tmpfile.name

    plt.close(fig)
    return gif_path

def gradio_interface(lx, ly, t_max, gamma, nx, ny, initial, bc, frame_skip):
    nx, ny, frame_skip = int(nx), int(ny), int(frame_skip)
    return solve_and_animate_2d(
        Lx=lx, Ly=ly, t_max=t_max, Gamma=gamma, Nx=nx, Ny=ny,
        initial=initial, bc=bc, frame_skip=frame_skip
    )

with gr.Blocks(theme=gr.themes.Soft(), title="2D Heat Simulator") as demo:
    gr.Markdown("# ♨️ 2D Heat Equation Simulator\nAdjust parameters and run the simulation.")
    with gr.Row():
        with gr.Column(scale=1):
            gr.Markdown("## Domain & Grid")
            lx_slider = gr.Slider(0.1, 5.0, 1.0, 0.1, label="Lx")
            ly_slider = gr.Slider(0.1, 5.0, 1.0, 0.1, label="Ly")
            nx_slider = gr.Slider(3, 200, 50, 1, label="Nx")
            ny_slider = gr.Slider(3, 200, 50, 1, label="Ny")

            gr.Markdown("## Simulation")
            t_slider = gr.Slider(0.01, 5.0, 0.5, 0.01, label="t_max")
            gamma_slider = gr.Slider(0.001, 1.0, 0.1, 0.001, label="Gamma")

            gr.Markdown("## Conditions")
            initial_dropdown = gr.Dropdown(
                ["gaussian", "random", "sinusoidal", "step"], "gaussian", label="Initial"
            )
            bc_dropdown = gr.Dropdown(
                ["dirichlet", "neumann", "periodic"], "dirichlet", label="Boundary"
            )

            gr.Markdown("## Animation")
            frame_skip_slider = gr.Slider(1, 50, 5, 1, label="Frame Skip")
            run_btn = gr.Button("Run Simulation", variant="primary")
        with gr.Column(scale=3):
            plot_output = gr.Image(label="Heatmap Animation")
    inputs_list = [lx_slider, ly_slider, t_slider, gamma_slider,
                   nx_slider, ny_slider, initial_dropdown, bc_dropdown, frame_skip_slider]
    run_btn.click(fn=gradio_interface, inputs=inputs_list, outputs=plot_output)
    gr.Examples(
        examples=[
            [1.0, 1.0, 0.5, 0.1, 50, 50, "gaussian", "dirichlet", 5],
            [2.0, 1.0, 1.0, 0.05, 60, 30, "sinusoidal", "periodic", 10],
            [1.0, 1.0, 0.2, 0.2, 80, 80, "step", "neumann", 2],
        ],
        inputs=inputs_list,
        outputs=[plot_output],
        fn=gradio_interface
    )

if __name__ == "__main__":
    demo.launch()