def calculate_E_old(B, J, qn, DX=dx): '''Calculates the value of the electric field based on source term and magnetic field contributions, assuming constant electron temperature across simulation grid. This is done via a reworking of Ampere's Law that assumes quasineutrality, and removes the requirement to calculate the electron current. Based on equation 10 of Buchner (2003, p. 140). INPUT: B -- Magnetic field array. Displaced from E-field array by half a spatial step. J -- Ion current density. Source term, based on particle velocities qn -- Charge density. Source term, based on particle positions OUTPUT: E_out -- Updated electric field array ''' Te = get_electron_temp(qn) B_center = interpolate_to_center_cspline3D(B, DX=DX) JxB = cross_product(J, B_center) curlB = get_curl_B(B, DX=DX) BdB = cross_product(B_center, curlB) / mu0 del_p = get_grad_P(qn, Te) E_out = np.zeros((J.shape[0], 3)) E_out[:, 0] = (- JxB[:, 0] - BdB[:, 0] - del_p ) / qn E_out[:, 1] = (- JxB[:, 1] - BdB[:, 1] ) / qn E_out[:, 2] = (- JxB[:, 2] - BdB[:, 2] ) / qn E_out[0] = E_out[J.shape[0] - 3] E_out[J.shape[0] - 2] = E_out[1] E_out[J.shape[0] - 1] = E_out[2] # This doesn't really get used, but might as well return E_out
def calculate_E(B, J, qn): '''Calculates the value of the electric field based on source term and magnetic field contributions, assuming constant electron temperature across simulation grid. This is done via a reworking of Ampere's Law that assumes quasineutrality, and removes the requirement to calculate the electron current. Based on equation 10 of Buchner (2003, p. 140). INPUT: B -- Magnetic field array. Displaced from E-field array by half a spatial step. J_i -- Ion current density. Source term, based on particle velocities n_i -- Ion number density. Source term, based on particle positions OUTPUT: E_out -- Updated electric field array ''' size = NX + 2 E_out = np.zeros((size, 3)) # Output array - new electric field Te = np.ones(size) * Te0 # Electron temperature array B_center = interpolate_to_center(B) JxB = cross_product(J, B_center) curlB = get_curl_B(B) BdB = cross_product(B_center, curlB) / mu0 del_p = get_grad_P(qn, Te) E_out[:, 0] = (-JxB[:, 0] - BdB[:, 0] - del_p) / qn E_out[:, 1] = (-JxB[:, 1] - BdB[:, 1]) / qn E_out[:, 2] = (-JxB[:, 2] - BdB[:, 2]) / qn E_out[0] = E_out[NX] E_out[NX + 1] = E_out[1] return E_out
def calculate_E(B, Ji, q_dens, E, Ve, Te, Te0, temp3De, temp3Db, grad_P, E_damping_array, qq, DT, half_flag): '''Calculates the value of the electric field based on source term and magnetic field contributions, assuming constant electron temperature across simulation grid. This is done via a reworking of Ampere's Law that assumes quasineutrality, and removes the requirement to calculate the electron current. Based on equation 10 of Buchner (2003, p. 140). INPUT: B -- Magnetic field array. Displaced from E-field array by half a spatial step. Ji -- Ion current density. Source term, based on particle velocities qn -- Charge density. Source term, based on particle positions OUTPUT: E -- Updated electric field array Ve -- Electron velocity moment Te -- Electron temperature arr3D, arr1D are tertiary arrays used for intermediary computations NOTE: Sending all but the last element in the cross_product() function seems clumsy... but it works! Check :: Does it dereference anything? 12/06/2020 -- Added E-field damping option as per Hu & Denton (2010), Ve x B term only ''' # No driven wave if driver_status == 0: pass # Single point source elif driver_status == 1: add_J_ext(qq, Ji, DT, half_flag=half_flag) # Multi-point source elif driver_status == 2: add_J_ext_pol(qq, Ji, DT, half_flag=half_flag) curl_B_term(B, temp3De) # temp3De is now curl B term Ve[:, 0] = (Ji[:, 0] - temp3De[:, 0]) / q_dens Ve[:, 1] = (Ji[:, 1] - temp3De[:, 1]) / q_dens Ve[:, 2] = (Ji[:, 2] - temp3De[:, 2]) / q_dens get_electron_temp(q_dens, Te, Te0) get_grad_P( q_dens, Te, grad_P, temp3Db[:, 0] ) # temp1D is now del_p term, temp3D2 slice used for computation aux.interpolate_edges_to_center(B, temp3Db) # temp3db is now B_center aux.cross_product(Ve, temp3Db[:temp3Db.shape[0] - 1, :], temp3De) # temp3De is now Ve x B term if E_damping == 1: temp3De *= E_damping_array E[:, 0] = -temp3De[:, 0] - grad_P[:] / q_dens[:] E[:, 1] = -temp3De[:, 1] E[:, 2] = -temp3De[:, 2] # Diagnostic flag for testing if disable_waves == 1: E *= 0. return
def push_current(J_in, J_out, E, B, L, G, dt): '''Uses an MHD-like equation to advance the current with a moment method as per Matthews (1994) CAM-CL method. Fills in ghost cells at edges (excluding very last one) INPUT: J -- Ionic current (J plus) E -- Electric field B -- Magnetic field (offset from E by 0.5dx) L -- "Lambda" MHD variable G -- "Gamma" MHD variable dt -- Timestep OUTPUT: J_plus in main() becomes J_half (same memory space) ''' J_out *= 0 B_center = interpolate_to_center_cspline3D(B) G_cross_B = cross_product(G, B_center) for ii in range(3): J_out[:, ii] = J_in[:, ii] + 0.5 * dt * (L * E[:, ii] + G_cross_B[:, ii]) J_out[0] = J_out[J_out.shape[0] - 3] J_out[J_out.shape[0] - 2] = J_out[1] J_out[J_out.shape[0] - 1] = J_out[2] return
def test_cross_product(): npts = 100 x = np.linspace(0, 1, npts) A = np.ones((npts, 3)) B = np.ones((npts, 3)) anal_result = np.ones((npts, 3)) s1x = np.sin(2 * np.pi * x) s2x = np.sin(2 * np.pi * 2*x) s3x = np.sin(2 * np.pi * 3*x) c1x = np.cos(2 * np.pi * x) c2x = np.cos(2 * np.pi * 2*x) c3x = np.cos(2 * np.pi * 3*x) A[:, 0] = s1x ; B[:, 0] = c1x A[:, 1] = s2x ; B[:, 1] = c2x A[:, 2] = s3x ; B[:, 2] = c3x anal_result[:, 0] = s2x*c3x - c2x*s3x anal_result[:, 1] = -(s1x*c3x - c1x*s3x) anal_result[:, 2] = s2x*c3x - c2x*s3x test_result = aux.cross_product(A, B) diff = test_result - anal_result print(diff) return
def calculate_E(B, J, qn, DX=dx): '''Calculates the value of the electric field based on source term and magnetic field contributions, assuming constant electron temperature across simulation grid. This is done via a reworking of Ampere's Law that assumes quasineutrality, and removes the requirement to calculate the electron current. Based on equation 10 of Buchner (2003, p. 140). INPUT: B -- Magnetic field array. Displaced from E-field array by half a spatial step. J -- Ion current density. Source term, based on particle velocities qn -- Charge density. Source term, based on particle positions OUTPUT: E_out -- Updated electric field array ''' curlB = get_curl_B(B, DX=DX) / mu0 Ve = np.zeros((J.shape[0], 3)) Ve[:, 0] = (J[:, 0] - curlB[:, 0]) / qn Ve[:, 1] = (J[:, 1] - curlB[:, 1]) / qn Ve[:, 2] = (J[:, 2] - curlB[:, 2]) / qn Te = get_electron_temp(qn) del_p = get_grad_P(qn, Te) B_center = interpolate_to_center_cspline3D(B, DX=DX) VexB = cross_product(Ve, B_center) E = np.zeros((J.shape[0], 3)) E[:, 0] = -VexB[:, 0] - del_p / qn E[:, 1] = -VexB[:, 1] E[:, 2] = -VexB[:, 2] E[0] = E[J.shape[0] - 3] E[J.shape[0] - 2] = E[1] E[J.shape[0] - 1] = E[2] return E, Ve, Te
def push_E(B, J_i, n_i, dt): '''Calculates the value of the electric field based on source term and magnetic field contributions, assuming constant electron temperature across simulation grid. This is done via a reworking of Ampere's Law that assumes quasineutrality, and removes the requirement to calculate the electron current. Based on equation 10 of Buchner (2003, p. 140). INPUT: B -- Magnetic field array. Displaced from E-field array by half a spatial step. J_i -- Ion current density. Source term, based on particle velocities n_i -- Ion number density. Source term, based on particle positions dt -- Simulation time cadence OUTPUT: E_out -- Updated electric field array ''' size = NX + 2 E_out = np.zeros((size, 3)) # Output array - new electric field JxB = np.zeros((size, 3)) # V cross B holder BdB = np.zeros((size, 3)) # B cross del cross B holder del_p = np.zeros((size, 3)) # Electron pressure tensor gradient array J = np.zeros((size, 3)) # Ion current qn = np.zeros(size, ) # Ion charge density Te = np.ones(size) * Te0 # Electron temperature array # Calculate change/current summations over each species for jj in range(Nj): qn += charge[jj] * n_i[:, jj] # Total charge density, sum(qj * nj) for kk in range(3): J[:, kk] += J_i[:, jj, kk] # Total ion current vector: J_k = qj * nj * Vj_k B_center = 0.5 * (B[:-1, :] + B[1:, :]) JxB = cross_product(J[1:-1, :], B_center) for mm in range(1, size - 1): # B cross curl B BdB[mm, 0] = B[mm, 1] * ((B[mm + 1, 1] - B[mm - 1, 1]) / (2 * dx)) + B[mm, 2] * ( (B[mm + 1, 2] - B[mm - 1, 2]) / (2 * dx)) BdB[mm, 1] = (-B[mm, 0]) * ((B[mm + 1, 1] - B[mm - 1, 1]) / (2 * dx)) BdB[mm, 2] = (-B[mm, 0]) * ((B[mm + 1, 2] - B[mm - 1, 2]) / (2 * dx)) # del P del_p[mm, 0] = ((qn[mm + 1] - qn[mm - 1]) / (2 * dx * q)) * kB * Te[mm] del_p[mm, 1] = 0 del_p[mm, 2] = 0 # Final Calculation E_out[:, 0] = (-JxB[:, 0] - del_p[:, 0] - (BdB[:, 0] / mu0)) / (qn[:]) E_out[:, 1] = (-JxB[:, 1] - del_p[:, 1] - (BdB[:, 1] / mu0)) / (qn[:]) E_out[:, 2] = (-JxB[:, 2] - del_p[:, 2] - (BdB[:, 2] / mu0)) / (qn[:]) E_out[0] = E_out[NX] E_out[NX + 1] = E_out[1] return E_out
def calculate_E(B, Ji, q_dens, E, Ve, Te, Te0, temp3De, temp3Db, grad_P): '''Calculates the value of the electric field based on source term and magnetic field contributions, assuming constant electron temperature across simulation grid. This is done via a reworking of Ampere's Law that assumes quasineutrality, and removes the requirement to calculate the electron current. Based on equation 10 of Buchner (2003, p. 140). INPUT: B -- Magnetic field array. Displaced from E-field array by half a spatial step. Ji -- Ion current density. Source term, based on particle velocities qn -- Charge density. Source term, based on particle positions OUTPUT: E -- Updated electric field array Ve -- Electron velocity moment Te -- Electron temperature arr3D, arr1D are tertiary arrays used for intermediary computations To Do: In the interpolation function, add on B0 along with the spline interpolation. No other part of the code requires B0 in the nodes. ''' curl_B_term(B, temp3De) # temp3De is now curl B term Ve[:, 0] = (Ji[:, 0] - temp3De[:, 0]) / q_dens Ve[:, 1] = (Ji[:, 1] - temp3De[:, 1]) / q_dens Ve[:, 2] = (Ji[:, 2] - temp3De[:, 2]) / q_dens get_electron_temp(q_dens, Te, Te0) get_grad_P( q_dens, Te, grad_P, temp3Db[:, 0] ) # temp1D is now del_p term, temp3D2 slice used for computation aux.interpolate_edges_to_center(B, temp3Db) # temp3db is now B_center aux.cross_product(Ve, temp3Db, temp3De) # temp3De is now Ve x B term E[:, 0] = -temp3De[:, 0] - grad_P[:] / q_dens[:] E[:, 1] = -temp3De[:, 1] E[:, 2] = -temp3De[:, 2] # Diagnostic flag for testing if disable_waves == True: E *= 0. return
def push_current(J, E, B, L, G, dt): '''Uses an MHD-like equation to advance the current with a moment method. Could probably be shortened with loops. ''' J_out = np.zeros((NX + 2, 3)) B_center = interpolate_to_center(B) G_cross_B = cross_product(G, B_center) for ii in range(3): J_out[:, ii] = J[:, ii] + 0.5 * dt * (L * E[:, ii] + G_cross_B[:, ii]) return J_out
def calculate_E(B, Ji, q_dens, E, Ve, Te, temp3D, temp3D2, temp1D, qq=0): '''Calculates the value of the electric field based on source term and magnetic field contributions, assuming constant electron temperature across simulation grid. This is done via a reworking of Ampere's Law that assumes quasineutrality, and removes the requirement to calculate the electron current. Based on equation 10 of Buchner (2003, p. 140). INPUT: B -- Magnetic field array. Displaced from E-field array by half a spatial step. Ji -- Ion current density. Source term, based on particle velocities qn -- Charge density. Source term, based on particle positions OUTPUT: E -- Updated electric field array Ve -- Electron velocity moment Te -- Electron temperature arr3D, arr1D are tertiary arrays used for intermediary computations ''' curl_B_term(B, temp3D) # temp3D is now curl B term Ve[:, 0] = (Ji[:, 0] - temp3D[:, 0]) / q_dens Ve[:, 1] = (Ji[:, 1] - temp3D[:, 1]) / q_dens Ve[:, 2] = (Ji[:, 2] - temp3D[:, 2]) / q_dens get_electron_temp(q_dens, Te) get_grad_P(q_dens, Te, temp1D, temp3D2[:, 0]) # temp1D is now del_p term, temp3D2 slice used for computation aux.interpolate_to_center_cspline3D(B, temp3D2) # temp3d2 is now B_center aux.cross_product(Ve, temp3D2, temp3D) # temp3D is now Ve x B term E[:, 0] = - temp3D[:, 0] - temp1D[:] / q_dens[:] E[:, 1] = - temp3D[:, 1] E[:, 2] = - temp3D[:, 2] E[0] = E[Ji.shape[0] - 3] E[Ji.shape[0] - 2] = E[1] E[Ji.shape[0] - 1] = E[2] return
def test_interp_cross_manual(): ''' Test order of cross product with interpolation, separate from hall term calculation (double check) ''' grids = [16, 32, 64, 128, 256, 512, 1024] errors = np.zeros(len(grids)) #NX = 32 #const.NX xmin = 0.0 #const.xmin xmax = 2*np.pi #const.xmax k = 1.0 marker_size = 20 for NX, ii in zip(grids, list(range(len(grids)))): dx = xmax / NX #x = np.arange(xmin, xmax, dx/100.) # Physical location of nodes E_nodes = (np.arange(NX + 3) - 0.5) * dx B_nodes = (np.arange(NX + 3) - 1.0) * dx ## TEST INPUT FIELDS ## A = np.ones((NX + 3, 3)) Ax = np.sin(1.0*E_nodes*k) Ay = np.sin(2.0*E_nodes*k) Az = np.sin(3.0*E_nodes*k) B = np.ones((NX + 3, 3)) Bx = np.cos(1.0*B_nodes*k) By = np.cos(2.0*B_nodes*k) Bz = np.cos(3.0*B_nodes*k) Be = np.ones((NX + 3, 3)) Bxe = np.cos(1.0*E_nodes*k) Bye = np.cos(2.0*E_nodes*k) Bze = np.cos(3.0*E_nodes*k) A[:, 0] = Ax ; B[:, 0] = Bx ; Be[:, 0] = Bxe A[:, 1] = Ay ; B[:, 1] = By ; Be[:, 1] = Bye A[:, 2] = Az ; B[:, 2] = Bz ; Be[:, 2] = Bze B_inter = aux.interpolate_to_center_cspline3D(B) ## RESULTS (AxB) ## anal_result = np.ones((NX + 3, 3)) anal_result[:, 0] = Ay*Bze - Az*Bye anal_result[:, 1] = Az*Bxe - Ax*Bze anal_result[:, 2] = Ax*Bye - Ay*Bxe test_result = aux.cross_product(A, Be) inter_result = aux.cross_product(A, B_inter) error_x = abs(anal_result[:, 0] - inter_result[:, 0]).max() error_y = abs(anal_result[:, 1] - inter_result[:, 1]).max() error_z = abs(anal_result[:, 2] - inter_result[:, 2]).max() errors[ii] = np.max([error_x, error_y, error_z]) for ii in range(len(grids) - 1): order = np.log(errors[ii] / errors[ii + 1]) / np.log(2) print(order) # OUTPUTS (Plots the highest grid-resolution version) plot = False if plot == True: plt.figure() plt.scatter(E_nodes, anal_result[:, 0], s=marker_size, c='k', marker='o') plt.scatter(E_nodes, anal_result[:, 1], s=marker_size, c='k', marker='o') plt.scatter(E_nodes, anal_result[:, 2], s=marker_size, c='k', marker='o') plt.scatter(E_nodes, test_result[:, 0], s=marker_size, c='r', marker='x') plt.scatter(E_nodes, test_result[:, 1], s=marker_size, c='r', marker='x') plt.scatter(E_nodes, test_result[:, 2], s=marker_size, c='r', marker='x') plt.scatter(E_nodes, inter_result[:, 0], s=marker_size, c='b', marker='x') plt.scatter(E_nodes, inter_result[:, 1], s=marker_size, c='b', marker='x') plt.scatter(E_nodes, inter_result[:, 2], s=marker_size, c='b', marker='x') for kk in range(NX + 3): plt.axvline(E_nodes[kk], linestyle='--', c='r', alpha=0.2) plt.axvline(B_nodes[kk], linestyle='--', c='b', alpha=0.2) plt.axvline(xmin, linestyle='-', c='k', alpha=0.2) plt.axvline(xmax, linestyle='-', c='k', alpha=0.2) plt.xlim(xmin - 1.5*dx, xmax + 2*dx) plt.legend() return