The main function reads in the calculation parameters, checks that they are sensible, initializes the electron coordinates, and then evolves the electron equations of motion from to some specified , using a fixed step RK4 routine with some specified time-step . Information on the electron phase-space coordinates and the electric field is periodically written to various data-files.
// 1-d PIC code to solve plasma two-stream instability problem. #include <stdlib.h> #include <stdio.h> #include <math.h> #include <time.h> #include <blitz/array.h> #include <fftw.h> using namespace blitz; void Output (char* fn1, char* fn2, double t, Array<double,1> r, Array<double,1> v); void Density (Array<double,1> r, Array<double,1>& n); void Electric (Array<double,1> phi, Array<double,1>& E); void Poisson1D (Array<double,1>& u, Array<double,1> v, double kappa); void rk4_fixed (double& x, Array<double,1>& y, void (*rhs_eval)(double, Array<double,1>, Array<double,1>&), double h); void rhs_eval (double t, Array<double,1> y, Array<double,1>& dydt); void Load (Array<double,1> r, Array<double,1> v, Array<double,1>& y); void UnLoad (Array<double,1> y, Array<double,1>& r, Array<double,1>& v); double distribution (double vb); double L; int N, J; int main() { // Parameters L; // Domain of solution 0 <= x <= L (in Debye lengths) N; // Number of electrons J; // Number of grid points double vb; // Beam velocity double dt; // Time-step (in inverse plasma frequencies) double tmax; // Simulation run from t = 0. to t = tmax // Get parameters printf ("Please input N: "); scanf ("%d", &N); printf ("Please input vb: "); scanf ("%lf", &vb); printf ("Please input L: "); scanf ("%lf", &L); printf ("Please input J: "); scanf ("%d", &J); printf ("Please input dt: "); scanf ("%lf", &dt); printf ("Please input tmax: "); scanf ("%lf", &tmax); int skip = int (tmax / dt) / 10; if ((N < 1) || (J < 2) || (L <= 0.) || (vb <= 0.) || (dt <= 0.) || (tmax <= 0.) || (skip < 1)) { printf ("Error - invalid input parameters\n"); exit (1); } // Set names of output files char* phase[11]; char* data[11]; phase[0] = "phase0.out";phase[1] = "phase1.out";phase[2] = "phase2.out"; phase[3] = "phase3.out";phase[4] = "phase4.out";phase[5] = "phase5.out"; phase[6] = "phase6.out";phase[7] = "phase7.out";phase[8] = "phase8.out"; phase[9] = "phase9.out";phase[10] = "phase10.out";data[0] = "data0.out"; data[1] = "data1.out"; data[2] = "data2.out"; data[3] = "data3.out"; data[4] = "data4.out"; data[5] = "data5.out"; data[6] = "data6.out"; data[7] = "data7.out"; data[8] = "data8.out"; data[9] = "data9.out"; data[10] = "data10.out"; // Initialize solution double t = 0.; int seed = time (NULL); srand (seed); Array<double,1> r(N), v(N); for (int i = 0; i < N; i++) { r(i) = L * double (rand ()) / double (RAND_MAX); v(i) = distribution (vb); } Output (phase[0], data[0], t, r, v); // Evolve solution Array<double,1> y(2*N); Load (r, v, y); for (int k = 1; k <= 10; k++) { for (int kk = 0; kk < skip; kk++) { // Take time-step rk4_fixed (t, y, rhs_eval, dt); // Make sure all coordinates in range 0 to L. for (int i = 0; i < N; i++) { if (y(i) < 0.) y(i) += L; if (y(i) > L) y(i) -= L; } printf ("t = %11.4e\n", t); } printf ("Plot %3d\n", k); // Output data UnLoad (y, r, v); Output(phase[k], data[k], t, r, v); } return 0; }
The following routine outputs the simulation data to various data-files.
// Write data to output files void Output (char* fn1, char* fn2, double t, Array<double,1> r, Array<double,1> v) { // Write phase-space data FILE* file = fopen (fn1, "w"); for (int i = 0; i < N; i++) fprintf (file, "%e %e\n", r(i), v(i)); fclose (file); // Write electric field data Array<double,1> ne(J), n(J), phi(J), E(J); Density (r, ne); for (int j = 0; j < J; j++) n(j) = double (J) * ne(j) / double (N) - 1.; double kappa = 2. * M_PI / L; Poisson1D (phi, n, kappa); Electric (phi, E); file = fopen (fn2, "w"); for (int j = 0; j < J; j++) { double x = double (j) * L / double (J); fprintf (file, "%e %e %e %e\n", x, ne(j), n(j), E(j)); } double x = L; fprintf (file, "%e %e %e %e\n", x, ne(0), n(0), E(0)); fclose (file); }
The following routine returns a random velocity distributed on a double Maxwellian distribution function corresponding to two counter-streaming beams. The algorithm used to achieve this is called the rejection method, and will be discussed later in this course.
// Function to distribute electron velocities randomly so as // to generate two counter propagating warm beams of thermal // velocities unity and mean velocities +/- vb. // Uses rejection method. double distribution (double vb) { // Initialize random number generator static int flag = 0; if (flag == 0) { int seed = time (NULL); srand (seed); flag = 1; } // Generate random v value double fmax = 0.5 * (1. + exp (-2. * vb * vb)); double vmin = - 5. * vb; double vmax = + 5. * vb; double v = vmin + (vmax - vmin) * double (rand ()) / double (RAND_MAX); // Accept/reject value double f = 0.5 * (exp (-(v - vb) * (v - vb) / 2.) + exp (-(v + vb) * (v + vb) / 2.)); double x = fmax * double (rand ()) / double (RAND_MAX); if (x > f) return distribution (vb); else return v; }
The routine below evaluates the electron number density on an evenly spaced mesh given the instantaneous electron coordinates.
// Evaluates electron number density n(0:J-1) from // array r(0:N-1) of electron coordinates. void Density (Array<double,1> r, Array<double,1>& n) { // Initialize double dx = L / double (J); n = 0.; // Evaluate number density. for (int i = 0; i < N; i++) { int j = int (r(i) / dx); double y = r(i) / dx - double (j); n(j) += (1. - y) / dx; if (j+1 == J) n(0) += y / dx; else n(j+1) += y / dx; } }
The following functions are wrapper routines for using the fftw library with periodic functions.
// Functions to calculate Fourier transforms of real data // using fftw Fast-Fourier-Transform routine. // Input/ouput arrays are assumed to be of extent J. // Calculates Fourier transform of array f in arrays Fr and Fi void fft_forward (Array<double,1>f, Array<double,1>&Fr, Array<double,1>& Fi) { fftw_complex ff[J], FF[J]; // Load data for (int j = 0; j < J; j++) { c_re (ff[j]) = f(j); c_im (ff[j]) = 0.; } // Call fftw routine fftw_plan p = fftw_create_plan (J, FFTW_FORWARD, FFTW_ESTIMATE); fftw_one (p, ff, FF); fftw_destroy_plan (p); // Unload data for (int j = 0; j < J; j++) { Fr(j) = c_re (FF[j]); Fi(j) = c_im (FF[j]); } // Normalize data Fr /= double (J); Fi /= double (J); } // Calculates inverse Fourier transform of arrays Fr and Fi in array f void fft_backward (Array<double,1> Fr, Array<double,1> Fi, Array<double,1>& f) { fftw_complex ff[J], FF[J]; // Load data for (int j = 0; j < J; j++) { c_re (FF[j]) = Fr(j); c_im (FF[j]) = Fi(j); } // Call fftw routine fftw_plan p = fftw_create_plan (J, FFTW_BACKWARD, FFTW_ESTIMATE); fftw_one (p, FF, ff); fftw_destroy_plan (p); // Unload data for (int j = 0; j < J; j++) f(j) = c_re (ff[j]); }
The following routine solves Poisson's equation in 1-D to find the instantaneous electric potential on a uniform grid.
// Solves 1-d Poisson equation: // d^u / dx^2 = v for 0 <= x <= L // Periodic boundary conditions: // u(x + L) = u(x), v(x + L) = v(x) // Arrays u and v assumed to be of length J. // Now, jth grid point corresponds to // x_j = j dx for j = 0,J-1 // where dx = L / J. // Also, // kappa = 2 pi / L void Poisson1D (Array<double,1>& u, Array<double,1> v, double kappa) { // Declare local arrays. Array<double,1> Vr(J), Vi(J), Ur(J), Ui(J); // Fourier transform source term fft_forward (v, Vr, Vi); // Calculate Fourier transform of u Ur(0) = Ui(0) = 0.; for (int j = 1; j <= J/2; j++) { Ur(j) = - Vr(j) / double (j * j) / kappa / kappa; Ui(j) = - Vi(j) / double (j * j) / kappa / kappa; } for (int j = J/2; j < J; j++) { Ur(j) = Ur(J-j); Ui(j) = - Ui(J-j); } // Inverse Fourier transform to obtain u fft_backward (Ur, Ui, u); }
The following function evaluates the electric field on a uniform grid from the electric potential.
// Calculate electric field from potential void Electric (Array<double,1> phi, Array<double,1>& E) { double dx = L / double (J); for (int j = 1; j < J-1; j++) E(j) = (phi(j-1) - phi(j+1)) / 2. / dx; E(0) = (phi(J-1) - phi(1)) / 2. / dx; E(J-1) = (phi(J-2) - phi(0)) / 2. / dx; }
The following routine is the right-hand side routine for the electron equations of motion. Is is designed to be used with the fixed-step RK4 solver described earlier in this course.
// Electron equations of motion: // y(0:N-1) = r_i // y(N:2N-1) = dr_i/dt void rhs_eval (double t, Array<double,1> y, Array<double,1>& dydt) { // Declare local arrays Array<double,1> r(N), v(N), rdot(N), vdot(N), r0(N); Array<double,1> ne(J), rho(J), phi(J), E(J); // Unload data from y UnLoad (y, r, v); // Make sure all coordinates in range 0 to L r0 = r; for (int i = 0; i < N; i++) { if (r0(i) < 0.) r0(i) += L; if (r0(i) > L) r0(i) -= L; } // Calculate electron number density Density (r0, ne); // Solve Poisson's equation double n0 = double (N) / L; for (int j = 0; j < J; j++) rho(j) = ne(j) / n0 - 1.; double kappa = 2. * M_PI / L; Poisson1D (phi, rho, kappa); // Calculate electric field Electric (phi, E); // Equations of motion for (int i = 0; i < N; i++) { double dx = L / double (J); int j = int (r0(i) / dx); double y = r0(i) / dx - double (j); double Efield; if (j+1 == J) Efield = E(j) * (1. - y) + E(0) * y; else Efield = E(j) * (1. - y) + E(j+1) * y; rdot(i) = v(i); vdot(i) = - Efield; } // Load data into dydt Load (rdot, vdot, dydt); }
The following functions load and unload the electron phase-space
coordinates into the solution vector y
used by the RK4 routine.
// Load particle coordinates into solution vector void Load (Array<double,1> r, Array<double,1> v, Array<double,1>& y) { for (int i = 0; i < N; i++) { y(i) = r(i); y(N+i) = v(i); } } // Unload particle coordinates from solution vector void UnLoad (Array<double,1> y, Array<double,1>& r, Array<double,1>& v) { for (int i = 0; i < N; i++) { r(i) = y(i); v(i) = y(N+i); } }