Need help finishing the Code specifically steps 3 and 4.
Problem
Compute the temperature (K) profile throughout a cylindrical SiGe wire with thermal conductivity k = 4.2 W/(m*K), length L = 0.05 m, and radius R = 0.005 m.
The boundary conditions are given in the figure below. The solid lines correspond to zero-flux boundary conditions, the long-dashed line to open boundaries with a known temperature, and the short-dashed lines to open boundaries with known flux via convection (the listed T is the “ambient” T). In this figure, assume z (in m) varies in the horizontal direction while r (in m) varies in the vertical direction (the upper left corner is the origin).
Justify your approach. Plot the temperature distribution throughout the wire using a 2D color- map with proper labels. Include contour lines.
https://www.chegg.com/homework-help/questions-and-answers/problem-2-compute-temperature-k-profile-throughout-cylindrical-sige-wire-thermal-conductiv-q96105385
Code
# Problem 2
# Import the required modules
import numpy as np
import matplotlib.pyplot as plt
# Constants
k = 4.2 # Thermal conductivity in W/(m.K)
L = 0.05 # Length in m
R = 0.005 # Radius in m
T = 575 # Ambient temp. in K
T1 = 423 # K
h1 = 45 # kW/(m^2.K)
T2 = 348 # K
h2 = 650 # kW/(m^2.K)
Ta = 298 # K
h = 7.5 # kW/(m^2.K)
# Iteration parameters
maxit = 2000
tol = 0.0001 # Relative tolerance
merr = 1e5
lam = 1.4 # Parameter for convergence rate
# Setup grid
dr = 0.01
nr = int(R/dr) + 1
nz = int(L/dr) + 1
rr = np.linspace(0,R,num=nr,endpoint=True);
zz = np.linspace(0,L,num=nz,endpoint=True);
# Step 1 - Initial Guesses
M = np.ones((nz,nr)) # Create 2D array w/ ones (z = # of rows, r = # of col)
M = M*T # Matrix of T now
# Step 2 - Apply Boundary Conditions
M[0,0:nr] = T; M[-1,0:nr] = T1;
M[0:nz,0] = T2; M[0:nz,-1] = Ta;
# Step 3 and 4 - Apply LDE, walking over nodes
cc = 0; # Counter
a = k*dr*dr/(4*R)
while merr > tol:
Mold = np.copy(M) # Save current values to old
M[-1,-1]=(2*M[-2,-1]+2*M[-1,-2])/(4+a) # Corner
for j in range(1,nz-1):
M[j,-1]=(2*M[j,-2]+M[j-1,-1]+M[j+1,-1])/(4+a)
for i in range(1,nr-1):
M[-1,i]=(M[-1,i-1]+M[-1,i+1]+2*M[-2,i])/(4+a)
for i in range(1,70):
M[0,i]=(M[0,i-1]+M[0,i+1]+2*M[1,i])/(4+a)
for j in range(1,nz-1):
for i in range(1,nr-1):
M[j,i] = (M[j,i-1]+M[j,i+1]+M[j-1,i]+M[j+1,i])/(4+a)
M = lam*M+(1-lam)*Mold # Adjust for convergence rate
ea = np.abs((M-Mold)/Mold)
cc = cc + 1;
merr = np.max(ea)
# Plot color mesh
X, Y = np.meshgrid(rr, zz)
p = plt.pcolormesh(rr, zz, M, cmap="RdBu", shading="flat", vmin=0, vmax=100)
ct = plt.contour(X, Y, M, cmap="gray", levels=10, vmin=0, vmax=100)
c = plt.colorbar(p)
plt.xlabel("R (m)")
plt.ylabel("Z (m)")
c.set_label("Teamperature (K)")
plt.show()
# Print Results
print("Converged in %d iterations" % cc)
print("Max error is %f" % merr)
print("Mean Teamperature along central axis %f K" %(np.mean(M[:,0])))
I have been trying to replicate Figures 1 and 2 from O'Dwyer's paper, "Electronic and thermal transport in hot carrier solar cells with low-dimensional contacts" (Link to O'Dwyer Paper), with Python on Spyder.
Figures to replicate
Figure 1: w = 1e-5
Figure 1
Figure 2 = w = 1e-2
Figure 2
Method
To find the absorber temperature, T_H, one needs to equate the net incoming energy flow due to radiation, Qrad, and the net heat current flowing out of the hot absorber reservoir, Qabs. Their equations are as follows:
Equations for Qrad and Qabs
The bold lined plots from Figures 1 and 2 refer to Wurfel's solutions that are given by the following equations:
Wurfel's Solutions
I am having some success replicating Figure 2 where w=1e-2 (my results shown below) but have no success getting Figure 1 where w=1e-5 (points and num_T below refer to the number of plotting points and the number of temperatures to iterate through respectively).
My attempt at Figure 2 when w=1e-2, points = 21, num_T = 300
My attempt at Figure 2
I think I am currently having trouble with the "overflow encountered in exp" warning trying to get Figure 1 with w=1e-5 to work. When I try to calculate Qabs (refer to code below in 'Parameters' function), it gives absurd values with orders of magnitude ~1e-70. However, when I run the same equations in WolframAlpha, I get a more reasonable result.
For example, the T_H value when W = 1e-5, N = 1e12 and Voltage = 0 V is ~T_H = 1448K (refer to Figure 1, top left graph).Using WolframAlpha, I get 4.54986×10^22 for Qrad and 4.83602×10^22 for Qabs (WolframAlpha solution for Qrad at w=1e-5,N=1e12,V=0) and WolframAlpha solution for Qabs at w=1e-5,N=1e12,V=0)) which are the results I want in Python. Find below all my code.
All Code
import os
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import matplotlib.ticker as ticker
from scipy.integrate import quad
from scipy.special import expit
import time
from sympy import symbols, Eq, solve
# import warnings
# warnings.filterwarnings("ignore")
t0= time.perf_counter()
directory = r'C:\Users\gyanj\Documents\GADGET BACKUP\University\5th Year\Thesis\Python Simul\ODwyer\Plots'
os.chdir(directory)
c = 3e8 #speed of light, m/s
q = 1.602e-19 # charge of electron, C
h = 6.626e-34/q #Planck's Constant, eVs
k = 8.617e-5 # Boltzmann's Constant, eVK^-1
stefan = 5.67e-8 #Stefan-Boltzmann's Constant, Wm^-2K^-4
T_C = 300 #Cold Reservoir Temperature, K
T_S = 6000 #Sun Temperature, K
Omega = np.pi #Absorption/Emission Solid Angle, sr
A = 1e-4 #Absorber Area, m^2
points = 21 # Number of plotting points
num_T = 300 #Number of temperatures to iterate through
Temperatures = np.linspace(T_C,T_S,num_T) # array of temperatures
E_u = 1 #Average electrochemical potential of system, eV
V = np.linspace(0,1,points) #V applied symetrically across device
max_lim = np.inf# integral upper limit
W = [1e-2] #Transmission function width
N = [1e9,1e10,1e12] #Number of contacts
#Following block used for progress bar (not relevant to calculations)
global total
total = len(W)*len(N)*(points)*len(Temperatures)
progress = 0
counter = 0
full_time = 0
#Object containing all relevant parameters
class param:
def __init__(self, TH, I, P, n, Qrad, Qabs):
self.TH = TH #Hot reservoir/Absorber Temperature, K
self.I = I # Current, A/m^2
self.P = P #Power, W/m^2
self.n = n #Efficiency
self.Qrad = Qrad #net incoming energy flow due to radiation
self.Qabs = Qabs #net heat current flowing out of the hot absorber reservoir
Data = np.empty([len(W),len(N),points], dtype = object) #Contain all param objects
datafile = 'ODwyer.dat'
fout = open(datafile,'w')
fout.write('')
fout.close()
for i in range(len(W)):
for j in range(len(N)):
for x in range(points):
Data[i][j][x] = param(0,0,0,0,0,0)
# Function Paramaters calculates Qrad,Qabs and I for a given T_H,u_H,u_C,N_contact,w,voltage
def Parameters (T_H, u_H, u_C, N_contact, w, voltage):
eqn1 = lambda E: ((E)**3/(np.exp(E/(k*T_S))-1)-(E)**3/(np.exp(E/(k*T_H))-1))
Qrad = ((2*Omega*A*q)/((h**3)*(c**2)))*quad(eqn1,0,max_lim)[0]
eqn2 = lambda E:(E-u_H)*(expit(-(E-u_H)/(k*T_H))-expit(-(E-u_C)/(k*T_C)))*(np.exp(-(E-E_u/2)**2/(w)))
Qabs = ((4*N_contact*q)/h)*quad(eqn2,0,max_lim)[0]
if Qabs < 0:
Qabs = np.inf
error = abs(Qrad-Qabs)
eqn3 = lambda E:(expit(-(E-u_H)/(k*T_H))-expit(-(E-u_C)/(k*T_C)))*(np.exp(-(E-E_u/2)**2/(w)))
I = -((2*N_contact*q)/h)*quad(eqn3,0,max_lim)[0]/A
fout = open(datafile,'a')
fout.write('%.2e\t%.2e\t%.1f\t%.2f\t%.2e\t%.2e\n'%(w,N_contact,T_H,voltage,Qrad,Qabs))
fout.close()
return error, I, Qrad, Qabs
#Progress bar for simulation time (not relevant for calculations)
def progressbar(progress):
if (progress >= 0.01):
t1 = time.perf_counter() - t0
full_time = t1*1/progress*100
timeleft = full_time-t1
if timeleft >= 3600:
timelefthrs = int(round(timeleft/3600,0))
timeleftmins = int((timeleft-timelefthrs*3600)%60)
print('\rSimulation Progress: %.2f%%\t Estimated Time Left: %dh %dm '%(progress,timelefthrs, timeleftmins), end='')
elif timeleft >= 60 and timeleft <3600: # in mins
timeleftmins = int(round(timeleft/60,0))
timeleftsecs = int((timeleft-timeleftmins*60)%60)
print('\rSimulation Progress: %.2f%%\t Estimated Time Left: %dm %ds '%(progress,timeleftmins, timeleftsecs), end='')
else:
print('\rSimulation Progress: %.2f%%\t Estimated Time Left: %ds '%(progress,timeleft), end='')
else:
print('\rSimulation Progress: %.2f%%'%(progress), end='')
def Odwyer(index, counter):
for j in range(len(N)):
for i in range(points): #per V
u_H = E_u+V[i]/2 #Hot absorber electrochemical potential, eV
u_C = E_u-V[i]/2 #Cold Reservoir electrochemical potential, eV
error = np.inf #initialise error between Qrad and Qabs as inf
for x in range(len(Temperatures)):
temperature = Temperatures[x]
diff, I, Qrad, Qabs= Parameters(Temperatures[x], u_H, u_C, N[j], W[index], V[i])
if diff <= error: #if difference between Qabs and Qrad is smaller than previous error, use this Temperature[x]
Data[index][j][i].TH = temperature
Data[index][j][i].Qrad = Qrad
Data[index][j][i].Qabs = Qabs
Data[index][j][i].I = I
Data[index][j][i].P = I*V[i]
Data[index][j][i].n = I*V[i]/(stefan*(T_S**4))
error = abs(diff)
counter += 1
progress = counter/total*100
progressbar(progress)
#Plotting
fig, axs= plt.subplots(2,2, constrained_layout=True)
ax1 = axs[0,0]
ax2 = axs[0,1]
ax3 = axs[1,0]
ax4 = axs[1,1]
for i in range(2):
for j in range(2):
axs[i,j].set_xlim(0,1)
axs[i,j].xaxis.set_major_locator(ticker.MultipleLocator(0.5))
axs[i,j].set_xlabel("Voltage (V)")
ax1.set_ylim(0,T_S)
ax1.set_ylabel("TH (K)")
ax1.yaxis.set_major_locator(ticker.MultipleLocator(2000))
ax2.set_ylim(0,1e8)
ax2.set_ylabel("I (A/m^2)")
ax2.yaxis.set_major_locator(ticker.MultipleLocator(2e7))
ax3.set_ylim(0,1e8)
ax3.set_ylabel("Power (W/m^2)")
ax3.yaxis.set_major_locator(ticker.MultipleLocator(2e7))
ax4.set_ylim(0,1)
ax4.set_ylabel("Efficiency")
ax4.yaxis.set_major_locator(ticker.MultipleLocator(0.2))
TH = np.empty([len(N),points])
I = np.empty([len(N),points])
P = np.empty([len(N),points])
n = np.empty([len(N),points])
for j in range(len(N)):
for x in range(points):
TH[j][x] = Data[index][j][x].TH
I[j][x] = Data[index][j][x].I
P[j][x] = Data[index][j][x].P
n[j][x] = Data[index][j][x].n
#Wurfel's Solution
TH_W = []
I_W = []
P_W = []
n_W = []
for x in range(points):
if V[x] == E_u:
TH_wurfel = 1e20
else:
TH_wurfel = T_C/(1-V[x]/E_u)
TH_W.append(TH_wurfel)
Iwurfel = (stefan)/(E_u)*(T_S**4-TH_wurfel**4)
Pwurfel = stefan*(T_S**4-TH_wurfel**4)*(1-T_C/TH_wurfel)
nwurfel = (T_S**4-TH_wurfel**4)/(T_S**4)*(1-T_C/TH_wurfel)
I_W.append(Iwurfel)
P_W.append(Pwurfel)
n_W.append(nwurfel)
linestyles = ['--','-','-.']
for j in range(len(N)):
for x in range(points):
if TH[j][x] == T_S:
TH[j][x] = 1e8
for i in range(len(N)):
ax1.plot(V,TH[i], label='N = %.0e'%N[i], color = 'black', linestyle = linestyles[i], linewidth = 1)
ax2.plot(V,I[i], label='N = %.0e'%N[i], color = 'black', linestyle = linestyles[i], linewidth = 1)
ax3.plot(V,P[i], label='N = %.0e'%N[i], color = 'black', linestyle = linestyles[i], linewidth = 1)
ax4.plot(V,n[i], label='N = %.0e'%N[i], color = 'black', linestyle = linestyles[i], linewidth = 1)
ax1.plot(V,TH_W, color = 'black', label='Wurfel', linewidth = 3)
ax2.plot(V,I_W, color = 'black', label='Wurfel', linewidth = 3)
ax3.plot(V,P_W, color = 'black', label='Wurfel', linewidth = 3)
ax4.plot(V,n_W, color = 'black', label='Wurfel', linewidth = 3)
fig.suptitle('w = %.0e eV' % W[index])
ax1.legend(loc='upper right', fontsize = 8)
ax2.legend(loc='upper right', fontsize = 8)
ax3.legend(loc='upper right', fontsize = 8)
ax4.legend(loc='upper right', fontsize = 8)
#Saving figure
fig.savefig('w = %.0e eV, pp = %d, num_T = %d.jpg' %(W[index],points,num_T), dpi=800)
return counter
for x in range(len(W)):
counter = Odwyer(x, counter)
# Printing out object values
for x in range(len(W)):
for j in range(len(N)):
print('Parameters for W = %0.e, N = %.0e'%(W[x],N[j]))
for i in range(points):
print('w = %.0e\tV = %.2f\tTH = %.0f\tQrad = %.2e\tQabs = %.2e\tI = %.2e'%(W[x],V[i],Data[x][j][i].TH,Data[x][j][i].Qrad,Data[x][j][i].Qabs,Data[x][j][i].I))
print('\nComplete!')
What I've tried
I have tried changing the upper limit of the integrals from inf to lower values and although it removed the overflow warning for values ~<15, it made Qabs = 0.00e00. I also tried changing arguments for 'limit' and 'epsabs' in the 'quad' function but couldn't get that to work either. Changing the variables 'points' and 'num_T' did not improve the accuracy of my values either. I have also read and tried solutions from relevant posts regarding overflows such as Overflow Post but to no avail. This is my first post so if you require any further information from me to fix my problem, feel free to let me know!
Here's a quick and dirty stdlib (no numpy) script that got something close to the WolframAlpha answer:
from math import exp, pi
C1 = 8.617e-5 * 6000
C2 = 8.617e-5 * 1448
def f(x):
denom1 = exp(x / C1)
denom2 = exp(x / C2)
# did some algebra
difference = (denom2 - denom1) / (denom1 - 1) / (denom2 - 1)
return x ** 3 * difference
bins = 10_000
endpoint = 10
total = 0.0
for i in range(1, bins+1):
x = i * endpoint / bins
total += f(x)
# account for widths
total *= (endpoint / bins)
scaled = float(total) * 2 * pi * 1e-4 / (4.14e-15)**3 / (3e8)**2
print(scaled)
# 4.549838698077388e+22
Part of the problem (I'm guessing, not sure) would be that 1/(a-1) - 1/(b-1) will be wildly imprecise for a and b close to 1, so you can do some algebra to try and fix that, and make it (b-a)/(a-1)/(b-1).
I am trying to write a code for the orbit of the earth in SI using a symplectic integrator, my attempt is as follows:
import numpy as np
import matplotlib.pyplot as plt
#Set parameters
G = 6.67348e-11
mEar = 5.972e24
mSun = 1.989e30
def earth_orbit(x0, y0, vx0, vy0, N):
dt = 1/N #timestep
pos_arr = np.zeros((N,2)) #empty array to store position
vel_arr = np.zeros((N,2)) #empty array to store velocities
#Initial conditions
# x0 = x
# y0 = y
# vx0 = vx
# vy0 = vy
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k]*dt #update positions
force = -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
vel_arr[k+1] = vel_arr[k] + (force/mEar) * dt #update velocities
#Plot:
plt.plot(pos_arr, 'go', markersize = 1, label = 'Earth trajectory')
# plt.plot(0,0,'yo', label = 'Sun positon') # yellow marker
# plt.plot(pos_arr[0],'bo', label = 'Earth initial positon') # dark blue marker
plt.axis('equal')
plt.xlabel ('x')
plt.ylabel ('y')
return pos_arr, vel_arr
earth_orbit(149.59787e9, 0, 0, 29800, 1000)
The output is 2 dots and I can't figure out if this is a unit issue or a calculation issue?
Display the trajectory
pos_arr contains the x and y coordinates in its columns. To display the whole trajectory, plt.plot(pos_arr[:,0], pos_arr[:,1]) can thus be used. I would prefer to use plt.plot(*pos_arr.T) as a shorter alternative. The line that displays the trajectory must be replaced by:
plt.plot(*pos_arr.T, 'g', label = 'Earth trajectory')
Change the timestep
Here the timestep (in second) is chosen as 1/N, where N is the number of iterations. So, the total duration of the simulation is equal to timestep * N = 1 second ! For N=1000, you can instead try with timestep = 3600*12 (half-day), so that the total duration is a little less than 1.5 years. I suggest passing the duration as a parameter of the function earth_orbit and then setting timestep as duration / N.
def earth_orbit(x0, y0, vx0, vy0, N=1000, duration=3.15e7):
dt = duration / N
...
As said in the comments, this is not the Verlet algorithm, but the symplectic Euler algorithm. The difference is in the initialization, but in comparing against a more exact reference solution and with several step sizes, the difference in the orders, 2 vs. 1, will be quite visible.
A short change to the time loop ensuring that the velocities are at the half-time steps as required for Leapfrog Verlet could look like this:
def force(pos): return -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
vel_arr[0] += (force(pos_arr[0])/mEar) * (0.5*dt) #correct for velocity at half-time
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k] * dt #update positions
vel_arr[k+1] = vel_arr[k] + (force(pos_arr[k+1])/mEar) * dt #update velocities
I am completing a project on planetary motion in Python and the first task is to write a code for the orbit of earth around the sun. This is what I have so far:
def earth_orbit(rEar,v0):
#r0 = xEar[0] = rEar
#v0 = vyEar[0] = np.sqrt(mu/rEar)
#Set parameters:
N = 365 # Earth days in a year
dt = 1.00/N # Time Step: Fractions of a year - 1 Earth day (i.e. 1/365)
mu = 4*np.pi**2 # Gravitational parameter
#Create an array, for all variables, of size N with all entries equal to zero:
xEar = np.zeros((N,))
yEar = np.zeros((N,))
vxEar = np.zeros((N,))
vyEar = np.zeros((N,))
# Initial Conditions:
xEar[0] = rEar # (x0 = r, y0 = 0) in AU
vyEar[0] = v0 #units: AU/yr
#Implement Verlet Algorithm:
for k in range(0,N-1):
rEar = (xEar[k]**2+yEar[k]**2)**0.5
vxEar[k+1] = vxEar[k] - (mu*xEar[k])/((rEar)**3)*dt
xEar [k+1] = xEar[k] + vxEar[k+1]*dt
vyEar[k+1] = vyEar[k] - (mu*yEar[k])/((rEar)**3)*dt
yEar [k+1] = yEar[k] + vyEar[k+1]*dt
#Plot:
a = plt.plot(xEar, yEar, 'go', markersize = 1, label = 'Earth trajectory')
plt.plot(0,0,'yo', label = 'Sun positon') #yellow marker for the sun
plt.plot(xEar[0],0,'bo', label = 'Earth initial positon') #dark blue marker for earth's initial position
plt.axis('equal')
plt.xlabel ('x')
plt.ylabel ('y')
return a, xEar, yEar
This works well, however for subsequent parts of the project I've been told that I shouldn't use AU for this base code as it'll cause difficulties. I've tried changing the gravitational parameter and putting the inputs in standard units but the graph becomes just 2 dots (the values of xEar and vyEar remain constant) instead of a circular orbit and I don't know why this is?
Due to the definition of the gravitational constant, when you change the length unit, you have to change the value of mu by the cube of the scaling factor.
I pulled the definition of mu outside of the function for convenience.
import numpy as np
import matplotlib.pyplot as plt
def earth_orbit(rEar, v0):
#Set parameters:
N = 365 # Earth days in a year
dt = 1. / N # Time Step: Fractions of a year - 1 Earth day (i.e. 1/365)
#Create an array, for all variables, of size N with all entries equal to zero:
xEar = np.zeros((N,))
yEar = np.zeros((N,))
vxEar = np.zeros((N,))
vyEar = np.zeros((N,))
# Initial Conditions:
xEar[0] = rEar # (x0 = r, y0 = 0) in AU
vyEar[0] = v0 #units: AU/yr
#Implement Verlet Algorithm:
for k in range(0, N-1):
rEar = (xEar[k]**2+yEar[k]**2)**0.5
vxEar[k+1] = vxEar[k] - ((mu * xEar[k]) / (rEar**3)) * dt
xEar [k+1] = xEar[k] + vxEar[k+1]*dt
vyEar[k+1] = vyEar[k] - ((mu * yEar[k]) / (rEar**3)) * dt
yEar [k+1] = yEar[k] + vyEar[k+1]*dt
#Plot:
a = plt.plot(xEar, yEar, 'go', markersize = 1, label = 'Earth trajectory')
plt.plot(0,0,'yo', label = 'Sun positon') # yellow marker
plt.plot(xEar[0],0,'bo', label = 'Earth initial positon') # dark blue marker
plt.axis('equal')
plt.xlabel ('x')
plt.ylabel ('y')
return a, xEar, yEar
# average distance earth-sun (1 AU) in meter
au_to_m = 149_597_870_700.
mu = au_to_m**3 * 4 * np.pi**2 # Gravitational parameter
earth_orbit(au_to_m, np.sqrt(mu / au_to_m));