Numerical-Simulation/HW4/case1.py

422 lines
16 KiB
Python

# case1.py
import numpy as np
import matplotlib.pyplot as plt
from Bar import Bar, set_k_ratio
def get_analytical_constants(bar1:'Bar', bar2:'Bar', x_bar=2/np.pi, l=1):
'''Solve for C1, D1, C2, and D2
Returns np.array([C1, D1, C2, D2])'''
epsilon = (bar1.k*bar1.area*bar1.alpha) / (bar2.k*bar2.area*bar2.alpha)
A = np.array([
#C1 D1 C2 D2
[0, 1, 0, 0],
[0, 0, np.sinh(bar2.alpha*l), np.cosh(bar2.alpha*l)],
[np.sinh(bar1.alpha*x_bar), 0, -1*np.sinh(bar2.alpha*x_bar), -1*np.cosh(bar2.alpha*x_bar)],
[epsilon*np.cosh(bar1.alpha*x_bar), 0, -1*np.cosh(bar2.alpha*x_bar), -1*np.sinh(bar2.alpha*x_bar)]
])
B = np.array([
0,
100,
0,
0
])
return np.linalg.solve(A, B)
def get_analytical_datapoints(x_points, bar1:'Bar', bar2:'Bar', x_bar=2/np.pi, l=1):
'''Generates the temperature distribution given x_points.
Returns np.array of tempature values'''
constants = get_analytical_constants(bar1=bar1,bar2=bar2,x_bar=x_bar,l=1)
C1, D1, C2, D2 = constants[0], constants[1], constants[2], constants[3]
# Two temp functions for the left and right side of the interface
left_temp = lambda x: C1*np.sinh(bar1.alpha*x) + D1*np.cosh(bar1.alpha*x)
right_temp = lambda x: C2*np.sinh(bar2.alpha*x) + D2*np.cosh(bar2.alpha*x)
temp_values = np.array([])
for x in x_points:
if x <= x_bar:
temp = left_temp(x)
else:
temp = right_temp(x)
temp_values = np.append(temp_values, temp)
return temp_values
def get_analytical_heat_convection(x, bar1:'Bar', bar2:'Bar', x_bar=2/np.pi, l=1):
'''Gets the analytical heat convection at x from heat flux conservation'''
constants = get_analytical_constants(bar1=bar1,bar2=bar2,x_bar=x_bar,l=1)
C1, D1, C2, D2 = constants[0], constants[1], constants[2], constants[3]
t_prime = C2*bar2.alpha*np.cosh(bar2.alpha*x) + D2*bar2.alpha*np.sinh(bar2.alpha*x)
q_dot = -bar2.k*bar2.area*t_prime
return q_dot
def fdm_array(bar1:'Bar', bar2:'Bar', num_sections: int=6, x_bar=2/np.pi, l=1, order=2):
'''num_sections = total sections to cut the bar. Note that this is NOT evenly distributed over the entire
bar. Half of the sections are left of x_bar, half are to the right.
Returns the A matrix of the FDM equation Ax = B'''
dx1 = x_bar / (num_sections / 2)
dx2 = (l - x_bar) / (num_sections / 2)
if order == 2:
w1 = dx1
w2 = dx2
epsilon1 = 1 + dx1**2 * bar1.alpha**2 / 2
epsilon2 = 1 + dx2**2 * bar2.alpha**2 / 2
beta1 = bar1.k * bar1.area / w1
beta2 = bar2.k * bar2.area / w2
k1 = 2*beta1*epsilon1
k2 = 2*beta2*epsilon2
rho = beta1*epsilon1 + beta2*epsilon2
elif order == 4:
w1 = dx1 + dx1**3 * bar1.alpha**2 / 6
w2 = dx2 + dx2**3 * bar2.alpha**2 / 6
epsilon1 = 1 + dx1**2 * bar1.alpha**2 / 2 + dx1**4 * bar1.alpha**4 / 24
epsilon2 = 1 + dx2**2 * bar2.alpha**2 / 2 + dx2**4 * bar2.alpha**4 / 24
beta1 = bar1.k * bar1.area / w1
beta2 = bar2.k * bar2.area / w2
k1 = 2*beta1*epsilon1
k2 = 2*beta2*epsilon2
rho = beta1*epsilon1 + beta2*epsilon2
# Making the matrix
# There are num_sections - 1 rows and columns
# row 0, row n - 2, and row num_sections/2 - 1 are unique
# the rest are -1, k, -1 where k changes from k1 to k2 at num_sections/2
# Initialize matrix A with zeros
A = np.zeros((num_sections - 1, num_sections - 1))
# Fill the matrix A
for row in range(num_sections - 1):
if row == 0:
# First row for boundary at x = 0 (for example Dirichlet or Neumann condition)
A[row, row] = k1
A[row, row+1] = -beta1
elif row == num_sections // 2 - 1:
# Special row at the interface between bar1 and bar2
A[row, row-1] = -beta1
A[row, row] = rho
A[row, row+1] = -beta2
elif row == num_sections - 2:
# Last row for boundary at x = l (again assuming Dirichlet or Neumann condition)
A[row, row-1] = -beta2
A[row, row] = k2
else:
# Interior rows for bar1 and bar2 (uniform grid)
if row < num_sections // 2 - 1:
# In bar1 region
A[row, row-1] = -beta1
A[row, row] = k1
A[row, row+1] = -beta1
else:
# In bar2 region
A[row, row-1] = -beta2
A[row, row] = k2
A[row, row+1] = -beta2
return A
def fem_array(bar1:'Bar', bar2:'Bar', num_sections: int=6, x_bar=2/np.pi, l=1):
'''num_sections = total sections to cut the bar. Note that this is NOT evenly distributed over the entire
bar. Half of the sections are left of x_bar, half are to the right.
Returns the A matrix of the FEM equation Ax = B'''
dx1 = x_bar / (num_sections / 2)
dx2 = (l - x_bar) / (num_sections / 2)
k1 = (2/dx1 + 4*bar1.alpha**2*dx1/6) / (1/dx1 - bar1.alpha**2*dx1/6)
k2 = (2/dx2 + 4*bar2.alpha**2*dx2/6) / (1/dx2 - bar2.alpha**2*dx2/6)
beta1 = (bar1.k * bar1.area) / (dx1)
beta2 = (bar2.k * bar2.area) / (dx2)
rho = beta1 + beta2 + (bar1.k * bar1.area * dx1 * bar1.alpha**2) / 2 + (bar2.k * bar2.area * dx2 * bar2.alpha**2) / 2
# Making the matrix
# There are num_sections - 1 rows and columns
# row 0, row n - 2, and row num_sections/2 - 1 are unique
# the rest are -1, k, -1 where k changes from k1 to k2 at num_sections/2
# Initialize matrix A with zeros
A = np.zeros((num_sections - 1, num_sections - 1))
# Fill the matrix A
for row in range(num_sections - 1):
if row == 0:
# First row for boundary at x = 0 (for example Dirichlet or Neumann condition)
A[row, row] = k1
A[row, row+1] = -1
elif row == num_sections // 2 - 1:
# Special row at the interface between bar1 and bar2
A[row, row-1] = -beta1
A[row, row] = rho # This is the rho value you computed
A[row, row+1] = -beta2
elif row == num_sections - 2:
# Last row for boundary at x = l (again assuming Dirichlet or Neumann condition)
A[row, row-1] = -1
A[row, row] = k2
else:
# Interior rows for bar1 and bar2 (uniform grid)
if row < num_sections // 2 - 1:
# In bar1 region
A[row, row-1] = -1
A[row, row] = k1
A[row, row+1] = -1
else:
# In bar2 region
A[row, row-1] = -1
A[row, row] = k2
A[row, row+1] = -1
return A
def get_fem_p2_k(row, col, bar:'Bar', dx):
if (row == 1 and col == 1) or (row == 2 and col == 2):
return bar.k * bar.area / dx + bar.h * bar.p * dx / 3.0
elif row == 3 and col == 3:
return 1 * bar.k * bar.area / (3*dx) + bar.h * bar.p * dx / 30
elif (row == 1 and col == 2) or (row == 2 and col == 1):
return -1 * bar.k * bar.area / dx + bar.h * bar.p * dx / 6
elif (row == 1 and col == 3) or (row == 3 and col == 1) or (row == 2 and col == 3) or (row == 3 and col == 2):
return bar.h * bar.p * dx / 12
def get_all_fem_p2_k(bar:'Bar', dx):
K = np.zeros((3, 3))
for row in range(3):
for col in range(3):
K[row, col] = get_fem_p2_k(row+1, col+1, bar, dx)
return K
def fem_p_2_array(bar1:'Bar', bar2:'Bar', num_sections: int=6, x_bar=2/np.pi, l=1):
'''num_sections = total sections to cut the bar. Note that this is NOT evenly distributed over the entire
bar. Half of the sections are left of x_bar, half are to the right.
Returns the A matrix of the FEM equation Ax = B'''
dx1 = x_bar / (num_sections / 2)
dx2 = (l - x_bar) / (num_sections / 2)
K_1 = get_all_fem_p2_k(bar1, dx1)
K_2 = get_all_fem_p2_k(bar2, dx2)
# Initialize matrix A with zeros
rows = 2 * num_sections - 1
cols = 2 * num_sections - 1
A = np.zeros((rows, cols))
omega_col_index = num_sections - 1
num_omega = num_sections
for i in range(num_sections):
row1 = i*2
row2 = row1 + 1
if i == 0:
A[row1][0] = K_1[2-1, 2-1] + K_1[1-1, 1-1]
A[row1][1] = K_1[1-1, 2-1]
A[row1][omega_col_index] = K_1[2-1, 3-1]
A[row1][omega_col_index+1] = K_1[1-1, 3-1]
A[row2][0] = K_1[3-1, 2-1]
A[row2][omega_col_index] = K_1[3-1, 3-1]
elif i == num_sections - 2: # Second last pair of rows
A[row1][i-1] = K_2[2-1, 1-1]
A[row1][i] = K_2[2-1, 2-1] + K_2[1-1, 1-1]
A[row1][omega_col_index+i] = K_2[2-1, 3-1]
A[row1][omega_col_index+i+1] = K_2[1-1, 3-1]
A[row2][i-1] = K_2[3-1, 1-1]
A[row2][i] = K_2[3-1, 2-1]
A[row2][omega_col_index+i] = K_2[3-1, 3-1]
elif i == num_sections - 1: # Last pair of rows, use only omega equation
A[row1][omega_col_index-1] = K_2[3-1, 1-1]
A[row1][-1] = K_2[3-1, 3-1]
elif i == num_sections // 2 - 1: # Interface element
A[row1][i-1] = K_1[2-1, 1-1]
A[row1][i] = K_1[2-1, 2-1] + K_2[1-1, 1-1]
A[row1][i+1] = K_2[1-1, 2-1]
A[row1][omega_col_index + i] = K_1[2-1, 3-1]
A[row1][omega_col_index + i + 1] = K_2[1-1, 3-1]
A[row2][i-1] = K_1[3-1, 1-1]
A[row2][i] = K_1[3-1, 2-1]
A[row2][omega_col_index + i] = K_1[3-1, 3-1]
else:
if i < num_sections // 2: # We are in the first bar
A[row1][i-1] = K_1[2-1, 1-1]
A[row1][i] = K_1[2-1, 2-1] + K_1[1-1, 1-1]
A[row1][i+1] = K_1[1-1, 2-1]
A[row1][omega_col_index + i] = K_1[2-1, 3-1]
A[row1][omega_col_index + i + 1] = K_1[1-1, 3-1]
A[row2][i-1] = K_1[3-1, 1-1]
A[row2][i] = K_1[3-1, 2-1]
A[row2][omega_col_index + i] = K_1[3-1, 3-1]
else: # We are in the second bar
A[row1][i-1] = K_2[2-1, 1-1]
A[row1][i] = K_2[2-1, 2-1] + K_2[1-1, 1-1]
A[row1][i+1] = K_2[1-1, 2-1]
A[row1][omega_col_index + i] = K_2[2-1, 3-1]
A[row1][omega_col_index + i + 1] = K_2[1-1, 3-1]
A[row2][i-1] = K_2[3-1, 1-1]
A[row2][i] = K_2[3-1, 2-1]
A[row2][omega_col_index + i] = K_2[3-1, 3-1]
return A
def solve(bar1:'Bar', bar2:'Bar', num_sections: int=6, x_bar=2/np.pi, l=1, method="FDM", order=2):
'''Solves using FDM or FEM as specified. Returns (temps, x_values)'''
if method == "FDM":
A = fdm_array(bar1,
bar2,
num_sections=num_sections,
x_bar=x_bar,
l=l,
order=order)
# Generate the B vector
if order == 2:
dx2 = (l - x_bar) / (num_sections / 2)
w2 = dx2
beta2 = bar2.k * bar2.area / w2
elif order == 4:
dx2 = (l - x_bar) / (num_sections / 2)
w2 = dx2 + dx2**3 * bar2.alpha**2 / 6
beta2 = bar2.k * bar2.area / w2
B = np.zeros((num_sections - 1))
B[-1] = beta2*100
# Add boundary conditions
interior_temps = np.linalg.solve(A, B)
all_temps = np.array([0])
all_temps = np.append(all_temps, interior_temps)
all_temps = np.append(all_temps, 100)
# Generate the x_values to return along with the temps
dx2 = (l - x_bar) / (num_sections / 2)
x_values = np.linspace(0, x_bar, num_sections // 2 + 1) # [0, x_bar]
x_values = np.append(x_values, np.linspace(x_bar+dx2, l, num_sections // 2))
elif method == "FEM":
if order == 2:
A = fem_array(bar1,
bar2,
num_sections=num_sections,
x_bar=x_bar,
l=l)
# Generate the B vector
B = np.zeros((num_sections - 1))
B[-1] = 100
# Add boundary conditions
interior_temps = np.linalg.solve(A, B)
all_temps = np.array([0])
all_temps = np.append(all_temps, interior_temps)
all_temps = np.append(all_temps, 100)
# Generate the x_values to return along with the temps
dx2 = (l - x_bar) / (num_sections / 2)
x_values = np.linspace(0, x_bar, num_sections // 2 + 1) # [0, x_bar]
x_values = np.append(x_values, np.linspace(x_bar+dx2, l, num_sections // 2))
elif order == 4:
A = fem_p_2_array(bar1,
bar2,
num_sections=num_sections,
x_bar=x_bar,
l=l)
# Generate the B vector
dx2 = (l - x_bar) / (num_sections / 2)
B = np.zeros((num_sections*2-1))
K_2 = get_all_fem_p2_k(bar2, dx2)
B[0] = 0
B[1] = 0
B[-3] = -1 * K_2[1-1, 2-1] * 100
B[-2] = 0
B[-1] = -1 * K_2[3-1, 2-1] * 100
# Add boundary conditions
solution = np.linalg.solve(A, B)
all_temps = np.array([0])
all_temps = np.append(all_temps, solution[0:num_sections-1])
all_temps = np.append(all_temps, 100)
# Generate the x_values to return along with the temps
dx2 = (l - x_bar) / (num_sections / 2)
x_values = np.linspace(0, x_bar, num_sections // 2 + 1) # [0, x_bar]
x_values = np.append(x_values, np.linspace(x_bar+dx2, l, num_sections // 2))
return all_temps, x_values
if __name__ == "__main__":
bar1 = Bar(
radius=0.1,
k=0.5,
h=0.25
)
bar2 = Bar(
radius=0.1,
k=0.5,
h=0.25
)
#set_k_ratio(8, bar1, bar2)
x_bar = 0.7
x_values = np.linspace(0, 1, 1000)
temp_values = get_analytical_datapoints(x_points=x_values,
bar1=bar1,
bar2=bar2,
x_bar=x_bar)
# fdm tests
l = 1
num_sections = 16
dx1 = x_bar / (num_sections / 2)
dx2 = (l - x_bar) / (num_sections / 2)
fdm_temps, x_fdm = solve(bar1=bar1,
bar2=bar2,
num_sections=num_sections,
x_bar=x_bar,
l=1,
method="FEM",
order=2)
fem_temps, x_fem = solve(bar1=bar1,
bar2=bar2,
num_sections=num_sections,
x_bar=x_bar,
l=1,
method="FEM",
order=4)
print(f"A: {bar1.area}")
print(f"k: {bar1.k}")
print(f"h: {bar1.h}")
print(f"P: {bar1.p}")
print(f"dx: {dx1}")
# Plot the data
plt.plot(x_values, temp_values, label='Temperature Distribution')
plt.axvline(x=x_bar, color='r', linestyle='--', label='x_bar')
plt.plot(x_fdm, fdm_temps, 'o-', label='Temperature Distribution (FDM)', color='g')
plt.plot(x_fem, fem_temps, 'o-', label='Temperature Distribution (FEM)', color='r')
plt.xlabel('x')
plt.ylabel('Temperature')
plt.title('Temperature Distribution Along the Bar')
plt.legend()
plt.grid(True)
# Show plot
plt.show()