From 78513aefcc55db123536ddc0341b0076bb9a137f Mon Sep 17 00:00:00 2001 From: phuslage Date: Wed, 25 Aug 2021 00:21:08 +0200 Subject: [PATCH 1/4] new branch for relaxation method --- hermes-2.cxx | 3781 ++++++++++++++++++++++++++++---------------------- hermes-2.hxx | 88 +- 2 files changed, 2174 insertions(+), 1695 deletions(-) diff --git a/hermes-2.cxx b/hermes-2.cxx index d7b19ed..dd30602 100644 --- a/hermes-2.cxx +++ b/hermes-2.cxx @@ -26,6 +26,8 @@ #include #include +#include "parallel_boundary_region.hxx" +#include "boundary_region.hxx" #include "div_ops.hxx" #include "loadmetric.hxx" @@ -46,15 +48,18 @@ namespace FV { 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(); 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"); + 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(); @@ -84,25 +89,23 @@ namespace FV { ye = mesh->yend; } - for (int j = ys; j <= ye; j++) { - // Pre-calculate factors which multiply fluxes - - // 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)); - - // 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 j = ys; j <= ye; j++) { for (int k = 0; k < mesh->LocalNz; k++) { + // For right cell boundaries + 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, 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 @@ -201,7 +204,6 @@ namespace FV { } return fromFieldAligned(result, "RGN_NOBNDRY"); } - } BoutReal floor(const BoutReal &var, const BoutReal &f) { @@ -228,6 +230,96 @@ const Field3D ceil(const Field3D &var, BoutReal f, REGION rgn = RGN_ALL) { // Square function for vectors 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) { @@ -243,6 +335,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?") @@ -255,19 +348,21 @@ int Hermes::init(bool restarting) { j_diamag = optsc["j_diamag"] .doc("Diamagnetic current: Vort <-> Pe") .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_pi = optsc["j_pol_pi"] + .doc("Polarisation current with explicit Pi dependence") + .withDefault(true); + + 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); @@ -309,9 +404,7 @@ 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); @@ -320,6 +413,8 @@ int Hermes::init(bool restarting) { .doc("A fixed ion-neutral collision rate, normalised to ion cyclotron frequency.") .withDefault(0.0); + OPTION(optsc, staggered, false); + OPTION(optsc, boussinesq, false); OPTION(optsc, sinks, false); @@ -353,10 +448,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); + 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); @@ -373,42 +489,25 @@ int Hermes::init(bool restarting) { 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); - nesheath_floor = optsc["nesheath_floor"].doc("Ne sheath lower limit").withDefault(1e-5); - - // 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); 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); @@ -423,6 +522,8 @@ int Hermes::init(bool restarting) { // 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 +536,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); @@ -458,16 +560,24 @@ 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 +593,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 +628,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 +648,49 @@ 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 (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; } - if (j_par || j_diamag) { + 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 +698,7 @@ int Hermes::init(bool restarting) { SAVE_REPEAT(ddt(Vort)); } } else { - Vort = 0.0; + zero_all(Vort); } if (electromagnetic || FiniteElMass) { @@ -564,7 +711,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 +721,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 +734,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,7 +758,7 @@ 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?") @@ -661,8 +792,52 @@ int Hermes::init(bool restarting) { sqrtB = sqrt(coord->Bxy); // B^(1/2) 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 @@ -715,7 +890,7 @@ int Hermes::init(bool restarting) { // 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,7 +981,7 @@ int Hermes::init(bool restarting) { try { Curlb_B.covariant = false; // Contravariant mesh->get(Curlb_B, "bxcv"); - + SAVE_ONCE(Curlb_B); } catch (BoutException &e) { try { // May be 2D, reading as 3D @@ -825,24 +1000,10 @@ 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; @@ -854,9 +1015,12 @@ 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,94 +1037,100 @@ 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 + + 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)); + } } - - // 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) { + phi_1 = 0; + phi_1.setBoundary("phi_1"); } - } - phi = 0.0; - phi.setBoundary("phi"); // For y boundaries + 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); + 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"); + // 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 (phi_boundary_relax) { + if (phi_boundary_relax) { - if (!restarting) { - // Start by setting to the sheath current = 0 boundary value + if (!restarting) { + // Start by setting to the sheath current = 0 boundary value - Field3D Nelim = floor(Ne, 1e-5); - Telim = floor(Pe / Nelim, 0.1 / Tnorm); - Tilim = floor(Pi / Nelim, 0.1 / Tnorm); + Field3D Nelim = floor(Ne, 1e-5); + Telim = floor(Pe / Nelim, 0.1 / Tnorm); + Tilim = floor(Pi / Nelim, 0.1 / Tnorm); - phi.setBoundaryTo(DC( - (log(0.5 * sqrt(mi_me / PI)) + log(sqrt(Telim / (Telim + Tilim)))) * Telim)); - } + 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.; + // 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 - } + 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; - } + // 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); - + if (verbose) { // Save additional fields SAVE_REPEAT(Jpar); // Parallel current 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 +1146,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) { + 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 +1201,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->communicate(EvolvingVars); + + Field3D Nelim = floor_all(Ne, 1e-5); - mesh->communicateXZ(EvolvingVars); - for (auto* f : EvolvingVars.field3d()) { - f->clearParallelSlices(); // Make sure no parallel slices + if (!evolve_te) { + Pe = Te0*copy_all(Nelim); // Fixed ion temperature + Pe.splitParallelSlices(); + Pe.yup() = Te0*Nelim.yup(); + Pe.ydown() = Te0*Nelim.ydown(); } + + Te = div_all(Pe, Nelim); + Vi = div_all(NVi, Nelim); - Field3D Nelim = floor(Ne, 1e-5); - - Te = Pe / Nelim; - Vi = NVi / Nelim; - - Telim = floor(Te, 0.1 / Tnorm); - - Field3D Pelim = Telim * Nelim; + Telim = floor_all(Te, 0.1 / Tnorm); - Field3D logPelim = log(Pelim); - logPelim.applyBoundary("neumann"); + Field3D Pelim = mul_all(Telim, Nelim); - Ti = Pi / Nelim; - Tilim = floor(Ti, 0.1 / Tnorm); - Field3D Pilim = Tilim * Nelim; + 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 = 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 // @@ -1093,24 +1305,7 @@ int Hermes::rhs(BoutReal t) { sound_speed = floor(sound_speed, floor_num_cs); } 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 +1313,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 { @@ -1144,6 +1339,7 @@ int Hermes::rhs(BoutReal t) { // 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()) { @@ -1153,19 +1349,18 @@ int Hermes::rhs(BoutReal t) { phivalue += phi(mesh->xstart, j, k); } 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)); - - // 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++) { + + 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; + + // 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 // (all fields apparently smooth, well behaved) @@ -1182,15 +1377,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->xend + 1, j, 0) + phi(mesh->xend, j, 0)); + for (int k = 0; k < mesh->LocalNz; k++) { + phivalue = phi(mesh->xend, j, k); - // 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); + // 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; + + // 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 @@ -1226,7 +1424,9 @@ 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); @@ -1268,56 +1468,60 @@ int Hermes::rhs(BoutReal t) { } } } - 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); - } + 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 { + 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); + // Set the boundary + phi2D.setBoundaryTo(phi_boundary2d); - phi2D = laplacexy->solve(Vort2D, phi2D); + 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, + // 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), + } 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 { - //////////////////////////////////////////// - // 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 + } + 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,18 +1531,18 @@ int Hermes::rhs(BoutReal t) { } phi.applyBoundary(t); mesh->communicate(phi); - phi.clearParallelSlices(); } + ////////////////////////////////////////////////////////////// // Calculate perturbed magnetic field psi TRACE("Calculating psi"); 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 +1554,63 @@ 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); - } - - Ve = VePsi - 0.5 * mi_me * beta_e * psi + Vi; + + psi = aparSolver->solve(Field3D(-Ne * VePsi), Field3D(psi)); + // psi = aparSolver->solve(-Ne*VePsi, psi); + + 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 +1618,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 } @@ -1424,44 +1632,6 @@ int Hermes::rhs(BoutReal t) { // so shift to and then from field aligned 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) { @@ -1469,7 +1639,7 @@ int Hermes::rhs(BoutReal t) { 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 +1651,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); } @@ -1502,158 +1672,27 @@ 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); - } - } - } - 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"); - - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Temperature at the sheath entrance - BoutReal tesheath = floor(Te(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 - - // 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) = + // 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); - - 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); - } + NVi.ydown()(r.ind, mesh->ystart - 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->ystart, jz); } } break; @@ -1663,9 +1702,9 @@ int Hermes::rhs(BoutReal t) { 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; + 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); @@ -1678,7 +1717,7 @@ int Hermes::rhs(BoutReal t) { 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); } @@ -1690,34 +1729,33 @@ int Hermes::rhs(BoutReal t) { -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; @@ -1727,7 +1765,7 @@ int Hermes::rhs(BoutReal t) { 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)); + Ne.yup()(r.ind, mesh->ystart + 1, jz)); if (nesheath < 0.0) nesheath = 0.0; @@ -1744,7 +1782,7 @@ int Hermes::rhs(BoutReal t) { 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,136 +1791,66 @@ 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); - } + 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); + + // 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); + Pi.ydown()(r.ind, mesh->ystart - 1, jz) = Pi(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; } - 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); - - // 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); - } - - // 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); - } - } - } - 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); - } - } - } - } - - if (sheath_yup) { - switch (sheath_model) { - case 0: { // Normal Bohm sheath - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + } + } else { + // sheath_ydown == false + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; 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); + } + } + } + + if (sheath_yup) { + switch (sheath_model) { + case 0: { // Normal Bohm sheath + 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 +1862,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); } @@ -1914,153 +1882,26 @@ 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) = - 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); - } + // 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); + NVi.yup()(r.ind, mesh->yend + 1, jz) = + 2. * nesheath * visheath - NVi(r.ind, mesh->yend, jz); } } break; @@ -2070,9 +1911,9 @@ int Hermes::rhs(BoutReal t) { 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; + 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,7 +1925,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); } @@ -2096,34 +1937,32 @@ int Hermes::rhs(BoutReal t) { 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); - } + 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; @@ -2133,7 +1972,7 @@ int Hermes::rhs(BoutReal t) { 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)); + Ne.ydown()(r.ind, mesh->yend - 1, jz)); if (nesheath < 0.0) { nesheath = 0.0; } @@ -2148,7 +1987,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 +1996,280 @@ 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); - - // 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); - Pi(r.ind, jy, jz) = Pi(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) = -Jpar(r.ind, mesh->yend, jz); - NVi(r.ind, jy, jz) = - 2. * nesheath * visheath - NVi(r.ind, mesh->yend, 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->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); - } - - // 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)); - - // Zero-gradient potential - BoutReal phisheath = phi(r.ind, mesh->yend, jz); - - // Ion velocity goes to the sound speed - BoutReal visheath = sqrt(tesheath + tisheath); // Sound speed outwards - - if (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 / 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->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 - // 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); - } + 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; } - default: { - throw BoutException("Sheath model %d not implemented", sheath_model); } + } 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); + } } } - ////////////////////////////////////////////////////////////// - // 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); + 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.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); + } + + // 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-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; } - // 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); + 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 = 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); + + // Ion velocity goes to the sound speed. Note negative since out of the domain + BoutReal 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); + } + + 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 + // 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 (bndry_par->dir == 1){ + // Apply boundary condition half-way between cells + // 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.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; + } } } - - // 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,59 +2279,11 @@ 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, @@ -2426,7 +2320,7 @@ int Hermes::rhs(BoutReal t) { } // 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); @@ -2493,8 +2387,6 @@ int Hermes::rhs(BoutReal t) { } } } - - nu.applyBoundary(t); } if (thermal_conduction || sinks) { @@ -2544,6 +2436,8 @@ int Hermes::rhs(BoutReal t) { } } + if(currents){ nu.applyBoundary(t); } + if (ion_viscosity) { /////////////////////////////////////////////////////////// // Ion stress tensor. Split into @@ -2553,9 +2447,9 @@ 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,32 +2460,52 @@ 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 + + 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 - + + (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) // q parallel - - + 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(log(Tilim)) - Grad_par(log(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))); + (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++) { @@ -2618,12 +2532,13 @@ int Hermes::rhs(BoutReal t) { } 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++) { @@ -2647,9 +2562,10 @@ int Hermes::rhs(BoutReal t) { 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()]); } - Pi_ciperp = fromFieldAligned(Pi_ciperp); - Pi_cipar = fromFieldAligned(Pi_cipar); - + if (!fci_transform){ + Pi_ciperp = fromFieldAligned(Pi_ciperp); + Pi_cipar = fromFieldAligned(Pi_cipar); + } mesh->communicateXZ(Pi_ciperp); // Apply boundary conditions @@ -2659,6 +2575,17 @@ 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 +2593,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); - } else { - // Parallel wave speed is ion sound speed - ddt(Ne) -= FV::Div_par(Ne, Ve, sound_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{ + 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 +2652,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 +2681,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; + } } } } @@ -2752,7 +2713,12 @@ 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(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 +2728,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,7 +2750,7 @@ 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. @@ -2780,7 +2760,11 @@ int Hermes::rhs(BoutReal t) { // 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,33 +2772,57 @@ 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 - - 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); + 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); + } + + // 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 { // When the Boussinesq approximation is not made, // then the changing ion density introduces a number @@ -2826,23 +2834,32 @@ 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) - + // 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) { @@ -2864,15 +2881,34 @@ int Hermes::rhs(BoutReal t) { } if (vort_dissipation) { - // Adds dissipation term like in other equations - - ddt(Vort) -= FV::Div_par(Vort, 0.0, max_speed); + 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]; + } + + // 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); + ddt(Vort) -= SQ(coord->dy)*D2DY2(phi); + f = SQ(coord->dy)*D2DY2(phi); } } @@ -2889,22 +2925,25 @@ int Hermes::rhs(BoutReal t) { // Evolve VePsi except for electrostatic and zero electron mass case if (resistivity) { - ddt(VePsi) -= mi_me * nu * (Ve - Vi); + 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); } @@ -2915,6 +2954,7 @@ int Hermes::rhs(BoutReal t) { 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 +2963,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 +3001,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 +3042,74 @@ 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, - ne_bndry_flux); // Grad-B, curvature drift + 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); + 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)); + } // Ignoring polarisation drift for now if (pe_par) { - ddt(NVi) -= Grad_parP(Pe + Pi); - } - - 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); + 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); } } @@ -3002,10 +3117,13 @@ int Hermes::rhs(BoutReal t) { if (ion_neutral_rate > 0.0) { ddt(NVi) -= ion_neutral_rate * NVi; } - + 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,18 +3136,21 @@ 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) { @@ -3039,607 +3160,871 @@ 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); - // 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) += (2. / 3) * Ve * Grad_par(Pe); - // Parallel heat conduction - if (thermal_conduction) { - ddt(Pe) += (2. / 3) * FV::Div_par_K_Grad_par(kappa_epar, Te); - } + // mesh->getParallelTransform().integrateParallelSlices(peve); - if (thermal_flux) { - // Parallel heat convection - ddt(Pe) += (2. / 3) * 0.71 * Div_parP(Te * Jpar); - } + // 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); + } + } + } - if (currents && resistivity) { - // Ohmic heating - ddt(Pe) += nu * Jpar * (Jpar - Jpar0) / Nelim; - } + 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 (pe_hyper_z > 0.0) { - ddt(Pe) -= pe_hyper_z * SQ(SQ(coord->dz)) * D4DZ4(Pe); - } + // 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); + } - /////////////////////////////////// - // Heat transmission through sheath - // Note: Have to calculate in field-aligned coordinates + // 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); + } + } - Field3D Ne_FA = toFieldAligned(Ne); - Field3D Te_FA = toFieldAligned(Te); - Field3D Ti_FA = toFieldAligned(Ti); + if (thermal_flux) { + // Parallel heat convection + if (fci_transform) { + Field3D tejpar = mul_all(Te,Jpar); + mesh->communicate(tejpar); + // mesh->getParallelTransform().integrateParallelSlices(tejpar); - wall_power = 0.0; // Diagnostic output - if (sheath_yup) { - TRACE("electron sheath yup heat transmission"); + ddt(Pe) += (2. / 3) * 0.71 * Div_parP(tejpar); + } else { + ddt(Pe) += (2. / 3) * 0.71 * Div_par(Te * Jpar); + } + } - Field3D sheath_dpe{zeroFrom(Ne_FA)}; // Field aligned + if (currents && resistivity) { + // Ohmic heating + ddt(Pe) += nu * Jpar * (Jpar - Jpar0) / Nelim; + } - 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; + if (pe_hyper_z > 0.0) { + ddt(Pe) -= pe_hyper_z * SQ(SQ(coord->dz)) * D4DZ4(Pe); + } + + /////////////////////////////////// + // Heat transmission through sheath + + 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 (evolve_ti) { - 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; - } - - // Parallel flow - if (parallel_flow_p_term) { - ddt(Pi) -= FV::Div_par(Pi, Vi, sound_speed); - } - - 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); - - // 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)); - - ddt(Pi) += j_diamag_scale * Pi * Div((Pe + Pi) * Curlb_B); - } - - if (j_par) { - if (boussinesq) { - ddt(Pi) -= (2. / 3) * Jpar * Grad_parP(Pi); + 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) -= (2. / 3) * Jpar * Grad_parP(Pi) / Nelim; + ddt(Pi) = 0.0; } - } - // 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 - - ddt(Pi) += (2. / 3) * FV::Div_a_Laplace_perp( - 2. * Pilim / (SQ(coord->Bxy) * tau_i), Ti); + // 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); + } + } - // Resistive drift terms + 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); + } - // nu_rho2 = (Ti/Te) * nu_ei * rho_e^2 in normalised units - Field3D nu_rho2 = Tilim / (tau_e * mi_me * SQ(coord->Bxy)); + // 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); - ddt(Pi) += (5. / 3) * - (FV::Div_a_Laplace_perp(nu_rho2, Pe + Pi) - - (3. / 2) * FV::Div_a_Laplace_perp(nu_rho2 * Ne, Te)); + 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); + } + } - // Collisional heating from perpendicular viscosity - // in the vorticity equation + 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; + } + } - 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)); + // 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); + } } - } - 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); + // 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); + } - ddt(Pi) -= (2. / 6) * Pi_ci * Curlb_B * Grad(phi + Pi); - ddt(Pi) += (2. / 9) * bracket(Pi_ci, phi + Pi, BRACKET_ARAKAWA); + 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; } - } - - ////////////////////// - // Anomalous diffusion - if ((anomalous_D > 0.0) && anomalous_D_pepi) { - ddt(Pi) += FV::Div_a_Laplace_perp(anomalous_D * DC(Ti), DC(Ne)); - } + ////////////////////// + // Classical diffusion - if (anomalous_chi > 0.0) { - ddt(Pi) += - (2. / 3) * FV::Div_a_Laplace_perp(anomalous_chi * DC(Ne), DC(Ti)); - } + 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 - /////////////////////////////////// - // Heat transmission through sheath + 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 (sheath_yup) { - TRACE("ion sheath yup heat transmission"); - - Field3D sheath_dpi{zeroFrom(Te_FA)}; // Field aligned + 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); - 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 (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; } - break; } - default: { - throw BoutException("sheath_model %d not implemented", sheath_model); + + ////////////////////// + // Anomalous diffusion + + if ((anomalous_D > 0.0) && anomalous_D_pepi) { + ddt(Pi) += FV::Div_a_Laplace_perp(mul_all(a_d3d , Ti), Ne); } + + if (anomalous_chi > 0.0) { + ddt(Pi) += (2. / 3) * FV::Div_a_Laplace_perp(mul_all(a_chi3d , Ne), Ti); } - ddt(Pi) += fromFieldAligned(sheath_dpi); - } - if (sheath_ydown) { - TRACE("ion sheath ydown heat transmission"); - Field3D sheath_dpi{zeroFrom(Ti_FA)}; + /////////////////////////////////// + // Heat transmission through sheath - 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 (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 (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; + + } + } + } + ddt(Pi) += 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); + } } - } else { - Spi -= source_p * PiErr / PiTarget; - ddt(Spi) = -source_i * PiErr; + 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); + + 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 +4044,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 +4085,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 +4098,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; @@ -3798,14 +4178,14 @@ int Hermes::rhs(BoutReal t) { // 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))); + (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 @@ -3835,14 +4215,14 @@ int Hermes::rhs(BoutReal t) { 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 @@ -4007,10 +4387,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 +4408,66 @@ int Hermes::precon(BoutReal t, BoutReal gamma, BoutReal delta) { return 0; } +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); + 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 ® : 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..ad0e503 100644 --- a/hermes-2.hxx +++ b/hermes-2.hxx @@ -56,7 +56,7 @@ private: Field3D VePsi; // Combination of Ve and psi Field3D Vort; // Vorticity Field3D NVi; // Parallel momentum - + Field3D phi_1; FieldGroup EvolvingVars; // Auxilliary variables @@ -66,6 +66,17 @@ private: 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; @@ -98,15 +109,19 @@ private: // Switches 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_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 @@ -128,6 +143,7 @@ private: 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_pepi; // Include terms in Pe, Pi equations @@ -135,6 +151,8 @@ private: 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 @@ -151,10 +169,13 @@ private: 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 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; @@ -164,6 +185,14 @@ private: 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_power; // Power flux to wall (diagnostic) @@ -171,7 +200,7 @@ private: // Fix density in SOL bool sol_fix_profiles; std::shared_ptr sol_ne, sol_te; // Generating functions - + // Output switches for additional information bool verbose; // Outputs additional fields, mainly for debugging bool output_ddt; // Output time derivatives @@ -188,37 +217,41 @@ private: 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 + 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 BoutReal ramp_timescale; // Length of time for the initial ramp - Field2D NeTarget, PeTarget, PiTarget; // For adaptive sources + Field3D 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 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 - Field2D Sn, Spe, Spi; // Sources in density, Pe and Pi + 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) @@ -226,17 +259,22 @@ private: // Perturbed parallel gradient operators 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; + // 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? + // std::unique_ptr laplacexy{nullptr}; LaplaceXY *laplacexy; // Laplacian solver in X-Y (n=0) Field2D phi2D; // Axisymmetric phi @@ -245,11 +283,19 @@ private: 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 + 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 From c1998c759b82fed04f332750fba006e7773920d7 Mon Sep 17 00:00:00 2001 From: phuslage Date: Wed, 25 Aug 2021 11:15:25 +0200 Subject: [PATCH 2/4] fix conflicts --- div_ops.cxx | 200 +++++++++++++++++++---------------- full-velocity.cxx | 114 +++++++++++--------- mixed.cxx | 264 ++++++++++++++++++++-------------------------- 3 files changed, 287 insertions(+), 291 deletions(-) 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/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)); -} From c2274421193f1a74263de9096e9dd9e2f70a6bf3 Mon Sep 17 00:00:00 2001 From: phuslage Date: Thu, 26 Aug 2021 21:51:53 +0200 Subject: [PATCH 3/4] Fix formatting --- hermes-2.cxx | 2500 +++++++++++++++++++++++++------------------------- 1 file changed, 1260 insertions(+), 1240 deletions(-) diff --git a/hermes-2.cxx b/hermes-2.cxx index dd30602..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,188 +25,193 @@ #include #include -#include -#include "parallel_boundary_region.hxx" #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)); - bool use_parallel_slices = (f_in.hasParallelSlices() && v_in.hasParallelSlices() - && wave_speed_in.hasParallelSlices()); + 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 = 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"); + /// 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; + } - 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, 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, 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 { - // 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); - } + 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++) { + for (int k = 0; k < mesh->LocalNz; k++) { + + // For right cell boundaries + 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, 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; @@ -214,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); @@ -228,7 +233,7 @@ 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 @@ -255,17 +260,17 @@ BoutReal limitFree(BoutReal fm, BoutReal fc) { return fp; } -Field3D floor_all(const Field3D &f, BoutReal minval) { +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"); + 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 copy_all(const Field3D& f) { Field3D result = copy(f); result.splitParallelSlices(); result.yup() = copy(f.yup()); @@ -273,7 +278,7 @@ Field3D copy_all(const Field3D &f) { return result; } -Field3D div_all(const Field3D &num, const Field3D &den) { +Field3D div_all(const Field3D& num, const Field3D& den) { Field3D result = num / den; result.splitParallelSlices(); result.yup() = num.yup() / den.yup(); @@ -281,7 +286,7 @@ Field3D div_all(const Field3D &num, const Field3D &den) { return result; } -Field3D div_all(const Field3D &num, BoutReal den) { +Field3D div_all(const Field3D& num, BoutReal den) { Field3D result = num / den; result.splitParallelSlices(); result.yup() = num.yup() / den; @@ -289,7 +294,7 @@ Field3D div_all(const Field3D &num, BoutReal den) { return result; } -Field3D mul_all(const Field3D &a, const Field3D &b) { +Field3D mul_all(const Field3D& a, const Field3D& b) { Field3D result = a * b; result.splitParallelSlices(); result.yup() = a.yup() * b.yup(); @@ -297,7 +302,7 @@ Field3D mul_all(const Field3D &a, const Field3D &b) { return result; } -Field3D sub_all(const Field3D &a, const Field3D &b) { +Field3D sub_all(const Field3D& a, const Field3D& b) { Field3D result = a - b; result.splitParallelSlices(); result.yup() = a.yup() - b.yup(); @@ -305,7 +310,7 @@ Field3D sub_all(const Field3D &a, const Field3D &b) { return result; } -Field3D add_all(const Field3D &a, const Field3D &b) { +Field3D add_all(const Field3D& a, const Field3D& b) { Field3D result = a + b; result.splitParallelSlices(); result.yup() = a.yup() + b.yup(); @@ -313,7 +318,7 @@ Field3D add_all(const Field3D &a, const Field3D &b) { return result; } -void zero_all(Field3D &f) { +void zero_all(Field3D& f) { f = 0.0; f.splitParallelSlices(); f.yup() = 0.0; @@ -322,7 +327,7 @@ void zero_all(Field3D &f) { /// 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; } @@ -345,21 +350,18 @@ 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") - .withDefault(true); - - j_par = optsc["j_par"] - .doc("Parallel current: Vort <-> Psi") - .withDefault(true); + 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); + .doc("Polarisation current with explicit Pi dependence") + .withDefault(true); j_pol_simplified = optsc["j_pol_simplified"] - .doc("Polarisation current without explicit Pi dependence") - .withDefault(false); + .doc("Polarisation current without explicit Pi dependence") + .withDefault(false); OPTION(optsc, relaxation, false); OPTION(optsc, lambda_0, 1e3); OPTION(optsc, lambda_2, 1e5); @@ -371,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); @@ -410,8 +414,9 @@ int Hermes::init(bool restarting) { 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); @@ -423,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); @@ -450,8 +455,9 @@ int Hermes::init(bool restarting) { 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.") @@ -460,7 +466,7 @@ int Hermes::init(bool restarting) { 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); @@ -485,37 +491,38 @@ 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 + 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); - + 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); 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 @@ -551,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); @@ -562,7 +568,6 @@ int Hermes::init(bool restarting) { output.write("\tnormalised anomalous D_perp = %e\n", anomalous_D); a_d3d = anomalous_D; mesh->communicate(a_d3d); - } if (anomalous_chi > 0.0) { // Normalise @@ -577,7 +582,6 @@ int Hermes::init(bool restarting) { output.write("\tnormalised anomalous nu_perp = %e\n", anomalous_nu); a_nu3d = anomalous_nu; mesh->communicate(a_nu3d); - } if (ramp_mesh) { @@ -628,11 +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++) { - 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; - } + 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; + } } } } @@ -654,7 +658,7 @@ int Hermes::init(bool restarting) { if (output_ddt) { SAVE_REPEAT(ddt(Ne)); } - + if (relaxation) { SOLVE_FOR(phi_1); EvolvingVars.add(phi_1); @@ -664,8 +668,8 @@ int Hermes::init(bool restarting) { } // 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); + evolve_te = + optsc["evolve_te"].doc("Evolve electron temperature?").withDefault(true); if (evolve_te) { SOLVE_FOR(Pe); EvolvingVars.add(Pe); @@ -673,10 +677,9 @@ int Hermes::init(bool restarting) { SAVE_REPEAT(ddt(Pe)); } } else { - Pe = Te0*Ne; + Pe = Te0 * Ne; } - evolve_ti = optsc["evolve_ti"].doc("Evolve ion temperature?") - .withDefault(true); + evolve_ti = optsc["evolve_ti"].doc("Evolve ion temperature?").withDefault(true); if (evolve_ti) { SOLVE_FOR(Pi); EvolvingVars.add(Pi); @@ -684,11 +687,10 @@ int Hermes::init(bool restarting) { SAVE_REPEAT(ddt(Pi)); } } else { - Pi = Ti0*Ne; + Pi = Ti0 * Ne; } - evolve_vort = optsc["evolve_vort"].doc("Evolve Vorticity?") - .withDefault(true); + evolve_vort = optsc["evolve_vort"].doc("Evolve Vorticity?").withDefault(true); if ((j_par || j_diamag || relaxation) && evolve_vort) { // Have a source of vorticity @@ -758,14 +760,14 @@ int Hermes::init(bool restarting) { // field normalisations TRACE("Loading metric tensor"); - Coordinates *coord = bout::globals::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; @@ -792,24 +794,25 @@ int Hermes::init(bool restarting) { sqrtB = sqrt(coord->Bxy); // B^(1/2) B32 = sqrtB * coord->Bxy; // B^(3/2) - + SAVE_ONCE(B32); if (Options::root()["mesh:paralleltransform"]["type"].as() == "fci") { fci_transform = true; - }else{ + } else { fci_transform = false; } - if(fci_transform){ + if (fci_transform) { poloidal_flows = false; - mesh->get(Bxyz, "B",1.0); + 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 + 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) @@ -827,18 +830,18 @@ int Hermes::init(bool restarting) { logB = log(Bxyz); mesh->communicate(logB); if (relaxation) { - B_SQ = coord->Bxy * coord->Bxy; - mesh->communicate(B_SQ); - SAVE_ONCE(B_SQ); + 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{ + } else { mesh->communicate(coord->Bxy); bracket_factor = sqrt(coord->g_22) / (coord->J * coord->Bxy); SAVE_ONCE(bracket_factor); } - + ///////////////////////////////////////////////////////// // Neutral models @@ -853,7 +856,7 @@ int Hermes::init(bool restarting) { ///////////////////////////////////////////////////////// // Impurities TRACE("Impurities"); - + impurity_adas = optsc["impurity_adas"] .doc("Use Atomic++ interface to ADAS") .withDefault(false); @@ -864,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); @@ -885,7 +887,7 @@ int Hermes::init(bool restarting) { // Save impurity radiation SAVE_REPEAT(Rzrad); } - + ///////////////////////////////////////////////////////// // Read profiles from the mesh TRACE("Reading profiles"); @@ -982,14 +984,14 @@ int Hermes::init(bool restarting) { Curlb_B.covariant = false; // Contravariant mesh->get(Curlb_B, "bxcv"); SAVE_ONCE(Curlb_B); - } catch (BoutException &e) { + } 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; @@ -999,7 +1001,7 @@ int Hermes::init(bool restarting) { } } } - + if (Options::root()["mesh:paralleltransform"]["type"].as() == "shifted") { Field2D I; mesh->get(I, "sinty"); @@ -1014,7 +1016,7 @@ int Hermes::init(bool restarting) { ////////////////////////////////////////////////////////////// // Electromagnetic fields - + if (j_par | j_diamag | relaxation) { // Only needed if there are any currents SAVE_REPEAT(phi); @@ -1037,28 +1039,28 @@ int Hermes::init(bool restarting) { phiSolver3D = Laplace3D::create(); #endif } else { - + 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 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)); + // 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)); + // Use older Laplacian solver + phiSolver = Laplacian::create(&opt["phiSolver"]); + // Set coefficients for Boussinesq solve + phiSolver->setCoefC(1. / SQ(coord->Bxy)); } } @@ -1068,10 +1070,10 @@ int Hermes::init(bool restarting) { } 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); + .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, @@ -1080,33 +1082,35 @@ int Hermes::init(bool restarting) { if (phi_boundary_relax) { - if (!restarting) { - // Start by setting to the sheath current = 0 boundary value + if (!restarting) { + // Start by setting to the sheath current = 0 boundary value - Field3D Nelim = floor(Ne, 1e-5); - Telim = floor(Pe / Nelim, 0.1 / Tnorm); - Tilim = floor(Pi / Nelim, 0.1 / Tnorm); + Field3D Nelim = floor(Ne, 1e-5); + Telim = floor(Pe / Nelim, 0.1 / Tnorm); + Tilim = floor(Pi / Nelim, 0.1 / Tnorm); - phi.setBoundaryTo(DC( - (log(0.5 * sqrt(mi_me / PI)) + log(sqrt(Telim / (Telim + Tilim)))) * Telim)); - } + 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.; + // 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 + 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; + // Use another XY solver for n=0 psi component + aparXY = new LaplaceXY(mesh); + psi2D = 0.0; } Ve.setBoundary("Ve"); @@ -1122,7 +1126,7 @@ int Hermes::init(bool restarting) { Dn = 0.0; SAVE_REPEAT(Telim, Tilim); - + if (verbose) { // Save additional fields SAVE_REPEAT(Jpar); // Parallel current @@ -1152,12 +1156,12 @@ int Hermes::init(bool restarting) { 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(a, b, c, d); + if (evolve_te && evolve_ti && parallel_sheaths) { SAVE_REPEAT(sheath_dpe, sheath_dpi); } // Magnetic field in boundary @@ -1185,7 +1189,7 @@ int Hermes::rhs(BoutReal t) { printf("TIME = %e\r", t); } - Coordinates *coord = mesh->getCoordinates(); + Coordinates* coord = mesh->getCoordinates(); a = 0; b = 0; c = 0; @@ -1207,14 +1211,14 @@ int Hermes::rhs(BoutReal t) { mesh->communicate(EvolvingVars); Field3D Nelim = floor_all(Ne, 1e-5); - + if (!evolve_te) { - Pe = Te0*copy_all(Nelim); // Fixed ion temperature + Pe = Te0 * copy_all(Nelim); // Fixed ion temperature Pe.splitParallelSlices(); - Pe.yup() = Te0*Nelim.yup(); - Pe.ydown() = Te0*Nelim.ydown(); + Pe.yup() = Te0 * Nelim.yup(); + Pe.ydown() = Te0 * Nelim.ydown(); } - + Te = div_all(Pe, Nelim); Vi = div_all(NVi, Nelim); @@ -1223,15 +1227,15 @@ int Hermes::rhs(BoutReal t) { Field3D Pelim = mul_all(Telim, Nelim); if (!evolve_ti) { - if (Ti0 > 1.0){ + if (Ti0 > 1.0) { throw BoutException("Hot-ions for isothermal simulation!"); } - Pi = Ti0*copy_all(Nelim); // Fixed ion temperature + Pi = Ti0 * copy_all(Nelim); // Fixed ion temperature Pi.splitParallelSlices(); - Pi.yup() = Ti0*Nelim.yup(); - Pi.ydown() = Ti0*Nelim.ydown(); + Pi.yup() = Ti0 * Nelim.yup(); + Pi.ydown() = Ti0 * Nelim.ydown(); } - + Ti = div_all(Pi, Nelim); Tilim = floor_all(Ti, 0.1 / Tnorm); Field3D Pilim = mul_all(Tilim, Nelim); @@ -1305,7 +1309,7 @@ int Hermes::rhs(BoutReal t) { sound_speed = floor(sound_speed, floor_num_cs); } sound_speed.applyBoundary("neumann"); - + ////////////////////////////////////////////////////////////// // Calculate electrostatic potential phi // @@ -1322,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. @@ -1333,13 +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); + // output.write("weight: {}\n", weight); phi_boundary_last_update = t; if (mesh->firstX()) { @@ -1349,18 +1354,20 @@ int Hermes::rhs(BoutReal t) { phivalue += phi(mesh->xstart, j, k); } phivalue /= mesh->LocalNz; // Average in Z of point next to boundary - - 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; - - // Set phi at the boundary to this value - phi(mesh->xstart - 1, j, k) = 2.*newvalue - phi(mesh->xstart, j, k); - + + 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; + + // 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 // (all fields apparently smooth, well behaved) @@ -1377,18 +1384,19 @@ int Hermes::rhs(BoutReal t) { } phivalue /= mesh->LocalNz; // Average in Z of point next to boundary - for (int k = 0; k < mesh->LocalNz; k++) { - phivalue = phi(mesh->xend, j, k); + 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; - // 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; - - // Set phi at the boundary to this value - 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 @@ -1403,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)); @@ -1424,13 +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: @@ -1448,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)); } } } @@ -1461,19 +1468,18 @@ 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 (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 ); + // 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 { + } else { if (split_n0) { //////////////////////////////////////////// // Boussinesq, split @@ -1484,7 +1490,7 @@ int Hermes::rhs(BoutReal t) { // Make sure that the 2D boundary field is set phi_boundary2d = DC(phi_boundary3d); } - + // Set the boundary phi2D.setBoundaryTo(phi_boundary2d); @@ -1493,14 +1499,17 @@ int Hermes::rhs(BoutReal t) { // 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 + 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 = phiSolver->solve( + (Vort - Vort2D) * SQ(coord->Bxy), + phi_boundary3d - phi_boundary2d); // Note: non-zero due to Pi variation } phi += phi2D; // Add axisymmetric part } else { @@ -1518,10 +1527,10 @@ int Hermes::rhs(BoutReal t) { phi = phiSolver->solve(Vort * SQ(coord->Bxy), phi_boundary3d); } } - + // Hot ion term in vorticity - phi -= Pi; - } + phi -= Pi; + } } else { //////////////////////////////////////////// // Non-Boussinesq @@ -1533,7 +1542,6 @@ int Hermes::rhs(BoutReal t) { mesh->communicate(phi); } - ////////////////////////////////////////////////////////////// // Calculate perturbed magnetic field psi TRACE("Calculating psi"); @@ -1556,10 +1564,10 @@ int Hermes::rhs(BoutReal t) { // aparSolver->setCoefA(-Ne*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); - - psi = aparSolver->solve(Field3D(-Ne * VePsi), Field3D(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 * beta_e * mi_me * psi + Vi; Ve.applyBoundary(t); @@ -1574,13 +1582,13 @@ int Hermes::rhs(BoutReal t) { psi = div_all(VePsi, 0.5 * mi_me * beta_e); // Ve = (NVi - Delp2(psi)) / Nelim; - 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); - } + 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); @@ -1598,19 +1606,21 @@ int Hermes::rhs(BoutReal t) { } else { // Zero electron mass and electrostatic. // Special case where Ohm's law has no time-derivatives - mesh->communicate(phi,Pe); + mesh->communicate(phi, Pe); - 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 (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);} + if (fci_transform) { + mesh->communicate(Te); + } Ve -= 0.71 * Grad_parP(Te) / nu; } } @@ -1618,7 +1628,7 @@ int Hermes::rhs(BoutReal t) { Ve.applyBoundary(t); // Communicate auxilliary variables mesh->communicate(Ve); - Field3D neve = mul_all(Ne,Ve); + Field3D neve = mul_all(Ne, Ve); mesh->communicate(neve); Jpar = sub_all(NVi, neve); } @@ -1632,7 +1642,7 @@ int Hermes::rhs(BoutReal t) { // so shift to and then from field aligned TRACE("Sheath boundaries"); - + if (sheath_ydown) { switch (sheath_model) { case 0: { // Normal Bohm sheath @@ -1659,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); @@ -1687,8 +1696,10 @@ int Hermes::rhs(BoutReal t) { 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); + 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) = @@ -1701,8 +1712,9 @@ int Hermes::rhs(BoutReal t) { 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)); + 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; @@ -1714,8 +1726,7 @@ 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 (sheath_allow_supersonic && (Vi(r.ind, mesh->ystart, jz) < visheath)) { // If plasma is faster, go to plasma velocity @@ -1723,8 +1734,7 @@ int Hermes::rhs(BoutReal t) { } // 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) @@ -1735,7 +1745,7 @@ int Hermes::rhs(BoutReal t) { } // Apply boundary condition half-way between cells - + // 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); @@ -1745,15 +1755,19 @@ int Hermes::rhs(BoutReal t) { 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); + 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); + 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); } @@ -1764,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.yup()(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; @@ -1779,8 +1794,7 @@ 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 (sheath_allow_supersonic && (Vi(r.ind, mesh->ystart, jz) < visheath)) { // If plasma is faster, go to plasma velocity @@ -1804,8 +1818,10 @@ int Hermes::rhs(BoutReal t) { Pi.ydown()(r.ind, mesh->ystart - 1, jz) = Pi(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); + 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); @@ -1821,19 +1837,23 @@ int Hermes::rhs(BoutReal t) { // 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); + 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); + 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); + 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); + 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); @@ -1843,7 +1863,7 @@ int Hermes::rhs(BoutReal t) { } } } - + if (sheath_yup) { switch (sheath_model) { case 0: { // Normal Bohm sheath @@ -1870,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) @@ -1899,7 +1918,8 @@ int Hermes::rhs(BoutReal t) { // 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); + 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); } @@ -1910,8 +1930,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.ydown()(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; @@ -1931,8 +1952,7 @@ int Hermes::rhs(BoutReal t) { } // 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) @@ -1943,7 +1963,7 @@ int Hermes::rhs(BoutReal t) { } // Apply boundary condition half-way between cells - // Neumann conditions + // 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); @@ -1960,7 +1980,8 @@ int Hermes::rhs(BoutReal t) { 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); + 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); } @@ -1971,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.ydown()(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; } @@ -1997,7 +2019,8 @@ int Hermes::rhs(BoutReal t) { // Apply boundary condition half-way between cells 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); + 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 @@ -2025,19 +2048,19 @@ int Hermes::rhs(BoutReal t) { // 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); + 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); + 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); @@ -2048,228 +2071,220 @@ int Hermes::rhs(BoutReal t) { } } - if (parallel_sheaths){ + 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.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); - } - - // 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-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); - } - } + 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.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); + } + + // 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-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 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 = 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); - - // Ion velocity goes to the sound speed. Note negative since out of the domain - BoutReal 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); - } - - 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 - // 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 (bndry_par->dir == 1){ - // Apply boundary condition half-way between cells - // 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.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); - } - } + 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 = 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); + + // Ion velocity goes to the sound speed. Note negative since out of the domain + BoutReal 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); + } + + 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 + // 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 (bndry_par->dir == 1) { + // Apply boundary condition half-way between cells + // 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.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; } } } - - + if (!currents) { // No currents, so reset Ve to be equal to Vi // VePsi also reset, so saved in restart file correctly @@ -2283,19 +2298,19 @@ int Hermes::rhs(BoutReal t) { Pelim = Telim * Nelim; Tilim = floor(Ti, 0.001 / Tnorm); Pilim = Tilim * Nelim; - + ////////////////////////////////////////////////////////////// // 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); } @@ -2303,18 +2318,18 @@ 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; } @@ -2330,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; } @@ -2366,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; } @@ -2423,20 +2438,22 @@ 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 (currents) { + nu.applyBoundary(t); + } if (ion_viscosity) { /////////////////////////////////////////////////////////// @@ -2449,7 +2466,9 @@ int Hermes::rhs(BoutReal t) { // not really parallel parts Field3D Tifree = copy(Ti); Tifree.applyBoundary("free_o2"); - if(fci_transform){mesh->communicate(Tifree,Pilim,Tilim,coord->Bxy);} + 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); @@ -2462,57 +2481,51 @@ int Hermes::rhs(BoutReal t) { // 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); + mesh->communicate(phi161Ti, Pi); // Perpendicular part from curvature Pi_ciperp = - -0.5 * 0.96 * Pi * tau_i * - (fci_curvature(phi161Ti) - - fci_curvature(Pi) / Nelim); // q perpendicular - // q parallel + -0.5 * 0.96 * Pi * tau_i + * (fci_curvature(phi161Ti) - fci_curvature(Pi) / Nelim); // q perpendicular + // q parallel - if(fci_transform){ + 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{ + 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); + 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); + 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(logB)); + 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; } } } @@ -2530,9 +2543,9 @@ int Hermes::rhs(BoutReal t) { Pi_cipar[i] = SIGN(Pi_cipar[i]) * Pi[i]; } } - + mesh->communicateXZ(Pi_ciperp, Pi_cipar); - if(!fci_transform){ + if (!fci_transform) { Pi_ciperp.clearParallelSlices(); Pi_cipar.clearParallelSlices(); Pi_ciperp = toFieldAligned(Pi_ciperp); @@ -2542,32 +2555,33 @@ int Hermes::rhs(BoutReal t) { { 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){ + if (!fci_transform) { 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"); @@ -2577,13 +2591,15 @@ int Hermes::rhs(BoutReal t) { /////////////////////////////////////////////////////////// // Relaxation potential - // - if (relaxation){ + // + if (relaxation) { TRACE("relaxation"); - Field3D inv_b2 = 1/ ( coord->Bxy * coord->Bxy ); + 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 ); - + 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); } /////////////////////////////////////////////////////////// @@ -2595,26 +2611,27 @@ int Hermes::rhs(BoutReal t) { // 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) * bracket_factor; // ExB drift + true) + * bracket_factor; // ExB drift } else { ddt(Ne) = 0.0; } // Parallel flow if (parallel_flow) { - if (!fci_transform){ + 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); + // 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{ - Field3D neve = mul_all(Ne,Ve); + } else { + Field3D neve = mul_all(Ne, Ve); mesh->communicate(neve); - // mesh->getParallelTransform().integrateParallelSlices(neve); + // mesh->getParallelTransform().integrateParallelSlices(neve); ddt(Ne) -= Div_parP(neve); // b = Div_parP(neve); @@ -2631,18 +2648,17 @@ int Hermes::rhs(BoutReal t) { // - 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 - if (!fci_transform){ + 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)) { @@ -2653,7 +2669,7 @@ int Hermes::rhs(BoutReal t) { // Classical perpendicular diffusion // The only term here comes from the resistive drift Dn = (Telim + Tilim) / (tau_e * mi_me * SQ(coord->Bxy)); - if(fci_transform){ + if (fci_transform) { mesh->communicate(Dn); Field3D Ne_tauB2 = Ne / (tau_e * mi_me * SQ(coord->Bxy)); Field3D TiTediff = Ti - 0.5 * Te; @@ -2681,15 +2697,15 @@ int Hermes::rhs(BoutReal t) { continue; // Not periodic, so skip for (int y = mesh->ystart; y <= mesh->yend; y++) { - 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; - } + 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; + } } } } @@ -2707,18 +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 - if(fci_transform){ + if (fci_transform) { mesh->communicate(Ne); ddt(Ne) += Div_par_K_Grad_par(SQ(coord->dy) * coord->g_22 * 1e-4 / Nelim, Ne); - }else{ + } 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); @@ -2736,10 +2752,10 @@ int Hermes::rhs(BoutReal t) { 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()]); + for (auto& i : Ne.getRegion("RGN_NOBNDRY")) { + ddt(Ne)[i] += numdiff * (Ne.ydown()[i.ym()] - 2. * Ne[i] + Ne.yup()[i.yp()]); } } /////////////////////////////////////////////////////////// @@ -2757,11 +2773,11 @@ int Hermes::rhs(BoutReal t) { 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 mesh->communicate(Jpar); - // mesh->getParallelTransform().integrateParallelSlices(Jpar); + // mesh->getParallelTransform().integrateParallelSlices(Jpar); ddt(Vort) += Div_parP(Jpar); a = Div_parP(Jpar); @@ -2772,11 +2788,11 @@ int Hermes::rhs(BoutReal t) { // Note: This term is central differencing so that it balances // the corresponding compression term in the pressure equation - if(!fci_transform){ - ddt(Vort) += Div((Pi + Pe) * Curlb_B); - }else{ + if (!fci_transform) { + ddt(Vort) += Div((Pi + Pe) * Curlb_B); + } else { ddt(Vort) += fci_curvature(Pi + Pe); - b = fci_curvature(Pi + Pe); + b = fci_curvature(Pi + Pe); } } @@ -2784,45 +2800,46 @@ int Hermes::rhs(BoutReal t) { 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); - } - - // 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; - } - } + 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); + } + // 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 { // When the Boussinesq approximation is not made, // then the changing ion density introduces a number @@ -2834,11 +2851,13 @@ int Hermes::rhs(BoutReal t) { if (classical_diffusion) { TRACE("Vort:classical_diffusion"); // Perpendicular viscosity - Field3D tilim_3 = 0.3*Tilim; + 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);} + 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); } @@ -2852,8 +2871,8 @@ int Hermes::rhs(BoutReal t) { // } // 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); + 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) { @@ -2861,12 +2880,12 @@ int Hermes::rhs(BoutReal t) { // Perpendicular anomalous momentum diffusion 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); @@ -2881,34 +2900,34 @@ int Hermes::rhs(BoutReal t) { } if (vort_dissipation) { - 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]; - } - - // 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 (!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]; + } + + // 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) -= SQ(coord->dy)*D2DY2(phi); - f = SQ(coord->dy)*D2DY2(phi); + // 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); } } @@ -2925,36 +2944,44 @@ int Hermes::rhs(BoutReal t) { // Evolve VePsi except for electrostatic and zero electron mass case if (resistivity) { - ddt(VePsi) -= mi_me * nu * sub_all(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);} + if (fci_transform) { + mesh->communicate(phi); + } ddt(VePsi) += mi_me * Grad_parP(phi); } // parallel electron pressure if (pe_par) { - if(fci_transform){mesh->communicate(Pelim);} + if (fci_transform) { + mesh->communicate(Pelim); + } ddt(VePsi) -= mi_me * Grad_parP(Pelim) / NelimVe; } - + if (thermal_force) { - if(fci_transform){mesh->communicate(Te);} + 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);} + 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 @@ -2963,35 +2990,35 @@ int Hermes::rhs(BoutReal t) { mesh->communicate(ve_eta); ve_eta.applyBoundary("neumann"); } - 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 (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 - 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 + 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); + Field3D vdiff = sub_all(Ve, Vi); mesh->communicate(vdiff); - ddt(VePsi) -= bracket(phi, vdiff, BRACKET_ARAKAWA)*bracket_factor; // ExB advection + 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) { @@ -3009,7 +3036,7 @@ int Hermes::rhs(BoutReal t) { 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 // Maximum speed either electron sound speed or Alfven speed @@ -3042,16 +3069,17 @@ int Hermes::rhs(BoutReal t) { TRACE("Ion velocity"); if (currents) { - 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 + 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; @@ -3059,56 +3087,56 @@ int Hermes::rhs(BoutReal t) { if (j_diamag) { // Magnetic drift - if(!fci_transform){ - ddt(NVi) -= FV::Div_f_v(NVi, Tilim * Curlb_B, - ne_bndry_flux); // Grad-B, curvature drift - }else{ + 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){ + if (!fci_transform) { ddt(NVi) -= FV::Div_par_fvv(Ne, Vi, sound_speed, false); - }else{ + } else { Field3D nvivi = mul_all(NVi, Vi); mesh->communicate(nvivi); - // mesh->getParallelTransform().integrateParallelSlices(nvivi); + // mesh->getParallelTransform().integrateParallelSlices(nvivi); 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)); + // ddt(NVi) -= 0.5 * (Div_par(mul_all(NVi, Vi)) + Vi * Grad_par(NVi) + NVi * + // Div_par(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 (!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 (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 - Field3D Piciperp_B32 = Pi_ciperp/B32; - mesh->communicate(Piciperp_B32); + Field3D Piciperp_B32 = Pi_ciperp / B32; + mesh->communicate(Piciperp_B32); ddt(NVi) -= (2. / 3) * B32 * Grad_par(Piciperp_B32); } } @@ -3117,13 +3145,12 @@ int Hermes::rhs(BoutReal t) { if (ion_neutral_rate > 0.0) { ddt(NVi) -= ion_neutral_rate * NVi; } - + if (numdiff > 0.0) { - for(auto &i : NVi.getRegion("RGN_ALL")) { - ddt(NVi)[i] += numdiff*(Vi.ydown()[i.ym()] - 2.*Vi[i] + Vi.yup()[i.yp()]); + 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) { @@ -3136,7 +3163,7 @@ int Hermes::rhs(BoutReal t) { if (classical_diffusion) { // Using same cross-field drift as in density equation - Field3D ViDn = mul_all(Vi,Dn); + 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)); @@ -3146,13 +3173,13 @@ int Hermes::rhs(BoutReal t) { } if ((anomalous_D > 0.0) && anomalous_D_nvi) { - ddt(NVi) += FV::Div_a_Laplace_perp(mul_all(Vi , a_d3d), 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(mul_all(Ne , a_nu3d), 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; } @@ -3160,12 +3187,13 @@ 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 - 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 (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); @@ -3184,12 +3212,13 @@ int Hermes::rhs(BoutReal t) { if (evolve_te) { if (currents) { - 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); + 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) = 0.0; @@ -3197,18 +3226,18 @@ int Hermes::rhs(BoutReal t) { 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); + 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); + ddt(Pe) += (2. / 3) * Ve * Grad_par(Pe); - // mesh->getParallelTransform().integrateParallelSlices(peve); + // mesh->getParallelTransform().integrateParallelSlices(peve); - // ddt(Pe) -= Div_parP(peve); - } else { - if (currents) { + // 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); @@ -3243,9 +3272,9 @@ int Hermes::rhs(BoutReal t) { if (thermal_flux) { // Parallel heat convection if (fci_transform) { - Field3D tejpar = mul_all(Te,Jpar); + Field3D tejpar = mul_all(Te, Jpar); mesh->communicate(tejpar); - // mesh->getParallelTransform().integrateParallelSlices(tejpar); + // mesh->getParallelTransform().integrateParallelSlices(tejpar); ddt(Pe) += (2. / 3) * 0.71 * Div_parP(tejpar); } else { @@ -3360,101 +3389,96 @@ int Hermes::rhs(BoutReal t) { } } } - if (parallel_sheaths){ + 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; - } - } + 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; } - // Transfer and source terms if (thermal_force) { if (fci_transform) { @@ -3468,7 +3492,7 @@ int Hermes::rhs(BoutReal t) { // in Ohm's law if (fci_transform) { mesh->communicate(Ve); - // mesh->getParallelTransform().integrateParallelSlices(Ve); + // mesh->getParallelTransform().integrateParallelSlices(Ve); } ddt(Pe) -= (2. / 3) * Pelim * Div_parP(Ve); } @@ -3487,20 +3511,20 @@ int Hermes::rhs(BoutReal t) { Field3D PePi = add_all(Pe, Pi); mesh->communicate(nu_rho2); Field3D nu_rho2Ne = mul_all(nu_rho2, Ne); - mesh->communicate(nu_rho2Ne,Te); + 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)); + * (FV::Div_a_Laplace_perp(nu_rho2, PePi) + + (11. / 12) * FV::Div_a_Laplace_perp(nu_rho2Ne, Te)); } ////////////////////// // Anomalous diffusion if ((anomalous_D > 0.0) && anomalous_D_pepi) { - ddt(Pe) += FV::Div_a_Laplace_perp(mul_all(a_d3d , Te), Ne); + 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(mul_all(a_chi3d, Ne), Te); } ////////////////////// @@ -3519,15 +3543,15 @@ int Hermes::rhs(BoutReal t) { continue; // Not periodic, so skip 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 (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; - } + 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 (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; + } } } } @@ -3576,21 +3600,22 @@ int Hermes::rhs(BoutReal t) { } else { ddt(Pe) = 0.0; } - + /////////////////////////////////////////////////////////// // Ion pressure equation // Similar to electron pressure equation TRACE("Ion pressure"); - + if (evolve_ti) { 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); + 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; @@ -3599,12 +3624,12 @@ int Hermes::rhs(BoutReal t) { // Parallel flow if (parallel_flow_p_term) { if (fci_transform) { - Field3D pivi = mul_all(Pi,Vi); + Field3D pivi = mul_all(Pi, Vi); mesh->communicate(pivi); - // mesh->getParallelTransform().integrateParallelSlices(pivi); + // mesh->getParallelTransform().integrateParallelSlices(pivi); ddt(Pi) -= (5. / 3) * Div_parP(pivi); - ddt(Pi) += (2. / 3) * Vi * Grad_par(Pi); + ddt(Pi) += (2. / 3) * Vi * Grad_par(Pi); } else { ddt(Pi) -= FV::Div_par(Pi, Vi, sound_speed); } @@ -3660,7 +3685,7 @@ int Hermes::rhs(BoutReal t) { // in the parallel momentum equation if (fci_transform) { mesh->communicate(Vi); - // mesh->getParallelTransform().integrateParallelSlices(Vi); + // mesh->getParallelTransform().integrateParallelSlices(Vi); } ddt(Pi) -= (2. / 3) * Pilim * Div_parP(Vi); } @@ -3680,17 +3705,16 @@ int Hermes::rhs(BoutReal t) { // 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); + 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 PePi = add_all(Pe, Pi); + mesh->communicate(nu_rho2, PePi); Field3D nu_rho2Ne = mul_all(nu_rho2, Ne); - mesh->communicate(nu_rho2Ne,Te); + 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)); @@ -3700,8 +3724,8 @@ int Hermes::rhs(BoutReal t) { if (currents) { Vector3D Grad_perp_vort = Grad(Vort); - Field3D phiPi = phi+Pi; - mesh->communicate(phiPi); + 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)); @@ -3710,17 +3734,17 @@ int Hermes::rhs(BoutReal t) { if (ion_viscosity) { // Collisional heating due to parallel viscosity - Field3D sqrtBVi = mul_all(sqrtB,Vi); - mesh->communicate(sqrtBVi,Vi); + 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); 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); + 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; } } @@ -3729,11 +3753,11 @@ int Hermes::rhs(BoutReal t) { // Anomalous diffusion if ((anomalous_D > 0.0) && anomalous_D_pepi) { - ddt(Pi) += FV::Div_a_Laplace_perp(mul_all(a_d3d , Ti), Ne); + ddt(Pi) += FV::Div_a_Laplace_perp(mul_all(a_d3d, Ti), Ne); } if (anomalous_chi > 0.0) { - ddt(Pi) += (2. / 3) * FV::Div_a_Laplace_perp(mul_all(a_chi3d , Ne), Ti); + ddt(Pi) += (2. / 3) * FV::Div_a_Laplace_perp(mul_all(a_chi3d, Ne), Ti); } /////////////////////////////////// @@ -3833,101 +3857,97 @@ int Hermes::rhs(BoutReal t) { } } - if (parallel_sheaths){ + 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; - - } - } + 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; + } + } } ddt(Pi) += sheath_dpi; } @@ -3948,15 +3968,15 @@ int Hermes::rhs(BoutReal t) { continue; // Not periodic, so skip 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 (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; - } + 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 (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; + } } } } @@ -4135,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 @@ -4147,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); } } @@ -4168,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, 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))); + 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, jz) * - coord->dy(r.ind, mesh->ystart, jz)); + 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 @@ -4210,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, 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))); + 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, jz) * coord->dy(r.ind, mesh->yend, jz)); + flow / (coord->J(r.ind, mesh->yend, jz) * coord->dy(r.ind, mesh->yend, jz)); // Add mass, momentum and energy to the neutrals @@ -4243,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, @@ -4253,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; } @@ -4321,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 { @@ -4408,65 +4425,68 @@ int Hermes::precon(BoutReal t, BoutReal gamma, BoutReal delta) { return 0; } -const Field3D Hermes::fci_curvature(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) { +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) { +const Field3D Hermes::Div_parP(const Field3D& f) { auto* coords = mesh->getCoordinates(); Field3D result; result.allocate(); - for(auto &i : f.getRegion("RGN_NOBNDRY")) { + 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])); + 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); + // 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) { +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 ® : 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 (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); + 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")) { + + 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; } From 4d77c6690feca6d6c2df99880a131d854f4b99e7 Mon Sep 17 00:00:00 2001 From: phuslage Date: Thu, 26 Aug 2021 21:54:52 +0200 Subject: [PATCH 4/4] Fix formatting --- hermes-2.hxx | 279 ++++++++++++++++++++++++++------------------------- 1 file changed, 140 insertions(+), 139 deletions(-) diff --git a/hermes-2.hxx b/hermes-2.hxx index ad0e503..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,31 +53,30 @@ 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 - - //stuff for Pauls thesis (relaxation and geometry) - //Field3D phi_1; // Auxilary potential for relaxation method + 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 - + Field3D term_phi; // Debugging // Limited variables Field3D Telim, Tilim; @@ -83,227 +84,227 @@ private: // 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 show_timesteps; // Show intermediate timesteps? - bool evolve_te; // Evolve electron temperature? - bool evolve_ti; // Evolve ion temperature? - bool evolve_vort; // Evolve vorticity? - + 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 - 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 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 staggered; // Use staggered differencing along B + bool ion_velocity; // Include Vi terms + + bool phi3d; // Use a 3D solver for phi - bool boussinesq; // Use a fixed density (Nnorm) in the vorticity equation + bool staggered; // Use staggered differencing along B - bool sinks; // Sink terms for running 2D drift-plane simulations + bool boussinesq; // Use a fixed density (Nnorm) in the vorticity equation + + 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 bool phi_smoothing; BoutReal phi_sf; - - BoutReal resistivity_boundary; // Value of nu in boundary layer + + BoutReal resistivity_boundary; // Value of nu in boundary layer int resistivity_boundary_width; // Width of radial boundary - + 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 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; - + 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 - + // 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 + 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 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 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 + + 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? - + Field3D NeSource, PeSource, PiSource; // Actual sources added + bool density_inflow; // Does incoming density have momentum? + bool source_vary_g11; // Multiply source by g11 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, Te0, Ti0, Nnorm, Bnorm; BoutReal AA, Cs0, rho_s0, Omega_ci; 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 Div_par_integrate(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; + 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 - + 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? + 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) + 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; + + bool newXZsolver; std::unique_ptr phiSolver{nullptr}; // Old Laplacian in X-Z std::unique_ptr newSolver{nullptr}; // New Laplacian in X-Z - // Mesh quantities Coordinates::FieldMetric B32, sqrtB; bool fci_transform; Field3D Bxyz, logB, B_SQ; Field3D bracket_factor; - const Field3D fci_curvature(const Field3D &f); + const Field3D fci_curvature(const Field3D& f); - Field3D a,b,c,d,f; //Debugging variables + 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__