diff --git a/div_ops.cxx b/div_ops.cxx index 377cc8a..74a930a 100644 --- a/div_ops.cxx +++ b/div_ops.cxx @@ -55,14 +55,14 @@ const Field3D Div_par_diffusion_index(const Field3D &f, bool bndry_flux) { continue; } BoutReal J = - 0.5 * (coord->J(i, j) + coord->J(i, j + 1)); // Jacobian at boundary + 0.5 * (coord->J(i, j, k) + coord->J.yup()(i, j + 1, k)); // Jacobian at boundary - BoutReal gradient = f(i, j + 1, k) - f(i, j, k); + BoutReal gradient = f.yup()(i, j + 1, k) - f(i, j, k); BoutReal flux = J * gradient; - result(i, j, k) += flux / coord->J(i, j); - result(i, j + 1, k) -= flux / coord->J(i, j + 1); + result(i, j, k) += flux / coord->J(i, j, k); + result.yup()(i, j + 1, k) -= flux / coord->J.yup()(i, j + 1, k); } return result; } @@ -86,10 +86,10 @@ struct Stencil1D { }; // First order upwind for testing -void Upwind(Stencil1D &n, const BoutReal h) { n.L = n.R = n.c; } +void Upwind(Stencil1D &n) { n.L = n.R = n.c; } // Fromm method -void Fromm(Stencil1D &n, const BoutReal h) { +void Fromm(Stencil1D &n) { n.L = n.c - 0.25 * (n.p - n.m); n.R = n.c + 0.25 * (n.p - n.m); } @@ -115,7 +115,7 @@ BoutReal minmod(BoutReal a, BoutReal b, BoutReal c) { return SIGN(a) * BOUTMIN(fabs(a), fabs(b), fabs(c)); } -void MinMod(Stencil1D &n, const BoutReal h) { +void MinMod(Stencil1D &n) { // Choose the gradient within the cell // as the minimum (smoothest) solution BoutReal slope = minmod(n.p - n.c, n.c - n.m); @@ -124,7 +124,7 @@ void MinMod(Stencil1D &n, const BoutReal h) { } // Monotonized Central limiter (Van-Leer) -void MC(Stencil1D &n, const BoutReal h) { +void MC(Stencil1D &n) { BoutReal slope = minmod(2. * (n.p - n.c), 0.5 * (n.p - n.m), 2. * (n.c - n.m)); n.L = n.c - 0.5 * slope; @@ -267,13 +267,15 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, // 2) Calculate velocities on cell faces - BoutReal vU = coord->J(i, j) * (fmp - fpp) / coord->dx(i, j); // -J*df/dx - BoutReal vD = coord->J(i, j) * (fmm - fpm) / coord->dx(i, j); // -J*df/dx + BoutReal vU = 0.5 * (coord->J(i, j, k) + coord->J(i, j , kp)) * (fmp - fpp) / + coord->dx(i, j, k); // -J*df/dx + BoutReal vD = 0.5 * (coord->J(i, j, k) + coord->J(i, j , km)) * (fmm - fpm) / + coord->dx(i, j, k); // -J*df/dx - BoutReal vR = 0.5 * (coord->J(i, j) + coord->J(i + 1, j)) * (fpp - fpm) / - coord->dz; // J*df/dz - BoutReal vL = 0.5 * (coord->J(i, j) + coord->J(i - 1, j)) * (fmp - fmm) / - coord->dz; // J*df/dz + BoutReal vR = 0.5 * (coord->J(i, j, k) + coord->J(i + 1, j, k)) * (fpp - fpm) / + coord->dz(i,j,k); // J*df/dz + BoutReal vL = 0.5 * (coord->J(i, j, k) + coord->J(i - 1, j, k)) * (fmp - fmm) / + coord->dz(i,j,k); // J*df/dz // output.write("NEW: (%d,%d,%d) : (%e/%e, %e/%e)\n", i,j,k,vL,vR, // vU,vD); @@ -292,7 +294,7 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, // Upwind(s, mesh->dx(i,j)); // XPPM(s, mesh->dx(i,j)); // Fromm(s, coord->dx(i, j)); - MC(s, coord->dx(i, j)); + MC(s); // Right side if ((i == mesh->xend) && (mesh->lastX())) { @@ -307,18 +309,18 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, // Flux in from boundary flux = vR * 0.5 * (n(i + 1, j, k) + n(i, j, k)); } - result(i, j, k) += flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j, k) += flux / (coord->dx(i, j, k) * coord->J(i, j, k)); result(i + 1, j, k) -= - flux / (coord->dx(i + 1, j) * coord->J(i + 1, j)); + flux / (coord->dx(i + 1, j, k) * coord->J(i + 1, j, k)); } } else { // Not at a boundary if (vR > 0.0) { // Flux out into next cell BoutReal flux = vR * s.R; - result(i, j, k) += flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j, k) += flux / (coord->dx(i, j, k) * coord->J(i, j, k)); result(i + 1, j, k) -= - flux / (coord->dx(i + 1, j) * coord->J(i + 1, j)); + flux / (coord->dx(i + 1, j, k) * coord->J(i + 1, j, k)); // if(i==mesh->xend) // output.write("Setting flux (%d,%d) : %e\n", @@ -342,18 +344,18 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, // Flux in from boundary flux = vL * 0.5 * (n(i - 1, j, k) + n(i, j, k)); } - result(i, j, k) -= flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j, k) -= flux / (coord->dx(i, j, k) * coord->J(i, j, k)); result(i - 1, j, k) += - flux / (coord->dx(i - 1, j) * coord->J(i - 1, j)); + flux / (coord->dx(i - 1, j, k) * coord->J(i - 1, j, k)); } } else { // Not at a boundary if (vL < 0.0) { BoutReal flux = vL * s.L; - result(i, j, k) -= flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j, k) -= flux / (coord->dx(i, j, k) * coord->J(i, j, k)); result(i - 1, j, k) += - flux / (coord->dx(i - 1, j) * coord->J(i - 1, j)); + flux / (coord->dx(i - 1, j, k) * coord->J(i - 1, j, k)); } } @@ -368,17 +370,17 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, // Upwind(s, coord->dz); // XPPM(s, coord->dz); // Fromm(s, coord->dz); - MC(s, coord->dz); + MC(s); if (vU > 0.0) { - BoutReal flux = vU * s.R / (coord->J(i, j) * coord->dz); - result(i, j, k) += flux; - result(i, j, kp) -= flux; + BoutReal flux = vU * s.R; + result(i, j, k) += flux / (coord->J(i, j, k) * coord->dz(i, j, k)); + result(i, j, kp) -= flux / (coord->J(i, j, kp) * coord->dz(i, j, kp)); } if (vD < 0.0) { - BoutReal flux = vD * s.L / (coord->J(i, j) * coord->dz); - result(i, j, k) -= flux; - result(i, j, km) += flux; + BoutReal flux = vD * s.L; + result(i, j, k) -= flux / (coord->J(i, j, k) * coord->dz(i, j, k)); + result(i, j, km) += flux / (coord->J(i, j, km) * coord->dz(i, j, km)); } } FV::communicateFluxes(result); @@ -426,14 +428,14 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, // Average dfdy to right X boundary BoutReal f_R = - 0.5 * ((coord->g11(i + 1, j) * coord->g23(i + 1, j) / - SQ(coord->Bxy(i + 1, j))) * - dfdy(i + 1, j, k) + - (coord->g11(i, j) * coord->g23(i, j) / SQ(coord->Bxy(i, j))) * - dfdy(i, j, k)); + 0.5 * ((coord->g11(i + 1, j, k) * coord->g23(i + 1, j, k) / + SQ(coord->Bxy(i + 1, j, k))) * + dfdy(i + 1, j, k) + + (coord->g11(i, j, k) * coord->g23(i, j, k) / SQ(coord->Bxy(i, j, k))) * + dfdy(i, j, k)); // Advection velocity across cell face - BoutReal Vx = 0.5 * (coord->J(i + 1, j) + coord->J(i, j)) * f_R; + BoutReal Vx = 0.5 * (coord->J(i + 1, j, k) + coord->J(i, j, k)) * f_R; // Fromm method BoutReal flux = Vx; @@ -456,9 +458,9 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, flux *= nval; } - result(i, j, k) += flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j, k) += flux / (coord->dx(i, j, k) * coord->J(i, j, k)); result(i + 1, j, k) -= - flux / (coord->dx(i + 1, j) * coord->J(i + 1, j)); + flux / (coord->dx(i + 1, j, k) * coord->J(i + 1, j, k)); } } @@ -497,13 +499,13 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, // Average dfdx to upper Y boundary BoutReal f_U = - 0.5 * ((coord->g11(i, j + 1) * coord->g23(i, j + 1) / - SQ(coord->Bxy(i, j + 1))) * - dfdx(i, j + 1, k) + - (coord->g11(i, j) * coord->g23(i, j) / SQ(coord->Bxy(i, j))) * - dfdx(i, j, k)); + 0.5 * ((coord->g11(i, j + 1, k) * coord->g23(i, j + 1, k) / + SQ(coord->Bxy(i, j + 1, k))) * + dfdx(i, j + 1, k) + + (coord->g11(i, j, k) * coord->g23(i, j, k) / SQ(coord->Bxy(i, j, k))) * + dfdx(i, j, k)); - BoutReal Vy = -0.5 * (coord->J(i, j + 1) + coord->J(i, j)) * f_U; + BoutReal Vy = -0.5 * (coord->J(i, j + 1, k) + coord->J(i, j, k)) * f_U; if (mesh->firstY(i) && !mesh->periodicY(i) && (j == mesh->ystart - 1)) { @@ -537,8 +539,8 @@ const Field3D Div_n_bxGrad_f_B_XPPM(const Field3D &n, const Field3D &f, flux *= nval; } - yresult(i, j, k) += flux / (coord->dy(i, j) * coord->J(i, j)); - yresult(i, j + 1, k) -= flux / (coord->dy(i, j + 1) * coord->J(i, j + 1)); + yresult(i, j, k) += flux / (coord->dy(i, j, k) * coord->J(i, j, k)); + yresult(i, j + 1, k) -= flux / (coord->dy(i, j + 1, k) * coord->J(i, j + 1, k)); } } } @@ -588,26 +590,33 @@ const Field3D Div_Perp_Lap_FV_Index(const Field3D &as, const Field3D &fs, BoutReal gU = fs(i, j, kp) - fs(i, j, k); // Flow right - BoutReal flux = gR * 0.25 * (coord->J(i + 1, j) + coord->J(i, j)) * - (coord->dx(i + 1, j) + coord->dx(i, j)) * - (as(i + 1, j, k) + as(i, j, k)); + BoutReal flux = gR * 0.25 * (coord->J(i + 1, j, k) + coord->J(i, j, k)) * + (coord->dx(i + 1, j, k) + coord->dx(i, j, k)) * + (as(i + 1, j, k) + as(i, j, k)); - result(i, j, k) += flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j, k) += flux / (coord->dx(i, j, k) * coord->J(i, j, k)); // Flow left - flux = gL * 0.25 * (coord->J(i - 1, j) + coord->J(i, j)) * - (coord->dx(i - 1, j) + coord->dx(i, j)) * - (as(i - 1, j, k) + as(i, j, k)); + flux = gL * 0.25 * (coord->J(i - 1, j, k) + coord->J(i, j, k)) * + (coord->dx(i - 1, j, k) + coord->dx(i, j, k)) * + (as(i - 1, j, k) + as(i, j, k)); - result(i, j, k) -= flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j, k) -= flux / (coord->dx(i, j, k) * coord->J(i, j, k)); // Flow up - flux = gU * 0.5 * (as(i, j, k) + as(i, j, kp)); - result(i, j, k) += flux; + flux = gU * 0.25 * (coord->J(i, j, k) + coord->J(i, j, kp)) * + (coord->dz(i, j, k) + coord->dz(i, j, kp)) * + (as(i, j, k) + as(i, j, kp)); + + result(i, j, k) += flux / (coord->dz(i, j, k) * coord->J(i, j, k)); - flux = gD * 0.5 * (as(i, j, k) + as(i, j, km)); - result(i, j, k) -= flux; + // Flow down + flux = gD * 0.25 * (coord->J(i, j, km) + coord->J(i, j, k)) * + (coord->dz(i, j, km) + coord->dz(i, j, k)) * + (as(i, j, km) + as(i, j, k)); + + result(i, j, k) -= flux / (coord->dz(i, j, k) * coord->J(i, j, k)); } return result; @@ -628,8 +637,8 @@ const Field3D D4DX4_FV_Index(const Field3D &f, bool bndry_flux) { BoutReal d3fdx3 = (f(i + 2, j, k) - 3. * f(i + 1, j, k) + 3. * f(i, j, k) - f(i - 1, j, k)); - BoutReal flux = 0.25 * (coord->dx(i, j) + coord->dx(i + 1, j)) * - (coord->J(i, j) + coord->J(i + 1, j)) * d3fdx3; + BoutReal flux = 0.25 * (coord->dx(i, j, k) + coord->dx(i + 1, j, k)) * + (coord->J(i, j, k) + coord->J(i + 1, j, k)) * d3fdx3; if (mesh->lastX() && (i == mesh->xend)) { // Boundary @@ -644,8 +653,8 @@ const Field3D D4DX4_FV_Index(const Field3D &f, bool bndry_flux) { - (6. / 5) * f(i - 2, j, k) // f_2 ); - flux = 0.25 * (coord->dx(i, j) + coord->dx(i + 1, j)) * - (coord->J(i, j) + coord->J(i + 1, j)) * d3fdx3; + flux = 0.25 * (coord->dx(i, j, k) + coord->dx(i + 1, j, k)) * + (coord->J(i, j, k) + coord->J(i + 1, j, k)) * d3fdx3; } else { // No fluxes through boundary @@ -653,8 +662,8 @@ const Field3D D4DX4_FV_Index(const Field3D &f, bool bndry_flux) { } } - result(i, j, k) += flux / (coord->J(i, j) * coord->dx(i, j)); - result(i + 1, j, k) -= flux / (coord->J(i + 1, j) * coord->dx(i + 1, j)); + result(i, j, k) += flux / (coord->J(i, j, k) * coord->dx(i, j, k)); + result(i + 1, j, k) -= flux / (coord->J(i + 1, j, k) * coord->dx(i + 1, j, k)); if (j == mesh->xstart) { // Left cell boundary, no flux through boundaries @@ -670,12 +679,12 @@ const Field3D D4DX4_FV_Index(const Field3D &f, bool bndry_flux) { + (6. / 5) * f(i + 2, j, k) // f_2 ); - flux = 0.25 * (coord->dx(i, j) + coord->dx(i + 1, j)) * - (coord->J(i, j) + coord->J(i + 1, j)) * d3fdx3; + flux = 0.25 * (coord->dx(i, j, k) + coord->dx(i + 1, j, k)) * + (coord->J(i, j, k) + coord->J(i + 1, j, k)) * d3fdx3; - result(i, j, k) -= flux / (coord->J(i, j) * coord->dx(i, j)); + result(i, j, k) -= flux / (coord->J(i, j, k) * coord->dx(i, j, k)); result(i - 1, j, k) += - flux / (coord->J(i - 1, j) * coord->dx(i - 1, j)); + flux / (coord->J(i - 1, j, k) * coord->dx(i - 1, j, k)); } } else { @@ -683,12 +692,12 @@ const Field3D D4DX4_FV_Index(const Field3D &f, bool bndry_flux) { d3fdx3 = (f(i + 1, j, k) - 3. * f(i, j, k) + 3. * f(i - 1, j, k) - f(i - 2, j, k)); - flux = 0.25 * (coord->dx(i, j) + coord->dx(i + 1, j)) * - (coord->J(i, j) + coord->J(i + 1, j)) * d3fdx3; + flux = 0.25 * (coord->dx(i, j, k) + coord->dx(i + 1, j, k)) * + (coord->J(i, j, k) + coord->J(i + 1, j, k)) * d3fdx3; - result(i, j, k) -= flux / (coord->J(i, j) * coord->dx(i, j)); + result(i, j, k) -= flux / (coord->J(i, j, k) * coord->dx(i, j, k)); result(i - 1, j, k) += - flux / (coord->J(i - 1, j) * coord->dx(i - 1, j)); + flux / (coord->J(i - 1, j, k) * coord->dx(i - 1, j, k)); } } } @@ -697,6 +706,15 @@ const Field3D D4DX4_FV_Index(const Field3D &f, bool bndry_flux) { return result; } +const Field3D D4DZ4_Index(const Field3D &f) { + Field3D result{emptyFrom(f)}; + + BOUT_FOR(i, f.getRegion("RGN_NOBNDRY")) { + result[i] = f[i.zpp()] - 4. * f[i.zp()] + 6. * f[i] - 4. * f[i.zm()] + f[i.zmm()]; + } + return result; +} + /*! *** USED *** * X-Y diffusion * @@ -709,52 +727,56 @@ const Field2D Laplace_FV(const Field2D &k, const Field2D &f) { result.allocate(); Coordinates *coord = mesh->getCoordinates(); - + Field2D g11_2D = DC(coord->g11); + Field2D g22_2D = DC(coord->g22); + Field2D dx_2D = DC(coord->dx); + Field2D J_2D = DC(coord->J); + for (int i = mesh->xstart; i <= mesh->xend; i++) for (int j = mesh->ystart; j <= mesh->yend; j++) { // Calculate gradients on cell faces - BoutReal gR = (coord->g11(i, j) + coord->g11(i + 1, j)) * + BoutReal gR = (g11_2D(i, j) + g11_2D(i + 1, j)) * (f(i + 1, j) - f(i, j)) / - (coord->dx(i + 1, j) + coord->dx(i, j)); + (dx_2D(i + 1, j) + dx_2D(i, j)); - BoutReal gL = (coord->g11(i - 1, j) + coord->g11(i, j)) * + BoutReal gL = (g11_2D(i - 1, j) + g11_2D(i, j)) * (f(i, j) - f(i - 1, j)) / - (coord->dx(i - 1, j) + coord->dx(i, j)); + (dx_2D(i - 1, j) + dx_2D(i, j)); - BoutReal gU = (coord->g22(i, j) + coord->g22(i, j + 1)) * + BoutReal gU = (g22_2D(i, j) + g22_2D(i, j + 1)) * (f(i, j + 1) - f(i, j)) / - (coord->dy(i, j + 1) + coord->dy(i, j)); + (dx_2D(i, j + 1) + dx_2D(i, j)); - BoutReal gD = (coord->g22(i, j - 1) + coord->g22(i, j)) * + BoutReal gD = (g22_2D(i, j - 1) + g22_2D(i, j)) * (f(i, j) - f(i, j - 1)) / - (coord->dy(i, j) + coord->dy(i, j - 1)); + (dx_2D(i, j) + dx_2D(i, j - 1)); // Flow right - BoutReal flux = gR * 0.25 * (coord->J(i + 1, j) + coord->J(i, j)) * + BoutReal flux = gR * 0.25 * (J_2D(i + 1, j) + J_2D(i, j)) * (k(i + 1, j) + k(i, j)); - result(i, j) = flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j) = flux / (dx_2D(i, j) * J_2D(i, j)); // Flow left - flux = gL * 0.25 * (coord->J(i - 1, j) + coord->J(i, j)) * + flux = gL * 0.25 * (J_2D(i - 1, j) + J_2D(i, j)) * (k(i - 1, j) + k(i, j)); - result(i, j) -= flux / (coord->dx(i, j) * coord->J(i, j)); + result(i, j) -= flux / (dx_2D(i, j) * J_2D(i, j)); // Flow up - flux = gU * 0.25 * (coord->J(i, j + 1) + coord->J(i, j)) * + flux = gU * 0.25 * (J_2D(i, j + 1) + J_2D(i, j)) * (k(i, j + 1) + k(i, j)); - result(i, j) += flux / (coord->dy(i, j) * coord->J(i, j)); + result(i, j) += flux / (dx_2D(i, j) * J_2D(i, j)); // Flow down - flux = gD * 0.25 * (coord->J(i, j - 1) + coord->J(i, j)) * + flux = gD * 0.25 * (J_2D(i, j - 1) + J_2D(i, j)) * (k(i, j - 1) + k(i, j)); - result(i, j) -= flux / (coord->dy(i, j) * coord->J(i, j)); + result(i, j) -= flux / (dx_2D(i, j) * J_2D(i, j)); } return result; } diff --git a/full-velocity.cxx b/full-velocity.cxx index 3b54947..8ed22a1 100644 --- a/full-velocity.cxx +++ b/full-velocity.cxx @@ -16,8 +16,12 @@ FullVelocity::FullVelocity(Solver *solver, Mesh *mesh, Options &options) * V_x, V_y, V_z */ - coord = mesh->getCoordinates(); - + Coordinates *coord = mesh->getCoordinates(); + Field2D dx_2D = DC(coord->dx); + Field2D dy_2D = DC(coord->dy); + Field2D J_2D = DC(coord->J); + Field2D g_22_2D = DC(coord->g_22); + options.get("gamma_ratio", gamma_ratio, 5. / 3); options.get("viscosity", neutral_viscosity, 1e-2); options.get("bulk", neutral_bulk, 1e-2); @@ -75,22 +79,20 @@ FullVelocity::FullVelocity(Solver *solver, Mesh *mesh, Options &options) Txz.allocate(); Tyr.allocate(); Tyz.allocate(); - - Coordinates *coord = mesh->getCoordinates(); for (int i = 0; i < mesh->LocalNx; i++) for (int j = mesh->ystart; j <= mesh->yend; j++) { // Central differencing of coordinates BoutReal dRdtheta, dZdtheta; if (j == mesh->ystart) { - dRdtheta = (Rxy(i, j + 1) - Rxy(i, j)) / (coord->dy(i, j)); - dZdtheta = (Zxy(i, j + 1) - Zxy(i, j)) / (coord->dy(i, j)); + dRdtheta = (Rxy(i, j + 1) - Rxy(i, j)) / (dy_2D(i, j)); + dZdtheta = (Zxy(i, j + 1) - Zxy(i, j)) / (dy_2D(i, j)); } else if (j == mesh->yend) { - dRdtheta = (Rxy(i, j) - Rxy(i, j - 1)) / (coord->dy(i, j)); - dZdtheta = (Zxy(i, j) - Zxy(i, j - 1)) / (coord->dy(i, j)); + dRdtheta = (Rxy(i, j) - Rxy(i, j - 1)) / (dy_2D(i, j)); + dZdtheta = (Zxy(i, j) - Zxy(i, j - 1)) / (dy_2D(i, j)); } else { - dRdtheta = (Rxy(i, j + 1) - Rxy(i, j - 1)) / (2. * coord->dy(i, j)); - dZdtheta = (Zxy(i, j + 1) - Zxy(i, j - 1)) / (2. * coord->dy(i, j)); + dRdtheta = (Rxy(i, j + 1) - Rxy(i, j - 1)) / (2. * dy_2D(i, j)); + dZdtheta = (Zxy(i, j + 1) - Zxy(i, j - 1)) / (2. * dy_2D(i, j)); } // Match to hthe, 1/|Grad y| @@ -102,15 +104,15 @@ FullVelocity::FullVelocity(Solver *solver, Mesh *mesh, Options &options) BoutReal dRdpsi, dZdpsi; if (i == 0) { // One-sided differences - dRdpsi = (Rxy(i + 1, j) - Rxy(i, j)) / (coord->dx(i, j)); - dZdpsi = (Zxy(i + 1, j) - Zxy(i, j)) / (coord->dx(i, j)); + dRdpsi = (Rxy(i + 1, j) - Rxy(i, j)) / (dx_2D(i, j)); + dZdpsi = (Zxy(i + 1, j) - Zxy(i, j)) / (dx_2D(i, j)); } else if (i == (mesh->LocalNx - 1)) { // One-sided differences - dRdpsi = (Rxy(i, j) - Rxy(i - 1, j)) / (coord->dx(i, j)); - dZdpsi = (Zxy(i, j) - Zxy(i - 1, j)) / (coord->dx(i, j)); + dRdpsi = (Rxy(i, j) - Rxy(i - 1, j)) / (dx_2D(i, j)); + dZdpsi = (Zxy(i, j) - Zxy(i - 1, j)) / (dx_2D(i, j)); } else { - dRdpsi = (Rxy(i + 1, j) - Rxy(i - 1, j)) / (2. * coord->dx(i, j)); - dZdpsi = (Zxy(i + 1, j) - Zxy(i - 1, j)) / (2. * coord->dx(i, j)); + dRdpsi = (Rxy(i + 1, j) - Rxy(i - 1, j)) / (2. * dx_2D(i, j)); + dZdpsi = (Zxy(i + 1, j) - Zxy(i - 1, j)) / (2. * dx_2D(i, j)); } // Match to Bp, |Grad psi|. NOTE: this only works if @@ -156,8 +158,15 @@ FullVelocity::FullVelocity(Solver *solver, Mesh *mesh, Options &options) void FullVelocity::update(const Field3D &Ne, const Field3D &Te, const Field3D &Ti, const Field3D &Vi) { - + mesh->communicate(Nn2D, Vn2D, Pn2D); + + Coordinates *coord = mesh->getCoordinates(); + Field2D dx2D = DC(coord->dx); + Field2D dy_2D = DC(coord->dy); + Field2D J_2D = DC(coord->J); + Field2D g_22_2D = DC(coord->g_22); + Field2D Bxy2D = DC(coord->Bxy); // Navier-Stokes for axisymmetric neutral gas profiles // Nn2D, Pn2D and Tn2D are unfloored @@ -181,26 +190,28 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, for (RangeIterator idwn = mesh->iterateBndryLowerY(); !idwn.isDone(); idwn.next()) { - - if (Vn2D.y(idwn.ind, mesh->ystart) < 0.0) { - // Flowing out of domain - Vn2D.y(idwn.ind, mesh->ystart - 1) = Vn2D.y(idwn.ind, mesh->ystart); - } else { - // Flowing into domain - Vn2D.y(idwn.ind, mesh->ystart - 1) = -Vn2D.y(idwn.ind, mesh->ystart); + for (int k = 0; k < mesh->LocalNz; k++) { + + if (Vn2D.y(idwn.ind, mesh->ystart, k) < 0.0) { + // Flowing out of domain + Vn2D.y(idwn.ind, mesh->ystart - 1, k) = Vn2D.y(idwn.ind, mesh->ystart, k); + } else { + // Flowing into domain + Vn2D.y(idwn.ind, mesh->ystart - 1, k) = -Vn2D.y(idwn.ind, mesh->ystart, k); + } + // Neumann boundary condition on X and Z components + Vn2D.x(idwn.ind, mesh->ystart - 1, k) = Vn2D.x(idwn.ind, mesh->ystart, k); + Vn2D.z(idwn.ind, mesh->ystart - 1, k) = Vn2D.z(idwn.ind, mesh->ystart, k); + + // Neumann conditions on density and pressure + Nn2D(idwn.ind, mesh->ystart - 1, k) = Nn2D(idwn.ind, mesh->ystart, k); + Pn2D(idwn.ind, mesh->ystart - 1, k) = Pn2D(idwn.ind, mesh->ystart, k); } - // Neumann boundary condition on X and Z components - Vn2D.x(idwn.ind, mesh->ystart - 1) = Vn2D.x(idwn.ind, mesh->ystart); - Vn2D.z(idwn.ind, mesh->ystart - 1) = Vn2D.z(idwn.ind, mesh->ystart); - - // Neumann conditions on density and pressure - Nn2D(idwn.ind, mesh->ystart - 1) = Nn2D(idwn.ind, mesh->ystart); - Pn2D(idwn.ind, mesh->ystart - 1) = Pn2D(idwn.ind, mesh->ystart); } } // Density - ddt(Nn2D) = -Div(Vn2D, Nn2D); + ddt(Nn2D) = -DC(Div(Vn2D, Nn2D)); Field2D Nn2D_floor = floor(Nn2D, 1e-2); // Velocity @@ -212,12 +223,12 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, // Convert to cylindrical coordinates for velocity // advection term. This is to avoid Christoffel symbol // terms in curvilinear geometry - Field2D vr = Txr * Vn2D.x + Tyr * Vn2D.y; // Grad R component - Field2D vz = Txz * Vn2D.x + Tyz * Vn2D.y; // Grad Z component + Field2D vr = Txr * DC(Vn2D.x) + Tyr * DC(Vn2D.y); // Grad R component + Field2D vz = Txz * DC(Vn2D.x) + Tyz * DC(Vn2D.y); // Grad Z component // Advect as scalars (no Christoffel symbols needed) - ddt(vr) = -V_dot_Grad(Vn2D, vr); - ddt(vz) = -V_dot_Grad(Vn2D, vz); + ddt(vr) = -DC(V_dot_Grad(Vn2D, vr)); + ddt(vz) = -DC(V_dot_Grad(Vn2D, vz)); // Convert back to field-aligned coordinates ddt(Vn2D).x += Urx * ddt(vr) + Uzx * ddt(vz); @@ -234,7 +245,7 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, ddt(Vn2D).x += Urx * ddt(vr) + Uzx * ddt(vz); ddt(Vn2D).y += Ury * ddt(vr) + Uzy * ddt(vz); - DivV2D = Div(Vn2D); + DivV2D = DC(Div(Vn2D)); DivV2D.applyBoundary(0.0); mesh->communicate(DivV2D); @@ -243,7 +254,7 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, ////////////////////////////////////////////////////// // Pressure - ddt(Pn2D) = -Div(Vn2D, Pn2D) - + ddt(Pn2D) = -DC(Div(Vn2D, Pn2D)) - (gamma_ratio - 1.) * Pn2D * DivV2D * floor(Nn2D, 0) / Nn2D_floor + Laplace_FV(neutral_conduction, Pn2D / Nn2D); @@ -272,14 +283,14 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, BoutReal q = neutral_gamma * Nnout * Tnout * sqrt(Tnout); // Multiply by cell area to get power BoutReal heatflux = - q * (coord->J(r.ind, mesh->ystart) + coord->J(r.ind, mesh->ystart - 1)) / - (sqrt(coord->g_22(r.ind, mesh->ystart)) + - sqrt(coord->g_22(r.ind, mesh->ystart - 1))); + q * (J_2D(r.ind, mesh->ystart) + J_2D(r.ind, mesh->ystart - 1)) / + (sqrt(g_22_2D(r.ind, mesh->ystart)) + + sqrt(g_22_2D(r.ind, mesh->ystart - 1))); // Divide by volume of cell, and multiply by 2/3 to get pressure ddt(Pn2D)(r.ind, mesh->ystart) -= (2. / 3) * heatflux / - (coord->dy(r.ind, mesh->ystart) * coord->J(r.ind, mesh->ystart)); + (dy_2D(r.ind, mesh->ystart) * J_2D(r.ind, mesh->ystart)); } for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { @@ -304,14 +315,14 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, BoutReal q = neutral_gamma * Nnout * Tnout * sqrt(Tnout); // Multiply by cell area to get power BoutReal heatflux = - q * (coord->J(r.ind, mesh->yend) + coord->J(r.ind, mesh->yend + 1)) / - (sqrt(coord->g_22(r.ind, mesh->yend)) + - sqrt(coord->g_22(r.ind, mesh->yend + 1))); + q * (J_2D(r.ind, mesh->yend) + J_2D(r.ind, mesh->yend + 1)) / + (sqrt(g_22_2D(r.ind, mesh->yend)) + + sqrt(g_22_2D(r.ind, mesh->yend + 1))); // Divide by volume of cell, and multiply by 2/3 to get pressure ddt(Pn2D)(r.ind, mesh->yend) -= (2. / 3) * heatflux / - (coord->dy(r.ind, mesh->yend) * coord->J(r.ind, mesh->yend)); + (dy_2D(r.ind, mesh->yend) * J_2D(r.ind, mesh->yend)); } // Exchange of parallel momentum. This could be done @@ -319,7 +330,7 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, // Vn2D is covariant and b = e_y / (JB) to write: // // V_{||n} = b dot V_n = Vn2D.y / (JB) - Field2D Vnpar = Vn2D.y / (coord->J * coord->Bxy); + Field2D Vnpar = DC(Vn2D.y) / (J_2D * Bxy2D); ///////////////////////////////////////////////////// // Atomic processes @@ -340,7 +351,7 @@ void FullVelocity::update(const Field3D &Ne, const Field3D &Te, ddt(Pn2D) += (2. / 3) * DC(Qi); // Momentum. Note need to turn back into covariant form - ddt(Vn2D).y += DC(F) * (coord->J * coord->Bxy) / Nn2D_floor; + ddt(Vn2D).y += DC(F) * (J_2D * Bxy2D) / Nn2D_floor; // Density evolution for (auto &i : Nn2D.getRegion("RGN_ALL")) { @@ -358,11 +369,14 @@ void FullVelocity::addPressure(int x, int y, int UNUSED(z), BoutReal dpdt) { ddt(Pn2D)(x, y) += dpdt / mesh->LocalNz; } -void FullVelocity::addMomentum(int x, int y, int UNUSED(z), BoutReal dnvdt) { +void FullVelocity::addMomentum(int x, int y, int z, BoutReal dnvdt) { // Momentum added in the direction of the magnetic field // Vn2D is covariant and b = e_y / (JB) to write: // // V_{||n} = b dot V_n = Vn2D.y / (JB) + Coordinates *coord = mesh->getCoordinates(); + BoutReal J = coord->J(x, y, z); + BoutReal Bxy = coord->Bxy(x, y, z); - ddt(Vn2D.y)(x, y) += dnvdt * (coord->J(x, y) * coord->Bxy (x, y)) / Nn2D(x, y); + ddt(Vn2D.y)(x, y, z) += dnvdt * (J * Bxy) / Nn2D(x, y); } diff --git a/hermes-2.cxx b/hermes-2.cxx index d7b19ed..193898a 100644 --- a/hermes-2.cxx +++ b/hermes-2.cxx @@ -1,5 +1,5 @@ /* - + Copyright B.Dudson, J.Leddy, University of York, 2016-2019 email: benjamin.dudson@york.ac.uk @@ -25,186 +25,193 @@ #include #include +#include "boundary_region.hxx" +#include "parallel_boundary_region.hxx" #include #include "div_ops.hxx" #include "loadmetric.hxx" -#include #include +#include #include - // OpenADAS interface Atomicpp by T.Body #include "atomicpp/ImpuritySpecies.hxx" #include "atomicpp/Prad.hxx" namespace FV { - template - const Field3D Div_par_fvv(const Field3D &f_in, const Field3D &v_in, - const Field3D &wave_speed_in, bool fixflux=true) { +template +const Field3D Div_par_fvv(const Field3D& f_in, const Field3D& v_in, + const Field3D& wave_speed_in, bool fixflux = true) { - ASSERT1(areFieldsCompatible(f_in, v_in)); - ASSERT1(areFieldsCompatible(f_in, wave_speed_in)); + ASSERT1(areFieldsCompatible(f_in, v_in)); + ASSERT1(areFieldsCompatible(f_in, wave_speed_in)); + bool use_parallel_slices = (f_in.hasParallelSlices() && v_in.hasParallelSlices() + && wave_speed_in.hasParallelSlices()); - Mesh* mesh = f_in.getMesh(); + Mesh* mesh = f_in.getMesh(); - CellEdges cellboundary; + CellEdges cellboundary; - /// Ensure that f, v and wave_speed are field aligned - Field3D f = toFieldAligned(f_in, "RGN_NOX"); - Field3D v = toFieldAligned(v_in, "RGN_NOX"); - Field3D wave_speed = toFieldAligned(wave_speed_in, "RGN_NOX"); + /// Ensure that f, v and wave_speed are field aligned + Field3D f = use_parallel_slices ? f_in : toFieldAligned(f_in, "RGN_NOX"); + Field3D v = use_parallel_slices ? v_in : toFieldAligned(v_in, "RGN_NOX"); + Field3D wave_speed = + use_parallel_slices ? wave_speed_in : toFieldAligned(wave_speed_in, "RGN_NOX"); - Coordinates *coord = f_in.getCoordinates(); + Coordinates* coord = f_in.getCoordinates(); - Field3D result{zeroFrom(f)}; - - // Only need one guard cell, so no need to communicate fluxes - // Instead calculate in guard cells to preserve fluxes - int ys = mesh->ystart-1; - int ye = mesh->yend+1; + Field3D result{zeroFrom(f)}; - for (int i = mesh->xstart; i <= mesh->xend; i++) { + // Only need one guard cell, so no need to communicate fluxes + // Instead calculate in guard cells to preserve fluxes + int ys = mesh->ystart - 1; + int ye = mesh->yend + 1; - if (!mesh->firstY(i) || mesh->periodicY(i)) { - // Calculate in guard cell to get fluxes consistent between processors - ys = mesh->ystart - 1; - } else { - // Don't include the boundary cell. Note that this implies special - // handling of boundaries later - ys = mesh->ystart; - } + for (int i = mesh->xstart; i <= mesh->xend; i++) { - if (!mesh->lastY(i) || mesh->periodicY(i)) { - // Calculate in guard cells - ye = mesh->yend + 1; - } else { - // Not in boundary cells - ye = mesh->yend; - } + if (!mesh->firstY(i) || mesh->periodicY(i)) { + // Calculate in guard cell to get fluxes consistent between processors + ys = mesh->ystart - 1; + } else { + // Don't include the boundary cell. Note that this implies special + // handling of boundaries later + ys = mesh->ystart; + } + + if (!mesh->lastY(i) || mesh->periodicY(i)) { + // Calculate in guard cells + ye = mesh->yend + 1; + } else { + // Not in boundary cells + ye = mesh->yend; + } - for (int j = ys; j <= ye; j++) { - // Pre-calculate factors which multiply fluxes + for (int j = ys; j <= ye; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { // For right cell boundaries - BoutReal common_factor = (coord->J(i, j) + coord->J(i, j + 1)) / - (sqrt(coord->g_22(i, j)) + sqrt(coord->g_22(i, j + 1))); - - BoutReal flux_factor_rc = common_factor / (coord->dy(i, j) * coord->J(i, j)); - BoutReal flux_factor_rp = common_factor / (coord->dy(i, j + 1) * coord->J(i, j + 1)); + BoutReal common_factor = + (coord->J(i, j, k) + coord->J(i, j + 1, k)) + / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j + 1, k))); + + BoutReal flux_factor_rc = + common_factor / (coord->dy(i, j, k) * coord->J(i, j, k)); + BoutReal flux_factor_rp = + common_factor / (coord->dy(i, j + 1, k) * coord->J(i, j + 1, k)); // For left cell boundaries - common_factor = (coord->J(i, j) + coord->J(i, j - 1)) / - (sqrt(coord->g_22(i, j)) + sqrt(coord->g_22(i, j - 1))); - - BoutReal flux_factor_lc = common_factor / (coord->dy(i, j) * coord->J(i, j)); - BoutReal flux_factor_lm = common_factor / (coord->dy(i, j - 1) * coord->J(i, j - 1)); - - for (int k = 0; k < mesh->LocalNz; k++) { - - //////////////////////////////////////////// - // Reconstruct f at the cell faces - // This calculates s.R and s.L for the Right and Left - // face values on this cell - - // Reconstruct f at the cell faces - Stencil1D s; - s.c = f(i, j, k); - s.m = f(i, j - 1, k); - s.p = f(i, j + 1, k); - - cellboundary(s); // Calculate s.R and s.L - - // Reconstruct v at the cell faces - Stencil1D sv; - sv.c = v(i, j, k); - sv.m = v(i, j - 1, k); - sv.p = v(i, j + 1, k); - - cellboundary(sv); - - //////////////////////////////////////////// - // Right boundary - - // Calculate velocity at right boundary (y+1/2) - BoutReal vpar = 0.5 * (v(i, j, k) + v(i, j + 1, k)); - BoutReal flux; - - if (mesh->lastY(i) && (j == mesh->yend) && !mesh->periodicY(i)) { - // Last point in domain - - BoutReal bndryval = 0.5 * (s.c + s.p); - if (fixflux) { - // Use mid-point to be consistent with boundary conditions - flux = bndryval * vpar * vpar; - } else { - // Add flux due to difference in boundary values - flux = s.R * vpar * sv.R + wave_speed(i, j, k) * (s.R * sv.R - bndryval * vpar); - } + common_factor = (coord->J(i, j, k) + coord->J(i, j - 1, k)) + / (sqrt(coord->g_22(i, j, k)) + sqrt(coord->g_22(i, j - 1, k))); + + BoutReal flux_factor_lc = + common_factor / (coord->dy(i, j, k) * coord->J(i, j, k)); + BoutReal flux_factor_lm = + common_factor / (coord->dy(i, j - 1, k) * coord->J(i, j - 1, k)); + + //////////////////////////////////////////// + // Reconstruct f at the cell faces + // This calculates s.R and s.L for the Right and Left + // face values on this cell + + // Reconstruct f at the cell faces + Stencil1D s; + s.c = f(i, j, k); + s.m = f(i, j - 1, k); + s.p = f(i, j + 1, k); + + cellboundary(s); // Calculate s.R and s.L + + // Reconstruct v at the cell faces + Stencil1D sv; + sv.c = v(i, j, k); + sv.m = v(i, j - 1, k); + sv.p = v(i, j + 1, k); + + cellboundary(sv); + + //////////////////////////////////////////// + // Right boundary + + // Calculate velocity at right boundary (y+1/2) + BoutReal vpar = 0.5 * (v(i, j, k) + v(i, j + 1, k)); + BoutReal flux; + + if (mesh->lastY(i) && (j == mesh->yend) && !mesh->periodicY(i)) { + // Last point in domain + + BoutReal bndryval = 0.5 * (s.c + s.p); + if (fixflux) { + // Use mid-point to be consistent with boundary conditions + flux = bndryval * vpar * vpar; } else { - - // Maximum wave speed in the two cells - BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j + 1, k)); - - if (vpar > amax) { - // Supersonic flow out of this cell - flux = s.R * vpar * sv.R; - } else if (vpar < -amax) { - // Supersonic flow into this cell - flux = 0.0; - } else { - // Subsonic flow, so a mix of right and left fluxes - flux = s.R * 0.5 * (vpar + amax) * sv.R; - } + // Add flux due to difference in boundary values + flux = + s.R * vpar * sv.R + wave_speed(i, j, k) * (s.R * sv.R - bndryval * vpar); } - - result(i, j, k) += flux * flux_factor_rc; - result(i, j + 1, k) -= flux * flux_factor_rp; - - //////////////////////////////////////////// - // Calculate at left boundary - - vpar = 0.5 * (v(i, j, k) + v(i, j - 1, k)); - - if (mesh->firstY(i) && (j == mesh->ystart) && !mesh->periodicY(i)) { - // First point in domain - BoutReal bndryval = 0.5 * (s.c + s.m); - if (fixflux) { - // Use mid-point to be consistent with boundary conditions - flux = bndryval * vpar * vpar; - } else { - // Add flux due to difference in boundary values - flux = s.L * vpar * sv.L - wave_speed(i, j, k) * (s.L * sv.L - bndryval * vpar); - } + } else { + + // Maximum wave speed in the two cells + BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j + 1, k)); + + if (vpar > amax) { + // Supersonic flow out of this cell + flux = s.R * vpar * sv.R; + } else if (vpar < -amax) { + // Supersonic flow into this cell + flux = 0.0; } else { - - // Maximum wave speed in the two cells - BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j - 1, k)); - - if (vpar < -amax) { - // Supersonic out of this cell - flux = s.L * vpar * sv.L; - } else if (vpar > amax) { - // Supersonic into this cell - flux = 0.0; - } else { - flux = s.L * 0.5 * (vpar - amax) * sv.L; - } + // Subsonic flow, so a mix of right and left fluxes + flux = s.R * 0.5 * (vpar + amax) * sv.R; + } + } + + result(i, j, k) += flux * flux_factor_rc; + result(i, j + 1, k) -= flux * flux_factor_rp; + + //////////////////////////////////////////// + // Calculate at left boundary + + vpar = 0.5 * (v(i, j, k) + v(i, j - 1, k)); + + if (mesh->firstY(i) && (j == mesh->ystart) && !mesh->periodicY(i)) { + // First point in domain + BoutReal bndryval = 0.5 * (s.c + s.m); + if (fixflux) { + // Use mid-point to be consistent with boundary conditions + flux = bndryval * vpar * vpar; + } else { + // Add flux due to difference in boundary values + flux = + s.L * vpar * sv.L - wave_speed(i, j, k) * (s.L * sv.L - bndryval * vpar); + } + } else { + + // Maximum wave speed in the two cells + BoutReal amax = BOUTMAX(wave_speed(i, j, k), wave_speed(i, j - 1, k)); + + if (vpar < -amax) { + // Supersonic out of this cell + flux = s.L * vpar * sv.L; + } else if (vpar > amax) { + // Supersonic into this cell + flux = 0.0; + } else { + flux = s.L * 0.5 * (vpar - amax) * sv.L; } - - result(i, j, k) -= flux * flux_factor_lc; - result(i, j - 1, k) += flux * flux_factor_lm; - } + + result(i, j, k) -= flux * flux_factor_lc; + result(i, j - 1, k) += flux * flux_factor_lm; } } - return fromFieldAligned(result, "RGN_NOBNDRY"); } - + return fromFieldAligned(result, "RGN_NOBNDRY"); } +} // namespace FV -BoutReal floor(const BoutReal &var, const BoutReal &f) { +BoutReal floor(const BoutReal& var, const BoutReal& f) { if (var < f) return f; return var; @@ -212,7 +219,7 @@ BoutReal floor(const BoutReal &var, const BoutReal &f) { /// Returns a copy of input \p var with all values greater than \p f replaced by /// \p f. -const Field3D ceil(const Field3D &var, BoutReal f, REGION rgn = RGN_ALL) { +const Field3D ceil(const Field3D& var, BoutReal f, REGION rgn = RGN_ALL) { checkData(var); Field3D result = copy(var); @@ -226,11 +233,101 @@ const Field3D ceil(const Field3D &var, BoutReal f, REGION rgn = RGN_ALL) { } // Square function for vectors -Field3D SQ(const Vector3D &v) { return v * v; } +Field3D SQ(const Vector3D& v) { return v * v; } + +/// Limited free gradient of log of a quantity +/// This ensures that the guard cell values remain positive +/// while also ensuring that the quantity never increases +/// +/// fm fc | fp +/// ^ boundary +/// +/// exp( 2*log(fc) - log(fm) ) +/// + +// void setRegions(Field3D &f) { +// f.yup().setRegion("RGN_YPAR_+1"); +// f.ydown().setRegion("RGN_YPAR_-1"); +// } + +BoutReal limitFree(BoutReal fm, BoutReal fc) { + if (fm < fc) { + return fc; // Neumann rather than increasing into boundary + } + BoutReal fp = SQ(fc) / fm; + ASSERT2(std::isfinite(fp)); + + return fp; +} + +Field3D floor_all(const Field3D& f, BoutReal minval) { + Field3D result = floor(f, minval); + checkData(result, RGN_ALL); + result.splitParallelSlices(); + result.yup() = floor(f.yup(), minval); //, "RGN_YPAR_+1"); + result.ydown() = floor(f.ydown(), minval); //, "RGN_YPAR_-1"); + // setRegions(result); + return result; +} + +Field3D copy_all(const Field3D& f) { + Field3D result = copy(f); + result.splitParallelSlices(); + result.yup() = copy(f.yup()); + result.ydown() = copy(f.ydown()); + return result; +} + +Field3D div_all(const Field3D& num, const Field3D& den) { + Field3D result = num / den; + result.splitParallelSlices(); + result.yup() = num.yup() / den.yup(); + result.ydown() = num.ydown() / den.ydown(); + return result; +} + +Field3D div_all(const Field3D& num, BoutReal den) { + Field3D result = num / den; + result.splitParallelSlices(); + result.yup() = num.yup() / den; + result.ydown() = num.ydown() / den; + return result; +} + +Field3D mul_all(const Field3D& a, const Field3D& b) { + Field3D result = a * b; + result.splitParallelSlices(); + result.yup() = a.yup() * b.yup(); + result.ydown() = a.ydown() * b.ydown(); + return result; +} + +Field3D sub_all(const Field3D& a, const Field3D& b) { + Field3D result = a - b; + result.splitParallelSlices(); + result.yup() = a.yup() - b.yup(); + result.ydown() = a.ydown() - b.ydown(); + return result; +} + +Field3D add_all(const Field3D& a, const Field3D& b) { + Field3D result = a + b; + result.splitParallelSlices(); + result.yup() = a.yup() + b.yup(); + result.ydown() = a.ydown() + b.ydown(); + return result; +} + +void zero_all(Field3D& f) { + f = 0.0; + f.splitParallelSlices(); + f.yup() = 0.0; + f.ydown() = 0.0; +} /// Modifies and returns the first argument, taking the boundary from second argument /// This is used because unfortunately Field3D::setBoundary returns void -Field3D withBoundary(Field3D &&f, const Field3D &bndry) { +Field3D withBoundary(Field3D&& f, const Field3D& bndry) { f.setBoundaryTo(bndry); return f; } @@ -243,6 +340,7 @@ int Hermes::init(bool restarting) { auto& optsc = opt["Hermes"]; OPTION(optsc, evolve_plasma, true); + OPTION(optsc, show_timesteps, false); electromagnetic = optsc["electromagnetic"] .doc("Include vector potential psi in Ohm's law?") @@ -252,22 +350,21 @@ int Hermes::init(bool restarting) { .doc("Include electron inertia in Ohm's law?") .withDefault(true); - j_diamag = optsc["j_diamag"] - .doc("Diamagnetic current: Vort <-> Pe") + j_diamag = + optsc["j_diamag"].doc("Diamagnetic current: Vort <-> Pe").withDefault(true); + + j_par = optsc["j_par"].doc("Parallel current: Vort <-> Psi").withDefault(true); + + j_pol_pi = optsc["j_pol_pi"] + .doc("Polarisation current with explicit Pi dependence") .withDefault(true); - - if (optsc["j_diamag_scale"].isSet()) { - j_diamag_scale_generator = FieldFactory::get()->parse("hermes:j_diamag_scale", Options::getRoot()); - SAVE_REPEAT(j_diamag_scale); - } else { - j_diamag_scale_generator = FieldFactory::get()->parse("1"); - } - - j_par = optsc["j_par"] - .doc("Parallel current: Vort <-> Psi") - .withDefault(true); - OPTION(optsc, j_pol_terms, false); + j_pol_simplified = optsc["j_pol_simplified"] + .doc("Polarisation current without explicit Pi dependence") + .withDefault(false); + OPTION(optsc, relaxation, false); + OPTION(optsc, lambda_0, 1e3); + OPTION(optsc, lambda_2, 1e5); OPTION(optsc, parallel_flow, true); OPTION(optsc, parallel_flow_p_term, parallel_flow); OPTION(optsc, pe_par, true); @@ -276,25 +373,27 @@ int Hermes::init(bool restarting) { OPTION(optsc, thermal_flux, true); thermal_force = optsc["thermal_force"] - .doc("Force on electrons due to temperature gradients") - .withDefault(true); - + .doc("Force on electrons due to temperature gradients") + .withDefault(true); + OPTION(optsc, electron_viscosity, true); - ion_viscosity = optsc["ion_viscosity"].doc("Include ion viscosity?").withDefault(true); - ion_viscosity_par = optsc["ion_viscosity_par"].doc("Include parallel diffusion of ion momentum?").withDefault(ion_viscosity); + ion_viscosity = + optsc["ion_viscosity"].doc("Include ion viscosity?").withDefault(true); + ion_viscosity_par = optsc["ion_viscosity_par"] + .doc("Include parallel diffusion of ion momentum?") + .withDefault(ion_viscosity); electron_neutral = optsc["electron_neutral"] - .doc("Include electron-neutral collisions in resistivity?") - .withDefault(true); - + .doc("Include electron-neutral collisions in resistivity?") + .withDefault(true); + ion_neutral = optsc["ion_neutral"] - .doc("Include ion-neutral collisions in tau_i?") - .withDefault(false); + .doc("Include ion-neutral collisions in tau_i?") + .withDefault(false); + + poloidal_flows = + optsc["poloidal_flows"].doc("Include ExB flows in X-Y plane").withDefault(true); - poloidal_flows = optsc["poloidal_flows"] - .doc("Include ExB flows in X-Y plane") - .withDefault(true); - OPTION(optsc, ion_velocity, true); OPTION(optsc, thermal_conduction, true); @@ -309,16 +408,17 @@ int Hermes::init(bool restarting) { OPTION(optsc, pe_bndry_flux, true); OPTION(optsc, vort_bndry_flux, false); - ramp_mesh = optsc["ramp_mesh"] - .doc("Add profiles from mesh file over a period of time") - .withDefault(false); + OPTION(optsc, ramp_mesh, true); OPTION(optsc, ramp_timescale, 1e4); OPTION(optsc, energy_source, false); ion_neutral_rate = optsc["ion_neutral_rate"] - .doc("A fixed ion-neutral collision rate, normalised to ion cyclotron frequency.") - .withDefault(0.0); + .doc("A fixed ion-neutral collision rate, normalised to ion " + "cyclotron frequency.") + .withDefault(0.0); + + OPTION(optsc, staggered, false); OPTION(optsc, boussinesq, false); @@ -328,8 +428,8 @@ int Hermes::init(bool restarting) { // Cross-field transport classical_diffusion = optsc["classical_diffusion"] - .doc("Collisional cross-field diffusion, including viscosity") - .withDefault(false); + .doc("Collisional cross-field diffusion, including viscosity") + .withDefault(false); OPTION(optsc, anomalous_D, -1); OPTION(optsc, anomalous_chi, -1); OPTION(optsc, anomalous_nu, -1); @@ -353,9 +453,31 @@ int Hermes::init(bool restarting) { OPTION(optsc, floor_num_cs, -1.0); OPTION(optsc, vepsi_dissipation, false); OPTION(optsc, vort_dissipation, false); + phi_dissipation = optsc["phi_dissipation"] - .doc("Add a dissipation term to vorticity, depending on reconstruction of potential?") - .withDefault(false); + .doc("Add a dissipation term to vorticity, depending on " + "reconstruction of potential?") + .withDefault(false); + + ne_num_diff = optsc["ne_num_diff"] + .doc("Numerical Ne diffusion in X-Z plane. < 0 => off.") + .withDefault(-1.0); + + ne_num_hyper = optsc["ne_num_hyper"] + .doc("Numerical Ne hyper-diffusion in X-Z plane. < 0 => off.") + .withDefault(-1.0); + + vi_num_diff = optsc["vi_num_diff"] + .doc("Numerical Vi diffusion in X-Z plane. < 0 => off.") + .withDefault(-1.0); + + ve_num_diff = optsc["ve_num_diff"] + .doc("Numerical Ve diffusion in X-Z plane. < 0 => off.") + .withDefault(-1.0); + + ve_num_hyper = optsc["ve_num_hyper"] + .doc("Numerical Ve hyper-diffusion in X-Z plane. < 0 => off.") + .withDefault(-1.0); OPTION(optsc, ne_hyper_z, -1.0); OPTION(optsc, pe_hyper_z, -1.0); @@ -369,60 +491,46 @@ int Hermes::init(bool restarting) { OPTION(optsc, sheath_gamma_e, 5.5); OPTION(optsc, sheath_gamma_i, 1.0); - OPTION(optsc, neutral_vwall, 1. / 3); // 1/3rd Franck-Condon energy at wall - OPTION(optsc, sheath_yup, true); // Apply sheath at yup? - OPTION(optsc, sheath_ydown, true); // Apply sheath at ydown? - OPTION(optsc, test_boundaries, false); // Test boundary conditions - - nesheath_floor = optsc["nesheath_floor"].doc("Ne sheath lower limit").withDefault(1e-5); + OPTION(optsc, neutral_vwall, 1. / 3); // 1/3rd Franck-Condon energy at wall + OPTION(optsc, sheath_yup, true); // Apply sheath at yup? + OPTION(optsc, sheath_ydown, true); // Apply sheath at ydown? + OPTION(optsc, test_boundaries, false); // Test boundary conditions + OPTION(optsc, parallel_sheaths, false); // Apply parallel sheath conditions? + OPTION(optsc, par_sheath_model, 0); + OPTION(optsc, par_sheath_ve, true); + OPTION(optsc, electron_weight, 1.0); - // Fix profiles in SOL - OPTION(optsc, sol_fix_profiles, false); - if (sol_fix_profiles) { - sol_ne = FieldFactory::get()->parse("sol_ne", &optsc); - sol_te = FieldFactory::get()->parse("sol_te", &optsc); - } + sheath_allow_supersonic = + optsc["sheath_allow_supersonic"] + .doc("If plasma is faster than sound speed, go to plasma velocity") + .withDefault(true); radial_buffers = optsc["radial_buffers"] - .doc("Turn on radial buffer regions?").withDefault(false); + .doc("Turn on radial buffer regions?") + .withDefault(false); OPTION(optsc, radial_inner_width, 4); OPTION(optsc, radial_outer_width, 4); OPTION(optsc, radial_buffer_D, 1.0); - - // Only average in Y if in a closed field line region - radial_inner_averagey = mesh->periodicY(1) - & optsc["radial_core_averagey"] - .doc("Average Ne, Pe and Pi in Y in core radial buffer?") - .withDefault(true); - // Note: It is probably a bad idea to do this, but you never know... - radial_inner_averagey_vort = mesh->periodicY(1) - & optsc["radial_core_averagey_vort"] - .doc("Average Vort in Y in core radial buffer?") - .withDefault(false); - // These treatments of NVi might be reasonable choices, but are off by default - radial_inner_averagey_nvi = mesh->periodicY(1) - & optsc["radial_core_averagey_nvi"] - .doc("Average NVi in Y in core radial buffer?") - .withDefault(false); - radial_inner_zero_nvi = mesh->periodicY(1) - & optsc["radial_core_zero_nvi"] - .doc("Damp NVi toward zero in core radial buffer?") - .withDefault(false); + + OPTION(optsc, phi_smoothing, false); + OPTION(optsc, phi_sf, 0.0); resistivity_boundary = optsc["resistivity_boundary"] - .doc("Normalised resistivity in radial boundary region") - .withDefault(1.0); + .doc("Normalised resistivity in radial boundary region") + .withDefault(1.0); resistivity_boundary_width = optsc["resistivity_boundary_width"] - .doc("Number of grid cells in radial (x) direction") - .withDefault(0); - + .doc("Number of grid cells in radial (x) direction") + .withDefault(0); + // Output additional information OPTION(optsc, verbose, false); // Save additional fields OPTION(optsc, output_ddt, false); // Save time derivatives // Normalisation OPTION(optsc, Tnorm, 100); // Reference temperature [eV] + OPTION(optsc, Te0, Tnorm); // Reference temperature [eV] + OPTION(optsc, Ti0, Tnorm); // Reference temperature [eV] OPTION(optsc, Nnorm, 1e19); // Reference density [m^-3] OPTION(optsc, Bnorm, 1.0); // Reference magnetic field [T] @@ -435,11 +543,12 @@ int Hermes::init(bool restarting) { Omega_ci = qe * Bnorm / (AA * Mp); // Ion cyclotron frequency [1/s] rho_s0 = Cs0 / Omega_ci; - mi_me = AA * Mp / Me; + mi_me = AA * Mp / (electron_weight * Me); + me_mi = (electron_weight * Me) / (AA * Mp); beta_e = qe * Tnorm * Nnorm / (SQ(Bnorm) / (2. * mu0)); - output.write("\tmi_me=%e, beta_e=%e\n", mi_me, beta_e); - SAVE_ONCE(mi_me, beta_e); + output.write("\tmi_me={}, beta_e={}\n", mi_me, beta_e); + SAVE_ONCE(mi_me, beta_e, me_mi); output.write("\t Cs=%e, rho_s=%e, Omega_ci=%e\n", Cs0, rho_s0, Omega_ci); SAVE_ONCE(Cs0, rho_s0, Omega_ci); @@ -449,8 +558,7 @@ int Hermes::init(bool restarting) { BoutReal lambda_ii = 23. - log(sqrt(2. * Nnorm / 1e6) / pow(Tnorm, 1.5)); tau_e0 = 1. / (2.91e-6 * (Nnorm / 1e6) * lambda_ei * pow(Tnorm, -3. / 2)); - tau_i0 = - sqrt(AA) / (4.78e-8 * (Nnorm / 1e6) * lambda_ii * pow(Tnorm, -3. / 2)); + tau_i0 = sqrt(AA) / (4.78e-8 * (Nnorm / 1e6) * lambda_ii * pow(Tnorm, -3. / 2)); output.write("\ttau_e0=%e, tau_i0=%e\n", tau_e0, tau_i0); @@ -458,16 +566,22 @@ int Hermes::init(bool restarting) { // Normalise anomalous_D /= rho_s0 * rho_s0 * Omega_ci; // m^2/s output.write("\tnormalised anomalous D_perp = %e\n", anomalous_D); + a_d3d = anomalous_D; + mesh->communicate(a_d3d); } if (anomalous_chi > 0.0) { // Normalise anomalous_chi /= rho_s0 * rho_s0 * Omega_ci; // m^2/s output.write("\tnormalised anomalous chi_perp = %e\n", anomalous_chi); + a_chi3d = anomalous_chi; + mesh->communicate(a_chi3d); } if (anomalous_nu > 0.0) { // Normalise anomalous_nu /= rho_s0 * rho_s0 * Omega_ci; // m^2/s output.write("\tnormalised anomalous nu_perp = %e\n", anomalous_nu); + a_nu3d = anomalous_nu; + mesh->communicate(a_nu3d); } if (ramp_mesh) { @@ -483,7 +597,7 @@ int Hermes::init(bool restarting) { if (sinks) { std::string source = optsc["sink_invlpar"].withDefault("0.05"); // 20 m - sink_invlpar = fact.create2D(source); + sink_invlpar = fact.create3D(source); sink_invlpar *= rho_s0; // Normalise SAVE_ONCE(sink_invlpar); @@ -518,9 +632,11 @@ int Hermes::init(bool restarting) { if (!mesh->periodicY(x)) { // Not periodic, so not in core for (int y = mesh->ystart; y <= mesh->yend; y++) { - Sn(x, y) = 0.0; - Spe(x, y) = 0.0; - Spi(x, y) = 0.0; + for (int z = 0; z <= mesh->LocalNz; z++) { + Sn(x, y, z) = 0.0; + Spe(x, y, z) = 0.0; + Spi(x, y, z) = 0.0; + } } } } @@ -536,14 +652,47 @@ int Hermes::init(bool restarting) { Spe += (2. / 3) * qmid; // Add variables to solver - SOLVE_FOR(Ne, Pe, Pi); - EvolvingVars.add(Ne, Pe, Pi); + SOLVE_FOR(Ne); + EvolvingVars.add(Ne); if (output_ddt) { - SAVE_REPEAT(ddt(Ne), ddt(Pe), ddt(Pi)); + SAVE_REPEAT(ddt(Ne)); } - if (j_par || j_diamag) { + if (relaxation) { + SOLVE_FOR(phi_1); + EvolvingVars.add(phi_1); + if (output_ddt) { + SAVE_REPEAT(ddt(phi_1)); + } + } + // Temperature evolution can be turned off + // so that Pe = Ne and/or Pi = Ne + evolve_te = + optsc["evolve_te"].doc("Evolve electron temperature?").withDefault(true); + if (evolve_te) { + SOLVE_FOR(Pe); + EvolvingVars.add(Pe); + if (output_ddt) { + SAVE_REPEAT(ddt(Pe)); + } + } else { + Pe = Te0 * Ne; + } + evolve_ti = optsc["evolve_ti"].doc("Evolve ion temperature?").withDefault(true); + if (evolve_ti) { + SOLVE_FOR(Pi); + EvolvingVars.add(Pi); + if (output_ddt) { + SAVE_REPEAT(ddt(Pi)); + } + } else { + Pi = Ti0 * Ne; + } + + evolve_vort = optsc["evolve_vort"].doc("Evolve Vorticity?").withDefault(true); + + if ((j_par || j_diamag || relaxation) && evolve_vort) { // Have a source of vorticity solver->add(Vort, "Vort"); EvolvingVars.add(Vort); @@ -551,7 +700,7 @@ int Hermes::init(bool restarting) { SAVE_REPEAT(ddt(Vort)); } } else { - Vort = 0.0; + zero_all(Vort); } if (electromagnetic || FiniteElMass) { @@ -564,7 +713,7 @@ int Hermes::init(bool restarting) { // If both electrostatic and zero electron mass, // then Ohm's law has no time-derivative terms, // but is calculated from other evolving quantities - VePsi = 0.0; + zero_all(VePsi); } if (ion_velocity) { @@ -574,12 +723,12 @@ int Hermes::init(bool restarting) { SAVE_REPEAT(ddt(NVi)); } } else { - NVi = 0.0; + zero_all(NVi); } if (verbose) { SAVE_REPEAT(Ti); - if (electron_ion_transfer) { + if (electron_ion_transfer && evolve_ti) { SAVE_REPEAT(Wi); } if (ion_velocity) { @@ -587,39 +736,23 @@ int Hermes::init(bool restarting) { } } - bool adapt_source = optsc["adapt_source"] - .doc("Vary sources in time to match core profiles. This sets the default for adapt_source_p and adapt_source_n").withDefault(false); - adapt_source_p = optsc["adapt_source_p"] - .doc("Vary pressure source in time to match core profiles").withDefault(adapt_source); - adapt_source_n = optsc["adapt_source_n"] - .doc("Vary density source in time to match core profiles").withDefault(adapt_source); - sources_positive = optsc["sources_positive"].doc("Ensure that sources are always positive").withDefault(true); - - if (adapt_source_p) { - // Adaptive pressure sources to match profiles + OPTION(optsc, adapt_source, false); + if (adapt_source) { + // Adaptive sources to match profiles // PI controller, including an integrated difference term OPTION(optsc, source_p, 1e-2); OPTION(optsc, source_i, 1e-6); - Field2D Spesave = copy(Spe); - Field2D Spisave = copy(Spi); - SOLVE_FOR(Spe, Spi); + Coordinates::FieldMetric Snsave = copy(Sn); + Coordinates::FieldMetric Spesave = copy(Spe); + Coordinates::FieldMetric Spisave = copy(Spi); + SOLVE_FOR(Sn, Spe, Spi); + Sn = Snsave; Spe = Spesave; Spi = Spisave; } else { - SAVE_ONCE(Spe, Spi); - } - - if (adapt_source_n) { - // Adaptive density sources to match profile - OPTION(optsc, source_p, 1e-2); - OPTION(optsc, source_i, 1e-6); - Field2D Snsave = copy(Sn); - SOLVE_FOR(Sn); - Sn = Snsave; - } else { - SAVE_ONCE(Sn); + SAVE_ONCE(Sn, Spe, Spi); } ///////////////////////////////////////////////////////// @@ -627,14 +760,14 @@ int Hermes::init(bool restarting) { // field normalisations TRACE("Loading metric tensor"); - Coordinates *coord = mesh->getCoordinates(); + Coordinates* coord = bout::globals::mesh->getCoordinates(); if (optsc["loadmetric"] .doc("Load Rxy, Bpxy etc. to create orthogonal metric?") .withDefault(true)) { LoadMetric(rho_s0, Bnorm); } else { - Coordinates *coord = mesh->getCoordinates(); + Coordinates* coord = mesh->getCoordinates(); // To use non-orthogonal metric // Normalise coord->dx /= rho_s0 * rho_s0 * Bnorm; @@ -663,7 +796,52 @@ int Hermes::init(bool restarting) { B32 = sqrtB * coord->Bxy; // B^(3/2) SAVE_ONCE(B32); - + + if (Options::root()["mesh:paralleltransform"]["type"].as() == "fci") { + fci_transform = true; + } else { + fci_transform = false; + } + + if (fci_transform) { + poloidal_flows = false; + mesh->get(Bxyz, "B", 1.0); + Bxyz /= Bnorm; + SAVE_ONCE(Bxyz); + Bxyz.applyBoundary("neumann"); + ASSERT1(min(Bxyz) > 0.0); + + mesh->communicate(Bxyz, coord->dz, coord->dy, coord->J, coord->g_22, coord->g_23, + coord->g23, coord->Bxy); // To get yup/ydown fields + + // Note: A Neumann condition simplifies boundary conditions on fluxes + // where the condition e.g. on J should be on flux (J/B) + Bxyz.applyParallelBoundary("parallel_neumann"); + coord->dz.applyParallelBoundary("parallel_neumann"); + coord->dy.applyParallelBoundary("parallel_neumann"); + coord->J.applyParallelBoundary("parallel_neumann"); + coord->g_22.applyParallelBoundary("parallel_neumann"); + coord->g_23.applyParallelBoundary("parallel_neumann"); + coord->g23.applyParallelBoundary("parallel_neumann"); + coord->Bxy.applyParallelBoundary("parallel_neumann"); + // bout::checkPositive(coord->Bxy, "f", "RGN_NOCORNERS"); + // bout::checkPositive(coord->Bxy.yup(), "fyup", "RGN_YPAR_+1"); + // bout::checkPositive(coord->Bxy.ydown(), "fdown", "RGN_YPAR_-1"); + logB = log(Bxyz); + mesh->communicate(logB); + if (relaxation) { + B_SQ = coord->Bxy * coord->Bxy; + mesh->communicate(B_SQ); + SAVE_ONCE(B_SQ); + } + bracket_factor = sqrt(coord->g_22) / (coord->J * Bxyz); + SAVE_ONCE(bracket_factor); + } else { + mesh->communicate(coord->Bxy); + bracket_factor = sqrt(coord->g_22) / (coord->J * coord->Bxy); + SAVE_ONCE(bracket_factor); + } + ///////////////////////////////////////////////////////// // Neutral models @@ -678,7 +856,7 @@ int Hermes::init(bool restarting) { ///////////////////////////////////////////////////////// // Impurities TRACE("Impurities"); - + impurity_adas = optsc["impurity_adas"] .doc("Use Atomic++ interface to ADAS") .withDefault(false); @@ -689,17 +867,16 @@ int Hermes::init(bool restarting) { .doc("Fixed fraction ADAS impurity, multiple of electron density") .withDefault(0.0); - string impurity_species = - optsc["impurity_species"] - .doc("Short name of the ADAS species e.g. 'c' or 'ne'") - .withDefault("c"); + string impurity_species = optsc["impurity_species"] + .doc("Short name of the ADAS species e.g. 'c' or 'ne'") + .withDefault("c"); impurity = new ImpuritySpecies(impurity_species); } carbon_fraction = optsc["carbon_fraction"] - .doc("Include a fixed fraction carbon impurity. < 0 means none.") - .withDefault(-1.); + .doc("Include a fixed fraction carbon impurity. < 0 means none.") + .withDefault(-1.); if (carbon_fraction > 0.0) { SAVE_REPEAT(Rzrad); SAVE_ONCE(carbon_fraction); @@ -710,12 +887,12 @@ int Hermes::init(bool restarting) { // Save impurity radiation SAVE_REPEAT(Rzrad); } - + ///////////////////////////////////////////////////////// // Read profiles from the mesh TRACE("Reading profiles"); - Field2D NeMesh, TeMesh, TiMesh; + Field3D NeMesh, TeMesh, TiMesh; if (mesh->get(NeMesh, "Ne0")) { // No Ne0. Try Ni0 if (mesh->get(NeMesh, "Ni0")) { @@ -806,15 +983,15 @@ int Hermes::init(bool restarting) { try { Curlb_B.covariant = false; // Contravariant mesh->get(Curlb_B, "bxcv"); - - } catch (BoutException &e) { + SAVE_ONCE(Curlb_B); + } catch (BoutException& e) { try { // May be 2D, reading as 3D Vector2D curv2d; curv2d.covariant = false; mesh->get(curv2d, "bxcv"); Curlb_B = curv2d; - } catch (BoutException &e) { + } catch (BoutException& e) { if (j_diamag) { // Need curvature throw; @@ -824,25 +1001,11 @@ int Hermes::init(bool restarting) { } } } - - if (Options::root()["mesh"]["paralleltransform"].as() == "shifted") { - // Check if the gridfile was created for "shiftedmetric" or for "identity" parallel - // transform - std::string gridfile_parallel_transform; - if (mesh->get(gridfile_parallel_transform, "parallel_transform")) { - // Did not find in gridfile, indicates older gridfile, which generated output for - // field-aligned coordinates, i.e. "identity" parallel transform - gridfile_parallel_transform = "identity"; - } - if (gridfile_parallel_transform == "identity") { - Field2D I; - mesh->get(I, "sinty"); - Curlb_B.z += I * Curlb_B.x; - } else if ((gridfile_parallel_transform != "shifted") and - (gridfile_parallel_transform != "shiftedmetric")){ - throw BoutException("Gridfile generated for unsupported parallel transform %s", - gridfile_parallel_transform.c_str()); - } + + if (Options::root()["mesh:paralleltransform"]["type"].as() == "shifted") { + Field2D I; + mesh->get(I, "sinty"); + Curlb_B.z += I * Curlb_B.x; } Curlb_B.x /= Bnorm; @@ -853,10 +1016,13 @@ int Hermes::init(bool restarting) { ////////////////////////////////////////////////////////////// // Electromagnetic fields - - if (j_par | j_diamag) { + + if (j_par | j_diamag | relaxation) { // Only needed if there are any currents SAVE_REPEAT(phi); + if (relaxation) { + SAVE_REPEAT(phi_1); + } if (j_par) { SAVE_REPEAT(Ve); @@ -873,80 +1039,90 @@ int Hermes::init(bool restarting) { phiSolver3D = Laplace3D::create(); #endif } else { - if (split_n0) { - // Create an XY solver for n=0 component - laplacexy = new LaplaceXY(mesh); - // Set coefficients for Boussinesq solve - laplacexy->setCoefs(1. / SQ(coord->Bxy), 0.0); - phi2D = 0.0; // Starting guess - } - - // Create an XZ solver - OPTION(optsc, newXZsolver, false); - if (newXZsolver) { - // Test new LaplaceXZ solver - newSolver = LaplaceXZ::create(mesh); - // Set coefficients for Boussinesq solve - newSolver->setCoefs(1. / SQ(coord->Bxy), 0.0); - } else { - // Use older Laplacian solver - phiSolver = Laplacian::create(&opt["phiSolver"]); - // Set coefficients for Boussinesq solve - phiSolver->setCoefC(1. / SQ(coord->Bxy)); + + if (!relaxation) { + if (split_n0) { + // Create an XY solver for n=0 component + laplacexy = new LaplaceXY(mesh); + // Set coefficients for Boussinesq solve + laplacexy->setCoefs(1. / SQ(DC(coord->Bxy)), 0.0); + phi2D = 0.0; // Starting guess + } + + // Create an XZ solver + OPTION(optsc, newXZsolver, false); + if (newXZsolver) { + // Test new LaplaceXZ solver + newSolver = LaplaceXZ::create(bout::globals::mesh); + // Set coefficients for Boussinesq solve + newSolver->setCoefs(1. / SQ(coord->Bxy), Field3D(0.0)); + } else { + // Use older Laplacian solver + phiSolver = Laplacian::create(&opt["phiSolver"]); + // Set coefficients for Boussinesq solve + phiSolver->setCoefC(1. / SQ(coord->Bxy)); + } } - } - phi = 0.0; - phi.setBoundary("phi"); // For y boundaries - - phi_boundary_relax = optsc["phi_boundary_relax"] - .doc("Relax x boundaries of phi towards Neumann?") - .withDefault(false); - // Add phi to restart files so that the value in the boundaries - // is restored on restart. This is done even when phi is not evolving, - // so that phi can be saved and re-loaded - restart.addOnce(phi, "phi"); + if (relaxation) { + phi_1 = 0; + phi_1.setBoundary("phi_1"); + } + phi = 0.0; + phi.setBoundary("phi"); // For y boundaries - if (phi_boundary_relax) { + phi_boundary_relax = optsc["phi_boundary_relax"] + .doc("Relax x boundaries of phi towards Neumann?") + .withDefault(false); - if (!restarting) { - // Start by setting to the sheath current = 0 boundary value + // Add phi to restart files so that the value in the boundaries + // is restored on restart. This is done even when phi is not evolving, + // so that phi can be saved and re-loaded + // restart.addOnce(phi, "phi"); - Field3D Nelim = floor(Ne, 1e-5); - Telim = floor(Pe / Nelim, 0.1 / Tnorm); - Tilim = floor(Pi / Nelim, 0.1 / Tnorm); + if (phi_boundary_relax) { - phi.setBoundaryTo(DC( - (log(0.5 * sqrt(mi_me / PI)) + log(sqrt(Telim / (Telim + Tilim)))) * Telim)); - } + if (!restarting) { + // Start by setting to the sheath current = 0 boundary value - // Set the last update time to -1, so it will reset - // the first time RHS function is called - phi_boundary_last_update = -1.; + Field3D Nelim = floor(Ne, 1e-5); + Telim = floor(Pe / Nelim, 0.1 / Tnorm); + Tilim = floor(Pi / Nelim, 0.1 / Tnorm); - phi_boundary_timescale = optsc["phi_boundary_timescale"] - .doc("Timescale for phi boundary relaxation [seconds]") - .withDefault(1e-4) - * Omega_ci; // Normalise to internal time units - } - - // Apar (Psi) solver - aparSolver = LaplaceXZ::create(mesh, &opt["aparSolver"], CELL_CENTRE); - if (split_n0_psi) { - // Use another XY solver for n=0 psi component - aparXY = new LaplaceXY(mesh); - psi2D = 0.0; - } + phi.setBoundaryTo( + DC((log(0.5 * sqrt(mi_me / PI)) + log(sqrt(Telim / (Telim + Tilim)))) + * Telim)); + } + + // Set the last update time to -1, so it will reset + // the first time RHS function is called + phi_boundary_last_update = -1.; + + phi_boundary_timescale = + optsc["phi_boundary_timescale"] + .doc("Timescale for phi boundary relaxation [seconds]") + .withDefault(1e-4) + * Omega_ci; // Normalise to internal time units + } + + // Apar (Psi) solver + aparSolver = LaplaceXZ::create(mesh, &opt["aparSolver"], CELL_CENTRE); + if (split_n0_psi) { + // Use another XY solver for n=0 psi component + aparXY = new LaplaceXY(mesh); + psi2D = 0.0; + } - Ve.setBoundary("Ve"); - nu.setBoundary("nu"); - Jpar.setBoundary("Jpar"); + Ve.setBoundary("Ve"); + nu.setBoundary("nu"); + Jpar.setBoundary("Jpar"); - psi = 0.0; + psi = 0.0; + } } - nu = 0.0; kappa_epar = 0.0; + kappa_ipar = 0.0; Dn = 0.0; SAVE_REPEAT(Telim, Tilim); @@ -957,10 +1133,8 @@ int Hermes::init(bool restarting) { SAVE_REPEAT(tau_e, tau_i); - if (thermal_conduction || sinks) { - SAVE_REPEAT(kappa_epar); // Parallel electron heat conductivity - SAVE_REPEAT(kappa_ipar); // Parallel ion heat conductivity - } + SAVE_REPEAT(kappa_epar); // Parallel electron heat conductivity + SAVE_REPEAT(kappa_ipar); // Parallel ion heat conductivity if (resistivity) { SAVE_REPEAT(nu); // Parallel resistivity @@ -976,16 +1150,51 @@ int Hermes::init(bool restarting) { // Sources added to Ne, Pe and Pi equations SAVE_REPEAT(NeSource, PeSource, PiSource); } - + + zero_all(phi); + zero_all(psi); + if (relaxation) { + zero_all(phi_1); + } + // Preconditioner setPrecon((preconfunc)&Hermes::precon); - + + SAVE_REPEAT(a, b, c, d); + if (evolve_te && evolve_ti && parallel_sheaths) { + SAVE_REPEAT(sheath_dpe, sheath_dpi); + } + // Magnetic field in boundary + auto& Bxy = mesh->getCoordinates()->Bxy; + mesh->communicate(Bxy); + + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + Bxy.ydown()(r.ind, mesh->ystart - 1, jz) = Bxy(r.ind, mesh->ystart, jz); + Bxy(r.ind, mesh->ystart - 1, jz) = Bxy(r.ind, mesh->ystart, jz); + } + } + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + Bxy.yup()(r.ind, mesh->yend + 1, jz) = Bxy(r.ind, mesh->yend, jz); + Bxy(r.ind, mesh->yend + 1, jz) = Bxy(r.ind, mesh->yend, jz); + } + } + return 0; } int Hermes::rhs(BoutReal t) { - Coordinates *coord = mesh->getCoordinates(); - + if (show_timesteps) { + printf("TIME = %e\r", t); + } + + Coordinates* coord = mesh->getCoordinates(); + a = 0; + b = 0; + c = 0; + d = 0; + f = 0; if (!evolve_plasma) { Ne = 0.0; Pe = 0.0; @@ -996,33 +1205,40 @@ int Hermes::rhs(BoutReal t) { sheath_model = 0; } - // Factor scaling the diamagnetic current - j_diamag_scale = j_diamag_scale_generator->generate(0, 0, 0, t); - // Communicate evolving variables // Note: Parallel slices are not calculated because parallel derivatives // are calculated using field aligned quantities - - mesh->communicateXZ(EvolvingVars); - for (auto* f : EvolvingVars.field3d()) { - f->clearParallelSlices(); // Make sure no parallel slices - } + mesh->communicate(EvolvingVars); + + Field3D Nelim = floor_all(Ne, 1e-5); - Field3D Nelim = floor(Ne, 1e-5); + if (!evolve_te) { + Pe = Te0 * copy_all(Nelim); // Fixed ion temperature + Pe.splitParallelSlices(); + Pe.yup() = Te0 * Nelim.yup(); + Pe.ydown() = Te0 * Nelim.ydown(); + } - Te = Pe / Nelim; - Vi = NVi / Nelim; + Te = div_all(Pe, Nelim); + Vi = div_all(NVi, Nelim); - Telim = floor(Te, 0.1 / Tnorm); + Telim = floor_all(Te, 0.1 / Tnorm); - Field3D Pelim = Telim * Nelim; + Field3D Pelim = mul_all(Telim, Nelim); - Field3D logPelim = log(Pelim); - logPelim.applyBoundary("neumann"); + if (!evolve_ti) { + if (Ti0 > 1.0) { + throw BoutException("Hot-ions for isothermal simulation!"); + } + Pi = Ti0 * copy_all(Nelim); // Fixed ion temperature + Pi.splitParallelSlices(); + Pi.yup() = Ti0 * Nelim.yup(); + Pi.ydown() = Ti0 * Nelim.ydown(); + } - Ti = Pi / Nelim; - Tilim = floor(Ti, 0.1 / Tnorm); - Field3D Pilim = Tilim * Nelim; + Ti = div_all(Pi, Nelim); + Tilim = floor_all(Ti, 0.1 / Tnorm); + Field3D Pilim = mul_all(Tilim, Nelim); // Set radial boundary conditions on Te, Ti, Vi // @@ -1094,23 +1310,6 @@ int Hermes::rhs(BoutReal t) { } sound_speed.applyBoundary("neumann"); - // Maximum wave speed either electron sound speed or Alfven speed - Field3D max_speed = Bnorm * coord->Bxy / - sqrt(SI::mu0 * AA * SI::Mp * Nnorm * Nelim) / - Cs0; // Alfven speed (normalised by Cs0) - Field3D elec_sound = sqrt(mi_me) * sound_speed; // Electron sound speed - for (auto& i : max_speed.getRegion(RGN_ALL)) { - if (elec_sound[i] > max_speed[i]) { - max_speed[i] = elec_sound[i]; - } - - // Limit to 100x reference sound speed or light speed - BoutReal lim = BOUTMIN(100., 3e8/Cs0); - if (max_speed[i] > lim) { - max_speed[i] = lim; - } - } - ////////////////////////////////////////////////////////////// // Calculate electrostatic potential phi // @@ -1118,7 +1317,7 @@ int Hermes::rhs(BoutReal t) { TRACE("Electrostatic potential"); - if (!currents) { + if (!currents && !relaxation) { // Disabling electric fields // phi = 0.0; // Already set in initialisation } else { @@ -1127,10 +1326,11 @@ int Hermes::rhs(BoutReal t) { // Set the boundary of phi. Both 2D and 3D fields are kept, though the 3D field // is constant in Z. This is for efficiency, to reduce the number of conversions. // Note: For now the boundary values are all at the midpoint, - // and only phi is considered, not phi + Pi which is handled in Boussinesq solves + // and only phi is considered, not phi + Pi which is handled in Boussinesq + // solves Field2D phi_boundary2d; Field3D phi_boundary3d; - + if (phi_boundary_relax) { // Update the boundary regions by relaxing towards zero gradient // on a given timescale. @@ -1138,12 +1338,13 @@ int Hermes::rhs(BoutReal t) { if (phi_boundary_last_update < 0.0) { // First time this has been called. phi_boundary_last_update = t; - + } else if (t > phi_boundary_last_update) { // Only update if time has advanced // Uses an exponential decay of the weighting of the value in the boundary // so that the solution is well behaved for arbitrary steps BoutReal weight = exp(-(t - phi_boundary_last_update) / phi_boundary_timescale); + // output.write("weight: {}\n", weight); phi_boundary_last_update = t; if (mesh->firstX()) { @@ -1154,17 +1355,18 @@ int Hermes::rhs(BoutReal t) { } phivalue /= mesh->LocalNz; // Average in Z of point next to boundary - // Old value of phi at boundary - BoutReal oldvalue = - 0.5 * (phi(mesh->xstart - 1, j, 0) + phi(mesh->xstart, j, 0)); + for (int k = 0; k < mesh->LocalNz; k++) { + phivalue = phi(mesh->xstart, j, k); + // Old value of phi at boundary + BoutReal oldvalue = phi( + mesh->xstart, j, + k); // 0.5 * (phi(mesh->xstart - 1, j, k) + phi(mesh->xstart, j, k)); - // New value of phi at boundary, relaxing towards phivalue - BoutReal newvalue = - weight * oldvalue + (1. - weight) * phivalue; + // New value of phi at boundary, relaxing towards phivalue + BoutReal newvalue = weight * oldvalue + (1. - weight) * phivalue; - // Set phi at the boundary to this value - for (int k = 0; k < mesh->LocalNz; k++) { - phi(mesh->xstart - 1, j, k) = 2.*newvalue - phi(mesh->xstart, j, k); + // Set phi at the boundary to this value + phi(mesh->xstart - 1, j, k) = 2. * newvalue - phi(mesh->xstart, j, k); // Note: This seems to make a difference, but don't know why. // Without this, get convergence failures with no apparent instability @@ -1182,15 +1384,19 @@ int Hermes::rhs(BoutReal t) { } phivalue /= mesh->LocalNz; // Average in Z of point next to boundary - // Old value of phi at boundary - BoutReal oldvalue = 0.5 * (phi(mesh->xend + 1, j, 0) + phi(mesh->xend, j, 0)); + for (int k = 0; k < mesh->LocalNz; k++) { + phivalue = phi(mesh->xend, j, k); + + // Old value of phi at boundary + BoutReal oldvalue = + phi(mesh->xend, j, + k); // 0.5 * (phi(mesh->xend + 1, j, k) + phi(mesh->xend, j, k)); - // New value of phi at boundary, relaxing towards phivalue - BoutReal newvalue = weight * oldvalue + (1. - weight) * phivalue; + // New value of phi at boundary, relaxing towards phivalue + BoutReal newvalue = weight * oldvalue + (1. - weight) * phivalue; - // Set phi at the boundary to this value - for (int k = 0; k < mesh->LocalNz; k++) { - phi(mesh->xend + 1, j, k) = 2.*newvalue - phi(mesh->xend, j, k); + // Set phi at the boundary to this value + phi(mesh->xend + 1, j, k) = 2. * newvalue - phi(mesh->xend, j, k); // Note: This seems to make a difference, but don't know why. // Without this, get convergence failures with no apparent instability @@ -1205,14 +1411,14 @@ int Hermes::rhs(BoutReal t) { // phi_boundary_relax = false // // Set boundary from temperature, to be consistent with j=0 at sheath - + // Sheath multiplier Te -> phi (2.84522 for Deuterium if Ti = 0) phi_boundary2d = DC((log(0.5 * sqrt(mi_me / PI)) + log(sqrt(Telim / (Telim + Tilim)))) * Telim); - + phi_boundary3d = phi_boundary2d; } - + if (phi3d) { #ifdef PHISOLVER phiSolver3D->setCoefC(Ne / SQ(coord->Bxy)); @@ -1226,11 +1432,13 @@ int Hermes::rhs(BoutReal t) { } phi = phiSolver3D->solve(Vort, phi); #endif - } else { + } + + else { // Phi flags should be set in BOUT.inp // phiSolver->setInnerBoundaryFlags(INVERT_DC_GRAD); // phiSolver->setOuterBoundaryFlags(INVERT_SET); - + if (boussinesq) { // Update boundary conditions. Two issues: @@ -1248,10 +1456,9 @@ int Hermes::rhs(BoutReal t) { // onto the cell mid-point phi_boundary3d(mesh->xstart - 1, j, k) = 0.5 - * (phi_boundary3d(mesh->xstart - 1, j, k) + - phi_boundary3d(mesh->xstart, j, k) + - Pi(mesh->xstart - 1, j, k) + - Pi(mesh->xstart, j, k)); + * (phi_boundary3d(mesh->xstart - 1, j, k) + + phi_boundary3d(mesh->xstart, j, k) + Pi(mesh->xstart - 1, j, k) + + Pi(mesh->xstart, j, k)); } } } @@ -1261,63 +1468,69 @@ int Hermes::rhs(BoutReal t) { for (int k = 0; k < mesh->LocalNz; k++) { phi_boundary3d(mesh->xend + 1, j, k) = 0.5 - * (phi_boundary3d(mesh->xend + 1, j, k) + - phi_boundary3d(mesh->xend, j, k) + - Pi(mesh->xend + 1, j, k) + - Pi(mesh->xend, j, k)); + * (phi_boundary3d(mesh->xend + 1, j, k) + + phi_boundary3d(mesh->xend, j, k) + Pi(mesh->xend + 1, j, k) + + Pi(mesh->xend, j, k)); } } } - if (split_n0) { - //////////////////////////////////////////// - // Boussinesq, split - // Split into axisymmetric and non-axisymmetric components - Field2D Vort2D = DC(Vort); // n=0 component - - if (!phi_boundary2d.isAllocated()) { - // Make sure that the 2D boundary field is set - phi_boundary2d = DC(phi_boundary3d); - } - - // Set the boundary - phi2D.setBoundaryTo(phi_boundary2d); - - phi2D = laplacexy->solve(Vort2D, phi2D); - - // Solve non-axisymmetric part using X-Z solver - if (newXZsolver) { - newSolver->setCoefs(1. / SQ(coord->Bxy), 0.0); - phi = newSolver->solve(Vort - Vort2D, - // Second argument is initial guess. Use current phi, and update boundary - withBoundary(phi + Pi - phi2D, // Value in domain - phi_boundary3d - phi_boundary2d)); // boundary - } else { - phiSolver->setCoefC(1. / SQ(coord->Bxy)); - phi = phiSolver->solve((Vort - Vort2D) * SQ(coord->Bxy), - phi_boundary3d - phi_boundary2d); // Note: non-zero due to Pi variation - } - phi += phi2D; // Add axisymmetric part + + if (relaxation) { + // ddt(phi_1) = lambda_0 * lambda_2 * ( ( 1 / lambda_2 * FV::Div_a_Laplace_perp( + // 1/B_SQ, phi_1 ) + FV::Div_a_Laplace_perp( 1/B_SQ, phi_1 ) ) - Vort ); + phi = phi_1 / lambda_2; } else { - //////////////////////////////////////////// - // Boussinesq, non-split - // Solve all components using X-Z solver - - if (newXZsolver) { - // Use the new LaplaceXZ solver - // newSolver->setCoefs(1./SQ(coord->Bxy), 0.0); // Set when initialised - phi = newSolver->solve(Vort, - withBoundary(phi + Pi, // Value in domain - phi_boundary3d)); // boundary + if (split_n0) { + //////////////////////////////////////////// + // Boussinesq, split + // Split into axisymmetric and non-axisymmetric components + Field2D Vort2D = DC(Vort); // n=0 component + + if (!phi_boundary2d.isAllocated()) { + // Make sure that the 2D boundary field is set + phi_boundary2d = DC(phi_boundary3d); + } + + // Set the boundary + phi2D.setBoundaryTo(phi_boundary2d); + + phi2D = laplacexy->solve(Vort2D, phi2D); + + // Solve non-axisymmetric part using X-Z solver + if (newXZsolver) { + newSolver->setCoefs(1. / SQ(coord->Bxy), 0.0); + phi = newSolver->solve( + Vort - Vort2D, + // Second argument is initial guess. Use current phi, and update + // boundary + withBoundary(phi + Pi - phi2D, // Value in domain + phi_boundary3d - phi_boundary2d)); // boundary + } else { + phiSolver->setCoefC(1. / SQ(coord->Bxy)); + phi = phiSolver->solve( + (Vort - Vort2D) * SQ(coord->Bxy), + phi_boundary3d - phi_boundary2d); // Note: non-zero due to Pi variation + } + phi += phi2D; // Add axisymmetric part } else { - // Use older Laplacian solver - // phiSolver->setCoefC(1./SQ(coord->Bxy)); // Set when initialised - phi = phiSolver->solve(Vort * SQ(coord->Bxy), phi_boundary3d); + //////////////////////////////////////////// + // Boussinesq, non-split + // Solve all components using X-Z solver + + if (newXZsolver) { + // Use the new LaplaceXZ solver + // newSolver->setCoefs(1./SQ(coord->Bxy), 0.0); // Set when initialised + phi = newSolver->solve(Vort, phi + Pi); + } else { + // Use older Laplacian solver + // phiSolver->setCoefC(1./SQ(coord->Bxy)); // Set when initialised + phi = phiSolver->solve(Vort * SQ(coord->Bxy), phi_boundary3d); + } } - } - - // Hot ion term in vorticity - phi -= Pi; + // Hot ion term in vorticity + phi -= Pi; + } } else { //////////////////////////////////////////// // Non-Boussinesq @@ -1327,7 +1540,6 @@ int Hermes::rhs(BoutReal t) { } phi.applyBoundary(t); mesh->communicate(phi); - phi.clearParallelSlices(); } ////////////////////////////////////////////////////////////// @@ -1336,9 +1548,9 @@ int Hermes::rhs(BoutReal t) { if (!currents) { // No magnetic fields or currents - psi = 0.0; - Jpar = 0.0; - Ve = 0.0; // Ve will be set after the sheath boundaries below + zero_all(psi); + zero_all(Jpar); + zero_all(Ve); // Ve will be set after the sheath boundaries below } else { // Calculate electomagnetic potential psi from VePsi // VePsi = Ve - Vi + 0.5 * mi_me * beta_e * psi @@ -1350,60 +1562,65 @@ int Hermes::rhs(BoutReal t) { // Solve Helmholtz equation for psi // aparSolver->setCoefA(-Ne*0.5*mi_me*beta_e); - Field2D NDC = DC(Ne); - aparSolver->setCoefs(1.0, -NDC * 0.5 * mi_me * beta_e); + aparSolver->setCoefs(Field3D(1.0), -Ne * 0.5 * mi_me * beta_e); // aparSolver->setCoefs(1.0, -Ne*0.5*mi_me*beta_e); - if (split_n0_psi) { - // Solve n=0 component separately - - aparXY->setCoefs(1.0, -NDC * 0.5 * mi_me * beta_e); - - Field2D JDC = -NDC * DC(VePsi); - aparXY->solve(JDC, psi2D); - psi = aparSolver->solve(-NDC * VePsi - JDC, psi - psi2D) + psi2D; - } else { - psi = aparSolver->solve(-NDC * VePsi, psi); - // psi = aparSolver->solve(-Ne*VePsi, psi); - } + psi = aparSolver->solve(Field3D(-Ne * VePsi), Field3D(psi)); + // psi = aparSolver->solve(-Ne*VePsi, psi); - Ve = VePsi - 0.5 * mi_me * beta_e * psi + Vi; + Ve = VePsi - 0.5 * beta_e * mi_me * psi + Vi; Ve.applyBoundary(t); mesh->communicate(Ve, psi); - Jpar = Ne * (Vi - Ve); + Jpar = mul_all(Ne, sub_all(Vi, Ve)); Jpar.applyBoundary(t); } else { // Zero electron mass // No Ve term in VePsi, only electromagnetic term - - psi = VePsi / (0.5 * mi_me * beta_e); + psi = div_all(VePsi, 0.5 * mi_me * beta_e); // Ve = (NVi - Delp2(psi)) / Nelim; - Jpar = FV::Div_a_Laplace_perp(1.0, psi); + if (fci_transform) { + Field3D atmp = 1.0; + mesh->communicate(atmp, psi); + Jpar = FV::Div_a_Laplace_perp(atmp, psi); + } else { + Jpar = FV::Div_a_Laplace_perp(1.0, psi); + } + mesh->communicate(Jpar); Jpar.applyBoundary(t); - Ve = (NVi - Jpar) / Nelim; + Ve = div_all(sub_all(NVi, Jpar), Nelim); } // psi -= psi.DC(); // Remove toroidal average, only keep fluctuations } else { // Electrostatic - psi = 0.0; + zero_all(psi); if (FiniteElMass) { // No psi contribution to VePsi Ve = VePsi + Vi; } else { // Zero electron mass and electrostatic. // Special case where Ohm's law has no time-derivatives - mesh->communicate(phi); + mesh->communicate(phi, Pe); - Ve = Vi + (Grad_parP(phi) - Grad_parP(Pe) / Ne) / nu; + if (fci_transform) { + tau_e = (Cs0 / rho_s0) * tau_e0 * pow(Telim, 1.5) / Nelim; + nu = resistivity_multiply / (1.96 * tau_e * mi_me); + + Ve = Vi + (Grad_parP(phi) - Grad_parP(Pe) / Ne) / nu; + } else { + Ve = Vi + (Grad_parP(phi) - Grad_parP(Pe) / Ne) / nu; + } if (thermal_force) { + if (fci_transform) { + mesh->communicate(Te); + } Ve -= 0.71 * Grad_parP(Te) / nu; } } @@ -1411,8 +1628,9 @@ int Hermes::rhs(BoutReal t) { Ve.applyBoundary(t); // Communicate auxilliary variables mesh->communicate(Ve); - - Jpar = NVi - Ne * Ve; + Field3D neve = mul_all(Ne, Ve); + mesh->communicate(neve); + Jpar = sub_all(NVi, neve); } // Ve -= Jpar0 / Ne; // Equilibrium current density } @@ -1425,51 +1643,13 @@ int Hermes::rhs(BoutReal t) { TRACE("Sheath boundaries"); - // Manually setting boundary conditions - Ne.clearParallelSlices(); - Pe.clearParallelSlices(); - Pi.clearParallelSlices(); - Pelim.clearParallelSlices(); - Pilim.clearParallelSlices(); - Te.clearParallelSlices(); - Ti.clearParallelSlices(); - Ve.clearParallelSlices(); - Vi.clearParallelSlices(); - Vi.clearParallelSlices(); - NVi.clearParallelSlices(); - phi.clearParallelSlices(); - Vort.clearParallelSlices(); - Jpar.clearParallelSlices(); - - // Shift to field aligned - - Ne = toFieldAligned(Ne); - - Te = toFieldAligned(Te); - Ti = toFieldAligned(Ti); - Telim = toFieldAligned(Telim); - Tilim = toFieldAligned(Tilim); - - Pe = toFieldAligned(Pe); - Pi = toFieldAligned(Pi); - Pelim = toFieldAligned(Pelim); - Pilim = toFieldAligned(Pilim); - - Ve = toFieldAligned(Ve); - Vi = toFieldAligned(Vi); - NVi = toFieldAligned(NVi); - - phi = toFieldAligned(phi); - Jpar = toFieldAligned(Jpar); - Vort = toFieldAligned(Vort); - if (sheath_ydown) { switch (sheath_model) { case 0: { // Normal Bohm sheath for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { // Zero-gradient density - BoutReal nesheath = floor(Ne(r.ind, mesh->ystart, jz), nesheath_floor); + BoutReal nesheath = floor(Ne(r.ind, mesh->ystart, jz), 0.0); // Temperature at the sheath entrance BoutReal tesheath = floor(Te(r.ind, mesh->ystart, jz), 0.0); @@ -1481,7 +1661,7 @@ int Hermes::rhs(BoutReal t) { // Ion velocity goes to the sound speed. Note negative since out of the domain BoutReal visheath = -sqrt(tesheath + tisheath); - if (Vi(r.ind, mesh->ystart, jz) < visheath) { + if (sheath_allow_supersonic && (Vi(r.ind, mesh->ystart, jz) < visheath)) { // If plasma is faster, go to plasma velocity visheath = Vi(r.ind, mesh->ystart, jz); } @@ -1489,8 +1669,7 @@ int Hermes::rhs(BoutReal t) { // Sheath current // Note that phi/Te >= 0.0 since for phi < 0 // vesheath is the electron saturation current - BoutReal phi_te = - floor(phisheath / Telim(r.ind, mesh->ystart, jz), 0.0); + BoutReal phi_te = floor(phisheath / Telim(r.ind, mesh->ystart, jz), 0.0); BoutReal vesheath = -sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); @@ -1502,222 +1681,95 @@ int Hermes::rhs(BoutReal t) { } // Apply boundary condition half-way between cells - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - // Neumann conditions - Ne(r.ind, jy, jz) = nesheath; - phi(r.ind, jy, jz) = phisheath; - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->ystart, jz); - - // Here zero-gradient Te, heat flux applied later - Te(r.ind, jy, jz) = Te(r.ind, mesh->ystart, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->ystart, jz); - - Pe(r.ind, jy, jz) = Pe(r.ind, mesh->ystart, jz); - Pelim(r.ind, jy, jz) = Pelim(r.ind, mesh->ystart, jz); - Pi(r.ind, jy, jz) = Pi(r.ind, mesh->ystart, jz); - Pilim(r.ind, jy, jz) = Pilim(r.ind, mesh->ystart, jz); - - // Dirichlet conditions - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->ystart, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->ystart, jz); - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); - } + // Neumann conditions + Ne.ydown()(r.ind, mesh->ystart - 1, jz) = nesheath; + phi.ydown()(r.ind, mesh->ystart - 1, jz) = phisheath; + Vort.ydown()(r.ind, mesh->ystart - 1, jz) = Vort(r.ind, mesh->ystart, jz); + + // Here zero-gradient Te, heat flux applied later + Te.ydown()(r.ind, mesh->ystart - 1, jz) = Te(r.ind, mesh->ystart, jz); + Ti.ydown()(r.ind, mesh->ystart - 1, jz) = Ti(r.ind, mesh->ystart, jz); + + Pe.ydown()(r.ind, mesh->ystart - 1, jz) = Pe(r.ind, mesh->ystart, jz); + Pelim.ydown()(r.ind, mesh->ystart - 1, jz) = Pelim(r.ind, mesh->ystart, jz); + Pi.ydown()(r.ind, mesh->ystart - 1, jz) = Pi(r.ind, mesh->ystart, jz); + Pilim.ydown()(r.ind, mesh->ystart - 1, jz) = Pilim(r.ind, mesh->ystart, jz); + + // Dirichlet conditions + Vi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * visheath - Vi(r.ind, mesh->ystart, jz); + Ve.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * vesheath - Ve(r.ind, mesh->ystart, jz); + Jpar.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); + NVi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); } } break; } - case 1: { - /* - Loizu boundary conditions - - Temperature - - Grad_par(Te) = 0 - - Density equation - - Grad_par(n) = -(n/Cs) Grad_par(Vi) - -> n_p - n_m = - (n_p + n_m)/(2Cs) (Vi_p - Vi_m) - - Pressure - - Grad_par(Pe) = Te Grad_par(n) - - Potential - Grad_par(phi) = -Cs Grad_par(Vi) - -> phi_p - phi_m = -Cs (Vi_p - Vi_m) - */ - - throw BoutException("Sorry, no Loizu boundary for hot ions yet"); - + case 2: { // Bohm sheath with free density for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Zero-gradient density + BoutReal nesheath = 0.5 + * (3. * Ne(r.ind, mesh->ystart, jz) + - Ne.yup()(r.ind, mesh->ystart + 1, jz)); + if (nesheath < 0.0) + nesheath = 0.0; + // Temperature at the sheath entrance BoutReal tesheath = floor(Te(r.ind, mesh->ystart, jz), 0.0); + BoutReal tisheath = floor(Ti(r.ind, mesh->ystart, jz), 0.0); - // Zero gradient Te - Te(r.ind, mesh->ystart - 1, jz) = Te(r.ind, mesh->ystart, jz); - BoutReal Cs = sqrt(tesheath); // Sound speed + // Zero-gradient potential + BoutReal phisheath = phi(r.ind, mesh->ystart, jz); // Ion velocity goes to the sound speed - // Dirichlet boundary condition - Vi(r.ind, mesh->ystart - 1, jz) = - -2. * Cs - Vi(r.ind, mesh->ystart, jz); - - BoutReal g = 0.0; - if (tesheath > 0.1 / Tnorm) { - // Only divide by Cs if the temperature is greater than 0.1eV - // to avoid divide-by-zero errors - g = (Vi(r.ind, mesh->ystart - 1, jz) - - Vi(r.ind, mesh->ystart, jz)) / - (-2. * Cs); - } - - // Mixed boundary condition on n - Ne(r.ind, mesh->ystart - 1, jz) = - Ne(r.ind, mesh->ystart, jz) * (1 - g) / (1 + g); - - // Make sure nesheath doesn't go negative - Ne(r.ind, mesh->ystart - 1, jz) = floor( - Ne(r.ind, mesh->ystart - 1, jz), -Ne(r.ind, mesh->ystart, jz)); - - // Density at the sheath - BoutReal nesheath = 0.5 * (Ne(r.ind, mesh->ystart, jz) + - Ne(r.ind, mesh->ystart - 1, jz)); - - // Momentum - NVi(r.ind, mesh->ystart - 1, jz) = NVi(r.ind, mesh->ystart, jz); - if (NVi(r.ind, mesh->ystart - 1, jz) > 0.0) { - // Limit flux to be <= 0 - NVi(r.ind, mesh->ystart - 1, jz) = -NVi(r.ind, mesh->ystart, jz); - } - - // Pressure - Pe(r.ind, mesh->ystart - 1, jz) = - Pe(r.ind, mesh->ystart, jz) + - tesheath * (Ne(r.ind, mesh->ystart - 1, jz) - - Ne(r.ind, mesh->ystart, jz)); - - // Potential - phi(r.ind, mesh->ystart - 1, jz) = - phi(r.ind, mesh->ystart, jz) - - Cs * (Vi(r.ind, mesh->ystart - 1, jz) - - Vi(r.ind, mesh->ystart, jz)); - BoutReal phisheath = 0.5 * (phi(r.ind, mesh->ystart, jz) + - phi(r.ind, mesh->ystart - 1, jz)); - - // Sheath current - BoutReal phi_te = floor(phisheath / tesheath, 0.0); - BoutReal vesheath = - -Cs * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); - - BoutReal jsheath = nesheath * (-Cs - vesheath); - - // Electron velocity - Ve(r.ind, mesh->ystart - 1, jz) = - 2. * vesheath - Ve(r.ind, mesh->ystart, jz); - - // Parallel velocity - Jpar(r.ind, mesh->ystart - 1, jz) = - 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); - - if (currents && !finite(Jpar(r.ind, mesh->ystart - 1, jz))) { - output.write("JPAR: %d, %d: %e, %e, %e, %e\n", r.ind, jz, jsheath, - vesheath, Cs, nesheath); - output.write(" -> %e, %e, %e\n", Ne(r.ind, mesh->ystart, jz), - Ne(r.ind, mesh->ystart - 1, jz), g); - exit(1); - } - - // Constant gradient on other cells - for (int jy = mesh->ystart - 2; jy >= 0; jy--) { - Vi(r.ind, jy, jz) = - 2. * Vi(r.ind, jy + 1, jz) - Vi(r.ind, jy + 2, jz); - Ve(r.ind, jy, jz) = - 2. * Ve(r.ind, jy + 1, jz) - Ve(r.ind, jy + 2, jz); - NVi(r.ind, jy, jz) = - 2. * NVi(r.ind, jy + 1, jz) - NVi(r.ind, jy + 2, jz); - - Ne(r.ind, jy, jz) = - 2. * Ne(r.ind, jy + 1, jz) - Ne(r.ind, jy + 2, jz); - Te(r.ind, jy, jz) = - 2. * Te(r.ind, jy + 1, jz) - Te(r.ind, jy + 2, jz); - Pe(r.ind, jy, jz) = - 2. * Pe(r.ind, jy + 1, jz) - Pe(r.ind, jy + 2, jz); - - phi(r.ind, jy, jz) = - 2. * phi(r.ind, jy + 1, jz) - phi(r.ind, jy + 2, jz); - Vort(r.ind, jy, jz) = - 2. * Vort(r.ind, jy + 1, jz) - Vort(r.ind, jy + 2, jz); - Jpar(r.ind, jy, jz) = - 2. * Jpar(r.ind, jy + 1, jz) - Jpar(r.ind, jy + 2, jz); - } - } - } - break; - } - case 2: { // Bohm sheath with free density - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Zero-gradient density - BoutReal nesheath = 0.5 * (3. * Ne(r.ind, mesh->ystart, jz) - - Ne(r.ind, mesh->ystart + 1, jz)); - if (nesheath < nesheath_floor) - nesheath = nesheath_floor; - - // Temperature at the sheath entrance - BoutReal tesheath = floor(Te(r.ind, mesh->ystart, jz), 0.0); - BoutReal tisheath = floor(Ti(r.ind, mesh->ystart, jz), 0.0); - - // Zero-gradient potential - BoutReal phisheath = phi(r.ind, mesh->ystart, jz); - - // Ion velocity goes to the sound speed - BoutReal visheath = - -sqrt(tesheath + tisheath); // Sound speed outwards - - if (Vi(r.ind, mesh->ystart, jz) < visheath) { - // If plasma is faster, go to plasma velocity - visheath = Vi(r.ind, mesh->ystart, jz); + BoutReal visheath = -sqrt(tesheath + tisheath); // Sound speed outwards + + if (sheath_allow_supersonic && (Vi(r.ind, mesh->ystart, jz) < visheath)) { + // If plasma is faster, go to plasma velocity + visheath = Vi(r.ind, mesh->ystart, jz); } // Sheath current - BoutReal phi_te = - floor(phisheath / Telim(r.ind, mesh->ystart, jz), 0.0); + BoutReal phi_te = floor(phisheath / Telim(r.ind, mesh->ystart, jz), 0.0); BoutReal vesheath = -sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); // J = n*(Vi - Ve) BoutReal jsheath = nesheath * (visheath - vesheath); - if (nesheath <= nesheath_floor) { + if (nesheath < 1e-10) { vesheath = visheath; jsheath = 0.0; } // Apply boundary condition half-way between cells - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - // Neumann conditions - phi(r.ind, jy, jz) = phisheath; - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->ystart, jz); - - // Here zero-gradient Te, heat flux applied later - Te(r.ind, jy, jz) = Te(r.ind, mesh->ystart, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->ystart, jz); - // Dirichlet conditions - Ne(r.ind, jy, jz) = 2. * nesheath - Ne(r.ind, mesh->ystart, jz); - Pe(r.ind, jy, jz) = - 2. * nesheath * tesheath - Pe(r.ind, mesh->ystart, jz); - Pi(r.ind, jy, jz) = - 2. * nesheath * tisheath - Pi(r.ind, mesh->ystart, jz); - - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->ystart, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->ystart, jz); - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); - } + // Neumann conditions + phi.ydown()(r.ind, mesh->ystart - 1, jz) = phisheath; + Vort.ydown()(r.ind, mesh->ystart - 1, jz) = Vort(r.ind, mesh->ystart, jz); + + // Here zero-gradient Te, heat flux applied later + Te.ydown()(r.ind, mesh->ystart - 1, jz) = Te(r.ind, mesh->ystart, jz); + Ti.ydown()(r.ind, mesh->ystart - 1, jz) = Ti(r.ind, mesh->ystart, jz); + + // Dirichlet conditions + Ne.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * nesheath - Ne(r.ind, mesh->ystart, jz); + Pe.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * nesheath * tesheath - Pe(r.ind, mesh->ystart, jz); + Pi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * nesheath * tisheath - Pi(r.ind, mesh->ystart, jz); + + Vi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * visheath - Vi(r.ind, mesh->ystart, jz); + Ve.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * vesheath - Ve(r.ind, mesh->ystart, jz); + Jpar.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); + NVi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); } } break; @@ -1726,8 +1778,9 @@ int Hermes::rhs(BoutReal t) { for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { // Free density - BoutReal nesheath = 0.5 * (3. * Ne(r.ind, mesh->ystart, jz) - - Ne(r.ind, mesh->ystart + 1, jz)); + BoutReal nesheath = 0.5 + * (3. * Ne(r.ind, mesh->ystart, jz) + - Ne.yup()(r.ind, mesh->ystart + 1, jz)); if (nesheath < 0.0) nesheath = 0.0; @@ -1741,10 +1794,9 @@ int Hermes::rhs(BoutReal t) { BoutReal phisheath = phi(r.ind, mesh->ystart, jz); // Ion velocity goes to the sound speed - BoutReal visheath = - -sqrt(tesheath + tisheath); // Sound speed outwards + BoutReal visheath = -sqrt(tesheath + tisheath); // Sound speed outwards - if (Vi(r.ind, mesh->ystart, jz) < visheath) { + if (sheath_allow_supersonic && (Vi(r.ind, mesh->ystart, jz) < visheath)) { // If plasma is faster, go to plasma velocity visheath = Vi(r.ind, mesh->ystart, jz); } @@ -1753,125 +1805,61 @@ int Hermes::rhs(BoutReal t) { BoutReal vesheath = visheath; // Apply boundary condition half-way between cells - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - Ne(r.ind, jy, jz) = 2. * nesheath - Ne(r.ind, mesh->ystart, jz); - phi(r.ind, jy, jz) = 2. * phisheath - phi(r.ind, mesh->ystart, jz); - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->ystart, jz); - - // Here zero-gradient Te, heat flux applied later - Te(r.ind, jy, jz) = Te(r.ind, mesh->ystart, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->ystart, jz); - - Pe(r.ind, jy, jz) = Pe(r.ind, mesh->ystart, jz); - Pi(r.ind, jy, jz) = Pi(r.ind, mesh->ystart, jz); - - // Dirichlet conditions - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->ystart, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->ystart, jz); - Jpar(r.ind, jy, jz) = -Jpar(r.ind, mesh->ystart, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); - } - } - } - break; - } - case 4: { - // Bohm sheath, with free boundary on the density and pressure - // The extrapolation is done using ratios i.e. linear in log(Ne) and log(P) - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - - // Free boundary, linear extrapolation of logarithms - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - Ne(r.ind, jy, jz) = SQ(Ne(r.ind, mesh->ystart, jz)) / Ne(r.ind, mesh->ystart + 1, jz); - Pe(r.ind, jy, jz) = SQ(Pe(r.ind, mesh->ystart, jz)) / Pe(r.ind, mesh->ystart + 1, jz); - Pi(r.ind, jy, jz) = SQ(Pi(r.ind, mesh->ystart, jz)) / Pi(r.ind, mesh->ystart + 1, jz); - } - - // Value at sheath from linear interpolation - // consistent with finite different methods - BoutReal nesheath = 0.5 * (Ne(r.ind, mesh->ystart, jz) + - Ne(r.ind, mesh->ystart - 1, jz)); - BoutReal tesheath = 0.5 * (Te(r.ind, mesh->ystart, jz) + - Pe(r.ind, mesh->ystart-1, jz) / Ne(r.ind, mesh->ystart-1, jz)); - BoutReal tisheath = 0.5 * (Ti(r.ind, mesh->ystart, jz) + - Pi(r.ind, mesh->ystart-1, jz) / Ne(r.ind, mesh->ystart-1, jz)); - - // Zero-gradient potential - BoutReal phisheath = phi(r.ind, mesh->ystart, jz); + Ne(r.ind, mesh->ystart - 1, jz) = 2. * nesheath - Ne(r.ind, mesh->ystart, jz); + phi(r.ind, mesh->ystart - 1, jz) = + 2. * phisheath - phi(r.ind, mesh->ystart, jz); + Vort(r.ind, mesh->ystart - 1, jz) = Vort(r.ind, mesh->ystart, jz); - // Ion velocity goes to the sound speed - BoutReal visheath = - -sqrt(tesheath + tisheath); // Sound speed outwards + // Here zero-gradient Te, heat flux applied later + Te.ydown()(r.ind, mesh->ystart - 1, jz) = Te(r.ind, mesh->ystart, jz); + Ti.ydown()(r.ind, mesh->ystart - 1, jz) = Ti(r.ind, mesh->ystart, jz); - if (Vi(r.ind, mesh->ystart, jz) < visheath) { - // If plasma is faster, go to plasma velocity - visheath = Vi(r.ind, mesh->ystart, jz); - } + Pe.ydown()(r.ind, mesh->ystart - 1, jz) = Pe(r.ind, mesh->ystart, jz); + Pi.ydown()(r.ind, mesh->ystart - 1, jz) = Pi(r.ind, mesh->ystart, jz); - // Sheath current - BoutReal phi_te = floor(phisheath / tesheath, 0.0); - BoutReal vesheath = - -sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); - // J = n*(Vi - Ve) - BoutReal jsheath = nesheath * (visheath - vesheath); - if (nesheath < 1e-10) { - vesheath = visheath; - jsheath = 0.0; - } - - // Apply boundary condition half-way between cells - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - // Neumann conditions - phi(r.ind, jy, jz) = phisheath; - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->ystart, jz); - - // Here zero-gradient Te, heat flux applied later - Te(r.ind, jy, jz) = Te(r.ind, mesh->ystart, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->ystart, jz); - - // Dirichlet conditions to set flows - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->ystart, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->ystart, jz); - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); - } + // Dirichlet conditions + Vi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * visheath - Vi(r.ind, mesh->ystart, jz); + Ve.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * vesheath - Ve(r.ind, mesh->ystart, jz); + Jpar.ydown()(r.ind, mesh->ystart - 1, jz) = -Jpar(r.ind, mesh->ystart, jz); + NVi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); } } break; } - default: { - throw BoutException("Sheath model %d not implemented", sheath_model); - } } } else { // sheath_ydown == false for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - // Zero-gradient Te - Te(r.ind, jy, jz) = Te(r.ind, mesh->ystart, jz); - Telim(r.ind, jy, jz) = Telim(r.ind, mesh->ystart, jz); - - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->ystart, jz); - Tilim(r.ind, jy, jz) = Tilim(r.ind, mesh->ystart, jz); - - Pe(r.ind, jy, jz) = Ne(r.ind, jy, jz) * Te(r.ind, jy, jz); - Pelim(r.ind, jy, jz) = Nelim(r.ind, jy, jz) * Telim(r.ind, jy, jz); - - Pi(r.ind, jy, jz) = Ne(r.ind, jy, jz) * Ti(r.ind, jy, jz); - Pilim(r.ind, jy, jz) = Nelim(r.ind, jy, jz) * Tilim(r.ind, jy, jz); - - phi(r.ind, jy, jz) = phi(r.ind, mesh->ystart, jz); - - // No flows - Vi(r.ind, jy, jz) = -Vi(r.ind, mesh->ystart, jz); - NVi(r.ind, jy, jz) = -NVi(r.ind, mesh->ystart, jz); - Ve(r.ind, jy, jz) = -Ve(r.ind, mesh->ystart, jz); - Jpar(r.ind, jy, jz) = -Jpar(r.ind, mesh->ystart, jz); - } + // Zero-gradient Te + Te.ydown()(r.ind, mesh->ystart - 1, jz) = Te(r.ind, mesh->ystart, jz); + Telim.ydown()(r.ind, mesh->ystart - 1, jz) = Telim(r.ind, mesh->ystart, jz); + + Ti.ydown()(r.ind, mesh->ystart - 1, jz) = Ti(r.ind, mesh->ystart, jz); + Tilim.ydown()(r.ind, mesh->ystart - 1, jz) = Tilim(r.ind, mesh->ystart, jz); + + Pe.ydown()(r.ind, mesh->ystart - 1, jz) = + Ne.ydown()(r.ind, mesh->ystart - 1, jz) + * Te.ydown()(r.ind, mesh->ystart - 1, jz); + Pelim.ydown()(r.ind, mesh->ystart - 1, jz) = + Nelim.ydown()(r.ind, mesh->ystart - 1, jz) + * Telim.ydown()(r.ind, mesh->ystart - 1, jz); + + Pi.ydown()(r.ind, mesh->ystart - 1, jz) = + Ne.ydown()(r.ind, mesh->ystart - 1, jz) + * Ti.ydown()(r.ind, mesh->ystart - 1, jz); + Pilim.ydown()(r.ind, mesh->ystart - 1, jz) = + Nelim.ydown()(r.ind, mesh->ystart - 1, jz) + * Tilim.ydown()(r.ind, mesh->ystart - 1, jz); + + // No flows + Vi.ydown()(r.ind, mesh->ystart - 1, jz) = -Vi(r.ind, mesh->ystart, jz); + NVi.ydown()(r.ind, mesh->ystart - 1, jz) = -NVi(r.ind, mesh->ystart, jz); + Ve.ydown()(r.ind, mesh->ystart - 1, jz) = -Ve(r.ind, mesh->ystart, jz); + Jpar.ydown()(r.ind, mesh->ystart - 1, jz) = -Jpar(r.ind, mesh->ystart, jz); } } } @@ -1882,7 +1870,7 @@ int Hermes::rhs(BoutReal t) { for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { // Zero-gradient density - BoutReal nesheath = floor(Ne(r.ind, mesh->yend, jz), nesheath_floor); + BoutReal nesheath = floor(Ne(r.ind, mesh->yend, jz), 0.0); // Temperature at the sheath entrance BoutReal tesheath = floor(Te(r.ind, mesh->yend, jz), 0.0); @@ -1894,7 +1882,7 @@ int Hermes::rhs(BoutReal t) { // Ion velocity goes to the sound speed BoutReal visheath = sqrt(tesheath + tisheath); // Sound speed outwards - if (Vi(r.ind, mesh->yend, jz) > visheath) { + if (sheath_allow_supersonic && (Vi(r.ind, mesh->yend, jz) > visheath)) { // If plasma is faster, go to plasma velocity visheath = Vi(r.ind, mesh->yend, jz); } @@ -1902,8 +1890,7 @@ int Hermes::rhs(BoutReal t) { // Sheath current // Note that phi/Te >= 0.0 since for phi < 0 // vesheath is the electron saturation current - BoutReal phi_te = - floor(phisheath / Telim(r.ind, mesh->yend, jz), 0.0); + BoutReal phi_te = floor(phisheath / Telim(r.ind, mesh->yend, jz), 0.0); BoutReal vesheath = sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); // J = n*(Vi - Ve) @@ -1914,153 +1901,27 @@ int Hermes::rhs(BoutReal t) { } // Apply boundary condition half-way between cells - for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { - // Neumann conditions - Ne(r.ind, jy, jz) = nesheath; - phi(r.ind, jy, jz) = phisheath; - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->yend, jz); - - // Here zero-gradient Te, heat flux applied later - Te(r.ind, jy, jz) = Te(r.ind, mesh->yend, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->yend, jz); - - Pe(r.ind, jy, jz) = Pe(r.ind, mesh->yend, jz); - Pelim(r.ind, jy, jz) = Pelim(r.ind, mesh->yend, jz); - Pi(r.ind, jy, jz) = Pi(r.ind, mesh->yend, jz); - Pilim(r.ind, jy, jz) = Pilim(r.ind, mesh->yend, jz); - - // Dirichlet conditions - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->yend, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->yend, jz); - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->yend, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); - } - } - } - break; - } - case 1: { - /* - Loizu boundary conditions - - Temperature - - Grad_par(Te) = 0 - - Density equation - - Grad_par(n) = -(n/Cs) Grad_par(Vi) - -> n_p - n_m = - (n_p + n_m)/(2Cs) (Vi_p - Vi_m) - - Pressure - - Grad_par(Pe) = Te Grad_par(n) - - Potential - - Grad_par(phi) = -Cs Grad_par(Vi) - -> phi_p - phi_m = -Cs (Vi_p - Vi_m) - */ - - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Temperature at the sheath entrance - BoutReal tesheath = floor(Te(r.ind, mesh->yend, jz), 0.0); - - // Zero gradient Te - Te(r.ind, mesh->yend + 1, jz) = Te(r.ind, mesh->yend, jz); - BoutReal Cs = sqrt(tesheath); // Sound speed - - // Ion velocity goes to the sound speed - // Dirichlet boundary condition - Vi(r.ind, mesh->yend + 1, jz) = 2. * Cs - Vi(r.ind, mesh->yend, jz); - - BoutReal g = 0.0; - if (tesheath > 0.1 / Tnorm) { - // Only divide by Cs if the temperature is greater than 0.1eV - // to avoid divide-by-zero errors - g = (Vi(r.ind, mesh->yend + 1, jz) - Vi(r.ind, mesh->yend, jz)) / - (2. * Cs); - } - - // Mixed boundary condition on n - Ne(r.ind, mesh->yend + 1, jz) = - Ne(r.ind, mesh->yend, jz) * (1 - g) / (1 + g); - - // Make sure nesheath doesn't go negative - Ne(r.ind, mesh->yend + 1, jz) = - floor(Ne(r.ind, mesh->yend + 1, jz), -Ne(r.ind, mesh->yend, jz)); - // Density at the sheath - BoutReal nesheath = - 0.5 * (Ne(r.ind, mesh->yend, jz) + Ne(r.ind, mesh->yend + 1, jz)); - - // Momentum - NVi(r.ind, mesh->yend + 1, jz) = NVi(r.ind, mesh->yend, jz); - if (NVi(r.ind, mesh->yend + 1, jz) < 0.0) { - // Limit flux to be >= 0 - NVi(r.ind, mesh->yend + 1, jz) = -NVi(r.ind, mesh->yend, jz); - } - - // Pressure - Pe(r.ind, mesh->yend + 1, jz) = - Pe(r.ind, mesh->yend, jz) + - tesheath * - (Ne(r.ind, mesh->yend + 1, jz) - Ne(r.ind, mesh->yend, jz)); - - // Potential - phi(r.ind, mesh->yend + 1, jz) = - phi(r.ind, mesh->yend, jz) - - Cs * (Vi(r.ind, mesh->yend + 1, jz) - Vi(r.ind, mesh->yend, jz)); - BoutReal phisheath = 0.5 * (phi(r.ind, mesh->yend, jz) + - phi(r.ind, mesh->yend + 1, jz)); - - // Sheath current - BoutReal phi_te = floor(phisheath / tesheath, 0.0); - BoutReal vesheath = - Cs * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); - - BoutReal jsheath = nesheath * (Cs - vesheath); - - // Electron velocity - Ve(r.ind, mesh->yend + 1, jz) = - 2. * vesheath - Ve(r.ind, mesh->yend, jz); - - // Parallel velocity - Jpar(r.ind, mesh->yend + 1, jz) = + // Neumann conditions + Ne.yup()(r.ind, mesh->yend + 1, jz) = nesheath; + phi.yup()(r.ind, mesh->yend + 1, jz) = phisheath; + Vort.yup()(r.ind, mesh->yend + 1, jz) = Vort(r.ind, mesh->yend, jz); + + // Here zero-gradient Te, heat flux applied later + Te.yup()(r.ind, mesh->yend + 1, jz) = Te(r.ind, mesh->yend, jz); + Ti.yup()(r.ind, mesh->yend + 1, jz) = Ti(r.ind, mesh->yend, jz); + + Pe.yup()(r.ind, mesh->yend + 1, jz) = Pe(r.ind, mesh->yend, jz); + Pelim.yup()(r.ind, mesh->yend + 1, jz) = Pelim(r.ind, mesh->yend, jz); + Pi.yup()(r.ind, mesh->yend + 1, jz) = Pi(r.ind, mesh->yend, jz); + Pilim.yup()(r.ind, mesh->yend + 1, jz) = Pilim(r.ind, mesh->yend, jz); + + // Dirichlet conditions + Vi.yup()(r.ind, mesh->yend + 1, jz) = 2. * visheath - Vi(r.ind, mesh->yend, jz); + Ve.yup()(r.ind, mesh->yend + 1, jz) = 2. * vesheath - Ve(r.ind, mesh->yend, jz); + Jpar.yup()(r.ind, mesh->yend + 1, jz) = 2. * jsheath - Jpar(r.ind, mesh->yend, jz); - - if (currents && !finite(Jpar(r.ind, mesh->yend + 1, jz))) { - output.write("JPAR: %d, %d: %e, %e, %e, %e\n", r.ind, jz, jsheath, - vesheath, Cs, nesheath); - output.write(" -> %e, %e, %e\n", Ne(r.ind, mesh->yend, jz), - Ne(r.ind, mesh->yend + 1, jz), g); - exit(1); - } - - // Constant gradient on other cells - for (int jy = mesh->yend + 2; jy < mesh->LocalNy; jy++) { - Vi(r.ind, jy, jz) = - 2. * Vi(r.ind, jy - 1, jz) - Vi(r.ind, jy - 2, jz); - Ve(r.ind, jy, jz) = - 2. * Ve(r.ind, jy - 1, jz) - Ve(r.ind, jy - 2, jz); - NVi(r.ind, jy, jz) = - 2. * NVi(r.ind, jy - 1, jz) - NVi(r.ind, jy - 2, jz); - - Ne(r.ind, jy, jz) = - 2. * Ne(r.ind, jy - 1, jz) - Ne(r.ind, jy - 2, jz); - Te(r.ind, jy, jz) = - 2. * Te(r.ind, jy - 1, jz) - Te(r.ind, jy - 2, jz); - Pe(r.ind, jy, jz) = - 2. * Pe(r.ind, jy - 1, jz) - Pe(r.ind, jy - 2, jz); - - phi(r.ind, jy, jz) = - 2. * phi(r.ind, jy - 1, jz) - phi(r.ind, jy - 2, jz); - Vort(r.ind, jy, jz) = - 2. * Vort(r.ind, jy - 1, jz) - Vort(r.ind, jy - 2, jz); - Jpar(r.ind, jy, jz) = - 2. * Jpar(r.ind, jy - 1, jz) - Jpar(r.ind, jy - 2, jz); - } + NVi.yup()(r.ind, mesh->yend + 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); } } break; @@ -2069,10 +1930,11 @@ int Hermes::rhs(BoutReal t) { for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { // Zero-gradient density - BoutReal nesheath = 0.5 * (3. * Ne(r.ind, mesh->yend, jz) - - Ne(r.ind, mesh->yend - 1, jz)); - if (nesheath < nesheath_floor) - nesheath = nesheath_floor; + BoutReal nesheath = + 0.5 + * (3. * Ne(r.ind, mesh->yend, jz) - Ne.ydown()(r.ind, mesh->yend - 1, jz)); + if (nesheath < 0.0) + nesheath = 0.0; // Temperature at the sheath entrance BoutReal tesheath = floor(Te(r.ind, mesh->yend, jz), 0.0); @@ -2084,46 +1946,44 @@ int Hermes::rhs(BoutReal t) { // Ion velocity goes to the sound speed BoutReal visheath = sqrt(tesheath + tisheath); // Sound speed outwards - if (Vi(r.ind, mesh->yend, jz) > visheath) { + if (sheath_allow_supersonic && (Vi(r.ind, mesh->yend, jz) > visheath)) { // If plasma is faster, go to plasma velocity visheath = Vi(r.ind, mesh->yend, jz); } // Sheath current - BoutReal phi_te = - floor(phisheath / Telim(r.ind, mesh->yend, jz), 0.0); + BoutReal phi_te = floor(phisheath / Telim(r.ind, mesh->yend, jz), 0.0); BoutReal vesheath = sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); // J = n*(Vi - Ve) BoutReal jsheath = nesheath * (visheath - vesheath); - if (nesheath <= nesheath_floor) { + if (nesheath < 1e-10) { vesheath = visheath; jsheath = 0.0; } // Apply boundary condition half-way between cells - for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { - // Neumann conditions - phi(r.ind, jy, jz) = phisheath; - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->yend, jz); - - // Here zero-gradient Te, heat flux applied later - Te(r.ind, jy, jz) = Te(r.ind, mesh->yend, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->yend, jz); - - // Dirichlet conditions - Ne(r.ind, jy, jz) = 2. * nesheath - Ne(r.ind, mesh->yend, jz); - Pe(r.ind, jy, jz) = - 2. * nesheath * tesheath - Pe(r.ind, mesh->yend, jz); - Pi(r.ind, jy, jz) = - 2. * nesheath * tisheath - Pi(r.ind, mesh->yend, jz); - - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->yend, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->yend, jz); - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->yend, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); - } + // Neumann conditions + phi.yup()(r.ind, mesh->yend + 1, jz) = phisheath; + Vort.yup()(r.ind, mesh->yend + 1, jz) = Vort(r.ind, mesh->yend, jz); + + // Here zero-gradient Te, heat flux applied later + Te.yup()(r.ind, mesh->yend + 1, jz) = tesheath; + Ti.yup()(r.ind, mesh->yend + 1, jz) = Ti(r.ind, mesh->yend, jz); + + // Dirichlet conditions + Ne.yup()(r.ind, mesh->yend + 1, jz) = 2. * nesheath - Ne(r.ind, mesh->yend, jz); + Pe.yup()(r.ind, mesh->yend + 1, jz) = + 2. * nesheath * tesheath - Pe(r.ind, mesh->yend, jz); + Pi.yup()(r.ind, mesh->yend + 1, jz) = + 2. * nesheath * tisheath - Pi(r.ind, mesh->yend, jz); + + Vi.yup()(r.ind, mesh->yend + 1, jz) = 2. * visheath - Vi(r.ind, mesh->yend, jz); + Ve.yup()(r.ind, mesh->yend + 1, jz) = 2. * vesheath - Ve(r.ind, mesh->yend, jz); + Jpar.yup()(r.ind, mesh->yend + 1, jz) = + 2. * jsheath - Jpar(r.ind, mesh->yend, jz); + NVi.yup()(r.ind, mesh->yend + 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); } } break; @@ -2132,8 +1992,9 @@ int Hermes::rhs(BoutReal t) { for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { // Zero-gradient density - BoutReal nesheath = 0.5 * (3. * Ne(r.ind, mesh->yend, jz) - - Ne(r.ind, mesh->yend - 1, jz)); + BoutReal nesheath = + 0.5 + * (3. * Ne(r.ind, mesh->yend, jz) - Ne.ydown()(r.ind, mesh->yend - 1, jz)); if (nesheath < 0.0) { nesheath = 0.0; } @@ -2148,7 +2009,7 @@ int Hermes::rhs(BoutReal t) { // Ion velocity goes to the sound speed BoutReal visheath = sqrt(tesheath + tisheath); // Sound speed outwards - if (Vi(r.ind, mesh->yend, jz) > visheath) { + if (sheath_allow_supersonic && (Vi(r.ind, mesh->yend, jz) > visheath)) { // If plasma is faster, go to plasma velocity visheath = Vi(r.ind, mesh->yend, jz); } @@ -2157,177 +2018,273 @@ int Hermes::rhs(BoutReal t) { BoutReal vesheath = visheath; // Apply boundary condition half-way between cells - for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { - Ne(r.ind, jy, jz) = 2. * nesheath - Ne(r.ind, mesh->yend, jz); - phi(r.ind, jy, jz) = 2. * phisheath - phi(r.ind, mesh->yend, jz); - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->yend, jz); + Ne.yup()(r.ind, mesh->yend + 1, jz) = 2. * nesheath - Ne(r.ind, mesh->yend, jz); + phi.yup()(r.ind, mesh->yend + 1, jz) = + 2. * phisheath - phi(r.ind, mesh->yend, jz); + Vort.yup()(r.ind, mesh->yend + 1, jz) = Vort(r.ind, mesh->yend, jz); + + // Here zero-gradient Te, heat flux applied later + Te.yup()(r.ind, mesh->yend + 1, jz) = Te(r.ind, mesh->yend, jz); + Ti.yup()(r.ind, mesh->yend + 1, jz) = Ti(r.ind, mesh->yend, jz); + + Pe.yup()(r.ind, mesh->yend + 1, jz) = Pe(r.ind, mesh->yend, jz); + Pi.yup()(r.ind, mesh->yend + 1, jz) = Pi(r.ind, mesh->yend, jz); + + // Dirichlet conditions + Vi.yup()(r.ind, mesh->yend + 1, jz) = 2. * visheath - Vi(r.ind, mesh->yend, jz); + Ve.yup()(r.ind, mesh->yend + 1, jz) = 2. * vesheath - Ve(r.ind, mesh->yend, jz); + Jpar.yup()(r.ind, mesh->yend + 1, jz) = -Jpar(r.ind, mesh->yend, jz); + NVi.yup()(r.ind, mesh->yend + 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); + } + } + break; + } + } + } else { + // sheath_yup == false + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Zero-gradient Te + Te.yup()(r.ind, mesh->yend + 1, jz) = Te(r.ind, mesh->yend, jz); + Telim.yup()(r.ind, mesh->yend + 1, jz) = Telim(r.ind, mesh->yend, jz); + + Ti.yup()(r.ind, mesh->yend + 1, jz) = Ti(r.ind, mesh->yend, jz); + Tilim.yup()(r.ind, mesh->yend + 1, jz) = Tilim(r.ind, mesh->yend, jz); + + Pe.yup()(r.ind, mesh->yend + 1, jz) = + Ne.yup()(r.ind, mesh->yend + 1, jz) * Te.yup()(r.ind, mesh->yend + 1, jz); + Pelim.yup()(r.ind, mesh->yend + 1, jz) = Nelim.yup()(r.ind, mesh->yend + 1, jz) + * Telim.yup()(r.ind, mesh->yend + 1, jz); + + Pi.yup()(r.ind, mesh->yend + 1, jz) = + Ne.yup()(r.ind, mesh->yend + 1, jz) * Ti.yup()(r.ind, mesh->yend + 1, jz); + Pilim.yup()(r.ind, mesh->yend + 1, jz) = Nelim.yup()(r.ind, mesh->yend + 1, jz) + * Tilim.yup()(r.ind, mesh->yend + 1, jz); + + // No flows + Vi.yup()(r.ind, mesh->yend + 1, jz) = -Vi(r.ind, mesh->yend, jz); + NVi.yup()(r.ind, mesh->yend + 1, jz) = -NVi(r.ind, mesh->yend, jz); + Ve.yup()(r.ind, mesh->yend + 1, jz) = -Ve(r.ind, mesh->yend, jz); + Jpar.yup()(r.ind, mesh->yend + 1, jz) = -Jpar(r.ind, mesh->yend, jz); + } + } + } + + if (parallel_sheaths) { + switch (par_sheath_model) { + case 0: { + for (const auto& bndry_par : mesh->getBoundariesPar()) { + for (bndry_par->first(); !bndry_par->isDone(); bndry_par->next()) { + int x = bndry_par->x; + int y = bndry_par->y; + int z = bndry_par->z; + // output.write("x: {},y: {},z: {}\n", x,y,z); + if (bndry_par->dir == 1) { + // Zero-gradient density + BoutReal nesheath = floor(Ne(x, y, z), 0.0); + + // Temperature at the sheath entrance + BoutReal tesheath = floor(Te(x, y, z), 0.0); + BoutReal tisheath = floor(Ti(x, y, z), 0.0); + + // Zero-gradient potential + BoutReal phisheath = phi(x, y, z); + BoutReal visheath = sqrt(tisheath + tesheath); + + if (sheath_allow_supersonic && (Vi(x, y, z) > visheath)) { + // If plasma is faster, go to plasma velocity + visheath = Vi(x, y, z); + } + + // Sheath current + // Note that phi/Te >= 0.0 since for phi < 0 + // vesheath is the electron saturation current + BoutReal phi_te = floor(phisheath / tesheath, 0.0); + + BoutReal vesheath = + sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); + + // J = n*(Vi - Ve) + BoutReal jsheath = nesheath * (visheath - vesheath); + if (nesheath < 1e-10) { + vesheath = visheath; + jsheath = 0.0; + } + + // Neumann conditions + Ne.yup()(x, y + bndry_par->dir, z) = nesheath; + phi.yup()(x, y + bndry_par->dir, z) = phisheath; + Vort.yup()(x, y + bndry_par->dir, z) = Vort(x, y, z); // Here zero-gradient Te, heat flux applied later - Te(r.ind, jy, jz) = Te(r.ind, mesh->yend, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->yend, jz); + Te.yup()(x, y + bndry_par->dir, z) = Te(x, y, z); + Ti.yup()(x, y + bndry_par->dir, z) = Ti(x, y, z); + + Pe.yup()(x, y + bndry_par->dir, z) = Pe(x, y, z); + Pelim.yup()(x, y + bndry_par->dir, z) = Pelim(x, y, z); + Pi.yup()(x, y + bndry_par->dir, z) = Pi(x, y, z); + Pilim.yup()(x, y + bndry_par->dir, z) = Pilim(x, y, z); + + // // Dirichlet conditions + Vi.yup()(x, y + bndry_par->dir, z) = 2. * visheath - Vi(x, y, z); + if (par_sheath_ve) { + Ve.yup()(x, y + bndry_par->dir, z) = 2. * vesheath - Ve(x, y, z); + } + Jpar.yup()(x, y + bndry_par->dir, z) = 2. * jsheath - Jpar(x, y, z); + NVi.yup()(x, y + bndry_par->dir, z) = 2. * nesheath * visheath - NVi(x, y, z); + } else { // backwards + // Zero-gradient density + BoutReal nesheath = floor(Ne(x, y, z), 0.0); + + // Temperature at the sheath entrance + BoutReal tesheath = floor(Te(x, y, z), 0.0); + BoutReal tisheath = floor(Ti(x, y, z), 0.0); + + // Zero-gradient potential + BoutReal phisheath = phi(x, y, z); + BoutReal visheath = -sqrt(tisheath + tesheath); + + if (sheath_allow_supersonic && (Vi(x, y, z) < visheath)) { + // If plasma is faster, go to plasma velocity + visheath = Vi(x, y, z); + } - Pe(r.ind, jy, jz) = Pe(r.ind, mesh->yend, jz); - Pi(r.ind, jy, jz) = Pi(r.ind, mesh->yend, jz); + // Sheath current + // Note that phi/Te >= 0.0 since for phi < 0 + // vesheath is the electron saturation current + BoutReal phi_te = floor(phisheath / tesheath, 0.0); - // Dirichlet conditions - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->yend, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->yend, jz); - Jpar(r.ind, jy, jz) = -Jpar(r.ind, mesh->yend, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); + BoutReal vesheath = + -sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); + + // J = n*(Vi - Ve) + BoutReal jsheath = nesheath * (visheath - vesheath); + if (nesheath < 1e-8) { + vesheath = visheath; + jsheath = 0.0; + } + + // Neumann conditions + Ne.ydown()(x, y + bndry_par->dir, z) = nesheath; + phi.ydown()(x, y + bndry_par->dir, z) = phisheath; + Vort.ydown()(x, y + bndry_par->dir, z) = Vort(x, y, z); + + // Here zero-gradient Te, heat flux applied later + Te.ydown()(x, y + bndry_par->dir, z) = Te(x, y, z); + Ti.ydown()(x, y + bndry_par->dir, z) = Ti(x, y, z); + + Pe.ydown()(x, y + bndry_par->dir, z) = Pe(x, y, z); + Pelim.ydown()(x, y + bndry_par->dir, z) = Pelim(x, y, z); + Pi.ydown()(x, y + bndry_par->dir, z) = Pi(x, y, z); + Pilim.ydown()(x, y + bndry_par->dir, z) = Pilim(x, y, z); + + // // Dirichlet conditions + Vi.ydown()(x, y + bndry_par->dir, z) = 2. * visheath - Vi(x, y, z); + if (par_sheath_ve) { + Ve.ydown()(x, y + bndry_par->dir, z) = 2. * vesheath - Ve(x, y, z); + } + Jpar.ydown()(x, y + bndry_par->dir, z) = 2. * jsheath - Jpar(x, y, z); + NVi.ydown()(x, y + bndry_par->dir, z) = + 2. * nesheath * visheath - NVi(x, y, z); } } } break; } - case 4: { - // Bohm sheath, with free boundary on the density and pressure - // The extrapolation is done using ratios i.e. linear in log(Ne) and log(P) - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Free boundary, linear extrapolation of logarithms - for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { - Ne(r.ind, jy, jz) = SQ(Ne(r.ind, mesh->yend, jz)) / Ne(r.ind, mesh->yend - 1, jz); - Pe(r.ind, jy, jz) = SQ(Pe(r.ind, mesh->yend, jz)) / Pe(r.ind, mesh->yend - 1, jz); - Pi(r.ind, jy, jz) = SQ(Pi(r.ind, mesh->yend, jz)) / Pi(r.ind, mesh->yend - 1, jz); - } - + case 1: { // insulating boundary + for (const auto& bndry_par : mesh->getBoundariesPar()) { + for (bndry_par->first(); !bndry_par->isDone(); bndry_par->next()) { + int x = bndry_par->x; + int y = bndry_par->y; + int z = bndry_par->z; // Zero-gradient density - BoutReal nesheath = 0.5 * (Ne(r.ind, mesh->yend, jz) + - Ne(r.ind, mesh->yend + 1, jz)); - BoutReal tesheath = 0.5 * (Te(r.ind, mesh->yend, jz) + - Pe(r.ind, mesh->yend+1, jz) / Ne(r.ind, mesh->yend+1, jz)); - BoutReal tisheath = 0.5 * (Ti(r.ind, mesh->yend, jz) + - Pi(r.ind, mesh->yend+1, jz) / Ne(r.ind, mesh->yend+1, jz)); + BoutReal nesheath = floor(Ne(x, y, z), 0.0); + + // Temperature at the sheath entrance + BoutReal tesheath = floor(Te(x, y, z), 0.0); + BoutReal tisheath = floor(Ti(x, y, z), 0.0); // Zero-gradient potential - BoutReal phisheath = phi(r.ind, mesh->yend, jz); + BoutReal phisheath = phi(x, y, z); - // Ion velocity goes to the sound speed - BoutReal visheath = sqrt(tesheath + tisheath); // Sound speed outwards + // Ion velocity goes to the sound speed. Note negative since out of the domain + BoutReal visheath = -sqrt(tesheath + tisheath); - if (Vi(r.ind, mesh->yend, jz) > visheath) { + if (sheath_allow_supersonic && (Vi(x, y, z) < visheath)) { // If plasma is faster, go to plasma velocity - visheath = Vi(r.ind, mesh->yend, jz); + visheath = Vi(x, y, z); + } + + if (bndry_par->dir == 1) { + visheath = sqrt(tesheath + tisheath); + + if (sheath_allow_supersonic && (Vi(x, y, z) > visheath)) { + // If plasma is faster, go to plasma velocity + visheath = Vi(x, y, z); + } } // Sheath current - BoutReal phi_te = floor(phisheath / tesheath, 0.0); - BoutReal vesheath = - sqrt(tesheath) * (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te); + // Note that phi/Te >= 0.0 since for phi < 0 + // vesheath is the electron saturation current + BoutReal phi_te = floor(phisheath / Telim(x, y, z), 0.0); + BoutReal vesheath = visheath; + // J = n*(Vi - Ve) BoutReal jsheath = nesheath * (visheath - vesheath); - if (nesheath < 1e-10) { - vesheath = visheath; - jsheath = 0.0; - } - // Apply boundary condition half-way between cells - for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { + if (bndry_par->dir == 1) { + // Apply boundary condition half-way between cells // Neumann conditions - phi(r.ind, jy, jz) = phisheath; - Vort(r.ind, jy, jz) = Vort(r.ind, mesh->yend, jz); + Ne.yup()(x, y + bndry_par->dir, z) = nesheath; + phi.yup()(x, y + bndry_par->dir, z) = phisheath; + Vort.yup()(x, y + bndry_par->dir, z) = Vort(x, y, z); // Here zero-gradient Te, heat flux applied later - // This is so that heat diffusion doesn't remove (or add) additional heat - Te(r.ind, jy, jz) = Te(r.ind, mesh->yend, jz); - Ti(r.ind, jy, jz) = Ti(r.ind, mesh->yend, jz); - - // Dirichlet conditions to set flows - Vi(r.ind, jy, jz) = 2. * visheath - Vi(r.ind, mesh->yend, jz); - Ve(r.ind, jy, jz) = 2. * vesheath - Ve(r.ind, mesh->yend, jz); - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->yend, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); + Te.yup()(x, y + bndry_par->dir, z) = Te(x, y, z); + Ti.yup()(x, y + bndry_par->dir, z) = Ti(x, y, z); + + Pe.yup()(x, y + bndry_par->dir, z) = Pe(x, y, z); + Pelim.yup()(x, y + bndry_par->dir, z) = Pelim(x, y, z); + Pi.yup()(x, y + bndry_par->dir, z) = Pi(x, y, z); + Pilim.yup()(x, y + bndry_par->dir, z) = Pilim(x, y, z); + + // Dirichlet conditions + Vi.yup()(x, y + bndry_par->dir, z) = 2. * visheath - Vi(x, y, z); + Ve.yup()(x, y + bndry_par->dir, z) = 2. * vesheath - Ve(x, y, z); + Jpar.yup()(x, y + bndry_par->dir, z) = 2. * jsheath - Jpar(x, y, z); + NVi.yup()(x, y + bndry_par->dir, z) = 2. * nesheath * visheath - NVi(x, y, z); + } else if (bndry_par->dir == -1) { + // Apply boundary condition half-way between cells + // Neumann conditions + Ne.ydown()(x, y + bndry_par->dir, z) = nesheath; + phi.ydown()(x, y + bndry_par->dir, z) = phisheath; + Vort.ydown()(x, y + bndry_par->dir, z) = Vort(x, y, z); + + // Here zero-gradient Te, heat flux applied later + Te.ydown()(x, y + bndry_par->dir, z) = Te(x, y, z); + Ti.ydown()(x, y + bndry_par->dir, z) = Ti(x, y, z); + + Pe.ydown()(x, y + bndry_par->dir, z) = Pe(x, y, z); + Pelim.ydown()(x, y + bndry_par->dir, z) = Pelim(x, y, z); + Pi.ydown()(x, y + bndry_par->dir, z) = Pi(x, y, z); + Pilim.ydown()(x, y + bndry_par->dir, z) = Pilim(x, y, z); + + // Dirichlet conditions + Vi.ydown()(x, y + bndry_par->dir, z) = 2. * visheath - Vi(x, y, z); + Ve.ydown()(x, y + bndry_par->dir, z) = 2. * vesheath - Ve(x, y, z); + Jpar.ydown()(x, y + bndry_par->dir, z) = 2. * jsheath - Jpar(x, y, z); + NVi.ydown()(x, y + bndry_par->dir, z) = + 2. * nesheath * visheath - NVi(x, y, z); } } } break; } - default: { - throw BoutException("Sheath model %d not implemented", sheath_model); - } } } - ////////////////////////////////////////////////////////////// - // Test boundary conditions for UEDGE comparison - // This applies boundary conditions to Vi, Te and Ti at the targets - // then updates NVi, Pe and Pi boundaries - if (test_boundaries) { - // Lower Y target - int jy = 1; - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Apply Vi = -3e4 m/s (into target) - BoutReal vi_bndry = -3e4 / Cs0; - Vi(r.ind, jy, jz) = 2. * vi_bndry - Vi(r.ind, jy + 1, jz); - - // Apply Te = Ti = 10eV - BoutReal te_bndry = 10. / Tnorm; - BoutReal ti_bndry = 10. / Tnorm; - Te(r.ind, jy, jz) = 2. * te_bndry - Te(r.ind, jy + 1, jz); - Ti(r.ind, jy, jz) = 2. * ti_bndry - Ti(r.ind, jy + 1, jz); - - // Get density at the boundary - BoutReal ne_bndry = 0.5 * (Ne(r.ind, jy, jz) + Ne(r.ind, jy + 1, jz)); - if (ne_bndry < 1e-5) - ne_bndry = 1e-5; - - NVi(r.ind, jy, jz) = 2 * ne_bndry * vi_bndry - NVi(r.ind, jy + 1, jz); - Pe(r.ind, jy, jz) = 2 * ne_bndry * te_bndry - Pe(r.ind, jy + 1, jz); - Pi(r.ind, jy, jz) = 2 * ne_bndry * ti_bndry - Pi(r.ind, jy + 1, jz); - } - } - // Upper Y target - jy = mesh->yend + 1; - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Apply Vi = 3e4 m/s - BoutReal vi_bndry = 3e4 / Cs0; - Vi(r.ind, jy, jz) = 2. * vi_bndry - Vi(r.ind, jy - 1, jz); - - // Apply Te = Ti = 10eV - BoutReal te_bndry = 10. / Tnorm; - BoutReal ti_bndry = 10. / Tnorm; - Te(r.ind, jy, jz) = 2. * te_bndry - Te(r.ind, jy - 1, jz); - Ti(r.ind, jy, jz) = 2. * ti_bndry - Ti(r.ind, jy - 1, jz); - - // Get density at the boundary - BoutReal ne_bndry = 0.5 * (Ne(r.ind, jy, jz) + Ne(r.ind, jy - 1, jz)); - if (ne_bndry < 1e-5) - ne_bndry = 1e-5; - - NVi(r.ind, jy, jz) = 2 * ne_bndry * vi_bndry - NVi(r.ind, jy - 1, jz); - Pe(r.ind, jy, jz) = 2 * ne_bndry * te_bndry - Pe(r.ind, jy - 1, jz); - Pi(r.ind, jy, jz) = 2 * ne_bndry * ti_bndry - Pi(r.ind, jy - 1, jz); - } - } - } - - // Shift from field aligned - - Ne = fromFieldAligned(Ne); - - Te = fromFieldAligned(Te); - Ti = fromFieldAligned(Ti); - Telim = fromFieldAligned(Telim); - Tilim = fromFieldAligned(Tilim); - - Pe = fromFieldAligned(Pe); - Pi = fromFieldAligned(Pi); - Pelim = fromFieldAligned(Pelim); - Pilim = fromFieldAligned(Pilim); - - Ve = fromFieldAligned(Ve); - Vi = fromFieldAligned(Vi); - NVi = fromFieldAligned(NVi); - - phi = fromFieldAligned(phi); - Jpar = fromFieldAligned(Jpar); - Vort = fromFieldAligned(Vort); - - if (!currents) { // No currents, so reset Ve to be equal to Vi // VePsi also reset, so saved in restart file correctly @@ -2337,71 +2294,23 @@ int Hermes::rhs(BoutReal t) { // Ensure that Nelim, Telim and Pelim are calculated in guard cells Nelim = floor(Ne, 1e-5); - Telim = floor(Te, 0.1 / Tnorm); + Telim = floor(Te, 0.001 / Tnorm); Pelim = Telim * Nelim; - Tilim = floor(Ti, 0.1 / Tnorm); + Tilim = floor(Ti, 0.001 / Tnorm); Pilim = Tilim * Nelim; - - ////////////////////////////////////////////////////////////// - // Fix profiles on lower Y in SOL region by applying - // a Dirichlet boundary condition. - // This is to remain consistent with no-flow boundary conditions - // on velocity fields, and to avoid spurious fluxes of energy - // through the boundaries. - // - // A generator is used (sol_ne, sol_te), and where sol_ne gives a negative - // value, no boundary condition is applied. This is to allow - // parts of the domain to be Dirichlet, and parts (e.g. PF) to be Neumann - - if (sol_fix_profiles) { - TRACE("Fix profiles"); - for (RangeIterator idwn = mesh->iterateBndryLowerY(); !idwn.isDone(); - idwn.next()) { - - BoutReal xnorm = mesh->GlobalX(idwn.ind); - BoutReal ynorm = - 0.5 * (mesh->GlobalY(mesh->ystart) + mesh->GlobalY(mesh->ystart - 1)); - - BoutReal neval = sol_ne->generate(xnorm, TWOPI * ynorm, 0.0, t); - BoutReal teval = sol_te->generate(xnorm, TWOPI * ynorm, 0.0, t); - - if ((neval < 0.0) || (teval < 0.0)) - continue; // Skip, leave as previous boundary - - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - Ne(idwn.ind, jy, jz) = 2. * neval - Ne(idwn.ind, mesh->ystart, jz); - Te(idwn.ind, jy, jz) = 2. * teval - Te(idwn.ind, mesh->ystart, jz); - - Pe(idwn.ind, jy, jz) = Ne(idwn.ind, jy, jz) * Te(idwn.ind, jy, jz); - - Telim(idwn.ind, jy, jz) = floor(Te(idwn.ind, jy, jz), 0.1 / Tnorm); - - // Zero gradient on Vi to allow flows through boundary - Vi(idwn.ind, jy, jz) = Vi(idwn.ind, mesh->ystart, jz); - - NVi(idwn.ind, jy, jz) = Vi(idwn.ind, jy, jz) * Ne(idwn.ind, jy, jz); - - // At the boundary, Ve = Vi so no currents - Ve(idwn.ind, jy, jz) = - 2. * Vi(idwn.ind, jy, jz) - Ve(idwn.ind, mesh->ystart, jz); - } - } - } - } ////////////////////////////////////////////////////////////// // Plasma quantities calculated. // At this point we have calculated all boundary conditions, // and auxilliary variables like jpar, phi, psi - + // Now can update the neutral gas model, so that we can use neutral // gas densities and interaction frequencies in the collision processes. // Neutral gas if (neutrals) { TRACE("Neutral gas model update"); - + // Update neutral gas model neutrals->update(Ne, Te, Ti, Vi); } @@ -2409,24 +2318,24 @@ int Hermes::rhs(BoutReal t) { ////////////////////////////////////////////////////////////// // Collisions and stress tensor TRACE("Collisions"); - + // Normalised electron collision time tau_e = (Cs0 / rho_s0) * tau_e0 * pow(Telim, 1.5) / Nelim; // Normalised ion-ion collision time tau_i = (Cs0 / rho_s0) * tau_i0 * pow(Tilim, 1.5) / Nelim; - if (ion_neutral && ( neutrals || (ion_neutral_rate > 0.0))) { + if (ion_neutral && (neutrals || (ion_neutral_rate > 0.0))) { // Include ion-neutral collisions in collision time - + Field3D neutral_rate = neutrals ? neutrals->Fperp : ion_neutral_rate; - + // Add collision frequencies (1/tau_i + neutral rate) tau_i /= 1.0 + tau_i * neutral_rate; } // Collisional damping (normalised) - if (currents && (resistivity || (!electromagnetic && !FiniteElMass))) { + if (resistivity || (!electromagnetic && !FiniteElMass)) { // Need to calculate nu if electrostatic and zero electron mass nu = resistivity_multiply / (1.96 * tau_e * mi_me); @@ -2436,27 +2345,27 @@ int Hermes::rhs(BoutReal t) { * the resistivity at low temperatures (~1eV) * * This assumes a fixed cross-section, independent of energy - * + * */ - BoutReal a0 = PI*SQ(5.29e-11); // Cross-section [m^2] - + BoutReal a0 = PI * SQ(5.29e-11); // Cross-section [m^2] + // Electron thermal speed - Field3D vth_e = sqrt(mi_me*Telim); - + Field3D vth_e = sqrt(mi_me * Telim); + // Electron-neutral collision rate Field3D nu_ne = vth_e * Nnorm * neutrals->getDensity() * a0 * rho_s0; - + // Add collision rate to the electron-ion rate nu += nu_ne; } - if (resistivity_boundary_width > 0 && - ((mesh->getGlobalXIndex(mesh->xstart) - mesh->xstart) - < resistivity_boundary_width)) { + if (resistivity_boundary_width > 0 + && ((mesh->getGlobalXIndex(mesh->xstart) - mesh->xstart) + < resistivity_boundary_width)) { // A region near the boundary with different resistivity - - int imax = mesh->xstart + resistivity_boundary_width - 1 - - (mesh->getGlobalXIndex(mesh->xstart) - mesh->xstart); + + int imax = mesh->xstart + resistivity_boundary_width - 1 + - (mesh->getGlobalXIndex(mesh->xstart) - mesh->xstart); if (imax > mesh->xend) { imax = mesh->xend; } @@ -2472,16 +2381,16 @@ int Hermes::rhs(BoutReal t) { } } } - + // Number of points in outer guard cells int nguard = mesh->LocalNx - mesh->xend - 1; - - if (resistivity_boundary_width > 0 && - (mesh->GlobalNx - nguard - mesh->getGlobalXIndex(mesh->xend) <= - resistivity_boundary_width)) { + + if (resistivity_boundary_width > 0 + && (mesh->GlobalNx - nguard - mesh->getGlobalXIndex(mesh->xend) + <= resistivity_boundary_width)) { // Outer boundary int imin = - mesh->GlobalNx - nguard - resistivity_boundary_width - mesh->getGlobalXIndex(0); + mesh->GlobalNx - nguard - resistivity_boundary_width - mesh->getGlobalXIndex(0); if (imin < mesh->xstart) { imin = mesh->xstart; } @@ -2493,8 +2402,6 @@ int Hermes::rhs(BoutReal t) { } } } - - nu.applyBoundary(t); } if (thermal_conduction || sinks) { @@ -2531,19 +2438,23 @@ int Hermes::rhs(BoutReal t) { // Boundary conditions on heat conduction coefficients for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { - kappa_epar(r.ind, mesh->ystart - 1, jz) = kappa_epar(r.ind, mesh->ystart, jz); - kappa_ipar(r.ind, mesh->ystart - 1, jz) = kappa_ipar(r.ind, mesh->ystart, jz); + kappa_epar(r.ind, mesh->ystart - 1, jz) = kappa_epar(r.ind, mesh->ystart, jz); + kappa_ipar(r.ind, mesh->ystart - 1, jz) = kappa_ipar(r.ind, mesh->ystart, jz); } } - + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { - kappa_epar(r.ind, mesh->yend + 1, jz) = kappa_epar(r.ind, mesh->yend, jz); - kappa_ipar(r.ind, mesh->yend + 1, jz) = kappa_ipar(r.ind, mesh->yend, jz); + kappa_epar(r.ind, mesh->yend + 1, jz) = kappa_epar(r.ind, mesh->yend, jz); + kappa_ipar(r.ind, mesh->yend + 1, jz) = kappa_ipar(r.ind, mesh->yend, jz); } } } + if (currents) { + nu.applyBoundary(t); + } + if (ion_viscosity) { /////////////////////////////////////////////////////////// // Ion stress tensor. Split into @@ -2553,9 +2464,11 @@ int Hermes::rhs(BoutReal t) { // is solved as a parallel diffusion, so is treated separately // All other terms are added to Pi_ciperp, even if they are // not really parallel parts - Field3D Tifree = copy(Ti); Tifree.applyBoundary("free_o2"); + if (fci_transform) { + mesh->communicate(Tifree, Pilim, Tilim, coord->Bxy); + } // For nonlinear terms, need to evaluate qipar and qi squared Field3D qipar = -kappa_ipar * Grad_par(Tifree); @@ -2566,39 +2479,53 @@ int Hermes::rhs(BoutReal t) { // Square of total heat flux, parallel and perpendicular // The first Pi term cancels the parallel part of the second term // Doesn't include perpendicular collisional transport + Field3D qisq = - (SQ(kappa_ipar) - SQ((5. / 2) * Pilim)) * SQ(Grad_par(Tifree)) + - SQ((5. / 2) * Pilim) * SQ(Grad(Tifree)); // This term includes a - // parallel component which is - // cancelled in first term + (SQ(kappa_ipar) - SQ((5. / 2) * Pilim)) * SQ(Grad_par(Tifree)) + + SQ((5. / 2) * Pilim) * SQ(Grad(Tifree)); // This term includes a + // parallel component which is + // cancelled in first term + + Field3D phi161Ti = phi + 1.61 * Ti; + mesh->communicate(phi161Ti, Pi); // Perpendicular part from curvature Pi_ciperp = - -0.5 * 0.96 * Pi * tau_i * - (Curlb_B * Grad(phi + 1.61 * Ti) - - Curlb_B * Grad(Pi) / Nelim) // q perpendicular - + - 0.96 * tau_i * (1.42 / B32) * - FV::Div_par_K_Grad_par(B32 * kappa_ipar, Tifree) // q parallel - - - 0.49 * (qipar / Pilim) * - (2.27 * Grad_par(log(Tilim)) - Grad_par(log(Pilim))) + - 0.75 * (0.2 * SQ(qipar) - 0.085 * qisq) / (Pilim * Tilim); + -0.5 * 0.96 * Pi * tau_i + * (fci_curvature(phi161Ti) - fci_curvature(Pi) / Nelim); // q perpendicular + // q parallel + + if (fci_transform) { + Coordinates::FieldMetric B32kappa_ipar = B32 * kappa_ipar; + mesh->communicate(B32kappa_ipar, Tifree); + Pi_ciperp += + 0.96 * tau_i * (1.42 / B32) * Div_par_K_Grad_par(B32kappa_ipar, Tifree); + } else { + Pi_ciperp += + 0.96 * tau_i * (1.42 / B32) * FV::Div_par_K_Grad_par(B32 * kappa_ipar, Tifree); + } + + Field3D logTilim = log(Tilim); + Field3D logPilim = log(Pilim); + mesh->communicate(logTilim, logPilim); + Pi_ciperp -= 0.49 * (qipar / Pilim) * (2.27 * Grad_par(logTilim) - Grad_par(logPilim)) + + 0.75 * (0.2 * SQ(qipar) - 0.085 * qisq) / (Pilim * Tilim); + Field3D logB = log(coord->Bxy); + mesh->communicate(Vi, logB); // Parallel part - Pi_cipar = -0.96 * Pi * tau_i * - (2. * Grad_par(Vi) + Vi * Grad_par(log(coord->Bxy))); + Pi_cipar = -0.96 * Pi * tau_i * (2. * Grad_par(Vi) + Vi * Grad_par(logB)); + // Could also be written as: // Pi_cipar = - // 0.96*Pi*tau_i*2.*Grad_par(sqrt(coord->Bxy)*Vi)/sqrt(coord->Bxy); - if (mesh->firstX()) { // First cells in X subject to boundary effects. - for(int i=mesh->xstart; (i <= mesh->xend) && (i < 4); i++) { + for (int i = mesh->xstart; (i <= mesh->xend) && (i < 4); i++) { for (int j = mesh->ystart; j <= mesh->yend; j++) { for (int k = 0; k < mesh->LocalNz; k++) { - Pi_ciperp(i, j, k) *= float(i - mesh->xstart)/4.0; - Pi_cipar(i, j, k) *= float(i - mesh->xstart)/4.0; + Pi_ciperp(i, j, k) *= float(i - mesh->xstart) / 4.0; + Pi_cipar(i, j, k) *= float(i - mesh->xstart) / 4.0; } } } @@ -2616,42 +2543,45 @@ int Hermes::rhs(BoutReal t) { Pi_cipar[i] = SIGN(Pi_cipar[i]) * Pi[i]; } } - + mesh->communicateXZ(Pi_ciperp, Pi_cipar); + if (!fci_transform) { + Pi_ciperp.clearParallelSlices(); + Pi_cipar.clearParallelSlices(); + Pi_ciperp = toFieldAligned(Pi_ciperp); + Pi_cipar = toFieldAligned(Pi_cipar); + } - Pi_ciperp.clearParallelSlices(); - Pi_cipar.clearParallelSlices(); - - Pi_ciperp = toFieldAligned(Pi_ciperp); - Pi_cipar = toFieldAligned(Pi_cipar); { int jy = 1; for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - Pi_ciperp(r.ind, jy, jz) = Pi_ciperp(r.ind, jy + 1, jz); - Pi_cipar(r.ind, jy, jz) = Pi_cipar(r.ind, jy + 1, jz); - } + for (int jz = 0; jz < mesh->LocalNz; jz++) { + Pi_ciperp(r.ind, jy, jz) = Pi_ciperp(r.ind, jy + 1, jz); + Pi_cipar(r.ind, jy, jz) = Pi_cipar(r.ind, jy + 1, jz); + } } jy = mesh->yend + 1; for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - Pi_ciperp(r.ind, jy, jz) = Pi_ciperp(r.ind, jy - 1, jz); - Pi_cipar(r.ind, jy, jz) = Pi_cipar(r.ind, jy - 1, jz); - } + for (int jz = 0; jz < mesh->LocalNz; jz++) { + Pi_ciperp(r.ind, jy, jz) = Pi_ciperp(r.ind, jy - 1, jz); + Pi_cipar(r.ind, jy, jz) = Pi_cipar(r.ind, jy - 1, jz); + } } } - + // Smooth Pi_ciperp along Y - //Pi_ciperp.applyBoundary("neumann_o2"); + // Pi_ciperp.applyBoundary("neumann_o2"); Field3D Pi_ciperp_orig = copy(Pi_ciperp); - for (auto &i : Pi_ciperp.getRegion("RGN_NOBNDRY")) { - Pi_ciperp[i] = 0.5*Pi_ciperp_orig[i] + 0.25*(Pi_ciperp_orig[i.ym()] + Pi_ciperp_orig[i.yp()]); + for (auto& i : Pi_ciperp.getRegion("RGN_NOBNDRY")) { + Pi_ciperp[i] = 0.5 * Pi_ciperp_orig[i] + + 0.25 * (Pi_ciperp_orig[i.ym()] + Pi_ciperp_orig[i.yp()]); + } + if (!fci_transform) { + Pi_ciperp = fromFieldAligned(Pi_ciperp); + Pi_cipar = fromFieldAligned(Pi_cipar); } - Pi_ciperp = fromFieldAligned(Pi_ciperp); - Pi_cipar = fromFieldAligned(Pi_cipar); - mesh->communicateXZ(Pi_ciperp); - + // Apply boundary conditions Pi_ciperp.applyBoundary("neumann_o2"); Pi_cipar.applyBoundary("neumann_o2"); @@ -2659,6 +2589,19 @@ int Hermes::rhs(BoutReal t) { Pi_ci = Pi_cipar + Pi_ciperp; } + /////////////////////////////////////////////////////////// + // Relaxation potential + // + if (relaxation) { + TRACE("relaxation"); + Field3D inv_b2 = 1 / (coord->Bxy * coord->Bxy); + mesh->communicate(inv_b2); + ddt(phi_1) = lambda_0 * lambda_2 + * ((1 / lambda_2 * FV::Div_a_Laplace_perp(inv_b2, phi_1) + + FV::Div_a_Laplace_perp(inv_b2, Pi)) + - Vort); + } + /////////////////////////////////////////////////////////// // Density // This is the electron density equation @@ -2666,28 +2609,56 @@ int Hermes::rhs(BoutReal t) { if (currents) { // ExB drift, only if electric field is evolved + // ddt(Ne) = bracket(Ne, phi, BRACKET_ARAKAWA) * bracket_factor; ddt(Ne) = -Div_n_bxGrad_f_B_XPPM(Ne, phi, ne_bndry_flux, poloidal_flows, - true); // ExB drift + true) + * bracket_factor; // ExB drift } else { ddt(Ne) = 0.0; } // Parallel flow if (parallel_flow) { - if (currents) { - // Parallel wave speed increased to electron sound speed - // since electrostatic & electromagnetic waves are supported - ddt(Ne) -= FV::Div_par(Ne, Ve, max_speed); + if (!fci_transform) { + if (currents) { + // Parallel wave speed increased to electron sound speed + // since electrostatic & electromagnetic waves are supported + ddt(Ne) -= FV::Div_par(Ne, Ve, sqrt(mi_me) * sound_speed); + } else { + // Parallel wave speed is ion sound speed + ddt(Ne) -= FV::Div_par(Ne, Ve, sound_speed); + } } else { - // Parallel wave speed is ion sound speed - ddt(Ne) -= FV::Div_par(Ne, Ve, sound_speed); + Field3D neve = mul_all(Ne, Ve); + mesh->communicate(neve); + // mesh->getParallelTransform().integrateParallelSlices(neve); + + ddt(Ne) -= Div_parP(neve); + // b = Div_parP(neve); + // Skew-symmetric form + // Field3D gparne = Grad_par(Ne); + // Field3D dparve = Div_parP(Ve); + // mesh->communicate(gparne,dparve); + // ddt(Ne) -= 0.5 * (Div_par(neve) + mul_all(Ve,gparne) + mul_all(Ne, dparve)); + // //ddt(Ne) -= 0.5 * (Div_par(neve) + Ve * Grad_par(Ne) + Ne * Div_par(Ve)); + // a = -0.5 * (Div_parP(neve) + Ve * Grad_par(Ne) + Ne * Div_par(Ve)); + // auto* coords = mesh->getCoordinates(); + // for(auto &i : Ne.getRegion("RGN_ALL")) { + // ddt(Ne)[i] -= (Ne.yup()[i.yp()] * Ve.yup()[i.yp()] / coords->Bxy[i.yp()] + // - Ne.ydown()[i.ym()] * Ve.ydown()[i.ym()] / coords->Bxy[i.ym()]) + // * coords->Bxy[i] / (coords->dy[i] * sqrt(coords->g_22[i])); + // } } } if (j_diamag) { // Diamagnetic drift, formulated as a magnetic drift // i.e Grad-B + curvature drift - ddt(Ne) -= j_diamag_scale * FV::Div_f_v(Ne, -Telim * Curlb_B, ne_bndry_flux); + if (!fci_transform) { + ddt(Ne) -= FV::Div_f_v(Ne, -Telim * Curlb_B, ne_bndry_flux); + } else { + ddt(Ne) -= fci_curvature(Pelim); + } } if (ramp_mesh && (t < ramp_timescale)) { @@ -2697,20 +2668,24 @@ int Hermes::rhs(BoutReal t) { if (classical_diffusion) { // Classical perpendicular diffusion // The only term here comes from the resistive drift - Dn = (Telim + Tilim) / (tau_e * mi_me * SQ(coord->Bxy)); - ddt(Ne) += FV::Div_a_Laplace_perp(Dn, Ne); - ddt(Ne) += FV::Div_a_Laplace_perp(Ne / (tau_e * mi_me * SQ(coord->Bxy)), - Ti - 0.5 * Te); + if (fci_transform) { + mesh->communicate(Dn); + Field3D Ne_tauB2 = Ne / (tau_e * mi_me * SQ(coord->Bxy)); + Field3D TiTediff = Ti - 0.5 * Te; + mesh->communicate(Ne_tauB2, TiTediff); + ddt(Ne) += FV::Div_a_Laplace_perp(Dn, Ne); + ddt(Ne) += FV::Div_a_Laplace_perp(Ne_tauB2, TiTediff); + } } if (anomalous_D > 0.0) { - ddt(Ne) += FV::Div_a_Laplace_perp(anomalous_D, DC(Ne)); + ddt(Ne) += FV::Div_a_Laplace_perp(a_d3d, Ne); } // Source - if (adapt_source_n) { + if (adapt_source) { // Add source. Ensure that sink will go to zero as Ne -> 0 - Field2D NeErr = averageY(DC(Ne) - NeTarget); + Field3D NeErr = averageY(DC(Ne) - NeTarget); if (core_sources) { // Sources only in core (periodic Y) domain @@ -2722,13 +2697,15 @@ int Hermes::rhs(BoutReal t) { continue; // Not periodic, so skip for (int y = mesh->ystart; y <= mesh->yend; y++) { - Sn(x, y) -= source_p * NeErr(x, y); - ddt(Sn)(x, y) = -source_i * NeErr(x, y); - - if (sources_positive && Sn(x, y) < 0.0) { - Sn(x, y) = 0.0; - if (ddt(Sn)(x, y) < 0.0) - ddt(Sn)(x, y) = 0.0; + for (int z = 0; z <= mesh->LocalNz; z++) { + Sn(x, y, z) -= source_p * NeErr(x, y, z); + ddt(Sn)(x, y, z) = -source_i * NeErr(x, y, z); + + if (Sn(x, y, z) < 0.0) { + Sn(x, y, z) = 0.0; + if (ddt(Sn)(x, y, z) < 0.0) + ddt(Sn)(x, y, z) = 0.0; + } } } } @@ -2746,13 +2723,18 @@ int Hermes::rhs(BoutReal t) { NeSource *= g11norm; } } - + ddt(Ne) += NeSource; - + if (low_n_diffuse) { // Diffusion which kicks in at very low density, in order to // help prevent negative density regions - ddt(Ne) += FV::Div_par_K_Grad_par(SQ(coord->dy) * coord->g_22 * 1e-4 / Nelim, Ne); + if (fci_transform) { + mesh->communicate(Ne); + ddt(Ne) += Div_par_K_Grad_par(SQ(coord->dy) * coord->g_22 * 1e-4 / Nelim, Ne); + } else { + ddt(Ne) += FV::Div_par_K_Grad_par(SQ(coord->dy) * coord->g_22 * 1e-4 / Nelim, Ne); + } } if (low_n_diffuse_perp) { ddt(Ne) += Div_Perp_Lap_FV_Index(1e-4 / Nelim, Ne, ne_bndry_flux); @@ -2762,6 +2744,20 @@ int Hermes::rhs(BoutReal t) { ddt(Ne) -= ne_hyper_z * SQ(SQ(coord->dz)) * D4DZ4(Ne); } + if (ne_num_diff > 0.0) { + // Numerical perpendicular diffusion + ddt(Ne) += Div_Perp_Lap_FV_Index(ne_num_diff, Ne, ne_bndry_flux); + } + + if (ne_num_hyper > 0.0) { + ddt(Ne) -= ne_num_hyper * (D4DX4_FV_Index(Ne, true) + D4DZ4_Index(Ne)); + } + + if (numdiff > 0.0) { + for (auto& i : Ne.getRegion("RGN_NOBNDRY")) { + ddt(Ne)[i] += numdiff * (Ne.ydown()[i.ym()] - 2. * Ne[i] + Ne.yup()[i.yp()]); + } + } /////////////////////////////////////////////////////////// // Vorticity // This is the current continuity equation @@ -2770,17 +2766,21 @@ int Hermes::rhs(BoutReal t) { ddt(Vort) = 0.0; - if (currents) { + if (currents && evolve_vort) { // Only evolve vorticity if any diamagnetic or parallel currents // are included. if (j_par) { TRACE("Vort:j_par"); // Parallel current - + // This term is central differencing so that it balances the parallel gradient // of the potential in Ohm's law - ddt(Vort) += Div_par(Jpar); + mesh->communicate(Jpar); + // mesh->getParallelTransform().integrateParallelSlices(Jpar); + + ddt(Vort) += Div_parP(Jpar); + a = Div_parP(Jpar); } if (j_diamag) { @@ -2788,31 +2788,56 @@ int Hermes::rhs(BoutReal t) { // Note: This term is central differencing so that it balances // the corresponding compression term in the pressure equation - ddt(Vort) += j_diamag_scale * Div((Pi + Pe) * Curlb_B); + if (!fci_transform) { + ddt(Vort) += Div((Pi + Pe) * Curlb_B); + } else { + ddt(Vort) += fci_curvature(Pi + Pe); + b = fci_curvature(Pi + Pe); + } } // Advection of vorticity by ExB if (boussinesq) { TRACE("Vort:boussinesq"); // Using the Boussinesq approximation + if (!fci_transform) { + ddt(Vort) -= Div_n_bxGrad_f_B_XPPM(0.5 * Vort, phi, vort_bndry_flux, + poloidal_flows, false); + } else { // fci used + if (j_pol_pi) { + ddt(Vort) -= Div_n_bxGrad_f_B_XPPM(0.5 * Vort, phi, vort_bndry_flux, + poloidal_flows, false) + * bracket_factor; + // V_ExB dot Grad(Pi) + Field3D vEdotGradPi = bracket(phi, Pi, BRACKET_ARAKAWA) * bracket_factor; + vEdotGradPi.applyBoundary("free_o2"); + // delp2(phi) term + Field3D DelpPhi_2B2 = 0.5 * Delp2(phi) / SQ(Bxyz); + DelpPhi_2B2.applyBoundary("free_o2"); + + mesh->communicate(vEdotGradPi, DelpPhi_2B2); + + if (!fci_transform) { + ddt(Vort) -= FV::Div_a_Laplace_perp(0.5 / SQ(coord->Bxy), vEdotGradPi); + } else { + Field3D inv_2sqb = 0.5 / SQ(Bxyz); + mesh->communicate(inv_2sqb); + ddt(Vort) -= FV::Div_a_Laplace_perp(inv_2sqb, vEdotGradPi); + } - ddt(Vort) -= Div_n_bxGrad_f_B_XPPM(0.5 * Vort, phi, vort_bndry_flux, - poloidal_flows); - - // V_ExB dot Grad(Pi) - Field3D vEdotGradPi = bracket(phi, Pi, BRACKET_ARAKAWA); - vEdotGradPi.applyBoundary("free_o2"); - // delp2(phi) term - Field3D DelpPhi_2B2 = 0.5 * Delp2(phi) / SQ(coord->Bxy); - DelpPhi_2B2.applyBoundary("free_o2"); - - mesh->communicate(vEdotGradPi, DelpPhi_2B2); - - ddt(Vort) -= FV::Div_a_Laplace_perp(0.5 / SQ(coord->Bxy), vEdotGradPi); - - if (j_pol_terms){ - ddt(Vort) -= Div_n_bxGrad_f_B_XPPM(DelpPhi_2B2, phi + Pi, vort_bndry_flux, - poloidal_flows); + // delp2 phi v_ExB term + ddt(Vort) -= Div_n_bxGrad_f_B_XPPM(DelpPhi_2B2, phi + Pi, vort_bndry_flux, + poloidal_flows) + * bracket_factor; + + } else if (j_pol_simplified) { + // use simplified polarization term from i.e. GBS + ddt(Vort) -= + Div_n_bxGrad_f_B_XPPM(Vort, phi, vort_bndry_flux, poloidal_flows, false) + * bracket_factor; + c = Div_n_bxGrad_f_B_XPPM(Vort, phi, vort_bndry_flux, poloidal_flows, false) + * bracket_factor; + } } } else { @@ -2826,30 +2851,41 @@ int Hermes::rhs(BoutReal t) { if (classical_diffusion) { TRACE("Vort:classical_diffusion"); // Perpendicular viscosity - Field3D mu = 0.3 * Tilim / (tau_i * SQ(coord->Bxy)); + Field3D tilim_3 = 0.3 * Tilim; + Field3D tauisqB = tau_i * SQ(coord->Bxy); + mesh->communicate(tilim_3, tauisqB); + Field3D mu = div_all(tilim_3, tauisqB); + if (fci_transform) { + mesh->communicate(mu); + } ddt(Vort) += FV::Div_a_Laplace_perp(mu, Vort); + d = FV::Div_a_Laplace_perp(mu, Vort); } if (ion_viscosity) { TRACE("Vort:ion_viscosity"); // Ion collisional viscosity. // Contains poloidal viscosity - - ddt(Vort) += Div(0.5 * Pi_ci * Curlb_B) - - Div_n_bxGrad_f_B_XPPM(1. / 3, Pi_ci, vort_bndry_flux); + // if(fci_transform){ + // throw BoutException("Ion viscosity not implemented for FCI yet\n"); + // } + // Vector3D Pi_ciCb_B_2 = 0.5 * Pi_ci * Curlb_B; + // mesh->communicate(Pi_ciCb_B_2); + ddt(Vort) += 0.5 * fci_curvature(Pi_ci) + - Div_n_bxGrad_f_B_XPPM(1. / 3, Pi_ci, vort_bndry_flux); } if (anomalous_nu > 0.0) { TRACE("Vort:anomalous_nu"); // Perpendicular anomalous momentum diffusion - ddt(Vort) += FV::Div_a_Laplace_perp(anomalous_nu, DC(Vort)); + ddt(Vort) += FV::Div_a_Laplace_perp(a_nu3d, Vort); } - + if (ion_neutral_rate > 0.0) { // Sink of vorticity due to ion-neutral friction ddt(Vort) -= ion_neutral_rate * Vort; } - + if (x_hyper_viscos > 0) { // Form of hyper-viscosity to suppress zig-zags in X ddt(Vort) -= x_hyper_viscos * D4DX4_FV_Index(Vort); @@ -2864,15 +2900,34 @@ int Hermes::rhs(BoutReal t) { } if (vort_dissipation) { - // Adds dissipation term like in other equations + if (!fci_transform) { + // Adds dissipation term like in other equations + // Maximum speed either electron sound speed or Alfven speed + Field3D max_speed = Bnorm * coord->Bxy + / sqrt(SI::mu0 * AA * SI::Mp * Nnorm * Nelim) + / Cs0; // Alfven speed (normalised by Cs0) + Field3D elec_sound = sqrt(mi_me) * sound_speed; // Electron sound speed + for (auto& i : max_speed.getRegion("RGN_ALL")) { + if (elec_sound[i] > max_speed[i]) { + max_speed[i] = elec_sound[i]; + } - ddt(Vort) -= FV::Div_par(Vort, 0.0, max_speed); + // Limit to 100x reference sound speed or light speed + BoutReal lim = BOUTMIN(100., 3e8 / Cs0); + if (max_speed[i] > lim) { + max_speed[i] = lim; + } + } + ddt(Vort) -= FV::Div_par(Vort, 0.0, max_speed); + } else { + ddt(Vort) += SQ(coord->dy) * D2DY2(Vort); + } } - if (phi_dissipation) { - // Adds dissipation term like in other equations, but depending on gradient of potential - // Note: Doesn't seem to need faster than sound speed - ddt(Vort) -= FV::Div_par(-phi, 0.0, sound_speed); + // Adds dissipation term like in other equations, but depending on gradient of + // potential Note: Doesn't seem to need faster than sound speed + ddt(Vort) -= SQ(coord->dy) * D2DY2(phi); + f = SQ(coord->dy) * D2DY2(phi); } } @@ -2889,32 +2944,44 @@ int Hermes::rhs(BoutReal t) { // Evolve VePsi except for electrostatic and zero electron mass case if (resistivity) { - ddt(VePsi) -= mi_me * nu * (Ve - Vi); - // External electric field + ddt(VePsi) -= mi_me * nu * sub_all(Ve, Vi); + // External electric field // ddt(VePsi) += mi_me*nu*(Jpar - Jpar0)/NelimVe; } // Parallel electric field if (j_par) { + if (fci_transform) { + mesh->communicate(phi); + } ddt(VePsi) += mi_me * Grad_parP(phi); } - // Parallel electron pressure + // parallel electron pressure if (pe_par) { + if (fci_transform) { + mesh->communicate(Pelim); + } ddt(VePsi) -= mi_me * Grad_parP(Pelim) / NelimVe; } - + if (thermal_force) { + if (fci_transform) { + mesh->communicate(Te); + } ddt(VePsi) -= mi_me * 0.71 * Grad_parP(Te); } if (electron_viscosity) { // Electron parallel viscosity (Braginskii) Field3D ve_eta = 0.973 * mi_me * tau_e * Telim; - + if (eta_limit_alpha > 0.) { // SOLPS-style flux limiter // Values of alpha ~ 0.5 typically + if (fci_transform) { + mesh->communicate(Ve); + } Field3D q_cl = ve_eta * Grad_par(Ve); // Collisional value Field3D q_fl = eta_limit_alpha * Pelim * mi_me; // Flux limit @@ -2923,18 +2990,35 @@ int Hermes::rhs(BoutReal t) { mesh->communicate(ve_eta); ve_eta.applyBoundary("neumann"); } - ddt(VePsi) += FV::Div_par_K_Grad_par(ve_eta, Ve); + if (fci_transform) { + mesh->communicate(ve_eta, Ve); + ddt(VePsi) += Div_par_K_Grad_par(ve_eta, Ve); + } else { + ddt(VePsi) += FV::Div_par_K_Grad_par(ve_eta, Ve); + } } - + if (FiniteElMass) { // Finite Electron Mass. Small correction needed to conserve energy - ddt(VePsi) -= Vi * Grad_par(Ve - Vi); // Parallel advection - ddt(VePsi) -= bracket(phi, Ve - Vi, BRACKET_ARAKAWA); // ExB advection + if (!fci_transform) { + ddt(VePsi) -= Vi * Grad_par(Ve - Vi); // Parallel advection + } else { + Field3D vdiff = sub_all(Ve, Vi); + mesh->communicate(vdiff); + ddt(VePsi) -= Vi * Grad_par(vdiff); // Parallel advection + } + Field3D vdiff = sub_all(Ve, Vi); + mesh->communicate(vdiff); + ddt(VePsi) -= + bracket(phi, vdiff, BRACKET_ARAKAWA) * bracket_factor; // ExB advection // Should also have ion polarisation advection here } - + if (numdiff > 0.0) { ddt(VePsi) += sqrt(mi_me) * numdiff * Div_par_diffusion_index(Ve); + // for(auto &i : VePsi.getRegion("RGN_ALL")) { + // ddt(VePsi)[i] += numdiff*(Ve.ydown()[i.ym()] - 2.*Ve[i] + Ve.yup()[i.yp()]); + // } } if (hyper > 0.0) { @@ -2944,10 +3028,38 @@ int Hermes::rhs(BoutReal t) { if (hyperpar > 0.0) { ddt(VePsi) -= hyperpar * FV::D4DY4_Index(Ve - Vi); } - + + if (ve_num_diff > 0.0) { + // Numerical perpendicular diffusion + ddt(VePsi) += Div_Perp_Lap_FV_Index(ve_num_diff, Ve, ne_bndry_flux); + } + if (ve_num_hyper > 0.0) { + ddt(VePsi) -= ve_num_hyper * (D4DX4_FV_Index(Ve, true) + D4DZ4_Index(Ve)); + } + if (vepsi_dissipation) { // Adds dissipation term like in other equations - ddt(VePsi) -= FV::Div_par(Ve - Vi, 0.0, max_speed); + // Maximum speed either electron sound speed or Alfven speed + Field3D max_speed = Bnorm * coord->Bxy / sqrt(SI::mu0 * AA * SI::Mp * Nnorm * Nelim) + / Cs0; // Alfven speed (normalised by Cs0) + Field3D elec_sound = sqrt(mi_me) * sound_speed; // Electron sound speed + for (auto& i : max_speed.getRegion("RGN_NOBNDRY")) { + // Maximum of Alfven or thermal electron speed + if (elec_sound[i] > max_speed[i]) { + max_speed[i] = elec_sound[i]; + } + + // Limit to 100x reference sound speed or light speed + BoutReal lim = BOUTMIN(100., 3e8 / Cs0); + if (max_speed[i] > lim) { + max_speed[i] = lim; + } + } + if (!fci_transform) { + ddt(VePsi) -= FV::Div_par(Ve - Vi, 0.0, max_speed); + } else { + ddt(VePsi) += SQ(coord->dy) * (D2DY2(Ve) - D2DY2(Vi)); + } } } @@ -2957,44 +3069,75 @@ int Hermes::rhs(BoutReal t) { TRACE("Ion velocity"); if (currents) { - // ExB drift, only if electric field calculated - ddt(NVi) = -Div_n_bxGrad_f_B_XPPM(NVi, phi, ne_bndry_flux, - poloidal_flows); // ExB drift + if (fci_transform) { + // ddt(NVi) = bracket(NVi, phi, BRACKET_ARAKAWA) * bracket_factor; + // ExB drift, only if electric field calculated + ddt(NVi) = -Div_n_bxGrad_f_B_XPPM(NVi, phi, ne_bndry_flux, + poloidal_flows) + * bracket_factor; // ExB drift + + } else { + // ExB drift, only if electric field calculated + ddt(NVi) = -Div_n_bxGrad_f_B_XPPM(NVi, phi, ne_bndry_flux, + poloidal_flows); // ExB drift + } } else { ddt(NVi) = 0.0; } if (j_diamag) { // Magnetic drift - ddt(NVi) -= j_diamag_scale - * FV::Div_f_v(NVi, Tilim * Curlb_B, + if (!fci_transform) { + ddt(NVi) -= FV::Div_f_v(NVi, Tilim * Curlb_B, ne_bndry_flux); // Grad-B, curvature drift + } else { + ddt(NVi) -= fci_curvature(NVi * Tilim); + } } + if (!fci_transform) { + ddt(NVi) -= FV::Div_par_fvv(Ne, Vi, sound_speed, false); + } else { + Field3D nvivi = mul_all(NVi, Vi); + mesh->communicate(nvivi); + // mesh->getParallelTransform().integrateParallelSlices(nvivi); - ddt(NVi) -= FV::Div_par_fvv(Ne, Vi, sound_speed, false); //FV::Div_par(NVi, Vi, sound_speed, false); - - // Ignoring polarisation drift for now - if (pe_par) { - ddt(NVi) -= Grad_parP(Pe + Pi); + ddt(NVi) -= Div_parP(nvivi); + // Skew-symmetric form + // ddt(NVi) -= 0.5 * (Div_par(mul_all(NVi, Vi)) + Vi * Grad_par(NVi) + NVi * + // Div_par(Vi)); } - - if (ion_viscosity_par) { - TRACE("NVi:ion viscosity parallel"); - // Poloidal flow damping parallel part - // The parallel part is solved as a diffusion term - ddt(NVi) += 1.28 * sqrtB * - FV::Div_par_K_Grad_par(Pi * tau_i / (coord->Bxy), sqrtB * Vi); + // Ignoring polarisation drift for now + if (pe_par) { + if (!fci_transform) { + ddt(NVi) -= Grad_parP(Pe + Pi); + } else { + Field3D peppi = add_all(Pe, Pi); + mesh->communicate(peppi); + ddt(NVi) -= Grad_parP(peppi); + } } if (ion_viscosity) { TRACE("NVi:ion viscosity"); // Poloidal flow damping + if (fci_transform) { + // The parallel part is solved as a diffusion term + Coordinates::FieldMetric sqrtBVi = sqrtB * Vi; + Coordinates::FieldMetric Pitau_i_B = Pi * tau_i / (coord->Bxy); + mesh->communicate(sqrtBVi, Pitau_i_B); + ddt(NVi) += 1.28 * sqrtB * Div_par_K_Grad_par(Pitau_i_B, sqrtBVi); + } else { + ddt(NVi) += + 1.28 * sqrtB * FV::Div_par_K_Grad_par(Pi * tau_i / (coord->Bxy), sqrtB * Vi); + } if (currents) { // Perpendicular part. B32 = B^{3/2} // This is only included if ExB flow is included - ddt(NVi) -= (2. / 3) * B32 * Grad_par(Pi_ciperp / B32); + Field3D Piciperp_B32 = Pi_ciperp / B32; + mesh->communicate(Piciperp_B32); + ddt(NVi) -= (2. / 3) * B32 * Grad_par(Piciperp_B32); } } @@ -3004,8 +3147,10 @@ int Hermes::rhs(BoutReal t) { } if (numdiff > 0.0) { - ddt(NVi) += numdiff * Div_par_diffusion_index(Vi); - // ddt(NVi) += FV::Div_par_K_Grad_par(SQ(mesh->dy)*mesh->g_22*numdiff, Vi); + for (auto& i : NVi.getRegion("RGN_ALL")) { + ddt(NVi)[i] += numdiff * (Vi.ydown()[i.ym()] - 2. * Vi[i] + Vi.yup()[i.yp()]); + } + // ddt(NVi) += numdiff * Div_par_diffusion_index(NVi); } if (density_inflow) { @@ -3018,20 +3163,23 @@ int Hermes::rhs(BoutReal t) { if (classical_diffusion) { // Using same cross-field drift as in density equation - ddt(NVi) += FV::Div_a_Laplace_perp(Vi * Dn, Ne); - - ddt(NVi) += FV::Div_a_Laplace_perp(NVi / (tau_e * mi_me * SQ(coord->Bxy)), - Ti - 0.5 * Te); + Field3D ViDn = mul_all(Vi, Dn); + mesh->communicate(ViDn); + ddt(NVi) += FV::Div_a_Laplace_perp(ViDn, Ne); + Field3D NVi_tauB2 = NVi / (tau_e * mi_me * SQ(coord->Bxy)); + Field3D TiTediff = Ti - 0.5 * Te; + mesh->communicate(NVi_tauB2, TiTediff); + ddt(NVi) += FV::Div_a_Laplace_perp(NVi_tauB2, TiTediff); } if ((anomalous_D > 0.0) && anomalous_D_nvi) { - ddt(NVi) += FV::Div_a_Laplace_perp(DC(Vi) * anomalous_D, DC(Ne)); + ddt(NVi) += FV::Div_a_Laplace_perp(mul_all(Vi, a_d3d), Ne); } - + if (anomalous_nu > 0.0) { - ddt(NVi) += FV::Div_a_Laplace_perp(DC(Ne) * anomalous_nu, DC(Vi)); + ddt(NVi) += FV::Div_a_Laplace_perp(mul_all(Ne, a_nu3d), Vi); } - + if (hyperpar > 0.0) { ddt(NVi) -= hyperpar * FV::D4DY4_Index(Vi) / mi_me; } @@ -3039,607 +3187,864 @@ int Hermes::rhs(BoutReal t) { if (low_n_diffuse) { // Diffusion which kicks in at very low density, in order to // help prevent negative density regions - ddt(NVi) += FV::Div_par_K_Grad_par(SQ(coord->dy) * coord->g_22 * 1e-4 / Nelim, NVi); + if (fci_transform) { + mesh->communicate(NVi); + ddt(NVi) += Div_par_K_Grad_par(SQ(coord->dy) * coord->g_22 * 1e-4 / Nelim, NVi); + } else { + ddt(NVi) += + FV::Div_par_K_Grad_par(SQ(coord->dy) * coord->g_22 * 1e-4 / Nelim, NVi); + } } if (low_n_diffuse_perp) { ddt(NVi) += Div_Perp_Lap_FV_Index(1e-4 / Nelim, NVi, ne_bndry_flux); } + + if (vi_num_diff > 0.0) { + // Numerical perpendicular diffusion + ddt(NVi) += Div_Perp_Lap_FV_Index(vi_num_diff * Nelim, Vi, ne_bndry_flux); + } } /////////////////////////////////////////////////////////// // Pressure equation TRACE("Electron pressure"); - if (currents) { - // Divergence of heat flux due to ExB advection - ddt(Pe) = - -Div_n_bxGrad_f_B_XPPM(Pe, phi, pe_bndry_flux, poloidal_flows, true); - } else { - ddt(Pe) = 0.0; - } + if (evolve_te) { - if (parallel_flow_p_term) { - // Parallel flow if (currents) { - // Like Ne term, parallel wave speed increased - ddt(Pe) -= FV::Div_par(Pe, Ve, max_speed); + if (fci_transform) { + // ddt(Pe) = bracket(Pe, phi, BRACKET_ARAKAWA) * bracket_factor; + ddt(Pe) = -Div_n_bxGrad_f_B_XPPM(Pe, phi, pe_bndry_flux, poloidal_flows, true) + * bracket_factor; + } else { + // Divergence of heat flux due to ExB advection + ddt(Pe) = -Div_n_bxGrad_f_B_XPPM(Pe, phi, pe_bndry_flux, poloidal_flows, true); + } } else { - ddt(Pe) -= FV::Div_par(Pe, Ve, sound_speed); + ddt(Pe) = 0.0; } - } - if (j_diamag) { // Diamagnetic flow - // Magnetic drift (curvature) divergence. - ddt(Pe) -= - j_diamag_scale * (5. / 3) * FV::Div_f_v(Pe, -Telim * Curlb_B, pe_bndry_flux); + if (parallel_flow_p_term) { + // Parallel flow + if (fci_transform) { + Field3D peve = mul_all(Pe, Ve); + mesh->communicate(peve); + ddt(Pe) -= (5. / 3) * Div_parP(peve); + + ddt(Pe) += (2. / 3) * Ve * Grad_par(Pe); + + // mesh->getParallelTransform().integrateParallelSlices(peve); - // This term energetically balances diamagnetic term - // in the vorticity equation - ddt(Pe) -= j_diamag_scale * (2. / 3) * floor(Pe, 0.0) * (Curlb_B * Grad(phi)); - } + // ddt(Pe) -= Div_parP(peve); + } else { + if (currents) { + ddt(Pe) -= FV::Div_par(Pe, Ve, sqrt(mi_me) * sound_speed); + } else { + ddt(Pe) -= FV::Div_par(Pe, Ve, sound_speed); + } + } + } - // Parallel heat conduction - if (thermal_conduction) { - ddt(Pe) += (2. / 3) * FV::Div_par_K_Grad_par(kappa_epar, Te); - } + if (j_diamag) { // Diamagnetic flow + // Magnetic drift (curvature) divergence. + if (fci_transform) { + ddt(Pe) -= (5. / 3) * fci_curvature(Pe * Telim); + } else { + ddt(Pe) -= (5. / 3) * FV::Div_f_v(Pe, -Telim * Curlb_B, pe_bndry_flux); + } - if (thermal_flux) { - // Parallel heat convection - ddt(Pe) += (2. / 3) * 0.71 * Div_parP(Te * Jpar); - } + // This term energetically balances diamagnetic term + // in the vorticity equation + // ddt(Pe) -= (2. / 3) * Pe * (Curlb_B * Grad(phi)); + ddt(Pe) -= (2. / 3) * Pe * fci_curvature(phi); + } - if (currents && resistivity) { - // Ohmic heating - ddt(Pe) += nu * Jpar * (Jpar - Jpar0) / Nelim; - } + // Parallel heat conduction + if (thermal_conduction) { + if (fci_transform) { + mesh->communicate(kappa_epar, Te); + ddt(Pe) += (2. / 3) * Div_par_K_Grad_par(kappa_epar, Te); + } else { + ddt(Pe) += (2. / 3) * FV::Div_par_K_Grad_par(kappa_epar, Te); + } + } - if (pe_hyper_z > 0.0) { - ddt(Pe) -= pe_hyper_z * SQ(SQ(coord->dz)) * D4DZ4(Pe); - } + if (thermal_flux) { + // Parallel heat convection + if (fci_transform) { + Field3D tejpar = mul_all(Te, Jpar); + mesh->communicate(tejpar); + // mesh->getParallelTransform().integrateParallelSlices(tejpar); - /////////////////////////////////// - // Heat transmission through sheath - // Note: Have to calculate in field-aligned coordinates + ddt(Pe) += (2. / 3) * 0.71 * Div_parP(tejpar); + } else { + ddt(Pe) += (2. / 3) * 0.71 * Div_par(Te * Jpar); + } + } - Field3D Ne_FA = toFieldAligned(Ne); - Field3D Te_FA = toFieldAligned(Te); - Field3D Ti_FA = toFieldAligned(Ti); + if (currents && resistivity) { + // Ohmic heating + ddt(Pe) += nu * Jpar * (Jpar - Jpar0) / Nelim; + } - wall_power = 0.0; // Diagnostic output - if (sheath_yup) { - TRACE("electron sheath yup heat transmission"); + if (pe_hyper_z > 0.0) { + ddt(Pe) -= pe_hyper_z * SQ(SQ(coord->dz)) * D4DZ4(Pe); + } - Field3D sheath_dpe{zeroFrom(Ne_FA)}; // Field aligned + /////////////////////////////////// + // Heat transmission through sheath - switch (sheath_model) { - case 0: - case 2: - case 3: - case 4: { - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Temperature and density at the sheath entrance - BoutReal tesheath = floor( - 0.5 * (Te_FA(r.ind, mesh->yend, jz) + Te_FA(r.ind, mesh->yend + 1, jz)), - 0.0); - BoutReal tisheath = floor( - 0.5 * (Ti_FA(r.ind, mesh->yend, jz) + Ti_FA(r.ind, mesh->yend + 1, jz)), - 0.0); - BoutReal nesheath = floor( - 0.5 * (Ne_FA(r.ind, mesh->yend, jz) + Ne_FA(r.ind, mesh->yend + 1, jz)), - nesheath_floor); - - // Sound speed (normalised units) - BoutReal Cs = sqrt(tesheath + tisheath); - - // Heat flux - BoutReal q = (sheath_gamma_e - 1.5) * tesheath * nesheath * Cs; - - // Multiply by cell area to get power - BoutReal flux = q * (coord->J(r.ind, mesh->yend) + - coord->J(r.ind, mesh->yend + 1)) / - (sqrt(coord->g_22(r.ind, mesh->yend)) + - sqrt(coord->g_22(r.ind, mesh->yend + 1))); - - // Divide by volume of cell, and 2/3 to get pressure - BoutReal power = - flux / (coord->dy(r.ind, mesh->yend) * coord->J(r.ind, mesh->yend)); - sheath_dpe(r.ind, mesh->yend, jz) = -(2. / 3) * power; - wall_power(r.ind, mesh->yend) += power; + wall_power = 0.0; // Diagnostic output + if (sheath_yup) { + TRACE("electron sheath yup heat transmission"); + + switch (sheath_model) { + case 0: + case 2: + case 3: { + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Temperature and density at the sheath entrance + BoutReal tesheath = floor( + 0.5 * (Te(r.ind, mesh->yend, jz) + Te.yup()(r.ind, mesh->yend + 1, jz)), + 0.0); + BoutReal tisheath = floor( + 0.5 * (Ti(r.ind, mesh->yend, jz) + Ti.yup()(r.ind, mesh->yend + 1, jz)), + 0.0); + BoutReal nesheath = floor( + 0.5 * (Ne(r.ind, mesh->yend, jz) + Ne.yup()(r.ind, mesh->yend + 1, jz)), + 0.0); + + // Sound speed (normalised units) + BoutReal Cs = sqrt(tesheath + tisheath); + + // Heat flux + BoutReal q = (sheath_gamma_e - 1.5) * tesheath * nesheath * Cs; + + // Multiply by cell area to get power + BoutReal flux = + q + * (coord->J(r.ind, mesh->yend, jz) + coord->J(r.ind, mesh->yend + 1, jz)) + / (sqrt(coord->g_22(r.ind, mesh->yend, jz)) + + sqrt(coord->g_22(r.ind, mesh->yend + 1, jz))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = + flux + / (coord->dy(r.ind, mesh->yend, jz) * coord->J(r.ind, mesh->yend, jz)); + ddt(Pe)(r.ind, mesh->yend, jz) -= (2. / 3) * power; + wall_power(r.ind, mesh->yend) += power; + } } + break; + } } - break; } - default: { - throw BoutException("sheath_model %d not implemented", sheath_model); + if (sheath_ydown) { + TRACE("electron sheath ydown heat transmission"); + + switch (sheath_model) { + case 0: + case 2: + case 3: { + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Temperature and density at the sheath entrance + BoutReal tesheath = floor(0.5 + * (Te(r.ind, mesh->ystart, jz) + + Te.ydown()(r.ind, mesh->ystart - 1, jz)), + 0.0); + BoutReal tisheath = floor(0.5 + * (Ti(r.ind, mesh->ystart, jz) + + Ti.ydown()(r.ind, mesh->ystart - 1, jz)), + 0.0); + BoutReal nesheath = floor(0.5 + * (Ne(r.ind, mesh->ystart, jz) + + Ne.ydown()(r.ind, mesh->ystart - 1, jz)), + 0.0); + + // Sound speed (normalised units) + BoutReal Cs = sqrt(tesheath + tisheath); + + // Heat flux + BoutReal q = + (sheath_gamma_e - 1.5) * tesheath * nesheath * Cs; // NB: positive + + // Multiply by cell area to get power + BoutReal flux = q + * (coord->J(r.ind, mesh->ystart, jz) + + coord->J(r.ind, mesh->ystart - 1, jz)) + / (sqrt(coord->g_22(r.ind, mesh->ystart, jz)) + + sqrt(coord->g_22(r.ind, mesh->ystart - 1, jz))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = flux + / (coord->dy(r.ind, mesh->ystart, jz) + * coord->J(r.ind, mesh->ystart, jz)); + ddt(Pe)(r.ind, mesh->ystart, jz) -= (2. / 3) * power; + wall_power(r.ind, mesh->ystart) += power; + } + } + break; + } + } } + if (parallel_sheaths) { + sheath_dpe = 0.; + + for (const auto& bndry_par : mesh->getBoundariesPar()) { + for (bndry_par->first(); !bndry_par->isDone(); bndry_par->next()) { + int x = bndry_par->x; + int y = bndry_par->y; + int z = bndry_par->z; + if (bndry_par->dir == 1) { // forwards + // Free gradient of log electron density and temperature + // Limited so that the values don't increase into the sheath + // This ensures that the guard cell values remain positive + // exp( 2*log(N[i]) - log(N[ip]) ) + Ne.yup()(x, y + bndry_par->dir, z) = + limitFree(Ne.ydown()(x, y - 1, z), Ne(x, y, z)); + Te.yup()(x, y + bndry_par->dir, z) = + limitFree(Te.ydown()(x, y - 1, z), Te(x, y, z)); + Pe.yup()(x, y + bndry_par->dir, z) = + limitFree(Pe.ydown()(x, y - 1, z), Pe(x, y, z)); + + // Temperature and density at the sheath entrance + BoutReal tesheath = + floor(0.5 * (Te(x, y, z) + Te.yup()(x, y + bndry_par->dir, z)), 0.0); + BoutReal nesheath = + floor(0.5 * (Ne(x, y, z) + Ne.yup()(x, y + bndry_par->dir, z)), 0.0); + BoutReal vesheath = + floor(0.5 * (Ve(x, y, z) + Ve.yup()(x, y + bndry_par->dir, z)), 0.0); + // BoutReal tisheath = floor( + // 0.5 * (Ti(x, y, z) + Ti.yup()(x, y + bndry_par->dir, + // z)), 0.0); + + // Sound speed (normalised units) + // BoutReal Cs = sqrt(tesheath + tisheath); + + // Heat flux + BoutReal q = (sheath_gamma_e - 1.5) * tesheath * nesheath * vesheath; + // Multiply by cell area to get power + BoutReal flux = + q * (coord->J(x, y, z) + coord->J.yup()(x, y + bndry_par->dir, z)) + / (sqrt(coord->g_22(x, y, z)) + + sqrt(coord->g_22.yup()(x, y + bndry_par->dir, z))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = flux / (coord->dy(x, y, z) * coord->J(x, y, z)); + // ddt(Pe)(x, y, z) -= (2. / 3) * power; + sheath_dpe(x, y, z) -= (2. / 3) * power; + } else { // backwards + // Free gradient of log electron density and temperature + // Limited so that the values don't increase into the sheath + // This ensures that the guard cell values remain positive + // exp( 2*log(N[i]) - log(N[ip]) ) + Ne.ydown()(x, y + bndry_par->dir, z) = + limitFree(Ne.yup()(x, y + 1, z), Ne(x, y, z)); + Te.ydown()(x, y + bndry_par->dir, z) = + limitFree(Te.yup()(x, y + 1, z), Te(x, y, z)); + Pe.ydown()(x, y + bndry_par->dir, z) = + limitFree(Pe.yup()(x, y + 1, z), Pe(x, y, z)); + + // Temperature and density at the sheath entrance + BoutReal tesheath = + floor(0.5 * (Te(x, y, z) + Te.ydown()(x, y + bndry_par->dir, z)), 0.0); + BoutReal nesheath = + floor(0.5 * (Ne(x, y, z) + Ne.ydown()(x, y + bndry_par->dir, z)), 0.0); + BoutReal vesheath = + floor(0.5 * (Ve(x, y, z) + Ve.ydown()(x, y + bndry_par->dir, z)), 0.0); + // BoutReal tisheath = floor( + // 0.5 * (Ti(x, y, z) + Ti.ydown()(x, y + bndry_par->dir, + // z)), 0.0); + + // Sound speed (normalised units) + // BoutReal Cs = -sqrt(tesheath + tisheath); + + // Heat flux + BoutReal q = (sheath_gamma_e - 1.5) * tesheath * nesheath * vesheath; + + // Multiply by cell area to get power + BoutReal flux = + q * (coord->J(x, y, z) + coord->J.ydown()(x, y + bndry_par->dir, z)) + / (sqrt(coord->g_22(x, y, z)) + + sqrt(coord->g_22.ydown()(x, y + bndry_par->dir, z))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = flux / (coord->dy(x, y, z) * coord->J(x, y, z)); + sheath_dpe(x, y, z) -= (2. / 3) * power; + } + } + } + ddt(Pe) += sheath_dpe; } - ddt(Pe) += fromFieldAligned(sheath_dpe); - } - if (sheath_ydown) { - TRACE("electron sheath ydown heat transmission"); - Field3D sheath_dpe{zeroFrom(Te_FA)}; - - switch (sheath_model) { - case 0: - case 2: - case 3: - case 4: { - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Temperature and density at the sheath entrance - BoutReal tesheath = floor(0.5 * (Te_FA(r.ind, mesh->ystart, jz) + - Te_FA(r.ind, mesh->ystart - 1, jz)), - 0.0); - BoutReal tisheath = floor(0.5 * (Ti_FA(r.ind, mesh->ystart, jz) + - Ti_FA(r.ind, mesh->ystart - 1, jz)), - 0.0); - BoutReal nesheath = floor(0.5 * (Ne_FA(r.ind, mesh->ystart, jz) + - Ne_FA(r.ind, mesh->ystart - 1, jz)), - nesheath_floor); - - // Sound speed (normalised units) - BoutReal Cs = sqrt(tesheath + tisheath); - - // Heat flux - BoutReal q = - (sheath_gamma_e - 1.5) * tesheath * nesheath * Cs; // NB: positive - - // Multiply by cell area to get power - BoutReal flux = q * (coord->J(r.ind, mesh->ystart) + - coord->J(r.ind, mesh->ystart - 1)) / - (sqrt(coord->g_22(r.ind, mesh->ystart)) + - sqrt(coord->g_22(r.ind, mesh->ystart - 1))); - - // Divide by volume of cell, and 2/3 to get pressure - BoutReal power = flux / (coord->dy(r.ind, mesh->ystart) * - coord->J(r.ind, mesh->ystart)); - sheath_dpe(r.ind, mesh->ystart, jz) = -(2. / 3) * power; - wall_power(r.ind, mesh->ystart) += power; - } + // Transfer and source terms + if (thermal_force) { + if (fci_transform) { + mesh->communicate(Te); } - break; + ddt(Pe) -= (2. / 3) * 0.71 * Jpar * Grad_parP(Te); } - default: { - throw BoutException("sheath_model %d not implemented", sheath_model); + + if (pe_par_p_term) { + // This term balances energetically the pressure term + // in Ohm's law + if (fci_transform) { + mesh->communicate(Ve); + // mesh->getParallelTransform().integrateParallelSlices(Ve); + } + ddt(Pe) -= (2. / 3) * Pelim * Div_parP(Ve); } + if (ramp_mesh && (t < ramp_timescale)) { + ddt(Pe) += PeTarget / ramp_timescale; } - ddt(Pe) += fromFieldAligned(sheath_dpe); - } - // Transfer and source terms - if (thermal_force) { - ddt(Pe) -= (2. / 3) * 0.71 * Jpar * Grad_parP(Te); - } + ////////////////////// + // Classical diffusion - if (pe_par_p_term) { - // This term balances energetically the pressure term - // in Ohm's law - ddt(Pe) -= (2. / 3) * floor(Pe, 0.0) * Div_par(Ve); - } - if (ramp_mesh && (t < ramp_timescale)) { - ddt(Pe) += PeTarget / ramp_timescale; - } + if (classical_diffusion) { - ////////////////////// - // Classical diffusion + // Combined resistive drift and cross-field heat diffusion + // nu_rho2 = nu_ei * rho_e^2 in normalised units + Field3D nu_rho2 = Telim / (tau_e * mi_me * SQ(coord->Bxy)); + Field3D PePi = add_all(Pe, Pi); + mesh->communicate(nu_rho2); + Field3D nu_rho2Ne = mul_all(nu_rho2, Ne); + mesh->communicate(nu_rho2Ne, Te); + ddt(Pe) += (2. / 3) + * (FV::Div_a_Laplace_perp(nu_rho2, PePi) + + (11. / 12) * FV::Div_a_Laplace_perp(nu_rho2Ne, Te)); + } - if (classical_diffusion) { + ////////////////////// + // Anomalous diffusion - // Combined resistive drift and cross-field heat diffusion - // nu_rho2 = nu_ei * rho_e^2 in normalised units - Field3D nu_rho2 = Telim / (tau_e * mi_me * SQ(coord->Bxy)); + if ((anomalous_D > 0.0) && anomalous_D_pepi) { + ddt(Pe) += FV::Div_a_Laplace_perp(mul_all(a_d3d, Te), Ne); + } + if (anomalous_chi > 0.0) { + ddt(Pe) += (2. / 3) * FV::Div_a_Laplace_perp(mul_all(a_chi3d, Ne), Te); + } - ddt(Pe) += - (2. / 3) * (FV::Div_a_Laplace_perp(nu_rho2, Pe + Pi) + - (11. / 12) * FV::Div_a_Laplace_perp(nu_rho2 * Ne, Te)); - } + ////////////////////// + // Sources - ////////////////////// - // Anomalous diffusion + if (adapt_source) { + // Add source. Ensure that sink will go to zero as Pe -> 0 + Field3D PeErr = averageY(DC(Pe) - PeTarget); - if ((anomalous_D > 0.0) && anomalous_D_pepi) { - ddt(Pe) += FV::Div_a_Laplace_perp(anomalous_D * DC(Te), DC(Ne)); - } - if (anomalous_chi > 0.0) { - ddt(Pe) += (2. / 3) * FV::Div_a_Laplace_perp(anomalous_chi * DC(Ne), DC(Te)); - } + if (core_sources) { + // Sources only in core - ////////////////////// - // Sources + ddt(Spe) = 0.0; + for (int x = mesh->xstart; x <= mesh->xend; x++) { + if (!mesh->periodicY(x)) + continue; // Not periodic, so skip - if (adapt_source_p) { - // Add source. Ensure that sink will go to zero as Pe -> 0 - Field2D PeErr = averageY(DC(Pe) - PeTarget); + for (int y = mesh->ystart; y <= mesh->yend; y++) { + for (int z = 0; z <= mesh->LocalNz; z++) { + Spe(x, y, z) -= source_p * PeErr(x, y, z); + ddt(Spe)(x, y, z) = -source_i * PeErr(x, y, z); - if (core_sources) { - // Sources only in core + if (Spe(x, y, z) < 0.0) { + Spe(x, y, z) = 0.0; + if (ddt(Spe)(x, y, z) < 0.0) + ddt(Spe)(x, y, z) = 0.0; + } + } + } + } - ddt(Spe) = 0.0; - for (int x = mesh->xstart; x <= mesh->xend; x++) { - if (!mesh->periodicY(x)) - continue; // Not periodic, so skip + if (energy_source) { + // Add the same amount of energy to each particle + PeSource = Spe * Nelim / DC(Nelim); + } else { + PeSource = Spe; + } + } else { - for (int y = mesh->ystart; y <= mesh->yend; y++) { - Spe(x, y) -= source_p * PeErr(x, y); - ddt(Spe)(x, y) = -source_i * PeErr(x, y); + Spe -= source_p * PeErr / PeTarget; + ddt(Spe) = -source_i * PeErr; - if (sources_positive && Spe(x, y) < 0.0) { - Spe(x, y) = 0.0; - if (ddt(Spe)(x, y) < 0.0) - ddt(Spe)(x, y) = 0.0; - } + if (energy_source) { + // Add the same amount of energy to each particle + PeSource = Spe * Nelim / DC(Nelim); + } else { + PeSource = Spe * where(Spe, PeTarget, Pe); } } - if (energy_source) { - // Add the same amount of energy to each particle - PeSource = Spe * Nelim / DC(Nelim); - } else { - PeSource = Spe; + if (source_vary_g11) { + PeSource *= g11norm; } - } else { - Spe -= source_p * PeErr / PeTarget; - ddt(Spe) = -source_i * PeErr; + } else { + // Not adapting sources if (energy_source) { // Add the same amount of energy to each particle PeSource = Spe * Nelim / DC(Nelim); + + if (source_vary_g11) { + PeSource *= g11norm; + } } else { - PeSource = Spe * where(Spe, PeTarget, Pe); + // Add the same amount of energy per volume + // If no particle source added, then this can lead to + // a small number of particles with a lot of energy! } } - - if (source_vary_g11) { - PeSource *= g11norm; - } - - } else { - // Not adapting sources - if (energy_source) { - // Add the same amount of energy to each particle - PeSource = Spe * Nelim / DC(Nelim); - - if (source_vary_g11) { - PeSource *= g11norm; - } - } else { - // Add the same amount of energy per volume - // If no particle source added, then this can lead to - // a small number of particles with a lot of energy! - } + ddt(Pe) += PeSource; + } else { + ddt(Pe) = 0.0; } - ddt(Pe) += PeSource; - /////////////////////////////////////////////////////////// // Ion pressure equation // Similar to electron pressure equation TRACE("Ion pressure"); - if (currents) { - // ExB advection - ddt(Pi) = - -Div_n_bxGrad_f_B_XPPM(Pi, phi, pe_bndry_flux, poloidal_flows, true); - } else { - ddt(Pi) = 0.0; - } + if (evolve_ti) { - // Parallel flow - if (parallel_flow_p_term) { - ddt(Pi) -= FV::Div_par(Pi, Vi, sound_speed); - } + if (currents) { + if (fci_transform) { + // ddt(Pi) = bracket(Pi, phi, BRACKET_ARAKAWA) * bracket_factor; + ddt(Pi) = -Div_n_bxGrad_f_B_XPPM(Pi, phi, pe_bndry_flux, poloidal_flows, true) + * bracket_factor; + } else { + // Divergence of heat flux due to ExB advection + ddt(Pi) = -Div_n_bxGrad_f_B_XPPM(Pi, phi, pe_bndry_flux, poloidal_flows, true); + } + } else { + ddt(Pi) = 0.0; + } - if (j_diamag) { // Diamagnetic flow - // Magnetic drift (curvature) divergence - ddt(Pi) -= j_diamag_scale * (5. / 3) * FV::Div_f_v(Pi, Tilim * Curlb_B, pe_bndry_flux); + // Parallel flow + if (parallel_flow_p_term) { + if (fci_transform) { + Field3D pivi = mul_all(Pi, Vi); + mesh->communicate(pivi); + // mesh->getParallelTransform().integrateParallelSlices(pivi); + ddt(Pi) -= (5. / 3) * Div_parP(pivi); + + ddt(Pi) += (2. / 3) * Vi * Grad_par(Pi); + } else { + ddt(Pi) -= FV::Div_par(Pi, Vi, sound_speed); + } + } - // Compression of ExB flow - // These terms energetically balances diamagnetic term - // in the vorticity equation - ddt(Pi) -= j_diamag_scale * (2. / 3) * Pi * (Curlb_B * Grad(phi)); + if (j_diamag) { // Diamagnetic flow + // Magnetic drift (curvature) divergence + if (fci_transform) { + ddt(Pi) -= (5. / 3) * fci_curvature(Pi * Tilim); + } else { + ddt(Pi) -= (5. / 3) * FV::Div_f_v(Pi, Tilim * Curlb_B, pe_bndry_flux); + } - ddt(Pi) += j_diamag_scale * Pi * Div((Pe + Pi) * Curlb_B); - } + // Compression of ExB flow + // These terms energetically balances diamagnetic term + // in the vorticity equation + // ddt(Pi) -= (2. / 3) * Pi * (Curlb_B * Grad(phi)); + ddt(Pi) -= (2. / 3) * Pi * fci_curvature(phi); - if (j_par) { - if (boussinesq) { - ddt(Pi) -= (2. / 3) * Jpar * Grad_parP(Pi); - } else { - ddt(Pi) -= (2. / 3) * Jpar * Grad_parP(Pi) / Nelim; + if (fci_transform) { + // Vector3D pipecb = (Pi + Pe); + // mesh->communicate(pipe); + ddt(Pi) += Pi * fci_curvature(Pi + Pe); + } else { + ddt(Pi) += Pi * Div((Pe + Pi) * Curlb_B); + } } - } - - // Parallel heat conduction - if (thermal_conduction) { - ddt(Pi) += (2. / 3) * FV::Div_par_K_Grad_par(kappa_ipar, Ti); - } - - // Parallel pressure gradients (sound waves) - if (pe_par_p_term) { - // This term balances energetically the pressure term - // in the parallel momentum equation - ddt(Pi) -= (2. / 3) * Pilim * Div_par(Vi); - } - - if (electron_ion_transfer) { - // Electron-ion heat transfer - Wi = (3. / mi_me) * Nelim * (Te - Ti) / tau_e; - ddt(Pi) += (2. / 3) * Wi; - ddt(Pe) -= (2. / 3) * Wi; - } - - ////////////////////// - // Classical diffusion - if (classical_diffusion) { - // Cross-field heat conduction - // kappa_perp = 2 * n * nu_ii * rho_i^2 + if (j_par) { + if (fci_transform) { + mesh->communicate(Pi); + } + if (boussinesq) { + ddt(Pi) -= (2. / 3) * Jpar * Grad_parP(Pi); + } else { + ddt(Pi) -= (2. / 3) * Jpar * Grad_parP(Pi) / Nelim; + } + } - ddt(Pi) += (2. / 3) * FV::Div_a_Laplace_perp( - 2. * Pilim / (SQ(coord->Bxy) * tau_i), Ti); + // Parallel heat conduction + if (thermal_conduction) { + if (fci_transform) { + mesh->communicate(kappa_ipar, Ti); + ddt(Pi) += (2. / 3) * Div_par_K_Grad_par(kappa_ipar, Ti); + } else { + ddt(Pi) += (2. / 3) * FV::Div_par_K_Grad_par(kappa_ipar, Ti); + } + } - // Resistive drift terms + // Parallel pressure gradients (sound waves) + if (pe_par_p_term) { + // This term balances energetically the pressure term + // in the parallel momentum equation + if (fci_transform) { + mesh->communicate(Vi); + // mesh->getParallelTransform().integrateParallelSlices(Vi); + } + ddt(Pi) -= (2. / 3) * Pilim * Div_parP(Vi); + } - // nu_rho2 = (Ti/Te) * nu_ei * rho_e^2 in normalised units - Field3D nu_rho2 = Tilim / (tau_e * mi_me * SQ(coord->Bxy)); + if (electron_ion_transfer) { + // Electron-ion heat transfer + Wi = (3. / mi_me) * Nelim * (Te - Ti) / tau_e; + ddt(Pi) += (2. / 3) * Wi; + ddt(Pe) -= (2. / 3) * Wi; + } - ddt(Pi) += (5. / 3) * - (FV::Div_a_Laplace_perp(nu_rho2, Pe + Pi) - - (3. / 2) * FV::Div_a_Laplace_perp(nu_rho2 * Ne, Te)); + ////////////////////// + // Classical diffusion - // Collisional heating from perpendicular viscosity - // in the vorticity equation + if (classical_diffusion) { + // Cross-field heat conduction + // kappa_perp = 2 * n * nu_ii * rho_i^2 + Field3D Pi_B2tau = 2. * Pilim / (SQ(coord->Bxy) * tau_i); + mesh->communicate(Pi_B2tau); + ddt(Pi) += (2. / 3) * FV::Div_a_Laplace_perp(Pi_B2tau, Ti); + + // Resistive drift terms + + // nu_rho2 = (Ti/Te) * nu_ei * rho_e^2 in normalised units + Field3D nu_rho2 = Tilim / (tau_e * mi_me * SQ(coord->Bxy)); + Field3D PePi = add_all(Pe, Pi); + mesh->communicate(nu_rho2, PePi); + Field3D nu_rho2Ne = mul_all(nu_rho2, Ne); + mesh->communicate(nu_rho2Ne, Te); + ddt(Pi) += (5. / 3) + * (FV::Div_a_Laplace_perp(nu_rho2, PePi) + - (3. / 2) * FV::Div_a_Laplace_perp(nu_rho2Ne, Te)); + + // Collisional heating from perpendicular viscosity + // in the vorticity equation - if (currents) { - Vector3D Grad_perp_vort = Grad(Vort); - Grad_perp_vort.y = 0.0; // Zero parallel component - ddt(Pi) -= (2. / 3) * (3. / 10) * Tilim / (SQ(coord->Bxy) * tau_i) * - (Grad_perp_vort * Grad(phi + Pi)); + if (currents) { + Vector3D Grad_perp_vort = Grad(Vort); + Field3D phiPi = phi + Pi; + mesh->communicate(phiPi); + Grad_perp_vort.y = 0.0; // Zero parallel component + ddt(Pi) -= (2. / 3) * (3. / 10) * Tilim / (SQ(coord->Bxy) * tau_i) + * (Grad_perp_vort * Grad(phiPi)); + } } - } - if (ion_viscosity) { - // Collisional heating due to parallel viscosity - - ddt(Pi) += (2. / 3) * 1.28 * (Pi * tau_i / sqrtB) * Grad_par(sqrtB * Vi) * Div_par(Vi); - - if (currents) { - ddt(Pi) -= (4. / 9) * Pi_ciperp * Div_par(Vi); - //(4. / 9) * Vi * B32 * Grad_par(Pi_ciperp / B32); + if (ion_viscosity) { + // Collisional heating due to parallel viscosity + Field3D sqrtBVi = mul_all(sqrtB, Vi); + mesh->communicate(sqrtBVi, Vi); + ddt(Pi) += + (2. / 3) * 1.28 * (Pi * tau_i / sqrtB) * Grad_par(sqrtBVi) * Div_parP(Vi); - ddt(Pi) -= (2. / 6) * Pi_ci * Curlb_B * Grad(phi + Pi); - ddt(Pi) += (2. / 9) * bracket(Pi_ci, phi + Pi, BRACKET_ARAKAWA); + if (currents) { + ddt(Pi) -= (4. / 9) * Pi_ciperp * Div_parP(Vi); + //(4. / 9) * Vi * B32 * Grad_par(Pi_ciperp / B32); + Field3D phiPi = phi + Pi; + mesh->communicate(phiPi); + ddt(Pi) -= (2. / 6) * Pi_ci * fci_curvature(phiPi); // Curlb_B * Grad(phiPi); + ddt(Pi) += (2. / 9) * bracket(Pi_ci, phi + Pi, BRACKET_ARAKAWA) * bracket_factor; + } } - } - - ////////////////////// - // Anomalous diffusion - if ((anomalous_D > 0.0) && anomalous_D_pepi) { - ddt(Pi) += FV::Div_a_Laplace_perp(anomalous_D * DC(Ti), DC(Ne)); - } + ////////////////////// + // Anomalous diffusion - if (anomalous_chi > 0.0) { - ddt(Pi) += - (2. / 3) * FV::Div_a_Laplace_perp(anomalous_chi * DC(Ne), DC(Ti)); - } + if ((anomalous_D > 0.0) && anomalous_D_pepi) { + ddt(Pi) += FV::Div_a_Laplace_perp(mul_all(a_d3d, Ti), Ne); + } - /////////////////////////////////// - // Heat transmission through sheath + if (anomalous_chi > 0.0) { + ddt(Pi) += (2. / 3) * FV::Div_a_Laplace_perp(mul_all(a_chi3d, Ne), Ti); + } - if (sheath_yup) { - TRACE("ion sheath yup heat transmission"); - - Field3D sheath_dpi{zeroFrom(Te_FA)}; // Field aligned + /////////////////////////////////// + // Heat transmission through sheath - switch (sheath_model) { - case 0: - case 2: - case 3: - case 4: { - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Temperature and density at the sheath entrance - BoutReal tesheath = floor( - 0.5 * (Te_FA(r.ind, mesh->yend, jz) + Te_FA(r.ind, mesh->yend + 1, jz)), - 0.0); - BoutReal tisheath = floor( - 0.5 * (Ti_FA(r.ind, mesh->yend, jz) + Ti_FA(r.ind, mesh->yend + 1, jz)), - 0.0); - BoutReal nesheath = floor( - 0.5 * (Ne_FA(r.ind, mesh->yend, jz) + Ne_FA(r.ind, mesh->yend + 1, jz)), - nesheath_floor); - - // Sound speed (normalised units) - BoutReal Cs = sqrt(tesheath + tisheath); - - // Heat flux - BoutReal q = (sheath_gamma_i - 1.5) * tisheath * nesheath * Cs; - - // Multiply by cell area to get power - BoutReal flux = q * (coord->J(r.ind, mesh->yend) + - coord->J(r.ind, mesh->yend + 1)) / - (sqrt(coord->g_22(r.ind, mesh->yend)) + - sqrt(coord->g_22(r.ind, mesh->yend + 1))); - - // Divide by volume of cell, and 2/3 to get pressure - BoutReal power = - flux / (coord->dy(r.ind, mesh->yend) * coord->J(r.ind, mesh->yend)); - sheath_dpi(r.ind, mesh->yend, jz) = -(2. / 3) * power; - wall_power(r.ind, mesh->yend) += power; + if (sheath_yup) { + TRACE("ion sheath yup heat transmission"); + + switch (sheath_model) { + case 0: + case 2: + case 3: { + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Temperature and density at the sheath entrance + BoutReal tesheath = floor( + 0.5 * (Te(r.ind, mesh->yend, jz) + Te.yup()(r.ind, mesh->yend + 1, jz)), + 0.0); + BoutReal tisheath = floor( + 0.5 * (Ti(r.ind, mesh->yend, jz) + Ti.yup()(r.ind, mesh->yend + 1, jz)), + 0.0); + BoutReal nesheath = floor( + 0.5 * (Ne(r.ind, mesh->yend, jz) + Ne.yup()(r.ind, mesh->yend + 1, jz)), + 0.0); + + // Sound speed (normalised units) + BoutReal Cs = sqrt(tesheath + tisheath); + + // Heat flux + BoutReal q = (sheath_gamma_i - 1.5) * tisheath * nesheath * Cs; + + // Multiply by cell area to get power + BoutReal flux = + q + * (coord->J(r.ind, mesh->yend, jz) + coord->J(r.ind, mesh->yend + 1, jz)) + / (sqrt(coord->g_22(r.ind, mesh->yend, jz)) + + sqrt(coord->g_22(r.ind, mesh->yend + 1, jz))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = + flux + / (coord->dy(r.ind, mesh->yend, jz) * coord->J(r.ind, mesh->yend, jz)); + ddt(Pi)(r.ind, mesh->yend, jz) -= (2. / 3) * power; + wall_power(r.ind, mesh->yend) += power; + } } + break; + } } - break; - } - default: { - throw BoutException("sheath_model %d not implemented", sheath_model); } + if (sheath_ydown) { + TRACE("ion sheath ydown heat transmission"); + + switch (sheath_model) { + case 0: + case 2: + case 3: { + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + // Temperature and density at the sheath entrance + BoutReal tesheath = floor(0.5 + * (Te(r.ind, mesh->ystart, jz) + + Te.ydown()(r.ind, mesh->ystart - 1, jz)), + 0.0); + BoutReal tisheath = floor(0.5 + * (Ti(r.ind, mesh->ystart, jz) + + Ti.ydown()(r.ind, mesh->ystart - 1, jz)), + 0.0); + BoutReal nesheath = floor(0.5 + * (Ne(r.ind, mesh->ystart, jz) + + Ne.ydown()(r.ind, mesh->ystart - 1, jz)), + 0.0); + + // Sound speed (normalised units) + BoutReal Cs = sqrt(tesheath + tisheath); + + // Heat flux (positive) + BoutReal q = (sheath_gamma_i - 1.5) * tisheath * nesheath * Cs; + + // Multiply by cell area to get power + BoutReal flux = q + * (coord->J(r.ind, mesh->ystart, jz) + + coord->J(r.ind, mesh->ystart - 1, jz)) + / (sqrt(coord->g_22(r.ind, mesh->ystart, jz)) + + sqrt(coord->g_22(r.ind, mesh->ystart - 1, jz))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = flux + / (coord->dy(r.ind, mesh->ystart, jz) + * coord->J(r.ind, mesh->ystart, jz)); + ddt(Pi)(r.ind, mesh->ystart, jz) -= (2. / 3) * power; + wall_power(r.ind, mesh->ystart) += power; + } + } + break; + } + } } - ddt(Pi) += fromFieldAligned(sheath_dpi); - } - if (sheath_ydown) { - TRACE("ion sheath ydown heat transmission"); - - Field3D sheath_dpi{zeroFrom(Ti_FA)}; - switch (sheath_model) { - case 0: - case 2: - case 3: - case 4: { - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Temperature and density at the sheath entrance - BoutReal tesheath = floor(0.5 * (Te_FA(r.ind, mesh->ystart, jz) + - Te_FA(r.ind, mesh->ystart - 1, jz)), - 0.0); - BoutReal tisheath = floor(0.5 * (Ti_FA(r.ind, mesh->ystart, jz) + - Ti_FA(r.ind, mesh->ystart - 1, jz)), - 0.0); - BoutReal nesheath = floor(0.5 * (Ne_FA(r.ind, mesh->ystart, jz) + - Ne_FA(r.ind, mesh->ystart - 1, jz)), - nesheath_floor); - - // Sound speed (normalised units) - BoutReal Cs = sqrt(tesheath + tisheath); - - // Heat flux (positive) - BoutReal q = (sheath_gamma_i - 1.5) * tisheath * nesheath * Cs; - - // Multiply by cell area to get power - BoutReal flux = q * (coord->J(r.ind, mesh->ystart) + - coord->J(r.ind, mesh->ystart - 1)) / - (sqrt(coord->g_22(r.ind, mesh->ystart)) + - sqrt(coord->g_22(r.ind, mesh->ystart - 1))); - - // Divide by volume of cell, and 2/3 to get pressure - BoutReal power = flux / (coord->dy(r.ind, mesh->ystart) * - coord->J(r.ind, mesh->ystart)); - sheath_dpi(r.ind, mesh->ystart, jz) = -(2. / 3) * power; - wall_power(r.ind, mesh->ystart) += power; + if (parallel_sheaths) { + sheath_dpi = 0.0; + + for (const auto& bndry_par : mesh->getBoundariesPar()) { + for (bndry_par->first(); !bndry_par->isDone(); bndry_par->next()) { + int x = bndry_par->x; + int y = bndry_par->y; + int z = bndry_par->z; + if (bndry_par->dir == 1) { // forwards + // Free gradient of log electron density and ion temperature + // Limited so that the values don't increase into the sheath + // This ensures that the guard cell values remain positive + // exp( 2*log(N[i]) - log(N[ip]) ) + Ne.yup()(x, y + bndry_par->dir, z) = + limitFree(Ne.ydown()(x, y - 1, z), Ne(x, y, z)); + Ti.yup()(x, y + bndry_par->dir, z) = + limitFree(Ti.ydown()(x, y - 1, z), Ti(x, y, z)); + Pi.yup()(x, y + bndry_par->dir, z) = + limitFree(Pi.ydown()(x, y - 1, z), Pi(x, y, z)); + + // Temperature and density at the sheath entrance + BoutReal tisheath = + floor(0.5 * (Ti(x, y, z) + Ti.yup()(x, y + bndry_par->dir, z)), 0.0); + BoutReal tesheath = + floor(0.5 * (Te(x, y, z) + Te.yup()(x, y + bndry_par->dir, z)), 0.0); + BoutReal nesheath = + floor(0.5 * (Ne(x, y, z) + Ne.yup()(x, y + bndry_par->dir, z)), 0.0); + BoutReal visheath = + floor(0.5 * (Vi(x, y, z) + Vi.yup()(x, y + bndry_par->dir, z)), 0.0); + + // BoutReal tesheath = floor(Te(x,y,z),0.); + // BoutReal tisheath = floor(Te(x,y,z),0.); + // BoutReal nesheath = floor(Ne(x,y,z), 0.); + // Sound speed (normalised units) + BoutReal Cs = sqrt(tesheath + tisheath); + + // Heat flux + BoutReal q = (sheath_gamma_i - 1.5) * tisheath * nesheath * visheath; + + // Multiply by cell area to get power + BoutReal flux = + q * (coord->J(x, y, z) + coord->J.yup()(x, y + bndry_par->dir, z)) + / (sqrt(coord->g_22(x, y, z)) + + sqrt(coord->g_22.yup()(x, y + bndry_par->dir, z))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = flux / (coord->dy(x, y, z) * coord->J(x, y, z)); + sheath_dpi(x, y, z) -= (3. / 2) * power; + + } else { // backwards + // Free gradient of log electron density and temperature + // Limited so that the values don't increase into the sheath + // This ensures that the guard cell values remain positive + // exp( 2*log(N[i]) - log(N[ip]) ) + Ne.ydown()(x, y + bndry_par->dir, z) = + limitFree(Ne.yup()(x, y + 1, z), Ne(x, y, z)); + Ti.ydown()(x, y + bndry_par->dir, z) = + limitFree(Ti.yup()(x, y + 1, z), Ti(x, y, z)); + Pi.ydown()(x, y + bndry_par->dir, z) = + limitFree(Pi.yup()(x, y + 1, z), Pi(x, y, z)); + + // // Temperature and density at the sheath entrance + BoutReal tisheath = + floor(0.5 * (Ti(x, y, z) + Ti.ydown()(x, y + bndry_par->dir, z)), 0.0); + BoutReal tesheath = + floor(0.5 * (Te(x, y, z) + Te.ydown()(x, y + bndry_par->dir, z)), 0.0); + BoutReal nesheath = + floor(0.5 * (Ne(x, y, z) + Ne.ydown()(x, y + bndry_par->dir, z)), 0.0); + BoutReal visheath = + floor(0.5 * (Vi(x, y, z) + Vi.ydown()(x, y + bndry_par->dir, z)), 0.0); + // BoutReal tesheath = floor(Te(x,y,z),0.); + // BoutReal tisheath = floor(Te(x,y,z),0.); + // BoutReal nesheath = floor(Ne(x,y,z), 0.); + + // Sound speed (normalised units) + BoutReal Cs = -sqrt(tesheath + tisheath); + + // Heat flux + BoutReal q = (sheath_gamma_i - 1.5) * tisheath * nesheath * visheath; + + // Multiply by cell area to get power + BoutReal flux = + q * (coord->J(x, y, z) + coord->J.ydown()(x, y + bndry_par->dir, z)) + / (sqrt(coord->g_22(x, y, z)) + + sqrt(coord->g_22.ydown()(x, y + bndry_par->dir, z))); + + // Divide by volume of cell, and 2/3 to get pressure + BoutReal power = flux / (coord->dy(x, y, z) * coord->J(x, y, z)); + sheath_dpi(x, y, z) -= (3. / 2) * power; + } } } - break; - } - default: { - throw BoutException("sheath_model %d not implemented", sheath_model); + ddt(Pi) += sheath_dpi; } - } - - ddt(Pi) += fromFieldAligned(sheath_dpi); - } - ////////////////////// - // Sources + ////////////////////// + // Sources - if (adapt_source_p) { - // Add source. Ensure that sink will go to zero as Pe -> 0 - Field2D PiErr = averageY(DC(Pi) - PiTarget); + if (adapt_source) { + // Add source. Ensure that sink will go to zero as Pe -> 0 + Field3D PiErr = averageY(DC(Pi) - PiTarget); - if (core_sources) { - // Sources only in core + if (core_sources) { + // Sources only in core - ddt(Spi) = 0.0; - for (int x = mesh->xstart; x <= mesh->xend; x++) { - if (!mesh->periodicY(x)) - continue; // Not periodic, so skip + ddt(Spi) = 0.0; + for (int x = mesh->xstart; x <= mesh->xend; x++) { + if (!mesh->periodicY(x)) + continue; // Not periodic, so skip - for (int y = mesh->ystart; y <= mesh->yend; y++) { - Spi(x, y) -= source_p * PiErr(x, y); - ddt(Spi)(x, y) = -source_i * PiErr(x, y); + for (int y = mesh->ystart; y <= mesh->yend; y++) { + for (int z = 0; z <= mesh->LocalNz; z++) { + Spi(x, y, z) -= source_p * PiErr(x, y, z); + ddt(Spi)(x, y, z) = -source_i * PiErr(x, y, z); - if (sources_positive && Spi(x, y) < 0.0) { - Spi(x, y) = 0.0; - if (ddt(Spi)(x, y) < 0.0) - ddt(Spi)(x, y) = 0.0; + if (Spi(x, y, z) < 0.0) { + Spi(x, y, z) = 0.0; + if (ddt(Spi)(x, y, z) < 0.0) + ddt(Spi)(x, y, z) = 0.0; + } + } } } - } - if (energy_source) { - // Add the same amount of energy to each particle - PiSource = Spi * Nelim / DC(Nelim); + if (energy_source) { + // Add the same amount of energy to each particle + PiSource = Spi * Nelim / DC(Nelim); + } else { + PiSource = Spi; + } } else { - PiSource = Spi; + + Spi -= source_p * PiErr / PiTarget; + ddt(Spi) = -source_i * PiErr; + + if (energy_source) { + // Add the same amount of energy to each particle + PiSource = Spi * Nelim / DC(Nelim); + } else { + PiSource = Spi * where(Spi, PiTarget, Pi); + } + } + + if (source_vary_g11) { + PiSource *= g11norm; } - } else { - Spi -= source_p * PiErr / PiTarget; - ddt(Spi) = -source_i * PiErr; + } else { + // Not adapting sources if (energy_source) { // Add the same amount of energy to each particle PiSource = Spi * Nelim / DC(Nelim); + + if (source_vary_g11) { + PiSource *= g11norm; + } + } else { - PiSource = Spi * where(Spi, PiTarget, Pi); + // Add the same amount of energy per volume + // If no particle source added, then this can lead to + // a small number of particles with a lot of energy! } } - - if (source_vary_g11) { - PiSource *= g11norm; - } - - } else { - // Not adapting sources - if (energy_source) { - // Add the same amount of energy to each particle - PiSource = Spi * Nelim / DC(Nelim); + ddt(Pi) += PiSource; - if (source_vary_g11) { - PiSource *= g11norm; - } - - } else { - // Add the same amount of energy per volume - // If no particle source added, then this can lead to - // a small number of particles with a lot of energy! - } + } else { + ddt(Pi) = 0.0; } - - ddt(Pi) += PiSource; - + /////////////////////////////////////////////////////////// // Radial buffer regions for turbulence simulations if (radial_buffers) { /// Radial buffer regions - // Calculate flux Z averages. - // This is used for both inner and outer boundaries - Field2D PeDC = DC(Pe); - Field2D PiDC = DC(Pi); - Field2D NeDC = DC(Ne); - Field2D VortDC = DC(Vort); - Field2D NViDC = DC(NVi); - Field2D VePsiDC = DC(VePsi); - - // Flux surface averages. - // In the core region it can be desirable to damp towards a flux surface average - - // First the plasma density and pressures, which should be approximately - // constant on core flux surfaces - Field2D PeInner = radial_inner_averagey ? averageY(PeDC) : PeDC; - Field2D PiInner = radial_inner_averagey ? averageY(PiDC) : PiDC; - Field2D NeInner = radial_inner_averagey ? averageY(NeDC) : NeDC; - - // Vorticity. Probably usually don't usually want to average this in Y - Field2D VortInner = radial_inner_averagey_vort ? averageY(VortDC) : VortDC; - - // Parallel flow can be damped to zero, a constant value, or allowed to vary in Y - Field2D NViInner = radial_inner_zero_nvi - ? 0.0 - : (radial_inner_averagey_nvi ? averageY(NViDC) : NViDC); + // Calculate flux sZ averages + Field2D PeDC = averageY(DC(Pe)); + Field2D PiDC = averageY(DC(Pi)); + Field2D NeDC = averageY(DC(Ne)); + Field2D VortDC = averageY(DC(Vort)); if ((mesh->getGlobalXIndex(mesh->xstart) - mesh->xstart) < radial_inner_width) { // This processor contains points inside the inner radial boundary - int imax = mesh->xstart + radial_inner_width - 1 - - (mesh->getGlobalXIndex(mesh->xstart) - mesh->xstart); + int imax = mesh->xstart + radial_inner_width - 1 + - (mesh->getGlobalXIndex(mesh->xstart) - mesh->xstart); if (imax > mesh->xend) { imax = mesh->xend; } @@ -3659,25 +4064,24 @@ int Hermes::rhs(BoutReal t) { BoutReal D = radial_buffer_D * (1. - pos / radial_inner_width); for (int j = mesh->ystart; j <= mesh->yend; ++j) { - BoutReal dx = coord->dx(i, j); - BoutReal dx_xp = coord->dx(i + 1, j); - BoutReal J = coord->J(i, j); - BoutReal J_xp = coord->J(i + 1, j); - - // Calculate metric factors for radial fluxes - BoutReal rad_flux_factor = 0.25 * (J + J_xp) * (dx + dx_xp); - BoutReal x_factor = rad_flux_factor / (J * dx); - BoutReal xp_factor = rad_flux_factor / (J_xp * dx_xp); - for (int k = 0; k < ncz; ++k) { + BoutReal dx = coord->dx(i, j, k); + BoutReal dx_xp = coord->dx(i + 1, j, k); + BoutReal J = coord->J(i, j, k); + BoutReal J_xp = coord->J(i + 1, j, k); + + // Calculate metric factors for radial fluxes + BoutReal rad_flux_factor = 0.25 * (J + J_xp) * (dx + dx_xp); + BoutReal x_factor = rad_flux_factor / (J * dx); + BoutReal xp_factor = rad_flux_factor / (J_xp * dx_xp); // Relax towards constant value on flux surface - ddt(Pe)(i, j, k) -= D * (Pe(i, j, k) - PeInner(i, j)); - ddt(Pi)(i, j, k) -= D * (Pi(i, j, k) - PiInner(i, j)); - ddt(Ne)(i, j, k) -= D * (Ne(i, j, k) - NeInner(i, j)); - ddt(Vort)(i, j, k) -= D * (Vort(i, j, k) - VortInner(i, j)); - ddt(NVi)(i, j, k) -= D * (NVi(i, j, k) - NViInner(i, j)); - ddt(VePsi)(i, j, k) -= D * (VePsi(i, j, k) - VePsiDC(i, j)); - + + ddt(Pe)(i, j, k) -= D * (Pe(i, j, k) - PeDC(i, j)); + ddt(Pi)(i, j, k) -= D * (Pi(i, j, k) - PiDC(i, j)); + ddt(Ne)(i, j, k) -= D * (Ne(i, j, k) - NeDC(i, j)); + ddt(Vort)(i, j, k) -= D * (Vort(i, j, k) - VortDC(i, j)); + ddt(NVi)(i, j, k) -= D * NVi(i, j, k); + // Radial fluxes BoutReal f = D * (Ne(i + 1, j, k) - Ne(i, j, k)); ddt(Ne)(i, j, k) += f * x_factor; @@ -3701,12 +4105,11 @@ int Hermes::rhs(BoutReal t) { // Number of points in outer guard cells int nguard = mesh->LocalNx - mesh->xend - 1; - if (mesh->GlobalNx - nguard - mesh->getGlobalXIndex(mesh->xend) <= - radial_outer_width) { + if (mesh->GlobalNx - nguard - mesh->getGlobalXIndex(mesh->xend) + <= radial_outer_width) { // Outer boundary - int imin = - mesh->GlobalNx - nguard - radial_outer_width - mesh->getGlobalXIndex(0); + int imin = mesh->GlobalNx - nguard - radial_outer_width - mesh->getGlobalXIndex(0); if (imin < mesh->xstart) { imin = mesh->xstart; } @@ -3715,33 +4118,30 @@ int Hermes::rhs(BoutReal t) { // position inside the boundary BoutReal pos = - static_cast(mesh->GlobalNx - nguard - mesh->getGlobalXIndex(i)) - - 0.5; + static_cast(mesh->GlobalNx - nguard - mesh->getGlobalXIndex(i)) + - 0.5; // Diffusion coefficient which increases towards the boundary BoutReal D = radial_buffer_D * (1. - pos / radial_outer_width); for (int j = mesh->ystart; j <= mesh->yend; ++j) { - BoutReal dx = coord->dx(i, j); - BoutReal dx_xp = coord->dx(i + 1, j); - BoutReal J = coord->J(i, j); - BoutReal J_xp = coord->J(i + 1, j); + for (int k = 0; k < ncz; ++k) { + BoutReal dx = coord->dx(i, j, k); + BoutReal dx_xp = coord->dx(i + 1, j, k); + BoutReal J = coord->J(i, j, k); + BoutReal J_xp = coord->J(i + 1, j, k); - // Calculate metric factors for radial fluxes - BoutReal rad_flux_factor = 0.25 * (J + J_xp) * (dx + dx_xp); - BoutReal x_factor = rad_flux_factor / (J * dx); - BoutReal xp_factor = rad_flux_factor / (J_xp * dx_xp); + // Calculate metric factors for radial fluxes + BoutReal rad_flux_factor = 0.25 * (J + J_xp) * (dx + dx_xp); + BoutReal x_factor = rad_flux_factor / (J * dx); + BoutReal xp_factor = rad_flux_factor / (J_xp * dx_xp); - for (int k = 0; k < ncz; ++k) { ddt(Pe)(i, j, k) -= D * (Pe(i, j, k) - PeDC(i, j)); ddt(Pi)(i, j, k) -= D * (Pi(i, j, k) - PiDC(i, j)); ddt(Ne)(i, j, k) -= D * (Ne(i, j, k) - NeDC(i, j)); ddt(Vort)(i, j, k) -= D * (Vort(i, j, k) - VortDC(i, j)); - ddt(NVi)(i, j, k) -= D * (NVi(i, j, k) - NViDC(i, j)); - ddt(VePsi)(i, j, k) -= D * (VePsi(i, j, k) - VePsiDC(i, j)); + // ddt(Vort)(i,j,k) -= D*Vort(i,j,k); - // Radial fluxes - BoutReal f = D * (Vort(i + 1, j, k) - Vort(i, j, k)); ddt(Vort)(i, j, k) += f * x_factor; ddt(Vort)(i + 1, j, k) -= f * xp_factor; @@ -3755,7 +4155,7 @@ int Hermes::rhs(BoutReal t) { // Neutral gas if (neutrals) { TRACE("Neutral gas model add sources"); - + // Add sources/sinks to plasma equations ddt(Ne) -= neutrals->S; // Sink of plasma density ddt(NVi) -= neutrals->F; // Plasma momentum @@ -3767,11 +4167,10 @@ int Hermes::rhs(BoutReal t) { if (neutral_friction) { // Vorticity if (boussinesq) { - ddt(Vort) -= FV::Div_a_Laplace_perp( - neutrals->Fperp / (Nelim * SQ(coord->Bxy)), phi); - } else { ddt(Vort) -= - FV::Div_a_Laplace_perp(neutrals->Fperp / SQ(coord->Bxy), phi); + FV::Div_a_Laplace_perp(neutrals->Fperp / (Nelim * SQ(coord->Bxy)), phi); + } else { + ddt(Vort) -= FV::Div_a_Laplace_perp(neutrals->Fperp / SQ(coord->Bxy), phi); } } @@ -3788,24 +4187,24 @@ int Hermes::rhs(BoutReal t) { for (int jz = 0; jz < mesh->LocalNz; jz++) { BoutReal flux_ion = - -0.5 * - (Ne(r.ind, mesh->ystart, jz) + Ne(r.ind, mesh->ystart - 1, jz)) * - 0.5 * (Ve(r.ind, mesh->ystart, jz) + - Ve(r.ind, mesh->ystart - 1, jz)); // Flux through surface - // [m^-2 s^-1], should be - // positive since Ve < - // 0.0 + -0.5 * (Ne(r.ind, mesh->ystart, jz) + Ne(r.ind, mesh->ystart - 1, jz)) * 0.5 + * (Ve(r.ind, mesh->ystart, jz) + + Ve(r.ind, mesh->ystart - 1, jz)); // Flux through surface + // [m^-2 s^-1], should be + // positive since Ve < + // 0.0 // Flow of neutrals inwards - BoutReal flow = frecycle * flux_ion * - (coord->J(r.ind, mesh->ystart) + - coord->J(r.ind, mesh->ystart - 1)) / - (sqrt(coord->g_22(r.ind, mesh->ystart)) + - sqrt(coord->g_22(r.ind, mesh->ystart - 1))); + BoutReal flow = frecycle * flux_ion + * (coord->J(r.ind, mesh->ystart, jz) + + coord->J(r.ind, mesh->ystart - 1, jz)) + / (sqrt(coord->g_22(r.ind, mesh->ystart, jz)) + + sqrt(coord->g_22(r.ind, mesh->ystart - 1, jz))); // Rate of change of neutrals in final cell - BoutReal dndt = flow / (coord->J(r.ind, mesh->ystart) * - coord->dy(r.ind, mesh->ystart)); + BoutReal dndt = + flow + / (coord->J(r.ind, mesh->ystart, jz) * coord->dy(r.ind, mesh->ystart, jz)); // Add mass, momentum and energy to the neutrals @@ -3830,19 +4229,19 @@ int Hermes::rhs(BoutReal t) { for (int jz = 0; jz < mesh->LocalNz; jz++) { // Flux through surface [m^-2 s^-1], should be positive BoutReal flux_ion = - frecycle * 0.5 * - (Ne(r.ind, mesh->yend, jz) + Ne(r.ind, mesh->yend + 1, jz)) * - 0.5 * (Ve(r.ind, mesh->yend, jz) + Ve(r.ind, mesh->yend + 1, jz)); + frecycle * 0.5 * (Ne(r.ind, mesh->yend, jz) + Ne(r.ind, mesh->yend + 1, jz)) + * 0.5 * (Ve(r.ind, mesh->yend, jz) + Ve(r.ind, mesh->yend + 1, jz)); // Flow of neutrals inwards - BoutReal flow = flux_ion * (coord->J(r.ind, mesh->yend) + - coord->J(r.ind, mesh->yend + 1)) / - (sqrt(coord->g_22(r.ind, mesh->yend)) + - sqrt(coord->g_22(r.ind, mesh->yend + 1))); + BoutReal flow = + flux_ion + * (coord->J(r.ind, mesh->yend, jz) + coord->J(r.ind, mesh->yend + 1, jz)) + / (sqrt(coord->g_22(r.ind, mesh->yend, jz)) + + sqrt(coord->g_22(r.ind, mesh->yend + 1, jz))); // Rate of change of neutrals in final cell BoutReal dndt = - flow / (coord->J(r.ind, mesh->yend) * coord->dy(r.ind, mesh->yend)); + flow / (coord->J(r.ind, mesh->yend, jz) * coord->dy(r.ind, mesh->yend, jz)); // Add mass, momentum and energy to the neutrals @@ -3863,7 +4262,7 @@ int Hermes::rhs(BoutReal t) { // Impurities if ((carbon_fraction > 0.0) || impurity_adas) { - + if (carbon_fraction > 0.0) { TRACE("Carbon impurity radiation"); Rzrad = carbon_rad->power(Te * Tnorm, Ne * Nnorm, @@ -3873,47 +4272,45 @@ int Hermes::rhs(BoutReal t) { } if (impurity_adas) { - for (auto &i : Rzrad.getRegion("RGN_NOY")) { + for (auto& i : Rzrad.getRegion("RGN_NOY")) { // Calculate cell centre (C), left (L) and right (R) values - - BoutReal Te_C = Te[i], - Te_L = 0.5 * (Te[i.ym()] + Te[i]), - Te_R = 0.5 * (Te[i] + Te[i.yp()]); - BoutReal Ne_C = Ne[i], - Ne_L = 0.5 * (Ne[i.ym()] + Ne[i]), - Ne_R = 0.5 * (Ne[i] + Ne[i.yp()]); - - BoutReal Rz_L = computeRadiatedPower(*impurity, - Te_L * Tnorm, // electron temperature [eV] - Ne_L * Nnorm, // electron density [m^-3] - fimp * Ne_L * Nnorm, // impurity density [m^-3] - 0.0); // Neutral density [m^-3] - - BoutReal Rz_C = computeRadiatedPower(*impurity, - Te_C * Tnorm, // electron temperature [eV] - Ne_C * Nnorm, // electron density [m^-3] - fimp * Ne_C * Nnorm, // impurity density [m^-3] - 0.0); // Neutral density [m^-3] - - BoutReal Rz_R = computeRadiatedPower(*impurity, - Te_R * Tnorm, // electron temperature [eV] - Ne_R * Nnorm, // electron density [m^-3] - fimp * Ne_R * Nnorm, // impurity density [m^-3] - 0.0); // Neutral density [m^-3] - + + BoutReal Te_C = Te[i], Te_L = 0.5 * (Te[i.ym()] + Te[i]), + Te_R = 0.5 * (Te[i] + Te[i.yp()]); + BoutReal Ne_C = Ne[i], Ne_L = 0.5 * (Ne[i.ym()] + Ne[i]), + Ne_R = 0.5 * (Ne[i] + Ne[i.yp()]); + + BoutReal Rz_L = + computeRadiatedPower(*impurity, + Te_L * Tnorm, // electron temperature [eV] + Ne_L * Nnorm, // electron density [m^-3] + fimp * Ne_L * Nnorm, // impurity density [m^-3] + 0.0); // Neutral density [m^-3] + + BoutReal Rz_C = + computeRadiatedPower(*impurity, + Te_C * Tnorm, // electron temperature [eV] + Ne_C * Nnorm, // electron density [m^-3] + fimp * Ne_C * Nnorm, // impurity density [m^-3] + 0.0); // Neutral density [m^-3] + + BoutReal Rz_R = + computeRadiatedPower(*impurity, + Te_R * Tnorm, // electron temperature [eV] + Ne_R * Nnorm, // electron density [m^-3] + fimp * Ne_R * Nnorm, // impurity density [m^-3] + 0.0); // Neutral density [m^-3] + // Jacobian (Cross-sectional area) - BoutReal J_C = coord->J[i], - J_L = 0.5 * (coord->J[i.ym()] + coord->J[i]), - J_R = 0.5 * (coord->J[i] + coord->J[i.yp()]); - + BoutReal J_C = coord->J[i], J_L = 0.5 * (coord->J[i.ym()] + coord->J[i]), + J_R = 0.5 * (coord->J[i] + coord->J[i.yp()]); + // Simpson's rule, calculate average over cell - Rzrad[i] += (J_L * Rz_L + - 4. * J_C * Rz_C + - J_R * Rz_R) / (6. * J_C); + Rzrad[i] += (J_L * Rz_L + 4. * J_C * Rz_C + J_R * Rz_R) / (6. * J_C); } } - - Rzrad /= SI::qe * Tnorm * Nnorm * Omega_ci; // Normalise + + Rzrad /= SI::qe * Tnorm * Nnorm * Omega_ci; // Normalise ddt(Pe) -= (2. / 3) * Rzrad; } @@ -3941,8 +4338,8 @@ int Hermes::rhs(BoutReal t) { // Sheath dissipation closure Field3D phi_te = floor(phi / Telim, 0.0); - Field3D jsheath = Nelim * sqrt(Telim) * - (1 - (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te)); + Field3D jsheath = + Nelim * sqrt(Telim) * (1 - (sqrt(mi_me) / (2. * sqrt(PI))) * exp(-phi_te)); ddt(Vort) += jsheath * sink_invlpar; } else { @@ -4007,10 +4404,10 @@ int Hermes::rhs(BoutReal t) { * @param[in] delta Not used here */ int Hermes::precon(BoutReal t, BoutReal gamma, BoutReal delta) { - static InvertPar *inv = NULL; + static std::unique_ptr inv{nullptr}; if (!inv) { // Initialise parallel inversion class - inv = InvertPar::Create(); + auto inv = InvertPar::create(); inv->setCoefA(1.0); } if (thermal_conduction) { @@ -4028,13 +4425,69 @@ int Hermes::precon(BoutReal t, BoutReal gamma, BoutReal delta) { return 0; } -const Field3D Hermes::Grad_parP(const Field3D &f) { +const Field3D Hermes::fci_curvature(const Field3D& f) { + // Field3D result = mul_all(bracket(logB, f, BRACKET_ARAKAWA), bracket_factor); + // mesh->communicate(result); + return 2 * bracket(logB, f, BRACKET_ARAKAWA) * bracket_factor; +} + +const Field3D Hermes::Grad_parP(const Field3D& f) { return Grad_par(f); //+ 0.5*beta_e*bracket(psi, f, BRACKET_ARAKAWA); } -const Field3D Hermes::Div_parP(const Field3D &f) { - return Div_par(f); - //+ 0.5*beta_e*coord->Bxy*bracket(psi, f/coord->Bxy, BRACKET_ARAKAWA); +const Field3D Hermes::Div_parP(const Field3D& f) { + auto* coords = mesh->getCoordinates(); + + Field3D result; + result.allocate(); + for (auto& i : f.getRegion("RGN_NOBNDRY")) { + auto yp = i.yp(); + auto ym = i.ym(); + result[i] = + (f.yup()[yp] / coords->Bxy.yup()[yp] - f.ydown()[ym] / coords->Bxy.ydown()[ym]) + * coords->Bxy[i] / (coords->dy[i] * sqrt(coords->g_22[i])); + } + return result; + // return Div_par(f) + 0.5*beta_e*coord->Bxy*bracket(psi, f/coord->Bxy, + // BRACKET_ARAKAWA); +} + +/// Parallel divergence, using integration over projected cells +const Field3D Hermes::Div_par_integrate(const Field3D& f) { + auto* coords = mesh->getCoordinates(); + + Field3D f_B = f / coords->Bxy; + + f_B.splitParallelSlices(); + mesh->getParallelTransform().integrateParallelSlices(f_B); + + // integrateYUpDown replaces all yup/down points, so the boundary conditions + // now need to be applied. If Bxyz has neumann parallel boundary conditions + // then the boundary condition is simpler since f = 0 gives f_B=0 boundary condition. + + /// Loop over the mesh boundary regions + for (const auto& reg : mesh->getBoundariesPar()) { + Field3D& f_B_next = f_B.ynext(reg->dir); + const Field3D& f_next = f.ynext(reg->dir); + const Field3D& B_next = Bxyz.ynext(reg->dir); + + for (reg->first(); !reg->isDone(); reg->next()) { + f_B_next(reg->x, reg->y + reg->dir, reg->z) = + f_next(reg->x, reg->y + reg->dir, reg->z) + / B_next(reg->x, reg->y + reg->dir, reg->z); + } + } + + Field3D result; + result.allocate(); + Coordinates::FieldMetric inv_dy = 1. / (sqrt(coords->g_22) * coords->dy); + // Coordinates *coord = mesh->getCoordinates(); + + for (auto i : result.getRegion("RGN_NOBNDRY")) { + result[i] = Bxyz[i] * (f_B.yup()[i.yp()] - f_B.ydown()[i.ym()]) * 0.5 * inv_dy[i]; + } + + return result; } // Standard main() function diff --git a/hermes-2.hxx b/hermes-2.hxx index d33f4d3..d33d0c9 100644 --- a/hermes-2.hxx +++ b/hermes-2.hxx @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with Hermes. If not, see . - + */ class Hermes; @@ -26,10 +26,10 @@ class Hermes; #include -#include +#include #include #include -#include +#include #include "neutral-model.hxx" @@ -39,11 +39,13 @@ class Hermes; class Hermes : public PhysicsModel { public: virtual ~Hermes() {} + protected: int init(bool restarting); int rhs(BoutReal t); - + int precon(BoutReal t, BoutReal gamma, BoutReal delta); + private: // Equilibrium current Field2D Jpar0; @@ -51,123 +53,152 @@ private: BoutReal nesheath_floor; // Density floor used in sheath boundary conditions // Evolving variables - Field3D Ne; // Electron density - Field3D Pe, Pi; // Electron and Ion pressures - Field3D VePsi; // Combination of Ve and psi - Field3D Vort; // Vorticity - Field3D NVi; // Parallel momentum - + Field3D Ne; // Electron density + Field3D Pe, Pi; // Electron and Ion pressures + Field3D VePsi; // Combination of Ve and psi + Field3D Vort; // Vorticity + Field3D NVi; // Parallel momentum + Field3D phi_1; FieldGroup EvolvingVars; // Auxilliary variables - Field3D Te; // Electron temperature - Field3D Ti; // Ion temperature - Field3D Ve, Vi, Jpar; // Electron and ion parallel velocities - Field3D psi; // Electromagnetic potential (-A_||) - Field3D phi; // Electrostatic potential - + Field3D Te; // Electron temperature + Field3D Ti; // Ion temperature + Field3D Ve, Vi, Jpar; // Electron and ion parallel velocities + Field3D psi; // Electromagnetic potential (-A_||) + Field3D phi; // Electrostatic potential + + // stuff for Pauls thesis (relaxation and geometry) + // Field3D phi_1; // Auxilary potential for relaxation method + Field3D shear; + Field3D curv_g; + Field3D curv_n; + BoutReal lambda_0, lambda_2; + bool relaxation; + Field3D term_pi; + Field3D term_phi; // Debugging + // Limited variables Field3D Telim, Tilim; // Collisional terms Field3D nu, kappa_epar, kappa_ipar, Dn; BoutReal tau_e0, tau_i0; - Field3D tau_e, tau_i; // Collision times for electrons and ions - Field3D Wi; // Energy transfer from electrons to ions - Field3D Pi_ciperp, Pi_cipar, Pi_ci; // Ion collisional stress tensor - BoutReal resistivity_multiply; ///< Factor in front of nu + Field3D tau_e, tau_i; // Collision times for electrons and ions + Field3D Wi; // Energy transfer from electrons to ions + Field3D Pi_ciperp, Pi_cipar, Pi_ci; // Ion collisional stress tensor + BoutReal resistivity_multiply; ///< Factor in front of nu BoutReal flux_limit_alpha; // Flux limiter. < 0 disables BoutReal kappa_limit_alpha; // Heat flux limiter from SOLPS BoutReal eta_limit_alpha; // Momentum flux limiter from SOLPS - + // Neutral gas model - NeutralModel *neutrals; // Handles evolution of neutral gas + NeutralModel* neutrals; // Handles evolution of neutral gas bool neutral_friction; - BoutReal frecycle; // Recycling fraction + BoutReal frecycle; // Recycling fraction BoutReal ion_neutral_rate; // Fixed ion-neutral collision rate - + // Impurity radiation BoutReal fimp; // Impurity fraction (of Ne) bool impurity_adas; // True if using ImpuritySpecies, false if using - ImpuritySpecies *impurity; // Atomicpp impurity - + ImpuritySpecies* impurity; // Atomicpp impurity + BoutReal carbon_fraction; Field3D Rzrad; // Radiated power - RadiatedPower *carbon_rad; // Carbon cooling curve - + RadiatedPower* carbon_rad; // Carbon cooling curve + // Switches - bool evolve_plasma; // Should plasma be evolved? - + bool evolve_plasma; // Should plasma be evolved? + bool show_timesteps; // Show intermediate timesteps? + bool evolve_te; // Evolve electron temperature? + bool evolve_ti; // Evolve ion temperature? + bool evolve_vort; // Evolve vorticity? + bool electromagnetic; // Include magnetic potential psi bool FiniteElMass; // Finite Electron Mass - - bool j_diamag; // Diamagnetic current: Vort <-> Pe - FieldGeneratorPtr j_diamag_scale_generator; // Time-varying diamagnetic current scaling - BoutReal j_diamag_scale; // Diamagnetic current scaling factor. - bool j_par; // Parallel current: Vort <-> Psi - bool j_pol_terms; // Extra terms in Vort + + bool j_diamag; // Diamagnetic current: Vort <-> Pe + bool j_par; // Parallel current: Vort <-> Psi + bool j_pol_pi; // Polarisation current with explicit Pi dependence + bool j_pol_simplified; // Polarisation current with explicit Pi dependence + bool parallel_flow; bool parallel_flow_p_term; // Vi advection terms in Pe, Pi - bool pe_par; // Parallel pressure gradient: Pe <-> Psi - bool pe_par_p_term; // Includes terms in Pe,Pi equations - bool resistivity; // Resistivity: Psi -> Pe - bool thermal_force; // Force due to temperature gradients - bool electron_viscosity; // Electron parallel viscosity - bool ion_viscosity; // Ion viscosity - bool ion_viscosity_par; // Parallel part of ion viscosity - bool electron_neutral; // Include electron-neutral collisions in resistivity - bool ion_neutral; // Include ion-neutral collisions in ion collision time - bool poloidal_flows; // Include y derivatives in diamagnetic and ExB drifts - bool thermal_flux; // Include parallel and perpendicular energy flux from Te gradients - bool thermal_conduction; // Braginskii electron heat conduction + bool pe_par; // Parallel pressure gradient: Pe <-> Psi + bool pe_par_p_term; // Includes terms in Pe,Pi equations + bool resistivity; // Resistivity: Psi -> Pe + bool thermal_force; // Force due to temperature gradients + bool electron_viscosity; // Electron parallel viscosity + bool ion_viscosity; // Ion viscosity + bool ion_viscosity_par; // Parallel part of ion viscosity + bool electron_neutral; // Include electron-neutral collisions in resistivity + bool ion_neutral; // Include ion-neutral collisions in ion collision time + bool poloidal_flows; // Include y derivatives in diamagnetic and ExB drifts + bool thermal_flux; // Include parallel and perpendicular energy flux from Te gradients + bool thermal_conduction; // Braginskii electron heat conduction bool electron_ion_transfer; // Electron-ion heat transfer - bool classical_diffusion; // Collisional diffusion, including viscosity - + bool classical_diffusion; // Collisional diffusion, including viscosity + // Anomalous perpendicular diffusion coefficients - BoutReal anomalous_D; // Density diffusion - BoutReal anomalous_chi; // Electron thermal diffusion - BoutReal anomalous_nu; // Momentum diffusion (kinematic viscosity) + BoutReal anomalous_D; // Density diffusion + BoutReal anomalous_chi; // Electron thermal diffusion + BoutReal anomalous_nu; // Momentum diffusion (kinematic viscosity) + Field3D a_d3d, a_chi3d, a_nu3d; // 3D coef - bool anomalous_D_nvi; // Include terms in momentum equation + bool anomalous_D_nvi; // Include terms in momentum equation bool anomalous_D_pepi; // Include terms in Pe, Pi equations - - bool ion_velocity; // Include Vi terms - bool phi3d; // Use a 3D solver for phi + bool ion_velocity; // Include Vi terms + + bool phi3d; // Use a 3D solver for phi + + bool staggered; // Use staggered differencing along B - bool boussinesq; // Use a fixed density (Nnorm) in the vorticity equation + bool boussinesq; // Use a fixed density (Nnorm) in the vorticity equation - bool sinks; // Sink terms for running 2D drift-plane simulations + bool sinks; // Sink terms for running 2D drift-plane simulations bool sheath_closure; // Sheath closure sink on vorticity (if sinks = true) bool drift_wave; // Drift-wave closure (if sinks=true) - bool radial_buffers; // Radial buffer regions - int radial_inner_width; // Number of points in the inner radial buffer - int radial_outer_width; // Number of points in the outer radial buffer - BoutReal radial_buffer_D; // Diffusion in buffer region + bool radial_buffers; // Radial buffer regions + int radial_inner_width; // Number of points in the inner radial buffer + int radial_outer_width; // Number of points in the outer radial buffer + BoutReal radial_buffer_D; // Diffusion in buffer region bool radial_inner_averagey; // Average Ne, Pe, Pi fields in Y in inner radial buffer bool radial_inner_averagey_vort; // Average vorticity in Y in inner buffer - bool radial_inner_averagey_nvi; // Average NVi in Y in inner buffer - bool radial_inner_zero_nvi; // Damp NVi towards zero in inner buffer + bool radial_inner_averagey_nvi; // Average NVi in Y in inner buffer + bool radial_inner_zero_nvi; // Damp NVi towards zero in inner buffer - BoutReal resistivity_boundary; // Value of nu in boundary layer + bool phi_smoothing; + BoutReal phi_sf; + + BoutReal resistivity_boundary; // Value of nu in boundary layer int resistivity_boundary_width; // Width of radial boundary - - Field2D sink_invlpar; // Parallel inverse connection length (1/L_{||}) for + + Field3D sink_invlpar; // Parallel inverse connection length (1/L_{||}) for // sink terms Field2D alpha_dw; // Sheath heat transmission factor - int sheath_model; // Sets boundary condition model - BoutReal sheath_gamma_e, sheath_gamma_i; // Heat transmission - BoutReal neutral_vwall; // Scale velocity at the wall - bool sheath_yup, sheath_ydown; + int sheath_model; // Sets boundary condition model + BoutReal sheath_gamma_e, sheath_gamma_i; // Heat transmission + BoutReal neutral_vwall; // Scale velocity at the wall + bool sheath_yup, sheath_ydown; bool test_boundaries; + bool sheath_allow_supersonic; // If plasma is faster than sound speed, go to plasma + // velocity + bool parallel_sheaths; + int par_sheath_model; // Sets parallel boundary condition model + BoutReal electron_weight; // electron heaviness in units of m_e (for slower boundaries) + bool par_sheath_ve; + Field3D sheath_dpe, sheath_dpi; + + BoundaryRegionPar* bndry_par; - Field2D wall_flux; // Particle flux to wall (diagnostic) + Field2D wall_flux; // Particle flux to wall (diagnostic) Field2D wall_power; // Power flux to wall (diagnostic) - + // Fix density in SOL bool sol_fix_profiles; std::shared_ptr sol_ne, sol_te; // Generating functions @@ -175,89 +206,105 @@ private: // Output switches for additional information bool verbose; // Outputs additional fields, mainly for debugging bool output_ddt; // Output time derivatives - + // Numerical dissipation - BoutReal numdiff, hyper, hyperpar; ///< Numerical dissipation - int low_pass_z; // Fourier filter in Z + BoutReal numdiff, hyper, hyperpar; ///< Numerical dissipation + int low_pass_z; // Fourier filter in Z BoutReal z_hyper_viscos, x_hyper_viscos, y_hyper_viscos; // 4th-order derivatives - bool low_n_diffuse; // Diffusion in parallel direction at low density - bool low_n_diffuse_perp; // Diffusion in perpendicular direction at low density + bool low_n_diffuse; // Diffusion in parallel direction at low density + bool low_n_diffuse_perp; // Diffusion in perpendicular direction at low density BoutReal ne_hyper_z, pe_hyper_z; // Hyper-diffusion - BoutReal scale_num_cs; // Scale numerical sound speed - BoutReal floor_num_cs; // Apply a floor to the numerical sound speed - bool vepsi_dissipation; // Dissipation term in VePsi equation - bool vort_dissipation; // Dissipation term in Vorticity equation - bool phi_dissipation; // Dissipation term in Vorticity equation, depending on phi - + BoutReal scale_num_cs; // Scale numerical sound speed + BoutReal floor_num_cs; // Apply a floor to the numerical sound speed + bool vepsi_dissipation; // Dissipation term in VePsi equation + bool vort_dissipation; // Dissipation term in Vorticity equation + bool phi_dissipation; // Dissipation term in Vorticity equation + + BoutReal ne_num_diff; + BoutReal ne_num_hyper; + BoutReal vi_num_diff; // Numerical perpendicular diffusion + BoutReal ve_num_diff; // Numerical perpendicular diffusion + BoutReal ve_num_hyper; // Numerical hyper-diffusion + // Sources and profiles - - bool ramp_mesh; // Use Ne,Pe in the grid file for starting ramp target + + bool ramp_mesh; // Use Ne,Pe in the grid file for starting ramp target BoutReal ramp_timescale; // Length of time for the initial ramp - Field2D NeTarget, PeTarget, PiTarget; // For adaptive sources - - bool adapt_source_p; // Use a PI controller to feedback pressure profiles - bool adapt_source_n; // Use a PI controller to feedback density profiles - bool sources_positive; // Ensure sources > 0 - bool core_sources; // Sources only in the core - bool energy_source; // Add the same amount of energy to each particle - BoutReal source_p, source_i; // Proportional-Integral controller - Field2D Sn, Spe, Spi; // Sources in density, Pe and Pi - Field3D NeSource, PeSource, PiSource; // Actual sources added - bool density_inflow; // Does incoming density have momentum? - + Field3D NeTarget, PeTarget, PiTarget; // For adaptive sources + + bool adapt_source; // Use a PI controller to feedback profiles + bool core_sources; // Sources only in the core + bool energy_source; // Add the same amount of energy to each particle + BoutReal source_p, source_i; // Proportional-Integral controller + Coordinates::FieldMetric Sn, Spe, Spi; // Sources in density, Pe and Pi + Field3D NeSource, PeSource, PiSource; // Actual sources added + bool density_inflow; // Does incoming density have momentum? + bool source_vary_g11; // Multiply source by g11 - Field2D g11norm; - + Coordinates::FieldMetric g11norm; + // Boundary fluxes - + bool pe_bndry_flux; // Allow flux of pe through radial boundaries bool ne_bndry_flux; // Allow flux of ne through radial boundaries bool vort_bndry_flux; // Allow flux of vorticity through radial boundaries - + // Normalisation parameters - BoutReal Tnorm, Nnorm, Bnorm; + BoutReal Tnorm, Te0, Ti0, Nnorm, Bnorm; BoutReal AA, Cs0, rho_s0, Omega_ci; - BoutReal mi_me, beta_e; - + BoutReal mi_me, me_mi, beta_e; + // Curvature, Grad-B drift Vector3D Curlb_B; // Curl(b/B) - + // Perturbed parallel gradient operators - const Field3D Grad_parP(const Field3D &f); - const Field3D Div_parP(const Field3D &f); - + const Field3D Grad_parP(const Field3D& f); + const Field3D Div_parP(const Field3D& f); + const Field3D Div_par_integrate(const Field3D& f); + // Electromagnetic solver for finite electron mass case - bool split_n0_psi; // Split the n=0 component of Apar (psi)? - //Laplacian *aparSolver; - LaplaceXZ *aparSolver; - LaplaceXY *aparXY; // Solves n=0 component - Field2D psi2D; // Axisymmetric Psi - + bool split_n0_psi; // Split the n=0 component of Apar (psi)? + // Laplacian *aparSolver; + // LaplaceXZ *aparSolver; + std::unique_ptr aparSolver{nullptr}; + + // std::unique_ptr aparXY{nullptr}; + LaplaceXY* aparXY; // Solves n=0 component + Field2D psi2D; // Axisymmetric Psi + // Solvers for the electrostatic potential - bool split_n0; // Split solve into n=0 and n~=0? - LaplaceXY *laplacexy; // Laplacian solver in X-Y (n=0) + bool split_n0; // Split solve into n=0 and n~=0? + // std::unique_ptr laplacexy{nullptr}; + LaplaceXY* laplacexy; // Laplacian solver in X-Y (n=0) Field2D phi2D; // Axisymmetric phi - bool phi_boundary_relax; ///< Relax the boundary towards Neumann? - BoutReal phi_boundary_timescale; ///< Relaxation timescale + bool phi_boundary_relax; ///< Relax the boundary towards Neumann? + BoutReal phi_boundary_timescale; ///< Relaxation timescale BoutReal phi_boundary_last_update; ///< The last time the boundary was updated - - bool newXZsolver; - Laplacian *phiSolver; // Old Laplacian in X-Z - LaplaceXZ *newSolver; // New Laplacian in X-Z + + bool newXZsolver; + std::unique_ptr phiSolver{nullptr}; // Old Laplacian in X-Z + std::unique_ptr newSolver{nullptr}; // New Laplacian in X-Z // Mesh quantities - Field2D B32, sqrtB; + Coordinates::FieldMetric B32, sqrtB; + + bool fci_transform; + Field3D Bxyz, logB, B_SQ; + Field3D bracket_factor; + const Field3D fci_curvature(const Field3D& f); + + Field3D a, b, c, d, f; // Debugging variables }; /// Fundamental constants -const BoutReal e0 = 8.854e-12; // Permittivity of free space -const BoutReal mu0 = 4.e-7*PI; // Permeability of free space -const BoutReal qe = 1.602e-19; // Electron charge -const BoutReal Me = 9.109e-31; // Electron mass -const BoutReal Mp = 1.67262158e-27; // Proton mass +const BoutReal e0 = 8.854e-12; // Permittivity of free space +const BoutReal mu0 = 4.e-7 * PI; // Permeability of free space +const BoutReal qe = 1.602e-19; // Electron charge +const BoutReal Me = 9.109e-31; // Electron mass +const BoutReal Mp = 1.67262158e-27; // Proton mass #endif // __HERMES_H__ diff --git a/mixed.cxx b/mixed.cxx index 530f171..7479c29 100644 --- a/mixed.cxx +++ b/mixed.cxx @@ -1,9 +1,9 @@ #include "mixed.hxx" -#include #include #include +#include #include "div_ops.hxx" @@ -15,49 +15,26 @@ NeutralMixed::NeutralMixed(Solver *solver, Mesh *UNUSED(mesh), Options &options) solver->add(Pn, "Pn"); solver->add(NVn, "NVn"); - sheath_ydown = options["sheath_ydown"] - .doc("Enable wall boundary conditions at ydown") - .withDefault(true); - - sheath_yup = options["sheath_yup"] - .doc("Enable wall boundary conditions at yup") - .withDefault(true); - + OPTION(options, sheath_ydown, true); + OPTION(options, sheath_yup, true); + neutral_gamma = options["neutral_gamma"] .doc("Heat flux to the wall q = neutral_gamma * n * T * cs") .withDefault(5. / 4); nn_floor = options["nn_floor"] - .doc("A minimum density used when dividing NVn by Nn. " - "Normalised units.") - .withDefault(1e-4); - - precondition = options["precondition"] - .doc("Enable preconditioning in neutral model?") - .withDefault(true); - - if (precondition) { - inv = std::unique_ptr(Laplacian::create(&options["precon_laplace"])); - - inv->setInnerBoundaryFlags(INVERT_DC_GRAD | INVERT_AC_GRAD); - inv->setOuterBoundaryFlags(INVERT_DC_GRAD | INVERT_AC_GRAD); - - inv->setCoefA(1.0); - } - + .doc("A minimum density used when dividing NVn by Nn. Normalised units.") + .withDefault(1e-4); + // Optionally output time derivatives - if (options["output_ddt"] - .doc("Save derivatives to output?") - .withDefault(false)) { + if (options["output_ddt"].doc("Save derivatives to output?").withDefault(false)) { SAVE_REPEAT(ddt(Nn), ddt(Pn), ddt(NVn)); } - if (options["diagnose"] - .doc("Save additional diagnostic fields (Dnn) ?") - .withDefault(false)) { + if (options["diagnose"].doc("Save additional diagnostic fields?").withDefault(false)) { SAVE_REPEAT(Dnn); } - + Dnn = 0.0; // Neutral gas diffusion S = 0; @@ -77,7 +54,7 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, NVn.clearParallelSlices(); Coordinates *coord = mesh->getCoordinates(); - + ////////////////////////////////////////////////////// // 3D model, diffusive in X-Z, fluid in Y // @@ -90,7 +67,7 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, // Nnlim Used where division by neutral density is needed Field3D Nnlim = floor(Nn, nn_floor); Field3D Tn = Pn / Nn; - // Tn = floor(Tn, 0.01 / Tnorm); + //Tn = floor(Tn, 0.01 / Tnorm); Vn = NVn / Nn; Field3D Vnlim = NVn / Nnlim; // Neutral parallel velocity @@ -124,7 +101,7 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, // Zero-gradient pressure Pn(r.ind, mesh->ystart - 1, jz) = Pn(r.ind, mesh->ystart, jz); - + // No flow into wall Vn(r.ind, mesh->ystart - 1, jz) = -Vn(r.ind, mesh->ystart, jz); Vnlim(r.ind, mesh->ystart - 1, jz) = -Vnlim(r.ind, mesh->ystart, jz); @@ -155,7 +132,7 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, // Zero-gradient pressure Pn(r.ind, mesh->yend + 1, jz) = Pn(r.ind, mesh->yend, jz); - + // No flow into wall Vn(r.ind, mesh->yend + 1, jz) = -Vn(r.ind, mesh->yend, jz); Vnlim(r.ind, mesh->yend + 1, jz) = -Vnlim(r.ind, mesh->yend, jz); @@ -175,7 +152,7 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, BoutReal neutral_lmax = 0.1 / Lnorm; Field3D Rnn = Nn * sqrt(Tn) / neutral_lmax; // Neutral-neutral collisions Dnn = Pnlim / (Riz + Rcx + Rnn); - + mesh->communicate(Dnn); Dnn.clearParallelSlices(); Dnn.applyBoundary("dirichlet_o2"); @@ -186,32 +163,32 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, Field3D DnnPn = Dnn * Pn; DnnPn.applyBoundary("dirichlet_o2"); Field3D DnnNn = Dnn * Nn; - DnnNn.applyBoundary("dirichlet_o2"); + DnnNn.applyBoundary("dirichlet_o2"); Field3D DnnNVn = Dnn * NVn; DnnNVn.applyBoundary("dirichlet_o2"); - + if (sheath_ydown) { for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { - Dnn(r.ind, mesh->ystart - 1, jz) = -Dnn(r.ind, mesh->ystart, jz); - DnnPn(r.ind, mesh->ystart - 1, jz) = -DnnPn(r.ind, mesh->ystart, jz); - DnnNn(r.ind, mesh->ystart - 1, jz) = -DnnNn(r.ind, mesh->ystart, jz); - DnnNVn(r.ind, mesh->ystart - 1, jz) = -DnnNVn(r.ind, mesh->ystart, jz); + Dnn(r.ind, mesh->ystart-1, jz) = -Dnn(r.ind, mesh->ystart, jz); + DnnPn(r.ind, mesh->ystart-1, jz) = -DnnPn(r.ind, mesh->ystart, jz); + DnnNn(r.ind, mesh->ystart-1, jz) = -DnnNn(r.ind, mesh->ystart, jz); + DnnNVn(r.ind, mesh->ystart-1, jz) = -DnnNVn(r.ind, mesh->ystart, jz); } } } - + if (sheath_yup) { for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { for (int jz = 0; jz < mesh->LocalNz; jz++) { - Dnn(r.ind, mesh->yend + 1, jz) = -Dnn(r.ind, mesh->yend, jz); - DnnPn(r.ind, mesh->yend + 1, jz) = -DnnPn(r.ind, mesh->yend, jz); - DnnNn(r.ind, mesh->yend + 1, jz) = -DnnNn(r.ind, mesh->yend, jz); - DnnNVn(r.ind, mesh->yend + 1, jz) = -DnnNVn(r.ind, mesh->yend, jz); + Dnn(r.ind, mesh->yend+1, jz) = -Dnn(r.ind, mesh->yend, jz); + DnnPn(r.ind, mesh->yend+1, jz) = -DnnPn(r.ind, mesh->yend, jz); + DnnNn(r.ind, mesh->yend+1, jz) = -DnnNn(r.ind, mesh->yend, jz); + DnnNVn(r.ind, mesh->yend+1, jz) = -DnnNVn(r.ind, mesh->yend, jz); } } } - + // Logarithms used to calculate perpendicular velocity // V_perp = -Dnn * ( Grad_perp(Nn)/Nn + Grad_perp(Tn)/Tn ) // @@ -225,14 +202,15 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, // Sound speed appearing in Lax flux for advection terms Field3D sound_speed = sqrt(Tn * (5. / 3)); - + ///////////////////////////////////////////////////// // Neutral density TRACE("Neutral density"); - ddt(Nn) = -FV::Div_par(Nn, Vnlim, sound_speed) // Advection - + S // Source from recombining plasma - + FV::Div_a_Laplace_perp(DnnNn, logPnlim) // Perpendicular diffusion + ddt(Nn) = + -FV::Div_par(Nn, Vnlim, sound_speed) // Advection + + S // Source from recombining plasma + + FV::Div_a_Laplace_perp(DnnNn, logPnlim) // Perpendicular diffusion ; ///////////////////////////////////////////////////// @@ -246,104 +224,102 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, // eta_n = (2. / 5) * kappa_n; ddt(NVn) = - -FV::Div_par(NVn, Vnlim, sound_speed) // Momentum flow - + F // Friction with plasma - - Grad_par(Pnlim) // Pressure gradient - + FV::Div_a_Laplace_perp(DnnNVn, logPnlim) // Perpendicular diffusion - + FV::Div_a_Laplace_perp((2. / 5) * DnnNn, Vn) // Perpendicular viscosity - + FV::Div_par_K_Grad_par((2. / 5) * DnnNn, Vn) // Parallel viscosity + -FV::Div_par(NVn, Vnlim, sound_speed) // Momentum flow + + F // Friction with plasma + - Grad_par(Pnlim) // Pressure gradient + + FV::Div_a_Laplace_perp(DnnNVn, logPnlim) // Perpendicular diffusion + + FV::Div_a_Laplace_perp((2./5)*DnnNn, Vn) // Perpendicular viscosity + + FV::Div_par_K_Grad_par((2./5)*DnnNn, Vn) // Parallel viscosity ; + Fperp = Rcx + Rrc; ///////////////////////////////////////////////////// // Neutral pressure TRACE("Neutral pressure"); - ddt(Pn) = -FV::Div_par(Pn, Vnlim, sound_speed) // Advection - - (2. / 3) * Pnlim * Div_par(Vn) // Compression - + (2. / 3) * Qi // Energy exchange with ions - + FV::Div_a_Laplace_perp(DnnPn, logPnlim) // Perpendicular diffusion - + FV::Div_a_Laplace_perp(DnnNn, Tn) // Conduction - + FV::Div_par_K_Grad_par(DnnNn, Tn) // Parallel conduction + ddt(Pn) = + -FV::Div_par(Pn, Vnlim, sound_speed) // Advection + - (2. / 3) * Pnlim * Div_par(Vn) // Compression + + (2. / 3) * Qi // Energy exchange with ions + + FV::Div_a_Laplace_perp(DnnPn, logPnlim) // Perpendicular diffusion + + FV::Div_a_Laplace_perp(DnnNn, Tn) // Conduction + + FV::Div_par_K_Grad_par(DnnNn, Tn) // Parallel conduction ; - + ////////////////////////////////////////////////////// // Boundary condition on fluxes TRACE("Neutral boundary fluxes"); - if (neutral_gamma > 0.0) { - if (sheath_ydown) { - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - - // Loss of thermal energy to the target. - // This depends on the reflection coefficient - // and is controlled by the option neutral_gamma - // q = neutral_gamma * n * T * cs - - // Density at the target - BoutReal Nnout = 0.5 * (Nn(r.ind, mesh->ystart, jz) + - Nn(r.ind, mesh->ystart - 1, jz)); - if (Nnout < 0.0) - Nnout = 0.0; - // Temperature at the target - BoutReal Tnout = 0.5 * (Tn(r.ind, mesh->ystart, jz) + - Tn(r.ind, mesh->ystart - 1, jz)); - if (Tnout < 0.0) - Tnout = 0.0; - - // gamma * n * T * cs - BoutReal q = neutral_gamma * Nnout * Tnout * sqrt(Tnout); - // Multiply by cell area to get power - BoutReal heatflux = q * - (coord->J(r.ind, mesh->ystart) + - coord->J(r.ind, mesh->ystart - 1)) / - (sqrt(coord->g_22(r.ind, mesh->ystart)) + - sqrt(coord->g_22(r.ind, mesh->ystart - 1))); - - // Divide by volume of cell, and multiply by 2/3 to get pressure - ddt(Pn)(r.ind, mesh->ystart, jz) -= - (2. / 3) * heatflux / - (coord->dy(r.ind, mesh->ystart) * coord->J(r.ind, mesh->ystart)); - } + if (sheath_ydown) { + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + + // Loss of thermal energy to the target. + // This depends on the reflection coefficient + // and is controlled by the option neutral_gamma + // q = neutral_gamma * n * T * cs + + // Density at the target + BoutReal Nnout = 0.5 * (Nn(r.ind, mesh->ystart, jz) + + Nn(r.ind, mesh->ystart - 1, jz)); + if (Nnout < 0.0) + Nnout = 0.0; + // Temperature at the target + BoutReal Tnout = 0.5 * (Tn(r.ind, mesh->ystart, jz) + + Tn(r.ind, mesh->ystart - 1, jz)); + if (Tnout < 0.0) + Tnout = 0.0; + + // gamma * n * T * cs + BoutReal q = neutral_gamma * Nnout * Tnout * sqrt(Tnout); + // Multiply by cell area to get power + BoutReal heatflux = q * (coord->J(r.ind, mesh->ystart, jz) + + coord->J(r.ind, mesh->ystart - 1, jz)) / + (sqrt(coord->g_22(r.ind, mesh->ystart, jz)) + + sqrt(coord->g_22(r.ind, mesh->ystart - 1, jz))); + + // Divide by volume of cell, and multiply by 2/3 to get pressure + ddt(Pn)(r.ind, mesh->ystart, jz) -= + (2. / 3) * heatflux / + (coord->dy(r.ind, mesh->ystart, jz) * coord->J(r.ind, mesh->ystart, jz)); } } + } + + if (sheath_yup) { + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { - if (sheath_yup) { - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - - // Loss of thermal energy to the target. - // This depends on the reflection coefficient - // and is controlled by the option neutral_gamma - // q = neutral_gamma * n * T * cs - - // Density at the target - BoutReal Nnout = - 0.5 * (Nn(r.ind, mesh->yend, jz) + Nn(r.ind, mesh->yend + 1, jz)); - if (Nnout < 0.0) - Nnout = 0.0; - // Temperature at the target - BoutReal Tnout = - 0.5 * (Tn(r.ind, mesh->yend, jz) + Tn(r.ind, mesh->yend + 1, jz)); - if (Tnout < 0.0) - Tnout = 0.0; - - // gamma * n * T * cs - BoutReal q = neutral_gamma * Nnout * Tnout * sqrt(Tnout); - // Multiply by cell area to get power - BoutReal heatflux = - q * - (coord->J(r.ind, mesh->yend) + coord->J(r.ind, mesh->yend + 1)) / - (sqrt(coord->g_22(r.ind, mesh->yend)) + - sqrt(coord->g_22(r.ind, mesh->yend + 1))); - - // Divide by volume of cell, and multiply by 2/3 to get pressure - ddt(Pn)(r.ind, mesh->yend, jz) -= - (2. / 3) * heatflux / - (coord->dy(r.ind, mesh->yend) * coord->J(r.ind, mesh->yend)); - } + // Loss of thermal energy to the target. + // This depends on the reflection coefficient + // and is controlled by the option neutral_gamma + // q = neutral_gamma * n * T * cs + + // Density at the target + BoutReal Nnout = + 0.5 * (Nn(r.ind, mesh->yend, jz) + Nn(r.ind, mesh->yend + 1, jz)); + if (Nnout < 0.0) + Nnout = 0.0; + // Temperature at the target + BoutReal Tnout = + 0.5 * (Tn(r.ind, mesh->yend, jz) + Tn(r.ind, mesh->yend + 1, jz)); + if (Tnout < 0.0) + Tnout = 0.0; + + // gamma * n * T * cs + BoutReal q = neutral_gamma * Nnout * Tnout * sqrt(Tnout); + // Multiply by cell area to get power + BoutReal heatflux = + q * (coord->J(r.ind, mesh->yend, jz) + coord->J(r.ind, mesh->yend + 1, jz)) / + (sqrt(coord->g_22(r.ind, mesh->yend, jz)) + + sqrt(coord->g_22(r.ind, mesh->yend + 1, jz))); + + // Divide by volume of cell, and multiply by 2/3 to get pressure + ddt(Pn)(r.ind, mesh->yend, jz) -= + (2. / 3) * heatflux / + (coord->dy(r.ind, mesh->yend, jz) * coord->J(r.ind, mesh->yend, jz)); } } } @@ -357,19 +333,3 @@ void NeutralMixed::update(const Field3D &Ne, const Field3D &Te, } } } - -void NeutralMixed::precon(BoutReal UNUSED(t), BoutReal gamma, - BoutReal UNUSED(delta)) { - if (!precondition) { - return; - } - - // Neutral gas diffusion - // Solve (1 - gamma*Dnn*Delp2)^{-1} - - inv->setCoefD(-gamma * Dnn); - - ddt(Nn) = inv->solve(ddt(Nn)); - ddt(NVn) = inv->solve(ddt(NVn)); - ddt(Pn) = inv->solve(ddt(Pn)); -}