harishaseebat92 commited on
Commit
eecc35c
·
verified ·
1 Parent(s): 37fa40d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +26 -79
app.py CHANGED
@@ -20,19 +20,12 @@ def solve_3d_heat_equation(Lx: float, Ly: float, Lz: float,
20
  z = np.linspace(0, Lz, Nz)
21
 
22
  # Corrected dx, dy, dz calculation
23
- if Nx > 1: dx = x[1] - x
24
- else: dx = Lx # Or handle as an error / specific case if Nx=1
25
- if Ny > 1: dy = y[1] - y
26
- else: dy = Ly
27
- if Nz > 1: dz = z[1] - z
28
- else: dz = Lz
29
-
30
- if dx == 0 or dy == 0 or dz == 0: # This check might need adjustment if Nx/Ny/Nz=1 is allowed and handled differently
31
- # If Nx, Ny, or Nz is 1, dx, dy, or dz could be the length itself, or this indicates an issue.
32
- # For FTCS, we need at least 3 points in a dimension to compute spatial derivatives,
33
- # unless boundary conditions handle the 1-point or 2-point cases specifically.
34
- # The current loop u[1:-1,...] assumes at least 3 points.
35
- raise ValueError("Nx, Ny, and Nz must be > 1 for the current FTCS scheme. Or dx/dy/dz became zero.")
36
 
37
  # Stability condition for 3D FTCS scheme
38
  dt = 0.5 / (Gamma * (1/dx**2 + 1/dy**2 + 1/dz**2))
@@ -43,7 +36,7 @@ def solve_3d_heat_equation(Lx: float, Ly: float, Lz: float,
43
  X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
44
 
45
  if initial == "gaussian":
46
- u = np.exp(-(((X - Lx/2)**2 + (Y - Ly/2)**2 + (Z - Lz/2)**2) / (2*(max(Lx,Ly,Lz)/10)**2))) # Adjusted sigma
47
  elif initial == "random":
48
  u = np.random.rand(Nx, Ny, Nz)
49
  elif initial == "sinusoidal":
@@ -57,11 +50,10 @@ def solve_3d_heat_equation(Lx: float, Ly: float, Lz: float,
57
  raise ValueError(f"Unknown initial condition: {initial}")
58
 
59
  U = np.zeros((Nt, Nx, Ny, Nz))
60
- U = u.copy() # Store initial condition
61
 
62
  for n in range(1, Nt):
63
  un = u.copy()
64
- # Check if dimensions are large enough for slicing
65
  if Nx > 2 and Ny > 2 and Nz > 2:
66
  u[1:-1, 1:-1, 1:-1] = (
67
  un[1:-1, 1:-1, 1:-1]
@@ -69,25 +61,18 @@ def solve_3d_heat_equation(Lx: float, Ly: float, Lz: float,
69
  + ry * (un[1:-1, 2:, 1:-1] - 2 * un[1:-1, 1:-1, 1:-1] + un[1:-1, :-2, 1:-1])
70
  + rz * (un[1:-1, 1:-1, 2:] - 2 * un[1:-1, 1:-1, 1:-1] + un[1:-1, 1:-1, :-2])
71
  )
72
- else:
73
- # If any dimension is too small for the [1:-1] slice,
74
- # the heat equation update needs to be handled differently (e.g. only boundaries apply)
75
- # For simplicity, we assume Nx,Ny,Nz >=3 for internal point updates.
76
- # Otherwise, u remains largely unchanged except by boundary conditions.
77
- pass
78
-
79
 
80
  if bc == "dirichlet":
81
  if Nx > 0: u[0, :, :] = u[-1, :, :] = 0.0
82
  if Ny > 0: u[:, 0, :] = u[:, -1, :] = 0.0
83
  if Nz > 0: u[:, :, 0] = u[:, :, -1] = 0.0
84
- elif bc == "neumann": # Zero flux
85
  if Nx > 1: u[0, :, :] = u[1, :, :]; u[-1, :, :] = u[-2, :, :]
86
  if Ny > 1: u[:, 0, :] = u[:, 1, :]; u[:, -1, :] = u[:, -2, :]
87
  if Nz > 1: u[:, :, 0] = u[:, :, 1]; u[:, :, -1] = u[:, :, -2]
88
  elif bc == "periodic":
89
  if Nx > 1: u[0, :, :] = un[-2, :, :]; u[-1, :, :] = un[1, :, :]
90
- if Ny > 1: u[:, 0, :] = un[:, -2, :]; u[:, -1, :] = un[:, 1, :]
91
  if Nz > 1: u[:, :, 0] = un[:, :, -2]; u[:, :, -1] = un[:, :, 1]
92
  else:
93
  raise ValueError(f"Unknown bc: {bc}")
@@ -99,27 +84,14 @@ def create_animation_gif_3d_slice(U, Lx, Ly, Lz, initial, bc, Gamma, frame_skip,
99
  Nt, Nx, Ny, Nz = U.shape
100
  fig, ax = plt.subplots()
101
 
102
- # Ensure Nz is valid for slicing
103
  slice_z_idx = Nz // 2 if Nz > 0 else 0
104
  z_coord_slice = np.linspace(0, Lz, Nz)[slice_z_idx] if Nz > 0 else 0
105
 
106
- if Nt == 0: # Should not happen if solve_3d_heat_equation guarantees Nt >= 1
107
- # Create a blank image or raise an error
108
- # For now, let's assume Nt >= 1 based on solve_3d_heat_equation
109
- # If this occurs, U.min()/max() and U will fail.
110
- # Returning a placeholder or erroring out early is better.
111
- # This path indicates an issue upstream.
112
- # For robustness, one might return a path to a pre-made "error" GIF.
113
- # However, given Nt = ceil(t_max/dt) + 1, Nt is always >= 1.
114
- pass
115
-
116
-
117
- data_slice = U[0, :, :, slice_z_idx].T if Nt > 0 and Nz > 0 else np.zeros((Ny, Nx)) # Handle empty slice
118
 
119
- # Handle U being potentially empty or having non-finite values for min/max
120
  vmin_val = U.min() if Nt > 0 and U.size > 0 and np.all(np.isfinite(U)) else 0
121
  vmax_val = U.max() if Nt > 0 and U.size > 0 and np.all(np.isfinite(U)) else 1
122
- if vmin_val == vmax_val: vmax_val = vmin_val + 1 # Avoid error in imshow if all values are same
123
 
124
  im = ax.imshow(data_slice, cmap='viridis', origin='lower',
125
  extent=[0, Lx, 0, Ly], vmin=vmin_val, vmax=vmax_val)
@@ -131,45 +103,22 @@ def create_animation_gif_3d_slice(U, Lx, Ly, Lz, initial, bc, Gamma, frame_skip,
131
  def update(frame):
132
  if Nz > 0:
133
  im.set_data(U[frame, :, :, slice_z_idx].T)
134
- else: # Should not happen if Nz is reasonably set
135
- im.set_data(np.zeros((Ny, Nx))) # Placeholder for empty Z dimension
136
  return [im]
137
 
138
  # Corrected idx generation
139
- if Nt == 0:
140
- idx =
141
- elif Nt == 1:
142
- idx =
143
- else: # Nt > 1
144
- current_frame_skip = max(1, frame_skip) # Ensure frame_skip is at least 1
145
  idx = list(range(0, Nt, current_frame_skip))
146
-
147
- if 0 not in idx : # Should always be true for range(0,...) unless Nt=0
148
- idx.insert(0,0)
149
-
150
  if (Nt - 1) not in idx:
151
  idx.append(Nt - 1)
152
-
153
- idx = sorted(list(set(idx)))
154
-
155
- # FuncAnimation requires at least one frame. If Nt=0, idx is empty.
156
- # solve_3d_heat_equation ensures Nt >= 1, so idx will have at least .
157
- if not idx and Nt > 0: # Fallback, though current logic should prevent this if Nt > 0
158
- idx =
159
-
160
- if not idx: # If Nt is 0 and idx is empty
161
- # Create a dummy animation or return a placeholder path
162
- # For now, this will likely cause FuncAnimation to error if idx is empty.
163
- # However, as Nt >= 1, idx should not be empty.
164
- # If it could be, one might do:
165
- # if not idx: fig.savefig(tmp_path_for_static_image); return tmp_path
166
- pass
167
-
168
 
169
- ani = FuncAnimation(fig, update, frames=idx if idx else , blit=True) # Ensure frames is not empty
170
 
171
  with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as tmpfile:
172
- ani.save(tmpfile.name, writer='pillow', fps=max(1, 30 // max(1,frame_skip)))
173
  gif_path = tmpfile.name
174
 
175
  plt.close(fig)
@@ -178,7 +127,7 @@ def create_animation_gif_3d_slice(U, Lx, Ly, Lz, initial, bc, Gamma, frame_skip,
178
  # --- Plotly Figure Generator (3D Volume) ---
179
  def create_plotly_figure_3d(u_3d, Lx, Ly, Lz, time_label):
180
  Nx, Ny, Nz = u_3d.shape
181
- if Nx==0 or Ny==0 or Nz==0: # Handle empty u_3d
182
  return go.Figure(layout_title_text=f"3D Heat Distribution (No Data) at t={time_label}")
183
 
184
  x_coords = np.linspace(0, Lx, Nx)
@@ -187,10 +136,9 @@ def create_plotly_figure_3d(u_3d, Lx, Ly, Lz, time_label):
187
 
188
  X, Y, Z = np.meshgrid(x_coords, y_coords, z_coords, indexing='ij')
189
 
190
- # Ensure u_3d is finite for min/max
191
  vmin = np.min(u_3d[np.isfinite(u_3d)]) if np.any(np.isfinite(u_3d)) else 0
192
  vmax = np.max(u_3d[np.isfinite(u_3d)]) if np.any(np.isfinite(u_3d)) else 1
193
- if vmin == vmax: vmax = vmin + 0.1 # Avoid issues with single value
194
 
195
  fig = go.Figure(data=go.Volume(
196
  x=X.flatten(),
@@ -216,7 +164,6 @@ def create_plotly_figure_3d(u_3d, Lx, Ly, Lz, time_label):
216
 
217
  # --- Simulation Runner (Extracted Logic for 3D) ---
218
  def run_simulation_3d(lx, ly, lz, t_max, gamma, nx, ny, nz, initial, bc, frame_skip):
219
- # Ensure grid dimensions are at least 3 for FTCS internal points, or handle 1D/2D cases if needed
220
  nx = max(3, int(nx))
221
  ny = max(3, int(ny))
222
  nz = max(3, int(nz))
@@ -225,7 +172,7 @@ def run_simulation_3d(lx, ly, lz, t_max, gamma, nx, ny, nz, initial, bc, frame_s
225
  Lx=lx, Ly=ly, Lz=lz, t_max=t_max, Gamma=gamma,
226
  Nx=nx, Ny=ny, Nz=nz, initial=initial, bc=bc
227
  )
228
- Nt = U.shape
229
 
230
  idx0 = 0
231
  idx1 = round((Nt - 1) / 4) if Nt > 1 else 0
@@ -250,7 +197,6 @@ def gradio_interface_3d(lx, ly, lz, t_max, gamma, nx, ny, nz, initial, bc, frame
250
  nx_int, ny_int, nz_int = int(nx), int(ny), int(nz)
251
  frame_skip_int = max(1, int(frame_skip))
252
 
253
- # Ensure minimal grid dimensions for the current simulation logic
254
  nx_int = max(3, nx_int)
255
  ny_int = max(3, ny_int)
256
  nz_int = max(3, nz_int)
@@ -305,9 +251,11 @@ with gr.Blocks(theme=gr.themes.Soft(), title="3D Heat Simulator") as demo:
305
  run_btn.click(fn=gradio_interface_3d, inputs=inputs_list, outputs=outputs_list)
306
 
307
  gr.Examples(
308
- examples=[1.0, 1.0, 1.0, 0.1, 0.1, 20, 20, 20, "gaussian", "dirichlet", 5],
 
309
  [1.5, 1.0, 0.5, 0.2, 0.05, 25, 20, 15, "sinusoidal", "periodic", 10],
310
- [1.0, 1.0, 1.0, 0.05, 0.2, 15, 15, 15, "step", "neumann", 2],
 
311
  inputs=inputs_list,
312
  outputs=outputs_list,
313
  fn=gradio_interface_3d,
@@ -335,7 +283,6 @@ class SimulationParams3D(BaseModel):
335
  @app.post("/simulate_3d")
336
  def simulate_3d_api(params: SimulationParams3D):
337
  params.frame_skip = max(1, params.frame_skip)
338
- # Ensure minimal grid dimensions for API calls too
339
  params.nx = max(3, params.nx)
340
  params.ny = max(3, params.ny)
341
  params.nz = max(3, params.nz)
 
20
  z = np.linspace(0, Lz, Nz)
21
 
22
  # Corrected dx, dy, dz calculation
23
+ dx = x[1] - x[0] if Nx > 1 else Lx
24
+ dy = y[1] - y[0] if Ny > 1 else Ly
25
+ dz = z[1] - z[0] if Nz > 1 else Lz
26
+
27
+ if dx == 0 or dy == 0 or dz == 0:
28
+ raise ValueError("Grid spacing (dx, dy, dz) cannot be zero. Ensure Nx, Ny, Nz > 1.")
 
 
 
 
 
 
 
29
 
30
  # Stability condition for 3D FTCS scheme
31
  dt = 0.5 / (Gamma * (1/dx**2 + 1/dy**2 + 1/dz**2))
 
36
  X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
37
 
38
  if initial == "gaussian":
39
+ u = np.exp(-(((X - Lx/2)**2 + (Y - Ly/2)**2 + (Z - Lz/2)**2) / (2*(max(Lx,Ly,Lz)/10)**2)))
40
  elif initial == "random":
41
  u = np.random.rand(Nx, Ny, Nz)
42
  elif initial == "sinusoidal":
 
50
  raise ValueError(f"Unknown initial condition: {initial}")
51
 
52
  U = np.zeros((Nt, Nx, Ny, Nz))
53
+ U[0] = u.copy() # Store initial condition
54
 
55
  for n in range(1, Nt):
56
  un = u.copy()
 
57
  if Nx > 2 and Ny > 2 and Nz > 2:
58
  u[1:-1, 1:-1, 1:-1] = (
59
  un[1:-1, 1:-1, 1:-1]
 
61
  + ry * (un[1:-1, 2:, 1:-1] - 2 * un[1:-1, 1:-1, 1:-1] + un[1:-1, :-2, 1:-1])
62
  + rz * (un[1:-1, 1:-1, 2:] - 2 * un[1:-1, 1:-1, 1:-1] + un[1:-1, 1:-1, :-2])
63
  )
 
 
 
 
 
 
 
64
 
65
  if bc == "dirichlet":
66
  if Nx > 0: u[0, :, :] = u[-1, :, :] = 0.0
67
  if Ny > 0: u[:, 0, :] = u[:, -1, :] = 0.0
68
  if Nz > 0: u[:, :, 0] = u[:, :, -1] = 0.0
69
+ elif bc == "neumann":
70
  if Nx > 1: u[0, :, :] = u[1, :, :]; u[-1, :, :] = u[-2, :, :]
71
  if Ny > 1: u[:, 0, :] = u[:, 1, :]; u[:, -1, :] = u[:, -2, :]
72
  if Nz > 1: u[:, :, 0] = u[:, :, 1]; u[:, :, -1] = u[:, :, -2]
73
  elif bc == "periodic":
74
  if Nx > 1: u[0, :, :] = un[-2, :, :]; u[-1, :, :] = un[1, :, :]
75
+ if Ny > 1: u[:, 0, :] = un[:, -2, :]; u[-1, :] = un[:, 1, :]
76
  if Nz > 1: u[:, :, 0] = un[:, :, -2]; u[:, :, -1] = un[:, :, 1]
77
  else:
78
  raise ValueError(f"Unknown bc: {bc}")
 
84
  Nt, Nx, Ny, Nz = U.shape
85
  fig, ax = plt.subplots()
86
 
 
87
  slice_z_idx = Nz // 2 if Nz > 0 else 0
88
  z_coord_slice = np.linspace(0, Lz, Nz)[slice_z_idx] if Nz > 0 else 0
89
 
90
+ data_slice = U[0, :, :, slice_z_idx].T if Nt > 0 and Nz > 0 else np.zeros((Ny, Nx))
 
 
 
 
 
 
 
 
 
 
 
91
 
 
92
  vmin_val = U.min() if Nt > 0 and U.size > 0 and np.all(np.isfinite(U)) else 0
93
  vmax_val = U.max() if Nt > 0 and U.size > 0 and np.all(np.isfinite(U)) else 1
94
+ if vmin_val == vmax_val: vmax_val = vmin_val + 1
95
 
96
  im = ax.imshow(data_slice, cmap='viridis', origin='lower',
97
  extent=[0, Lx, 0, Ly], vmin=vmin_val, vmax=vmax_val)
 
103
  def update(frame):
104
  if Nz > 0:
105
  im.set_data(U[frame, :, :, slice_z_idx].T)
 
 
106
  return [im]
107
 
108
  # Corrected idx generation
109
+ if Nt <= 1:
110
+ idx = [0] # Only initial frame if Nt is 0 or 1
111
+ else:
112
+ current_frame_skip = max(1, frame_skip)
 
 
113
  idx = list(range(0, Nt, current_frame_skip))
 
 
 
 
114
  if (Nt - 1) not in idx:
115
  idx.append(Nt - 1)
116
+ idx = sorted(list(set(idx)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
+ ani = FuncAnimation(fig, update, frames=idx, blit=True)
119
 
120
  with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as tmpfile:
121
+ ani.save(tmpfile.name, writer='pillow', fps=max(1, 30 // max(1, frame_skip)))
122
  gif_path = tmpfile.name
123
 
124
  plt.close(fig)
 
127
  # --- Plotly Figure Generator (3D Volume) ---
128
  def create_plotly_figure_3d(u_3d, Lx, Ly, Lz, time_label):
129
  Nx, Ny, Nz = u_3d.shape
130
+ if Nx == 0 or Ny == 0 or Nz == 0:
131
  return go.Figure(layout_title_text=f"3D Heat Distribution (No Data) at t={time_label}")
132
 
133
  x_coords = np.linspace(0, Lx, Nx)
 
136
 
137
  X, Y, Z = np.meshgrid(x_coords, y_coords, z_coords, indexing='ij')
138
 
 
139
  vmin = np.min(u_3d[np.isfinite(u_3d)]) if np.any(np.isfinite(u_3d)) else 0
140
  vmax = np.max(u_3d[np.isfinite(u_3d)]) if np.any(np.isfinite(u_3d)) else 1
141
+ if vmin == vmax: vmax = vmin + 0.1
142
 
143
  fig = go.Figure(data=go.Volume(
144
  x=X.flatten(),
 
164
 
165
  # --- Simulation Runner (Extracted Logic for 3D) ---
166
  def run_simulation_3d(lx, ly, lz, t_max, gamma, nx, ny, nz, initial, bc, frame_skip):
 
167
  nx = max(3, int(nx))
168
  ny = max(3, int(ny))
169
  nz = max(3, int(nz))
 
172
  Lx=lx, Ly=ly, Lz=lz, t_max=t_max, Gamma=gamma,
173
  Nx=nx, Ny=ny, Nz=nz, initial=initial, bc=bc
174
  )
175
+ Nt = U.shape[0]
176
 
177
  idx0 = 0
178
  idx1 = round((Nt - 1) / 4) if Nt > 1 else 0
 
197
  nx_int, ny_int, nz_int = int(nx), int(ny), int(nz)
198
  frame_skip_int = max(1, int(frame_skip))
199
 
 
200
  nx_int = max(3, nx_int)
201
  ny_int = max(3, ny_int)
202
  nz_int = max(3, nz_int)
 
251
  run_btn.click(fn=gradio_interface_3d, inputs=inputs_list, outputs=outputs_list)
252
 
253
  gr.Examples(
254
+ examples=[
255
+ [1.0, 1.0, 1.0, 0.1, 0.1, 20, 20, 20, "gaussian", "dirichlet", 5],
256
  [1.5, 1.0, 0.5, 0.2, 0.05, 25, 20, 15, "sinusoidal", "periodic", 10],
257
+ [1.0, 1.0, 1.0, 0.05, 0.2, 15, 15, 15, "step", "neumann", 2]
258
+ ],
259
  inputs=inputs_list,
260
  outputs=outputs_list,
261
  fn=gradio_interface_3d,
 
283
  @app.post("/simulate_3d")
284
  def simulate_3d_api(params: SimulationParams3D):
285
  params.frame_skip = max(1, params.frame_skip)
 
286
  params.nx = max(3, params.nx)
287
  params.ny = max(3, params.ny)
288
  params.nz = max(3, params.nz)