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()