def initialize_magnetic_fields(self): if ('initialize_B' in dir(self.initialize)): B1, B2, B3 = self.initialize.initialize_B(self.q1_center, self.q2_center, self.params) # elif('initialize_A' in dir(self.initialize)): # A1, A2, A3 = self.initialize.initialize_A(self.q1_center, # self.q2_center, # self.params # ) # A1_hat = 2 * fft2(A1) / (self.N_q1 * self.N_q2) # A2_hat = 2 * fft2(A2) / (self.N_q1 * self.N_q2) # A3_hat = 2 * fft2(A3) / (self.N_q1 * self.N_q2) # B1 = ifft2(A3_hat * 1j * self.k_q2) # B2 = -ifft2(A3_hat * 1j * self.k_q1) # B3 = ifft2(A2_hat * 1j * self.k_q1 - A1_hat * 1j * self.k_q2) elif ('initialize_A3_B3' in dir(self.initialize)): A3 = self.initialize.initialize_A3_B3(self.q1_center, self.q2_center, self.params)[0] A3_hat = fft2(A3) B1 = ifft2(A3_hat * 1j * self.k_q2) B2 = -ifft2(A3_hat * 1j * self.k_q1) B3 = self.initialize.initialize_A3_B3(self.q1_center, self.q2_center, self.params)[1] B3 = af.cast(B3, af.Dtype.c64) else: raise NotImplementedError( 'Initialization method for magnetic fields not valid/found') af.eval(B1, B2, B3) return (B1, B2, B3)
def get_dist_func(self): """ Returns the distribution function in the same format as the nonlinear solver thereby allowing direct comparison with the distribution funcition of the nonlinear solver. """ f = 0.5 * self.N_q2 * self.N_q1 * \ af.real(ifft2(self.f_hat)) return(f)
def dump_distribution_function(self, file_name): """ This function is used to dump distribution function to a file for later usage.This dumps the complete 5D distribution function which can be used for post-processing Parameters ---------- file_name : The distribution_function array will be dumped to this provided file name. Returns ------- This function returns None. However it creates a file 'file_name.h5', containing the data of the distribution function Examples -------- >> solver.dump_distribution_function('distribution_function') The above statement will create a HDF5 file which contains the distribution function. The data is always stored with the key 'distribution_function' This can later be accessed using >> import h5py >> h5f = h5py.File('distribution_function', 'r') >> f = h5f['distribution_function'][:] >> h5f.close() Alternatively, it can also be used with the load function to resume a long-running calculation. >> solver.load_distribution_function('distribution_function') """ array_to_dump = 0.5 * self.N_q2 * self.N_q1 * af.real(ifft2(self.f_hat)) array_to_dump.to_ndarray(self._glob_f_array) viewer = PETSc.Viewer().createHDF5(file_name + '.h5', 'w') viewer(self._glob_f)
def RK5_step(self, dt): """ Evolves the physical system defined using an RK5 integrator. This method is 5th order accurate. Parameters ---------- dt: double The timestep size. """ # For purely collisional cases: if (self.physical_system.params.instantaneous_collisions == True): f0 = self._source( 0.5 * self.N_q1 * self.N_q2 * af.real(ifft2(self.f_hat)), self.time_elapsed, self.q1_center, self.q2_center, self.p1_center, self.p2_center, self.p3_center, self.compute_moments, self.physical_system.params, True) self.f_hat = 2 * fft2(f0) / (self.N_q1 * self.N_q2) if (self.physical_system.params.fields_enabled == True and self.physical_system.params.fields_type == 'electrodynamic'): # Since the fields and the distribution function are coupled, # we evolve the system by making use of a coupled integrator # which ensures that throughout the timestepping they are # evaluated at the same temporal locations. self.f_hat, self.fields_solver.fields_hat = \ integrators.RK5_coupled(df_hat_dt, self.f_hat, dfields_hat_dt, self.fields_solver.fields_hat, dt, self ) af.eval(self.f_hat) af.eval(self.fields_solver.fields_hat) else: self.f_hat = integrators.RK5(df_hat_dt, self.f_hat, dt, self.fields_solver.fields_hat, self) af.eval(self.f_hat) return
time_array = np.arange(0, params.t_final + dt, dt) n_data_nls = np.zeros([time_array.size]) n_data_ls = np.zeros([time_array.size]) E_data_nls = np.zeros([time_array.size]) E_data_ls = np.zeros([time_array.size]) # Storing data at time t = 0: n_data_nls[0] = af.max( nls.compute_moments('density')[:, :, N_g:-N_g, N_g:-N_g]) n_data_ls[0] = af.max(ls.compute_moments('density')) E_data_nls[0] = af.max(nls.fields_solver.cell_centered_EM_fields[:, :, N_g:-N_g, N_g:-N_g]) E1_ls = af.real(0.5 * (ls.N_q1 * ls.N_q2) * ifft2(ls.fields_solver.E1_hat)) E_data_ls[0] = af.max(E1_ls) for time_index, t0 in enumerate(time_array[1:]): print('Computing For Time =', t0) nls.strang_timestep(dt) # ls.RK5_timestep(dt) n_data_nls[time_index + 1] = af.max( nls.compute_moments('density')[:, :, N_g:-N_g, N_g:-N_g]) n_data_ls[time_index + 1] = af.max(ls.compute_moments('density')) E_data_nls[time_index + 1] = \ af.max(nls.fields_solver.cell_centered_EM_fields[:, :, N_g:-N_g, N_g:-N_g])
def dfields_hat_dt(f_hat, fields_hat, self): """ Returns the value of the derivative of the fields_hat with respect to time respect to time. This is used to evolve the fields with time. NOTE:All the fields quantities are included in fields_hat as follows: E1_hat = fields_hat[0] E2_hat = fields_hat[1] E3_hat = fields_hat[2] B1_hat = fields_hat[3] B2_hat = fields_hat[4] B3_hat = fields_hat[5] Input: ------ f_hat : Fourier mode values for the distribution function at which the slope is computed At t = 0 the initial state of the system is passed to this function: fields_hat : Fourier mode values for the fields at which the slope is computed At t = 0 the initial state of the system is passed to this function: Output: ------- df_dt : The time-derivative of f_hat """ eps = self.physical_system.params.eps mu = self.physical_system.params.mu B1_hat = fields_hat[3] B2_hat = fields_hat[4] B3_hat = fields_hat[5] if (self.physical_system.params.hybrid_model_enabled == True): # curlB_x = dB3/dq2 curlB_1 = B3_hat * 1j * self.k_q2 # curlB_y = -dB3/dq1 curlB_2 = -B3_hat * 1j * self.k_q1 # curlB_z = (dB2/dq1 - dB1/dq2) curlB_3 = (B2_hat * 1j * self.k_q1 - B1_hat * 1j * self.k_q2) # c --> inf limit: J = (∇ x B) / μ J1_hat = curlB_1 / mu J2_hat = curlB_2 / mu J3_hat = curlB_3 / mu else: J1_hat = multiply(self.physical_system.params.charge, self.compute_moments('mom_v1_bulk', f_hat=f_hat)) J2_hat = multiply(self.physical_system.params.charge, self.compute_moments('mom_v2_bulk', f_hat=f_hat)) J3_hat = multiply(self.physical_system.params.charge, self.compute_moments('mom_v3_bulk', f_hat=f_hat)) # Summing along all species: J1_hat = af.sum(J1_hat, 1) J2_hat = af.sum(J2_hat, 1) J3_hat = af.sum(J3_hat, 1) # Checking that there is no mean field component: # try: # assert(af.mean(af.abs(B1_hat[:, 0, 0])) < 1e-12) # assert(af.mean(af.abs(B2_hat[:, 0, 0])) < 1e-12) # assert(af.mean(af.abs(B3_hat[:, 0, 0])) < 1e-12) # except: # raise SystemExit('Linear Solver cannot solve for non-zero mean magnetic fields') # Equations Solved: # dE1/dt = + dB3/dq2 - J1 # dE2/dt = - dB3/dq1 - J2 # dE3/dt = dB2/dq1 - dB1/dq2 - J3 if (self.physical_system.params.hybrid_model_enabled == True): # Using Generalized Ohm's Law for electric field: n_i_hat = self.compute_moments('density', f_hat=f_hat) n_i = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * n_i_hat)) v1 = af.real( ifft2(0.5 * self.N_q2 * self.N_q1 * self.compute_moments('mom_v1_bulk', f_hat=f_hat))) / n_i v2 = af.real( ifft2(0.5 * self.N_q2 * self.N_q1 * self.compute_moments('mom_v2_bulk', f_hat=f_hat))) / n_i v3 = af.real( ifft2(0.5 * self.N_q2 * self.N_q1 * self.compute_moments('mom_v3_bulk', f_hat=f_hat))) / n_i B1 = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * B1_hat)) B2 = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * B2_hat)) B3 = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * B3_hat)) J1 = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * J1_hat)) J2 = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * J2_hat)) J3 = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * J3_hat)) T_e = self.physical_system.params.fluid_electron_temperature # Computing the quantities needed and then doing FT again: # # (v X B)_x = B3 * v2 - B2 * v3 # v_cross_B_1 = B3 * v2 - B2 * v3 # # (v X B)_y = B1 * v3 - B3 * v1 # v_cross_B_2 = B1 * v3 - B3 * v1 # # (v X B)_z = B2 * v1 - B1 * v2 # v_cross_B_3 = B2 * v1 - B1 * v2 # # (J X B)_x = B3 * J2 - B2 * J3 # J_cross_B_1 = B3 * J2 - B2 * J3 # # (J X B)_y = B1 * J3 - B3 * J1 # J_cross_B_2 = B1 * J3 - B3 * J1 # # (J X B)_z = B2 * J1 - B1 * J2 # J_cross_B_3 = B2 * J1 - B1 * J2 # # Using a 4th order stencil: # dn_q1 = (- af.shift(n_i, 0, 0, -2) + 8 * af.shift(n_i, 0, 0, -1) # - 8 * af.shift(n_i, 0, 0, 1) + af.shift(n_i, 0, 0, 2) # ) / (12 * self.dq1) # dn_q2 = (- af.shift(n_i, 0, 0, 0, -2) + 8 * af.shift(n_i, 0, 0, 0, -1) # - 8 * af.shift(n_i, 0, 0, 0, 1) + af.shift(n_i, 0, 0, 0, 2) # ) / (12 * self.dq2) # # E = -(v X B) + (J X B) / (en) - T ∇n / (en) # E1 = - v_cross_B_1 + J_cross_B_1 / multiply(self.physical_system.params.charge, n_i) \ # - T_e * dn_q1 / multiply(self.physical_system.params.charge, n_i) # E2 = - v_cross_B_2 + J_cross_B_2 / multiply(self.physical_system.params.charge, n_i) \ # - T_e * dn_q2 / multiply(self.physical_system.params.charge, n_i) # E3 = - v_cross_B_3 + J_cross_B_3 / multiply(self.physical_system.params.charge, n_i) # E1_hat = 2 * fft2(E1) / (self.N_q1 * self.N_q2) # E2_hat = 2 * fft2(E2) / (self.N_q1 * self.N_q2) - T_e * n_i_hat * self.k_q2 / af.mean(n_i) # E3_hat = 2 * fft2(E3) / (self.N_q1 * self.N_q2) # background magnetic fields == 0 ALWAYS: # (v X B)_x = B3 * v2 - B2 * v3 v_cross_B_1_hat = B3_hat * af.mean(v2) - B2_hat * af.mean(v3) # (v X B)_y = B1 * v3 - B3 * v1 v_cross_B_2_hat = B1_hat * af.mean(v3) - B3_hat * af.mean(v1) # (v X B)_z = B2 * v1 - B1 * v2 v_cross_B_3_hat = B2_hat * af.mean(v1) - B1_hat * af.mean(v2) # (J X B)_x = B3 * J2 - B2 * J3 J_cross_B_1_hat = B3_hat * af.mean(J2) - B2_hat * af.mean(J3) # (J X B)_y = B1 * J3 - B3 * J1 J_cross_B_2_hat = B1_hat * af.mean(J3) - B3_hat * af.mean(J1) # (J X B)_z = B2 * J1 - B1 * J2 J_cross_B_3_hat = B2_hat * af.mean(J1) - B1_hat * af.mean(J2) E1_hat = - v_cross_B_1_hat + multiply(J_cross_B_1_hat, 1 / self.physical_system.params.charge) / af.mean(n_i) \ - T_e * n_i_hat * self.k_q1 / af.mean(n_i) E2_hat = - v_cross_B_2_hat + multiply(J_cross_B_2_hat, 1 / self.physical_system.params.charge) / af.mean(n_i) \ - T_e * n_i_hat * self.k_q2 / af.mean(n_i) E3_hat = -v_cross_B_3_hat + multiply( J_cross_B_3_hat, 1 / self.physical_system.params.charge) / af.mean(n_i) self.fields_solver.fields_hat[0] = E1_hat self.fields_solver.fields_hat[1] = E2_hat self.fields_solver.fields_hat[2] = E3_hat else: E1_hat = fields_hat[0] E2_hat = fields_hat[1] E3_hat = fields_hat[2] dE1_hat_dt = B3_hat * 1j * self.k_q2 / (mu * eps) - J1_hat / eps dE2_hat_dt = -B3_hat * 1j * self.k_q1 / (mu * eps) - J2_hat / eps dE3_hat_dt = (B2_hat * 1j * self.k_q1 - B1_hat * 1j * self.k_q2) / (mu * eps) \ -J3_hat / eps # dB1/dt = - dE3/dq2 # dB2/dt = + dE3/dq1 # dB3/dt = - (dE2/dq1 - dE1/dq2) dB1_hat_dt = -E3_hat * 1j * self.k_q2 dB2_hat_dt = E3_hat * 1j * self.k_q1 dB3_hat_dt = E1_hat * 1j * self.k_q2 - E2_hat * 1j * self.k_q1 dfields_hat_dt = af.join(0, af.join(0, dE1_hat_dt, dE2_hat_dt, dE3_hat_dt), dB1_hat_dt, dB2_hat_dt, dB3_hat_dt) af.eval(dfields_hat_dt) return (dfields_hat_dt)
def dump_EM_fields(self, file_name): """ This function is used to EM fields to a file for later usage. This dumps all the EM fields quantities E1, E2, E3, B1, B2, B3 which can then be used later for post-processing Parameters ---------- file_name : The EM_fields array will be dumped to this provided file name. Returns ------- This function returns None. However it creates a file 'file_name.h5', containing the data of the EM fields. Examples -------- >> solver.dump_EM_fields('data_EM_fields') The above statement will create a HDF5 file which contains the EM fields data. The data is always stored with the key 'EM_fields' This can later be accessed using >> import h5py >> h5f = h5py.File('data_EM_fields.h5', 'r') >> EM_fields = h5f['EM_fields'][:] >> E1 = EM_fields[:, :, 0] >> E2 = EM_fields[:, :, 1] >> E3 = EM_fields[:, :, 2] >> B1 = EM_fields[:, :, 3] >> B2 = EM_fields[:, :, 4] >> B3 = EM_fields[:, :, 5] >> h5f.close() Alternatively, it can also be used with the load function to resume a long-running calculation. >> solver.load_EM_fields('data_EM_fields') """ array_to_dump = 0.5 * self.N_q2 * self.N_q1 * af.real( ifft2(self.fields_solver.fields_hat)) array_to_dump.to_ndarray(self.fields_solver._glob_fields_array) viewer = PETSc.Viewer().createHDF5(file_name + '.h5', 'w') viewer(self.fields_solver._glob_fields) return
n_data_ls = np.zeros_like(time_array) n_data_nls = np.zeros_like(time_array) for time_index, t0 in enumerate(time_array): if(time_index%100 == 0): print('Computing For Time =', t0) nls.dump_distribution_function('dump_f/%04d'%time_index) # nls.dump_moments('dump_moments/%04d'%time_index) E_data_nls[time_index] = af.sum(nls.fields_solver.cell_centered_EM_fields[:, :, N_g:-N_g, N_g:-N_g]**2) E1_ls = af.real(0.5 * (ls.N_q1 * ls.N_q2) * ifft2(ls.fields_solver.E1_hat) ) E_data_ls[time_index] = af.sum(E1_ls**2) n_data_nls[time_index] = af.max(nls.compute_moments('density')) n_data_ls[time_index] = af.max(ls.compute_moments('density')) nls.strang_timestep(dt) ls.RK4_timestep(dt) h5f = h5py.File('data.h5', 'w') h5f.create_dataset('electrical_energy_ls', data = E_data_ls) h5f.create_dataset('electrical_energy_nls', data = E_data_nls) h5f.create_dataset('density_ls', data = n_data_ls) h5f.create_dataset('density_nls', data = n_data_nls)
def df_hat_dt(f_hat, fields_hat, self): """ Returns the value of the derivative of the f_hat with respect to time respect to time. This is used to evolve the system in time. Input: ------ f_hat : Fourier mode values for the distribution function at which the slope is computed At t = 0 the initial state of the system is passed to this function: fields_hat : Fourier mode values for the fields at which the slope is computed At t = 0 the initial state of the system is passed to this function: Output: ------- df_dt : The time-derivative of f_hat """ (A_q1, A_q2) = self._A_q(self.time_elapsed, self.q1_center, self.q2_center, self.p1_center, self.p2_center, self.p3_center, self.physical_system.params) df_hat_dt = -1j * (multiply(self.k_q1, A_q1) + multiply(self.k_q2, A_q2)) * f_hat if (self.physical_system.params.source_enabled == True): # Scaling Appropriately: f = af.real(ifft2(0.5 * self.N_q2 * self.N_q1 * f_hat)) C_f_hat = 2 * fft2( self._source( f, self.time_elapsed, self.q1_center, self.q2_center, self.p1_center, self.p2_center, self.p3_center, self.compute_moments, self.physical_system.params)) / (self.N_q2 * self.N_q1) df_hat_dt += C_f_hat if (self.physical_system.params.fields_enabled == True): if (self.physical_system.params.fields_type == 'electrostatic'): rho_hat = multiply(self.physical_system.params.charge, self.compute_moments('density', f_hat=f_hat)) self.fields_solver.compute_electrostatic_fields(rho_hat) elif (self.physical_system.params.fields_type == 'electrodynamic'): # Handled by dfields_hat_dt pass # Used in debugging; advection tests for p-space where fields are to be held constant elif (self.physical_system.params.fields_type == 'None'): pass else: raise NotImplementedError('Invalid option for fields solver!') # get_fields for linear solver returns the mode amplitudes of the fields # So, we obtain A_p1_hat, A_p2_hat, A_p3_hat (A_p1_hat, A_p2_hat, A_p3_hat) = af.broadcast( self._A_p, self.time_elapsed, self.q1_center, self.q2_center, self.p1_center, self.p2_center, self.p3_center, self.fields_solver, self.physical_system.params) fields_term = multiply(A_p1_hat, self.dfdp1_background) \ + multiply(A_p2_hat, self.dfdp2_background) \ + multiply(A_p3_hat, self.dfdp3_background) # Including the mean magnetic field term: # TODO: Maybe wrong to add mean magnetic fields since e^{ikx} term won't cancel out. Check again # Multiplying by q_center**0 to get the values in the array of required dimension # Dividing by 2 to normalize appropriately(look at the initialization sector): B1_mean = af.mean( self.fields_solver.fields_hat[3, 0, 0, 0]) * self.q1_center**0 / 2 B2_mean = af.mean( self.fields_solver.fields_hat[4, 0, 0, 0]) * self.q1_center**0 / 2 B3_mean = af.mean( self.fields_solver.fields_hat[5, 0, 0, 0]) * self.q1_center**0 / 2 e = self.physical_system.params.charge m = self.physical_system.params.mass # Converting delta_f array to velocity_expanded form: f_hat_p_expanded = af.moddims(f_hat, self.N_p1, self.N_p2, self.N_p3, self.N_species * self.N_q1 * self.N_q2) # Computing ddelta_f_dp using a 4th order finite different stencil: ddelta_f_dp1 = multiply((-af.shift(f_hat_p_expanded, -2) + 8 * af.shift(f_hat_p_expanded, -1) + af.shift(f_hat_p_expanded, 2) - 8 * af.shift(f_hat_p_expanded, 1)), 1 / (12 * self.dp1)) ddelta_f_dp2 = multiply((-af.shift(f_hat_p_expanded, 0, -2) + 8 * af.shift(f_hat_p_expanded, 0, -1) + af.shift(f_hat_p_expanded, 0, 2) - 8 * af.shift(f_hat_p_expanded, 0, 1)), 1 / (12 * self.dp2)) ddelta_f_dp3 = multiply((-af.shift(f_hat_p_expanded, 0, 0, -2) + 8 * af.shift(f_hat_p_expanded, 0, 0, -1) + af.shift(f_hat_p_expanded, 0, 0, 2) - 8 * af.shift(f_hat_p_expanded, 0, 0, 1)), 1 / (12 * self.dp3)) # Converting back to positions expanded: ddelta_f_dp1 = af.moddims(ddelta_f_dp1, self.N_p1 * self.N_p2 * self.N_p3, self.N_species, self.N_q1, self.N_q2) ddelta_f_dp2 = af.moddims(ddelta_f_dp2, self.N_p1 * self.N_p2 * self.N_p3, self.N_species, self.N_q1, self.N_q2) ddelta_f_dp3 = af.moddims(ddelta_f_dp3, self.N_p1 * self.N_p2 * self.N_p3, self.N_species, self.N_q1, self.N_q2) fields_term_mean_magnetic_fields = \ multiply(e/m, ( (multiply(self.p2_center, B3_mean) - multiply(self.p3_center, B2_mean)) * ddelta_f_dp1 + (multiply(self.p3_center, B1_mean) - multiply(self.p1_center, B3_mean)) * ddelta_f_dp2 + (multiply(self.p1_center, B2_mean) - multiply(self.p2_center, B1_mean)) * ddelta_f_dp3 ) ) df_hat_dt -= fields_term + fields_term_mean_magnetic_fields af.eval(df_hat_dt) return (df_hat_dt)
def compute_moments(self, moment_name, f=None, f_hat=None): """ Used in computing the moments of the distribution function. The moment definitions which are passed to physical system are used in computing these moment quantities. Parameters ---------- moments_name : str Pass the moment name which needs to be computed. It must be noted that this needs to be defined by the user under moments under src and passed to the physical_system object. f/f_hat: np.ndarray Pass this argument as well when you want to compute the moments of the input array and not the one stored by the state vector of the object. Examples -------- >> solver.compute_moments('density') Will return the density of the system at its current state. """ if(f_hat is None and f is None): # af.broadcast(function, *args) performs batched operations on function(*args): moment_hat = af.broadcast(getattr(self.physical_system.moments, moment_name ), self.f_hat, self.p1_center, self.p2_center, self.p3_center, self.dp3 * self.dp2 * self.dp1 ) # Scaling Appropriately: moment_hat = 0.5 * self.N_q2 * self.N_q1 * moment_hat moment = af.real(ifft2(moment_hat)) af.eval(moment) return(moment) elif(f_hat is not None and f is None): moment_hat = af.broadcast(getattr(self.physical_system.moments, moment_name ), f_hat, self.p1_center, self.p2_center, self.p3_center, self.dp3 * self.dp2 * self.dp1 ) af.eval(moment_hat) return(moment_hat) elif(f_hat is None and f is not None): moment = af.broadcast(getattr(self.physical_system.moments, moment_name ), f, self.p1_center, self.p2_center, self.p3_center, self.dp3 * self.dp2 * self.dp1 ) af.eval(moment) return(moment) else: raise BaseException('Invalid Option: Both f and f_hat cannot \ be provided as arguments' )