11import numpy as np
22import matplotlib .pyplot as plt
3- from scipy .sparse import diags , kron , eye , csr_matrix
3+ from scipy .sparse import diags
44from scipy .sparse .linalg import spsolve
55from matplotlib .animation import FuncAnimation
66
7- # Global Constants - STABLE VERSION
7+ # Global Constants
88xLength = 10
99yLength = 10
1010maxTime = 0.5
2727# Crank-Nicolson parameters (following your notes)
2828c = diffusivityConstant * timeStepSize / 2 # Note: factor of 2 for Crank-Nicolson
2929cx = c / (spaceStepSize ** 2 )
30- cy = c / (spaceStepSize ** 2 ) # Same as cx since dx = dy
30+ cy = c / (spaceStepSize ** 2 )
3131alpha = 1 + 2 * cx + 2 * cy
3232beta = 1 - 2 * cx - 2 * cy
3333
3838
3939# Initial condition - a hot spot in the center
4040u0 = lambda x , y : 10 * np .exp (- ((x - xLength / 2 )** 2 + (y - yLength / 2 )** 2 ) / 2 )
41-
42- # Boundary conditions based on your MATLAB code
43-
44- # left = @(t,y) 20 + 10*y*(Ly-y)*t^2;
4541left = lambda t , y : 20 + 10 * y * (yLength - y ) * t ** 2
46-
47- # right = @(t, y) 20 + 100*y*(Ly-y)^3*(t-1)^2*(t>1);
4842right = lambda t , y : 20 + 100 * y * (yLength - y )** 3 * (t - 1 )** 2 * (t > 1 )
49-
50- # up = @(t, x) 20 + 5*x*(Lx-x)*t^4;
5143top = lambda t , x : 20 + 5 * x * (xLength - x ) * t ** 4
52-
53- # down = @(t, x) 20;
54- bottom = lambda t , x : 20 # Warm top boundary
44+ bottom = lambda t , x : 20
5545
5646def initMatrix (t_nodes , x_nodes , y_nodes , left , right , bottom , top , u0 , xDomain , yDomain , tDomain ):
5747 """Initialize matrix with boundary and initial conditions"""
@@ -80,29 +70,26 @@ def initMatrix(t_nodes, x_nodes, y_nodes, left, right, bottom, top, u0, xDomain,
8070
8171def build_2d_crank_nicolson_matrix (nx , ny , cx , cy ):
8272 """
83- Build G matrix for (I - c*Δ)U_{τ+1} = (I + c*Δ)U_τ
84- Following notes: -cx*U[i-1,j] + α*U[i,j] - cx*U[i+1,j] - cy*U[i,j-1] - cy*U[i,j+1] = RHS
73+ Build sparse matrix G for: -cx*U[i-1,j] + α*U[i,j] - cx*U[i+1,j] - cy*U[i,j-1] - cy*U[i,j+1] = RHS
8574 """
86- n_interior_x = nx - 2 # Exclude boundaries
75+ n_interior_x = nx - 2
8776 n_interior_y = ny - 2
8877 n_total = n_interior_x * n_interior_y
8978
90- alpha = 1 + 2 * cx + 2 * cy
91-
92- # Build diagonals for the sparse matrix
79+ # Main diagonal: α coefficients
9380 main_diagonal = np .full (n_total , alpha )
9481
95- # x-direction coupling (±1 in flattened index)
82+ # x-direction coupling: -cx coefficients (±1 in flattened index)
9683 x_off_diagonal = np .full (n_total - 1 , - cx )
9784 # Zero out connections across y-boundaries
9885 for i in range (n_interior_x - 1 , n_total - 1 , n_interior_x ):
9986 if i < len (x_off_diagonal ):
10087 x_off_diagonal [i ] = 0
10188
102- # y-direction coupling (±n_interior_x in flattened index)
89+ # y-direction coupling: -cy coefficients (±n_interior_x in flattened index)
10390 y_off_diagonal = np .full (n_total - n_interior_x , - cy )
10491
105- # Assemble matrix
92+ # Assemble sparse matrix
10693 diagonals = [y_off_diagonal , x_off_diagonal , main_diagonal ,
10794 x_off_diagonal [:n_total - 1 ], y_off_diagonal ]
10895 offsets = [- n_interior_x , - 1 , 0 , 1 , n_interior_x ]
@@ -112,42 +99,30 @@ def build_2d_crank_nicolson_matrix(nx, ny, cx, cy):
11299 return G , n_interior_x , n_interior_y
113100
114101def calculateTemperatureCN (U ):
115- """
116- Crank-Nicolson time stepping: (I - c*Δ)U_{τ+1} = (I + c*Δ)U_τ + boundary_terms
117- """
102+ """Crank-Nicolson time stepping"""
118103 nx , ny = numPointsSpace , numPointsSpace
119- beta = 1 - 2 * cx - 2 * cy
120104
121105 G , n_interior_x , n_interior_y = build_2d_crank_nicolson_matrix (nx , ny , cx , cy )
122106
123107 for tau in range (numPointsTime - 1 ):
124- # Extract interior points from previous time step
125- # u_prev_interior = U[tau, 1:-1, 1:-1]
126-
127- # Build RHS: (I + c*Δ)U_τ + boundary contributions
128108 rhs = np .zeros (n_interior_x * n_interior_y )
129109
130110 idx = 0
131- for j in range (1 , ny - 1 ): # j is y-index
132- for i in range (1 , nx - 1 ): # i is x-index
133- # Main term: β*U_τ (comes from (1 - 2cx - 2cy)*U_τ)
111+ for j in range (1 , ny - 1 ):
112+ for i in range (1 , nx - 1 ):
113+ # RHS = β*U_τ + cx*(neighbors_x) + cy*(neighbors_y) + boundary_terms
134114 rhs [idx ] = beta * U [tau , i , j ]
135-
136- # Explicit part: +c*Δ*U_τ
137- # x-direction: +cx*(U[i-1,j] + U[i+1,j]) from previous time
138115 rhs [idx ] += cx * (U [tau , i - 1 , j ] + U [tau , i + 1 , j ])
139- # y-direction: +cy*(U[i,j-1] + U[i,j+1]) from previous time
140116 rhs [idx ] += cy * (U [tau , i , j - 1 ] + U [tau , i , j + 1 ])
141117
142- # Implicit boundary contributions (known at τ+1)
143- # These come from -c*Δ*U_{τ+1} where boundary values are known
144- if i == 1 : # Left boundary
118+ # Boundary contributions
119+ if i == 1 :
145120 rhs [idx ] += cx * U [tau + 1 , 0 , j ]
146- if i == nx - 2 : # Right boundary
121+ if i == nx - 2 :
147122 rhs [idx ] += cx * U [tau + 1 , - 1 , j ]
148- if j == 1 : # Bottom boundary
123+ if j == 1 :
149124 rhs [idx ] += cy * U [tau + 1 , i , 0 ]
150- if j == ny - 2 : # Top boundary
125+ if j == ny - 2 :
151126 rhs [idx ] += cy * U [tau + 1 , i , - 1 ]
152127
153128 idx += 1
@@ -207,8 +182,5 @@ def animate(k):
207182 return plot_surface (tempMatrix [k ], k , ax )
208183
209184 anim = FuncAnimation (fig , animate , interval = 100 , frames = min (numPointsTime , 200 ), repeat = True )
210-
211- # Optionally save animation
212185 anim .save ("heat_equation_2d_crank_nicolson.gif" , writer = 'pillow' , fps = 10 )
213-
214186 plt .show ()
0 commit comments